summaryrefslogtreecommitdiffstats
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/android/SCsub31
-rw-r--r--platform/android/audio_driver_opensl.cpp35
-rw-r--r--platform/android/audio_driver_opensl.h18
-rw-r--r--platform/android/detect.py2
-rw-r--r--platform/android/display_server_android.cpp1
-rw-r--r--platform/android/export/export_plugin.cpp17
-rw-r--r--platform/android/java/app/build.gradle68
-rw-r--r--platform/android/java/app/config.gradle16
-rw-r--r--platform/android/java/build.gradle234
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt21
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt232
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt6
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java11
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java16
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotLib.java7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java15
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java20
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/gl/GodotRenderer.java10
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java198
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/InputEventRunnable.java353
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileData.kt2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt15
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt6
-rw-r--r--platform/android/java_godot_lib_jni.cpp28
-rw-r--r--platform/android/java_godot_lib_jni.h2
-rw-r--r--platform/android/java_godot_wrapper.cpp32
-rw-r--r--platform/android/java_godot_wrapper.h2
-rw-r--r--platform/ios/detect.py17
-rw-r--r--platform/ios/display_server_ios.h4
-rw-r--r--platform/ios/display_server_ios.mm26
-rw-r--r--platform/ios/doc_classes/EditorExportPlatformIOS.xml6
-rw-r--r--platform/ios/export/export_plugin.cpp11
-rw-r--r--platform/ios/godot_app_delegate.h2
-rw-r--r--platform/ios/godot_app_delegate.m4
-rw-r--r--platform/ios/godot_view.mm6
-rw-r--r--platform/ios/keyboard_input_view.mm30
-rw-r--r--platform/ios/main.m2
-rw-r--r--platform/linuxbsd/detect.py2
-rw-r--r--platform/linuxbsd/export/export_plugin.cpp100
-rw-r--r--platform/linuxbsd/export/export_plugin.h2
-rw-r--r--platform/linuxbsd/joypad_linux.cpp6
-rw-r--r--platform/linuxbsd/joypad_linux.h15
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.cpp2
-rw-r--r--platform/linuxbsd/wayland/wayland_thread.cpp77
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp37
-rw-r--r--platform/linuxbsd/x11/display_server_x11.h2
-rw-r--r--platform/macos/SCsub12
-rw-r--r--platform/macos/detect.py24
-rw-r--r--platform/macos/display_server_macos.h5
-rw-r--r--platform/macos/display_server_macos.mm46
-rw-r--r--platform/macos/doc_classes/EditorExportPlatformMacOS.xml6
-rw-r--r--platform/macos/export/export_plugin.cpp78
-rw-r--r--platform/macos/export/export_plugin.h8
-rw-r--r--platform/macos/gl_manager_macos_legacy.h1
-rw-r--r--platform/macos/gl_manager_macos_legacy.mm20
-rw-r--r--platform/macos/godot_content_view.mm3
-rw-r--r--platform/macos/godot_menu_item.h1
-rw-r--r--platform/macos/godot_menu_item.mm14
-rw-r--r--platform/macos/joypad_macos.mm5
-rw-r--r--platform/macos/native_menu_macos.mm37
-rw-r--r--platform/macos/os_macos.h1
-rw-r--r--platform/macos/os_macos.mm9
-rw-r--r--platform/web/audio_driver_web.cpp25
-rw-r--r--platform/web/audio_driver_web.h2
-rw-r--r--platform/web/detect.py6
-rw-r--r--platform/web/display_server_web.cpp6
-rw-r--r--platform/web/emscripten_helpers.py3
-rw-r--r--platform/web/export/export_plugin.cpp2
-rw-r--r--platform/web/godot_audio.h2
-rw-r--r--platform/web/js/engine/config.js2
-rw-r--r--platform/web/js/libs/audio.position.worklet.js50
-rw-r--r--platform/web/js/libs/library_godot_audio.js166
-rw-r--r--platform/web/js/libs/library_godot_input.js1
-rwxr-xr-xplatform/web/serve.py20
-rw-r--r--platform/windows/SCsub12
-rw-r--r--platform/windows/detect.py19
-rw-r--r--platform/windows/display_server_windows.cpp782
-rw-r--r--platform/windows/display_server_windows.h114
-rw-r--r--platform/windows/doc_classes/EditorExportPlatformWindows.xml2
-rw-r--r--platform/windows/export/export_plugin.cpp76
-rw-r--r--platform/windows/export/export_plugin.h2
-rw-r--r--platform/windows/gl_manager_windows_angle.cpp5
-rw-r--r--platform/windows/gl_manager_windows_angle.h2
-rw-r--r--platform/windows/gl_manager_windows_native.cpp43
-rw-r--r--platform/windows/gl_manager_windows_native.h2
-rw-r--r--platform/windows/native_menu_windows.cpp37
-rw-r--r--platform/windows/native_menu_windows.h1
-rw-r--r--platform/windows/os_windows.cpp50
94 files changed, 2467 insertions, 1014 deletions
diff --git a/platform/android/SCsub b/platform/android/SCsub
index bc1b5e9200..8c88b419b3 100644
--- a/platform/android/SCsub
+++ b/platform/android/SCsub
@@ -95,25 +95,18 @@ if lib_arch_dir != "":
else:
gradle_process = ["./gradlew"]
- if env["target"] != "editor" and env["dev_build"]:
- subprocess.run(
- gradle_process
- + [
- "generateDevTemplate",
- "--quiet",
- ],
- cwd="platform/android/java",
- )
- else:
- # Android editor with `dev_build=yes` is handled by the `generateGodotEditor` task.
- subprocess.run(
- gradle_process
- + [
- "generateGodotEditor" if env["target"] == "editor" else "generateGodotTemplates",
- "--quiet",
- ],
- cwd="platform/android/java",
- )
+ gradle_process += [
+ "generateGodotEditor" if env["target"] == "editor" else "generateGodotTemplates",
+ "--quiet",
+ ]
+
+ if env["debug_symbols"]:
+ gradle_process += ["-PdoNotStrip=true"]
+
+ subprocess.run(
+ gradle_process,
+ cwd="platform/android/java",
+ )
if env["generate_apk"]:
generate_apk_command = env_android.Command("generate_apk", [], generate_apk)
diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp
index 51e89c720d..ef9c51db07 100644
--- a/platform/android/audio_driver_opensl.cpp
+++ b/platform/android/audio_driver_opensl.cpp
@@ -268,6 +268,10 @@ Error AudioDriverOpenSL::init_input_device() {
}
Error AudioDriverOpenSL::input_start() {
+ if (recordItf || recordBufferQueueItf) {
+ return ERR_ALREADY_IN_USE;
+ }
+
if (OS::get_singleton()->request_permission("RECORD_AUDIO")) {
return init_input_device();
}
@@ -277,6 +281,10 @@ Error AudioDriverOpenSL::input_start() {
}
Error AudioDriverOpenSL::input_stop() {
+ if (!recordItf || !recordBufferQueueItf) {
+ return ERR_CANT_OPEN;
+ }
+
SLuint32 state;
SLresult res = (*recordItf)->GetRecordState(recordItf, &state);
ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN);
@@ -313,13 +321,36 @@ void AudioDriverOpenSL::unlock() {
}
void AudioDriverOpenSL::finish() {
- (*sl)->Destroy(sl);
+ if (recordItf) {
+ (*recordItf)->SetRecordState(recordItf, SL_RECORDSTATE_STOPPED);
+ recordItf = nullptr;
+ }
+ if (recorder) {
+ (*recorder)->Destroy(recorder);
+ recorder = nullptr;
+ }
+ if (playItf) {
+ (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_STOPPED);
+ playItf = nullptr;
+ }
+ if (player) {
+ (*player)->Destroy(player);
+ player = nullptr;
+ }
+ if (OutputMix) {
+ (*OutputMix)->Destroy(OutputMix);
+ OutputMix = nullptr;
+ }
+ if (sl) {
+ (*sl)->Destroy(sl);
+ sl = nullptr;
+ }
}
void AudioDriverOpenSL::set_pause(bool p_pause) {
pause = p_pause;
- if (active) {
+ if (active && playItf) {
if (pause) {
(*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PAUSED);
} else {
diff --git a/platform/android/audio_driver_opensl.h b/platform/android/audio_driver_opensl.h
index 6ea0f77def..bcd173826a 100644
--- a/platform/android/audio_driver_opensl.h
+++ b/platform/android/audio_driver_opensl.h
@@ -54,15 +54,15 @@ class AudioDriverOpenSL : public AudioDriver {
Vector<int16_t> rec_buffer;
- SLPlayItf playItf;
- SLRecordItf recordItf;
- SLObjectItf sl;
- SLEngineItf EngineItf;
- SLObjectItf OutputMix;
- SLObjectItf player;
- SLObjectItf recorder;
- SLAndroidSimpleBufferQueueItf bufferQueueItf;
- SLAndroidSimpleBufferQueueItf recordBufferQueueItf;
+ SLPlayItf playItf = nullptr;
+ SLRecordItf recordItf = nullptr;
+ SLObjectItf sl = nullptr;
+ SLEngineItf EngineItf = nullptr;
+ SLObjectItf OutputMix = nullptr;
+ SLObjectItf player = nullptr;
+ SLObjectItf recorder = nullptr;
+ SLAndroidSimpleBufferQueueItf bufferQueueItf = nullptr;
+ SLAndroidSimpleBufferQueueItf recordBufferQueueItf = nullptr;
SLDataSource audioSource;
SLDataFormat_PCM pcm;
SLDataSink audioSink;
diff --git a/platform/android/detect.py b/platform/android/detect.py
index 0b182aca90..0a10754e24 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -190,6 +190,8 @@ def configure(env: "SConsEnvironment"):
env.Append(CCFLAGS=["-mfix-cortex-a53-835769"])
env.Append(CPPDEFINES=["__ARM_ARCH_8A__"])
+ env.Append(CCFLAGS=["-ffp-contract=off"])
+
# Link flags
env.Append(LINKFLAGS="-Wl,--gc-sections -Wl,--no-undefined -Wl,-z,now".split())
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 06b304dcde..8dc0e869d0 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -651,7 +651,6 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis
#endif
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
- Input::get_singleton()->set_use_input_buffering(true); // Needed because events will come directly from the UI thread
r_error = OK;
}
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 479158c91f..689360aef6 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -441,6 +441,7 @@ void EditorExportPlatformAndroid::_update_preset_status() {
} else {
has_runnable_preset.clear();
}
+ devices_changed.set();
}
#endif
@@ -2322,7 +2323,8 @@ static bool has_valid_keystore_credentials(String &r_error_str, const String &p_
args.push_back(p_password);
args.push_back("-alias");
args.push_back(p_username);
- Error error = OS::get_singleton()->execute("keytool", args, &output, nullptr, true);
+ String keytool_path = EditorExportPlatformAndroid::get_keytool_path();
+ Error error = OS::get_singleton()->execute(keytool_path, args, &output, nullptr, true);
String keytool_error = "keytool error:";
bool valid = output.substr(0, keytool_error.length()) != keytool_error;
@@ -3268,18 +3270,17 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
}
List<String> copy_args;
- String copy_command;
- if (export_format == EXPORT_FORMAT_AAB) {
- copy_command = vformat("copyAndRename%sAab", build_type);
- } else if (export_format == EXPORT_FORMAT_APK) {
- copy_command = vformat("copyAndRename%sApk", build_type);
- }
-
+ String copy_command = "copyAndRenameBinary";
copy_args.push_back(copy_command);
copy_args.push_back("-p"); // argument to specify the start directory.
copy_args.push_back(build_path); // start directory.
+ copy_args.push_back("-Pexport_build_type=" + build_type.to_lower());
+
+ String export_format_arg = export_format == EXPORT_FORMAT_AAB ? "aab" : "apk";
+ copy_args.push_back("-Pexport_format=" + export_format_arg);
+
String export_filename = p_path.get_file();
String export_path = p_path.get_base_dir();
if (export_path.is_relative_path()) {
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 01d5d9ef92..05b4f379b3 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -211,70 +211,24 @@ android {
}
}
-task copyAndRenameDebugApk(type: Copy) {
+task copyAndRenameBinary(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()
-}
+ String exportPath = getExportPath()
+ String exportFilename = getExportFilename()
+ String exportBuildType = getExportBuildType()
+ String exportFormat = getExportFormat()
-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")
+ boolean isAab = exportFormat == "aab"
+ String sourceFilepath = isAab ? "$buildDir/outputs/bundle/$exportBuildType/build-${exportBuildType}.aab" : "$buildDir/outputs/apk/$exportBuildType/android_${exportBuildType}.apk"
+ String sourceFilename = isAab ? "build-${exportBuildType}.aab" : "android_${exportBuildType}.apk"
- from "$buildDir/outputs/bundle/release/build-release.aab"
- into getExportPath()
- rename "build-release.aab", getExportFilename()
+ from sourceFilepath
+ into exportPath
+ rename sourceFilename, exportFilename
}
/**
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index eb9ad9de05..611a9c4a40 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -224,6 +224,22 @@ ext.getExportFilename = {
return exportFilename
}
+ext.getExportBuildType = {
+ String exportBuildType = project.hasProperty("export_build_type") ? project.property("export_build_type") : ""
+ if (exportBuildType == null || exportBuildType.isEmpty()) {
+ exportBuildType = "debug"
+ }
+ return exportBuildType
+}
+
+ext.getExportFormat = {
+ String exportFormat = project.hasProperty("export_format") ? project.property("export_format") : ""
+ if (exportFormat == null || exportFormat.isEmpty()) {
+ exportFormat = "apk"
+ }
+ return exportFormat
+}
+
/**
* Parse the project properties for the 'plugins_maven_repos' property and return the list
* of maven repos.
diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle
index b91b023ce6..771bda6948 100644
--- a/platform/android/java/build.gradle
+++ b/platform/android/java/build.gradle
@@ -35,116 +35,17 @@ ext {
// `./gradlew generateGodotTemplates` build command instead after running the `scons` command(s).
// The {selectedAbis} values must be from the {supportedAbis} values.
selectedAbis = ["arm64"]
-}
-def rootDir = "../../.."
-def binDir = "$rootDir/bin/"
-def androidEditorBuildsDir = "$binDir/android_editor_builds/"
+ rootDir = "../../.."
+ binDir = "$rootDir/bin/"
+ androidEditorBuildsDir = "$binDir/android_editor_builds/"
+}
def getSconsTaskName(String flavor, String buildType, String abi) {
return "compileGodotNativeLibs" + flavor.capitalize() + buildType.capitalize() + abi.capitalize()
}
/**
- * Copy the generated 'android_debug.apk' binary template into the Godot bin directory.
- * Depends on the app build task to ensure the binary is generated prior to copying.
- */
-task copyDebugBinaryToBin(type: Copy) {
- dependsOn ':app:assembleDebug'
- from('app/build/outputs/apk/debug')
- into(binDir)
- include('android_debug.apk')
-}
-
-/**
- * Copy the generated 'android_dev.apk' binary template into the Godot bin directory.
- * Depends on the app build task to ensure the binary is generated prior to copying.
- */
-task copyDevBinaryToBin(type: Copy) {
- dependsOn ':app:assembleDev'
- from('app/build/outputs/apk/dev')
- into(binDir)
- include('android_dev.apk')
-}
-
-/**
- * Copy the generated 'android_release.apk' binary template into the Godot bin directory.
- * Depends on the app build task to ensure the binary is generated prior to copying.
- */
-task copyReleaseBinaryToBin(type: Copy) {
- dependsOn ':app:assembleRelease'
- from('app/build/outputs/apk/release')
- into(binDir)
- include('android_release.apk')
-}
-
-/**
- * Copy the Godot android library archive debug file into the app module debug libs directory.
- * Depends on the library build task to ensure the AAR file is generated prior to copying.
- */
-task copyDebugAARToAppModule(type: Copy) {
- dependsOn ':lib:assembleTemplateDebug'
- from('lib/build/outputs/aar')
- into('app/libs/debug')
- include('godot-lib.template_debug.aar')
-}
-
-/**
- * Copy the Godot android library archive debug file into the root bin directory.
- * Depends on the library build task to ensure the AAR file is generated prior to copying.
- */
-task copyDebugAARToBin(type: Copy) {
- dependsOn ':lib:assembleTemplateDebug'
- from('lib/build/outputs/aar')
- into(binDir)
- include('godot-lib.template_debug.aar')
-}
-
-/**
- * Copy the Godot android library archive dev file into the app module dev libs directory.
- * Depends on the library build task to ensure the AAR file is generated prior to copying.
- */
-task copyDevAARToAppModule(type: Copy) {
- dependsOn ':lib:assembleTemplateDev'
- from('lib/build/outputs/aar')
- into('app/libs/dev')
- include('godot-lib.template_debug.dev.aar')
-}
-
-/**
- * Copy the Godot android library archive dev file into the root bin directory.
- * Depends on the library build task to ensure the AAR file is generated prior to copying.
- */
-task copyDevAARToBin(type: Copy) {
- dependsOn ':lib:assembleTemplateDev'
- from('lib/build/outputs/aar')
- into(binDir)
- include('godot-lib.template_debug.dev.aar')
-}
-
-/**
- * Copy the Godot android library archive release file into the app module release libs directory.
- * Depends on the library build task to ensure the AAR file is generated prior to copying.
- */
-task copyReleaseAARToAppModule(type: Copy) {
- dependsOn ':lib:assembleTemplateRelease'
- from('lib/build/outputs/aar')
- into('app/libs/release')
- include('godot-lib.template_release.aar')
-}
-
-/**
- * Copy the Godot android library archive release file into the root bin directory.
- * Depends on the library build task to ensure the AAR file is generated prior to copying.
- */
-task copyReleaseAARToBin(type: Copy) {
- dependsOn ':lib:assembleTemplateRelease'
- from('lib/build/outputs/aar')
- into(binDir)
- include('godot-lib.template_release.aar')
-}
-
-/**
* Generate Godot gradle build template by zipping the source files from the app directory, as well
* as the AAR files generated by 'copyDebugAAR', 'copyDevAAR' and 'copyReleaseAAR'.
* The zip file also includes some gradle tools to enable gradle builds from the Godot Editor.
@@ -197,7 +98,7 @@ def generateBuildTasks(String flavor = "template") {
throw new GradleException("Invalid build flavor: $flavor")
}
- def tasks = []
+ def buildTasks = []
// Only build the apks and aar files for which we have native shared libraries unless we intend
// to run the scons build tasks.
@@ -206,72 +107,93 @@ def generateBuildTasks(String flavor = "template") {
String libsDir = isTemplate ? "lib/libs/" : "lib/libs/tools/"
for (String target : supportedFlavorsBuildTypes[flavor]) {
File targetLibs = new File(libsDir + target)
+
+ String targetSuffix = target
+ if (target == "dev") {
+ targetSuffix = "debug.dev"
+ }
+
if (!excludeSconsBuildTasks || (targetLibs != null
&& targetLibs.isDirectory()
&& targetLibs.listFiles() != null
&& targetLibs.listFiles().length > 0)) {
+
String capitalizedTarget = target.capitalize()
if (isTemplate) {
- // Copy the generated aar library files to the build directory.
- tasks += "copy${capitalizedTarget}AARToAppModule"
- // Copy the generated aar library files to the bin directory.
- tasks += "copy${capitalizedTarget}AARToBin"
- // Copy the prebuilt binary templates to the bin directory.
- tasks += "copy${capitalizedTarget}BinaryToBin"
+ // Copy the Godot android library archive file into the app module libs directory.
+ // Depends on the library build task to ensure the AAR file is generated prior to copying.
+ String copyAARTaskName = "copy${capitalizedTarget}AARToAppModule"
+ if (tasks.findByName(copyAARTaskName) != null) {
+ buildTasks += tasks.getByName(copyAARTaskName)
+ } else {
+ buildTasks += tasks.create(name: copyAARTaskName, type: Copy) {
+ dependsOn ":lib:assembleTemplate${capitalizedTarget}"
+ from('lib/build/outputs/aar')
+ include("godot-lib.template_${targetSuffix}.aar")
+ into("app/libs/${target}")
+ }
+ }
+
+ // Copy the Godot android library archive file into the root bin directory.
+ // Depends on the library build task to ensure the AAR file is generated prior to copying.
+ String copyAARToBinTaskName = "copy${capitalizedTarget}AARToBin"
+ if (tasks.findByName(copyAARToBinTaskName) != null) {
+ buildTasks += tasks.getByName(copyAARToBinTaskName)
+ } else {
+ buildTasks += tasks.create(name: copyAARToBinTaskName, type: Copy) {
+ dependsOn ":lib:assembleTemplate${capitalizedTarget}"
+ from('lib/build/outputs/aar')
+ include("godot-lib.template_${targetSuffix}.aar")
+ into(binDir)
+ }
+ }
+
+ // Copy the generated binary template into the Godot bin directory.
+ // Depends on the app build task to ensure the binary is generated prior to copying.
+ String copyBinaryTaskName = "copy${capitalizedTarget}BinaryToBin"
+ if (tasks.findByName(copyBinaryTaskName) != null) {
+ buildTasks += tasks.getByName(copyBinaryTaskName)
+ } else {
+ buildTasks += tasks.create(name: copyBinaryTaskName, type: Copy) {
+ dependsOn ":app:assemble${capitalizedTarget}"
+ from("app/build/outputs/apk/${target}")
+ into(binDir)
+ include("android_${target}.apk")
+ }
+ }
} else {
// Copy the generated editor apk to the bin directory.
- tasks += "copyEditor${capitalizedTarget}ApkToBin"
+ String copyEditorApkTaskName = "copyEditor${capitalizedTarget}ApkToBin"
+ if (tasks.findByName(copyEditorApkTaskName) != null) {
+ buildTasks += tasks.getByName(copyEditorApkTaskName)
+ } else {
+ buildTasks += tasks.create(name: copyEditorApkTaskName, type: Copy) {
+ dependsOn ":editor:assemble${capitalizedTarget}"
+ from("editor/build/outputs/apk/${target}")
+ into(androidEditorBuildsDir)
+ include("android_editor-${target}*.apk")
+ }
+ }
+
// Copy the generated editor aab to the bin directory.
- tasks += "copyEditor${capitalizedTarget}AabToBin"
+ String copyEditorAabTaskName = "copyEditor${capitalizedTarget}AabToBin"
+ if (tasks.findByName(copyEditorAabTaskName) != null) {
+ buildTasks += tasks.getByName(copyEditorAabTaskName)
+ } else {
+ buildTasks += tasks.create(name: copyEditorAabTaskName, type: Copy) {
+ dependsOn ":editor:bundle${capitalizedTarget}"
+ from("editor/build/outputs/bundle/${target}")
+ into(androidEditorBuildsDir)
+ include("android_editor-${target}*.aab")
+ }
+ }
}
} else {
logger.lifecycle("No native shared libs for target $target. Skipping build.")
}
}
- return tasks
-}
-
-task copyEditorReleaseApkToBin(type: Copy) {
- dependsOn ':editor:assembleRelease'
- from('editor/build/outputs/apk/release')
- into(androidEditorBuildsDir)
- include('android_editor-release*.apk')
-}
-
-task copyEditorReleaseAabToBin(type: Copy) {
- dependsOn ':editor:bundleRelease'
- from('editor/build/outputs/bundle/release')
- into(androidEditorBuildsDir)
- include('android_editor-release*.aab')
-}
-
-task copyEditorDebugApkToBin(type: Copy) {
- dependsOn ':editor:assembleDebug'
- from('editor/build/outputs/apk/debug')
- into(androidEditorBuildsDir)
- include('android_editor-debug.apk')
-}
-
-task copyEditorDebugAabToBin(type: Copy) {
- dependsOn ':editor:bundleDebug'
- from('editor/build/outputs/bundle/debug')
- into(androidEditorBuildsDir)
- include('android_editor-debug.aab')
-}
-
-task copyEditorDevApkToBin(type: Copy) {
- dependsOn ':editor:assembleDev'
- from('editor/build/outputs/apk/dev')
- into(androidEditorBuildsDir)
- include('android_editor-dev.apk')
-}
-
-task copyEditorDevAabToBin(type: Copy) {
- dependsOn ':editor:bundleDev'
- from('editor/build/outputs/bundle/dev')
- into(androidEditorBuildsDir)
- include('android_editor-dev.aab')
+ return buildTasks
}
/**
@@ -301,7 +223,7 @@ task generateGodotTemplates {
*/
task generateDevTemplate {
// add parameter to set symbols to true
- gradle.startParameter.projectProperties += [doNotStrip: "true"]
+ project.ext.doNotStrip = "true"
gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
dependsOn = generateBuildTasks("template")
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 dad397de61..7c11d69609 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
@@ -117,10 +117,6 @@ open class GodotEditor : GodotActivity() {
val longPressEnabled = enableLongPressGestures()
val panScaleEnabled = enablePanAndScaleGestures()
- val useInputBuffering = useInputBuffering()
- val useAccumulatedInput = useAccumulatedInput()
- GodotLib.updateInputDispatchSettings(useAccumulatedInput, useInputBuffering)
-
checkForProjectPermissionsToEnable()
runOnUiThread {
@@ -128,7 +124,6 @@ open class GodotEditor : GodotActivity() {
godotFragment?.godot?.renderView?.inputHandler?.apply {
enableLongPress(longPressEnabled)
enablePanningAndScalingGestures(panScaleEnabled)
- enableInputDispatchToRenderThread(!useInputBuffering && !useAccumulatedInput)
}
}
}
@@ -208,7 +203,14 @@ open class GodotEditor : GodotActivity() {
}
if (editorWindowInfo.windowClassName == javaClass.name) {
Log.d(TAG, "Restarting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
- ProcessPhoenix.triggerRebirth(this, newInstance)
+ val godot = godot
+ if (godot != null) {
+ godot.destroyAndKillProcess {
+ ProcessPhoenix.triggerRebirth(this, newInstance)
+ }
+ } else {
+ ProcessPhoenix.triggerRebirth(this, newInstance)
+ }
} else {
Log.d(TAG, "Starting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
newInstance.putExtra(EXTRA_NEW_LAUNCH, true)
@@ -280,13 +282,6 @@ open class GodotEditor : GodotActivity() {
java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_pan_and_scale_gestures"))
/**
- * Use input buffering for the Godot Android editor.
- */
- protected open fun useInputBuffering() = java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/editor/android/use_input_buffering"))
-
- protected open fun useAccumulatedInput() = java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/editor/android/use_accumulated_input"))
-
- /**
* Whether we should launch the new godot instance in an adjacent window
* @see https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT
*/
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
index f50b5577c3..2bcfba559c 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
@@ -45,10 +45,6 @@ class GodotGame : GodotEditor() {
override fun enablePanAndScaleGestures() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures"))
- override fun useInputBuffering() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_input_buffering"))
-
- override fun useAccumulatedInput() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_accumulated_input"))
-
override fun checkForProjectPermissionsToEnable() {
// Nothing to do.. by the time we get here, the project permissions will have already
// been requested by the Editor window.
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 c188a97ca5..111cd48405 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -39,8 +39,6 @@ import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Color
import android.hardware.Sensor
-import android.hardware.SensorEvent
-import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.*
import android.util.Log
@@ -53,6 +51,7 @@ import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat
import com.google.android.vending.expansion.downloader.*
import org.godotengine.godot.input.GodotEditText
+import org.godotengine.godot.input.GodotInputHandler
import org.godotengine.godot.io.directory.DirectoryAccessHandler
import org.godotengine.godot.io.file.FileAccessHandler
import org.godotengine.godot.plugin.GodotPluginRegistry
@@ -73,6 +72,8 @@ import java.io.InputStream
import java.lang.Exception
import java.security.MessageDigest
import java.util.*
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicReference
/**
* Core component used to interface with the native layer of the engine.
@@ -80,7 +81,7 @@ import java.util.*
* Can be hosted by [Activity], [Fragment] or [Service] android components, so long as its
* lifecycle methods are properly invoked.
*/
-class Godot(private val context: Context) : SensorEventListener {
+class Godot(private val context: Context) {
private companion object {
private val TAG = Godot::class.java.simpleName
@@ -98,15 +99,23 @@ class Godot(private val context: Context) : SensorEventListener {
private val pluginRegistry: GodotPluginRegistry by lazy {
GodotPluginRegistry.getPluginRegistry()
}
+
+ private val accelerometer_enabled = AtomicBoolean(false)
private val mAccelerometer: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
}
+
+ private val gravity_enabled = AtomicBoolean(false)
private val mGravity: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
}
+
+ private val magnetometer_enabled = AtomicBoolean(false)
private val mMagnetometer: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
}
+
+ private val gyroscope_enabled = AtomicBoolean(false)
private val mGyroscope: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
}
@@ -126,6 +135,12 @@ class Godot(private val context: Context) : SensorEventListener {
val fileAccessHandler = FileAccessHandler(context)
val netUtils = GodotNetUtils(context)
private val commandLineFileParser = CommandLineFileParser()
+ private val godotInputHandler = GodotInputHandler(context, this)
+
+ /**
+ * Task to run when the engine terminates.
+ */
+ private val runOnTerminate = AtomicReference<Runnable>()
/**
* Tracks whether [onCreate] was completed successfully.
@@ -148,6 +163,17 @@ class Godot(private val context: Context) : SensorEventListener {
private var renderViewInitialized = false
private var primaryHost: GodotHost? = null
+ /**
+ * Tracks whether we're in the RESUMED lifecycle state.
+ * See [onResume] and [onPause]
+ */
+ private var resumed = false
+
+ /**
+ * Tracks whether [onGodotSetupCompleted] fired.
+ */
+ private val godotMainLoopStarted = AtomicBoolean(false)
+
var io: GodotIO? = null
private var commandLine : MutableList<String> = ArrayList<String>()
@@ -410,10 +436,10 @@ class Godot(private val context: Context) : SensorEventListener {
if (!meetsVulkanRequirements(activity.packageManager)) {
throw IllegalStateException(activity.getString(R.string.error_missing_vulkan_requirements_message))
}
- GodotVulkanRenderView(host, this)
+ GodotVulkanRenderView(host, this, godotInputHandler)
} else {
// Fallback to openGl
- GodotGLRenderView(host, this, xrMode, useDebugOpengl)
+ GodotGLRenderView(host, this, godotInputHandler, xrMode, useDebugOpengl)
}
if (host == primaryHost) {
@@ -514,23 +540,13 @@ class Godot(private val context: Context) : SensorEventListener {
fun onResume(host: GodotHost) {
Log.v(TAG, "OnResume: $host")
+ resumed = true
if (host != primaryHost) {
return
}
renderView?.onActivityResumed()
- if (mAccelerometer != null) {
- mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME)
- }
- if (mGravity != null) {
- mSensorManager.registerListener(this, mGravity, SensorManager.SENSOR_DELAY_GAME)
- }
- if (mMagnetometer != null) {
- mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME)
- }
- if (mGyroscope != null) {
- mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME)
- }
+ registerSensorsIfNeeded()
if (useImmersive) {
val window = requireActivity().window
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
@@ -545,14 +561,34 @@ class Godot(private val context: Context) : SensorEventListener {
}
}
+ private fun registerSensorsIfNeeded() {
+ if (!resumed || !godotMainLoopStarted.get()) {
+ return
+ }
+
+ if (accelerometer_enabled.get() && mAccelerometer != null) {
+ mSensorManager.registerListener(godotInputHandler, mAccelerometer, SensorManager.SENSOR_DELAY_GAME)
+ }
+ if (gravity_enabled.get() && mGravity != null) {
+ mSensorManager.registerListener(godotInputHandler, mGravity, SensorManager.SENSOR_DELAY_GAME)
+ }
+ if (magnetometer_enabled.get() && mMagnetometer != null) {
+ mSensorManager.registerListener(godotInputHandler, mMagnetometer, SensorManager.SENSOR_DELAY_GAME)
+ }
+ if (gyroscope_enabled.get() && mGyroscope != null) {
+ mSensorManager.registerListener(godotInputHandler, mGyroscope, SensorManager.SENSOR_DELAY_GAME)
+ }
+ }
+
fun onPause(host: GodotHost) {
Log.v(TAG, "OnPause: $host")
+ resumed = false
if (host != primaryHost) {
return
}
renderView?.onActivityPaused()
- mSensorManager.unregisterListener(this)
+ mSensorManager.unregisterListener(godotInputHandler)
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainPause()
}
@@ -577,10 +613,7 @@ class Godot(private val context: Context) : SensorEventListener {
plugin.onMainDestroy()
}
- runOnRenderThread {
- GodotLib.ondestroy()
- forceQuit()
- }
+ renderView?.onActivityDestroyed()
}
/**
@@ -628,26 +661,19 @@ class Godot(private val context: Context) : SensorEventListener {
private fun onGodotSetupCompleted() {
Log.v(TAG, "OnGodotSetupCompleted")
- if (!isEditorBuild()) {
- // 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 rotaryInputAxisValue = GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis")
-
- val useInputBuffering = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_input_buffering"))
- val useAccumulatedInput = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_accumulated_input"))
- GodotLib.updateInputDispatchSettings(useAccumulatedInput, useInputBuffering)
-
- runOnUiThread {
- renderView?.inputHandler?.apply {
- enableLongPress(longPressEnabled)
- enablePanningAndScalingGestures(panScaleEnabled)
- enableInputDispatchToRenderThread(!useInputBuffering && !useAccumulatedInput)
- try {
- setRotaryInputAxis(Integer.parseInt(rotaryInputAxisValue))
- } catch (e: NumberFormatException) {
- Log.w(TAG, e)
- }
+ // 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 rotaryInputAxisValue = GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis")
+
+ runOnUiThread {
+ renderView?.inputHandler?.apply {
+ enableLongPress(longPressEnabled)
+ enablePanningAndScalingGestures(panScaleEnabled)
+ try {
+ setRotaryInputAxis(Integer.parseInt(rotaryInputAxisValue))
+ } catch (e: NumberFormatException) {
+ Log.w(TAG, e)
}
}
}
@@ -663,6 +689,16 @@ class Godot(private val context: Context) : SensorEventListener {
*/
private fun onGodotMainLoopStarted() {
Log.v(TAG, "OnGodotMainLoopStarted")
+ godotMainLoopStarted.set(true)
+
+ accelerometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer")))
+ gravity_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity")))
+ gyroscope_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope")))
+ magnetometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer")))
+
+ runOnUiThread {
+ registerSensorsIfNeeded()
+ }
for (plugin in pluginRegistry.allPlugins) {
plugin.onGodotMainLoopStarted()
@@ -670,6 +706,15 @@ class Godot(private val context: Context) : SensorEventListener {
primaryHost?.onGodotMainLoopStarted()
}
+ /**
+ * Invoked on the render thread when the engine is about to terminate.
+ */
+ @Keep
+ private fun onGodotTerminating() {
+ Log.v(TAG, "OnGodotTerminating")
+ runOnTerminate.get()?.run()
+ }
+
private fun restart() {
primaryHost?.onGodotRestartRequested(this)
}
@@ -805,8 +850,28 @@ class Godot(private val context: Context) : SensorEventListener {
mClipboard.setPrimaryClip(clip)
}
- fun forceQuit() {
- forceQuit(0)
+ /**
+ * Destroys the Godot Engine and kill the process it's running in.
+ */
+ @JvmOverloads
+ fun destroyAndKillProcess(destroyRunnable: Runnable? = null) {
+ val host = primaryHost
+ val activity = host?.activity
+ if (host == null || activity == null) {
+ // Run the destroyRunnable right away as we are about to force quit.
+ destroyRunnable?.run()
+
+ // Fallback to force quit
+ forceQuit(0)
+ return
+ }
+
+ // Store the destroyRunnable so it can be run when the engine is terminating
+ runOnTerminate.set(destroyRunnable)
+
+ runOnUiThread {
+ onDestroy(host)
+ }
}
@Keep
@@ -821,11 +886,7 @@ class Godot(private val context: Context) : SensorEventListener {
} ?: return false
}
- fun onBackPressed(host: GodotHost) {
- if (host != primaryHost) {
- return
- }
-
+ fun onBackPressed() {
var shouldQuit = true
for (plugin in pluginRegistry.allPlugins) {
if (plugin.onMainBackPressed()) {
@@ -837,77 +898,6 @@ class Godot(private val context: Context) : SensorEventListener {
}
}
- private fun getRotatedValues(values: FloatArray?): FloatArray? {
- if (values == null || values.size != 3) {
- return null
- }
- val rotatedValues = FloatArray(3)
- when (windowManager.defaultDisplay.rotation) {
- Surface.ROTATION_0 -> {
- rotatedValues[0] = values[0]
- rotatedValues[1] = values[1]
- rotatedValues[2] = values[2]
- }
- Surface.ROTATION_90 -> {
- rotatedValues[0] = -values[1]
- rotatedValues[1] = values[0]
- rotatedValues[2] = values[2]
- }
- Surface.ROTATION_180 -> {
- rotatedValues[0] = -values[0]
- rotatedValues[1] = -values[1]
- rotatedValues[2] = values[2]
- }
- Surface.ROTATION_270 -> {
- rotatedValues[0] = values[1]
- rotatedValues[1] = -values[0]
- rotatedValues[2] = values[2]
- }
- }
- return rotatedValues
- }
-
- override fun onSensorChanged(event: SensorEvent) {
- if (renderView == null) {
- return
- }
-
- val rotatedValues = getRotatedValues(event.values)
-
- when (event.sensor.type) {
- Sensor.TYPE_ACCELEROMETER -> {
- rotatedValues?.let {
- renderView?.queueOnRenderThread {
- GodotLib.accelerometer(-it[0], -it[1], -it[2])
- }
- }
- }
- Sensor.TYPE_GRAVITY -> {
- rotatedValues?.let {
- renderView?.queueOnRenderThread {
- GodotLib.gravity(-it[0], -it[1], -it[2])
- }
- }
- }
- Sensor.TYPE_MAGNETIC_FIELD -> {
- rotatedValues?.let {
- renderView?.queueOnRenderThread {
- GodotLib.magnetometer(-it[0], -it[1], -it[2])
- }
- }
- }
- Sensor.TYPE_GYROSCOPE -> {
- rotatedValues?.let {
- renderView?.queueOnRenderThread {
- GodotLib.gyroscope(it[0], it[1], it[2])
- }
- }
- }
- }
- }
-
- override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
-
/**
* Used by the native code (java_godot_wrapper.h) to vibrate the device.
* @param durationMs
@@ -1042,7 +1032,7 @@ class Godot(private val context: Context) : SensorEventListener {
@Keep
private fun initInputDevices() {
- renderView?.initInputDevices()
+ godotInputHandler.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 4c5e857b7a..913e3d04c5 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
@@ -85,12 +85,8 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
protected open fun getGodotAppLayout() = R.layout.godot_app_layout
override fun onDestroy() {
- Log.v(TAG, "Destroying Godot app...")
+ Log.v(TAG, "Destroying GodotActivity $this...")
super.onDestroy()
-
- godotFragment?.let {
- terminateGodotInstance(it.godot)
- }
}
override fun onGodotForceQuit(instance: Godot) {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
index 1612ddd0b3..fdda766594 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
@@ -187,7 +187,12 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
final Activity activity = getActivity();
mCurrentIntent = activity.getIntent();
- godot = new Godot(requireContext());
+ if (parentHost != null) {
+ godot = parentHost.getGodot();
+ }
+ if (godot == null) {
+ godot = new Godot(requireContext());
+ }
performEngineInitialization();
BenchmarkUtils.endBenchmarkMeasure("Startup", "GodotFragment::onCreate");
}
@@ -209,7 +214,7 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
final String errorMessage = TextUtils.isEmpty(e.getMessage())
? getString(R.string.error_engine_setup_message)
: e.getMessage();
- godot.alert(errorMessage, getString(R.string.text_error_title), godot::forceQuit);
+ godot.alert(errorMessage, getString(R.string.text_error_title), godot::destroyAndKillProcess);
} catch (IllegalArgumentException ignored) {
final Activity activity = getActivity();
Intent notifierIntent = new Intent(activity, activity.getClass());
@@ -325,7 +330,7 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
}
public void onBackPressed() {
- godot.onBackPressed(this);
+ godot.onBackPressed();
}
/**
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 81043ce782..15a811ce83 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
@@ -42,7 +42,6 @@ import org.godotengine.godot.xr.regular.RegularContextFactory;
import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser;
import android.annotation.SuppressLint;
-import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -77,19 +76,19 @@ import java.io.InputStream;
* that matches it exactly (with regards to red/green/blue/alpha channels
* bit depths). Failure to do so would result in an EGL_BAD_MATCH error.
*/
-public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView {
+class GodotGLRenderView extends GLSurfaceView implements GodotRenderView {
private final GodotHost host;
private final Godot godot;
private final GodotInputHandler inputHandler;
private final GodotRenderer godotRenderer;
private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();
- public GodotGLRenderView(GodotHost host, Godot godot, XRMode xrMode, boolean useDebugOpengl) {
+ public GodotGLRenderView(GodotHost host, Godot godot, GodotInputHandler inputHandler, XRMode xrMode, boolean useDebugOpengl) {
super(host.getActivity());
this.host = host;
this.godot = godot;
- this.inputHandler = new GodotInputHandler(this);
+ this.inputHandler = inputHandler;
this.godotRenderer = new GodotRenderer();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
@@ -103,11 +102,6 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
}
@Override
- public void initInputDevices() {
- this.inputHandler.initInputDevices();
- }
-
- @Override
public void queueOnRenderThread(Runnable event) {
queueEvent(event);
}
@@ -141,8 +135,8 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
}
@Override
- public void onBackPressed() {
- godot.onBackPressed(host);
+ public void onActivityDestroyed() {
+ requestRenderThreadExitAndWait();
}
@Override
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
index 37e889daf7..909daf05c9 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
@@ -242,9 +242,8 @@ public class GodotLib {
public static native void onRendererPaused();
/**
- * Invoked on the GL thread to update the input dispatch settings
- * @param useAccumulatedInput True to use accumulated input, false otherwise
- * @param useInputBuffering True to use input buffering, false otherwise
+ * @return true if input must be dispatched from the render thread. If false, input is
+ * dispatched from the UI thread.
*/
- public static native void updateInputDispatchSettings(boolean useAccumulatedInput, boolean useInputBuffering);
+ public static native boolean shouldDispatchInputToRenderThread();
}
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 5b2f9f57c7..30821eaa8e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
@@ -37,13 +37,14 @@ import android.view.SurfaceView;
public interface GodotRenderView {
SurfaceView getView();
- void initInputDevices();
-
/**
* Starts the thread that will drive Godot's rendering.
*/
void startRenderer();
+ /**
+ * Queues a runnable to be run on the rendering thread.
+ */
void queueOnRenderThread(Runnable event);
void onActivityPaused();
@@ -54,7 +55,7 @@ public interface GodotRenderView {
void onActivityStarted();
- void onBackPressed();
+ void onActivityDestroyed();
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 a1ee9bd6b4..d5b05913d8 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
@@ -50,19 +50,19 @@ import androidx.annotation.Keep;
import java.io.InputStream;
-public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
+class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
private final GodotHost host;
private final Godot godot;
private final GodotInputHandler mInputHandler;
private final VkRenderer mRenderer;
private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();
- public GodotVulkanRenderView(GodotHost host, Godot godot) {
+ public GodotVulkanRenderView(GodotHost host, Godot godot, GodotInputHandler inputHandler) {
super(host.getActivity());
this.host = host;
this.godot = godot;
- mInputHandler = new GodotInputHandler(this);
+ mInputHandler = inputHandler;
mRenderer = new VkRenderer();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
@@ -81,11 +81,6 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
}
@Override
- public void initInputDevices() {
- mInputHandler.initInputDevices();
- }
-
- @Override
public void queueOnRenderThread(Runnable event) {
queueOnVkThread(event);
}
@@ -119,8 +114,8 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
}
@Override
- public void onBackPressed() {
- godot.onBackPressed(host);
+ public void onActivityDestroyed() {
+ requestRenderThreadExitAndWait();
}
@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 c9421a3257..6a4e9da699 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
@@ -595,6 +595,15 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
protected final void resumeGLThread() {
mGLThread.onResume();
}
+
+ /**
+ * Requests the render thread to exit and block until it does.
+ */
+ protected final void requestRenderThreadExitAndWait() {
+ if (mGLThread != null) {
+ mGLThread.requestExitAndWait();
+ }
+ }
// -- GODOT end --
/**
@@ -783,6 +792,11 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
* @return true if the buffers should be swapped, false otherwise.
*/
boolean onDrawFrame(GL10 gl);
+
+ /**
+ * Invoked when the render thread is in the process of shutting down.
+ */
+ void onRenderThreadExiting();
// -- GODOT end --
}
@@ -1621,6 +1635,12 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
* clean-up everything...
*/
synchronized (sGLThreadManager) {
+ Log.d("GLThread", "Exiting render thread");
+ GLSurfaceView view = mGLSurfaceViewWeakRef.get();
+ if (view != null) {
+ view.mRenderer.onRenderThreadExiting();
+ }
+
stopEglSurfaceLocked();
stopEglContextLocked();
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/GodotRenderer.java b/platform/android/java/lib/src/org/godotengine/godot/gl/GodotRenderer.java
index 9d44d8826c..7e5e262b2d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/gl/GodotRenderer.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/gl/GodotRenderer.java
@@ -34,6 +34,8 @@ import org.godotengine.godot.GodotLib;
import org.godotengine.godot.plugin.GodotPlugin;
import org.godotengine.godot.plugin.GodotPluginRegistry;
+import android.util.Log;
+
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
@@ -41,6 +43,8 @@ import javax.microedition.khronos.opengles.GL10;
* Godot's GL renderer implementation.
*/
public class GodotRenderer implements GLSurfaceView.Renderer {
+ private final String TAG = GodotRenderer.class.getSimpleName();
+
private final GodotPluginRegistry pluginRegistry;
private boolean activityJustResumed = false;
@@ -62,6 +66,12 @@ public class GodotRenderer implements GLSurfaceView.Renderer {
return swapBuffers;
}
+ @Override
+ public void onRenderThreadExiting() {
+ Log.d(TAG, "Destroying Godot Engine");
+ GodotLib.ondestroy();
+ }
+
public void onSurfaceChanged(GL10 gl, int width, int height) {
GodotLib.resize(null, width, height);
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
index 4cd3bd8db9..2929a0a0b0 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
@@ -76,7 +76,10 @@ internal class GodotGestureHandler(private val inputHandler: GodotInputHandler)
}
override fun onLongPress(event: MotionEvent) {
- contextClickRouter(event)
+ val toolType = GodotInputHandler.getEventToolType(event)
+ if (toolType != MotionEvent.TOOL_TYPE_MOUSE) {
+ contextClickRouter(event)
+ }
}
private fun contextClickRouter(event: MotionEvent) {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
index 889618914d..fb41cd00c0 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
@@ -32,10 +32,14 @@ package org.godotengine.godot.input;
import static org.godotengine.godot.utils.GLUtils.DEBUG;
+import org.godotengine.godot.Godot;
import org.godotengine.godot.GodotLib;
import org.godotengine.godot.GodotRenderView;
import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
import android.hardware.input.InputManager;
import android.os.Build;
import android.util.Log;
@@ -46,6 +50,10 @@ import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
import java.util.Collections;
import java.util.HashSet;
@@ -54,7 +62,7 @@ import java.util.Set;
/**
* Handles input related events for the {@link GodotRenderView} view.
*/
-public class GodotInputHandler implements InputManager.InputDeviceListener {
+public class GodotInputHandler implements InputManager.InputDeviceListener, SensorEventListener {
private static final String TAG = GodotInputHandler.class.getSimpleName();
private static final int ROTARY_INPUT_VERTICAL_AXIS = 1;
@@ -64,8 +72,9 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
private final SparseArray<Joystick> mJoysticksDevices = new SparseArray<>(4);
private final HashSet<Integer> mHardwareKeyboardIds = new HashSet<>();
- private final GodotRenderView mRenderView;
+ private final Godot godot;
private final InputManager mInputManager;
+ private final WindowManager windowManager;
private final GestureDetector gestureDetector;
private final ScaleGestureDetector scaleGestureDetector;
private final GodotGestureHandler godotGestureHandler;
@@ -77,14 +86,13 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
private int rotaryInputAxis = ROTARY_INPUT_VERTICAL_AXIS;
- private boolean dispatchInputToRenderThread = false;
-
- public GodotInputHandler(GodotRenderView godotView) {
- final Context context = godotView.getView().getContext();
- mRenderView = godotView;
+ public GodotInputHandler(Context context, Godot godot) {
+ this.godot = godot;
mInputManager = (InputManager)context.getSystemService(Context.INPUT_SERVICE);
mInputManager.registerInputDeviceListener(this, null);
+ windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+
this.godotGestureHandler = new GodotGestureHandler(this);
this.gestureDetector = new GestureDetector(context, godotGestureHandler);
this.gestureDetector.setIsLongpressEnabled(false);
@@ -111,19 +119,11 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
/**
- * Specifies whether input should be dispatch on the UI thread or on the Render thread.
- * @param enable true to dispatch input on the Render thread, false to dispatch input on the UI thread
- */
- public void enableInputDispatchToRenderThread(boolean enable) {
- this.dispatchInputToRenderThread = enable;
- }
-
- /**
* @return true if input must be dispatched from the render thread. If false, input is
* dispatched from the UI thread.
*/
private boolean shouldDispatchInputToRenderThread() {
- return dispatchInputToRenderThread;
+ return GodotLib.shouldDispatchInputToRenderThread();
}
/**
@@ -184,7 +184,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
public boolean onKeyDown(final int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
- mRenderView.onBackPressed();
+ godot.onBackPressed();
// press 'back' button should not terminate program
//normal handle 'back' event in game logic
return true;
@@ -472,7 +472,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
return button;
}
- private static int getEventToolType(MotionEvent event) {
+ static int getEventToolType(MotionEvent event) {
return event.getPointerCount() > 0 ? event.getToolType(0) : MotionEvent.TOOL_TYPE_UNKNOWN;
}
@@ -517,7 +517,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
return handleTouchEvent(event, eventActionOverride, doubleTap);
}
- private static float getEventTiltX(MotionEvent event) {
+ static float getEventTiltX(MotionEvent event) {
// Orientation is returned as a radian value between 0 to pi clockwise or 0 to -pi counterclockwise.
final float orientation = event.getOrientation();
@@ -530,7 +530,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
return (float)-Math.sin(orientation) * tiltMult;
}
- private static float getEventTiltY(MotionEvent event) {
+ static float getEventTiltY(MotionEvent event) {
// Orientation is returned as a radian value between 0 to pi clockwise or 0 to -pi counterclockwise.
final float orientation = event.getOrientation();
@@ -589,6 +589,11 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative, float pressure, float tiltX, float tiltY) {
+ InputEventRunnable runnable = InputEventRunnable.obtain();
+ if (runnable == null) {
+ return false;
+ }
+
// Fix the buttonsMask
switch (eventAction) {
case MotionEvent.ACTION_CANCEL:
@@ -604,7 +609,6 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
break;
}
- final int updatedButtonsMask = buttonsMask;
// We don't handle ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE events as they typically
// follow ACTION_DOWN and ACTION_UP events. As such, handling them would result in duplicate
// stream of events to the engine.
@@ -617,11 +621,8 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_SCROLL: {
- if (shouldDispatchInputToRenderThread()) {
- mRenderView.queueOnRenderThread(() -> GodotLib.dispatchMouseEvent(eventAction, updatedButtonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY));
- } else {
- GodotLib.dispatchMouseEvent(eventAction, updatedButtonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY);
- }
+ runnable.setMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY);
+ dispatchInputEventRunnable(runnable);
return true;
}
}
@@ -637,22 +638,14 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
boolean handleTouchEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) {
- final int pointerCount = event.getPointerCount();
- if (pointerCount == 0) {
+ if (event.getPointerCount() == 0) {
return true;
}
- final float[] positions = new float[pointerCount * 6]; // pointerId1, x1, y1, pressure1, tiltX1, tiltY1, pointerId2, etc...
-
- for (int i = 0; i < pointerCount; i++) {
- positions[i * 6 + 0] = event.getPointerId(i);
- positions[i * 6 + 1] = event.getX(i);
- positions[i * 6 + 2] = event.getY(i);
- positions[i * 6 + 3] = event.getPressure(i);
- positions[i * 6 + 4] = getEventTiltX(event);
- positions[i * 6 + 5] = getEventTiltY(event);
+ InputEventRunnable runnable = InputEventRunnable.obtain();
+ if (runnable == null) {
+ return false;
}
- final int actionPointerId = event.getPointerId(event.getActionIndex());
switch (eventActionOverride) {
case MotionEvent.ACTION_DOWN:
@@ -661,11 +654,8 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_POINTER_DOWN: {
- if (shouldDispatchInputToRenderThread()) {
- mRenderView.queueOnRenderThread(() -> GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap));
- } else {
- GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap);
- }
+ runnable.setTouchEvent(event, eventActionOverride, doubleTap);
+ dispatchInputEventRunnable(runnable);
return true;
}
}
@@ -673,58 +663,128 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
void handleMagnifyEvent(float x, float y, float factor) {
- if (shouldDispatchInputToRenderThread()) {
- mRenderView.queueOnRenderThread(() -> GodotLib.magnify(x, y, factor));
- } else {
- GodotLib.magnify(x, y, factor);
+ InputEventRunnable runnable = InputEventRunnable.obtain();
+ if (runnable == null) {
+ return;
}
+
+ runnable.setMagnifyEvent(x, y, factor);
+ dispatchInputEventRunnable(runnable);
}
void handlePanEvent(float x, float y, float deltaX, float deltaY) {
- if (shouldDispatchInputToRenderThread()) {
- mRenderView.queueOnRenderThread(() -> GodotLib.pan(x, y, deltaX, deltaY));
- } else {
- GodotLib.pan(x, y, deltaX, deltaY);
+ InputEventRunnable runnable = InputEventRunnable.obtain();
+ if (runnable == null) {
+ return;
}
+
+ runnable.setPanEvent(x, y, deltaX, deltaY);
+ dispatchInputEventRunnable(runnable);
}
private void handleJoystickButtonEvent(int device, int button, boolean pressed) {
- if (shouldDispatchInputToRenderThread()) {
- mRenderView.queueOnRenderThread(() -> GodotLib.joybutton(device, button, pressed));
- } else {
- GodotLib.joybutton(device, button, pressed);
+ InputEventRunnable runnable = InputEventRunnable.obtain();
+ if (runnable == null) {
+ return;
}
+
+ runnable.setJoystickButtonEvent(device, button, pressed);
+ dispatchInputEventRunnable(runnable);
}
private void handleJoystickAxisEvent(int device, int axis, float value) {
- if (shouldDispatchInputToRenderThread()) {
- mRenderView.queueOnRenderThread(() -> GodotLib.joyaxis(device, axis, value));
- } else {
- GodotLib.joyaxis(device, axis, value);
+ InputEventRunnable runnable = InputEventRunnable.obtain();
+ if (runnable == null) {
+ return;
}
+
+ runnable.setJoystickAxisEvent(device, axis, value);
+ dispatchInputEventRunnable(runnable);
}
private void handleJoystickHatEvent(int device, int hatX, int hatY) {
- if (shouldDispatchInputToRenderThread()) {
- mRenderView.queueOnRenderThread(() -> GodotLib.joyhat(device, hatX, hatY));
- } else {
- GodotLib.joyhat(device, hatX, hatY);
+ InputEventRunnable runnable = InputEventRunnable.obtain();
+ if (runnable == null) {
+ return;
}
+
+ runnable.setJoystickHatEvent(device, hatX, hatY);
+ dispatchInputEventRunnable(runnable);
}
private void handleJoystickConnectionChangedEvent(int device, boolean connected, String name) {
- if (shouldDispatchInputToRenderThread()) {
- mRenderView.queueOnRenderThread(() -> GodotLib.joyconnectionchanged(device, connected, name));
- } else {
- GodotLib.joyconnectionchanged(device, connected, name);
+ InputEventRunnable runnable = InputEventRunnable.obtain();
+ if (runnable == null) {
+ return;
}
+
+ runnable.setJoystickConnectionChangedEvent(device, connected, name);
+ dispatchInputEventRunnable(runnable);
}
void handleKeyEvent(int physicalKeycode, int unicode, int keyLabel, boolean pressed, boolean echo) {
+ InputEventRunnable runnable = InputEventRunnable.obtain();
+ if (runnable == null) {
+ return;
+ }
+
+ runnable.setKeyEvent(physicalKeycode, unicode, keyLabel, pressed, echo);
+ dispatchInputEventRunnable(runnable);
+ }
+
+ private void dispatchInputEventRunnable(@NonNull InputEventRunnable runnable) {
if (shouldDispatchInputToRenderThread()) {
- mRenderView.queueOnRenderThread(() -> GodotLib.key(physicalKeycode, unicode, keyLabel, pressed, echo));
+ godot.runOnRenderThread(runnable);
} else {
- GodotLib.key(physicalKeycode, unicode, keyLabel, pressed, echo);
+ runnable.run();
+ }
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ final float[] values = event.values;
+ if (values == null || values.length != 3) {
+ return;
+ }
+
+ InputEventRunnable runnable = InputEventRunnable.obtain();
+ if (runnable == null) {
+ return;
+ }
+
+ float rotatedValue0 = 0f;
+ float rotatedValue1 = 0f;
+ float rotatedValue2 = 0f;
+ switch (windowManager.getDefaultDisplay().getRotation()) {
+ case Surface.ROTATION_0:
+ rotatedValue0 = values[0];
+ rotatedValue1 = values[1];
+ rotatedValue2 = values[2];
+ break;
+
+ case Surface.ROTATION_90:
+ rotatedValue0 = -values[1];
+ rotatedValue1 = values[0];
+ rotatedValue2 = values[2];
+ break;
+
+ case Surface.ROTATION_180:
+ rotatedValue0 = -values[0];
+ rotatedValue1 = -values[1];
+ rotatedValue2 = values[2];
+ break;
+
+ case Surface.ROTATION_270:
+ rotatedValue0 = values[1];
+ rotatedValue1 = -values[0];
+ rotatedValue2 = values[2];
+ break;
}
+
+ runnable.setSensorEvent(event.sensor.getType(), rotatedValue0, rotatedValue1, rotatedValue2);
+ godot.runOnRenderThread(runnable);
}
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/InputEventRunnable.java b/platform/android/java/lib/src/org/godotengine/godot/input/InputEventRunnable.java
new file mode 100644
index 0000000000..a282791b2e
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/InputEventRunnable.java
@@ -0,0 +1,353 @@
+/**************************************************************************/
+/* InputEventRunnable.java */
+/**************************************************************************/
+/* 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.input;
+
+import org.godotengine.godot.GodotLib;
+
+import android.hardware.Sensor;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.util.Pools;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Used to dispatch input events.
+ *
+ * This is a specialized version of @{@link Runnable} which allows to allocate a finite pool of
+ * objects for input events dispatching, thus avoid the creation (and garbage collection) of
+ * spurious @{@link Runnable} objects.
+ */
+final class InputEventRunnable implements Runnable {
+ private static final String TAG = InputEventRunnable.class.getSimpleName();
+
+ private static final int MAX_TOUCH_POINTER_COUNT = 10; // assuming 10 fingers as max supported concurrent touch pointers
+
+ private static final Pools.Pool<InputEventRunnable> POOL = new Pools.Pool<>() {
+ private static final int MAX_POOL_SIZE = 120 * 10; // up to 120Hz input events rate for up to 5 secs (ANR limit) * 2
+
+ private final ArrayBlockingQueue<InputEventRunnable> queue = new ArrayBlockingQueue<>(MAX_POOL_SIZE);
+ private final AtomicInteger createdCount = new AtomicInteger();
+
+ @Nullable
+ @Override
+ public InputEventRunnable acquire() {
+ InputEventRunnable instance = queue.poll();
+ if (instance == null) {
+ int creationCount = createdCount.incrementAndGet();
+ if (creationCount <= MAX_POOL_SIZE) {
+ instance = new InputEventRunnable(creationCount - 1);
+ }
+ }
+
+ return instance;
+ }
+
+ @Override
+ public boolean release(@NonNull InputEventRunnable instance) {
+ return queue.offer(instance);
+ }
+ };
+
+ @Nullable
+ static InputEventRunnable obtain() {
+ InputEventRunnable runnable = POOL.acquire();
+ if (runnable == null) {
+ Log.w(TAG, "Input event pool is at capacity");
+ }
+ return runnable;
+ }
+
+ /**
+ * Used to track when this instance was created and added to the pool. Primarily used for
+ * debug purposes.
+ */
+ private final int creationRank;
+
+ private InputEventRunnable(int creationRank) {
+ this.creationRank = creationRank;
+ }
+
+ /**
+ * Set of supported input events.
+ */
+ private enum EventType {
+ MOUSE,
+ TOUCH,
+ MAGNIFY,
+ PAN,
+ JOYSTICK_BUTTON,
+ JOYSTICK_AXIS,
+ JOYSTICK_HAT,
+ JOYSTICK_CONNECTION_CHANGED,
+ KEY,
+ SENSOR
+ }
+
+ private EventType currentEventType = null;
+
+ // common event fields
+ private float eventX;
+ private float eventY;
+ private float eventDeltaX;
+ private float eventDeltaY;
+ private boolean eventPressed;
+
+ // common touch / mouse fields
+ private int eventAction;
+ private boolean doubleTap;
+
+ // Mouse event fields and setter
+ private int buttonsMask;
+ private boolean sourceMouseRelative;
+ private float pressure;
+ private float tiltX;
+ private float tiltY;
+ void setMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative, float pressure, float tiltX, float tiltY) {
+ this.currentEventType = EventType.MOUSE;
+ this.eventAction = eventAction;
+ this.buttonsMask = buttonsMask;
+ this.eventX = x;
+ this.eventY = y;
+ this.eventDeltaX = deltaX;
+ this.eventDeltaY = deltaY;
+ this.doubleTap = doubleClick;
+ this.sourceMouseRelative = sourceMouseRelative;
+ this.pressure = pressure;
+ this.tiltX = tiltX;
+ this.tiltY = tiltY;
+ }
+
+ // Touch event fields and setter
+ private int actionPointerId;
+ private int pointerCount;
+ private final float[] positions = new float[MAX_TOUCH_POINTER_COUNT * 6]; // pointerId1, x1, y1, pressure1, tiltX1, tiltY1, pointerId2, etc...
+ void setTouchEvent(MotionEvent event, int eventAction, boolean doubleTap) {
+ this.currentEventType = EventType.TOUCH;
+ this.eventAction = eventAction;
+ this.doubleTap = doubleTap;
+ this.actionPointerId = event.getPointerId(event.getActionIndex());
+ this.pointerCount = Math.min(event.getPointerCount(), MAX_TOUCH_POINTER_COUNT);
+ for (int i = 0; i < pointerCount; i++) {
+ positions[i * 6 + 0] = event.getPointerId(i);
+ positions[i * 6 + 1] = event.getX(i);
+ positions[i * 6 + 2] = event.getY(i);
+ positions[i * 6 + 3] = event.getPressure(i);
+ positions[i * 6 + 4] = GodotInputHandler.getEventTiltX(event);
+ positions[i * 6 + 5] = GodotInputHandler.getEventTiltY(event);
+ }
+ }
+
+ // Magnify event fields and setter
+ private float magnifyFactor;
+ void setMagnifyEvent(float x, float y, float factor) {
+ this.currentEventType = EventType.MAGNIFY;
+ this.eventX = x;
+ this.eventY = y;
+ this.magnifyFactor = factor;
+ }
+
+ // Pan event setter
+ void setPanEvent(float x, float y, float deltaX, float deltaY) {
+ this.currentEventType = EventType.PAN;
+ this.eventX = x;
+ this.eventY = y;
+ this.eventDeltaX = deltaX;
+ this.eventDeltaY = deltaY;
+ }
+
+ // common joystick field
+ private int joystickDevice;
+
+ // Joystick button event fields and setter
+ private int button;
+ void setJoystickButtonEvent(int device, int button, boolean pressed) {
+ this.currentEventType = EventType.JOYSTICK_BUTTON;
+ this.joystickDevice = device;
+ this.button = button;
+ this.eventPressed = pressed;
+ }
+
+ // Joystick axis event fields and setter
+ private int axis;
+ private float value;
+ void setJoystickAxisEvent(int device, int axis, float value) {
+ this.currentEventType = EventType.JOYSTICK_AXIS;
+ this.joystickDevice = device;
+ this.axis = axis;
+ this.value = value;
+ }
+
+ // Joystick hat event fields and setter
+ private int hatX;
+ private int hatY;
+ void setJoystickHatEvent(int device, int hatX, int hatY) {
+ this.currentEventType = EventType.JOYSTICK_HAT;
+ this.joystickDevice = device;
+ this.hatX = hatX;
+ this.hatY = hatY;
+ }
+
+ // Joystick connection changed event fields and setter
+ private boolean connected;
+ private String joystickName;
+ void setJoystickConnectionChangedEvent(int device, boolean connected, String name) {
+ this.currentEventType = EventType.JOYSTICK_CONNECTION_CHANGED;
+ this.joystickDevice = device;
+ this.connected = connected;
+ this.joystickName = name;
+ }
+
+ // Key event fields and setter
+ private int physicalKeycode;
+ private int unicode;
+ private int keyLabel;
+ private boolean echo;
+ void setKeyEvent(int physicalKeycode, int unicode, int keyLabel, boolean pressed, boolean echo) {
+ this.currentEventType = EventType.KEY;
+ this.physicalKeycode = physicalKeycode;
+ this.unicode = unicode;
+ this.keyLabel = keyLabel;
+ this.eventPressed = pressed;
+ this.echo = echo;
+ }
+
+ // Sensor event fields and setter
+ private int sensorType;
+ private float rotatedValue0;
+ private float rotatedValue1;
+ private float rotatedValue2;
+ void setSensorEvent(int sensorType, float rotatedValue0, float rotatedValue1, float rotatedValue2) {
+ this.currentEventType = EventType.SENSOR;
+ this.sensorType = sensorType;
+ this.rotatedValue0 = rotatedValue0;
+ this.rotatedValue1 = rotatedValue1;
+ this.rotatedValue2 = rotatedValue2;
+ }
+
+ @Override
+ public void run() {
+ try {
+ if (currentEventType == null) {
+ Log.w(TAG, "Invalid event type");
+ return;
+ }
+
+ switch (currentEventType) {
+ case MOUSE:
+ GodotLib.dispatchMouseEvent(
+ eventAction,
+ buttonsMask,
+ eventX,
+ eventY,
+ eventDeltaX,
+ eventDeltaY,
+ doubleTap,
+ sourceMouseRelative,
+ pressure,
+ tiltX,
+ tiltY);
+ break;
+
+ case TOUCH:
+ GodotLib.dispatchTouchEvent(
+ eventAction,
+ actionPointerId,
+ pointerCount,
+ positions,
+ doubleTap);
+ break;
+
+ case MAGNIFY:
+ GodotLib.magnify(eventX, eventY, magnifyFactor);
+ break;
+
+ case PAN:
+ GodotLib.pan(eventX, eventY, eventDeltaX, eventDeltaY);
+ break;
+
+ case JOYSTICK_BUTTON:
+ GodotLib.joybutton(joystickDevice, button, eventPressed);
+ break;
+
+ case JOYSTICK_AXIS:
+ GodotLib.joyaxis(joystickDevice, axis, value);
+ break;
+
+ case JOYSTICK_HAT:
+ GodotLib.joyhat(joystickDevice, hatX, hatY);
+ break;
+
+ case JOYSTICK_CONNECTION_CHANGED:
+ GodotLib.joyconnectionchanged(joystickDevice, connected, joystickName);
+ break;
+
+ case KEY:
+ GodotLib.key(physicalKeycode, unicode, keyLabel, eventPressed, echo);
+ break;
+
+ case SENSOR:
+ switch (sensorType) {
+ case Sensor.TYPE_ACCELEROMETER:
+ GodotLib.accelerometer(-rotatedValue0, -rotatedValue1, -rotatedValue2);
+ break;
+
+ case Sensor.TYPE_GRAVITY:
+ GodotLib.gravity(-rotatedValue0, -rotatedValue1, -rotatedValue2);
+ break;
+
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ GodotLib.magnetometer(-rotatedValue0, -rotatedValue1, -rotatedValue2);
+ break;
+
+ case Sensor.TYPE_GYROSCOPE:
+ GodotLib.gyroscope(rotatedValue0, rotatedValue1, rotatedValue2);
+ break;
+ }
+ break;
+ }
+ } finally {
+ recycle();
+ }
+ }
+
+ /**
+ * Release the current instance back to the pool
+ */
+ private void recycle() {
+ currentEventType = null;
+ POOL.release(this);
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileData.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileData.kt
index f2c0577c21..d0b8a8dffa 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileData.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileData.kt
@@ -53,7 +53,7 @@ internal class FileData(filePath: String, accessFlag: FileAccessFlags) : DataAcc
fun fileLastModified(filepath: String): Long {
return try {
- File(filepath).lastModified()
+ File(filepath).lastModified() / 1000L
} catch (e: SecurityException) {
0L
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt
index 5410eed727..146fc04da4 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt
@@ -203,7 +203,7 @@ internal class MediaStoreData(context: Context, filePath: String, accessFlag: Fi
}
val dataItem = result[0]
- return dataItem.dateModified.toLong()
+ return dataItem.dateModified.toLong() / 1000L
}
fun rename(context: Context, from: String, to: String): Boolean {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
index 711bca02e7..8976dd65db 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
@@ -43,6 +43,7 @@ import androidx.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.util.Collection;
+import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -82,6 +83,9 @@ public final class GodotPluginRegistry {
* Retrieve the full set of loaded plugins.
*/
public Collection<GodotPlugin> getAllPlugins() {
+ if (registry.isEmpty()) {
+ return Collections.emptyList();
+ }
return registry.values();
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt
index 6f09f51d4c..a93a7dbe09 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt
@@ -31,11 +31,9 @@
@file:JvmName("VkRenderer")
package org.godotengine.godot.vulkan
+import android.util.Log
import android.view.Surface
-
-import org.godotengine.godot.Godot
import org.godotengine.godot.GodotLib
-import org.godotengine.godot.plugin.GodotPlugin
import org.godotengine.godot.plugin.GodotPluginRegistry
/**
@@ -52,6 +50,11 @@ import org.godotengine.godot.plugin.GodotPluginRegistry
* @see [VkSurfaceView.startRenderer]
*/
internal class VkRenderer {
+
+ companion object {
+ private val TAG = VkRenderer::class.java.simpleName
+ }
+
private val pluginRegistry: GodotPluginRegistry = GodotPluginRegistry.getPluginRegistry()
/**
@@ -101,8 +104,10 @@ internal class VkRenderer {
}
/**
- * Called when the rendering thread is destroyed and used as signal to tear down the Vulkan logic.
+ * Invoked when the render thread is in the process of shutting down.
*/
- fun onVkDestroy() {
+ fun onRenderThreadExiting() {
+ Log.d(TAG, "Destroying Godot Engine")
+ GodotLib.ondestroy()
}
}
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 791b425444..9e30de6a15 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
@@ -113,12 +113,10 @@ open internal class VkSurfaceView(context: Context) : SurfaceView(context), Surf
}
/**
- * Tear down the rendering thread.
- *
- * Must not be called before a [VkRenderer] has been set.
+ * Requests the render thread to exit and block until it does.
*/
- fun onDestroy() {
- vkThread.blockingExit()
+ fun requestRenderThreadExitAndWait() {
+ vkThread.requestExitAndWait()
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
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 8c0065b31e..c7cb97d911 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
@@ -75,6 +75,9 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
private fun threadExiting() {
lock.withLock {
+ Log.d(TAG, "Exiting render thread")
+ vkRenderer.onRenderThreadExiting()
+
exited = true
lockCondition.signalAll()
}
@@ -93,7 +96,7 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
/**
* Request the thread to exit and block until it's done.
*/
- fun blockingExit() {
+ fun requestExitAndWait() {
lock.withLock {
shouldExit = true
lockCondition.signalAll()
@@ -171,7 +174,6 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk
while (true) {
// Code path for exiting the thread loop.
if (shouldExit) {
- vkRenderer.onVkDestroy()
return
}
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 87d4281c5a..a4a425f685 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -51,6 +51,7 @@
#include "core/config/project_settings.h"
#include "core/input/input.h"
#include "main/main.h"
+#include "servers/xr_server.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
@@ -83,6 +84,10 @@ static Vector3 magnetometer;
static Vector3 gyroscope;
static void _terminate(JNIEnv *env, bool p_restart = false) {
+ if (step.get() == STEP_TERMINATED) {
+ return;
+ }
+
step.set(STEP_TERMINATED); // Ensure no further steps are attempted and no further events are sent
// lets cleanup
@@ -114,6 +119,7 @@ static void _terminate(JNIEnv *env, bool p_restart = false) {
NetSocketAndroid::terminate();
if (godot_java) {
+ godot_java->on_godot_terminating(env);
if (!restart_on_cleanup) {
if (p_restart) {
godot_java->restart(env);
@@ -265,7 +271,18 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env,
}
if (step.get() == STEP_SHOW_LOGO) {
- Main::setup_boot_logo();
+ bool xr_enabled;
+ if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) {
+ xr_enabled = GLOBAL_GET("xr/shaders/enabled");
+ } else {
+ xr_enabled = XRServer::get_xr_mode() == XRServer::XRMODE_ON;
+ }
+ // Unlike PCVR, there's no additional 2D screen onto which to render the boot logo,
+ // so we skip this step if xr is enabled.
+ if (!xr_enabled) {
+ Main::setup_boot_logo();
+ }
+
step.increment();
return true;
}
@@ -550,10 +567,11 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIE
}
}
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateInputDispatchSettings(JNIEnv *env, jclass clazz, jboolean p_use_accumulated_input, jboolean p_use_input_buffering) {
- if (Input::get_singleton()) {
- Input::get_singleton()->set_use_accumulated_input(p_use_accumulated_input);
- Input::get_singleton()->set_use_input_buffering(p_use_input_buffering);
+JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_shouldDispatchInputToRenderThread(JNIEnv *env, jclass clazz) {
+ Input *input = Input::get_singleton();
+ if (input) {
+ return !input->is_agile_input_event_flushing();
}
+ return false;
}
}
diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h
index 852c475e7e..d027da31fa 100644
--- a/platform/android/java_godot_lib_jni.h
+++ b/platform/android/java_godot_lib_jni.h
@@ -69,7 +69,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz);
-JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateInputDispatchSettings(JNIEnv *env, jclass clazz, jboolean p_use_accumulated_input, jboolean p_use_input_buffering);
+JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_shouldDispatchInputToRenderThread(JNIEnv *env, jclass clazz);
}
#endif // JAVA_GODOT_LIB_JNI_H
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 6e7f5ef5a1..91bf7b48a6 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -76,6 +76,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;");
_on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V");
_on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V");
+ _on_godot_terminating = p_env->GetMethodID(godot_class, "onGodotTerminating", "()V");
_create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)I");
_get_render_view = p_env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;");
_begin_benchmark_measure = p_env->GetMethodID(godot_class, "nativeBeginBenchmarkMeasure", "(Ljava/lang/String;Ljava/lang/String;)V");
@@ -136,6 +137,16 @@ void GodotJavaWrapper::on_godot_main_loop_started(JNIEnv *p_env) {
}
}
+void GodotJavaWrapper::on_godot_terminating(JNIEnv *p_env) {
+ if (_on_godot_terminating) {
+ if (p_env == nullptr) {
+ p_env = get_jni_env();
+ }
+ ERR_FAIL_NULL(p_env);
+ p_env->CallVoidMethod(godot_instance, _on_godot_terminating);
+ }
+}
+
void GodotJavaWrapper::restart(JNIEnv *p_env) {
if (_restart) {
if (p_env == nullptr) {
@@ -202,25 +213,27 @@ bool GodotJavaWrapper::has_get_clipboard() {
}
String GodotJavaWrapper::get_clipboard() {
+ String clipboard;
if (_get_clipboard) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, String());
jstring s = (jstring)env->CallObjectMethod(godot_instance, _get_clipboard);
- return jstring_to_string(s, env);
- } else {
- return String();
+ clipboard = jstring_to_string(s, env);
+ env->DeleteLocalRef(s);
}
+ return clipboard;
}
String GodotJavaWrapper::get_input_fallback_mapping() {
+ String input_fallback_mapping;
if (_get_input_fallback_mapping) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, String());
jstring fallback_mapping = (jstring)env->CallObjectMethod(godot_instance, _get_input_fallback_mapping);
- return jstring_to_string(fallback_mapping, env);
- } else {
- return String();
+ input_fallback_mapping = jstring_to_string(fallback_mapping, env);
+ env->DeleteLocalRef(fallback_mapping);
}
+ return input_fallback_mapping;
}
bool GodotJavaWrapper::has_set_clipboard() {
@@ -313,14 +326,15 @@ Vector<String> GodotJavaWrapper::get_gdextension_list_config_file() const {
}
String GodotJavaWrapper::get_ca_certificates() const {
+ String ca_certificates;
if (_get_ca_certificates) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, String());
jstring s = (jstring)env->CallObjectMethod(godot_instance, _get_ca_certificates);
- return jstring_to_string(s, env);
- } else {
- return String();
+ ca_certificates = jstring_to_string(s, env);
+ env->DeleteLocalRef(s);
}
+ return ca_certificates;
}
void GodotJavaWrapper::init_input_devices() {
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index e86391d4e3..358cf3261d 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -68,6 +68,7 @@ private:
jmethodID _get_input_fallback_mapping = nullptr;
jmethodID _on_godot_setup_completed = nullptr;
jmethodID _on_godot_main_loop_started = nullptr;
+ jmethodID _on_godot_terminating = nullptr;
jmethodID _create_new_godot_instance = nullptr;
jmethodID _get_render_view = nullptr;
jmethodID _begin_benchmark_measure = nullptr;
@@ -85,6 +86,7 @@ public:
void on_godot_setup_completed(JNIEnv *p_env = nullptr);
void on_godot_main_loop_started(JNIEnv *p_env = nullptr);
+ void on_godot_terminating(JNIEnv *p_env = nullptr);
void restart(JNIEnv *p_env = nullptr);
bool force_quit(JNIEnv *p_env = nullptr, int p_instance_id = 0);
void set_keep_screen_on(bool p_enabled);
diff --git a/platform/ios/detect.py b/platform/ios/detect.py
index 53b367a0a7..ecd1f3d32d 100644
--- a/platform/ios/detect.py
+++ b/platform/ios/detect.py
@@ -51,6 +51,7 @@ def get_flags():
"arch": "arm64",
"target": "template_debug",
"use_volk": False,
+ "metal": True,
"supported": ["mono"],
"builtin_pcre2_with_jit": False,
}
@@ -154,8 +155,22 @@ def configure(env: "SConsEnvironment"):
env.Prepend(CPPPATH=["#platform/ios"])
env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"])
+ if env["metal"] and env["arch"] != "arm64":
+ # Only supported on arm64, so skip it for x86_64 builds.
+ env["metal"] = False
+
+ if env["metal"]:
+ env.AppendUnique(CPPDEFINES=["METAL_ENABLED", "RD_ENABLED"])
+ env.Prepend(
+ CPPPATH=[
+ "$IOS_SDK_PATH/System/Library/Frameworks/Metal.framework/Headers",
+ "$IOS_SDK_PATH/System/Library/Frameworks/QuartzCore.framework/Headers",
+ ]
+ )
+ env.Prepend(CPPPATH=["#thirdparty/spirv-cross"])
+
if env["vulkan"]:
- env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
+ env.AppendUnique(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
if env["opengl3"]:
env.Append(CPPDEFINES=["GLES3_ENABLED", "GLES_SILENCE_DEPRECATION"])
diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h
index 4dded5aa29..bbb758074d 100644
--- a/platform/ios/display_server_ios.h
+++ b/platform/ios/display_server_ios.h
@@ -47,6 +47,10 @@
#include <vulkan/vulkan.h>
#endif
#endif // VULKAN_ENABLED
+
+#if defined(METAL_ENABLED)
+#include "drivers/metal/rendering_context_driver_metal.h"
+#endif // METAL_ENABLED
#endif // RD_ENABLED
#if defined(GLES3_ENABLED)
diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm
index 802fbefc0d..5a027e0196 100644
--- a/platform/ios/display_server_ios.mm
+++ b/platform/ios/display_server_ios.mm
@@ -73,6 +73,13 @@ DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode
#ifdef VULKAN_ENABLED
RenderingContextDriverVulkanIOS::WindowPlatformData vulkan;
#endif
+#ifdef METAL_ENABLED
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability"
+ // Eliminate "RenderingContextDriverMetal is only available on iOS 14.0 or newer".
+ RenderingContextDriverMetal::WindowPlatformData metal;
+#pragma clang diagnostic pop
+#endif
} wpd;
#if defined(VULKAN_ENABLED)
@@ -85,7 +92,19 @@ DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode
rendering_context = memnew(RenderingContextDriverVulkanIOS);
}
#endif
-
+#ifdef METAL_ENABLED
+ if (rendering_driver == "metal") {
+ if (@available(iOS 14.0, *)) {
+ layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"metal"];
+ wpd.metal.layer = (CAMetalLayer *)layer;
+ rendering_context = memnew(RenderingContextDriverMetal);
+ } else {
+ OS::get_singleton()->alert("Metal is only supported on iOS 14.0 and later.");
+ r_error = ERR_UNAVAILABLE;
+ return;
+ }
+ }
+#endif
if (rendering_context) {
if (rendering_context->initialize() != OK) {
ERR_PRINT(vformat("Failed to initialize %s context", rendering_driver));
@@ -172,6 +191,11 @@ Vector<String> DisplayServerIOS::get_rendering_drivers_func() {
#if defined(VULKAN_ENABLED)
drivers.push_back("vulkan");
#endif
+#if defined(METAL_ENABLED)
+ if (@available(ios 14.0, *)) {
+ drivers.push_back("metal");
+ }
+#endif
#if defined(GLES3_ENABLED)
drivers.push_back("opengl3");
#endif
diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
index 87994ef22b..1d4a944dc4 100644
--- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml
+++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
@@ -151,16 +151,16 @@
Indicates whether your app uses advertising data for tracking.
</member>
<member name="privacy/collected_data/audio_data/collected" type="bool" setter="" getter="">
- Indicates whether your app collects audio data data.
+ Indicates whether your app collects audio data.
</member>
<member name="privacy/collected_data/audio_data/collection_purposes" type="int" setter="" getter="">
The reasons your app collects audio data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url].
</member>
<member name="privacy/collected_data/audio_data/linked_to_user" type="bool" setter="" getter="">
- Indicates whether your app links audio data data to the user's identity.
+ Indicates whether your app links audio data to the user's identity.
</member>
<member name="privacy/collected_data/audio_data/used_for_tracking" type="bool" setter="" getter="">
- Indicates whether your app uses audio data data for tracking.
+ Indicates whether your app uses audio data for tracking.
</member>
<member name="privacy/collected_data/browsing_history/collected" type="bool" setter="" getter="">
Indicates whether your app collects browsing history.
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index 490f73a36d..e4b5392c4e 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -282,6 +282,7 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
+ // TODO(sgc): set to iOS 14.0 for Metal
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_ios_version"), "12.0"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/additional_plist_content", PROPERTY_HINT_MULTILINE_TEXT), ""));
@@ -1628,7 +1629,7 @@ Error EditorExportPlatformIOS::_copy_asset(const Ref<EditorExportPreset> &p_pres
asset_path = asset_path.path_join(framework_name);
destination_dir = p_out_dir.path_join(asset_path);
- destination = destination_dir.path_join(file_name);
+ destination = destination_dir;
// Convert to framework and copy.
Error err = _convert_to_framework(p_asset, destination, p_preset->get("application/bundle_identifier"));
@@ -2656,6 +2657,13 @@ bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref<EditorExp
}
}
+ if (GLOBAL_GET("rendering/rendering_device/driver.ios") == "metal") {
+ float version = p_preset->get("application/min_ios_version").operator String().to_float();
+ if (version < 14.0) {
+ err += TTR("Metal renderer require iOS 14+.") + "\n";
+ }
+ }
+
if (!err.is_empty()) {
r_error = err;
}
@@ -2971,6 +2979,7 @@ void EditorExportPlatformIOS::_update_preset_status() {
} else {
has_runnable_preset.clear();
}
+ devices_changed.set();
}
#endif
diff --git a/platform/ios/godot_app_delegate.h b/platform/ios/godot_app_delegate.h
index a9bfcbb0b2..85dc6bb390 100644
--- a/platform/ios/godot_app_delegate.h
+++ b/platform/ios/godot_app_delegate.h
@@ -32,7 +32,7 @@
typedef NSObject<UIApplicationDelegate> ApplicationDelegateService;
-@interface GodotApplicalitionDelegate : NSObject <UIApplicationDelegate>
+@interface GodotApplicationDelegate : NSObject <UIApplicationDelegate>
@property(class, readonly, strong) NSArray<ApplicationDelegateService *> *services;
diff --git a/platform/ios/godot_app_delegate.m b/platform/ios/godot_app_delegate.m
index 74e8705bc3..53e53cd0c6 100644
--- a/platform/ios/godot_app_delegate.m
+++ b/platform/ios/godot_app_delegate.m
@@ -32,11 +32,11 @@
#import "app_delegate.h"
-@interface GodotApplicalitionDelegate ()
+@interface GodotApplicationDelegate ()
@end
-@implementation GodotApplicalitionDelegate
+@implementation GodotApplicationDelegate
static NSMutableArray<ApplicationDelegateService *> *services = nil;
diff --git a/platform/ios/godot_view.mm b/platform/ios/godot_view.mm
index 1dddc9306e..552c4c262c 100644
--- a/platform/ios/godot_view.mm
+++ b/platform/ios/godot_view.mm
@@ -71,7 +71,7 @@ static const float earth_gravity = 9.80665;
CALayer<DisplayLayer> *layer;
- if ([driverName isEqualToString:@"vulkan"]) {
+ if ([driverName isEqualToString:@"vulkan"] || [driverName isEqualToString:@"metal"]) {
#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
if (@available(iOS 13, *)) {
layer = [GodotMetalLayer layer];
@@ -441,6 +441,9 @@ static const float earth_gravity = 9.80665;
UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
+#if __IPHONE_OS_VERSION_MAX_ALLOWED < 140000
+ interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
+#else
if (@available(iOS 13, *)) {
interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
@@ -448,6 +451,7 @@ static const float earth_gravity = 9.80665;
interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
#endif
}
+#endif
switch (interfaceOrientation) {
case UIInterfaceOrientationLandscapeLeft: {
diff --git a/platform/ios/keyboard_input_view.mm b/platform/ios/keyboard_input_view.mm
index 8b614662b7..4067701a41 100644
--- a/platform/ios/keyboard_input_view.mm
+++ b/platform/ios/keyboard_input_view.mm
@@ -149,23 +149,18 @@
return;
}
+ NSString *substringToDelete = nil;
if (self.previousSelectedRange.length == 0) {
- // We are deleting all text before cursor if no range was selected.
- // This way any inserted or changed text will be updated.
- NSString *substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location];
- [self deleteText:substringToDelete.length];
+ // Get previous text to delete.
+ substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location];
} else {
- // If text was previously selected
- // we are sending only one `backspace`.
- // It will remove all text from text input.
+ // If text was previously selected we are sending only one `backspace`. It will remove all text from text input.
[self deleteText:1];
}
- NSString *substringToEnter;
-
+ NSString *substringToEnter = nil;
if (self.selectedRange.length == 0) {
- // If previous cursor had a selection
- // we have to calculate an inserted text.
+ // If previous cursor had a selection we have to calculate an inserted text.
if (self.previousSelectedRange.length != 0) {
NSInteger rangeEnd = self.selectedRange.location + self.selectedRange.length;
NSInteger rangeStart = MIN(self.previousSelectedRange.location, self.selectedRange.location);
@@ -187,7 +182,18 @@
substringToEnter = [self.text substringWithRange:self.selectedRange];
}
- [self enterText:substringToEnter];
+ NSInteger skip = 0;
+ if (substringToDelete != nil) {
+ for (NSInteger i = 0; i < MIN([substringToDelete length], [substringToEnter length]); i++) {
+ if ([substringToDelete characterAtIndex:i] == [substringToEnter characterAtIndex:i]) {
+ skip++;
+ } else {
+ break;
+ }
+ }
+ [self deleteText:[substringToDelete length] - skip]; // Delete changed part of previous text.
+ }
+ [self enterText:[substringToEnter substringFromIndex:skip]]; // Enter changed part of new text.
self.previousText = self.text;
self.previousSelectedRange = self.selectedRange;
diff --git a/platform/ios/main.m b/platform/ios/main.m
index 33b1034d98..89a00c9ae9 100644
--- a/platform/ios/main.m
+++ b/platform/ios/main.m
@@ -46,7 +46,7 @@ int main(int argc, char *argv[]) {
gargv = argv;
@autoreleasepool {
- NSString *className = NSStringFromClass([GodotApplicalitionDelegate class]);
+ NSString *className = NSStringFromClass([GodotApplicationDelegate class]);
UIApplicationMain(argc, argv, nil, className);
}
return 0;
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py
index 303a88ab26..d1de760f34 100644
--- a/platform/linuxbsd/detect.py
+++ b/platform/linuxbsd/detect.py
@@ -179,6 +179,8 @@ def configure(env: "SConsEnvironment"):
env.Append(CCFLAGS=["-fsanitize-recover=memory"])
env.Append(LINKFLAGS=["-fsanitize=memory"])
+ env.Append(CCFLAGS=["-ffp-contract=off"])
+
# LTO
if env["lto"] == "auto": # Full LTO for production.
diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp
index 936adddda3..0032b898d2 100644
--- a/platform/linuxbsd/export/export_plugin.cpp
+++ b/platform/linuxbsd/export/export_plugin.cpp
@@ -61,6 +61,20 @@ Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportP
}
Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+ String custom_debug = p_preset->get("custom_template/debug");
+ String custom_release = p_preset->get("custom_template/release");
+ String arch = p_preset->get("binary_format/architecture");
+
+ String template_path = p_debug ? custom_debug : custom_release;
+ template_path = template_path.strip_edges();
+ if (!template_path.is_empty()) {
+ String exe_arch = _get_exe_arch(template_path);
+ if (arch != exe_arch) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Mismatching custom export template executable architecture, found \"%s\", expected \"%s\"."), exe_arch, arch));
+ return ERR_CANT_CREATE;
+ }
+ }
+
bool export_as_zip = p_path.ends_with("zip");
String pkg_name;
@@ -205,8 +219,76 @@ bool EditorExportPlatformLinuxBSD::is_executable(const String &p_path) const {
return is_elf(p_path) || is_shebang(p_path);
}
+bool EditorExportPlatformLinuxBSD::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
+ String err;
+ bool valid = EditorExportPlatformPC::has_valid_export_configuration(p_preset, err, r_missing_templates, p_debug);
+
+ String custom_debug = p_preset->get("custom_template/debug").operator String().strip_edges();
+ String custom_release = p_preset->get("custom_template/release").operator String().strip_edges();
+ String arch = p_preset->get("binary_format/architecture");
+
+ if (!custom_debug.is_empty() && FileAccess::exists(custom_debug)) {
+ String exe_arch = _get_exe_arch(custom_debug);
+ if (arch != exe_arch) {
+ err += vformat(TTR("Mismatching custom debug export template executable architecture: found \"%s\", expected \"%s\"."), exe_arch, arch) + "\n";
+ }
+ }
+ if (!custom_release.is_empty() && FileAccess::exists(custom_release)) {
+ String exe_arch = _get_exe_arch(custom_release);
+ if (arch != exe_arch) {
+ err += vformat(TTR("Mismatching custom release export template executable architecture: found \"%s\", expected \"%s\"."), exe_arch, arch) + "\n";
+ }
+ }
+
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
+
+String EditorExportPlatformLinuxBSD::_get_exe_arch(const String &p_path) const {
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
+ if (f.is_null()) {
+ return "invalid";
+ }
+
+ // Read and check ELF magic number.
+ {
+ uint32_t magic = f->get_32();
+ if (magic != 0x464c457f) { // 0x7F + "ELF"
+ return "invalid";
+ }
+ }
+
+ // Process header.
+ int64_t header_pos = f->get_position();
+ f->seek(header_pos + 14);
+ uint16_t machine = f->get_16();
+ f->close();
+
+ switch (machine) {
+ case 0x0003:
+ return "x86_32";
+ case 0x003e:
+ return "x86_64";
+ case 0x0014:
+ return "ppc32";
+ case 0x0015:
+ return "ppc64";
+ case 0x0028:
+ return "arm32";
+ case 0x00b7:
+ return "arm64";
+ case 0x00f3:
+ return "rv64";
+ default:
+ return "unknown";
+ }
+}
+
Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) {
- // Patch the header of the "pck" section in the ELF file so that it corresponds to the embedded data
+ // Patch the header of the "pck" section in the ELF file so that it corresponds to the embedded data.
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ_WRITE);
if (f.is_null()) {
@@ -214,7 +296,7 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int
return ERR_CANT_OPEN;
}
- // Read and check ELF magic number
+ // Read and check ELF magic number.
{
uint32_t magic = f->get_32();
if (magic != 0x464c457f) { // 0x7F + "ELF"
@@ -223,7 +305,7 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int
}
}
- // Read program architecture bits from class field
+ // Read program architecture bits from class field.
int bits = f->get_8() * 32;
@@ -231,7 +313,7 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int
add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("32-bit executables cannot have embedded data >= 4 GiB."));
}
- // Get info about the section header table
+ // Get info about the section header table.
int64_t section_table_pos;
int64_t section_header_size;
@@ -249,13 +331,13 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int
int num_sections = f->get_16();
int string_section_idx = f->get_16();
- // Load the strings table
+ // Load the strings table.
uint8_t *strings;
{
- // Jump to the strings section header
+ // Jump to the strings section header.
f->seek(section_table_pos + string_section_idx * section_header_size);
- // Read strings data size and offset
+ // Read strings data size and offset.
int64_t string_data_pos;
int64_t string_data_size;
if (bits == 32) {
@@ -268,7 +350,7 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int
string_data_size = f->get_64();
}
- // Read strings data
+ // Read strings data.
f->seek(string_data_pos);
strings = (uint8_t *)memalloc(string_data_size);
if (!strings) {
@@ -277,7 +359,7 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int
f->get_buffer(strings, string_data_size);
}
- // Search for the "pck" section
+ // Search for the "pck" section.
bool found = false;
for (int i = 0; i < num_sections; ++i) {
diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h
index 21bd81ed2f..bbc55b82ce 100644
--- a/platform/linuxbsd/export/export_plugin.h
+++ b/platform/linuxbsd/export/export_plugin.h
@@ -69,11 +69,13 @@ class EditorExportPlatformLinuxBSD : public EditorExportPlatformPC {
bool is_shebang(const String &p_path) const;
Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path);
+ String _get_exe_arch(const String &p_path) const;
public:
virtual void get_export_options(List<ExportOption> *r_options) const override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override;
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
virtual String get_template_file_name(const String &p_target, const String &p_arch) const override;
virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override;
diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp
index 3534c1afee..a67428b9a4 100644
--- a/platform/linuxbsd/joypad_linux.cpp
+++ b/platform/linuxbsd/joypad_linux.cpp
@@ -374,6 +374,12 @@ void JoypadLinux::open_joypad(const char *p_path) {
name = namebuf;
}
+ for (const String &word : name.to_lower().split(" ")) {
+ if (banned_words.has(word)) {
+ return;
+ }
+ }
+
if (ioctl(fd, EVIOCGID, &inpid) < 0) {
close(fd);
return;
diff --git a/platform/linuxbsd/joypad_linux.h b/platform/linuxbsd/joypad_linux.h
index 26a9908d4e..bf24d8e5a5 100644
--- a/platform/linuxbsd/joypad_linux.h
+++ b/platform/linuxbsd/joypad_linux.h
@@ -94,6 +94,21 @@ private:
Vector<String> attached_devices;
+ // List of lowercase words that will prevent the controller from being recognized if its name matches.
+ // This is done to prevent trackpads, graphics tablets and motherboard LED controllers from being
+ // recognized as controllers (and taking up controller ID slots as a result).
+ // Only whole words are matched within the controller name string. The match is case-insensitive.
+ const Vector<String> banned_words = {
+ "touchpad", // Matches e.g. "SynPS/2 Synaptics TouchPad", "Sony Interactive Entertainment DualSense Wireless Controller Touchpad"
+ "trackpad",
+ "clickpad",
+ "keyboard", // Matches e.g. "PG-90215 Keyboard", "Usb Keyboard Usb Keyboard Consumer Control"
+ "mouse", // Matches e.g. "Mouse passthrough"
+ "pen", // Matches e.g. "Wacom One by Wacom S Pen"
+ "finger", // Matches e.g. "Wacom HID 495F Finger"
+ "led", // Matches e.g. "ASRock LED Controller"
+ };
+
static void monitor_joypads_thread_func(void *p_user);
void monitor_joypads_thread_run();
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
index adc9beed66..93096fcdcc 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.cpp
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -1238,7 +1238,7 @@ void DisplayServerWayland::process_events() {
} else {
try_suspend();
}
- } else if (wayland_thread.get_reset_frame()) {
+ } else if (!wayland_thread.is_suspended() || wayland_thread.get_reset_frame()) {
// At last, a sign of life! We're no longer suspended.
suspended = false;
}
diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp
index 1ef17657b6..ab13105d18 100644
--- a/platform/linuxbsd/wayland/wayland_thread.cpp
+++ b/platform/linuxbsd/wayland/wayland_thread.cpp
@@ -1263,23 +1263,25 @@ 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);
- wl_surface_commit(ss->cursor_surface);
+ if (!ss->wl_pointer) {
+ ss->cursor_surface = wl_compositor_create_surface(ss->registry->wl_compositor);
+ wl_surface_commit(ss->cursor_surface);
- ss->wl_pointer = wl_seat_get_pointer(wl_seat);
- wl_pointer_add_listener(ss->wl_pointer, &wl_pointer_listener, ss);
+ ss->wl_pointer = wl_seat_get_pointer(wl_seat);
+ wl_pointer_add_listener(ss->wl_pointer, &wl_pointer_listener, ss);
- if (ss->registry->wp_relative_pointer_manager) {
- ss->wp_relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(ss->registry->wp_relative_pointer_manager, ss->wl_pointer);
- zwp_relative_pointer_v1_add_listener(ss->wp_relative_pointer, &wp_relative_pointer_listener, ss);
- }
+ if (ss->registry->wp_relative_pointer_manager) {
+ ss->wp_relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(ss->registry->wp_relative_pointer_manager, ss->wl_pointer);
+ zwp_relative_pointer_v1_add_listener(ss->wp_relative_pointer, &wp_relative_pointer_listener, ss);
+ }
- if (ss->registry->wp_pointer_gestures) {
- ss->wp_pointer_gesture_pinch = zwp_pointer_gestures_v1_get_pinch_gesture(ss->registry->wp_pointer_gestures, ss->wl_pointer);
- zwp_pointer_gesture_pinch_v1_add_listener(ss->wp_pointer_gesture_pinch, &wp_pointer_gesture_pinch_listener, ss);
- }
+ if (ss->registry->wp_pointer_gestures) {
+ ss->wp_pointer_gesture_pinch = zwp_pointer_gestures_v1_get_pinch_gesture(ss->registry->wp_pointer_gestures, ss->wl_pointer);
+ zwp_pointer_gesture_pinch_v1_add_listener(ss->wp_pointer_gesture_pinch, &wp_pointer_gesture_pinch_listener, ss);
+ }
- // TODO: Constrain new pointers if the global mouse mode is constrained.
+ // TODO: Constrain new pointers if the global mouse mode is constrained.
+ }
} else {
if (ss->cursor_frame_callback) {
// Just in case. I got bitten by weird race-like conditions already.
@@ -1317,11 +1319,13 @@ void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat
// Keyboard handling.
if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
- ss->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
- ERR_FAIL_NULL(ss->xkb_context);
+ if (!ss->wl_keyboard) {
+ ss->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ ERR_FAIL_NULL(ss->xkb_context);
- ss->wl_keyboard = wl_seat_get_keyboard(wl_seat);
- wl_keyboard_add_listener(ss->wl_keyboard, &wl_keyboard_listener, ss);
+ ss->wl_keyboard = wl_seat_get_keyboard(wl_seat);
+ wl_keyboard_add_listener(ss->wl_keyboard, &wl_keyboard_listener, ss);
+ }
} else {
if (ss->xkb_context) {
xkb_context_unref(ss->xkb_context);
@@ -2047,11 +2051,21 @@ void WaylandThread::_wp_relative_pointer_on_relative_motion(void *data, struct z
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
+ if (!ss->pointed_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
PointerData &pd = ss->pointer_data_buffer;
+ WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
+ ERR_FAIL_NULL(ws);
+
pd.relative_motion.x = wl_fixed_to_double(dx);
pd.relative_motion.y = wl_fixed_to_double(dy);
+ pd.relative_motion *= window_state_get_scale_factor(ws);
+
pd.relative_motion_time = uptime_lo;
}
@@ -2244,13 +2258,11 @@ void WaylandThread::_wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v
void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
-
if (!ts) {
return;
}
SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
-
if (!ss) {
return;
}
@@ -2270,14 +2282,17 @@ void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_too
}
void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) {
- TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
+ if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) {
+ // We're probably on a decoration or something.
+ return;
+ }
+ TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
if (!ts) {
return;
}
SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
-
if (!ss) {
return;
}
@@ -2299,13 +2314,12 @@ void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_table
void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
-
- if (!ts) {
+ if (!ts || !ts->data_pending.proximal_surface) {
+ // Not our stuff, we don't care.
return;
}
SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
-
if (!ss) {
return;
}
@@ -2326,7 +2340,6 @@ void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tabl
void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial) {
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
-
if (!ts) {
return;
}
@@ -2344,7 +2357,6 @@ void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v
void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) {
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
-
if (!ts) {
return;
}
@@ -2360,11 +2372,15 @@ void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2
void WaylandThread::_wp_tablet_tool_on_motion(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t x, wl_fixed_t y) {
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
-
if (!ts) {
return;
}
+ if (!ts->data_pending.proximal_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
WindowState *ws = wl_surface_get_window_state(ts->data_pending.proximal_surface);
ERR_FAIL_NULL(ws);
@@ -2381,7 +2397,6 @@ void WaylandThread::_wp_tablet_tool_on_motion(void *data, struct zwp_tablet_tool
void WaylandThread::_wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t pressure) {
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
-
if (!ts) {
return;
}
@@ -2395,7 +2410,6 @@ 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 *wp_tablet_tool_v2, wl_fixed_t tilt_x, wl_fixed_t tilt_y) {
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
-
if (!ts) {
return;
}
@@ -2420,7 +2434,6 @@ 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 *wp_tablet_tool_v2, uint32_t serial, uint32_t button, uint32_t state) {
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
-
if (!ts) {
return;
}
@@ -2456,13 +2469,11 @@ 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 *wp_tablet_tool_v2, uint32_t time) {
TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2);
-
if (!ts) {
return;
}
SeatState *ss = wl_seat_get_seat_state(ts->wl_seat);
-
if (!ss) {
return;
}
@@ -3240,6 +3251,8 @@ void WaylandThread::window_create(DisplayServer::WindowID p_window_id, int p_wid
zxdg_exported_v1_add_listener(ws.xdg_exported, &xdg_exported_listener, &ws);
}
+ wl_surface_commit(ws.wl_surface);
+
// Wait for the surface to be configured before continuing.
wl_display_roundtrip(wl_display);
}
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index edf3a40ccb..8a2f83be2d 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -1519,7 +1519,7 @@ Color DisplayServerX11::screen_get_pixel(const Point2i &p_position) const {
if (image) {
XColor c;
c.pixel = XGetPixel(image, 0, 0);
- XFree(image);
+ XDestroyImage(image);
XQueryColor(x11_display, XDefaultColormap(x11_display, i), &c);
color = Color(float(c.red) / 65535.0, float(c.green) / 65535.0, float(c.blue) / 65535.0, 1.0);
break;
@@ -1637,11 +1637,12 @@ Ref<Image> DisplayServerX11::screen_get_image(int p_screen) const {
}
}
} else {
- XFree(image);
- ERR_FAIL_V_MSG(Ref<Image>(), vformat("XImage with RGB mask %x %x %x and depth %d is not supported.", (uint64_t)image->red_mask, (uint64_t)image->green_mask, (uint64_t)image->blue_mask, (int64_t)image->bits_per_pixel));
+ String msg = vformat("XImage with RGB mask %x %x %x and depth %d is not supported.", (uint64_t)image->red_mask, (uint64_t)image->green_mask, (uint64_t)image->blue_mask, (int64_t)image->bits_per_pixel);
+ XDestroyImage(image);
+ ERR_FAIL_V_MSG(Ref<Image>(), msg);
}
img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, img_data);
- XFree(image);
+ XDestroyImage(image);
}
return img;
@@ -1734,7 +1735,7 @@ Vector<DisplayServer::WindowID> DisplayServerX11::get_window_list() const {
return ret;
}
-DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) {
+DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, bool p_exclusive, WindowID p_transient_parent) {
_THREAD_SAFE_METHOD_
WindowID id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect);
@@ -1748,6 +1749,11 @@ DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, V
rendering_device->screen_create(id);
}
#endif
+
+ if (p_transient_parent != INVALID_WINDOW_ID) {
+ window_set_transient(id, p_transient_parent);
+ }
+
return id;
}
@@ -4964,6 +4970,23 @@ void DisplayServerX11::process_events() {
pos = Point2i(windows[focused_window_id].size.width / 2, windows[focused_window_id].size.height / 2);
}
+ BitField<MouseButtonMask> last_button_state = 0;
+ if (event.xmotion.state & Button1Mask) {
+ last_button_state.set_flag(MouseButtonMask::LEFT);
+ }
+ if (event.xmotion.state & Button2Mask) {
+ last_button_state.set_flag(MouseButtonMask::MIDDLE);
+ }
+ if (event.xmotion.state & Button3Mask) {
+ last_button_state.set_flag(MouseButtonMask::RIGHT);
+ }
+ if (event.xmotion.state & Button4Mask) {
+ last_button_state.set_flag(MouseButtonMask::MB_XBUTTON1);
+ }
+ if (event.xmotion.state & Button5Mask) {
+ last_button_state.set_flag(MouseButtonMask::MB_XBUTTON2);
+ }
+
Ref<InputEventMouseMotion> mm;
mm.instantiate();
@@ -4971,13 +4994,13 @@ void DisplayServerX11::process_events() {
if (xi.pressure_supported) {
mm->set_pressure(xi.pressure);
} else {
- mm->set_pressure(bool(mouse_get_button_state().has_flag(MouseButtonMask::LEFT)) ? 1.0f : 0.0f);
+ mm->set_pressure(bool(last_button_state.has_flag(MouseButtonMask::LEFT)) ? 1.0f : 0.0f);
}
mm->set_tilt(xi.tilt);
mm->set_pen_inverted(xi.pen_inverted);
_get_key_modifier_state(event.xmotion.state, mm);
- mm->set_button_mask(mouse_get_button_state());
+ mm->set_button_mask(last_button_state);
mm->set_position(pos);
mm->set_global_position(pos);
mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h
index 341ba5f079..0cbfbe51ef 100644
--- a/platform/linuxbsd/x11/display_server_x11.h
+++ b/platform/linuxbsd/x11/display_server_x11.h
@@ -438,7 +438,7 @@ public:
virtual Vector<DisplayServer::WindowID> get_window_list() const override;
- virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()) override;
+ virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i(), bool p_exclusive = false, WindowID p_transient_parent = INVALID_WINDOW_ID) override;
virtual void show_window(WindowID p_id) override;
virtual void delete_sub_window(WindowID p_id) override;
diff --git a/platform/macos/SCsub b/platform/macos/SCsub
index c965e875c1..a10262c524 100644
--- a/platform/macos/SCsub
+++ b/platform/macos/SCsub
@@ -23,10 +23,10 @@ def generate_bundle(target, source, env):
prefix += ".double"
# Lipo editor executable.
- target_bin = lipo(bin_dir + "/" + prefix, env.extra_suffix)
+ target_bin = lipo(bin_dir + "/" + prefix, env.extra_suffix + env.module_version_string)
# Assemble .app bundle and update version info.
- app_dir = Dir("#bin/" + (prefix + env.extra_suffix).replace(".", "_") + ".app").abspath
+ app_dir = Dir("#bin/" + (prefix + env.extra_suffix + env.module_version_string).replace(".", "_") + ".app").abspath
templ = Dir("#misc/dist/macos_tools.app").abspath
if os.path.exists(app_dir):
shutil.rmtree(app_dir)
@@ -35,6 +35,8 @@ def generate_bundle(target, source, env):
os.mkdir(app_dir + "/Contents/MacOS")
if target_bin != "":
shutil.copy(target_bin, app_dir + "/Contents/MacOS/Godot")
+ if "mono" in env.module_version_string:
+ shutil.copytree(Dir("#bin/GodotSharp").abspath, app_dir + "/Contents/Resources/GodotSharp")
version = get_build_version(False)
short_version = get_build_version(True)
with open(Dir("#misc/dist/macos").abspath + "/editor_info_plist.template", "rt", encoding="utf-8") as fin:
@@ -76,8 +78,8 @@ def generate_bundle(target, source, env):
dbg_prefix += ".double"
# Lipo template executables.
- rel_target_bin = lipo(bin_dir + "/" + rel_prefix, env.extra_suffix)
- dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, env.extra_suffix)
+ rel_target_bin = lipo(bin_dir + "/" + rel_prefix, env.extra_suffix + env.module_version_string)
+ dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, env.extra_suffix + env.module_version_string)
# Assemble .app bundle.
app_dir = Dir("#bin/macos_template.app").abspath
@@ -93,7 +95,7 @@ def generate_bundle(target, source, env):
shutil.copy(dbg_target_bin, app_dir + "/Contents/MacOS/godot_macos_debug.universal")
# ZIP .app bundle.
- zip_dir = Dir("#bin/" + (app_prefix + env.extra_suffix).replace(".", "_")).abspath
+ zip_dir = Dir("#bin/" + (app_prefix + env.extra_suffix + env.module_version_string).replace(".", "_")).abspath
shutil.make_archive(zip_dir, "zip", root_dir=bin_dir, base_dir="macos_template.app")
shutil.rmtree(app_dir)
diff --git a/platform/macos/detect.py b/platform/macos/detect.py
index 70cb00c6ff..bfd18bea99 100644
--- a/platform/macos/detect.py
+++ b/platform/macos/detect.py
@@ -56,6 +56,7 @@ def get_flags():
return {
"arch": detect_arch(),
"use_volk": False,
+ "metal": True,
"supported": ["mono"],
}
@@ -96,6 +97,8 @@ def configure(env: "SConsEnvironment"):
env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.13"])
env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.13"])
+ env.Append(CCFLAGS=["-ffp-contract=off"])
+
cc_version = get_compiler_version(env)
cc_version_major = cc_version["apple_major"]
cc_version_minor = cc_version["apple_minor"]
@@ -237,9 +240,22 @@ def configure(env: "SConsEnvironment"):
env.Append(LINKFLAGS=["-rpath", "@executable_path/../Frameworks", "-rpath", "@executable_path"])
+ if env["metal"] and env["arch"] != "arm64":
+ # Only supported on arm64, so skip it for x86_64 builds.
+ env["metal"] = False
+
+ extra_frameworks = set()
+
+ if env["metal"]:
+ env.AppendUnique(CPPDEFINES=["METAL_ENABLED", "RD_ENABLED"])
+ extra_frameworks.add("Metal")
+ extra_frameworks.add("MetalKit")
+ env.Prepend(CPPPATH=["#thirdparty/spirv-cross"])
+
if env["vulkan"]:
- env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
- env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "IOSurface"])
+ env.AppendUnique(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
+ extra_frameworks.add("Metal")
+ extra_frameworks.add("IOSurface")
if not env["use_volk"]:
env.Append(LINKFLAGS=["-lMoltenVK"])
@@ -258,3 +274,7 @@ def configure(env: "SConsEnvironment"):
"MoltenVK SDK installation directory not found, use 'vulkan_sdk_path' SCons parameter to specify SDK path."
)
sys.exit(255)
+
+ if len(extra_frameworks) > 0:
+ frameworks = [item for key in extra_frameworks for item in ["-framework", key]]
+ env.Append(LINKFLAGS=frameworks)
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index b4741dc08f..97af6d0a5a 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -47,6 +47,9 @@
#if defined(VULKAN_ENABLED)
#include "rendering_context_driver_vulkan_macos.h"
#endif // VULKAN_ENABLED
+#if defined(METAL_ENABLED)
+#include "drivers/metal/rendering_context_driver_metal.h"
+#endif
#endif // RD_ENABLED
#define BitMap _QDBitMap // Suppress deprecated QuickDraw definition.
@@ -326,7 +329,7 @@ public:
virtual Vector<int> get_window_list() const override;
- virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()) override;
+ virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i(), bool p_exclusive = false, WindowID p_transient_parent = INVALID_WINDOW_ID) override;
virtual void show_window(WindowID p_id) override;
virtual void delete_sub_window(WindowID p_id) override;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index a1a91345ac..3e0a5efe52 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -139,12 +139,20 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
#ifdef VULKAN_ENABLED
RenderingContextDriverVulkanMacOS::WindowPlatformData vulkan;
#endif
+#ifdef METAL_ENABLED
+ RenderingContextDriverMetal::WindowPlatformData metal;
+#endif
} wpd;
#ifdef VULKAN_ENABLED
if (rendering_driver == "vulkan") {
wpd.vulkan.layer_ptr = (CAMetalLayer *const *)&layer;
}
#endif
+#ifdef METAL_ENABLED
+ if (rendering_driver == "metal") {
+ wpd.metal.layer = (CAMetalLayer *)layer;
+ }
+#endif
Error err = rendering_context->window_create(window_id_counter, &wpd);
ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, vformat("Can't create a %s context", rendering_driver));
@@ -568,23 +576,7 @@ void DisplayServerMacOS::menu_callback(id p_sender) {
}
GodotMenuItem *value = [p_sender representedObject];
-
if (value) {
- if (value->max_states > 0) {
- value->state++;
- if (value->state >= value->max_states) {
- value->state = 0;
- }
- }
-
- if (value->checkable_type == CHECKABLE_TYPE_CHECK_BOX) {
- if ([p_sender state] == NSControlStateValueOff) {
- [p_sender setState:NSControlStateValueOn];
- } else {
- [p_sender setState:NSControlStateValueOff];
- }
- }
-
if (value->callback.is_valid()) {
MenuCall mc;
mc.tag = value->meta;
@@ -1730,7 +1722,7 @@ Vector<DisplayServer::WindowID> DisplayServerMacOS::get_window_list() const {
return ret;
}
-DisplayServer::WindowID DisplayServerMacOS::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) {
+DisplayServer::WindowID DisplayServerMacOS::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, bool p_exclusive, WindowID p_transient_parent) {
_THREAD_SAFE_METHOD_
WindowID id = _create_window(p_mode, p_vsync_mode, p_rect);
@@ -1744,6 +1736,12 @@ DisplayServer::WindowID DisplayServerMacOS::create_sub_window(WindowMode p_mode,
rendering_device->screen_create(id);
}
#endif
+
+ window_set_exclusive(id, p_exclusive);
+ if (p_transient_parent != INVALID_WINDOW_ID) {
+ window_set_transient(id, p_transient_parent);
+ }
+
return id;
}
@@ -2342,7 +2340,7 @@ void DisplayServerMacOS::window_set_window_buttons_offset(const Vector2i &p_offs
wd.wb_offset = p_offset / scale;
wd.wb_offset = wd.wb_offset.maxi(12);
if (wd.window_button_view) {
- [wd.window_button_view setOffset:NSMakePoint(wd.wb_offset.x, wd.wb_offset.y)];
+ [(GodotButtonView *)wd.window_button_view setOffset:NSMakePoint(wd.wb_offset.x, wd.wb_offset.y)];
}
}
@@ -2710,7 +2708,7 @@ void DisplayServerMacOS::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_
gl_manager_legacy->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);
}
#endif
-#if defined(VULKAN_ENABLED)
+#if defined(RD_ENABLED)
if (rendering_context) {
rendering_context->window_set_vsync_mode(p_window, p_vsync_mode);
}
@@ -2727,7 +2725,7 @@ DisplayServer::VSyncMode DisplayServerMacOS::window_get_vsync_mode(WindowID p_wi
return (gl_manager_legacy->is_using_vsync() ? DisplayServer::VSyncMode::VSYNC_ENABLED : DisplayServer::VSyncMode::VSYNC_DISABLED);
}
#endif
-#if defined(VULKAN_ENABLED)
+#if defined(RD_ENABLED)
if (rendering_context) {
return rendering_context->window_get_vsync_mode(p_window);
}
@@ -3311,6 +3309,9 @@ Vector<String> DisplayServerMacOS::get_rendering_drivers_func() {
#if defined(VULKAN_ENABLED)
drivers.push_back("vulkan");
#endif
+#if defined(METAL_ENABLED)
+ drivers.push_back("metal");
+#endif
#if defined(GLES3_ENABLED)
drivers.push_back("opengl3");
drivers.push_back("opengl3_angle");
@@ -3633,6 +3634,11 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
rendering_context = memnew(RenderingContextDriverVulkanMacOS);
}
#endif
+#if defined(METAL_ENABLED)
+ if (rendering_driver == "metal") {
+ rendering_context = memnew(RenderingContextDriverMetal);
+ }
+#endif
if (rendering_context) {
if (rendering_context->initialize() != OK) {
diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
index 92ade4b77a..34ad52bbf6 100644
--- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
+++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
@@ -224,16 +224,16 @@
Indicates whether your app uses advertising data for tracking.
</member>
<member name="privacy/collected_data/audio_data/collected" type="bool" setter="" getter="">
- Indicates whether your app collects audio data data.
+ Indicates whether your app collects audio data.
</member>
<member name="privacy/collected_data/audio_data/collection_purposes" type="int" setter="" getter="">
The reasons your app collects audio data. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests]Describing data use in privacy manifests[/url].
</member>
<member name="privacy/collected_data/audio_data/linked_to_user" type="bool" setter="" getter="">
- Indicates whether your app links audio data data to the user's identity.
+ Indicates whether your app links audio data to the user's identity.
</member>
<member name="privacy/collected_data/audio_data/used_for_tracking" type="bool" setter="" getter="">
- Indicates whether your app uses audio data data for tracking.
+ Indicates whether your app uses audio data for tracking.
</member>
<member name="privacy/collected_data/browsing_history/collected" type="bool" setter="" getter="">
Indicates whether your app collects browsing history.
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 73e2f2d45b..290b0082fc 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -141,7 +141,7 @@ String EditorExportPlatformMacOS::get_export_option_warning(const EditorExportPr
if (p_name == "codesign/codesign") {
if (dist_type == 2) {
- if (codesign_tool == 2 && Engine::get_singleton()->has_singleton("GodotSharp")) {
+ if (codesign_tool == 2 && ClassDB::class_exists("CSharpScript")) {
return TTR("'rcodesign' doesn't support signing applications with embedded dynamic libraries (GDExtension or .NET).");
}
if (codesign_tool == 0) {
@@ -333,7 +333,7 @@ bool EditorExportPlatformMacOS::get_export_option_visibility(const EditorExportP
}
// These entitlements are required to run managed code, and are always enabled in Mono builds.
- if (Engine::get_singleton()->has_singleton("GodotSharp")) {
+ if (ClassDB::class_exists("CSharpScript")) {
if (p_option == "codesign/entitlements/allow_jit_code_execution" || p_option == "codesign/entitlements/allow_unsigned_executable_memory" || p_option == "codesign/entitlements/allow_dyld_environment_variables") {
return false;
}
@@ -458,6 +458,7 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/additional_plist_content", PROPERTY_HINT_MULTILINE_TEXT), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/platform_build"), "14C18"));
+ // TODO(sgc): Need to set appropriate version when using Metal
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/sdk_version"), "13.1"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/sdk_build"), "22C55"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/sdk_name"), "macosx13.1"));
@@ -1064,7 +1065,7 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres
return OK;
}
-Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_warn, bool p_set_id) {
+void EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_warn, bool p_set_id) {
int codesign_tool = p_preset->get("codesign/codesign");
switch (codesign_tool) {
case 1: { // built-in ad-hoc
@@ -1074,7 +1075,7 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre
Error err = CodeSign::codesign(false, true, p_path, p_ent_path, error_msg);
if (err != OK) {
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Built-in CodeSign failed with error \"%s\"."), error_msg));
- return Error::FAILED;
+ return;
}
#else
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Built-in CodeSign require regex module."));
@@ -1086,13 +1087,13 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre
String rcodesign = EDITOR_GET("export/macos/rcodesign").operator String();
if (rcodesign.is_empty()) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Xrcodesign path is not set. Configure rcodesign path in the Editor Settings (Export > macOS > rcodesign)."));
- return Error::FAILED;
+ return;
}
List<String> args;
args.push_back("sign");
- if (p_path.get_extension() != "dmg") {
+ if (!p_ent_path.is_empty()) {
args.push_back("--entitlements-xml-path");
args.push_back(p_ent_path);
}
@@ -1124,13 +1125,13 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre
Error err = OS::get_singleton()->execute(rcodesign, args, &str, &exitcode, true);
if (err != OK) {
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start rcodesign executable."));
- return err;
+ return;
}
if (exitcode != 0) {
print_line("rcodesign (" + p_path + "):\n" + str);
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Code signing failed, see editor log for details."));
- return Error::FAILED;
+ return;
} else {
print_verbose("rcodesign (" + p_path + "):\n" + str);
}
@@ -1141,7 +1142,7 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre
if (!FileAccess::exists("/usr/bin/codesign") && !FileAccess::exists("/bin/codesign")) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Xcode command line tools are not installed."));
- return Error::FAILED;
+ return;
}
bool ad_hoc = (p_preset->get("codesign/identity") == "" || p_preset->get("codesign/identity") == "-");
@@ -1153,7 +1154,7 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre
args.push_back("runtime");
}
- if (p_path.get_extension() != "dmg") {
+ if (!p_ent_path.is_empty()) {
args.push_back("--entitlements");
args.push_back(p_ent_path);
}
@@ -1190,13 +1191,13 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre
Error err = OS::get_singleton()->execute("codesign", args, &str, &exitcode, true);
if (err != OK) {
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start codesign executable, make sure Xcode command line tools are installed."));
- return err;
+ return;
}
if (exitcode != 0) {
print_line("codesign (" + p_path + "):\n" + str);
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Code signing failed, see editor log for details."));
- return Error::FAILED;
+ return;
} else {
print_verbose("codesign (" + p_path + "):\n" + str);
}
@@ -1205,14 +1206,13 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre
default: {
};
}
-
- return OK;
}
-Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path,
+void EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path,
const String &p_ent_path, const String &p_helper_ent_path, bool p_should_error_on_non_code) {
static Vector<String> extensions_to_sign;
+ bool sandbox = p_preset->get("codesign/entitlements/app_sandbox/enabled");
if (extensions_to_sign.is_empty()) {
extensions_to_sign.push_back("dylib");
extensions_to_sign.push_back("framework");
@@ -1223,7 +1223,8 @@ Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPres
Ref<DirAccess> dir_access{ DirAccess::open(p_path, &dir_access_error) };
if (dir_access_error != OK) {
- return dir_access_error;
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Cannot sign directory %s."), p_path));
+ return;
}
dir_access->list_dir_begin();
@@ -1237,44 +1238,35 @@ Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPres
}
if (extensions_to_sign.has(current_file.get_extension())) {
- String ent_path = p_ent_path;
+ String ent_path;
bool set_bundle_id = false;
- if (FileAccess::exists(current_file_path)) {
+ if (sandbox && FileAccess::exists(current_file_path)) {
int ftype = MachO::get_filetype(current_file_path);
if (ftype == 2 || ftype == 5) {
ent_path = p_helper_ent_path;
set_bundle_id = true;
}
}
- Error code_sign_error{ _code_sign(p_preset, current_file_path, ent_path, false, set_bundle_id) };
- if (code_sign_error != OK) {
- return code_sign_error;
- }
+ _code_sign(p_preset, current_file_path, ent_path, false, set_bundle_id);
if (is_executable(current_file_path)) {
// chmod with 0755 if the file is executable.
FileAccess::set_unix_permissions(current_file_path, 0755);
}
} else if (dir_access->current_is_dir()) {
- Error code_sign_error{ _code_sign_directory(p_preset, current_file_path, p_ent_path, p_helper_ent_path, p_should_error_on_non_code) };
- if (code_sign_error != OK) {
- return code_sign_error;
- }
+ _code_sign_directory(p_preset, current_file_path, p_ent_path, p_helper_ent_path, p_should_error_on_non_code);
} else if (p_should_error_on_non_code) {
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Cannot sign file %s."), current_file));
- return Error::FAILED;
}
current_file = dir_access->get_next();
}
-
- return OK;
}
Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref<DirAccess> &dir_access, const String &p_src_path,
const String &p_in_app_path, bool p_sign_enabled,
const Ref<EditorExportPreset> &p_preset, const String &p_ent_path,
const String &p_helper_ent_path,
- bool p_should_error_on_non_code_sign) {
+ bool p_should_error_on_non_code_sign, bool p_sandbox) {
static Vector<String> extensions_to_sign;
if (extensions_to_sign.is_empty()) {
@@ -1363,19 +1355,19 @@ Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref<DirAccess> &dir_access
if (err == OK && p_sign_enabled) {
if (dir_access->dir_exists(p_src_path) && p_src_path.get_extension().is_empty()) {
// If it is a directory, find and sign all dynamic libraries.
- err = _code_sign_directory(p_preset, p_in_app_path, p_ent_path, p_helper_ent_path, p_should_error_on_non_code_sign);
+ _code_sign_directory(p_preset, p_in_app_path, p_ent_path, p_helper_ent_path, p_should_error_on_non_code_sign);
} else {
if (extensions_to_sign.has(p_in_app_path.get_extension())) {
- String ent_path = p_ent_path;
+ String ent_path;
bool set_bundle_id = false;
- if (FileAccess::exists(p_in_app_path)) {
+ if (p_sandbox && FileAccess::exists(p_in_app_path)) {
int ftype = MachO::get_filetype(p_in_app_path);
if (ftype == 2 || ftype == 5) {
ent_path = p_helper_ent_path;
set_bundle_id = true;
}
}
- err = _code_sign(p_preset, p_in_app_path, ent_path, false, set_bundle_id);
+ _code_sign(p_preset, p_in_app_path, ent_path, false, set_bundle_id);
}
if (dir_access->file_exists(p_in_app_path) && is_executable(p_in_app_path)) {
// chmod with 0755 if the file is executable.
@@ -1389,13 +1381,13 @@ Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref<DirAccess> &dir_access
Error EditorExportPlatformMacOS::_export_macos_plugins_for(Ref<EditorExportPlugin> p_editor_export_plugin,
const String &p_app_path_name, Ref<DirAccess> &dir_access,
bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset,
- const String &p_ent_path, const String &p_helper_ent_path) {
+ const String &p_ent_path, const String &p_helper_ent_path, bool p_sandbox) {
Error error{ OK };
const Vector<String> &macos_plugins{ p_editor_export_plugin->get_macos_plugin_files() };
for (int i = 0; i < macos_plugins.size(); ++i) {
String src_path{ ProjectSettings::get_singleton()->globalize_path(macos_plugins[i]) };
String path_in_app{ p_app_path_name + "/Contents/PlugIns/" + src_path.get_file() };
- error = _copy_and_sign_files(dir_access, src_path, path_in_app, p_sign_enabled, p_preset, p_ent_path, p_helper_ent_path, false);
+ error = _copy_and_sign_files(dir_access, src_path, path_in_app, p_sign_enabled, p_preset, p_ent_path, p_helper_ent_path, false, p_sandbox);
if (error != OK) {
break;
}
@@ -1988,7 +1980,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
ent_f->store_line("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
ent_f->store_line("<plist version=\"1.0\">");
ent_f->store_line("<dict>");
- if (Engine::get_singleton()->has_singleton("GodotSharp")) {
+ if (ClassDB::class_exists("CSharpScript")) {
// These entitlements are required to run managed code, and are always enabled in Mono builds.
ent_f->store_line("<key>com.apple.security.cs.allow-jit</key>");
ent_f->store_line("<true/>");
@@ -2156,7 +2148,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
String hlp_path = helpers[i];
err = da->copy(hlp_path, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file());
if (err == OK && sign_enabled) {
- err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), hlp_ent_path, false, true);
+ _code_sign(p_preset, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), hlp_ent_path, false, true);
}
FileAccess::set_unix_permissions(tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), 0755);
}
@@ -2168,11 +2160,11 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
String src_path = ProjectSettings::get_singleton()->globalize_path(shared_objects[i].path);
if (shared_objects[i].target.is_empty()) {
String path_in_app = tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file();
- err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, hlp_ent_path, true);
+ err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, hlp_ent_path, true, sandbox);
} else {
String path_in_app = tmp_app_path_name.path_join(shared_objects[i].target);
tmp_app_dir->make_dir_recursive(path_in_app);
- err = _copy_and_sign_files(da, src_path, path_in_app.path_join(src_path.get_file()), sign_enabled, p_preset, ent_path, hlp_ent_path, false);
+ err = _copy_and_sign_files(da, src_path, path_in_app.path_join(src_path.get_file()), sign_enabled, p_preset, ent_path, hlp_ent_path, false, sandbox);
}
if (err != OK) {
break;
@@ -2181,7 +2173,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
Vector<Ref<EditorExportPlugin>> export_plugins{ EditorExport::get_singleton()->get_export_plugins() };
for (int i = 0; i < export_plugins.size(); ++i) {
- err = _export_macos_plugins_for(export_plugins[i], tmp_app_path_name, da, sign_enabled, p_preset, ent_path, hlp_ent_path);
+ err = _export_macos_plugins_for(export_plugins[i], tmp_app_path_name, da, sign_enabled, p_preset, ent_path, hlp_ent_path, sandbox);
if (err != OK) {
break;
}
@@ -2201,7 +2193,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
if (ep.step(TTR("Code signing bundle"), 2)) {
return ERR_SKIP;
}
- err = _code_sign(p_preset, tmp_app_path_name, ent_path, true, false);
+ _code_sign(p_preset, tmp_app_path_name, ent_path, true, false);
}
String noto_path = p_path;
@@ -2219,7 +2211,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
if (ep.step(TTR("Code signing DMG"), 3)) {
return ERR_SKIP;
}
- err = _code_sign(p_preset, p_path, ent_path, false, false);
+ _code_sign(p_preset, p_path, ent_path, false, false);
}
} else if (export_format == "pkg") {
// Create a Installer.
diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h
index 6134d756b9..062a2e5f95 100644
--- a/platform/macos/export/export_plugin.h
+++ b/platform/macos/export/export_plugin.h
@@ -90,14 +90,14 @@ class EditorExportPlatformMacOS : public EditorExportPlatform {
void _make_icon(const Ref<EditorExportPreset> &p_preset, const Ref<Image> &p_icon, Vector<uint8_t> &p_data);
Error _notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path);
- Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_warn = true, bool p_set_id = false);
- Error _code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, const String &p_helper_ent_path, bool p_should_error_on_non_code = true);
+ void _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_warn = true, bool p_set_id = false);
+ void _code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, const String &p_helper_ent_path, bool p_should_error_on_non_code = true);
Error _copy_and_sign_files(Ref<DirAccess> &dir_access, const String &p_src_path, const String &p_in_app_path,
bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, const String &p_ent_path, const String &p_helper_ent_path,
- bool p_should_error_on_non_code_sign);
+ bool p_should_error_on_non_code_sign, bool p_sandbox);
Error _export_macos_plugins_for(Ref<EditorExportPlugin> p_editor_export_plugin, const String &p_app_path_name,
Ref<DirAccess> &dir_access, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset,
- const String &p_ent_path, const String &p_helper_ent_path);
+ const String &p_ent_path, const String &p_helper_ent_path, bool p_sandbox);
Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name);
Error _create_pkg(const Ref<EditorExportPreset> &p_preset, const String &p_pkg_path, const String &p_app_path_name);
Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path);
diff --git a/platform/macos/gl_manager_macos_legacy.h b/platform/macos/gl_manager_macos_legacy.h
index af9be8f5ba..383c5c3306 100644
--- a/platform/macos/gl_manager_macos_legacy.h
+++ b/platform/macos/gl_manager_macos_legacy.h
@@ -62,6 +62,7 @@ class GLManagerLegacy_MacOS {
Error create_context(GLWindow &win);
+ bool framework_loaded = false;
bool use_vsync = false;
CGLEnablePtr CGLEnable = nullptr;
CGLSetParameterPtr CGLSetParameter = nullptr;
diff --git a/platform/macos/gl_manager_macos_legacy.mm b/platform/macos/gl_manager_macos_legacy.mm
index 6ce3831d9c..a0d037144e 100644
--- a/platform/macos/gl_manager_macos_legacy.mm
+++ b/platform/macos/gl_manager_macos_legacy.mm
@@ -32,6 +32,7 @@
#if defined(MACOS_ENABLED) && defined(GLES3_ENABLED)
+#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
@@ -156,7 +157,7 @@ void GLManagerLegacy_MacOS::window_set_per_pixel_transparency_enabled(DisplaySer
}
Error GLManagerLegacy_MacOS::initialize() {
- return OK;
+ return framework_loaded ? OK : ERR_CANT_CREATE;
}
void GLManagerLegacy_MacOS::set_use_vsync(bool p_use) {
@@ -186,12 +187,17 @@ NSOpenGLContext *GLManagerLegacy_MacOS::get_context(DisplayServer::WindowID p_wi
}
GLManagerLegacy_MacOS::GLManagerLegacy_MacOS() {
- CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
- CFBundleLoadExecutable(framework);
-
- CGLEnable = (CGLEnablePtr)CFBundleGetFunctionPointerForName(framework, CFSTR("CGLEnable"));
- CGLSetParameter = (CGLSetParameterPtr)CFBundleGetFunctionPointerForName(framework, CFSTR("CGLSetParameter"));
- CGLGetCurrentContext = (CGLGetCurrentContextPtr)CFBundleGetFunctionPointerForName(framework, CFSTR("CGLGetCurrentContext"));
+ NSBundle *framework = [NSBundle bundleWithPath:@"/System/Library/Frameworks/OpenGL.framework"];
+ if (framework) {
+ void *library_handle = dlopen([framework.executablePath UTF8String], RTLD_NOW);
+ if (library_handle) {
+ CGLEnable = (CGLEnablePtr)dlsym(library_handle, "CGLEnable");
+ CGLSetParameter = (CGLSetParameterPtr)dlsym(library_handle, "CGLSetParameter");
+ CGLGetCurrentContext = (CGLGetCurrentContextPtr)dlsym(library_handle, "CGLGetCurrentContext");
+
+ framework_loaded = CGLEnable && CGLSetParameter && CGLGetCurrentContext;
+ }
+ }
}
GLManagerLegacy_MacOS::~GLManagerLegacy_MacOS() {
diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm
index 77f3a28ae7..7d43ac9fe6 100644
--- a/platform/macos/godot_content_view.mm
+++ b/platform/macos/godot_content_view.mm
@@ -329,8 +329,9 @@
Callable::CallError ce;
wd.drop_files_callback.callp((const Variant **)&v_args, 1, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(wd.drop_files_callback, v_args, 1, ce)));
+ ERR_FAIL_V_MSG(NO, vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(wd.drop_files_callback, v_args, 1, ce)));
}
+ return YES;
}
return NO;
diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h
index b6e2d41c08..e1af317259 100644
--- a/platform/macos/godot_menu_item.h
+++ b/platform/macos/godot_menu_item.h
@@ -52,6 +52,7 @@ enum GlobalMenuCheckType {
Callable hover_callback;
Variant meta;
GlobalMenuCheckType checkable_type;
+ bool checked;
int max_states;
int state;
Ref<Image> img;
diff --git a/platform/macos/godot_menu_item.mm b/platform/macos/godot_menu_item.mm
index 30dac9be9b..479542113a 100644
--- a/platform/macos/godot_menu_item.mm
+++ b/platform/macos/godot_menu_item.mm
@@ -31,4 +31,18 @@
#include "godot_menu_item.h"
@implementation GodotMenuItem
+
+- (id)init {
+ self = [super init];
+
+ self->callback = Callable();
+ self->key_callback = Callable();
+ self->checkable_type = GlobalMenuCheckType::CHECKABLE_TYPE_NONE;
+ self->checked = false;
+ self->max_states = 0;
+ self->state = 0;
+
+ return self;
+}
+
@end
diff --git a/platform/macos/joypad_macos.mm b/platform/macos/joypad_macos.mm
index 8cd5cdd9f2..beb32d9129 100644
--- a/platform/macos/joypad_macos.mm
+++ b/platform/macos/joypad_macos.mm
@@ -228,7 +228,7 @@ void JoypadMacOS::joypad_vibration_stop(Joypad *p_joypad, uint64_t p_timestamp)
@property(assign, nonatomic) BOOL isObserving;
@property(assign, nonatomic) BOOL isProcessing;
@property(strong, nonatomic) NSMutableDictionary<NSNumber *, Joypad *> *connectedJoypads;
-@property(strong, nonatomic) NSMutableArray<Joypad *> *joypadsQueue;
+@property(strong, nonatomic) NSMutableArray<GCController *> *joypadsQueue;
@end
@@ -364,8 +364,7 @@ void JoypadMacOS::joypad_vibration_stop(Joypad *p_joypad, uint64_t p_timestamp)
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];
+ [self.joypadsQueue addObject:controller];
} else {
[self addMacOSJoypad:controller];
}
diff --git a/platform/macos/native_menu_macos.mm b/platform/macos/native_menu_macos.mm
index 1ae1137ca0..802d58dc26 100644
--- a/platform/macos/native_menu_macos.mm
+++ b/platform/macos/native_menu_macos.mm
@@ -373,12 +373,7 @@ int NativeMenuMacOS::add_submenu_item(const RID &p_rid, const String &p_label, c
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()]];
@@ -417,9 +412,6 @@ int NativeMenuMacOS::add_item(const RID &p_rid, const String &p_label, const Cal
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];
}
@@ -438,8 +430,6 @@ int NativeMenuMacOS::add_check_item(const RID &p_rid, const String &p_label, con
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];
}
@@ -457,9 +447,6 @@ int NativeMenuMacOS::add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_ico
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() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
obj->img = p_icon->get_image();
@@ -489,8 +476,6 @@ int NativeMenuMacOS::add_icon_check_item(const RID &p_rid, const Ref<Texture2D>
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() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
obj->img = p_icon->get_image();
@@ -520,8 +505,6 @@ int NativeMenuMacOS::add_radio_check_item(const RID &p_rid, const String &p_labe
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];
}
@@ -540,8 +523,6 @@ int NativeMenuMacOS::add_icon_radio_check_item(const RID &p_rid, const Ref<Textu
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() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
obj->img = p_icon->get_image();
@@ -570,7 +551,6 @@ int NativeMenuMacOS::add_multistate_item(const RID &p_rid, const String &p_label
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)];
@@ -640,7 +620,10 @@ bool NativeMenuMacOS::is_item_checked(const RID &p_rid, int p_idx) const {
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);
+ const GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->checked;
+ }
}
return false;
}
@@ -958,10 +941,14 @@ void NativeMenuMacOS::set_item_checked(const RID &p_rid, int p_idx, bool p_check
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];
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ obj->checked = p_checked;
+ if (p_checked) {
+ [menu_item setState:NSControlStateValueOn];
+ } else {
+ [menu_item setState:NSControlStateValueOff];
+ }
}
}
}
diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h
index 912a682a6b..303fc112bf 100644
--- a/platform/macos/os_macos.h
+++ b/platform/macos/os_macos.h
@@ -109,6 +109,7 @@ public:
virtual String get_executable_path() const 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 create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override;
+ virtual bool is_process_running(const ProcessID &p_pid) const override;
virtual String get_unique_id() const override;
virtual String get_processor_name() const override;
diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm
index 9f0bea5951..3a82514766 100644
--- a/platform/macos/os_macos.mm
+++ b/platform/macos/os_macos.mm
@@ -666,6 +666,15 @@ Error OS_MacOS::create_instance(const List<String> &p_arguments, ProcessID *r_ch
}
}
+bool OS_MacOS::is_process_running(const ProcessID &p_pid) const {
+ NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:(pid_t)p_pid];
+ if (!app) {
+ return OS_Unix::is_process_running(p_pid);
+ }
+
+ return ![app isTerminated];
+}
+
String OS_MacOS::get_unique_id() const {
static String serial_number;
diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp
index dd986e650c..5e046d7050 100644
--- a/platform/web/audio_driver_web.cpp
+++ b/platform/web/audio_driver_web.cpp
@@ -33,6 +33,8 @@
#include "godot_audio.h"
#include "core/config/project_settings.h"
+#include "core/object/object.h"
+#include "scene/main/node.h"
#include "servers/audio/audio_stream.h"
#include <emscripten.h>
@@ -51,6 +53,21 @@ void AudioDriverWeb::_latency_update_callback(float p_latency) {
AudioDriverWeb::audio_context.output_latency = p_latency;
}
+void AudioDriverWeb::_sample_playback_finished_callback(const char *p_playback_object_id) {
+ const ObjectID playback_id = ObjectID(String::to_int(p_playback_object_id));
+
+ Object *playback_object = ObjectDB::get_instance(playback_id);
+ if (playback_object == nullptr) {
+ return;
+ }
+ Ref<AudioSamplePlayback> playback = Object::cast_to<AudioSamplePlayback>(playback_object);
+ if (playback.is_null()) {
+ return;
+ }
+
+ AudioServer::get_singleton()->stop_sample_playback(playback);
+}
+
void AudioDriverWeb::_audio_driver_process(int p_from, int p_samples) {
int32_t *stream_buffer = reinterpret_cast<int32_t *>(output_rb);
const int max_samples = memarr_len(output_rb);
@@ -132,6 +149,9 @@ Error AudioDriverWeb::init() {
if (!input_rb) {
return ERR_OUT_OF_MEMORY;
}
+
+ godot_audio_sample_set_finished_callback(&_sample_playback_finished_callback);
+
return OK;
}
@@ -292,6 +312,11 @@ bool AudioDriverWeb::is_sample_playback_active(const Ref<AudioSamplePlayback> &p
return godot_audio_sample_is_active(itos(p_playback->get_instance_id()).utf8().get_data()) != 0;
}
+double AudioDriverWeb::get_sample_playback_position(const Ref<AudioSamplePlayback> &p_playback) {
+ ERR_FAIL_COND_V_MSG(p_playback.is_null(), false, "Parameter p_playback is null.");
+ return godot_audio_get_sample_playback_position(itos(p_playback->get_instance_id()).utf8().get_data());
+}
+
void AudioDriverWeb::update_sample_playback_pitch_scale(const Ref<AudioSamplePlayback> &p_playback, float p_pitch_scale) {
ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null.");
godot_audio_sample_update_pitch_scale(
diff --git a/platform/web/audio_driver_web.h b/platform/web/audio_driver_web.h
index 298ad90fae..d352fa4692 100644
--- a/platform/web/audio_driver_web.h
+++ b/platform/web/audio_driver_web.h
@@ -58,6 +58,7 @@ private:
WASM_EXPORT static void _state_change_callback(int p_state);
WASM_EXPORT static void _latency_update_callback(float p_latency);
+ WASM_EXPORT static void _sample_playback_finished_callback(const char *p_playback_object_id);
static AudioDriverWeb *singleton;
@@ -95,6 +96,7 @@ public:
virtual void stop_sample_playback(const Ref<AudioSamplePlayback> &p_playback) override;
virtual void set_sample_playback_pause(const Ref<AudioSamplePlayback> &p_playback, bool p_paused) override;
virtual bool is_sample_playback_active(const Ref<AudioSamplePlayback> &p_playback) override;
+ virtual double get_sample_playback_position(const Ref<AudioSamplePlayback> &p_playback) override;
virtual void update_sample_playback_pitch_scale(const Ref<AudioSamplePlayback> &p_playback, float p_pitch_scale = 0.0f) override;
virtual void set_sample_playback_bus_volumes_linear(const Ref<AudioSamplePlayback> &p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes) override;
diff --git a/platform/web/detect.py b/platform/web/detect.py
index cb4dac1125..bf75c2f9fc 100644
--- a/platform/web/detect.py
+++ b/platform/web/detect.py
@@ -78,6 +78,7 @@ def get_flags():
# -Os reduces file size by around 5 MiB over -O3. -Oz only saves about
# 100 KiB over -Os, which does not justify the negative impact on
# run-time performance.
+ # Note that this overrides the "auto" behavior for target/dev_build.
"optimize": "size",
}
@@ -226,6 +227,11 @@ def configure(env: "SConsEnvironment"):
env.Append(LINKFLAGS=["-sDEFAULT_PTHREAD_STACK_SIZE=%sKB" % env["default_pthread_stack_size"]])
env.Append(LINKFLAGS=["-sPTHREAD_POOL_SIZE=8"])
env.Append(LINKFLAGS=["-sWASM_MEM_MAX=2048MB"])
+ if not env["dlink_enabled"]:
+ # Workaround https://github.com/emscripten-core/emscripten/issues/21844#issuecomment-2116936414.
+ # Not needed (and potentially dangerous) when dlink_enabled=yes, since we set EXPORT_ALL=1 in that case.
+ env.Append(LINKFLAGS=["-sEXPORTED_FUNCTIONS=['__emscripten_thread_crashed','_main']"])
+
elif env["proxy_to_pthread"]:
print_warning('"threads=no" support requires "proxy_to_pthread=no", disabling proxy to pthread.')
env["proxy_to_pthread"] = False
diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp
index 40de4e523b..4e55cc137a 100644
--- a/platform/web/display_server_web.cpp
+++ b/platform/web/display_server_web.cpp
@@ -902,8 +902,10 @@ void DisplayServerWeb::process_joypads() {
for (int b = 0; b < s_btns_num; b++) {
// Buttons 6 and 7 in the standard mapping need to be
// axis to be handled as JoyAxis::TRIGGER by Godot.
- if (s_standard && (b == 6 || b == 7)) {
- input->joy_axis(idx, (JoyAxis)b, s_btns[b]);
+ if (s_standard && (b == 6)) {
+ input->joy_axis(idx, JoyAxis::TRIGGER_LEFT, s_btns[b]);
+ } else if (s_standard && (b == 7)) {
+ input->joy_axis(idx, JoyAxis::TRIGGER_RIGHT, s_btns[b]);
} else {
input->joy_button(idx, (JoyButton)b, s_btns[b]);
}
diff --git a/platform/web/emscripten_helpers.py b/platform/web/emscripten_helpers.py
index 2cee3e8110..8fcabb21c7 100644
--- a/platform/web/emscripten_helpers.py
+++ b/platform/web/emscripten_helpers.py
@@ -51,11 +51,13 @@ def create_template_zip(env, js, wasm, worker, side):
js,
wasm,
"#platform/web/js/libs/audio.worklet.js",
+ "#platform/web/js/libs/audio.position.worklet.js",
]
out_files = [
zip_dir.File(binary_name + ".js"),
zip_dir.File(binary_name + ".wasm"),
zip_dir.File(binary_name + ".audio.worklet.js"),
+ zip_dir.File(binary_name + ".audio.position.worklet.js"),
]
if env["threads"]:
in_files.append(worker)
@@ -74,6 +76,7 @@ def create_template_zip(env, js, wasm, worker, side):
"offline.html",
"godot.editor.js",
"godot.editor.audio.worklet.js",
+ "godot.editor.audio.position.worklet.js",
"logo.svg",
"favicon.png",
]
diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp
index d83e465e8e..d8c1b6033d 100644
--- a/platform/web/export/export_plugin.cpp
+++ b/platform/web/export/export_plugin.cpp
@@ -242,6 +242,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese
}
cache_files.push_back(name + ".worker.js");
cache_files.push_back(name + ".audio.worklet.js");
+ cache_files.push_back(name + ".audio.position.worklet.js");
replaces["___GODOT_CACHE___"] = Variant(cache_files).to_json_string();
// Heavy files that are cached on demand.
@@ -835,6 +836,7 @@ Error EditorExportPlatformWeb::_export_project(const Ref<EditorExportPreset> &p_
DirAccess::remove_file_or_error(basepath + ".js");
DirAccess::remove_file_or_error(basepath + ".worker.js");
DirAccess::remove_file_or_error(basepath + ".audio.worklet.js");
+ DirAccess::remove_file_or_error(basepath + ".audio.position.worklet.js");
DirAccess::remove_file_or_error(basepath + ".service.worker.js");
DirAccess::remove_file_or_error(basepath + ".pck");
DirAccess::remove_file_or_error(basepath + ".png");
diff --git a/platform/web/godot_audio.h b/platform/web/godot_audio.h
index 8bebbcf7de..4961ebd2bb 100644
--- a/platform/web/godot_audio.h
+++ b/platform/web/godot_audio.h
@@ -55,8 +55,10 @@ extern void godot_audio_sample_start(const char *p_playback_object_id, const cha
extern void godot_audio_sample_stop(const char *p_playback_object_id);
extern void godot_audio_sample_set_pause(const char *p_playback_object_id, bool p_pause);
extern int godot_audio_sample_is_active(const char *p_playback_object_id);
+extern double godot_audio_get_sample_playback_position(const char *p_playback_object_id);
extern void godot_audio_sample_update_pitch_scale(const char *p_playback_object_id, float p_pitch_scale);
extern void godot_audio_sample_set_volumes_linear(const char *p_playback_object_id, int *p_buses_buf, int p_buses_size, float *p_volumes_buf, int p_volumes_size);
+extern void godot_audio_sample_set_finished_callback(void (*p_callback)(const char *));
extern void godot_audio_sample_bus_set_count(int p_count);
extern void godot_audio_sample_bus_remove(int p_index);
diff --git a/platform/web/js/engine/config.js b/platform/web/js/engine/config.js
index 8c4e1b1b24..61b488cf81 100644
--- a/platform/web/js/engine/config.js
+++ b/platform/web/js/engine/config.js
@@ -299,6 +299,8 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
return `${loadPath}.worker.js`;
} else if (path.endsWith('.audio.worklet.js')) {
return `${loadPath}.audio.worklet.js`;
+ } else if (path.endsWith('.audio.position.worklet.js')) {
+ return `${loadPath}.audio.position.worklet.js`;
} else if (path.endsWith('.js')) {
return `${loadPath}.js`;
} else if (path in gdext) {
diff --git a/platform/web/js/libs/audio.position.worklet.js b/platform/web/js/libs/audio.position.worklet.js
new file mode 100644
index 0000000000..bf3ac4ae2d
--- /dev/null
+++ b/platform/web/js/libs/audio.position.worklet.js
@@ -0,0 +1,50 @@
+/**************************************************************************/
+/* godot.audio.position.worklet.js */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+class GodotPositionReportingProcessor extends AudioWorkletProcessor {
+ constructor() {
+ super();
+ this.position = 0;
+ }
+
+ process(inputs, _outputs, _parameters) {
+ if (inputs.length > 0) {
+ const input = inputs[0];
+ if (input.length > 0) {
+ this.position += input[0].length;
+ this.port.postMessage({ 'type': 'position', 'data': this.position });
+ return true;
+ }
+ }
+ return true;
+ }
+}
+
+registerProcessor('godot-position-reporting-processor', GodotPositionReportingProcessor);
diff --git a/platform/web/js/libs/library_godot_audio.js b/platform/web/js/libs/library_godot_audio.js
index 531dbdaeab..0ba6eed464 100644
--- a/platform/web/js/libs/library_godot_audio.js
+++ b/platform/web/js/libs/library_godot_audio.js
@@ -77,7 +77,7 @@ class Sample {
* Creates a `Sample` based on the params. Will register it to the
* `GodotAudio.samples` registry.
* @param {SampleParams} params Base params
- * @param {SampleOptions} [options={}] Optional params
+ * @param {SampleOptions} [options={{}}] Optional params
* @returns {Sample}
*/
static create(params, options = {}) {
@@ -98,8 +98,7 @@ class Sample {
/**
* `Sample` constructor.
* @param {SampleParams} params Base params
- * @param {SampleOptions} [options={}] Optional params
- * @constructor
+ * @param {SampleOptions} [options={{}}] Optional params
*/
constructor(params, options = {}) {
/** @type {string} */
@@ -154,7 +153,7 @@ class Sample {
if (this._audioBuffer == null) {
throw new Error('couldn\'t duplicate a null audioBuffer');
}
- /** @type {Float32Array[]} */
+ /** @type {Array<Float32Array>} */
const channels = new Array(this._audioBuffer.numberOfChannels);
for (let i = 0; i < this._audioBuffer.numberOfChannels; i++) {
const channel = new Float32Array(this._audioBuffer.getChannelData(i));
@@ -189,7 +188,6 @@ class SampleNodeBus {
/**
* `SampleNodeBus` constructor.
* @param {Bus} bus The bus related to the new `SampleNodeBus`.
- * @constructor
*/
constructor(bus) {
const NUMBER_OF_WEB_CHANNELS = 6;
@@ -332,6 +330,7 @@ class SampleNodeBus {
* startTime?: number
* loopMode?: LoopMode
* volume?: Float32Array
+ * start?: boolean
* }} SampleNodeOptions
*/
@@ -413,8 +412,7 @@ class SampleNode {
/**
* @param {SampleNodeParams} params Base params
- * @param {SampleNodeOptions} [options={}] Optional params
- * @constructor
+ * @param {SampleNodeOptions} [options={{}}] Optional params
*/
constructor(params, options = {}) {
/** @type {string} */
@@ -424,9 +422,15 @@ class SampleNode {
/** @type {number} */
this.offset = options.offset ?? 0;
/** @type {number} */
+ this._playbackPosition = options.offset;
+ /** @type {number} */
this.startTime = options.startTime ?? 0;
/** @type {boolean} */
this.isPaused = false;
+ /** @type {boolean} */
+ this.isStarted = false;
+ /** @type {boolean} */
+ this.isCanceled = false;
/** @type {number} */
this.pauseTime = 0;
/** @type {number} */
@@ -441,8 +445,10 @@ class SampleNode {
this._sampleNodeBuses = new Map();
/** @type {AudioBufferSourceNode | null} */
this._source = GodotAudio.ctx.createBufferSource();
- /** @type {AudioBufferSourceNode["onended"]} */
+
this._onended = null;
+ /** @type {AudioWorkletNode | null} */
+ this._positionWorklet = null;
this.setPlaybackRate(options.playbackRate ?? 44100);
this._source.buffer = this.getSample().getAudioBuffer();
@@ -452,6 +458,8 @@ class SampleNode {
const bus = GodotAudio.Bus.getBus(params.busIndex);
const sampleNodeBus = this.getSampleNodeBus(bus);
sampleNodeBus.setVolume(options.volume);
+
+ this.connectPositionWorklet(options.start);
}
/**
@@ -463,6 +471,14 @@ class SampleNode {
}
/**
+ * Gets the playback position.
+ * @returns {number}
+ */
+ getPlaybackPosition() {
+ return this._playbackPosition;
+ }
+
+ /**
* Sets the playback rate.
* @param {number} val Value to set.
* @returns {void}
@@ -511,8 +527,12 @@ class SampleNode {
* @returns {void}
*/
start() {
+ if (this.isStarted) {
+ return;
+ }
this._resetSourceStartTime();
this._source.start(this.startTime, this.offset);
+ this.isStarted = true;
}
/**
@@ -558,7 +578,7 @@ class SampleNode {
/**
* Sets the volumes of the `SampleNode` for each buses passed in parameters.
- * @param {Bus[]} buses
+ * @param {Array<Bus>} buses
* @param {Float32Array} volumes
*/
setVolumes(buses, volumes) {
@@ -588,17 +608,73 @@ class SampleNode {
}
/**
+ * Sets up and connects the source to the GodotPositionReportingProcessor
+ * If the worklet module is not loaded in, it will be added
+ */
+ connectPositionWorklet(start) {
+ try {
+ this._positionWorklet = this.createPositionWorklet();
+ this._source.connect(this._positionWorklet);
+ if (start) {
+ this.start();
+ }
+ } catch (error) {
+ if (error?.name !== 'InvalidStateError') {
+ throw error;
+ }
+ const path = GodotConfig.locate_file('godot.audio.position.worklet.js');
+ GodotAudio.ctx.audioWorklet
+ .addModule(path)
+ .then(() => {
+ if (!this.isCanceled) {
+ this._positionWorklet = this.createPositionWorklet();
+ this._source.connect(this._positionWorklet);
+ if (start) {
+ this.start();
+ }
+ }
+ }).catch((addModuleError) => {
+ GodotRuntime.error('Failed to create PositionWorklet.', addModuleError);
+ });
+ }
+ }
+
+ /**
+ * Creates the AudioWorkletProcessor used to track playback position.
+ * @returns {AudioWorkletNode}
+ */
+ createPositionWorklet() {
+ const worklet = new AudioWorkletNode(
+ GodotAudio.ctx,
+ 'godot-position-reporting-processor'
+ );
+ worklet.port.onmessage = (event) => {
+ switch (event.data['type']) {
+ case 'position':
+ this._playbackPosition = (parseInt(event.data.data, 10) / this.getSample().sampleRate) + this.offset;
+ break;
+ default:
+ // Do nothing.
+ }
+ };
+ return worklet;
+ }
+
+ /**
* Clears the `SampleNode`.
* @returns {void}
*/
clear() {
+ this.isCanceled = true;
this.isPaused = false;
this.pauseTime = 0;
if (this._source != null) {
this._source.removeEventListener('ended', this._onended);
this._onended = null;
- this._source.stop();
+ if (this.isStarted) {
+ this._source.stop();
+ }
this._source.disconnect();
this._source = null;
}
@@ -608,6 +684,12 @@ class SampleNode {
}
this._sampleNodeBuses.clear();
+ if (this._positionWorklet) {
+ this._positionWorklet.disconnect();
+ this._positionWorklet.port.onmessage = null;
+ this._positionWorklet = null;
+ }
+
GodotAudio.SampleNode.delete(this.id);
}
@@ -633,7 +715,9 @@ class SampleNode {
* @returns {void}
*/
_restart() {
- this._source.disconnect();
+ if (this._source != null) {
+ this._source.disconnect();
+ }
this._source = GodotAudio.ctx.createBufferSource();
this._source.buffer = this.getSample().getAudioBuffer();
@@ -646,7 +730,9 @@ class SampleNode {
const pauseTime = this.isPaused
? this.pauseTime
: 0;
+ this.connectPositionWorklet();
this._source.start(this.startTime, this.offset + pauseTime);
+ this.isStarted = true;
}
/**
@@ -687,9 +773,15 @@ class SampleNode {
}
switch (self.getSample().loopMode) {
- case 'disabled':
+ case 'disabled': {
+ const id = this.id;
self.stop();
- break;
+ if (GodotAudio.sampleFinishedCallback != null) {
+ const idCharPtr = GodotRuntime.allocString(id);
+ GodotAudio.sampleFinishedCallback(idCharPtr);
+ GodotRuntime.free(idCharPtr);
+ }
+ } break;
case 'forward':
case 'backward':
self.restart();
@@ -812,7 +904,6 @@ class Bus {
/**
* `Bus` constructor.
- * @constructor
*/
constructor() {
/** @type {Set<SampleNode>} */
@@ -856,7 +947,10 @@ class Bus {
* @returns {void}
*/
setVolumeDb(val) {
- this._gainNode.gain.value = GodotAudio.db_to_linear(val);
+ const linear = GodotAudio.db_to_linear(val);
+ if (isFinite(linear)) {
+ this._gainNode.gain.value = linear;
+ }
}
/**
@@ -979,7 +1073,6 @@ class Bus {
GodotAudio.buses = GodotAudio.buses.filter((v) => v !== this);
}
- /** @type {Bus["prototype"]["_syncSampleNodes"]} */
_syncSampleNodes() {
const sampleNodes = Array.from(this._sampleNodes);
for (let i = 0; i < sampleNodes.length; i++) {
@@ -1080,7 +1173,7 @@ const _GodotAudio = {
// `Bus` class
/**
* Registry of `Bus`es.
- * @type {Bus[]}
+ * @type {Array<Bus>}
*/
buses: null,
/**
@@ -1090,6 +1183,12 @@ const _GodotAudio = {
busSolo: null,
Bus,
+ /**
+ * Callback to signal that a sample has finished.
+ * @type {(playbackObjectIdPtr: number) => void | null}
+ */
+ sampleFinishedCallback: null,
+
/** @type {AudioContext} */
ctx: null,
input: null,
@@ -1250,7 +1349,7 @@ const _GodotAudio = {
startOptions
) {
GodotAudio.SampleNode.stopSampleNode(playbackObjectId);
- const sampleNode = GodotAudio.SampleNode.create(
+ GodotAudio.SampleNode.create(
{
busIndex,
id: playbackObjectId,
@@ -1258,7 +1357,6 @@ const _GodotAudio = {
},
startOptions
);
- sampleNode.start();
},
/**
@@ -1297,7 +1395,7 @@ const _GodotAudio = {
/**
* Triggered when a sample node volumes need to be updated.
* @param {string} playbackObjectId Id of the sample playback
- * @param {number[]} busIndexes Indexes of the buses that need to be updated
+ * @param {Array<number>} busIndexes Indexes of the buses that need to be updated
* @param {Float32Array} volumes Array of the volumes
* @returns {void}
*/
@@ -1578,6 +1676,7 @@ const _GodotAudio = {
offset,
volume,
playbackRate: 1,
+ start: true,
};
GodotAudio.start_sample(
playbackObjectId,
@@ -1623,6 +1722,22 @@ const _GodotAudio = {
return Number(GodotAudio.sampleNodes.has(playbackObjectId));
},
+ godot_audio_get_sample_playback_position__proxy: 'sync',
+ godot_audio_get_sample_playback_position__sig: 'di',
+ /**
+ * Returns the position of the playback position.
+ * @param {number} playbackObjectIdStrPtr Playback object id pointer
+ * @returns {number}
+ */
+ godot_audio_get_sample_playback_position: function (playbackObjectIdStrPtr) {
+ const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
+ const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(playbackObjectId);
+ if (sampleNode == null) {
+ return 0;
+ }
+ return sampleNode.getPlaybackPosition();
+ },
+
godot_audio_sample_update_pitch_scale__proxy: 'sync',
godot_audio_sample_update_pitch_scale__sig: 'vii',
/**
@@ -1764,6 +1879,17 @@ const _GodotAudio = {
godot_audio_sample_bus_set_mute: function (bus, enable) {
GodotAudio.set_sample_bus_mute(bus, Boolean(enable));
},
+
+ godot_audio_sample_set_finished_callback__proxy: 'sync',
+ godot_audio_sample_set_finished_callback__sig: 'vi',
+ /**
+ * Sets the finished callback
+ * @param {Number} callbackPtr Finished callback pointer
+ * @returns {void}
+ */
+ godot_audio_sample_set_finished_callback: function (callbackPtr) {
+ GodotAudio.sampleFinishedCallback = GodotRuntime.get_func(callbackPtr);
+ },
};
autoAddDeps(_GodotAudio, '$GodotAudio');
diff --git a/platform/web/js/libs/library_godot_input.js b/platform/web/js/libs/library_godot_input.js
index 7ea89d553f..6e3b97023d 100644
--- a/platform/web/js/libs/library_godot_input.js
+++ b/platform/web/js/libs/library_godot_input.js
@@ -112,6 +112,7 @@ const GodotIME = {
ime.style.top = '0px';
ime.style.width = '100%';
ime.style.height = '40px';
+ ime.style.pointerEvents = 'none';
ime.style.display = 'none';
ime.contentEditable = 'true';
diff --git a/platform/web/serve.py b/platform/web/serve.py
index f0b0ec9622..4e1521449b 100755
--- a/platform/web/serve.py
+++ b/platform/web/serve.py
@@ -6,7 +6,7 @@ import os
import socket
import subprocess
import sys
-from http.server import HTTPServer, SimpleHTTPRequestHandler, test # type: ignore
+from http.server import HTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
@@ -38,12 +38,24 @@ def shell_open(url):
def serve(root, port, run_browser):
os.chdir(root)
+ address = ("", port)
+ httpd = DualStackServer(address, CORSRequestHandler)
+
+ url = f"http://127.0.0.1:{port}"
if run_browser:
# Open the served page in the user's default 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}")
+ print(f"Opening the served URL in the default browser (use `--no-browser` or `-n` to disable this): {url}")
+ shell_open(url)
+ else:
+ print(f"Serving at: {url}")
- test(CORSRequestHandler, DualStackServer, port=port)
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ print("\nKeyboard interrupt received, stopping server.")
+ finally:
+ # Clean-up server
+ httpd.server_close()
if __name__ == "__main__":
diff --git a/platform/windows/SCsub b/platform/windows/SCsub
index f2fb8616ae..f8ed8b73f5 100644
--- a/platform/windows/SCsub
+++ b/platform/windows/SCsub
@@ -108,18 +108,6 @@ if env["d3d12"]:
# Used in cases where we can have multiple archs side-by-side.
arch_bin_dir = "#bin/" + env["arch"]
- # DXC
- if env["dxc_path"] != "" and os.path.exists(env["dxc_path"]):
- dxc_dll = "dxil.dll"
- # Whether this one is loaded from arch-specific directory or not can be determined at runtime.
- # Let's copy to both and let the user decide the distribution model.
- for v in ["#bin", arch_bin_dir]:
- env.Command(
- v + "/" + dxc_dll,
- env["dxc_path"] + "/bin/" + dxc_arch_subdir + "/" + dxc_dll,
- Copy("$TARGET", "$SOURCE"),
- )
-
# Agility SDK
if env["agility_sdk_path"] != "" and os.path.exists(env["agility_sdk_path"]):
agility_dlls = ["D3D12Core.dll", "d3d12SDKLayers.dll"]
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index fee306a25c..b4830c5908 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -214,11 +214,6 @@ def get_opts():
os.path.join(d3d12_deps_folder, "mesa"),
),
(
- "dxc_path",
- "Path to the DirectX Shader Compiler distribution (required for D3D12)",
- os.path.join(d3d12_deps_folder, "dxc"),
- ),
- (
"agility_sdk_path",
"Path to the Agility SDK distribution (optional for D3D12)",
os.path.join(d3d12_deps_folder, "agility_sdk"),
@@ -306,7 +301,6 @@ def setup_msvc_manual(env: "SConsEnvironment"):
print("Using VCVARS-determined MSVC, arch %s" % (env_arch))
-# FIXME: Likely overwrites command-line options for the msvc compiler. See #91883.
def setup_msvc_auto(env: "SConsEnvironment"):
"""Set up MSVC using SCons's auto-detection logic"""
@@ -339,6 +333,12 @@ def setup_msvc_auto(env: "SConsEnvironment"):
env.Tool("msvc")
env.Tool("mssdk") # we want the MS SDK
+ # Re-add potentially overwritten flags.
+ env.AppendUnique(CCFLAGS=env.get("ccflags", "").split())
+ env.AppendUnique(CXXFLAGS=env.get("cxxflags", "").split())
+ env.AppendUnique(CFLAGS=env.get("cflags", "").split())
+ env.AppendUnique(RCFLAGS=env.get("rcflags", "").split())
+
# Note: actual compiler version can be found in env['MSVC_VERSION'], e.g. "14.1" for VS2015
print("Using SCons-detected MSVC version %s, arch %s" % (env["MSVC_VERSION"], env["arch"]))
@@ -467,6 +467,8 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
if env["arch"] == "x86_32":
env["x86_libtheora_opt_vc"] = True
+ env.Append(CCFLAGS=["/fp:strict"])
+
env.AppendUnique(CCFLAGS=["/Gd", "/GR", "/nologo"])
env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding.
env.AppendUnique(CXXFLAGS=["/TP"]) # assume all sources are C++
@@ -644,7 +646,8 @@ def configure_mingw(env: "SConsEnvironment"):
# TODO: Re-evaluate the need for this / streamline with common config.
if env["target"] == "template_release":
- env.Append(CCFLAGS=["-msse2"])
+ if env["arch"] != "arm64":
+ env.Append(CCFLAGS=["-msse2"])
elif env.dev_build:
# Allow big objects. It's supposed not to have drawbacks but seems to break
# GCC LTO, so enabling for debug builds only (which are not built with LTO
@@ -674,6 +677,8 @@ def configure_mingw(env: "SConsEnvironment"):
if env["arch"] in ["x86_32", "x86_64"]:
env["x86_libtheora_opt_gcc"] = True
+ env.Append(CCFLAGS=["-ffp-contract=off"])
+
mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"])
if env["use_llvm"]:
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 8d26a705a9..635e8326e2 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -38,6 +38,7 @@
#include "core/version.h"
#include "drivers/png/png_driver_common.h"
#include "main/main.h"
+#include "scene/resources/texture.h"
#if defined(VULKAN_ENABLED)
#include "rendering_context_driver_vulkan_windows.h"
@@ -249,6 +250,14 @@ void DisplayServerWindows::tts_stop() {
tts->stop();
}
+Error DisplayServerWindows::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ return _file_dialog_with_options_show(p_title, p_current_directory, String(), p_filename, p_show_hidden, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);
+}
+
+Error DisplayServerWindows::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) {
+ return _file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, true);
+}
+
// Silence warning due to a COM API weirdness.
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
@@ -376,22 +385,85 @@ public:
#pragma GCC diagnostic pop
#endif
-Error DisplayServerWindows::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
- return _file_dialog_with_options_show(p_title, p_current_directory, String(), p_filename, p_show_hidden, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);
+LRESULT CALLBACK WndProcFileDialog(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ DisplayServerWindows *ds_win = static_cast<DisplayServerWindows *>(DisplayServer::get_singleton());
+ if (ds_win) {
+ return ds_win->WndProcFileDialog(hWnd, uMsg, wParam, lParam);
+ } else {
+ return DefWindowProcW(hWnd, uMsg, wParam, lParam);
+ }
}
-Error DisplayServerWindows::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) {
- return _file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, true);
+LRESULT DisplayServerWindows::WndProcFileDialog(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ MutexLock lock(file_dialog_mutex);
+ if (file_dialog_wnd.has(hWnd)) {
+ if (file_dialog_wnd[hWnd]->close_requested.is_set()) {
+ IPropertyStore *prop_store;
+ HRESULT hr = SHGetPropertyStoreForWindow(hWnd, IID_IPropertyStore, (void **)&prop_store);
+ if (hr == S_OK) {
+ PROPVARIANT val;
+ PropVariantInit(&val);
+ prop_store->SetValue(PKEY_AppUserModel_ID, val);
+ prop_store->Release();
+ }
+ DestroyWindow(hWnd);
+ file_dialog_wnd.erase(hWnd);
+ }
+ }
+ return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
-Error DisplayServerWindows::_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) {
- _THREAD_SAFE_METHOD_
+void DisplayServerWindows::_thread_fd_monitor(void *p_ud) {
+ DisplayServerWindows *ds = static_cast<DisplayServerWindows *>(get_singleton());
+ FileDialogData *fd = (FileDialogData *)p_ud;
- ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+ if (fd->mode < 0 && fd->mode >= DisplayServer::FILE_DIALOG_MODE_SAVE_MAX) {
+ fd->finished.set();
+ return;
+ }
+ CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+ int64_t x = fd->wrect.position.x;
+ int64_t y = fd->wrect.position.y;
+ int64_t w = fd->wrect.size.x;
+ int64_t h = fd->wrect.size.y;
+
+ WNDCLASSW wc = {};
+ wc.lpfnWndProc = (WNDPROC)::WndProcFileDialog;
+ wc.hInstance = GetModuleHandle(nullptr);
+ wc.lpszClassName = L"Engine File Dialog";
+ RegisterClassW(&wc);
+
+ HWND hwnd_dialog = CreateWindowExW(WS_EX_APPWINDOW, L"Engine File Dialog", L"", WS_OVERLAPPEDWINDOW, x, y, w, h, nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
+ if (hwnd_dialog) {
+ {
+ MutexLock lock(ds->file_dialog_mutex);
+ ds->file_dialog_wnd[hwnd_dialog] = fd;
+ }
+
+ HICON mainwindow_icon = (HICON)SendMessage(fd->hwnd_owner, WM_GETICON, ICON_SMALL, 0);
+ if (mainwindow_icon) {
+ SendMessage(hwnd_dialog, WM_SETICON, ICON_SMALL, (LPARAM)mainwindow_icon);
+ }
+ mainwindow_icon = (HICON)SendMessage(fd->hwnd_owner, WM_GETICON, ICON_BIG, 0);
+ if (mainwindow_icon) {
+ SendMessage(hwnd_dialog, WM_SETICON, ICON_BIG, (LPARAM)mainwindow_icon);
+ }
+ IPropertyStore *prop_store;
+ HRESULT hr = SHGetPropertyStoreForWindow(hwnd_dialog, IID_IPropertyStore, (void **)&prop_store);
+ if (hr == S_OK) {
+ PROPVARIANT val;
+ InitPropVariantFromString((PCWSTR)fd->appid.utf16().get_data(), &val);
+ prop_store->SetValue(PKEY_AppUserModel_ID, val);
+ prop_store->Release();
+ }
+ }
+
+ SetCurrentProcessExplicitAppUserModelID((PCWSTR)fd->appid.utf16().get_data());
Vector<Char16String> filter_names;
Vector<Char16String> filter_exts;
- for (const String &E : p_filters) {
+ for (const String &E : fd->filters) {
Vector<String> tokens = E.split(";");
if (tokens.size() >= 1) {
String flt = tokens[0].strip_edges();
@@ -424,11 +496,9 @@ Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title
filters.push_back({ (LPCWSTR)filter_names[i].ptr(), (LPCWSTR)filter_exts[i].ptr() });
}
- WindowID prev_focus = last_focused_window;
-
HRESULT hr = S_OK;
IFileDialog *pfd = nullptr;
- if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) {
+ if (fd->mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
hr = CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC_SERVER, IID_IFileSaveDialog, (void **)&pfd);
} else {
hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_IFileOpenDialog, (void **)&pfd);
@@ -444,40 +514,32 @@ Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title
IFileDialogCustomize *pfdc = nullptr;
hr = pfd->QueryInterface(IID_PPV_ARGS(&pfdc));
- for (int i = 0; i < p_options.size(); i++) {
- const Dictionary &item = p_options[i];
+ for (int i = 0; i < fd->options.size(); i++) {
+ const Dictionary &item = fd->options[i];
if (!item.has("name") || !item.has("values") || !item.has("default")) {
continue;
}
- const String &name = item["name"];
- const Vector<String> &options = item["values"];
- int default_idx = item["default"];
-
- event_handler->add_option(pfdc, name, options, default_idx);
+ event_handler->add_option(pfdc, item["name"], item["values"], item["default_idx"]);
}
- event_handler->set_root(p_root);
+ event_handler->set_root(fd->root);
pfdc->Release();
DWORD flags;
pfd->GetOptions(&flags);
- if (p_mode == FILE_DIALOG_MODE_OPEN_FILES) {
+ if (fd->mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES) {
flags |= FOS_ALLOWMULTISELECT;
}
- if (p_mode == FILE_DIALOG_MODE_OPEN_DIR) {
+ if (fd->mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR) {
flags |= FOS_PICKFOLDERS;
}
- if (p_show_hidden) {
+ if (fd->show_hidden) {
flags |= FOS_FORCESHOWHIDDEN;
}
pfd->SetOptions(flags | FOS_FORCEFILESYSTEM);
- pfd->SetTitle((LPCWSTR)p_title.utf16().ptr());
+ pfd->SetTitle((LPCWSTR)fd->title.utf16().ptr());
- String dir = ProjectSettings::get_singleton()->globalize_path(p_current_directory);
- if (dir == ".") {
- dir = OS::get_singleton()->get_executable_path().get_base_dir();
- }
- dir = dir.replace("/", "\\");
+ String dir = fd->current_directory.replace("/", "\\");
IShellItem *shellitem = nullptr;
hr = SHCreateItemFromParsingName((LPCWSTR)dir.utf16().ptr(), nullptr, IID_IShellItem, (void **)&shellitem);
@@ -486,16 +548,11 @@ Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title
pfd->SetFolder(shellitem);
}
- pfd->SetFileName((LPCWSTR)p_filename.utf16().ptr());
+ pfd->SetFileName((LPCWSTR)fd->filename.utf16().ptr());
pfd->SetFileTypes(filters.size(), filters.ptr());
pfd->SetFileTypeIndex(0);
- WindowID window_id = _get_focused_window_or_popup();
- if (!windows.has(window_id)) {
- window_id = MAIN_WINDOW_ID;
- }
-
- hr = pfd->Show(windows[window_id].hWnd);
+ hr = pfd->Show(hwnd_dialog);
pfd->Unadvise(cookie);
Dictionary options = event_handler->get_selected();
@@ -512,7 +569,7 @@ Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title
if (SUCCEEDED(hr)) {
Vector<String> file_names;
- if (p_mode == FILE_DIALOG_MODE_OPEN_FILES) {
+ if (fd->mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES) {
IShellItemArray *results;
hr = static_cast<IFileOpenDialog *>(pfd)->GetResults(&results);
if (SUCCEEDED(hr)) {
@@ -545,73 +602,148 @@ Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title
result->Release();
}
}
- if (p_callback.is_valid()) {
- if (p_options_in_cb) {
+ if (fd->callback.is_valid()) {
+ if (fd->options_in_cb) {
Variant v_result = true;
Variant v_files = file_names;
Variant v_index = index;
Variant v_opt = options;
- Variant ret;
- Callable::CallError ce;
- const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+ const Variant *cb_args[4] = { &v_result, &v_files, &v_index, &v_opt };
- p_callback.callp(args, 4, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 4, ce)));
- }
+ fd->callback.call_deferredp(cb_args, 4);
} else {
Variant v_result = true;
Variant v_files = file_names;
Variant v_index = index;
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
+ const Variant *cb_args[3] = { &v_result, &v_files, &v_index };
- p_callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 3, ce)));
- }
+ fd->callback.call_deferredp(cb_args, 3);
}
}
} else {
- if (p_callback.is_valid()) {
- if (p_options_in_cb) {
+ if (fd->callback.is_valid()) {
+ if (fd->options_in_cb) {
Variant v_result = false;
Variant v_files = Vector<String>();
- Variant v_index = index;
- Variant v_opt = options;
- Variant ret;
- Callable::CallError ce;
- const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+ Variant v_index = 0;
+ Variant v_opt = Dictionary();
+ const Variant *cb_args[4] = { &v_result, &v_files, &v_index, &v_opt };
- p_callback.callp(args, 4, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 4, ce)));
- }
+ fd->callback.call_deferredp(cb_args, 4);
} else {
Variant v_result = false;
Variant v_files = Vector<String>();
- Variant v_index = index;
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
+ Variant v_index = 0;
+ const Variant *cb_args[3] = { &v_result, &v_files, &v_index };
- p_callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 3, ce)));
- }
+ fd->callback.call_deferredp(cb_args, 3);
}
}
}
pfd->Release();
- if (prev_focus != INVALID_WINDOW_ID) {
- callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus);
+ } else {
+ if (fd->callback.is_valid()) {
+ if (fd->options_in_cb) {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = 0;
+ Variant v_opt = Dictionary();
+ const Variant *cb_args[4] = { &v_result, &v_files, &v_index, &v_opt };
+
+ fd->callback.call_deferredp(cb_args, 4);
+ } else {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = 0;
+ const Variant *cb_args[3] = { &v_result, &v_files, &v_index };
+
+ fd->callback.call_deferredp(cb_args, 3);
+ }
+ }
+ }
+ {
+ MutexLock lock(ds->file_dialog_mutex);
+ if (hwnd_dialog && ds->file_dialog_wnd.has(hwnd_dialog)) {
+ IPropertyStore *prop_store;
+ hr = SHGetPropertyStoreForWindow(hwnd_dialog, IID_IPropertyStore, (void **)&prop_store);
+ if (hr == S_OK) {
+ PROPVARIANT val;
+ PropVariantInit(&val);
+ prop_store->SetValue(PKEY_AppUserModel_ID, val);
+ prop_store->Release();
+ }
+ DestroyWindow(hwnd_dialog);
+ ds->file_dialog_wnd.erase(hwnd_dialog);
}
+ }
+ UnregisterClassW(L"Engine File Dialog", GetModuleHandle(nullptr));
+ CoUninitialize();
+
+ fd->finished.set();
+
+ if (fd->window_id != INVALID_WINDOW_ID) {
+ callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd->window_id);
+ }
+}
- return OK;
+Error DisplayServerWindows::_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) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+
+ WindowID window_id = _get_focused_window_or_popup();
+ if (!windows.has(window_id)) {
+ window_id = MAIN_WINDOW_ID;
+ }
+ String appname;
+ if (Engine::get_singleton()->is_editor_hint()) {
+ appname = "Godot.GodotEditor." + String(VERSION_BRANCH);
} else {
- return ERR_CANT_OPEN;
+ 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;
+ }
+
+ FileDialogData *fd = memnew(FileDialogData);
+ if (window_id != INVALID_WINDOW_ID) {
+ fd->hwnd_owner = windows[window_id].hWnd;
+ RECT crect;
+ GetWindowRect(fd->hwnd_owner, &crect);
+ fd->wrect = Rect2i(crect.left, crect.top, crect.right - crect.left, crect.bottom - crect.top);
+ } else {
+ fd->hwnd_owner = 0;
+ fd->wrect = Rect2i(CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT);
}
+ fd->appid = appname;
+ fd->title = p_title;
+ fd->current_directory = p_current_directory;
+ fd->root = p_root;
+ fd->filename = p_filename;
+ fd->show_hidden = p_show_hidden;
+ fd->mode = p_mode;
+ fd->window_id = window_id;
+ fd->filters = p_filters;
+ fd->options = p_options;
+ fd->callback = p_callback;
+ fd->options_in_cb = p_options_in_cb;
+ fd->finished.clear();
+ fd->close_requested.clear();
+
+ fd->listener_thread.start(DisplayServerWindows::_thread_fd_monitor, fd);
+
+ file_dialogs.push_back(fd);
+
+ return OK;
}
void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) {
@@ -1305,10 +1437,10 @@ DisplayServer::WindowID DisplayServerWindows::get_window_at_screen_position(cons
return INVALID_WINDOW_ID;
}
-DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) {
+DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, bool p_exclusive, WindowID p_transient_parent) {
_THREAD_SAFE_METHOD_
- WindowID window_id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect);
+ WindowID window_id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect, p_exclusive, p_transient_parent);
ERR_FAIL_COND_V_MSG(window_id == INVALID_WINDOW_ID, INVALID_WINDOW_ID, "Failed to create sub window.");
WindowData &wd = windows[window_id];
@@ -1332,13 +1464,15 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod
wd.is_popup = true;
}
if (p_flags & WINDOW_FLAG_TRANSPARENT_BIT) {
- DWM_BLURBEHIND bb;
- ZeroMemory(&bb, sizeof(bb));
- HRGN hRgn = CreateRectRgn(0, 0, -1, -1);
- bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
- bb.hRgnBlur = hRgn;
- bb.fEnable = TRUE;
- DwmEnableBlurBehindWindow(wd.hWnd, &bb);
+ if (OS::get_singleton()->is_layered_allowed()) {
+ DWM_BLURBEHIND bb;
+ ZeroMemory(&bb, sizeof(bb));
+ HRGN hRgn = CreateRectRgn(0, 0, -1, -1);
+ bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
+ bb.hRgnBlur = hRgn;
+ bb.fEnable = TRUE;
+ DwmEnableBlurBehindWindow(wd.hWnd, &bb);
+ }
wd.layered_window = true;
}
@@ -2008,7 +2142,7 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
}
if (p_mode == WINDOW_MODE_WINDOWED) {
- ShowWindow(wd.hWnd, SW_RESTORE);
+ ShowWindow(wd.hWnd, SW_NORMAL);
wd.maximized = false;
wd.minimized = false;
}
@@ -2118,28 +2252,29 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W
} break;
case WINDOW_FLAG_TRANSPARENT: {
if (p_enabled) {
- //enable per-pixel alpha
-
- DWM_BLURBEHIND bb;
- ZeroMemory(&bb, sizeof(bb));
- HRGN hRgn = CreateRectRgn(0, 0, -1, -1);
- bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
- bb.hRgnBlur = hRgn;
- bb.fEnable = TRUE;
- DwmEnableBlurBehindWindow(wd.hWnd, &bb);
-
+ // Enable per-pixel alpha.
+ if (OS::get_singleton()->is_layered_allowed()) {
+ DWM_BLURBEHIND bb;
+ ZeroMemory(&bb, sizeof(bb));
+ HRGN hRgn = CreateRectRgn(0, 0, -1, -1);
+ bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
+ bb.hRgnBlur = hRgn;
+ bb.fEnable = TRUE;
+ DwmEnableBlurBehindWindow(wd.hWnd, &bb);
+ }
wd.layered_window = true;
} else {
- //disable per-pixel alpha
+ // Disable per-pixel alpha.
wd.layered_window = false;
-
- DWM_BLURBEHIND bb;
- ZeroMemory(&bb, sizeof(bb));
- HRGN hRgn = CreateRectRgn(0, 0, -1, -1);
- bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
- bb.hRgnBlur = hRgn;
- bb.fEnable = FALSE;
- DwmEnableBlurBehindWindow(wd.hWnd, &bb);
+ if (OS::get_singleton()->is_layered_allowed()) {
+ DWM_BLURBEHIND bb;
+ ZeroMemory(&bb, sizeof(bb));
+ HRGN hRgn = CreateRectRgn(0, 0, -1, -1);
+ bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
+ bb.hRgnBlur = hRgn;
+ bb.fEnable = FALSE;
+ DwmEnableBlurBehindWindow(wd.hWnd, &bb);
+ }
}
} break;
case WINDOW_FLAG_NO_FOCUS: {
@@ -2910,24 +3045,67 @@ Key DisplayServerWindows::keyboard_get_label_from_physical(Key p_keycode) const
return p_keycode;
}
-String _get_full_layout_name_from_registry(HKL p_layout) {
- String id = "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\" + String::num_int64((int64_t)p_layout, 16, false).lpad(8, "0");
+String DisplayServerWindows::_get_keyboard_layout_display_name(const String &p_klid) const {
String ret;
+ HKEY key;
+ if (RegOpenKeyW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts", &key) != ERROR_SUCCESS) {
+ return String();
+ }
- HKEY hkey;
- WCHAR layout_text[1024];
- memset(layout_text, 0, 1024 * sizeof(WCHAR));
-
- if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(id.utf16().get_data()), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) {
- return ret;
+ WCHAR buffer[MAX_PATH] = {};
+ DWORD buffer_size = MAX_PATH;
+ if (RegGetValueW(key, (LPCWSTR)p_klid.utf16().get_data(), L"Layout Display Name", RRF_RT_REG_SZ, nullptr, buffer, &buffer_size) == ERROR_SUCCESS) {
+ if (load_indirect_string) {
+ if (load_indirect_string(buffer, buffer, buffer_size, nullptr) == S_OK) {
+ ret = String::utf16((const char16_t *)buffer, buffer_size);
+ }
+ }
+ } else {
+ if (RegGetValueW(key, (LPCWSTR)p_klid.utf16().get_data(), L"Layout Text", RRF_RT_REG_SZ, nullptr, buffer, &buffer_size) == ERROR_SUCCESS) {
+ ret = String::utf16((const char16_t *)buffer, buffer_size);
+ }
}
- DWORD buffer = 1024;
- DWORD vtype = REG_SZ;
- if (RegQueryValueExW(hkey, L"Layout Text", nullptr, &vtype, (LPBYTE)layout_text, &buffer) == ERROR_SUCCESS) {
- ret = String::utf16((const char16_t *)layout_text);
+ RegCloseKey(key);
+ return ret;
+}
+
+String DisplayServerWindows::_get_klid(HKL p_hkl) const {
+ String ret;
+
+ WORD device = HIWORD(p_hkl);
+ if ((device & 0xf000) == 0xf000) {
+ WORD layout_id = device & 0x0fff;
+
+ HKEY key;
+ if (RegOpenKeyW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts", &key) != ERROR_SUCCESS) {
+ return String();
+ }
+
+ DWORD index = 0;
+ wchar_t klid_buffer[KL_NAMELENGTH];
+ DWORD klid_buffer_size = KL_NAMELENGTH;
+ while (RegEnumKeyExW(key, index, klid_buffer, &klid_buffer_size, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS) {
+ wchar_t layout_id_buf[MAX_PATH] = {};
+ DWORD layout_id_size = MAX_PATH;
+ if (RegGetValueW(key, klid_buffer, L"Layout Id", RRF_RT_REG_SZ, nullptr, layout_id_buf, &layout_id_size) == ERROR_SUCCESS) {
+ if (layout_id == String::utf16((char16_t *)layout_id_buf, layout_id_size).hex_to_int()) {
+ ret = String::utf16((const char16_t *)klid_buffer, klid_buffer_size).lpad(8, "0");
+ break;
+ }
+ }
+ klid_buffer_size = KL_NAMELENGTH;
+ ++index;
+ }
+
+ RegCloseKey(key);
+ } else {
+ if (device == 0) {
+ device = LOWORD(p_hkl);
+ }
+ ret = (String::num_uint64((uint64_t)device, 16, false)).lpad(8, "0");
}
- RegCloseKey(hkey);
+
return ret;
}
@@ -2939,7 +3117,7 @@ String DisplayServerWindows::keyboard_get_layout_name(int p_index) const {
HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));
GetKeyboardLayoutList(layout_count, layouts);
- String ret = _get_full_layout_name_from_registry(layouts[p_index]); // Try reading full name from Windows registry, fallback to locale name if failed (e.g. on Wine).
+ String ret = _get_keyboard_layout_display_name(_get_klid(layouts[p_index])); // Try reading full name from Windows registry, fallback to locale name if failed (e.g. on Wine).
if (ret.is_empty()) {
WCHAR buf[LOCALE_NAME_MAX_LENGTH];
memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(WCHAR));
@@ -2975,6 +3153,21 @@ void DisplayServerWindows::process_events() {
_process_key_events();
Input::get_singleton()->flush_buffered_events();
}
+
+ LocalVector<List<FileDialogData *>::Element *> to_remove;
+ for (List<FileDialogData *>::Element *E = file_dialogs.front(); E; E = E->next()) {
+ FileDialogData *fd = E->get();
+ if (fd->finished.is_set()) {
+ if (fd->listener_thread.is_started()) {
+ fd->listener_thread.wait_to_finish();
+ }
+ to_remove.push_back(E);
+ }
+ }
+ for (List<FileDialogData *>::Element *E : to_remove) {
+ memdelete(E->get());
+ E->erase();
+ }
}
void DisplayServerWindows::force_process_and_drop_events() {
@@ -3807,9 +4000,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
case WM_ACTIVATE: {
// Activation can happen just after the window has been created, even before the callbacks are set.
// Therefore, it's safer to defer the delivery of the event.
- if (!windows[window_id].activate_timer_id) {
- windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
- }
+ // It's important to set an nIDEvent different from the SetTimer for move_timer_id because
+ // if the same nIDEvent is passed, the timer is replaced and the same timer_id is returned.
+ windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_WINDOW_ACTIVATION, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
windows[window_id].activate_state = GET_WM_ACTIVATE_STATE(wParam, lParam);
return 0;
} break;
@@ -4160,6 +4353,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_focused || window_get_active_popup() == window_id) {
Input::get_singleton()->parse_input_event(mm);
}
@@ -4186,13 +4380,128 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
break;
}
+ pointer_button[GET_POINTERID_WPARAM(wParam)] = MouseButton::NONE;
windows[window_id].block_mm = true;
return 0;
} break;
case WM_POINTERLEAVE: {
+ pointer_button[GET_POINTERID_WPARAM(wParam)] = MouseButton::NONE;
windows[window_id].block_mm = false;
return 0;
} break;
+ case WM_POINTERDOWN:
+ case WM_POINTERUP: {
+ if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
+ break;
+ }
+
+ if ((tablet_get_current_driver() != "winink") || !winink_available) {
+ break;
+ }
+
+ uint32_t pointer_id = LOWORD(wParam);
+ POINTER_INPUT_TYPE pointer_type = PT_POINTER;
+ if (!win8p_GetPointerType(pointer_id, &pointer_type)) {
+ break;
+ }
+
+ if (pointer_type != PT_PEN) {
+ break;
+ }
+
+ Ref<InputEventMouseButton> mb;
+ mb.instantiate();
+ mb->set_window_id(window_id);
+
+ BitField<MouseButtonMask> last_button_state = 0;
+ if (IS_POINTER_FIRSTBUTTON_WPARAM(wParam)) {
+ last_button_state.set_flag(MouseButtonMask::LEFT);
+ mb->set_button_index(MouseButton::LEFT);
+ }
+ if (IS_POINTER_SECONDBUTTON_WPARAM(wParam)) {
+ last_button_state.set_flag(MouseButtonMask::RIGHT);
+ mb->set_button_index(MouseButton::RIGHT);
+ }
+ if (IS_POINTER_THIRDBUTTON_WPARAM(wParam)) {
+ last_button_state.set_flag(MouseButtonMask::MIDDLE);
+ mb->set_button_index(MouseButton::MIDDLE);
+ }
+ if (IS_POINTER_FOURTHBUTTON_WPARAM(wParam)) {
+ last_button_state.set_flag(MouseButtonMask::MB_XBUTTON1);
+ mb->set_button_index(MouseButton::MB_XBUTTON1);
+ }
+ if (IS_POINTER_FIFTHBUTTON_WPARAM(wParam)) {
+ last_button_state.set_flag(MouseButtonMask::MB_XBUTTON2);
+ mb->set_button_index(MouseButton::MB_XBUTTON2);
+ }
+ mb->set_button_mask(last_button_state);
+
+ const BitField<WinKeyModifierMask> &mods = _get_mods();
+ mb->set_ctrl_pressed(mods.has_flag(WinKeyModifierMask::CTRL));
+ mb->set_shift_pressed(mods.has_flag(WinKeyModifierMask::SHIFT));
+ mb->set_alt_pressed(mods.has_flag(WinKeyModifierMask::ALT));
+ mb->set_meta_pressed(mods.has_flag(WinKeyModifierMask::META));
+
+ POINT coords; // Client coords.
+ coords.x = GET_X_LPARAM(lParam);
+ coords.y = GET_Y_LPARAM(lParam);
+
+ // Note: Handle popup closing here, since mouse event is not emulated and hook will not be called.
+ uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup;
+ if (delta > 250) {
+ Point2i pos = Point2i(coords.x, coords.y) - _get_screens_origin();
+ List<WindowID>::Element *C = nullptr;
+ List<WindowID>::Element *E = popup_list.back();
+ // Find top popup to close.
+ while (E) {
+ // Popup window area.
+ Rect2i win_rect = Rect2i(window_get_position_with_decorations(E->get()), window_get_size_with_decorations(E->get()));
+ // Area of the parent window, which responsible for opening sub-menu.
+ Rect2i safe_rect = window_get_popup_safe_rect(E->get());
+ if (win_rect.has_point(pos)) {
+ break;
+ } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {
+ break;
+ } else {
+ C = E;
+ E = E->prev();
+ }
+ }
+ if (C) {
+ _send_window_event(windows[C->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST);
+ }
+ }
+
+ int64_t pen_id = GET_POINTERID_WPARAM(wParam);
+ if (uMsg == WM_POINTERDOWN) {
+ mb->set_pressed(true);
+ if (pointer_down_time.has(pen_id) && (pointer_prev_button[pen_id] == mb->get_button_index()) && (ABS(coords.y - pointer_last_pos[pen_id].y) < GetSystemMetrics(SM_CYDOUBLECLK)) && GetMessageTime() - pointer_down_time[pen_id] < (LONG)GetDoubleClickTime()) {
+ mb->set_double_click(true);
+ pointer_down_time[pen_id] = 0;
+ } else {
+ pointer_down_time[pen_id] = GetMessageTime();
+ pointer_prev_button[pen_id] = mb->get_button_index();
+ pointer_last_pos[pen_id] = Vector2(coords.x, coords.y);
+ }
+ pointer_button[pen_id] = mb->get_button_index();
+ } else {
+ if (!pointer_button.has(pen_id)) {
+ return 0;
+ }
+ mb->set_pressed(false);
+ mb->set_button_index(pointer_button[pen_id]);
+ pointer_button[pen_id] = MouseButton::NONE;
+ }
+
+ ScreenToClient(windows[window_id].hWnd, &coords);
+
+ mb->set_position(Vector2(coords.x, coords.y));
+ mb->set_global_position(Vector2(coords.x, coords.y));
+
+ Input::get_singleton()->parse_input_event(mb);
+
+ return 0;
+ } break;
case WM_POINTERUPDATE: {
if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
break;
@@ -4270,7 +4579,23 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
mm->set_alt_pressed(mods.has_flag(WinKeyModifierMask::ALT));
mm->set_meta_pressed(mods.has_flag(WinKeyModifierMask::META));
- mm->set_button_mask(mouse_get_button_state());
+ BitField<MouseButtonMask> last_button_state = 0;
+ if (IS_POINTER_FIRSTBUTTON_WPARAM(wParam)) {
+ last_button_state.set_flag(MouseButtonMask::LEFT);
+ }
+ if (IS_POINTER_SECONDBUTTON_WPARAM(wParam)) {
+ last_button_state.set_flag(MouseButtonMask::RIGHT);
+ }
+ if (IS_POINTER_THIRDBUTTON_WPARAM(wParam)) {
+ last_button_state.set_flag(MouseButtonMask::MIDDLE);
+ }
+ if (IS_POINTER_FOURTHBUTTON_WPARAM(wParam)) {
+ last_button_state.set_flag(MouseButtonMask::MB_XBUTTON1);
+ }
+ if (IS_POINTER_FIFTHBUTTON_WPARAM(wParam)) {
+ last_button_state.set_flag(MouseButtonMask::MB_XBUTTON2);
+ }
+ mm->set_button_mask(last_button_state);
POINT coords; // Client coords.
coords.x = GET_X_LPARAM(lParam);
@@ -4441,6 +4766,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
mm->set_position(mm->get_position() - window_get_position(receiving_window_id) + window_get_position(window_id));
mm->set_global_position(mm->get_position());
}
+
Input::get_singleton()->parse_input_event(mm);
} break;
@@ -4685,16 +5011,16 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
rect_changed = true;
}
#if defined(RD_ENABLED)
- if (rendering_context && window.context_created) {
+ if (window.create_completed && rendering_context && window.context_created) {
// Note: Trigger resize event to update swapchains when window is minimized/restored, even if size is not changed.
rendering_context->window_set_size(window_id, window.width, window.height);
}
#endif
#if defined(GLES3_ENABLED)
- if (gl_manager_native) {
+ if (window.create_completed && gl_manager_native) {
gl_manager_native->window_resize(window_id, window.width, window.height);
}
- if (gl_manager_angle) {
+ if (window.create_completed && gl_manager_angle) {
gl_manager_angle->window_resize(window_id, window.width, window.height);
}
#endif
@@ -4727,7 +5053,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
case WM_ENTERSIZEMOVE: {
Input::get_singleton()->release_pressed_events();
- windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
+ windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_MOVE_REDRAW, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
} break;
case WM_EXITSIZEMOVE: {
KillTimer(windows[window_id].hWnd, windows[window_id].move_timer_id);
@@ -5159,7 +5485,7 @@ void DisplayServerWindows::_update_tablet_ctx(const String &p_old_driver, const
}
}
-DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) {
+DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, bool p_exclusive, WindowID p_transient_parent) {
DWORD dwExStyle;
DWORD dwStyle;
@@ -5209,6 +5535,20 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
WindowID id = window_id_counter;
{
+ WindowData *wd_transient_parent = nullptr;
+ HWND owner_hwnd = nullptr;
+ if (p_transient_parent != INVALID_WINDOW_ID) {
+ if (!windows.has(p_transient_parent)) {
+ ERR_PRINT("Condition \"!windows.has(p_transient_parent)\" is true.");
+ p_transient_parent = INVALID_WINDOW_ID;
+ } else {
+ wd_transient_parent = &windows[p_transient_parent];
+ if (p_exclusive) {
+ owner_hwnd = wd_transient_parent->hWnd;
+ }
+ }
+ }
+
WindowData &wd = windows[id];
wd.hWnd = CreateWindowExW(
@@ -5219,7 +5559,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
WindowRect.top,
WindowRect.right - WindowRect.left,
WindowRect.bottom - WindowRect.top,
- nullptr,
+ owner_hwnd,
nullptr,
hInstance,
// tunnel the WindowData we need to handle creation message
@@ -5241,11 +5581,20 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
wd.pre_fs_valid = true;
}
+ wd.exclusive = p_exclusive;
+ if (wd_transient_parent) {
+ wd.transient_parent = p_transient_parent;
+ wd_transient_parent->transient_children.insert(id);
+ }
+
if (is_dark_mode_supported() && dark_title_available) {
BOOL value = is_dark_mode();
::DwmSetWindowAttribute(wd.hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
}
+ RECT real_client_rect;
+ GetClientRect(wd.hWnd, &real_client_rect);
+
#ifdef RD_ENABLED
if (rendering_context) {
union {
@@ -5275,7 +5624,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
return INVALID_WINDOW_ID;
}
- rendering_context->window_set_size(id, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top);
+ rendering_context->window_set_size(id, real_client_rect.right - real_client_rect.left, real_client_rect.bottom - real_client_rect.top);
rendering_context->window_set_vsync_mode(id, p_vsync_mode);
wd.context_created = true;
}
@@ -5283,7 +5632,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
#ifdef GLES3_ENABLED
if (gl_manager_native) {
- if (gl_manager_native->window_create(id, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) != OK) {
+ if (gl_manager_native->window_create(id, wd.hWnd, hInstance, real_client_rect.right - real_client_rect.left, real_client_rect.bottom - real_client_rect.top) != OK) {
memdelete(gl_manager_native);
gl_manager_native = nullptr;
windows.erase(id);
@@ -5293,7 +5642,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
}
if (gl_manager_angle) {
- if (gl_manager_angle->window_create(id, nullptr, wd.hWnd, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) != OK) {
+ if (gl_manager_angle->window_create(id, nullptr, wd.hWnd, real_client_rect.right - real_client_rect.left, real_client_rect.bottom - real_client_rect.top) != OK) {
memdelete(gl_manager_angle);
gl_manager_angle = nullptr;
windows.erase(id);
@@ -5355,7 +5704,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
PROPVARIANT val;
String appname;
if (Engine::get_singleton()->is_editor_hint()) {
- appname = "Godot.GodotEditor." + String(VERSION_BRANCH);
+ appname = "Godot.GodotEditor." + String(VERSION_FULL_CONFIG);
} else {
String name = GLOBAL_GET("application/config/name");
String version = GLOBAL_GET("application/config/version");
@@ -5402,12 +5751,15 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
SetWindowPos(wd.hWnd, HWND_TOP, srect.position.x, srect.position.y, srect.size.width, srect.size.height, SWP_NOZORDER | SWP_NOACTIVATE);
}
+ wd.create_completed = true;
window_id_counter++;
}
return id;
}
+BitField<DisplayServerWindows::DriverID> DisplayServerWindows::tested_drivers = 0;
+
// WinTab API.
bool DisplayServerWindows::wintab_available = false;
WTOpenPtr DisplayServerWindows::wintab_WTOpen = nullptr;
@@ -5432,6 +5784,9 @@ GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr;
LogicalToPhysicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_LogicalToPhysicalPointForPerMonitorDPI = nullptr;
PhysicalToLogicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_PhysicalToLogicalPointForPerMonitorDPI = nullptr;
+// Shell API,
+SHLoadIndirectStringPtr DisplayServerWindows::load_indirect_string = nullptr;
+
Vector2i _get_device_ids(const String &p_device_name) {
if (p_device_name.is_empty()) {
return Vector2i();
@@ -5494,12 +5849,6 @@ Vector2i _get_device_ids(const String &p_device_name) {
return ids;
}
-typedef enum _SHC_PROCESS_DPI_AWARENESS {
- SHC_PROCESS_DPI_UNAWARE = 0,
- SHC_PROCESS_SYSTEM_DPI_AWARE = 1,
- SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2
-} SHC_PROCESS_DPI_AWARENESS;
-
bool DisplayServerWindows::is_dark_mode_supported() const {
return ux_theme_available;
}
@@ -5567,6 +5916,8 @@ void DisplayServerWindows::tablet_set_current_driver(const String &p_driver) {
DisplayServerWindows::DisplayServerWindows(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, Context p_context, Error &r_error) {
KeyMappingWindows::initialize();
+ tested_drivers.clear();
+
drop_events = false;
key_event_pos = 0;
@@ -5605,6 +5956,12 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
FreeLibrary(nt_lib);
}
+ // Load Shell API.
+ HMODULE shellapi_lib = LoadLibraryW(L"shlwapi.dll");
+ if (shellapi_lib) {
+ load_indirect_string = (SHLoadIndirectStringPtr)GetProcAddress(shellapi_lib, "SHLoadIndirectString");
+ }
+
// Load UXTheme, available on Windows 10+ only.
if (os_ver.dwBuildNumber >= 10240) {
HMODULE ux_theme_lib = LoadLibraryW(L"uxtheme.dll");
@@ -5729,7 +6086,6 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
wc.lpszClassName = L"Engine";
if (!RegisterClassExW(&wc)) {
- MessageBoxW(nullptr, L"Failed To Register The Window Class.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
r_error = ERR_UNAVAILABLE;
return;
}
@@ -5740,33 +6096,83 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
#if defined(VULKAN_ENABLED)
if (rendering_driver == "vulkan") {
rendering_context = memnew(RenderingContextDriverVulkanWindows);
+ tested_drivers.set_flag(DRIVER_ID_RD_VULKAN);
}
#endif
#if defined(D3D12_ENABLED)
if (rendering_driver == "d3d12") {
rendering_context = memnew(RenderingContextDriverD3D12);
+ tested_drivers.set_flag(DRIVER_ID_RD_D3D12);
}
#endif
if (rendering_context) {
if (rendering_context->initialize() != OK) {
- memdelete(rendering_context);
- rendering_context = nullptr;
- r_error = ERR_UNAVAILABLE;
- return;
+ bool failed = true;
+#if defined(VULKAN_ENABLED)
+ bool fallback_to_vulkan = GLOBAL_GET("rendering/rendering_device/fallback_to_vulkan");
+ if (failed && fallback_to_vulkan && rendering_driver != "vulkan") {
+ memdelete(rendering_context);
+ rendering_context = memnew(RenderingContextDriverVulkanWindows);
+ tested_drivers.set_flag(DRIVER_ID_RD_VULKAN);
+ if (rendering_context->initialize() == OK) {
+ WARN_PRINT("Your video card drivers seem not to support Direct3D 12, switching to Vulkan.");
+ rendering_driver = "vulkan";
+ failed = false;
+ }
+ }
+#endif
+#if defined(D3D12_ENABLED)
+ bool fallback_to_d3d12 = GLOBAL_GET("rendering/rendering_device/fallback_to_d3d12");
+ if (failed && fallback_to_d3d12 && rendering_driver != "d3d12") {
+ memdelete(rendering_context);
+ rendering_context = memnew(RenderingContextDriverD3D12);
+ tested_drivers.set_flag(DRIVER_ID_RD_D3D12);
+ if (rendering_context->initialize() == OK) {
+ WARN_PRINT("Your video card drivers seem not to support Vulkan, switching to Direct3D 12.");
+ rendering_driver = "d3d12";
+ failed = false;
+ }
+ }
+#endif
+ if (failed) {
+ memdelete(rendering_context);
+ rendering_context = nullptr;
+ r_error = ERR_UNAVAILABLE;
+ return;
+ }
}
}
#endif
// Init context and rendering device
#if defined(GLES3_ENABLED)
-#if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64)
- // There's no native OpenGL drivers on Windows for ARM, switch to ANGLE over DX.
+ bool fallback = GLOBAL_GET("rendering/gl_compatibility/fallback_to_angle");
+ bool show_warning = true;
+
if (rendering_driver == "opengl3") {
- rendering_driver = "opengl3_angle";
+ // There's no native OpenGL drivers on Windows for ARM, always enable fallback.
+#if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64)
+ fallback = true;
+ show_warning = false;
+#else
+ typedef BOOL(WINAPI * IsWow64Process2Ptr)(HANDLE, USHORT *, USHORT *);
+
+ IsWow64Process2Ptr IsWow64Process2 = (IsWow64Process2Ptr)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process2");
+ if (IsWow64Process2) {
+ USHORT process_arch = 0;
+ USHORT machine_arch = 0;
+ if (!IsWow64Process2(GetCurrentProcess(), &process_arch, &machine_arch)) {
+ machine_arch = 0;
+ }
+ if (machine_arch == 0xAA64) {
+ fallback = true;
+ show_warning = false;
+ }
+ }
+#endif
}
-#elif defined(EGL_STATIC)
- bool fallback = GLOBAL_GET("rendering/gl_compatibility/fallback_to_angle");
+
if (fallback && (rendering_driver == "opengl3")) {
Dictionary gl_info = detect_wgl();
@@ -5792,14 +6198,17 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
if (force_angle || (gl_info["version"].operator int() < 30003)) {
- WARN_PRINT("Your video card drivers seem not to support the required OpenGL 3.3 version, switching to ANGLE.");
+ tested_drivers.set_flag(DRIVER_ID_COMPAT_OPENGL3);
+ if (show_warning) {
+ WARN_PRINT("Your video card drivers seem not to support the required OpenGL 3.3 version, switching to ANGLE.");
+ }
rendering_driver = "opengl3_angle";
}
}
-#endif
if (rendering_driver == "opengl3") {
gl_manager_native = memnew(GLManagerNative_Windows);
+ tested_drivers.set_flag(DRIVER_ID_COMPAT_OPENGL3);
if (gl_manager_native->initialize() != OK) {
memdelete(gl_manager_native);
@@ -5812,6 +6221,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
if (rendering_driver == "opengl3_angle") {
gl_manager_angle = memnew(GLManagerANGLE_Windows);
+ tested_drivers.set_flag(DRIVER_ID_COMPAT_ANGLE_D3D11);
if (gl_manager_angle->initialize() != OK) {
memdelete(gl_manager_angle);
@@ -5826,7 +6236,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
String appname;
if (Engine::get_singleton()->is_editor_hint()) {
- appname = "Godot.GodotEditor." + String(VERSION_BRANCH);
+ appname = "Godot.GodotEditor." + String(VERSION_FULL_CONFIG);
} else {
String name = GLOBAL_GET("application/config/name");
String version = GLOBAL_GET("application/config/version");
@@ -5841,6 +6251,17 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
clean_app_name = clean_app_name.substr(0, 120 - version.length()).trim_suffix(".");
appname = "Godot." + clean_app_name + "." + version;
+
+#ifndef TOOLS_ENABLED
+ // Set for exported projects only.
+ HKEY key;
+ if (RegOpenKeyW(HKEY_CURRENT_USER_LOCAL_SETTINGS, L"Software\\Microsoft\\Windows\\Shell\\MuiCache", &key) == ERROR_SUCCESS) {
+ Char16String cs_name = name.utf16();
+ String value_name = OS::get_singleton()->get_executable_path().replace("/", "\\") + ".FriendlyAppName";
+ RegSetValueExW(key, (LPCWSTR)value_name.utf16().get_data(), 0, REG_SZ, (const BYTE *)cs_name.get_data(), cs_name.size() * sizeof(WCHAR));
+ RegCloseKey(key);
+ }
+#endif
}
SetCurrentProcessExplicitAppUserModelID((PCWSTR)appname.utf16().get_data());
@@ -5857,7 +6278,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
window_position = scr_rect.position + (scr_rect.size - p_resolution) / 2;
}
- WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution));
+ WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution), false, INVALID_WINDOW_ID);
ERR_FAIL_COND_MSG(main_window == INVALID_WINDOW_ID, "Failed to create main window.");
joypad = new JoypadWindows(&windows[MAIN_WINDOW_ID].hWnd);
@@ -5934,32 +6355,41 @@ Vector<String> DisplayServerWindows::get_rendering_drivers_func() {
DisplayServer *DisplayServerWindows::create_func(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, Context p_context, Error &r_error) {
DisplayServer *ds = memnew(DisplayServerWindows(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, r_error));
if (r_error != OK) {
- if (p_rendering_driver == "vulkan") {
- String executable_name = OS::get_singleton()->get_executable_path().get_file();
- OS::get_singleton()->alert(
- vformat("Your video card drivers seem not to support the required Vulkan version.\n\n"
- "If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n"
- "You can enable the OpenGL 3 driver by starting the engine from the\n"
- "command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n"
- "If you have recently updated your video card drivers, try rebooting.",
- executable_name),
- "Unable to initialize Vulkan video driver");
- } else if (p_rendering_driver == "d3d12") {
+ if (tested_drivers == 0) {
+ OS::get_singleton()->alert("Failed to register the window class.", "Unable to initialize DisplayServer");
+ } else if (tested_drivers.has_flag(DRIVER_ID_RD_VULKAN) || tested_drivers.has_flag(DRIVER_ID_RD_D3D12)) {
+ Vector<String> drivers;
+ if (tested_drivers.has_flag(DRIVER_ID_RD_VULKAN)) {
+ drivers.push_back("Vulkan");
+ }
+ if (tested_drivers.has_flag(DRIVER_ID_RD_D3D12)) {
+ drivers.push_back("Direct3D 12");
+ }
String executable_name = OS::get_singleton()->get_executable_path().get_file();
OS::get_singleton()->alert(
- vformat("Your video card drivers seem not to support the required DirectX 12 version.\n\n"
+ vformat("Your video card drivers seem not to support the required %s version.\n\n"
"If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n"
"You can enable the OpenGL 3 driver by starting the engine from the\n"
"command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n"
"If you have recently updated your video card drivers, try rebooting.",
+ String(" or ").join(drivers),
executable_name),
- "Unable to initialize DirectX 12 video driver");
+ "Unable to initialize video driver");
} else {
+ Vector<String> drivers;
+ if (tested_drivers.has_flag(DRIVER_ID_COMPAT_OPENGL3)) {
+ drivers.push_back("OpenGL 3.3");
+ }
+ if (tested_drivers.has_flag(DRIVER_ID_COMPAT_ANGLE_D3D11)) {
+ drivers.push_back("Direct3D 11");
+ }
OS::get_singleton()->alert(
- "Your video card drivers seem not to support the required OpenGL 3.3 version.\n\n"
- "If possible, consider updating your video card drivers.\n\n"
- "If you have recently updated your video card drivers, try rebooting.",
- "Unable to initialize OpenGL video driver");
+ vformat(
+ "Your video card drivers seem not to support the required %s version.\n\n"
+ "If possible, consider updating your video card drivers.\n\n"
+ "If you have recently updated your video card drivers, try rebooting.",
+ String(" or ").join(drivers)),
+ "Unable to initialize video driver");
}
}
return ds;
@@ -5970,6 +6400,20 @@ void DisplayServerWindows::register_windows_driver() {
}
DisplayServerWindows::~DisplayServerWindows() {
+ LocalVector<List<FileDialogData *>::Element *> to_remove;
+ for (List<FileDialogData *>::Element *E = file_dialogs.front(); E; E = E->next()) {
+ FileDialogData *fd = E->get();
+ if (fd->listener_thread.is_started()) {
+ fd->close_requested.set();
+ fd->listener_thread.wait_to_finish();
+ }
+ to_remove.push_back(E);
+ }
+ for (List<FileDialogData *>::Element *E : to_remove) {
+ memdelete(E->get());
+ E->erase();
+ }
+
delete joypad;
touch_state.clear();
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 382f18c239..8a2f9f81a6 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -207,6 +207,50 @@ typedef UINT32 PEN_MASK;
#define POINTER_MESSAGE_FLAG_FIRSTBUTTON 0x00000010
#endif
+#ifndef POINTER_MESSAGE_FLAG_SECONDBUTTON
+#define POINTER_MESSAGE_FLAG_SECONDBUTTON 0x00000020
+#endif
+
+#ifndef POINTER_MESSAGE_FLAG_THIRDBUTTON
+#define POINTER_MESSAGE_FLAG_THIRDBUTTON 0x00000040
+#endif
+
+#ifndef POINTER_MESSAGE_FLAG_FOURTHBUTTON
+#define POINTER_MESSAGE_FLAG_FOURTHBUTTON 0x00000080
+#endif
+
+#ifndef POINTER_MESSAGE_FLAG_FIFTHBUTTON
+#define POINTER_MESSAGE_FLAG_FIFTHBUTTON 0x00000100
+#endif
+
+#ifndef IS_POINTER_FLAG_SET_WPARAM
+#define IS_POINTER_FLAG_SET_WPARAM(wParam, flag) (((DWORD)HIWORD(wParam) & (flag)) == (flag))
+#endif
+
+#ifndef IS_POINTER_FIRSTBUTTON_WPARAM
+#define IS_POINTER_FIRSTBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIRSTBUTTON)
+#endif
+
+#ifndef IS_POINTER_SECONDBUTTON_WPARAM
+#define IS_POINTER_SECONDBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_SECONDBUTTON)
+#endif
+
+#ifndef IS_POINTER_THIRDBUTTON_WPARAM
+#define IS_POINTER_THIRDBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_THIRDBUTTON)
+#endif
+
+#ifndef IS_POINTER_FOURTHBUTTON_WPARAM
+#define IS_POINTER_FOURTHBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FOURTHBUTTON)
+#endif
+
+#ifndef IS_POINTER_FIFTHBUTTON_WPARAM
+#define IS_POINTER_FIFTHBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIFTHBUTTON)
+#endif
+
+#ifndef GET_POINTERID_WPARAM
+#define GET_POINTERID_WPARAM(wParam) (LOWORD(wParam))
+#endif
+
#if WINVER < 0x0602
enum tagPOINTER_INPUT_TYPE {
PT_POINTER = 0x00000001,
@@ -274,10 +318,19 @@ typedef struct tagPOINTER_PEN_INFO {
#define WM_POINTERLEAVE 0x024A
#endif
+#ifndef WM_POINTERDOWN
+#define WM_POINTERDOWN 0x0246
+#endif
+
+#ifndef WM_POINTERUP
+#define WM_POINTERUP 0x0247
+#endif
+
typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type);
typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info);
typedef BOOL(WINAPI *LogicalToPhysicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint);
typedef BOOL(WINAPI *PhysicalToLogicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint);
+typedef HRESULT(WINAPI *SHLoadIndirectStringPtr)(PCWSTR pszSource, PWSTR pszOutBuf, UINT cchOutBuf, void **ppvReserved);
typedef struct {
BYTE bWidth; // Width, in pixels, of the image
@@ -297,6 +350,12 @@ typedef struct {
ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
} ICONDIR, *LPICONDIR;
+typedef enum _SHC_PROCESS_DPI_AWARENESS {
+ SHC_PROCESS_DPI_UNAWARE = 0,
+ SHC_PROCESS_SYSTEM_DPI_AWARE = 1,
+ SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2,
+} SHC_PROCESS_DPI_AWARENESS;
+
class DisplayServerWindows : public DisplayServer {
// No need to register with GDCLASS, it's platform-specific and nothing is added.
@@ -328,10 +387,26 @@ class DisplayServerWindows : public DisplayServer {
static LogicalToPhysicalPointForPerMonitorDPIPtr win81p_LogicalToPhysicalPointForPerMonitorDPI;
static PhysicalToLogicalPointForPerMonitorDPIPtr win81p_PhysicalToLogicalPointForPerMonitorDPI;
+ // Shell API
+ static SHLoadIndirectStringPtr load_indirect_string;
+
void _update_tablet_ctx(const String &p_old_driver, const String &p_new_driver);
String tablet_driver;
Vector<String> tablet_drivers;
+ enum DriverID {
+ DRIVER_ID_COMPAT_OPENGL3 = 1 << 0,
+ DRIVER_ID_COMPAT_ANGLE_D3D11 = 1 << 1,
+ DRIVER_ID_RD_VULKAN = 1 << 2,
+ DRIVER_ID_RD_D3D12 = 1 << 3,
+ };
+ static BitField<DriverID> tested_drivers;
+
+ enum TimerID {
+ TIMER_ID_MOVE_REDRAW = 1,
+ TIMER_ID_WINDOW_ACTIVATION = 2,
+ };
+
enum {
KEY_EVENT_BUFFER_SIZE = 512
};
@@ -380,6 +455,7 @@ class DisplayServerWindows : public DisplayServer {
Vector<Vector2> mpath;
+ bool create_completed = false;
bool pre_fs_valid = false;
RECT pre_fs_rect;
bool maximized = false;
@@ -456,7 +532,7 @@ class DisplayServerWindows : public DisplayServer {
uint64_t time_since_popup = 0;
Ref<Image> icon;
- WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect);
+ WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, bool p_exclusive, WindowID p_transient_parent);
WindowID window_id_counter = MAIN_WINDOW_ID;
RBMap<WindowID, WindowData> windows;
@@ -474,6 +550,36 @@ class DisplayServerWindows : public DisplayServer {
IndicatorID indicator_id_counter = 0;
HashMap<IndicatorID, IndicatorData> indicators;
+ struct FileDialogData {
+ HWND hwnd_owner = 0;
+ Rect2i wrect;
+ String appid;
+ String title;
+ String current_directory;
+ String root;
+ String filename;
+ bool show_hidden = false;
+ DisplayServer::FileDialogMode mode = FileDialogMode::FILE_DIALOG_MODE_OPEN_ANY;
+ Vector<String> filters;
+ TypedArray<Dictionary> options;
+ WindowID window_id = DisplayServer::INVALID_WINDOW_ID;
+ Callable callback;
+ bool options_in_cb = false;
+ Thread listener_thread;
+ SafeFlag close_requested;
+ SafeFlag finished;
+ };
+ Mutex file_dialog_mutex;
+ List<FileDialogData *> file_dialogs;
+ HashMap<HWND, FileDialogData *> file_dialog_wnd;
+
+ static void _thread_fd_monitor(void *p_ud);
+
+ HashMap<int64_t, MouseButton> pointer_prev_button;
+ HashMap<int64_t, MouseButton> pointer_button;
+ HashMap<int64_t, LONG> pointer_down_time;
+ HashMap<int64_t, Vector2> pointer_last_pos;
+
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_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex);
@@ -526,7 +632,11 @@ class DisplayServerWindows : public DisplayServer {
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);
+ String _get_keyboard_layout_display_name(const String &p_klid) const;
+ String _get_klid(HKL p_hkl) const;
+
public:
+ LRESULT WndProcFileDialog(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT MouseProc(int code, WPARAM wParam, LPARAM lParam);
@@ -583,7 +693,7 @@ public:
virtual Vector<DisplayServer::WindowID> get_window_list() const override;
- virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()) override;
+ virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i(), bool p_exclusive = false, WindowID p_transient_parent = INVALID_WINDOW_ID) override;
virtual void show_window(WindowID p_window) override;
virtual void delete_sub_window(WindowID p_window) override;
diff --git a/platform/windows/doc_classes/EditorExportPlatformWindows.xml b/platform/windows/doc_classes/EditorExportPlatformWindows.xml
index 06b272c10e..9e2db756ce 100644
--- a/platform/windows/doc_classes/EditorExportPlatformWindows.xml
+++ b/platform/windows/doc_classes/EditorExportPlatformWindows.xml
@@ -26,7 +26,7 @@
If set to [code]1[/code], ANGLE libraries are exported with the exported application. If set to [code]0[/code], ANGLE libraries are exported only if [member ProjectSettings.rendering/gl_compatibility/driver] is set to [code]"opengl3_angle"[/code].
</member>
<member name="application/export_d3d12" type="int" setter="" getter="">
- If set to [code]1[/code], Direct3D 12 runtime (DXIL, Agility SDK, PIX) libraries are exported with the exported application. If set to [code]0[/code], Direct3D 12 libraries are exported only if [member ProjectSettings.rendering/rendering_device/driver] is set to [code]"d3d12"[/code].
+ If set to [code]1[/code], the Direct3D 12 runtime libraries (Agility SDK, PIX) are exported with the exported application. If set to [code]0[/code], Direct3D 12 libraries are exported only if [member ProjectSettings.rendering/rendering_device/driver] is set to [code]"d3d12"[/code].
</member>
<member name="application/file_description" type="String" setter="" getter="">
File description to be presented to users. Required. See [url=https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block]StringFileInfo[/url].
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index 6ce9d27dc5..b465bd4ecd 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -187,6 +187,12 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset>
template_path = template_path.strip_edges();
if (template_path.is_empty()) {
template_path = find_export_template(get_template_file_name(p_debug ? "debug" : "release", arch));
+ } else {
+ String exe_arch = _get_exe_arch(template_path);
+ if (arch != exe_arch) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Mismatching custom export template executable architecture: found \"%s\", expected \"%s\"."), exe_arch, arch));
+ return ERR_CANT_CREATE;
+ }
}
int export_angle = p_preset->get("application/export_angle");
@@ -208,18 +214,14 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset>
int export_d3d12 = p_preset->get("application/export_d3d12");
bool agility_sdk_multiarch = p_preset->get("application/d3d12_agility_sdk_multiarch");
- bool include_dxil_libs = false;
+ bool include_d3d12_extra_libs = false;
if (export_d3d12 == 0) {
- include_dxil_libs = (String(GLOBAL_GET("rendering/rendering_device/driver.windows")) == "d3d12") && (String(GLOBAL_GET("rendering/renderer/rendering_method")) != "gl_compatibility");
+ include_d3d12_extra_libs = (String(GLOBAL_GET("rendering/rendering_device/driver.windows")) == "d3d12") && (String(GLOBAL_GET("rendering/renderer/rendering_method")) != "gl_compatibility");
} else if (export_d3d12 == 1) {
- include_dxil_libs = true;
+ include_d3d12_extra_libs = true;
}
- if (include_dxil_libs) {
+ if (include_d3d12_extra_libs) {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- if (da->file_exists(template_path.get_base_dir().path_join("dxil." + arch + ".dll"))) {
- da->make_dir_recursive(p_path.get_base_dir().path_join(arch));
- da->copy(template_path.get_base_dir().path_join("dxil." + arch + ".dll"), p_path.get_base_dir().path_join(arch).path_join("dxil.dll"), get_chmod_flags());
- }
if (da->file_exists(template_path.get_base_dir().path_join("D3D12Core." + arch + ".dll"))) {
if (agility_sdk_multiarch) {
da->make_dir_recursive(p_path.get_base_dir().path_join(arch));
@@ -757,9 +759,26 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
}
bool EditorExportPlatformWindows::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
- String err = "";
+ String err;
bool valid = EditorExportPlatformPC::has_valid_export_configuration(p_preset, err, r_missing_templates, p_debug);
+ String custom_debug = p_preset->get("custom_template/debug").operator String().strip_edges();
+ String custom_release = p_preset->get("custom_template/release").operator String().strip_edges();
+ String arch = p_preset->get("binary_format/architecture");
+
+ if (!custom_debug.is_empty() && FileAccess::exists(custom_debug)) {
+ String exe_arch = _get_exe_arch(custom_debug);
+ if (arch != exe_arch) {
+ err += vformat(TTR("Mismatching custom debug export template executable architecture: found \"%s\", expected \"%s\"."), exe_arch, arch) + "\n";
+ }
+ }
+ if (!custom_release.is_empty() && FileAccess::exists(custom_release)) {
+ String exe_arch = _get_exe_arch(custom_release);
+ if (arch != exe_arch) {
+ err += vformat(TTR("Mismatching custom release export template executable architecture: found \"%s\", expected \"%s\"."), exe_arch, arch) + "\n";
+ }
+ }
+
String rcedit_path = EDITOR_GET("export/windows/rcedit");
if (p_preset->get("application/modify_resources") && rcedit_path.is_empty()) {
err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > rcedit) to change the icon or app information data.") + "\n";
@@ -773,7 +792,7 @@ bool EditorExportPlatformWindows::has_valid_export_configuration(const Ref<Edito
}
bool EditorExportPlatformWindows::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
- String err = "";
+ String err;
bool valid = true;
List<ExportOption> options;
@@ -797,6 +816,43 @@ bool EditorExportPlatformWindows::has_valid_project_configuration(const Ref<Edit
return valid;
}
+String EditorExportPlatformWindows::_get_exe_arch(const String &p_path) const {
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
+ if (f.is_null()) {
+ return "invalid";
+ }
+
+ // Jump to the PE header and check the magic number.
+ {
+ f->seek(0x3c);
+ uint32_t pe_pos = f->get_32();
+
+ f->seek(pe_pos);
+ uint32_t magic = f->get_32();
+ if (magic != 0x00004550) {
+ return "invalid";
+ }
+ }
+
+ // Process header.
+ uint16_t machine = f->get_16();
+ f->close();
+
+ switch (machine) {
+ case 0x014c:
+ return "x86_32";
+ case 0x8664:
+ return "x86_64";
+ case 0x01c0:
+ case 0x01c4:
+ return "arm32";
+ case 0xaa64:
+ return "arm64";
+ default:
+ return "unknown";
+ }
+}
+
Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) {
// Patch the header of the "pck" section in the PE file so that it corresponds to the embedded data
diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h
index c644b1f9e1..6ccb4a15a7 100644
--- a/platform/windows/export/export_plugin.h
+++ b/platform/windows/export/export_plugin.h
@@ -73,6 +73,8 @@ class EditorExportPlatformWindows : public EditorExportPlatformPC {
Error _rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path, bool p_console_icon);
Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path);
+ String _get_exe_arch(const String &p_path) const;
+
public:
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) override;
diff --git a/platform/windows/gl_manager_windows_angle.cpp b/platform/windows/gl_manager_windows_angle.cpp
index 3086edc7f2..c52564676f 100644
--- a/platform/windows/gl_manager_windows_angle.cpp
+++ b/platform/windows/gl_manager_windows_angle.cpp
@@ -67,4 +67,9 @@ Vector<EGLint> GLManagerANGLE_Windows::_get_platform_context_attribs() const {
return ret;
}
+void GLManagerANGLE_Windows::window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) {
+ window_make_current(p_window_id);
+ eglWaitNative(EGL_CORE_NATIVE_ENGINE);
+}
+
#endif // WINDOWS_ENABLED && GLES3_ENABLED
diff --git a/platform/windows/gl_manager_windows_angle.h b/platform/windows/gl_manager_windows_angle.h
index d8dc651cfd..f43a6fbe02 100644
--- a/platform/windows/gl_manager_windows_angle.h
+++ b/platform/windows/gl_manager_windows_angle.h
@@ -50,7 +50,7 @@ private:
virtual Vector<EGLint> _get_platform_context_attribs() const override;
public:
- void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) {}
+ void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height);
GLManagerANGLE_Windows(){};
~GLManagerANGLE_Windows(){};
diff --git a/platform/windows/gl_manager_windows_native.cpp b/platform/windows/gl_manager_windows_native.cpp
index c8d7534e26..8590c46d12 100644
--- a/platform/windows/gl_manager_windows_native.cpp
+++ b/platform/windows/gl_manager_windows_native.cpp
@@ -76,6 +76,8 @@ static String format_error_message(DWORD id) {
const int OGL_THREAD_CONTROL_ID = 0x20C1221E;
const int OGL_THREAD_CONTROL_DISABLE = 0x00000002;
const int OGL_THREAD_CONTROL_ENABLE = 0x00000001;
+const int VRR_MODE_ID = 0x1194F158;
+const int VRR_MODE_FULLSCREEN_ONLY = 0x1;
typedef int(__cdecl *NvAPI_Initialize_t)();
typedef int(__cdecl *NvAPI_Unload_t)();
@@ -104,10 +106,12 @@ static bool nvapi_err_check(const char *msg, int status) {
return true;
}
-// On windows we have to disable threaded optimization when using NVIDIA graphics cards
-// 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() {
+// On windows we have to customize the NVIDIA application profile:
+// * disable threaded optimization when using NVIDIA cards to avoid stuttering, see
+// https://stackoverflow.com/questions/36959508/nvidia-graphics-driver-causing-noticeable-frame-stuttering/37632948
+// https://github.com/Ryujinx/Ryujinx/blob/master/src/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs
+// * disable G-SYNC in windowed mode, as it results in unstable editor refresh rates
+void GLManagerNative_Windows::_nvapi_setup_profile() {
HMODULE nvapi = nullptr;
#ifdef _WIN64
nvapi = LoadLibraryA("nvapi64.dll");
@@ -239,21 +243,29 @@ void GLManagerNative_Windows::_nvapi_disable_threaded_optimization() {
}
}
- NVDRS_SETTING setting;
- setting.version = NVDRS_SETTING_VER;
- setting.settingId = OGL_THREAD_CONTROL_ID;
- setting.settingType = NVDRS_DWORD_TYPE;
- setting.settingLocation = NVDRS_CURRENT_PROFILE_LOCATION;
- setting.isCurrentPredefined = 0;
- setting.isPredefinedValid = 0;
+ NVDRS_SETTING ogl_thread_control_setting = {};
+ ogl_thread_control_setting.version = NVDRS_SETTING_VER;
+ ogl_thread_control_setting.settingId = OGL_THREAD_CONTROL_ID;
+ ogl_thread_control_setting.settingType = NVDRS_DWORD_TYPE;
int thread_control_val = OGL_THREAD_CONTROL_DISABLE;
if (!GLOBAL_GET("rendering/gl_compatibility/nvidia_disable_threaded_optimization")) {
thread_control_val = OGL_THREAD_CONTROL_ENABLE;
}
- setting.u32CurrentValue = thread_control_val;
- setting.u32PredefinedValue = thread_control_val;
+ ogl_thread_control_setting.u32CurrentValue = thread_control_val;
- if (!nvapi_err_check("NVAPI: Error calling NvAPI_DRS_SetSetting", NvAPI_DRS_SetSetting(session_handle, profile_handle, &setting))) {
+ if (!nvapi_err_check("NVAPI: Error calling NvAPI_DRS_SetSetting", NvAPI_DRS_SetSetting(session_handle, profile_handle, &ogl_thread_control_setting))) {
+ NvAPI_DRS_DestroySession(session_handle);
+ NvAPI_Unload();
+ return;
+ }
+
+ NVDRS_SETTING vrr_mode_setting = {};
+ vrr_mode_setting.version = NVDRS_SETTING_VER;
+ vrr_mode_setting.settingId = VRR_MODE_ID;
+ vrr_mode_setting.settingType = NVDRS_DWORD_TYPE;
+ vrr_mode_setting.u32CurrentValue = VRR_MODE_FULLSCREEN_ONLY;
+
+ if (!nvapi_err_check("NVAPI: Error calling NvAPI_DRS_SetSetting", NvAPI_DRS_SetSetting(session_handle, profile_handle, &vrr_mode_setting))) {
NvAPI_DRS_DestroySession(session_handle);
NvAPI_Unload();
return;
@@ -270,6 +282,7 @@ void GLManagerNative_Windows::_nvapi_disable_threaded_optimization() {
} else {
print_verbose("NVAPI: Enabled OpenGL threaded optimization successfully");
}
+ print_verbose("NVAPI: Disabled G-SYNC for windowed mode successfully");
NvAPI_DRS_DestroySession(session_handle);
}
@@ -495,7 +508,7 @@ void GLManagerNative_Windows::swap_buffers() {
}
Error GLManagerNative_Windows::initialize() {
- _nvapi_disable_threaded_optimization();
+ _nvapi_setup_profile();
return OK;
}
diff --git a/platform/windows/gl_manager_windows_native.h b/platform/windows/gl_manager_windows_native.h
index b4e2a3acdf..532092ae74 100644
--- a/platform/windows/gl_manager_windows_native.h
+++ b/platform/windows/gl_manager_windows_native.h
@@ -78,7 +78,7 @@ private:
int glx_minor, glx_major;
private:
- void _nvapi_disable_threaded_optimization();
+ void _nvapi_setup_profile();
int _find_or_create_display(GLWindow &win);
Error _create_context(GLWindow &win, GLDisplay &gl_display);
diff --git a/platform/windows/native_menu_windows.cpp b/platform/windows/native_menu_windows.cpp
index d9dc28e9d9..fde55918e4 100644
--- a/platform/windows/native_menu_windows.cpp
+++ b/platform/windows/native_menu_windows.cpp
@@ -81,22 +81,6 @@ void NativeMenuWindows::_menu_activate(HMENU p_menu, int p_index) const {
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;
@@ -619,9 +603,12 @@ bool NativeMenuWindows::is_item_checked(const RID &p_rid, int p_idx) const {
MENUITEMINFOW item;
ZeroMemory(&item, sizeof(item));
item.cbSize = sizeof(item);
- item.fMask = MIIM_STATE;
+ item.fMask = MIIM_STATE | MIIM_DATA;
if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
- return (item.fState & MFS_CHECKED) == MFS_CHECKED;
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->checked;
+ }
}
return false;
}
@@ -861,12 +848,16 @@ void NativeMenuWindows::set_item_checked(const RID &p_rid, int p_idx, bool p_che
MENUITEMINFOW item;
ZeroMemory(&item, sizeof(item));
item.cbSize = sizeof(item);
- item.fMask = MIIM_STATE;
+ item.fMask = MIIM_STATE | MIIM_DATA;
if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
- if (p_checked) {
- item.fState |= MFS_CHECKED;
- } else {
- item.fState &= ~MFS_CHECKED;
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ item_data->checked = p_checked;
+ if (p_checked) {
+ item.fState |= MFS_CHECKED;
+ } else {
+ item.fState &= ~MFS_CHECKED;
+ }
}
SetMenuItemInfoW(md->menu, p_idx, true, &item);
}
diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h
index 5c4aaa52c8..235a4b332a 100644
--- a/platform/windows/native_menu_windows.h
+++ b/platform/windows/native_menu_windows.h
@@ -51,6 +51,7 @@ class NativeMenuWindows : public NativeMenu {
Callable callback;
Variant meta;
GlobalMenuCheckType checkable_type;
+ bool checked = false;
int max_states = 0;
int state = 0;
Ref<Image> img;
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index 157702655e..40b265785f 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -379,6 +379,8 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
//this code exists so gdextension can load .dll files from within the executable path
path = get_executable_path().get_base_dir().path_join(p_path.get_file());
}
+ // Path to load from may be different from original if we make copies.
+ String load_path = path;
ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND);
@@ -387,25 +389,22 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
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());
+ load_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);
+ if (FileAccess::exists(load_path)) {
+ DirAccess::remove_absolute(load_path);
}
- Error copy_err = DirAccess::copy_absolute(path, copy_path);
+ Error copy_err = DirAccess::copy_absolute(path, load_path);
if (copy_err) {
ERR_PRINT("Error copying library: " + path);
return ERR_CANT_CREATE;
}
- FileAccess::set_hidden_attribute(copy_path, true);
+ FileAccess::set_hidden_attribute(load_path, true);
- // Save the copied path so it can be deleted later.
- path = copy_path;
-
- Error pdb_err = WindowsUtils::copy_and_rename_pdb(path);
+ Error pdb_err = WindowsUtils::copy_and_rename_pdb(load_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));
}
@@ -421,21 +420,21 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
DLL_DIRECTORY_COOKIE cookie = nullptr;
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()));
+ cookie = add_dll_directory((LPCWSTR)(load_path.get_base_dir().utf16().get_data()));
}
- 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);
+ p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(load_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);
+ DirAccess::remove_absolute(load_path);
}
#ifdef DEBUG_ENABLED
DWORD err_code = GetLastError();
- HashSet<String> checekd_libs;
+ HashSet<String> checked_libs;
HashSet<String> missing_libs;
- debug_dynamic_library_check_dependencies(path, path, checekd_libs, missing_libs);
+ debug_dynamic_library_check_dependencies(load_path, load_path, checked_libs, missing_libs);
if (!missing_libs.is_empty()) {
String missing;
for (const String &E : missing_libs) {
@@ -464,7 +463,8 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
}
if (p_data != nullptr && p_data->generate_temp_files) {
- temp_libraries[p_library_handle] = path;
+ // Save the copied path so it can be deleted later.
+ temp_libraries[p_library_handle] = load_path;
}
return OK;
@@ -1634,26 +1634,6 @@ String OS_Windows::get_locale() const {
return "en";
}
-// We need this because GetSystemInfo() is unreliable on WOW64
-// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724381(v=vs.85).aspx
-// Taken from MSDN
-typedef BOOL(WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL);
-LPFN_ISWOW64PROCESS fnIsWow64Process;
-
-BOOL is_wow64() {
- BOOL wow64 = FALSE;
-
- fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
-
- if (fnIsWow64Process) {
- if (!fnIsWow64Process(GetCurrentProcess(), &wow64)) {
- wow64 = FALSE;
- }
- }
-
- return wow64;
-}
-
String OS_Windows::get_processor_name() const {
const String id = "Hardware\\Description\\System\\CentralProcessor\\0";