diff options
Diffstat (limited to 'platform')
48 files changed, 988 insertions, 148 deletions
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 445a6ea6ea..dd5ab46bd7 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -310,9 +310,9 @@ void DisplayServerAndroid::window_set_drop_files_callback(const Callable &p_call void DisplayServerAndroid::_window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred) const { if (!p_callable.is_null()) { if (p_deferred) { - p_callable.call(p_arg); - } else { p_callable.call_deferred(p_arg); + } else { + p_callable.call(p_arg); } } } diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml index 0f1da54376..dae968378b 100644 --- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml +++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml @@ -7,7 +7,7 @@ </description> <tutorials> <link title="Exporting for Android">$DOCS_URL/tutorials/export/exporting_for_android.html</link> - <link title="Custom builds for Android">$DOCS_URL/tutorials/export/android_custom_build.html</link> + <link title="Gradle builds for Android">$DOCS_URL/tutorials/export/android_gradle_build.html</link> <link title="Android plugins documentation index">$DOCS_URL/tutorials/platform/index.html</link> </tutorials> <members> diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index aeaa7b9ce7..c3015ec260 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -2238,6 +2238,19 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito #ifdef MODULE_MONO_ENABLED // Android export is still a work in progress, keep a message as a warning. err += TTR("Exporting to Android when using C#/.NET is experimental.") + "\n"; + + bool unsupported_arch = false; + Vector<ABI> enabled_abis = get_enabled_abis(p_preset); + for (ABI abi : enabled_abis) { + if (abi.arch != "arm64" && abi.arch != "x86_64") { + err += vformat(TTR("Android architecture %s not supported in C# projects."), abi.arch) + "\n"; + unsupported_arch = true; + } + } + if (unsupported_arch) { + r_error = err; + return false; + } #endif // Look for export templates (first official, and if defined custom templates). diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index e7c06628c8..a91e7bc7ce 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -9,7 +9,7 @@ ext.versions = [ kotlinVersion : '1.7.0', fragmentVersion : '1.3.6', nexusPublishVersion: '1.1.0', - javaVersion : 11, + javaVersion : 17, // Also update 'platform/android/detect.py#get_ndk_version()' when this is updated. ndkVersion : '23.2.8568313' 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 f819063a22..857f239e96 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -84,7 +84,7 @@ class Godot(private val context: Context) : SensorEventListener { } private val pluginRegistry: GodotPluginRegistry by lazy { - GodotPluginRegistry.initializePluginRegistry(this) + GodotPluginRegistry.getPluginRegistry() } private val mSensorManager: SensorManager by lazy { requireActivity().getSystemService(Context.SENSOR_SERVICE) as SensorManager @@ -190,7 +190,7 @@ class Godot(private val context: Context) : SensorEventListener { val activity = requireActivity() val window = activity.window window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) - GodotPluginRegistry.initializePluginRegistry(this) + GodotPluginRegistry.initializePluginRegistry(this, primaryHost.getHostPlugins(this)) if (io == null) { io = GodotIO(activity) } @@ -250,11 +250,7 @@ class Godot(private val context: Context) : SensorEventListener { } i++ } - if (newArgs.isEmpty()) { - commandLine = mutableListOf() - } else { - commandLine = newArgs - } + commandLine = if (newArgs.isEmpty()) { mutableListOf() } else { newArgs } if (useApkExpansion && mainPackMd5 != null && mainPackKey != null) { // Build the full path to the app's expansion files try { @@ -392,6 +388,7 @@ class Godot(private val context: Context) : SensorEventListener { // Fallback to openGl GodotGLRenderView(host, this, xrMode, useDebugOpengl) } + if (host == primaryHost) { renderView!!.startRenderer() } @@ -575,6 +572,19 @@ class Godot(private val context: Context) : SensorEventListener { * Invoked on the render thread when the Godot setup is complete. */ private fun onGodotSetupCompleted() { + Log.d(TAG, "OnGodotSetupCompleted") + + // 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")) + + runOnUiThread { + renderView?.inputHandler?.apply { + enableLongPress(longPressEnabled) + enablePanningAndScalingGestures(panScaleEnabled) + } + } + for (plugin in pluginRegistry.allPlugins) { plugin.onGodotSetupCompleted() } @@ -585,6 +595,8 @@ class Godot(private val context: Context) : SensorEventListener { * Invoked on the render thread when the Godot main loop has started. */ private fun onGodotMainLoopStarted() { + Log.d(TAG, "OnGodotMainLoopStarted") + for (plugin in pluginRegistry.allPlugins) { plugin.onGodotMainLoopStarted() } @@ -616,7 +628,7 @@ class Godot(private val context: Context) : SensorEventListener { private fun alert(message: String, title: String, okCallback: Runnable?) { val activity: Activity = getActivity() ?: return - runOnUiThread(Runnable { + runOnUiThread { val builder = AlertDialog.Builder(activity) builder.setMessage(message).setTitle(title) builder.setPositiveButton( @@ -627,7 +639,7 @@ class Godot(private val context: Context) : SensorEventListener { } val dialog = builder.create() dialog.show() - }) + } } /** @@ -685,9 +697,9 @@ class Godot(private val context: Context) : SensorEventListener { return false } - private fun setKeepScreenOn(p_enabled: Boolean) { + private fun setKeepScreenOn(enabled: Boolean) { runOnUiThread { - if (p_enabled) { + if (enabled) { getActivity()?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } else { getActivity()?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -835,9 +847,7 @@ class Godot(private val context: Context) : SensorEventListener { } } - override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { - // Do something here if sensor accuracy changes. - } + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} /** * Used by the native code (java_godot_wrapper.h) to vibrate the device. @@ -865,7 +875,7 @@ class Godot(private val context: Context) : SensorEventListener { private fun getCommandLine(): MutableList<String> { val original: MutableList<String> = parseCommandLine() val hostCommandLine = primaryHost?.commandLine - if (hostCommandLine != null && hostCommandLine.isNotEmpty()) { + if (!hostCommandLine.isNullOrEmpty()) { original.addAll(hostCommandLine) } return original 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 417be7cef4..a60f6e997e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt @@ -173,6 +173,10 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { return this } + override fun getGodot(): Godot? { + return godotFragment?.godot + } + /** * Used to initialize the Godot fragment instance in [onCreate]. */ 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 9a8b10ea3e..120e1722e5 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java @@ -30,6 +30,7 @@ package org.godotengine.godot; +import org.godotengine.godot.plugin.GodotPlugin; import org.godotengine.godot.utils.BenchmarkUtils; import android.app.Activity; @@ -65,6 +66,7 @@ import com.google.android.vending.expansion.downloader.IStub; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Set; /** * Base fragment for Android apps intending to use Godot for part of the app's UI. @@ -122,6 +124,7 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH } public ResultCallback resultCallback; + @Override public Godot getGodot() { return godot; } @@ -426,4 +429,13 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH } return 0; } + + @Override + @CallSuper + public Set<GodotPlugin> getHostPlugins(Godot engine) { + if (parentHost != null) { + return parentHost.getHostPlugins(engine); + } + return Collections.emptySet(); + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java index e5333085dd..1862b9fa9b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java @@ -30,39 +30,42 @@ package org.godotengine.godot; +import org.godotengine.godot.plugin.GodotPlugin; + import android.app.Activity; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Denotate a component (e.g: Activity, Fragment) that hosts the {@link Godot} engine. */ public interface GodotHost { /** - * Provides a set of command line parameters to setup the engine. + * Provides a set of command line parameters to setup the {@link Godot} engine. */ default List<String> getCommandLine() { return Collections.emptyList(); } /** - * Invoked on the render thread when the Godot setup is complete. + * Invoked on the render thread when setup of the {@link Godot} engine is complete. */ default void onGodotSetupCompleted() {} /** - * Invoked on the render thread when the Godot main loop has started. + * Invoked on the render thread when the {@link Godot} engine main loop has started. */ default void onGodotMainLoopStarted() {} /** - * Invoked on the render thread to terminate the given Godot instance. + * Invoked on the render thread to terminate the given {@link Godot} engine instance. */ default void onGodotForceQuit(Godot instance) {} /** - * Invoked on the render thread to terminate the Godot instance with the given id. + * Invoked on the render thread to terminate the {@link Godot} engine instance with the given id. * @param godotInstanceId id of the Godot instance to terminate. See {@code onNewGodotInstanceRequested} * * @return true if successful, false otherwise. @@ -90,7 +93,19 @@ public interface GodotHost { } /** - * Provide access to the Activity hosting the Godot engine. + * Provide access to the Activity hosting the {@link Godot} engine. */ Activity getActivity(); + + /** + * Provide access to the hosted {@link Godot} engine. + */ + Godot getGodot(); + + /** + * Returns a set of {@link GodotPlugin} to be registered with the hosted {@link Godot} engine. + */ + default Set<GodotPlugin> getHostPlugins(Godot engine) { + return Collections.emptySet(); + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt index 68cd2c1358..795dc921c7 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt @@ -8,8 +8,10 @@ import android.util.Log /** * Godot service responsible for hosting the Godot engine instance. + * + * Note: Still in development, so it's made private and inaccessible until completed. */ -class GodotService : Service() { +private class GodotService : Service() { companion object { private val TAG = GodotService::class.java.simpleName 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 c8b222254e..38c115ad7f 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 @@ -95,7 +95,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { /** * Enable multi-fingers pan & scale gestures. This is false by default. - * + * <p> * Note: This may interfere with multi-touch handling / support. */ public void enablePanningAndScalingGestures(boolean enable) { 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 d338b72441..711bca02e7 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 @@ -42,8 +42,8 @@ import android.util.Log; import androidx.annotation.Nullable; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.Collection; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -64,9 +64,8 @@ public final class GodotPluginRegistry { private static GodotPluginRegistry instance; private final ConcurrentHashMap<String, GodotPlugin> registry; - private GodotPluginRegistry(Godot godot) { + private GodotPluginRegistry() { registry = new ConcurrentHashMap<>(); - loadPlugins(godot); } /** @@ -93,12 +92,14 @@ public final class GodotPluginRegistry { * documentation. * * @param godot Godot instance + * @param runtimePlugins Set of plugins provided at runtime for registration * @return A singleton instance of {@link GodotPluginRegistry}. This ensures that only one instance * of each Godot Android plugins is available at runtime. */ - public static GodotPluginRegistry initializePluginRegistry(Godot godot) { + public static GodotPluginRegistry initializePluginRegistry(Godot godot, Set<GodotPlugin> runtimePlugins) { if (instance == null) { - instance = new GodotPluginRegistry(godot); + instance = new GodotPluginRegistry(); + instance.loadPlugins(godot, runtimePlugins); } return instance; @@ -108,7 +109,7 @@ public final class GodotPluginRegistry { * Return the plugin registry if it's initialized. * Throws a {@link IllegalStateException} exception if not. * - * @throws IllegalStateException if {@link GodotPluginRegistry#initializePluginRegistry(Godot)} has not been called prior to calling this method. + * @throws IllegalStateException if {@link GodotPluginRegistry#initializePluginRegistry(Godot, Set)} has not been called prior to calling this method. */ public static GodotPluginRegistry getPluginRegistry() throws IllegalStateException { if (instance == null) { @@ -118,7 +119,16 @@ public final class GodotPluginRegistry { return instance; } - private void loadPlugins(Godot godot) { + private void loadPlugins(Godot godot, Set<GodotPlugin> runtimePlugins) { + // Register the runtime plugins + if (runtimePlugins != null && !runtimePlugins.isEmpty()) { + for (GodotPlugin plugin : runtimePlugins) { + Log.i(TAG, "Registering runtime plugin " + plugin.getPluginName()); + registry.put(plugin.getPluginName(), plugin); + } + } + + // Register the manifest plugins try { final Activity activity = godot.getActivity(); ApplicationInfo appInfo = activity diff --git a/platform/ios/app_delegate.mm b/platform/ios/app_delegate.mm index 38846e7508..8a16f8fcc1 100644 --- a/platform/ios/app_delegate.mm +++ b/platform/ios/app_delegate.mm @@ -51,6 +51,15 @@ extern void ios_finish(); @implementation AppDelegate +enum { + SESSION_CATEGORY_AMBIENT, + SESSION_CATEGORY_MULTI_ROUTE, + SESSION_CATEGORY_PLAY_AND_RECORD, + SESSION_CATEGORY_PLAYBACK, + SESSION_CATEGORY_RECORD, + SESSION_CATEGORY_SOLO_AMBIENT +}; + static ViewController *mainViewController = nil; + (ViewController *)viewController { @@ -92,8 +101,28 @@ static ViewController *mainViewController = nil; mainViewController = viewController; - // prevent to stop music in another background app - [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil]; + int sessionCategorySetting = GLOBAL_GET("audio/general/ios/session_category"); + + // Initialize with default Ambient category. + AVAudioSessionCategory category = AVAudioSessionCategoryAmbient; + + if (sessionCategorySetting == SESSION_CATEGORY_MULTI_ROUTE) { + category = AVAudioSessionCategoryMultiRoute; + } else if (sessionCategorySetting == SESSION_CATEGORY_PLAY_AND_RECORD) { + category = AVAudioSessionCategoryPlayAndRecord; + } else if (sessionCategorySetting == SESSION_CATEGORY_PLAYBACK) { + category = AVAudioSessionCategoryPlayback; + } else if (sessionCategorySetting == SESSION_CATEGORY_RECORD) { + category = AVAudioSessionCategoryRecord; + } else if (sessionCategorySetting == SESSION_CATEGORY_SOLO_AMBIENT) { + category = AVAudioSessionCategorySoloAmbient; + } + + if (GLOBAL_GET("audio/general/ios/mix_with_others")) { + [[AVAudioSession sharedInstance] setCategory:category withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil]; + } else { + [[AVAudioSession sharedInstance] setCategory:category error:nil]; + } return YES; } diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm index 8508b67f1e..2561c1c095 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -317,37 +317,37 @@ String DisplayServerIOS::get_name() const { } bool DisplayServerIOS::tts_is_speaking() const { - ERR_FAIL_COND_V_MSG(!tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); + ERR_FAIL_NULL_V_MSG(tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); return [tts isSpeaking]; } bool DisplayServerIOS::tts_is_paused() const { - ERR_FAIL_COND_V_MSG(!tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); + ERR_FAIL_NULL_V_MSG(tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); return [tts isPaused]; } TypedArray<Dictionary> DisplayServerIOS::tts_get_voices() const { - ERR_FAIL_COND_V_MSG(!tts, TypedArray<Dictionary>(), "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); + ERR_FAIL_NULL_V_MSG(tts, TypedArray<Dictionary>(), "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); return [tts getVoices]; } void DisplayServerIOS::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { - ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); + ERR_FAIL_NULL_MSG(tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); [tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt]; } void DisplayServerIOS::tts_pause() { - ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); + ERR_FAIL_NULL_MSG(tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); [tts pauseSpeaking]; } void DisplayServerIOS::tts_resume() { - ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); + ERR_FAIL_NULL_MSG(tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); [tts resumeSpeaking]; } void DisplayServerIOS::tts_stop() { - ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); + ERR_FAIL_NULL_MSG(tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech."); [tts stopSpeaking]; } diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index a8596c30a6..3cae85ea55 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -178,7 +178,7 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/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"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); @@ -284,7 +284,7 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ } else if (lines[i].find("$bundle_identifier") != -1) { strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n"; } else if (lines[i].find("$short_version") != -1) { - strnew += lines[i].replace("$short_version", p_preset->get("application/short_version")) + "\n"; + strnew += lines[i].replace("$short_version", p_preset->get_version("application/short_version")) + "\n"; } else if (lines[i].find("$version") != -1) { strnew += lines[i].replace("$version", p_preset->get_version("application/version")) + "\n"; } else if (lines[i].find("$signature") != -1) { @@ -1927,16 +1927,19 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres } bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { -#ifdef MODULE_MONO_ENABLED - // Don't check for additional errors, as this particular error cannot be resolved. - r_error += TTR("Exporting to iOS is currently not supported in Godot 4 when using C#/.NET. Use Godot 3 to target iOS with C#/Mono instead.") + "\n"; - r_error += TTR("If this project does not use C#, use a non-C# editor build to export the project.") + "\n"; +#if defined(MODULE_MONO_ENABLED) && !defined(MACOS_ENABLED) + // TODO: Remove this restriction when we don't rely on macOS tools to package up the native libraries anymore. + r_error += TTR("Exporting to iOS when using C#/.NET is experimental and requires macOS.") + "\n"; return false; #else String err; bool valid = false; +#if defined(MODULE_MONO_ENABLED) + // iOS export is still a work in progress, keep a message as a warning. + err += TTR("Exporting to iOS when using C#/.NET is experimental.") + "\n"; +#endif // Look for export templates (first official, and if defined custom templates). bool dvalid = exists_export_template("ios.zip", &err); @@ -1963,7 +1966,7 @@ bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref<EditorExp } return valid; -#endif // !MODULE_MONO_ENABLED +#endif // !(MODULE_MONO_ENABLED && !MACOS_ENABLED) } bool EditorExportPlatformIOS::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const { diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index cf4354139a..d9aa98ba70 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -427,6 +427,17 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo return OK; } +void FreeDesktopPortalDesktop::_file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index) { + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &p_status, &p_list, &p_index }; + + p_callable.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute file dialogs callback: %s."), Variant::get_callable_error_text(p_callable, args, 3, ce))); + } +} + void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) { FreeDesktopPortalDesktop *portal = (FreeDesktopPortalDesktop *)p_ud; @@ -451,11 +462,7 @@ void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) { file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index); if (fd.callback.is_valid()) { - Variant v_status = !cancel; - Variant v_files = uris; - Variant v_index = index; - Variant *v_args[3] = { &v_status, &v_files, &v_index }; - fd.callback.call_deferredp((const Variant **)&v_args, 3); + callable_mp(portal, &FreeDesktopPortalDesktop::_file_dialog_callback).call_deferred(fd.callback, !cancel, uris, index); } if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) { callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus); diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h index 503c382207..71e9812ea9 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.h +++ b/platform/linuxbsd/freedesktop_portal_desktop.h @@ -40,7 +40,7 @@ struct DBusMessage; struct DBusConnection; struct DBusMessageIter; -class FreeDesktopPortalDesktop { +class FreeDesktopPortalDesktop : public Object { private: bool unsupported = false; @@ -54,6 +54,8 @@ private: static void append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value); static bool file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index); + void _file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index); + struct FileDialogData { Vector<String> filter_names; DBusConnection *connection = nullptr; diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 02a5208e11..1660101598 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -39,6 +39,7 @@ #include "core/math/math_funcs.h" #include "core/string/print_string.h" #include "core/string/ustring.h" +#include "drivers/png/png_driver_common.h" #include "main/main.h" #include "scene/resources/atlas_texture.h" @@ -519,7 +520,7 @@ Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent * } Bool DisplayServerX11::_predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg) { - if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue) { + if (event->type == PropertyNotify && event->xproperty.state == PropertyNewValue && event->xproperty.atom == *(Atom *)arg) { return True; } else { return False; @@ -593,7 +594,7 @@ String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, A // Non-blocking wait for next event and remove it from the queue. XEvent ev; - while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, nullptr)) { + while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, (XPointer)&selection)) { result = XGetWindowProperty(x11_display, x11_window, selection, // selection type 0, LONG_MAX, // offset - len @@ -664,6 +665,74 @@ String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, A return ret; } +Atom DisplayServerX11::_clipboard_get_image_target(Atom p_source, Window x11_window) const { + Atom target = XInternAtom(x11_display, "TARGETS", 0); + Atom png = XInternAtom(x11_display, "image/png", 0); + Atom *valid_targets = nullptr; + unsigned long atom_count = 0; + + Window selection_owner = XGetSelectionOwner(x11_display, p_source); + if (selection_owner != None) { + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); + + Atom selection = XA_PRIMARY; + XConvertSelection(x11_display, p_source, target, selection, x11_window, CurrentTime); + + XFlush(x11_display); + + // Blocking wait for predicate to be True and remove the event from the queue. + XEvent event; + XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window); + // Do not get any data, see how much data is there. + Atom type; + int format, result; + unsigned long len, bytes_left, dummy; + XGetWindowProperty(x11_display, x11_window, + selection, // Tricky.. + 0, 0, // offset - len + 0, // Delete 0==FALSE + XA_ATOM, // flag + &type, // return type + &format, // return format + &len, &bytes_left, // data length + (unsigned char **)&valid_targets); + + if (valid_targets) { + XFree(valid_targets); + valid_targets = nullptr; + } + + if (type == XA_ATOM && bytes_left > 0) { + // Data is ready and can be processed all at once. + result = XGetWindowProperty(x11_display, x11_window, + selection, 0, bytes_left / 4, 0, + XA_ATOM, &type, &format, + &len, &dummy, (unsigned char **)&valid_targets); + if (result == Success) { + atom_count = len; + } else { + print_verbose("Failed to get selection data."); + return None; + } + } else { + return None; + } + } else { + return None; + } + for (unsigned long i = 0; i < atom_count; i++) { + Atom atom = valid_targets[i]; + if (atom == png) { + XFree(valid_targets); + return png; + } + } + + XFree(valid_targets); + return None; +} + String DisplayServerX11::_clipboard_get(Atom p_source, Window x11_window) const { String ret; Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True); @@ -702,6 +771,158 @@ String DisplayServerX11::clipboard_get_primary() const { return ret; } +Ref<Image> DisplayServerX11::clipboard_get_image() const { + _THREAD_SAFE_METHOD_ + Atom clipboard = XInternAtom(x11_display, "CLIPBOARD", 0); + Window x11_window = windows[MAIN_WINDOW_ID].x11_window; + Ref<Image> ret; + Atom target = _clipboard_get_image_target(clipboard, x11_window); + if (target == None) { + return ret; + } + + Window selection_owner = XGetSelectionOwner(x11_display, clipboard); + + if (selection_owner != None) { + // Block events polling while processing selection events. + MutexLock mutex_lock(events_mutex); + + // Identifier for the property the other window + // will send the converted data to. + Atom transfer_prop = XA_PRIMARY; + XConvertSelection(x11_display, + clipboard, // source selection + target, // format to convert to + transfer_prop, // output property + x11_window, CurrentTime); + + XFlush(x11_display); + + // Blocking wait for predicate to be True and remove the event from the queue. + XEvent event; + XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window); + + // Do not get any data, see how much data is there. + Atom type; + int format, result; + unsigned long len, bytes_left, dummy; + unsigned char *data; + XGetWindowProperty(x11_display, x11_window, + transfer_prop, // Property data is transferred through + 0, 1, // offset, len (4 so we can get the size if INCR is used) + 0, // Delete 0==FALSE + AnyPropertyType, // flag + &type, // return type + &format, // return format + &len, &bytes_left, // data length + &data); + + if (type == XInternAtom(x11_display, "INCR", 0)) { + ERR_FAIL_COND_V_MSG(len != 1, ret, "Incremental transfer initial value was not length."); + + // Data is going to be received incrementally. + DEBUG_LOG_X11("INCR selection started.\n"); + + LocalVector<uint8_t> incr_data; + uint32_t data_size = 0; + bool success = false; + + // Initial response is the lower bound of the length of the transferred data. + incr_data.resize(*(unsigned long *)data); + XFree(data); + data = nullptr; + + // Delete INCR property to notify the owner. + XDeleteProperty(x11_display, x11_window, transfer_prop); + + // Process events from the queue. + bool done = false; + while (!done) { + if (!_wait_for_events()) { + // Error or timeout, abort. + break; + } + // Non-blocking wait for next event and remove it from the queue. + XEvent ev; + while (XCheckIfEvent(x11_display, &ev, _predicate_clipboard_incr, (XPointer)&transfer_prop)) { + result = XGetWindowProperty(x11_display, x11_window, + transfer_prop, // output property + 0, LONG_MAX, // offset - len + True, // delete property to notify the owner + AnyPropertyType, // flag + &type, // return type + &format, // return format + &len, &bytes_left, // data length + &data); + + DEBUG_LOG_X11("PropertyNotify: len=%lu, format=%i\n", len, format); + + if (result == Success) { + if (data && (len > 0)) { + uint32_t prev_size = incr_data.size(); + // New chunk, resize to be safe and append data. + incr_data.resize(MAX(data_size + len, prev_size)); + memcpy(incr_data.ptr() + data_size, data, len); + data_size += len; + } else if (!(format == 0 && len == 0)) { + // For unclear reasons the first GetWindowProperty always returns a length and format of 0. + // Otherwise, last chunk, process finished. + done = true; + success = true; + } + } else { + print_verbose("Failed to get selection data chunk."); + done = true; + } + + if (data) { + XFree(data); + data = nullptr; + } + + if (done) { + break; + } + } + } + + if (success && (data_size > 0)) { + ret.instantiate(); + PNGDriverCommon::png_to_image(incr_data.ptr(), incr_data.size(), false, ret); + } + } else if (bytes_left > 0) { + if (data) { + XFree(data); + data = nullptr; + } + // Data is ready and can be processed all at once. + result = XGetWindowProperty(x11_display, x11_window, + transfer_prop, 0, bytes_left + 4, 0, + AnyPropertyType, &type, &format, + &len, &dummy, &data); + if (result == Success) { + ret.instantiate(); + PNGDriverCommon::png_to_image((uint8_t *)data, bytes_left, false, ret); + } else { + print_verbose("Failed to get selection data."); + } + + if (data) { + XFree(data); + } + } + } + + return ret; +} + +bool DisplayServerX11::clipboard_has_image() const { + Atom target = _clipboard_get_image_target( + XInternAtom(x11_display, "CLIPBOARD", 0), + windows[MAIN_WINDOW_ID].x11_window); + return target != None; +} + Bool DisplayServerX11::_predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg) { if (event->xany.window == *(Window *)arg) { return (event->type == SelectionRequest) || @@ -2874,7 +3095,7 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu *(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32(); } - ERR_FAIL_COND(cursor_image->pixels == nullptr); + ERR_FAIL_NULL(cursor_image->pixels); // Save it for a further usage cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image); @@ -4085,9 +4306,6 @@ void DisplayServerX11::process_events() { if (XGetEventData(x11_display, &event.xcookie)) { if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; - int index = event_data->detail; - Vector2 pos = Vector2(event_data->event_x, event_data->event_y); - switch (event_data->evtype) { case XI_HierarchyChanged: case XI_DeviceChanged: { @@ -4204,6 +4422,9 @@ void DisplayServerX11::process_events() { } bool is_begin = event_data->evtype == XI_TouchBegin; + int index = event_data->detail; + Vector2 pos = Vector2(event_data->event_x, event_data->event_y); + Ref<InputEventScreenTouch> st; st.instantiate(); st->set_window_id(window_id); @@ -4235,6 +4456,10 @@ void DisplayServerX11::process_events() { if (ime_window_event || ignore_events) { break; } + + int index = event_data->detail; + Vector2 pos = Vector2(event_data->event_x, event_data->event_y); + HashMap<int, Vector2>::Iterator curr_pos_elem = xi.state.find(index); if (!curr_pos_elem) { // Defensive break; diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index 9706a4aa11..a8d134a6c7 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -304,6 +304,7 @@ class DisplayServerX11 : public DisplayServer { String _clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const; String _clipboard_get(Atom p_source, Window x11_window) const; + Atom _clipboard_get_image_target(Atom p_source, Window x11_window) const; void _clipboard_transfer_ownership(Atom p_source, Window x11_window) const; bool do_mouse_warp = false; @@ -408,6 +409,8 @@ public: virtual void clipboard_set(const String &p_text) override; virtual String clipboard_get() const override; + virtual Ref<Image> clipboard_get_image() const override; + virtual bool clipboard_has_image() const override; virtual void clipboard_set_primary(const String &p_text) override; virtual String clipboard_get_primary() const override; diff --git a/platform/macos/dir_access_macos.h b/platform/macos/dir_access_macos.h index 64556999a7..167c162200 100644 --- a/platform/macos/dir_access_macos.h +++ b/platform/macos/dir_access_macos.h @@ -49,6 +49,7 @@ protected: virtual String get_drive(int p_drive) override; virtual bool is_hidden(const String &p_name) override; + virtual bool is_case_sensitive(const String &p_path) const override; }; #endif // UNIX ENABLED diff --git a/platform/macos/dir_access_macos.mm b/platform/macos/dir_access_macos.mm index 2413d7bcf3..66d81f2687 100644 --- a/platform/macos/dir_access_macos.mm +++ b/platform/macos/dir_access_macos.mm @@ -30,6 +30,8 @@ #include "dir_access_macos.h" +#include "core/config/project_settings.h" + #if defined(UNIX_ENABLED) #include <errno.h> @@ -78,4 +80,19 @@ bool DirAccessMacOS::is_hidden(const String &p_name) { return [hidden boolValue]; } +bool DirAccessMacOS::is_case_sensitive(const String &p_path) const { + String f = p_path; + if (!f.is_absolute_path()) { + f = get_current_dir().path_join(f); + } + f = fix_path(f); + + NSURL *url = [NSURL fileURLWithPath:@(f.utf8().get_data())]; + NSNumber *cs = nil; + if (![url getResourceValue:&cs forKey:NSURLVolumeSupportsCaseSensitiveNamesKey error:nil]) { + return false; + } + return [cs boolValue]; +} + #endif // UNIX_ENABLED diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 2ca9e493b7..66c89d6cc5 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -119,6 +119,7 @@ public: bool is_popup = false; bool mpass = false; bool focused = false; + bool is_visible = true; Rect2i parent_safe_rect; }; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 0989e47b19..53db9a5abf 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -118,7 +118,7 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod WindowData wd; wd.window_delegate = [[GodotWindowDelegate alloc] init]; - ERR_FAIL_COND_V_MSG(wd.window_delegate == nil, INVALID_WINDOW_ID, "Can't create a window delegate"); + ERR_FAIL_NULL_V_MSG(wd.window_delegate, INVALID_WINDOW_ID, "Can't create a window delegate"); [wd.window_delegate setWindowID:window_id_counter]; int rq_screen = get_screen_from_rect(p_rect); @@ -144,11 +144,11 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:NO]; - ERR_FAIL_COND_V_MSG(wd.window_object == nil, INVALID_WINDOW_ID, "Can't create a window"); + ERR_FAIL_NULL_V_MSG(wd.window_object, INVALID_WINDOW_ID, "Can't create a window"); [wd.window_object setWindowID:window_id_counter]; wd.window_view = [[GodotContentView alloc] init]; - ERR_FAIL_COND_V_MSG(wd.window_view == nil, INVALID_WINDOW_ID, "Can't create a window view"); + ERR_FAIL_NULL_V_MSG(wd.window_view, INVALID_WINDOW_ID, "Can't create a window view"); [wd.window_view setWindowID:window_id_counter]; [wd.window_view setWantsLayer:TRUE]; @@ -567,7 +567,7 @@ NSImage *DisplayServerMacOS::_convert_to_nsimg(Ref<Image> &p_image) const { colorSpaceName:NSDeviceRGBColorSpace bytesPerRow:int(p_image->get_width()) * 4 bitsPerPixel:32]; - ERR_FAIL_COND_V(imgrep == nil, nil); + ERR_FAIL_NULL_V(imgrep, nil); uint8_t *pixels = [imgrep bitmapData]; int len = p_image->get_width() * p_image->get_height(); @@ -583,7 +583,7 @@ NSImage *DisplayServerMacOS::_convert_to_nsimg(Ref<Image> &p_image) const { } NSImage *nsimg = [[NSImage alloc] initWithSize:NSMakeSize(p_image->get_width(), p_image->get_height())]; - ERR_FAIL_COND_V(nsimg == nil, nil); + ERR_FAIL_NULL_V(nsimg, nil); [nsimg addRepresentation:imgrep]; return nsimg; } @@ -611,7 +611,13 @@ void DisplayServerMacOS::menu_open(NSMenu *p_menu) { MenuData &md = submenu[submenu_inv[p_menu]]; md.is_open = true; if (md.open.is_valid()) { - md.open.call(); + Variant ret; + Callable::CallError ce; + + md.open.callp(nullptr, 0, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute menu open callback: %s."), Variant::get_callable_error_text(md.open, nullptr, 0, ce))); + } } } } @@ -621,7 +627,13 @@ void DisplayServerMacOS::menu_close(NSMenu *p_menu) { MenuData &md = submenu[submenu_inv[p_menu]]; md.is_open = false; if (md.close.is_valid()) { - md.close.call(); + Variant ret; + Callable::CallError ce; + + md.close.callp(nullptr, 0, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute menu close callback: %s."), Variant::get_callable_error_text(md.close, nullptr, 0, ce))); + } } } } @@ -649,7 +661,7 @@ void DisplayServerMacOS::menu_callback(id p_sender) { } } - if (value->callback != Callable()) { + if (value->callback.is_valid()) { MenuCall mc; mc.tag = value->meta; mc.callback = value->callback; @@ -1574,7 +1586,7 @@ void DisplayServerMacOS::global_menu_set_item_hover_callbacks(const String &p_me NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; - ERR_FAIL_COND(!obj); + ERR_FAIL_NULL(obj); obj->hover_callback = p_callback; } } @@ -1978,20 +1990,27 @@ Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vect [window setInformativeText:ns_description]; [window setAlertStyle:NSAlertStyleInformational]; - int button_pressed; + Variant button_pressed; NSInteger ret = [window runModal]; if (ret == NSAlertFirstButtonReturn) { - button_pressed = 0; + button_pressed = int64_t(0); } else if (ret == NSAlertSecondButtonReturn) { - button_pressed = 1; + button_pressed = int64_t(1); } else if (ret == NSAlertThirdButtonReturn) { - button_pressed = 2; + button_pressed = int64_t(2); } else { - button_pressed = 2 + (ret - NSAlertThirdButtonReturn); + button_pressed = int64_t(2 + (ret - NSAlertThirdButtonReturn)); } if (!p_callback.is_null()) { - p_callback.call(button_pressed); + Variant ret; + Callable::CallError ce; + const Variant *args[1] = { &button_pressed }; + + p_callback.callp(args, 1, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute dialog callback: %s."), Variant::get_callable_error_text(p_callback, args, 1, ce))); + } } return OK; @@ -2166,11 +2185,31 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String & url.parse_utf8([[[panel URL] path] UTF8String]); files.push_back(url); if (!callback.is_null()) { - callback.call(true, files, [handler getIndex]); + Variant v_result = true; + Variant v_files = files; + Variant v_index = [handler getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute file dialog callback: %s."), Variant::get_callable_error_text(callback, args, 3, ce))); + } } } else { if (!callback.is_null()) { - callback.call(false, Vector<String>(), [handler getIndex]); + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [handler getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute file dialogs callback: %s."), Variant::get_callable_error_text(callback, args, 3, ce))); + } } } if (prev_focus != INVALID_WINDOW_ID) { @@ -2231,11 +2270,31 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String & files.push_back(url); } if (!callback.is_null()) { - callback.call(true, files, [handler getIndex]); + Variant v_result = true; + Variant v_files = files; + Variant v_index = [handler getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute file dialog callback: %s."), Variant::get_callable_error_text(callback, args, 3, ce))); + } } } else { if (!callback.is_null()) { - callback.call(false, Vector<String>(), [handler getIndex]); + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [handler getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute file dialogs callback: %s."), Variant::get_callable_error_text(callback, args, 3, ce))); + } } } if (prev_focus != INVALID_WINDOW_ID) { @@ -2269,7 +2328,15 @@ Error DisplayServerMacOS::dialog_input_text(String p_title, String p_description ret.parse_utf8([[input stringValue] UTF8String]); if (!p_callback.is_null()) { - p_callback.call(ret); + Variant v_result = ret; + Variant ret; + Callable::CallError ce; + const Variant *args[1] = { &v_result }; + + p_callback.callp(args, 1, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute input dialog callback: %s."), Variant::get_callable_error_text(p_callback, args, 1, ce))); + } } return OK; @@ -3562,14 +3629,14 @@ bool DisplayServerMacOS::window_is_focused(WindowID p_window) const { } bool DisplayServerMacOS::window_can_draw(WindowID p_window) const { - return (window_get_mode(p_window) != WINDOW_MODE_MINIMIZED) && [windows[p_window].window_object isOnActiveSpace]; + return windows[p_window].is_visible; } bool DisplayServerMacOS::can_any_window_draw() const { _THREAD_SAFE_METHOD_ for (const KeyValue<WindowID, WindowData> &E : windows) { - if ((window_get_mode(E.key) != WINDOW_MODE_MINIMIZED) && [E.value.window_object isOnActiveSpace]) { + if (E.value.is_visible) { return true; } } @@ -3858,7 +3925,7 @@ void DisplayServerMacOS::cursor_set_custom_image(const Ref<Resource> &p_cursor, bytesPerRow:int(texture_size.width) * 4 bitsPerPixel:32]; - ERR_FAIL_COND(imgrep == nil); + ERR_FAIL_NULL(imgrep); uint8_t *pixels = [imgrep bitmapData]; int len = int(texture_size.width * texture_size.height); @@ -4020,7 +4087,15 @@ void DisplayServerMacOS::process_events() { while (List<MenuCall>::Element *call_p = deferred_menu_calls.front()) { MenuCall call = call_p->get(); deferred_menu_calls.pop_front(); // Remove before call to avoid infinite loop in case callback is using `process_events` (e.g. EditorProgress). - call.callback.call(call.tag); + + Variant ret; + Callable::CallError ce; + const Variant *args[1] = { &call.tag }; + + call.callback.callp(args, 1, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute menu callback: %s."), Variant::get_callable_error_text(call.callback, args, 1, ce))); + } } if (!drop_events) { @@ -4124,7 +4199,7 @@ void DisplayServerMacOS::set_icon(const Ref<Image> &p_icon) { colorSpaceName:NSDeviceRGBColorSpace bytesPerRow:img->get_width() * 4 bitsPerPixel:32]; - ERR_FAIL_COND(imgrep == nil); + ERR_FAIL_NULL(imgrep); uint8_t *pixels = [imgrep bitmapData]; int len = img->get_width() * img->get_height(); @@ -4140,7 +4215,7 @@ void DisplayServerMacOS::set_icon(const Ref<Image> &p_icon) { } NSImage *nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())]; - ERR_FAIL_COND(nsimg == nil); + ERR_FAIL_NULL(nsimg); [nsimg addRepresentation:imgrep]; [NSApp setApplicationIconImage:nsimg]; diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 804028053d..eb78edd2e7 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -379,7 +379,7 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "", false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_category", PROPERTY_HINT_ENUM, "Business,Developer-tools,Education,Entertainment,Finance,Games,Action-games,Adventure-games,Arcade-games,Board-games,Card-games,Casino-games,Dice-games,Educational-games,Family-games,Kids-games,Music-games,Puzzle-games,Racing-games,Role-playing-games,Simulation-games,Sports-games,Strategy-games,Trivia-games,Word-games,Graphics-design,Healthcare-fitness,Lifestyle,Medical,Music,News,Photography,Productivity,Reference,Social-networking,Sports,Travel,Utilities,Video,Weather"), "Games")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/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"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "application/copyright_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); @@ -673,7 +673,7 @@ void EditorExportPlatformMacOS::_fix_plist(const Ref<EditorExportPreset> &p_pres } else if (lines[i].find("$bundle_identifier") != -1) { strnew += lines[i].replace("$bundle_identifier", p_preset->get("application/bundle_identifier")) + "\n"; } else if (lines[i].find("$short_version") != -1) { - strnew += lines[i].replace("$short_version", p_preset->get("application/short_version")) + "\n"; + strnew += lines[i].replace("$short_version", p_preset->get_version("application/short_version")) + "\n"; } else if (lines[i].find("$version") != -1) { strnew += lines[i].replace("$version", p_preset->get_version("application/version")) + "\n"; } else if (lines[i].find("$signature") != -1) { diff --git a/platform/macos/gl_manager_macos_legacy.mm b/platform/macos/gl_manager_macos_legacy.mm index 3e5a96bffd..701de6df78 100644 --- a/platform/macos/gl_manager_macos_legacy.mm +++ b/platform/macos/gl_manager_macos_legacy.mm @@ -50,10 +50,10 @@ Error GLManagerLegacy_MacOS::create_context(GLWindow &win) { }; NSOpenGLPixelFormat *pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; - ERR_FAIL_COND_V(pixel_format == nil, ERR_CANT_CREATE); + ERR_FAIL_NULL_V(pixel_format, ERR_CANT_CREATE); win.context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:shared_context]; - ERR_FAIL_COND_V(win.context == nil, ERR_CANT_CREATE); + ERR_FAIL_NULL_V(win.context, ERR_CANT_CREATE); if (shared_context == nullptr) { shared_context = win.context; } diff --git a/platform/macos/godot_menu_delegate.mm b/platform/macos/godot_menu_delegate.mm index e393a87134..844fd27f01 100644 --- a/platform/macos/godot_menu_delegate.mm +++ b/platform/macos/godot_menu_delegate.mm @@ -56,9 +56,16 @@ - (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item { if (item) { GodotMenuItem *value = [item representedObject]; - if (value && value->hover_callback != Callable()) { + if (value && value->hover_callback.is_valid()) { // If custom callback is set, use it. - value->hover_callback.call(value->meta); + Variant ret; + Callable::CallError ce; + const Variant *args[1] = { &value->meta }; + + value->hover_callback.callp(args, 1, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute menu hover callback: %s."), Variant::get_callable_error_text(value->hover_callback, args, 1, ce))); + } } } } @@ -73,19 +80,28 @@ if (ev_modifiers == item_modifiers) { GodotMenuItem *value = [menu_item representedObject]; - if (value->key_callback != Callable()) { - // If custom callback is set, use it. - value->key_callback.call(value->meta); - } else { - // Otherwise redirect event to the engine. - if (DisplayServer::get_singleton()) { - [[[NSApplication sharedApplication] keyWindow] sendEvent:event]; + if (value) { + if (value->key_callback.is_valid()) { + // If custom callback is set, use it. + Variant ret; + Callable::CallError ce; + const Variant *args[1] = { &value->meta }; + + value->key_callback.callp(args, 1, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute menu key callback: %s."), Variant::get_callable_error_text(value->key_callback, args, 1, ce))); + } + } else { + // Otherwise redirect event to the engine. + if (DisplayServer::get_singleton()) { + [[[NSApplication sharedApplication] keyWindow] sendEvent:event]; + } } - } - // Suppress default menu action. - *target = self; - *action = @selector(doNothing:); + // Suppress default menu action. + *target = self; + *action = @selector(doNothing:); + } return YES; } } diff --git a/platform/macos/godot_window_delegate.mm b/platform/macos/godot_window_delegate.mm index 1756f2d676..e359630d1d 100644 --- a/platform/macos/godot_window_delegate.mm +++ b/platform/macos/godot_window_delegate.mm @@ -356,4 +356,13 @@ } } +- (void)windowDidChangeOcclusionState:(NSNotification *)notification { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); + wd.is_visible = ([wd.window_object occlusionState] & NSWindowOcclusionStateVisible) && [wd.window_object isVisible]; +} + @end diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 1b8ca0134d..29dff683d5 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -819,7 +819,7 @@ OS_MacOS::OS_MacOS() { [NSApp finishLaunching]; id delegate = [[GodotApplicationDelegate alloc] init]; - ERR_FAIL_COND(!delegate); + ERR_FAIL_NULL(delegate); [NSApp setDelegate:delegate]; pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr); diff --git a/platform/web/detect.py b/platform/web/detect.py index 043ddca65d..b0044a80ff 100644 --- a/platform/web/detect.py +++ b/platform/web/detect.py @@ -41,6 +41,11 @@ def get_opts(): "dlink_enabled", "Enable WebAssembly dynamic linking (GDExtension support). Produces bigger binaries", False ), BoolVariable("use_closure_compiler", "Use closure compiler to minimize JavaScript code", False), + BoolVariable( + "proxy_to_pthread", + "Use Emscripten PROXY_TO_PTHREAD option to run the main application code to a separate thread", + False, + ), ] @@ -157,7 +162,7 @@ def configure(env: "Environment"): env.AddMethod(create_template_zip, "CreateTemplateZip") # Closure compiler extern and support for ecmascript specs (const, let, etc). - env["ENV"]["EMCC_CLOSURE_ARGS"] = "--language_in ECMASCRIPT6" + env["ENV"]["EMCC_CLOSURE_ARGS"] = "--language_in ECMASCRIPT_2020" env["CC"] = "emcc" env["CXX"] = "em++" @@ -211,6 +216,10 @@ def configure(env: "Environment"): env.Append(LINKFLAGS=["-Wl,-u,scalbnf"]) if env["dlink_enabled"]: + if env["proxy_to_pthread"]: + print("GDExtension support requires proxy_to_pthread=no, disabling") + env["proxy_to_pthread"] = False + if cc_semver < (3, 1, 14): print("GDExtension support requires emscripten >= 3.1.14, detected: %s.%s.%s" % cc_semver) sys.exit(255) @@ -221,6 +230,16 @@ def configure(env: "Environment"): env.Append(LINKFLAGS=["-fvisibility=hidden"]) env.extra_suffix = ".dlink" + env.extra_suffix + # Run the main application in a web worker + if env["proxy_to_pthread"]: + env.Append(LINKFLAGS=["-s", "PROXY_TO_PTHREAD=1"]) + env.Append(CPPDEFINES=["PROXY_TO_PTHREAD_ENABLED"]) + env.Append(LINKFLAGS=["-s", "EXPORTED_RUNTIME_METHODS=['_emscripten_proxy_main']"]) + # https://github.com/emscripten-core/emscripten/issues/18034#issuecomment-1277561925 + env.Append(LINKFLAGS=["-s", "TEXTDECODER=0"]) + # BigInt support to pass object pointers between contexts + env.Append(LINKFLAGS=["-s", "WASM_BIGINT"]) + # Reduce code size by generating less support code (e.g. skip NodeJS support). env.Append(LINKFLAGS=["-s", "ENVIRONMENT=web,worker"]) diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp index dcc4ac4bf7..022e044185 100644 --- a/platform/web/display_server_web.cpp +++ b/platform/web/display_server_web.cpp @@ -35,6 +35,7 @@ #include "os_web.h" #include "core/config/project_settings.h" +#include "core/object/callable_method_pointer.h" #include "scene/resources/atlas_texture.h" #include "servers/rendering/dummy/rasterizer_dummy.h" @@ -59,13 +60,26 @@ DisplayServerWeb *DisplayServerWeb::get_singleton() { bool DisplayServerWeb::check_size_force_redraw() { bool size_changed = godot_js_display_size_update() != 0; if (size_changed && !rect_changed_callback.is_null()) { - Variant size = Rect2i(Point2i(), window_get_size()); // TODO use window_get_position if implemented. + Size2i window_size = window_get_size(); + Variant size = Rect2i(Point2i(), window_size); // TODO use window_get_position if implemented. rect_changed_callback.call(size); + emscripten_set_canvas_element_size(canvas_id, window_size.x, window_size.y); } return size_changed; } void DisplayServerWeb::fullscreen_change_callback(int p_fullscreen) { +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_fullscreen_change_callback).bind(p_fullscreen).call_deferred(); + return; + } +#endif + + _fullscreen_change_callback(p_fullscreen); +} + +void DisplayServerWeb::_fullscreen_change_callback(int p_fullscreen) { DisplayServerWeb *display = get_singleton(); if (p_fullscreen) { display->window_mode = WINDOW_MODE_FULLSCREEN; @@ -75,7 +89,23 @@ void DisplayServerWeb::fullscreen_change_callback(int p_fullscreen) { } // Drag and drop callback. -void DisplayServerWeb::drop_files_js_callback(char **p_filev, int p_filec) { +void DisplayServerWeb::drop_files_js_callback(const char **p_filev, int p_filec) { + Vector<String> files; + for (int i = 0; i < p_filec; i++) { + files.push_back(String::utf8(p_filev[i])); + } + +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_drop_files_js_callback).bind(files).call_deferred(); + return; + } +#endif + + _drop_files_js_callback(files); +} + +void DisplayServerWeb::_drop_files_js_callback(const Vector<String> &p_files) { DisplayServerWeb *ds = get_singleton(); if (!ds) { ERR_FAIL_MSG("Unable to drop files because the DisplayServer is not active"); @@ -83,15 +113,22 @@ void DisplayServerWeb::drop_files_js_callback(char **p_filev, int p_filec) { if (ds->drop_files_callback.is_null()) { return; } - Vector<String> files; - for (int i = 0; i < p_filec; i++) { - files.push_back(String::utf8(p_filev[i])); - } - ds->drop_files_callback.call(files); + ds->drop_files_callback.call(p_files); } // Web quit request callback. void DisplayServerWeb::request_quit_callback() { +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_request_quit_callback).call_deferred(); + return; + } +#endif + + _request_quit_callback(); +} + +void DisplayServerWeb::_request_quit_callback() { DisplayServerWeb *ds = get_singleton(); if (ds && !ds->window_event_callback.is_null()) { Variant event = int(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST); @@ -119,17 +156,32 @@ void DisplayServerWeb::dom2godot_mod(Ref<InputEventWithModifiers> ev, int p_mod, void DisplayServerWeb::key_callback(int p_pressed, int p_repeat, int p_modifiers) { DisplayServerWeb *ds = get_singleton(); JSKeyEvent &key_event = ds->key_event; + + const String code = String::utf8(key_event.code); + const String key = String::utf8(key_event.key); + +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_key_callback).bind(code, key, p_pressed, p_repeat, p_modifiers).call_deferred(); + return; + } +#endif + + _key_callback(code, key, p_pressed, p_repeat, p_modifiers); +} + +void DisplayServerWeb::_key_callback(const String &p_key_event_code, const String &p_key_event_key, int p_pressed, int p_repeat, int p_modifiers) { // Resume audio context after input in case autoplay was denied. OS_Web::get_singleton()->resume_audio(); char32_t c = 0x00; - String unicode = String::utf8(key_event.key); + String unicode = p_key_event_key; if (unicode.length() == 1) { c = unicode[0]; } - Key keycode = dom_code2godot_scancode(key_event.code, key_event.key, false); - Key scancode = dom_code2godot_scancode(key_event.code, key_event.key, true); + Key keycode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), false); + Key scancode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), true); Ref<InputEventKey> ev; ev.instantiate(); @@ -150,6 +202,17 @@ void DisplayServerWeb::key_callback(int p_pressed, int p_repeat, int p_modifiers // Mouse int DisplayServerWeb::mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers) { +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_mouse_button_callback).bind(p_pressed, p_button, p_x, p_y, p_modifiers).call_deferred(); + return true; + } +#endif + + return _mouse_button_callback(p_pressed, p_button, p_x, p_y, p_modifiers); +} + +int DisplayServerWeb::_mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers) { DisplayServerWeb *ds = get_singleton(); Point2 pos(p_x, p_y); @@ -226,6 +289,17 @@ int DisplayServerWeb::mouse_button_callback(int p_pressed, int p_button, double } void DisplayServerWeb::mouse_move_callback(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers) { +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_mouse_move_callback).bind(p_x, p_y, p_rel_x, p_rel_y, p_modifiers).call_deferred(); + return; + } +#endif + + _mouse_move_callback(p_x, p_y, p_rel_x, p_rel_y, p_modifiers); +} + +void DisplayServerWeb::_mouse_move_callback(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers) { BitField<MouseButtonMask> input_mask = Input::get_singleton()->get_mouse_button_mask(); // For motion outside the canvas, only read mouse movement if dragging // started inside the canvas; imitating desktop app behavior. @@ -301,9 +375,25 @@ bool DisplayServerWeb::tts_is_paused() const { } void DisplayServerWeb::update_voices_callback(int p_size, const char **p_voice) { - get_singleton()->voices.clear(); + Vector<String> voices; for (int i = 0; i < p_size; i++) { - Vector<String> tokens = String::utf8(p_voice[i]).split(";", true, 2); + voices.append(String::utf8(p_voice[i])); + } + +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_update_voices_callback).bind(voices).call_deferred(); + return; + } +#endif + + _update_voices_callback(voices); +} + +void DisplayServerWeb::_update_voices_callback(const Vector<String> &p_voices) { + get_singleton()->voices.clear(); + for (int i = 0; i < p_voices.size(); i++) { + Vector<String> tokens = p_voices[i].split(";", true, 2); if (tokens.size() == 2) { Dictionary voice_d; voice_d["name"] = tokens[1]; @@ -334,7 +424,7 @@ void DisplayServerWeb::tts_speak(const String &p_text, const String &p_voice, in CharString string = p_text.utf8(); utterance_ids[p_utterance_id] = string; - godot_js_tts_speak(string.get_data(), p_voice.utf8().get_data(), CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, DisplayServerWeb::_js_utterance_callback); + godot_js_tts_speak(string.get_data(), p_voice.utf8().get_data(), CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, DisplayServerWeb::js_utterance_callback); } void DisplayServerWeb::tts_pause() { @@ -356,6 +446,17 @@ void DisplayServerWeb::tts_stop() { godot_js_tts_stop(); } +void DisplayServerWeb::js_utterance_callback(int p_event, int p_id, int p_pos) { +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_js_utterance_callback).bind(p_event, p_id, p_pos).call_deferred(); + return; + } +#endif + + _js_utterance_callback(p_event, p_id, p_pos); +} + void DisplayServerWeb::_js_utterance_callback(int p_event, int p_id, int p_pos) { DisplayServerWeb *ds = (DisplayServerWeb *)DisplayServer::get_singleton(); if (ds->utterance_ids.has(p_id)) { @@ -507,6 +608,17 @@ Point2i DisplayServerWeb::mouse_get_position() const { // Wheel int DisplayServerWeb::mouse_wheel_callback(double p_delta_x, double p_delta_y) { +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_mouse_wheel_callback).bind(p_delta_x, p_delta_y).call_deferred(); + return true; + } +#endif + + return _mouse_wheel_callback(p_delta_x, p_delta_y); +} + +int DisplayServerWeb::_mouse_wheel_callback(double p_delta_x, double p_delta_y) { if (!godot_js_display_canvas_is_focused()) { if (get_singleton()->cursor_inside_canvas) { godot_js_display_canvas_focus(); @@ -559,6 +671,17 @@ int DisplayServerWeb::mouse_wheel_callback(double p_delta_x, double p_delta_y) { // Touch void DisplayServerWeb::touch_callback(int p_type, int p_count) { +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_touch_callback).bind(p_type, p_count).call_deferred(); + return; + } +#endif + + _touch_callback(p_type, p_count); +} + +void DisplayServerWeb::_touch_callback(int p_type, int p_count) { DisplayServerWeb *ds = get_singleton(); const JSTouchEvent &touch_event = ds->touch_event; @@ -603,13 +726,25 @@ bool DisplayServerWeb::is_touchscreen_available() const { // Virtual Keyboard void DisplayServerWeb::vk_input_text_callback(const char *p_text, int p_cursor) { + String text = p_text; + +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_vk_input_text_callback).bind(text, p_cursor).call_deferred(); + return; + } +#endif + + _vk_input_text_callback(text, p_cursor); +} + +void DisplayServerWeb::_vk_input_text_callback(const String &p_text, int p_cursor) { DisplayServerWeb *ds = DisplayServerWeb::get_singleton(); if (!ds || ds->input_text_callback.is_null()) { return; } // Call input_text - Variant event = String::utf8(p_text); - ds->input_text_callback.call(event); + ds->input_text_callback.call(p_text); // Insert key right to reach position. Input *input = Input::get_singleton(); Ref<InputEventKey> k; @@ -636,14 +771,39 @@ void DisplayServerWeb::virtual_keyboard_hide() { } void DisplayServerWeb::window_blur_callback() { +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_window_blur_callback).call_deferred(); + return; + } +#endif + + _window_blur_callback(); +} + +void DisplayServerWeb::_window_blur_callback() { Input::get_singleton()->release_pressed_events(); } // Gamepad void DisplayServerWeb::gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid) { + String id = p_id; + String guid = p_guid; + +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_gamepad_callback).bind(p_index, p_connected, id, guid).call_deferred(); + return; + } +#endif + + _gamepad_callback(p_index, p_connected, id, guid); +} + +void DisplayServerWeb::_gamepad_callback(int p_index, int p_connected, const String &p_id, const String &p_guid) { Input *input = Input::get_singleton(); if (p_connected) { - input->joy_connection_changed(p_index, true, String::utf8(p_id), String::utf8(p_guid)); + input->joy_connection_changed(p_index, true, p_id, p_guid); } else { input->joy_connection_changed(p_index, false, ""); } @@ -687,7 +847,20 @@ Vector<String> DisplayServerWeb::get_rendering_drivers_func() { // Clipboard void DisplayServerWeb::update_clipboard_callback(const char *p_text) { - get_singleton()->clipboard = String::utf8(p_text); + String text = p_text; + +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_update_clipboard_callback).bind(text).call_deferred(); + return; + } +#endif + + _update_clipboard_callback(text); +} + +void DisplayServerWeb::_update_clipboard_callback(const String &p_text) { + get_singleton()->clipboard = p_text; } void DisplayServerWeb::clipboard_set(const String &p_text) { @@ -702,6 +875,17 @@ String DisplayServerWeb::clipboard_get() const { } void DisplayServerWeb::send_window_event_callback(int p_notification) { +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(DisplayServerWeb::_send_window_event_callback).bind(p_notification).call_deferred(); + return; + } +#endif + + _send_window_event_callback(p_notification); +} + +void DisplayServerWeb::_send_window_event_callback(int p_notification) { DisplayServerWeb *ds = get_singleton(); if (!ds) { return; @@ -816,19 +1000,19 @@ DisplayServerWeb::DisplayServerWeb(const String &p_rendering_driver, WindowMode godot_js_input_mouse_wheel_cb(&DisplayServerWeb::mouse_wheel_callback); godot_js_input_touch_cb(&DisplayServerWeb::touch_callback, touch_event.identifier, touch_event.coords); godot_js_input_key_cb(&DisplayServerWeb::key_callback, key_event.code, key_event.key); - godot_js_input_paste_cb(update_clipboard_callback); - godot_js_input_drop_files_cb(drop_files_js_callback); + godot_js_input_paste_cb(&DisplayServerWeb::update_clipboard_callback); + godot_js_input_drop_files_cb(&DisplayServerWeb::drop_files_js_callback); godot_js_input_gamepad_cb(&DisplayServerWeb::gamepad_callback); // JS Display interface (js/libs/library_godot_display.js) godot_js_display_fullscreen_cb(&DisplayServerWeb::fullscreen_change_callback); - godot_js_display_window_blur_cb(&window_blur_callback); - godot_js_display_notification_cb(&send_window_event_callback, + godot_js_display_window_blur_cb(&DisplayServerWeb::window_blur_callback); + godot_js_display_notification_cb(&DisplayServerWeb::send_window_event_callback, WINDOW_EVENT_MOUSE_ENTER, WINDOW_EVENT_MOUSE_EXIT, WINDOW_EVENT_FOCUS_IN, WINDOW_EVENT_FOCUS_OUT); - godot_js_display_vk_cb(&vk_input_text_callback); + godot_js_display_vk_cb(&DisplayServerWeb::vk_input_text_callback); Input::get_singleton()->set_event_dispatch_function(_dispatch_input_event); } diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h index 1653deff80..51c6ab3c0a 100644 --- a/platform/web/display_server_web.h +++ b/platform/web/display_server_web.h @@ -91,28 +91,43 @@ private: // events WASM_EXPORT static void fullscreen_change_callback(int p_fullscreen); + static void _fullscreen_change_callback(int p_fullscreen); WASM_EXPORT static int mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers); + static int _mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers); WASM_EXPORT static void mouse_move_callback(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers); + static void _mouse_move_callback(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers); WASM_EXPORT static int mouse_wheel_callback(double p_delta_x, double p_delta_y); + static int _mouse_wheel_callback(double p_delta_x, double p_delta_y); WASM_EXPORT static void touch_callback(int p_type, int p_count); + static void _touch_callback(int p_type, int p_count); WASM_EXPORT static void key_callback(int p_pressed, int p_repeat, int p_modifiers); + static void _key_callback(const String &p_key_event_code, const String &p_key_event_key, int p_pressed, int p_repeat, int p_modifiers); WASM_EXPORT static void vk_input_text_callback(const char *p_text, int p_cursor); + static void _vk_input_text_callback(const String &p_text, int p_cursor); WASM_EXPORT static void gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid); + static void _gamepad_callback(int p_index, int p_connected, const String &p_id, const String &p_guid); + WASM_EXPORT static void js_utterance_callback(int p_event, int p_id, int p_pos); + static void _js_utterance_callback(int p_event, int p_id, int p_pos); + WASM_EXPORT static void request_quit_callback(); + static void _request_quit_callback(); + WASM_EXPORT static void window_blur_callback(); + static void _window_blur_callback(); + WASM_EXPORT static void update_voices_callback(int p_size, const char **p_voice); + static void _update_voices_callback(const Vector<String> &p_voices); + WASM_EXPORT static void update_clipboard_callback(const char *p_text); + static void _update_clipboard_callback(const String &p_text); + WASM_EXPORT static void send_window_event_callback(int p_notification); + static void _send_window_event_callback(int p_notification); + WASM_EXPORT static void drop_files_js_callback(const char **p_filev, int p_filec); + static void _drop_files_js_callback(const Vector<String> &p_files); + void process_joypads(); - WASM_EXPORT static void _js_utterance_callback(int p_event, int p_id, int p_pos); static Vector<String> get_rendering_drivers_func(); static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error); static void _dispatch_input_event(const Ref<InputEvent> &p_event); - WASM_EXPORT static void request_quit_callback(); - WASM_EXPORT static void window_blur_callback(); - WASM_EXPORT static void update_voices_callback(int p_size, const char **p_voice); - WASM_EXPORT static void update_clipboard_callback(const char *p_text); - WASM_EXPORT static void send_window_event_callback(int p_notification); - WASM_EXPORT static void drop_files_js_callback(char **p_filev, int p_filec); - protected: int get_current_video_driver() const; diff --git a/platform/web/godot_js.h b/platform/web/godot_js.h index f172148bf9..031e67e486 100644 --- a/platform/web/godot_js.h +++ b/platform/web/godot_js.h @@ -38,6 +38,7 @@ extern "C" { #endif #include <stddef.h> +#include <stdint.h> // Config extern void godot_js_config_locale_get(char *p_ptr, int p_ptr_max); @@ -69,7 +70,7 @@ extern int godot_js_input_gamepad_sample(); extern int godot_js_input_gamepad_sample_count(); extern int godot_js_input_gamepad_sample_get(int p_idx, float r_btns[16], int32_t *r_btns_num, float r_axes[10], int32_t *r_axes_num, int32_t *r_standard); extern void godot_js_input_paste_cb(void (*p_callback)(const char *p_text)); -extern void godot_js_input_drop_files_cb(void (*p_callback)(char **p_filev, int p_filec)); +extern void godot_js_input_drop_files_cb(void (*p_callback)(const char **p_filev, int p_filec)); // TTS extern int godot_js_tts_is_speaking(); diff --git a/platform/web/godot_webgl2.h b/platform/web/godot_webgl2.h index d2f46e125e..3ade9e4239 100644 --- a/platform/web/godot_webgl2.h +++ b/platform/web/godot_webgl2.h @@ -44,6 +44,7 @@ extern "C" { #endif void godot_webgl2_glFramebufferTextureMultiviewOVR(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint baseViewIndex, GLsizei numViews); +void godot_webgl2_glGetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, GLvoid *data); #define glFramebufferTextureMultiviewOVR godot_webgl2_glFramebufferTextureMultiviewOVR diff --git a/platform/web/javascript_bridge_singleton.cpp b/platform/web/javascript_bridge_singleton.cpp index 1bb72456e8..d72ad8331b 100644 --- a/platform/web/javascript_bridge_singleton.cpp +++ b/platform/web/javascript_bridge_singleton.cpp @@ -72,7 +72,8 @@ private: WASM_EXPORT static void _free_lock(void **p_lock, int p_type); WASM_EXPORT static Variant _js2variant(int p_type, godot_js_wrapper_ex *p_val); WASM_EXPORT static void *_alloc_variants(int p_size); - WASM_EXPORT static void _callback(void *p_ref, int p_arg_id, int p_argc); + WASM_EXPORT static void callback(void *p_ref, int p_arg_id, int p_argc); + static void _callback(const JavaScriptObjectImpl *obj, Variant arg); protected: bool _set(const StringName &p_name, const Variant &p_value) override; @@ -163,7 +164,7 @@ void JavaScriptObjectImpl::_get_property_list(List<PropertyInfo> *p_list) const } void JavaScriptObjectImpl::_free_lock(void **p_lock, int p_type) { - ERR_FAIL_COND_MSG(*p_lock == nullptr, "No lock to free!"); + ERR_FAIL_NULL_MSG(*p_lock, "No lock to free!"); const Variant::Type type = (Variant::Type)p_type; switch (type) { case Variant::STRING: { @@ -245,9 +246,10 @@ Variant JavaScriptObjectImpl::callp(const StringName &p_method, const Variant ** return _js2variant(type, &exchange); } -void JavaScriptObjectImpl::_callback(void *p_ref, int p_args_id, int p_argc) { +void JavaScriptObjectImpl::callback(void *p_ref, int p_args_id, int p_argc) { const JavaScriptObjectImpl *obj = (JavaScriptObjectImpl *)p_ref; ERR_FAIL_COND_MSG(obj->_callable.is_null(), "JavaScript callback failed."); + Vector<const Variant *> argp; Array arg_arr; for (int i = 0; i < p_argc; i++) { @@ -256,7 +258,20 @@ void JavaScriptObjectImpl::_callback(void *p_ref, int p_args_id, int p_argc) { int type = godot_js_wrapper_object_getvar(p_args_id, Variant::INT, &exchange); arg_arr.push_back(_js2variant(type, &exchange)); } - obj->_callable.call(arg_arr); + Variant arg = arg_arr; + +#ifdef PROXY_TO_PTHREAD_ENABLED + if (!Thread::is_main_thread()) { + callable_mp_static(JavaScriptObjectImpl::_callback).bind(obj, arg).call_deferred(); + return; + } +#endif + + _callback(obj, arg); +} + +void JavaScriptObjectImpl::_callback(const JavaScriptObjectImpl *obj, Variant arg) { + obj->_callable.call(arg); // Set return value godot_js_wrapper_ex exchange; @@ -273,7 +288,7 @@ void JavaScriptObjectImpl::_callback(void *p_ref, int p_args_id, int p_argc) { Ref<JavaScriptObject> JavaScriptBridge::create_callback(const Callable &p_callable) { Ref<JavaScriptObjectImpl> out = memnew(JavaScriptObjectImpl); out->_callable = p_callable; - out->_js_id = godot_js_wrapper_create_cb(out.ptr(), JavaScriptObjectImpl::_callback); + out->_js_id = godot_js_wrapper_create_cb(out.ptr(), JavaScriptObjectImpl::callback); return out; } diff --git a/platform/web/js/engine/config.js b/platform/web/js/engine/config.js index 6a30c253fb..0b6626968e 100644 --- a/platform/web/js/engine/config.js +++ b/platform/web/js/engine/config.js @@ -292,7 +292,9 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- return {}; }, 'locateFile': function (path) { - if (path.endsWith('.worker.js')) { + if (!path.startsWith('godot.')) { + return path; + } else if (path.endsWith('.worker.js')) { return `${loadPath}.worker.js`; } else if (path.endsWith('.audio.worklet.js')) { return `${loadPath}.audio.worklet.js`; diff --git a/platform/web/js/engine/engine.js b/platform/web/js/engine/engine.js index fb80bd55e1..3d6720a2fc 100644 --- a/platform/web/js/engine/engine.js +++ b/platform/web/js/engine/engine.js @@ -164,6 +164,10 @@ const Engine = (function () { // Preload GDExtension libraries. const libs = []; + if (me.config.gdextensionLibs.length > 0 && !me.rtenv['loadDynamicLibrary']) { + return Promise.reject(new Error('GDExtension libraries are not supported by this engine version. ' + + 'Enable "Extensions Support" for your export preset and/or build your custom template with "dlink_enabled=yes".')); + } me.config.gdextensionLibs.forEach(function (lib) { libs.push(me.rtenv['loadDynamicLibrary'](lib, { 'loadAsync': true })); }); diff --git a/platform/web/js/libs/library_godot_audio.js b/platform/web/js/libs/library_godot_audio.js index cc86c81096..b54c5cac85 100644 --- a/platform/web/js/libs/library_godot_audio.js +++ b/platform/web/js/libs/library_godot_audio.js @@ -159,16 +159,19 @@ const GodotAudio = { return 1; }, + godot_audio_has_worklet__proxy: 'sync', godot_audio_has_worklet__sig: 'i', godot_audio_has_worklet: function () { return (GodotAudio.ctx && GodotAudio.ctx.audioWorklet) ? 1 : 0; }, + godot_audio_has_script_processor__proxy: 'sync', godot_audio_has_script_processor__sig: 'i', godot_audio_has_script_processor: function () { return (GodotAudio.ctx && GodotAudio.ctx.createScriptProcessor) ? 1 : 0; }, + godot_audio_init__proxy: 'sync', godot_audio_init__sig: 'iiiii', godot_audio_init: function (p_mix_rate, p_latency, p_state_change, p_latency_update) { const statechange = GodotRuntime.get_func(p_state_change); @@ -179,6 +182,7 @@ const GodotAudio = { return channels; }, + godot_audio_resume__proxy: 'sync', godot_audio_resume__sig: 'v', godot_audio_resume: function () { if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') { @@ -358,6 +362,7 @@ const GodotAudioWorklet = { }, }, + godot_audio_worklet_create__proxy: 'sync', godot_audio_worklet_create__sig: 'ii', godot_audio_worklet_create: function (channels) { try { @@ -369,6 +374,7 @@ const GodotAudioWorklet = { return 0; }, + godot_audio_worklet_start__proxy: 'sync', godot_audio_worklet_start__sig: 'viiiii', godot_audio_worklet_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) { const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size); @@ -377,6 +383,7 @@ const GodotAudioWorklet = { GodotAudioWorklet.start(in_buffer, out_buffer, state); }, + godot_audio_worklet_start_no_threads__proxy: 'sync', godot_audio_worklet_start_no_threads__sig: 'viiiiii', godot_audio_worklet_start_no_threads: function (p_out_buf, p_out_size, p_out_callback, p_in_buf, p_in_size, p_in_callback) { const out_callback = GodotRuntime.get_func(p_out_callback); @@ -465,6 +472,7 @@ const GodotAudioScript = { }, }, + godot_audio_script_create__proxy: 'sync', godot_audio_script_create__sig: 'iii', godot_audio_script_create: function (buffer_length, channel_count) { const buf_len = GodotRuntime.getHeapValue(buffer_length, 'i32'); @@ -478,6 +486,7 @@ const GodotAudioScript = { return 0; }, + godot_audio_script_start__proxy: 'sync', godot_audio_script_start__sig: 'viiiii', godot_audio_script_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) { const onprocess = GodotRuntime.get_func(p_cb); diff --git a/platform/web/js/libs/library_godot_display.js b/platform/web/js/libs/library_godot_display.js index c60e6899f2..99fc429d8f 100644 --- a/platform/web/js/libs/library_godot_display.js +++ b/platform/web/js/libs/library_godot_display.js @@ -345,6 +345,7 @@ const GodotDisplay = { }, }, + godot_js_display_is_swap_ok_cancel__proxy: 'sync', godot_js_display_is_swap_ok_cancel__sig: 'i', godot_js_display_is_swap_ok_cancel: function () { const win = (['Windows', 'Win64', 'Win32', 'WinCE']); @@ -355,16 +356,19 @@ const GodotDisplay = { return 0; }, + godot_js_tts_is_speaking__proxy: 'sync', godot_js_tts_is_speaking__sig: 'i', godot_js_tts_is_speaking: function () { return window.speechSynthesis.speaking; }, + godot_js_tts_is_paused__proxy: 'sync', godot_js_tts_is_paused__sig: 'i', godot_js_tts_is_paused: function () { return window.speechSynthesis.paused; }, + godot_js_tts_get_voices__proxy: 'sync', godot_js_tts_get_voices__sig: 'vi', godot_js_tts_get_voices: function (p_callback) { const func = GodotRuntime.get_func(p_callback); @@ -382,6 +386,7 @@ const GodotDisplay = { } }, + godot_js_tts_speak__proxy: 'sync', godot_js_tts_speak__sig: 'viiiffii', godot_js_tts_speak: function (p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_callback) { const func = GodotRuntime.get_func(p_callback); @@ -424,53 +429,63 @@ const GodotDisplay = { window.speechSynthesis.speak(utterance); }, + godot_js_tts_pause__proxy: 'sync', godot_js_tts_pause__sig: 'v', godot_js_tts_pause: function () { window.speechSynthesis.pause(); }, + godot_js_tts_resume__proxy: 'sync', godot_js_tts_resume__sig: 'v', godot_js_tts_resume: function () { window.speechSynthesis.resume(); }, + godot_js_tts_stop__proxy: 'sync', godot_js_tts_stop__sig: 'v', godot_js_tts_stop: function () { window.speechSynthesis.cancel(); window.speechSynthesis.resume(); }, + godot_js_display_alert__proxy: 'sync', godot_js_display_alert__sig: 'vi', godot_js_display_alert: function (p_text) { window.alert(GodotRuntime.parseString(p_text)); // eslint-disable-line no-alert }, + godot_js_display_screen_dpi_get__proxy: 'sync', godot_js_display_screen_dpi_get__sig: 'i', godot_js_display_screen_dpi_get: function () { return GodotDisplay.getDPI(); }, + godot_js_display_pixel_ratio_get__proxy: 'sync', godot_js_display_pixel_ratio_get__sig: 'f', godot_js_display_pixel_ratio_get: function () { return GodotDisplayScreen.getPixelRatio(); }, + godot_js_display_fullscreen_request__proxy: 'sync', godot_js_display_fullscreen_request__sig: 'i', godot_js_display_fullscreen_request: function () { return GodotDisplayScreen.requestFullscreen(); }, + godot_js_display_fullscreen_exit__proxy: 'sync', godot_js_display_fullscreen_exit__sig: 'i', godot_js_display_fullscreen_exit: function () { return GodotDisplayScreen.exitFullscreen(); }, + godot_js_display_desired_size_set__proxy: 'sync', godot_js_display_desired_size_set__sig: 'vii', godot_js_display_desired_size_set: function (width, height) { GodotDisplayScreen.desired_size = [width, height]; GodotDisplayScreen.updateSize(); }, + godot_js_display_size_update__proxy: 'sync', godot_js_display_size_update__sig: 'i', godot_js_display_size_update: function () { const updated = GodotDisplayScreen.updateSize(); @@ -480,6 +495,7 @@ const GodotDisplay = { return updated; }, + godot_js_display_screen_size_get__proxy: 'sync', godot_js_display_screen_size_get__sig: 'vii', godot_js_display_screen_size_get: function (width, height) { const scale = GodotDisplayScreen.getPixelRatio(); @@ -487,12 +503,14 @@ const GodotDisplay = { GodotRuntime.setHeapValue(height, window.screen.height * scale, 'i32'); }, + godot_js_display_window_size_get__proxy: 'sync', godot_js_display_window_size_get__sig: 'vii', godot_js_display_window_size_get: function (p_width, p_height) { GodotRuntime.setHeapValue(p_width, GodotConfig.canvas.width, 'i32'); GodotRuntime.setHeapValue(p_height, GodotConfig.canvas.height, 'i32'); }, + godot_js_display_has_webgl__proxy: 'sync', godot_js_display_has_webgl__sig: 'ii', godot_js_display_has_webgl: function (p_version) { if (p_version !== 1 && p_version !== 2) { @@ -507,11 +525,13 @@ const GodotDisplay = { /* * Canvas */ + godot_js_display_canvas_focus__proxy: 'sync', godot_js_display_canvas_focus__sig: 'v', godot_js_display_canvas_focus: function () { GodotConfig.canvas.focus(); }, + godot_js_display_canvas_is_focused__proxy: 'sync', godot_js_display_canvas_is_focused__sig: 'i', godot_js_display_canvas_is_focused: function () { return document.activeElement === GodotConfig.canvas; @@ -520,6 +540,7 @@ const GodotDisplay = { /* * Touchscreen */ + godot_js_display_touchscreen_is_available__proxy: 'sync', godot_js_display_touchscreen_is_available__sig: 'i', godot_js_display_touchscreen_is_available: function () { return 'ontouchstart' in window; @@ -528,6 +549,7 @@ const GodotDisplay = { /* * Clipboard */ + godot_js_display_clipboard_set__proxy: 'sync', godot_js_display_clipboard_set__sig: 'ii', godot_js_display_clipboard_set: function (p_text) { const text = GodotRuntime.parseString(p_text); @@ -541,6 +563,7 @@ const GodotDisplay = { return 0; }, + godot_js_display_clipboard_get__proxy: 'sync', godot_js_display_clipboard_get__sig: 'ii', godot_js_display_clipboard_get: function (callback) { const func = GodotRuntime.get_func(callback); @@ -560,11 +583,13 @@ const GodotDisplay = { /* * Window */ + godot_js_display_window_title_set__proxy: 'sync', godot_js_display_window_title_set__sig: 'vi', godot_js_display_window_title_set: function (p_data) { document.title = GodotRuntime.parseString(p_data); }, + godot_js_display_window_icon_set__proxy: 'sync', godot_js_display_window_icon_set__sig: 'vii', godot_js_display_window_icon_set: function (p_ptr, p_len) { let link = document.getElementById('-gd-engine-icon'); @@ -593,6 +618,7 @@ const GodotDisplay = { /* * Cursor */ + godot_js_display_cursor_set_visible__proxy: 'sync', godot_js_display_cursor_set_visible__sig: 'vi', godot_js_display_cursor_set_visible: function (p_visible) { const visible = p_visible !== 0; @@ -607,16 +633,19 @@ const GodotDisplay = { } }, + godot_js_display_cursor_is_hidden__proxy: 'sync', godot_js_display_cursor_is_hidden__sig: 'i', godot_js_display_cursor_is_hidden: function () { return !GodotDisplayCursor.visible; }, + godot_js_display_cursor_set_shape__proxy: 'sync', godot_js_display_cursor_set_shape__sig: 'vi', godot_js_display_cursor_set_shape: function (p_string) { GodotDisplayCursor.set_shape(GodotRuntime.parseString(p_string)); }, + godot_js_display_cursor_set_custom_shape__proxy: 'sync', godot_js_display_cursor_set_custom_shape__sig: 'viiiii', godot_js_display_cursor_set_custom_shape: function (p_shape, p_ptr, p_len, p_hotspot_x, p_hotspot_y) { const shape = GodotRuntime.parseString(p_shape); @@ -640,6 +669,7 @@ const GodotDisplay = { } }, + godot_js_display_cursor_lock_set__proxy: 'sync', godot_js_display_cursor_lock_set__sig: 'vi', godot_js_display_cursor_lock_set: function (p_lock) { if (p_lock) { @@ -649,6 +679,7 @@ const GodotDisplay = { } }, + godot_js_display_cursor_is_locked__proxy: 'sync', godot_js_display_cursor_is_locked__sig: 'i', godot_js_display_cursor_is_locked: function () { return GodotDisplayCursor.isPointerLocked() ? 1 : 0; @@ -657,6 +688,7 @@ const GodotDisplay = { /* * Listeners */ + godot_js_display_fullscreen_cb__proxy: 'sync', godot_js_display_fullscreen_cb__sig: 'vi', godot_js_display_fullscreen_cb: function (callback) { const canvas = GodotConfig.canvas; @@ -671,6 +703,7 @@ const GodotDisplay = { GodotEventListeners.add(document, 'webkitfullscreenchange', change_cb, false); }, + godot_js_display_window_blur_cb__proxy: 'sync', godot_js_display_window_blur_cb__sig: 'vi', godot_js_display_window_blur_cb: function (callback) { const func = GodotRuntime.get_func(callback); @@ -679,6 +712,7 @@ const GodotDisplay = { }, false); }, + godot_js_display_notification_cb__proxy: 'sync', godot_js_display_notification_cb__sig: 'viiiii', godot_js_display_notification_cb: function (callback, p_enter, p_exit, p_in, p_out) { const canvas = GodotConfig.canvas; @@ -691,6 +725,7 @@ const GodotDisplay = { }); }, + godot_js_display_setup_canvas__proxy: 'sync', godot_js_display_setup_canvas__sig: 'viiii', godot_js_display_setup_canvas: function (p_width, p_height, p_fullscreen, p_hidpi) { const canvas = GodotConfig.canvas; @@ -725,6 +760,7 @@ const GodotDisplay = { /* * Virtual Keyboard */ + godot_js_display_vk_show__proxy: 'sync', godot_js_display_vk_show__sig: 'viiii', godot_js_display_vk_show: function (p_text, p_type, p_start, p_end) { const text = GodotRuntime.parseString(p_text); @@ -733,21 +769,25 @@ const GodotDisplay = { GodotDisplayVK.show(text, p_type, start, end); }, + godot_js_display_vk_hide__proxy: 'sync', godot_js_display_vk_hide__sig: 'v', godot_js_display_vk_hide: function () { GodotDisplayVK.hide(); }, + godot_js_display_vk_available__proxy: 'sync', godot_js_display_vk_available__sig: 'i', godot_js_display_vk_available: function () { return GodotDisplayVK.available(); }, + godot_js_display_tts_available__proxy: 'sync', godot_js_display_tts_available__sig: 'i', godot_js_display_tts_available: function () { return 'speechSynthesis' in window; }, + godot_js_display_vk_cb__proxy: 'sync', godot_js_display_vk_cb__sig: 'vi', godot_js_display_vk_cb: function (p_input_cb) { const input_cb = GodotRuntime.get_func(p_input_cb); diff --git a/platform/web/js/libs/library_godot_fetch.js b/platform/web/js/libs/library_godot_fetch.js index 4ef24903e3..00616bc1a5 100644 --- a/platform/web/js/libs/library_godot_fetch.js +++ b/platform/web/js/libs/library_godot_fetch.js @@ -125,6 +125,7 @@ const GodotFetch = { }, }, + godot_js_fetch_create__proxy: 'sync', godot_js_fetch_create__sig: 'iiiiiii', godot_js_fetch_create: function (p_method, p_url, p_headers, p_headers_size, p_body, p_body_size) { const method = GodotRuntime.parseString(p_method); @@ -145,6 +146,7 @@ const GodotFetch = { }), body); }, + godot_js_fetch_state_get__proxy: 'sync', godot_js_fetch_state_get__sig: 'ii', godot_js_fetch_state_get: function (p_id) { const obj = IDHandler.get(p_id); @@ -166,6 +168,7 @@ const GodotFetch = { return -1; }, + godot_js_fetch_http_status_get__proxy: 'sync', godot_js_fetch_http_status_get__sig: 'ii', godot_js_fetch_http_status_get: function (p_id) { const obj = IDHandler.get(p_id); @@ -175,6 +178,7 @@ const GodotFetch = { return obj.status; }, + godot_js_fetch_read_headers__proxy: 'sync', godot_js_fetch_read_headers__sig: 'iiii', godot_js_fetch_read_headers: function (p_id, p_parse_cb, p_ref) { const obj = IDHandler.get(p_id); @@ -192,6 +196,7 @@ const GodotFetch = { return 0; }, + godot_js_fetch_read_chunk__proxy: 'sync', godot_js_fetch_read_chunk__sig: 'iiii', godot_js_fetch_read_chunk: function (p_id, p_buf, p_buf_size) { const obj = IDHandler.get(p_id); @@ -218,6 +223,7 @@ const GodotFetch = { return p_buf_size - to_read; }, + godot_js_fetch_is_chunked__proxy: 'sync', godot_js_fetch_is_chunked__sig: 'ii', godot_js_fetch_is_chunked: function (p_id) { const obj = IDHandler.get(p_id); @@ -227,6 +233,7 @@ const GodotFetch = { return obj.chunked ? 1 : 0; }, + godot_js_fetch_free__proxy: 'sync', godot_js_fetch_free__sig: 'vi', godot_js_fetch_free: function (id) { GodotFetch.free(id); diff --git a/platform/web/js/libs/library_godot_input.js b/platform/web/js/libs/library_godot_input.js index 1b221e78b3..92113e85c9 100644 --- a/platform/web/js/libs/library_godot_input.js +++ b/platform/web/js/libs/library_godot_input.js @@ -356,6 +356,7 @@ const GodotInput = { /* * Mouse API */ + godot_js_input_mouse_move_cb__proxy: 'sync', godot_js_input_mouse_move_cb__sig: 'vi', godot_js_input_mouse_move_cb: function (callback) { const func = GodotRuntime.get_func(callback); @@ -374,6 +375,7 @@ const GodotInput = { GodotEventListeners.add(window, 'mousemove', move_cb, false); }, + godot_js_input_mouse_wheel_cb__proxy: 'sync', godot_js_input_mouse_wheel_cb__sig: 'vi', godot_js_input_mouse_wheel_cb: function (callback) { const func = GodotRuntime.get_func(callback); @@ -385,6 +387,7 @@ const GodotInput = { GodotEventListeners.add(GodotConfig.canvas, 'wheel', wheel_cb, false); }, + godot_js_input_mouse_button_cb__proxy: 'sync', godot_js_input_mouse_button_cb__sig: 'vi', godot_js_input_mouse_button_cb: function (callback) { const func = GodotRuntime.get_func(callback); @@ -409,6 +412,7 @@ const GodotInput = { /* * Touch API */ + godot_js_input_touch_cb__proxy: 'sync', godot_js_input_touch_cb__sig: 'viii', godot_js_input_touch_cb: function (callback, ids, coords) { const func = GodotRuntime.get_func(callback); @@ -442,6 +446,7 @@ const GodotInput = { /* * Key API */ + godot_js_input_key_cb__proxy: 'sync', godot_js_input_key_cb__sig: 'viii', godot_js_input_key_cb: function (callback, code, key) { const func = GodotRuntime.get_func(callback); @@ -459,23 +464,27 @@ const GodotInput = { /* * Gamepad API */ + godot_js_input_gamepad_cb__proxy: 'sync', godot_js_input_gamepad_cb__sig: 'vi', godot_js_input_gamepad_cb: function (change_cb) { const onchange = GodotRuntime.get_func(change_cb); GodotInputGamepads.init(onchange); }, + godot_js_input_gamepad_sample_count__proxy: 'sync', godot_js_input_gamepad_sample_count__sig: 'i', godot_js_input_gamepad_sample_count: function () { return GodotInputGamepads.get_samples().length; }, + godot_js_input_gamepad_sample__proxy: 'sync', godot_js_input_gamepad_sample__sig: 'i', godot_js_input_gamepad_sample: function () { GodotInputGamepads.sample(); return 0; }, + godot_js_input_gamepad_sample_get__proxy: 'sync', godot_js_input_gamepad_sample_get__sig: 'iiiiiii', godot_js_input_gamepad_sample_get: function (p_index, r_btns, r_btns_num, r_axes, r_axes_num, r_standard) { const sample = GodotInputGamepads.get_sample(p_index); @@ -502,6 +511,7 @@ const GodotInput = { /* * Drag/Drop API */ + godot_js_input_drop_files_cb__proxy: 'sync', godot_js_input_drop_files_cb__sig: 'vi', godot_js_input_drop_files_cb: function (callback) { const func = GodotRuntime.get_func(callback); @@ -524,6 +534,7 @@ const GodotInput = { }, /* Paste API */ + godot_js_input_paste_cb__proxy: 'sync', godot_js_input_paste_cb__sig: 'vi', godot_js_input_paste_cb: function (callback) { const func = GodotRuntime.get_func(callback); @@ -535,6 +546,7 @@ const GodotInput = { }, false); }, + godot_js_input_vibrate_handheld__proxy: 'sync', godot_js_input_vibrate_handheld__sig: 'vi', godot_js_input_vibrate_handheld: function (p_duration_ms) { if (typeof navigator.vibrate !== 'function') { diff --git a/platform/web/js/libs/library_godot_javascript_singleton.js b/platform/web/js/libs/library_godot_javascript_singleton.js index 1764c9a026..b17fde1544 100644 --- a/platform/web/js/libs/library_godot_javascript_singleton.js +++ b/platform/web/js/libs/library_godot_javascript_singleton.js @@ -121,6 +121,7 @@ const GodotJSWrapper = { }, }, + godot_js_wrapper_interface_get__proxy: 'sync', godot_js_wrapper_interface_get__sig: 'ii', godot_js_wrapper_interface_get: function (p_name) { const name = GodotRuntime.parseString(p_name); @@ -130,6 +131,7 @@ const GodotJSWrapper = { return 0; }, + godot_js_wrapper_object_get__proxy: 'sync', godot_js_wrapper_object_get__sig: 'iiii', godot_js_wrapper_object_get: function (p_id, p_exchange, p_prop) { const obj = GodotJSWrapper.get_proxied_value(p_id); @@ -148,6 +150,7 @@ const GodotJSWrapper = { return GodotJSWrapper.js2variant(obj, p_exchange); }, + godot_js_wrapper_object_set__proxy: 'sync', godot_js_wrapper_object_set__sig: 'viiii', godot_js_wrapper_object_set: function (p_id, p_name, p_type, p_exchange) { const obj = GodotJSWrapper.get_proxied_value(p_id); @@ -162,6 +165,7 @@ const GodotJSWrapper = { } }, + godot_js_wrapper_object_call__proxy: 'sync', godot_js_wrapper_object_call__sig: 'iiiiiiiii', godot_js_wrapper_object_call: function (p_id, p_method, p_args, p_argc, p_convert_callback, p_exchange, p_lock, p_free_lock_callback) { const obj = GodotJSWrapper.get_proxied_value(p_id); @@ -189,6 +193,7 @@ const GodotJSWrapper = { } }, + godot_js_wrapper_object_unref__proxy: 'sync', godot_js_wrapper_object_unref__sig: 'vi', godot_js_wrapper_object_unref: function (p_id) { const proxy = IDHandler.get(p_id); @@ -197,6 +202,7 @@ const GodotJSWrapper = { } }, + godot_js_wrapper_create_cb__proxy: 'sync', godot_js_wrapper_create_cb__sig: 'iii', godot_js_wrapper_create_cb: function (p_ref, p_func) { const func = GodotRuntime.get_func(p_func); @@ -221,11 +227,13 @@ const GodotJSWrapper = { return id; }, + godot_js_wrapper_object_set_cb_ret__proxy: 'sync', godot_js_wrapper_object_set_cb_ret__sig: 'vii', godot_js_wrapper_object_set_cb_ret: function (p_val_type, p_val_ex) { GodotJSWrapper.cb_ret = GodotJSWrapper.variant2js(p_val_type, p_val_ex); }, + godot_js_wrapper_object_getvar__proxy: 'sync', godot_js_wrapper_object_getvar__sig: 'iiii', godot_js_wrapper_object_getvar: function (p_id, p_type, p_exchange) { const obj = GodotJSWrapper.get_proxied_value(p_id); @@ -244,6 +252,7 @@ const GodotJSWrapper = { } }, + godot_js_wrapper_object_setvar__proxy: 'sync', godot_js_wrapper_object_setvar__sig: 'iiiiii', godot_js_wrapper_object_setvar: function (p_id, p_key_type, p_key_ex, p_val_type, p_val_ex) { const obj = GodotJSWrapper.get_proxied_value(p_id); @@ -260,6 +269,7 @@ const GodotJSWrapper = { } }, + godot_js_wrapper_create_object__proxy: 'sync', godot_js_wrapper_create_object__sig: 'iiiiiiii', godot_js_wrapper_create_object: function (p_object, p_args, p_argc, p_convert_callback, p_exchange, p_lock, p_free_lock_callback) { const name = GodotRuntime.parseString(p_object); diff --git a/platform/web/js/libs/library_godot_os.js b/platform/web/js/libs/library_godot_os.js index 00ae399583..92635cb6ae 100644 --- a/platform/web/js/libs/library_godot_os.js +++ b/platform/web/js/libs/library_godot_os.js @@ -91,11 +91,13 @@ const GodotConfig = { }, }, + godot_js_config_canvas_id_get__proxy: 'sync', godot_js_config_canvas_id_get__sig: 'vii', godot_js_config_canvas_id_get: function (p_ptr, p_ptr_max) { GodotRuntime.stringToHeap(`#${GodotConfig.canvas.id}`, p_ptr, p_ptr_max); }, + godot_js_config_locale_get__proxy: 'sync', godot_js_config_locale_get__sig: 'vii', godot_js_config_locale_get: function (p_ptr, p_ptr_max) { GodotRuntime.stringToHeap(GodotConfig.locale, p_ptr, p_ptr_max); @@ -266,22 +268,26 @@ const GodotOS = { }, }, + godot_js_os_finish_async__proxy: 'sync', godot_js_os_finish_async__sig: 'vi', godot_js_os_finish_async: function (p_callback) { const func = GodotRuntime.get_func(p_callback); GodotOS.finish_async(func); }, + godot_js_os_request_quit_cb__proxy: 'sync', godot_js_os_request_quit_cb__sig: 'vi', godot_js_os_request_quit_cb: function (p_callback) { GodotOS.request_quit = GodotRuntime.get_func(p_callback); }, + godot_js_os_fs_is_persistent__proxy: 'sync', godot_js_os_fs_is_persistent__sig: 'i', godot_js_os_fs_is_persistent: function () { return GodotFS.is_persistent(); }, + godot_js_os_fs_sync__proxy: 'sync', godot_js_os_fs_sync__sig: 'vi', godot_js_os_fs_sync: function (callback) { const func = GodotRuntime.get_func(callback); @@ -291,6 +297,7 @@ const GodotOS = { }); }, + godot_js_os_has_feature__proxy: 'sync', godot_js_os_has_feature__sig: 'ii', godot_js_os_has_feature: function (p_ftr) { const ftr = GodotRuntime.parseString(p_ftr); @@ -313,6 +320,7 @@ const GodotOS = { return 0; }, + godot_js_os_execute__proxy: 'sync', godot_js_os_execute__sig: 'ii', godot_js_os_execute: function (p_json) { const json_args = GodotRuntime.parseString(p_json); @@ -324,11 +332,13 @@ const GodotOS = { return 1; }, + godot_js_os_shell_open__proxy: 'sync', godot_js_os_shell_open__sig: 'vi', godot_js_os_shell_open: function (p_uri) { window.open(GodotRuntime.parseString(p_uri), '_blank'); }, + godot_js_os_hw_concurrency_get__proxy: 'sync', godot_js_os_hw_concurrency_get__sig: 'i', godot_js_os_hw_concurrency_get: function () { // TODO Godot core needs fixing to avoid spawning too many threads (> 24). @@ -336,6 +346,7 @@ const GodotOS = { return concurrency < 2 ? concurrency : 2; }, + godot_js_os_download_buffer__proxy: 'sync', godot_js_os_download_buffer__sig: 'viiii', godot_js_os_download_buffer: function (p_ptr, p_size, p_name, p_mime) { const buf = GodotRuntime.heapSlice(HEAP8, p_ptr, p_size); @@ -426,6 +437,7 @@ const GodotPWA = { }, }, + godot_js_pwa_cb__proxy: 'sync', godot_js_pwa_cb__sig: 'vi', godot_js_pwa_cb: function (p_update_cb) { if ('serviceWorker' in navigator) { @@ -434,6 +446,7 @@ const GodotPWA = { } }, + godot_js_pwa_update__proxy: 'sync', godot_js_pwa_update__sig: 'i', godot_js_pwa_update: function () { if ('serviceWorker' in navigator && GodotPWA.hasUpdate) { diff --git a/platform/web/js/libs/library_godot_webgl2.js b/platform/web/js/libs/library_godot_webgl2.js index 3c6de4a071..dbaec9f01b 100644 --- a/platform/web/js/libs/library_godot_webgl2.js +++ b/platform/web/js/libs/library_godot_webgl2.js @@ -32,6 +32,19 @@ const GodotWebGL2 = { $GodotWebGL2__deps: ['$GL', '$GodotRuntime'], $GodotWebGL2: {}, + // This is implemented as "glGetBufferSubData" in new emscripten versions. + // Since we have to support older (pre 2.0.17) emscripten versions, we add this wrapper function instead. + godot_webgl2_glGetBufferSubData__proxy: 'sync', + godot_webgl2_glGetBufferSubData__sig: 'vippp', + godot_webgl2_glGetBufferSubData__deps: ['$GL', 'emscripten_webgl_get_current_context'], + godot_webgl2_glGetBufferSubData: function (target, offset, size, data) { + const gl_context_handle = _emscripten_webgl_get_current_context(); // eslint-disable-line no-undef + const gl = GL.getContext(gl_context_handle); + if (gl) { + gl.GLctx['getBufferSubData'](target, offset, HEAPU8, data, size); + } + }, + godot_webgl2_glFramebufferTextureMultiviewOVR__deps: ['emscripten_webgl_get_current_context'], godot_webgl2_glFramebufferTextureMultiviewOVR__proxy: 'sync', godot_webgl2_glFramebufferTextureMultiviewOVR__sig: 'viiiiii', diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp index 186e4abf92..cbdcbf565d 100644 --- a/platform/web/os_web.cpp +++ b/platform/web/os_web.cpp @@ -160,6 +160,12 @@ String OS_Web::get_name() const { return "Web"; } +void OS_Web::add_frame_delay(bool p_can_draw) { +#ifndef PROXY_TO_PTHREAD_ENABLED + OS::add_frame_delay(p_can_draw); +#endif +} + void OS_Web::vibrate_handheld(int p_duration_ms) { godot_js_input_vibrate_handheld(p_duration_ms); } diff --git a/platform/web/os_web.h b/platform/web/os_web.h index f262337f00..5a48997c17 100644 --- a/platform/web/os_web.h +++ b/platform/web/os_web.h @@ -91,9 +91,10 @@ public: String get_executable_path() const override; Error shell_open(String p_uri) override; String get_name() const override; + // Override default OS implementation which would block the main thread with delay_usec. // Implemented in web_main.cpp loop callback instead. - void add_frame_delay(bool p_can_draw) override {} + void add_frame_delay(bool p_can_draw) override; void vibrate_handheld(int p_duration_ms) override; diff --git a/platform/web/web_main.cpp b/platform/web/web_main.cpp index f199f8ffd8..ad2a801881 100644 --- a/platform/web/web_main.cpp +++ b/platform/web/web_main.cpp @@ -40,7 +40,10 @@ #include <stdlib.h> static OS_Web *os = nullptr; +#ifndef PROXY_TO_PTHREAD_ENABLED static uint64_t target_ticks = 0; +#endif + static bool main_started = false; static bool shutdown_complete = false; @@ -63,15 +66,20 @@ void cleanup_after_sync() { } void main_loop_callback() { +#ifndef PROXY_TO_PTHREAD_ENABLED uint64_t current_ticks = os->get_ticks_usec(); +#endif bool force_draw = DisplayServerWeb::get_singleton()->check_size_force_redraw(); if (force_draw) { Main::force_redraw(); +#ifndef PROXY_TO_PTHREAD_ENABLED } else if (current_ticks < target_ticks) { return; // Skip frame. +#endif } +#ifndef PROXY_TO_PTHREAD_ENABLED int max_fps = Engine::get_singleton()->get_max_fps(); if (max_fps > 0) { if (current_ticks - target_ticks > 1000000) { @@ -81,6 +89,8 @@ void main_loop_callback() { } target_ticks += (uint64_t)(1000000 / max_fps); } +#endif + if (os->main_loop_iterate()) { emscripten_cancel_main_loop(); // Cancel current loop and set the cleanup one. emscripten_set_main_loop(exit_callback, -1, false); diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 7caa0153d7..bdacdbb9ba 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -419,6 +419,7 @@ def configure_msvc(env, vcvars_msvc_config): "dwmapi", "dwrite", "wbemuuid", + "ntdll", ] if env.debug_features: @@ -610,6 +611,7 @@ def configure_mingw(env): "dwmapi", "dwrite", "wbemuuid", + "ntdll", ] ) diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index cc5ae9ad45..bb0b64ba10 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -346,11 +346,31 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String } } if (!p_callback.is_null()) { - p_callback.call(true, file_names, index); + 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 }; + + p_callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute file dialogs callback: %s."), Variant::get_callable_error_text(p_callback, args, 3, ce))); + } } } else { if (!p_callback.is_null()) { - p_callback.call(false, Vector<String>(), index); + 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 }; + + p_callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat(RTR("Failed to execute file dialogs callback: %s."), Variant::get_callable_error_text(p_callback, args, 3, ce))); + } } } pfd->Release(); diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp index 0ac6c2c8b0..60edb00dd2 100644 --- a/platform/windows/joypad_windows.cpp +++ b/platform/windows/joypad_windows.cpp @@ -109,6 +109,7 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) { static GUID IID_XOneSWirelessGamepad = { MAKELONG(0x045E, 0x02EA), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } }; static GUID IID_XOneSBluetoothGamepad = { MAKELONG(0x045E, 0x02E0), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } }; static GUID IID_XOneEliteWirelessGamepad = { MAKELONG(0x045E, 0x02E3), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } }; + static GUID IID_XOneElite2WirelessGamepad = { MAKELONG(0x045E, 0x0B22), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } }; if (memcmp(p_guid, &IID_ValveStreamingGamepad, sizeof(*p_guid)) == 0 || memcmp(p_guid, &IID_X360WiredGamepad, sizeof(*p_guid)) == 0 || @@ -120,7 +121,8 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) { memcmp(p_guid, &IID_XOneNewWirelessGamepad, sizeof(*p_guid)) == 0 || memcmp(p_guid, &IID_XOneSWirelessGamepad, sizeof(*p_guid)) == 0 || memcmp(p_guid, &IID_XOneSBluetoothGamepad, sizeof(*p_guid)) == 0 || - memcmp(p_guid, &IID_XOneEliteWirelessGamepad, sizeof(*p_guid)) == 0) + memcmp(p_guid, &IID_XOneEliteWirelessGamepad, sizeof(*p_guid)) == 0 || + memcmp(p_guid, &IID_XOneElite2WirelessGamepad, sizeof(*p_guid)) == 0) return true; PRAWINPUTDEVICELIST dev_list = nullptr; |
