diff options
Diffstat (limited to 'platform')
67 files changed, 1617 insertions, 535 deletions
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 11c0945ce0..445a6ea6ea 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -309,13 +309,10 @@ 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()) { - const Variant *argp = &p_arg; - Variant ret; - Callable::CallError ce; if (p_deferred) { - p_callable.callp((const Variant **)&argp, 1, ret, ce); + p_callable.call(p_arg); } else { - p_callable.call_deferredp((const Variant **)&argp, 1); + p_callable.call_deferred(p_arg); } } } @@ -538,16 +535,9 @@ void DisplayServerAndroid::reset_window() { } void DisplayServerAndroid::notify_surface_changed(int p_width, int p_height) { - if (rect_changed_callback.is_null()) { - return; + if (rect_changed_callback.is_valid()) { + rect_changed_callback.call(Rect2i(0, 0, p_width, p_height)); } - - const Variant size = Rect2i(0, 0, p_width, p_height); - const Variant *sizep = &size; - Variant ret; - Callable::CallError ce; - - rect_changed_callback.callp(reinterpret_cast<const Variant **>(&sizep), 1, ret, ce); } DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error) { 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/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt index 7cedfa6888..02709d4dc5 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt @@ -91,10 +91,6 @@ open class GodotEditor : GodotActivity() { private val commandLineParams = ArrayList<String>() override fun onCreate(savedInstanceState: Bundle?) { - // We exclude certain permissions from the set we request at startup, as they'll be - // requested on demand based on use-cases. - PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO)) - val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS) Log.d(TAG, "Received parameters ${params.contentToString()}") updateCommandLineParams(params) 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 0e111d5247..04d196a6e9 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,10 @@ class Godot(private val context: Context) : SensorEventListener { // Fallback to openGl GodotGLRenderView(host, this, xrMode, useDebugOpengl) } + + renderView?.inputHandler?.enableLongPress(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click"))) + renderView?.inputHandler?.enablePanningAndScalingGestures(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures"))) + if (host == primaryHost) { renderView!!.startRenderer() } @@ -616,7 +616,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 +627,7 @@ class Godot(private val context: Context) : SensorEventListener { } val dialog = builder.create() dialog.show() - }) + } } /** @@ -685,9 +685,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 +835,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 +863,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 @@ -928,6 +926,19 @@ class Godot(private val context: Context) : SensorEventListener { } /** + * Return true if the given feature is supported. + */ + @Keep + private fun hasFeature(feature: String): Boolean { + for (plugin in pluginRegistry.allPlugins) { + if (plugin.supportsFeature(feature)) { + return true + } + } + return false + } + + /** * Get the list of gdextension modules to register. */ @Keep diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt index 4636f753af..a60f6e997e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt @@ -30,12 +30,15 @@ package org.godotengine.godot +import android.Manifest import android.app.Activity import android.content.Intent +import android.content.pm.PackageManager import android.os.Bundle import android.util.Log import androidx.annotation.CallSuper import androidx.fragment.app.FragmentActivity +import org.godotengine.godot.utils.PermissionsUtil import org.godotengine.godot.utils.ProcessPhoenix /** @@ -62,6 +65,10 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { private set override fun onCreate(savedInstanceState: Bundle?) { + // We exclude certain permissions from the set we request at startup, as they'll be + // requested on demand based on use-cases. + PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO)) + super.onCreate(savedInstanceState) setContentView(R.layout.godot_app_layout) @@ -148,6 +155,14 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) godotFragment?.onRequestPermissionsResult(requestCode, permissions, grantResults) + + if (requestCode == PermissionsUtil.REQUEST_ALL_PERMISSION_REQ_CODE) { + Log.d(TAG, "Received permissions request result..") + for (i in permissions.indices) { + val permissionGranted = grantResults[i] == PackageManager.PERMISSION_GRANTED + Log.d(TAG, "Permission ${permissions[i]} ${if (permissionGranted) { "granted"} else { "denied" }}") + } + } } override fun onBackPressed() { @@ -158,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/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java index 7f3a3ac7a3..c0912ca4dc 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java @@ -316,6 +316,15 @@ public abstract class GodotPlugin { } /** + * Returns whether the plugin supports the given feature tag. + * + * @see <a href="https://docs.godotengine.org/en/stable/tutorials/export/feature_tags.html">Feature tags</a> + */ + public boolean supportsFeature(String featureTag) { + return false; + } + + /** * Runs the specified action on the UI thread. If the current thread is the UI * thread, then the action is executed immediately. If the current thread is * not the UI thread, the action is posted to the event queue of the UI thread. 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/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java index 8353fc8dc6..9a82204467 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java @@ -160,6 +160,7 @@ public final class PermissionsUtil { try { if (manifestPermission.equals(Manifest.permission.MANAGE_EXTERNAL_STORAGE)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { + Log.d(TAG, "Requesting permission " + manifestPermission); try { Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); intent.setData(Uri.parse(String.format("package:%s", activity.getPackageName()))); @@ -173,6 +174,7 @@ public final class PermissionsUtil { PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission); int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel; if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Requesting permission " + manifestPermission); requestedPermissions.add(manifestPermission); } } diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index edc934e927..d6455cbf1c 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -50,14 +50,14 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } int pc = E.param_types.size(); - if (pc > p_argcount) { + if (p_argcount < pc) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = pc; + r_error.expected = pc; continue; } - if (pc < p_argcount) { + if (p_argcount > pc) { r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = pc; + r_error.expected = pc; continue; } uint32_t *ptypes = E.param_types.ptrw(); diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 1703179b8e..cb6ebf14a8 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -82,6 +82,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _end_benchmark_measure = p_env->GetMethodID(godot_class, "nativeEndBenchmarkMeasure", "(Ljava/lang/String;)V"); _dump_benchmark = p_env->GetMethodID(godot_class, "nativeDumpBenchmark", "(Ljava/lang/String;)V"); _get_gdextension_list_config_file = p_env->GetMethodID(godot_class, "getGDExtensionConfigFiles", "()[Ljava/lang/String;"); + _has_feature = p_env->GetMethodID(godot_class, "hasFeature", "(Ljava/lang/String;)Z"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -373,3 +374,15 @@ void GodotJavaWrapper::dump_benchmark(const String &benchmark_file) { env->CallVoidMethod(godot_instance, _dump_benchmark, j_benchmark_file); } } + +bool GodotJavaWrapper::has_feature(const String &p_feature) const { + if (_has_feature) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, false); + + jstring j_feature = env->NewStringUTF(p_feature.utf8().get_data()); + return env->CallBooleanMethod(godot_instance, _has_feature, j_feature); + } else { + return false; + } +} diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index f427a2937c..7c6327c9e1 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -73,6 +73,7 @@ private: jmethodID _begin_benchmark_measure = nullptr; jmethodID _end_benchmark_measure = nullptr; jmethodID _dump_benchmark = nullptr; + jmethodID _has_feature = nullptr; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance); @@ -110,6 +111,9 @@ public: // Return the list of gdextensions config file. Vector<String> get_gdextension_list_config_file() const; + + // Return true if the given feature is supported. + bool has_feature(const String &p_feature) const; }; #endif // JAVA_GODOT_WRAPPER_H diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 8f80516a9f..92dc5f909f 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -60,10 +60,10 @@ String _remove_symlink(const String &dir) { // Change directory to the external data directory. chdir(dir.utf8().get_data()); // Get the actual directory without the potential symlink. - char dir_name_wihout_symlink[2048]; - getcwd(dir_name_wihout_symlink, 2048); + char dir_name_without_symlink[2048]; + getcwd(dir_name_without_symlink, 2048); // Convert back to a String. - String dir_without_symlink(dir_name_wihout_symlink); + String dir_without_symlink(dir_name_without_symlink); // Restore original current directory. chdir(current_dir_name); return dir_without_symlink; @@ -749,6 +749,11 @@ bool OS_Android::_check_internal_feature_support(const String &p_feature) { return true; } #endif + + if (godot_java->has_feature(p_feature)) { + return true; + } + return false; } diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm index 489894a135..2561c1c095 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -195,10 +195,7 @@ void DisplayServerIOS::send_window_event(DisplayServer::WindowEvent p_event) con void DisplayServerIOS::_window_callback(const Callable &p_callable, const Variant &p_arg) const { if (!p_callable.is_null()) { - const Variant *argp = &p_arg; - Variant ret; - Callable::CallError ce; - p_callable.callp((const Variant **)&argp, 1, ret, ce); + p_callable.call(p_arg); } } @@ -320,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..76546c164a 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -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/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp index 3a245460b4..fd4bcf92be 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -49,6 +49,10 @@ #include <stdlib.h> static void handle_crash(int sig) { + signal(SIGSEGV, SIG_DFL); + signal(SIGFPE, SIG_DFL); + signal(SIGILL, SIG_DFL); + if (OS::get_singleton() == nullptr) { abort(); } @@ -156,9 +160,9 @@ void CrashHandler::disable() { } #ifdef CRASH_HANDLER_ENABLED - signal(SIGSEGV, nullptr); - signal(SIGFPE, nullptr); - signal(SIGILL, nullptr); + signal(SIGSEGV, SIG_DFL); + signal(SIGFPE, SIG_DFL); + signal(SIGILL, SIG_DFL); #endif disabled = true; diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index 91c14e0e91..cf4354139a 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -142,36 +142,40 @@ void FreeDesktopPortalDesktop::append_dbus_string(DBusMessageIter *p_iter, const } } -void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filters) { +void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts) { DBusMessageIter dict_iter; DBusMessageIter var_iter; DBusMessageIter arr_iter; const char *filters_key = "filters"; + ERR_FAIL_COND(p_filter_names.size() != p_filter_exts.size()); + dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter); dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &filters_key); dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(sa(us))", &var_iter); dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(sa(us))", &arr_iter); - for (int i = 0; i < p_filters.size(); i++) { - Vector<String> tokens = p_filters[i].split(";"); - if (tokens.size() == 2) { - DBusMessageIter struct_iter; - DBusMessageIter array_iter; - DBusMessageIter array_struct_iter; - dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter); - append_dbus_string(&struct_iter, tokens[0]); - - dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter); + for (int i = 0; i < p_filter_names.size(); i++) { + DBusMessageIter struct_iter; + DBusMessageIter array_iter; + DBusMessageIter array_struct_iter; + dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter); + append_dbus_string(&struct_iter, p_filter_names[i]); + + dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter); + String flt = p_filter_exts[i]; + int filter_slice_count = flt.get_slice_count(","); + for (int j = 0; j < filter_slice_count; j++) { dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter); + String str = (flt.get_slice(",", j).strip_edges()); { const unsigned nil = 0; dbus_message_iter_append_basic(&array_struct_iter, DBUS_TYPE_UINT32, &nil); } - append_dbus_string(&array_struct_iter, tokens[1]); + append_dbus_string(&array_struct_iter, str); dbus_message_iter_close_container(&array_iter, &array_struct_iter); - dbus_message_iter_close_container(&struct_iter, &array_iter); - dbus_message_iter_close_container(&arr_iter, &struct_iter); } + dbus_message_iter_close_container(&struct_iter, &array_iter); + dbus_message_iter_close_container(&arr_iter, &struct_iter); } dbus_message_iter_close_container(&var_iter, &arr_iter); dbus_message_iter_close_container(&dict_iter, &var_iter); @@ -219,7 +223,7 @@ void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, co dbus_message_iter_close_container(p_iter, &dict_iter); } -bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Vector<String> &r_urls) { +bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index) { ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false); dbus_uint32_t resp_code; @@ -243,7 +247,22 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it DBusMessageIter var_iter; dbus_message_iter_recurse(&iter, &var_iter); - if (strcmp(key, "uris") == 0) { + if (strcmp(key, "current_filter") == 0) { // (sa(us)) + if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_STRUCT) { + DBusMessageIter struct_iter; + dbus_message_iter_recurse(&var_iter, &struct_iter); + while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRING) { + const char *value; + dbus_message_iter_get_basic(&struct_iter, &value); + String name = String::utf8(value); + + r_index = p_names.find(name); + if (!dbus_message_iter_next(&struct_iter)) { + break; + } + } + } + } else if (strcmp(key, "uris") == 0) { // as if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) { DBusMessageIter uri_iter; dbus_message_iter_recurse(&var_iter, &uri_iter); @@ -266,17 +285,43 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it return true; } -Error FreeDesktopPortalDesktop::file_dialog_show(const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { +Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { if (unsupported) { return FAILED; } + ERR_FAIL_INDEX_V(int(p_mode), DisplayServer::FILE_DIALOG_MODE_SAVE_MAX, FAILED); + + Vector<String> filter_names; + Vector<String> filter_exts; + for (int i = 0; i < p_filters.size(); i++) { + Vector<String> tokens = p_filters[i].split(";"); + if (tokens.size() >= 1) { + String flt = tokens[0].strip_edges(); + if (!flt.is_empty()) { + if (tokens.size() == 2) { + filter_exts.push_back(flt); + filter_names.push_back(tokens[1]); + } else { + filter_exts.push_back(flt); + filter_names.push_back(flt); + } + } + } + } + if (filter_names.is_empty()) { + filter_exts.push_back("*.*"); + filter_names.push_back(RTR("All Files")); + } + DBusError err; dbus_error_init(&err); // Open connection and add signal handler. FileDialogData fd; fd.callback = p_callback; + fd.prev_focus = p_window_id; + fd.filter_names = filter_names; CryptoCore::RandomGenerator rng; ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator."); @@ -307,16 +352,10 @@ Error FreeDesktopPortalDesktop::file_dialog_show(const String &p_xid, const Stri // Generate FileChooser message. const char *method = nullptr; - switch (p_mode) { - case DisplayServer::FILE_DIALOG_MODE_SAVE_FILE: { - method = "SaveFile"; - } break; - case DisplayServer::FILE_DIALOG_MODE_OPEN_ANY: - case DisplayServer::FILE_DIALOG_MODE_OPEN_FILE: - case DisplayServer::FILE_DIALOG_MODE_OPEN_DIR: - case DisplayServer::FILE_DIALOG_MODE_OPEN_FILES: { - method = "OpenFile"; - } break; + if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) { + method = "SaveFile"; + } else { + method = "OpenFile"; } DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_FILE_CHOOSER, method); @@ -333,7 +372,7 @@ Error FreeDesktopPortalDesktop::file_dialog_show(const String &p_xid, const Stri append_dbus_dict_string(&arr_iter, "handle_token", token); append_dbus_dict_bool(&arr_iter, "multiple", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES); append_dbus_dict_bool(&arr_iter, "directory", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR); - append_dbus_dict_filters(&arr_iter, p_filters); + append_dbus_dict_filters(&arr_iter, filter_names, filter_exts); append_dbus_dict_string(&arr_iter, "current_folder", p_current_directory, true); if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) { append_dbus_dict_string(&arr_iter, "current_name", p_filename); @@ -408,13 +447,18 @@ void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) { if (dbus_message_iter_init(msg, &iter)) { bool cancel = false; Vector<String> uris; - file_chooser_parse_response(&iter, cancel, uris); + int index = 0; + 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_args[2] = { &v_status, &v_files }; - fd.callback.call_deferredp((const Variant **)&v_args, 2); + Variant v_index = index; + Variant *v_args[3] = { &v_status, &v_files, &v_index }; + fd.callback.call_deferredp((const Variant **)&v_args, 3); + } + if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) { + callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus); } } dbus_message_unref(msg); diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h index a9b83b3844..503c382207 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.h +++ b/platform/linuxbsd/freedesktop_portal_desktop.h @@ -49,13 +49,15 @@ private: bool read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value); static void append_dbus_string(DBusMessageIter *p_iter, const String &p_string); - static void append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filters); + static void append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts); static void append_dbus_dict_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array = false); 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, bool &r_cancel, Vector<String> &r_urls); + static bool file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index); struct FileDialogData { + Vector<String> filter_names; DBusConnection *connection = nullptr; + DisplayServer::WindowID prev_focus = DisplayServer::INVALID_WINDOW_ID; Callable callback; String path; }; @@ -73,7 +75,7 @@ public: bool is_supported() { return !unsupported; } - Error file_dialog_show(const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback); + Error file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback); // Retrieve the system's preferred color scheme. // 0: No preference or unknown. diff --git a/platform/linuxbsd/platform_gl.h b/platform/linuxbsd/platform_gl.h index 1c19c4518a..5d4c2d8978 100644 --- a/platform/linuxbsd/platform_gl.h +++ b/platform/linuxbsd/platform_gl.h @@ -35,6 +35,10 @@ #define GL_API_ENABLED // Allow using desktop GL. #endif +#ifndef GLES_API_ENABLED +#define GLES_API_ENABLED // Allow using GLES. +#endif + #include "thirdparty/glad/glad/egl.h" #include "thirdparty/glad/glad/gl.h" diff --git a/platform/linuxbsd/x11/SCsub b/platform/linuxbsd/x11/SCsub index a4890391ce..bbfaaf10d1 100644 --- a/platform/linuxbsd/x11/SCsub +++ b/platform/linuxbsd/x11/SCsub @@ -25,7 +25,9 @@ if env["vulkan"]: if env["opengl3"]: env.Append(CPPDEFINES=["GLAD_GLX_NO_X11"]) - source_files.append(["gl_manager_x11.cpp", "detect_prime_x11.cpp", "#thirdparty/glad/glx.c"]) + source_files.append( + ["gl_manager_x11_egl.cpp", "gl_manager_x11.cpp", "detect_prime_x11.cpp", "#thirdparty/glad/glx.c"] + ) objects = [] diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 897a6438d2..fe81da76d3 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -364,14 +364,14 @@ bool DisplayServerX11::is_dark_mode() const { } Error DisplayServerX11::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { - WindowID window_id = _get_focused_window_or_popup(); + WindowID window_id = last_focused_window; if (!windows.has(window_id)) { window_id = MAIN_WINDOW_ID; } String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window); - return portal_desktop->file_dialog_show(xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback); + return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback); } #endif @@ -1495,6 +1495,9 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { if (gl_manager) { gl_manager->window_destroy(p_id); } + if (gl_manager_egl) { + gl_manager_egl->window_destroy(p_id); + } #endif if (wd.xic) { @@ -1534,6 +1537,9 @@ int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, Win if (gl_manager) { return (int64_t)gl_manager->get_glx_context(p_window); } + if (gl_manager_egl) { + return (int64_t)gl_manager_egl->get_context(p_window); + } return 0; } #endif @@ -1711,6 +1717,9 @@ void DisplayServerX11::gl_window_make_current(DisplayServer::WindowID p_window_i if (gl_manager) { gl_manager->window_make_current(p_window_id); } + if (gl_manager_egl) { + gl_manager_egl->window_make_current(p_window_id); + } #endif } @@ -2012,6 +2021,9 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { if (gl_manager) { gl_manager->window_resize(p_window, xwa.width, xwa.height); } + if (gl_manager_egl) { + gl_manager_egl->window_resize(p_window, xwa.width, xwa.height); + } #endif } @@ -2862,7 +2874,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); @@ -3721,17 +3733,13 @@ void DisplayServerX11::_window_changed(XEvent *event) { if (gl_manager) { gl_manager->window_resize(window_id, wd.size.width, wd.size.height); } + if (gl_manager_egl) { + gl_manager_egl->window_resize(window_id, wd.size.width, wd.size.height); + } #endif - if (!wd.rect_changed_callback.is_null()) { - Rect2i r = new_rect; - - Variant rect = r; - - Variant *rectp = ▭ - Variant ret; - Callable::CallError ce; - wd.rect_changed_callback.callp((const Variant **)&rectp, 1, ret, ce); + if (wd.rect_changed_callback.is_valid()) { + wd.rect_changed_callback.call(new_rect); } } @@ -3749,11 +3757,6 @@ void DisplayServerX11::_dispatch_input_events(const Ref<InputEvent> &p_event) { } void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) { - Variant ev = p_event; - Variant *evp = &ev; - Variant ret; - Callable::CallError ce; - { List<WindowID>::Element *E = popup_list.back(); if (E && Object::cast_to<InputEventKey>(*p_event)) { @@ -3761,7 +3764,7 @@ void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) { if (windows.has(E->get())) { Callable callable = windows[E->get()].input_event_callback; if (callable.is_valid()) { - callable.callp((const Variant **)&evp, 1, ret, ce); + callable.call(p_event); } } return; @@ -3774,7 +3777,7 @@ void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) { if (windows.has(event_from_window->get_window_id())) { Callable callable = windows[event_from_window->get_window_id()].input_event_callback; if (callable.is_valid()) { - callable.callp((const Variant **)&evp, 1, ret, ce); + callable.call(p_event); } } } else { @@ -3782,19 +3785,16 @@ void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) { for (KeyValue<WindowID, WindowData> &E : windows) { Callable callable = E.value.input_event_callback; if (callable.is_valid()) { - callable.callp((const Variant **)&evp, 1, ret, ce); + callable.call(p_event); } } } } void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_event) { - if (!wd.event_callback.is_null()) { + if (wd.event_callback.is_valid()) { Variant event = int(p_event); - Variant *eventp = &event; - Variant ret; - Callable::CallError ce; - wd.event_callback.callp((const Variant **)&eventp, 1, ret, ce); + wd.event_callback.call(event); } } @@ -4085,9 +4085,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 +4201,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 +4235,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; @@ -4739,11 +4743,7 @@ void DisplayServerX11::process_events() { } if (!windows[window_id].drop_files_callback.is_null()) { - Variant v = files; - Variant *vp = &v; - Variant ret; - Callable::CallError ce; - windows[window_id].drop_files_callback.callp((const Variant **)&vp, 1, ret, ce); + windows[window_id].drop_files_callback.call(files); } //Reply that all is well. @@ -4855,6 +4855,9 @@ void DisplayServerX11::release_rendering_thread() { if (gl_manager) { gl_manager->release_current(); } + if (gl_manager_egl) { + gl_manager_egl->release_current(); + } #endif } @@ -4863,6 +4866,9 @@ void DisplayServerX11::make_rendering_thread() { if (gl_manager) { gl_manager->make_current(); } + if (gl_manager_egl) { + gl_manager_egl->make_current(); + } #endif } @@ -4871,6 +4877,9 @@ void DisplayServerX11::swap_buffers() { if (gl_manager) { gl_manager->swap_buffers(); } + if (gl_manager_egl) { + gl_manager_egl->swap_buffers(); + } #endif } @@ -5024,6 +5033,9 @@ void DisplayServerX11::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mo if (gl_manager) { gl_manager->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED); } + if (gl_manager_egl) { + gl_manager_egl->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED); + } #endif } @@ -5038,6 +5050,9 @@ DisplayServer::VSyncMode DisplayServerX11::window_get_vsync_mode(WindowID p_wind if (gl_manager) { return gl_manager->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED; } + if (gl_manager_egl) { + return gl_manager_egl->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED; + } #endif return DisplayServer::VSYNC_ENABLED; } @@ -5050,6 +5065,7 @@ Vector<String> DisplayServerX11::get_rendering_drivers_func() { #endif #ifdef GLES3_ENABLED drivers.push_back("opengl3"); + drivers.push_back("opengl3_es"); #endif return drivers; @@ -5092,6 +5108,21 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't acquire visual info from display."); vi_selected = true; } + if (gl_manager_egl) { + XVisualInfo visual_info_template; + int visual_id = gl_manager_egl->display_get_native_visual_id(x11_display); + ERR_FAIL_COND_V_MSG(visual_id < 0, INVALID_WINDOW_ID, "Unable to get a visual id."); + + visual_info_template.visualid = (VisualID)visual_id; + + int number_of_visuals = 0; + XVisualInfo *vi_list = XGetVisualInfo(x11_display, VisualIDMask, &visual_info_template, &number_of_visuals); + ERR_FAIL_COND_V(number_of_visuals <= 0, INVALID_WINDOW_ID); + + visualInfo = vi_list[0]; + + XFree(vi_list); + } #endif if (!vi_selected) { @@ -5364,8 +5395,12 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V if (gl_manager) { Error err = gl_manager->window_create(id, wd.x11_window, x11_display, win_rect.size.width, win_rect.size.height); ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL window"); - window_set_vsync_mode(p_vsync_mode, id); } + if (gl_manager_egl) { + Error err = gl_manager_egl->window_create(id, x11_display, &wd.x11_window, win_rect.size.width, win_rect.size.height); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Failed to create an OpenGLES window."); + } + window_set_vsync_mode(p_vsync_mode, id); #endif //set_class_hint(x11_display, wd.x11_window); @@ -5766,7 +5801,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode #endif // Initialize context and rendering device. #if defined(GLES3_ENABLED) - if (rendering_driver == "opengl3") { + if (rendering_driver == "opengl3" || rendering_driver == "opengl3_es") { if (getenv("DRI_PRIME") == nullptr) { int use_prime = -1; @@ -5807,7 +5842,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode setenv("DRI_PRIME", "1", 1); } } - + } + if (rendering_driver == "opengl3") { GLManager_X11::ContextType opengl_api_type = GLManager_X11::GLES_3_0_COMPATIBLE; gl_manager = memnew(GLManager_X11(p_resolution, opengl_api_type)); @@ -5820,14 +5856,20 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } driver_found = true; - if (true) { - RasterizerGLES3::make_current(true); - } else { - memdelete(gl_manager); - gl_manager = nullptr; + RasterizerGLES3::make_current(true); + } + if (rendering_driver == "opengl3_es") { + gl_manager_egl = memnew(GLManagerEGL_X11); + + if (gl_manager_egl->initialize() != OK) { + memdelete(gl_manager_egl); + gl_manager_egl = nullptr; r_error = ERR_UNAVAILABLE; return; } + driver_found = true; + + RasterizerGLES3::make_current(false); } #endif if (!driver_found) { @@ -6044,6 +6086,9 @@ DisplayServerX11::~DisplayServerX11() { if (gl_manager) { gl_manager->window_destroy(E.key); } + if (gl_manager_egl) { + gl_manager_egl->window_destroy(E.key); + } #endif WindowData &wd = E.value; @@ -6094,6 +6139,10 @@ DisplayServerX11::~DisplayServerX11() { memdelete(gl_manager); gl_manager = nullptr; } + if (gl_manager_egl) { + memdelete(gl_manager_egl); + gl_manager_egl = nullptr; + } #endif if (xrandr_handle) { diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index 71beddce76..9706a4aa11 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -54,6 +54,7 @@ #if defined(GLES3_ENABLED) #include "x11/gl_manager_x11.h" +#include "x11/gl_manager_x11_egl.h" #endif #if defined(VULKAN_ENABLED) @@ -138,6 +139,7 @@ class DisplayServerX11 : public DisplayServer { #if defined(GLES3_ENABLED) GLManager_X11 *gl_manager = nullptr; + GLManagerEGL_X11 *gl_manager_egl = nullptr; #endif #if defined(VULKAN_ENABLED) VulkanContextX11 *context_vulkan = nullptr; diff --git a/platform/linuxbsd/x11/gl_manager_x11_egl.cpp b/platform/linuxbsd/x11/gl_manager_x11_egl.cpp new file mode 100644 index 0000000000..544684ebf1 --- /dev/null +++ b/platform/linuxbsd/x11/gl_manager_x11_egl.cpp @@ -0,0 +1,63 @@ +/**************************************************************************/ +/* gl_manager_x11_egl.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "gl_manager_x11_egl.h" + +#if defined(X11_ENABLED) && defined(GLES3_ENABLED) + +#include <stdio.h> +#include <stdlib.h> + +const char *GLManagerEGL_X11::_get_platform_extension_name() const { + return "EGL_KHR_platform_x11"; +} + +EGLenum GLManagerEGL_X11::_get_platform_extension_enum() const { + return EGL_PLATFORM_X11_KHR; +} + +Vector<EGLAttrib> GLManagerEGL_X11::_get_platform_display_attributes() const { + return Vector<EGLAttrib>(); +} + +EGLenum GLManagerEGL_X11::_get_platform_api_enum() const { + return EGL_OPENGL_ES_API; +} + +Vector<EGLint> GLManagerEGL_X11::_get_platform_context_attribs() const { + Vector<EGLint> ret; + ret.push_back(EGL_CONTEXT_CLIENT_VERSION); + ret.push_back(3); + ret.push_back(EGL_NONE); + + return ret; +} + +#endif // WINDOWS_ENABLED && GLES3_ENABLED diff --git a/platform/linuxbsd/x11/gl_manager_x11_egl.h b/platform/linuxbsd/x11/gl_manager_x11_egl.h new file mode 100644 index 0000000000..b9c96619c4 --- /dev/null +++ b/platform/linuxbsd/x11/gl_manager_x11_egl.h @@ -0,0 +1,61 @@ +/**************************************************************************/ +/* gl_manager_x11_egl.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GL_MANAGER_X11_EGL_H +#define GL_MANAGER_X11_EGL_H + +#if defined(X11_ENABLED) && defined(GLES3_ENABLED) + +#include "core/error/error_list.h" +#include "core/os/os.h" +#include "core/templates/local_vector.h" +#include "drivers/egl/egl_manager.h" +#include "servers/display_server.h" + +#include <X11/Xlib.h> + +class GLManagerEGL_X11 : public EGLManager { +private: + virtual const char *_get_platform_extension_name() const override; + virtual EGLenum _get_platform_extension_enum() const override; + virtual EGLenum _get_platform_api_enum() const override; + virtual Vector<EGLAttrib> _get_platform_display_attributes() const override; + virtual Vector<EGLint> _get_platform_context_attribs() const override; + +public: + void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) {} + + GLManagerEGL_X11(){}; + ~GLManagerEGL_X11(){}; +}; + +#endif // X11_ENABLED && GLES3_ENABLED + +#endif // GL_MANAGER_X11_EGL_H diff --git a/platform/macos/crash_handler_macos.mm b/platform/macos/crash_handler_macos.mm index 7f9a88121e..7c0cab0210 100644 --- a/platform/macos/crash_handler_macos.mm +++ b/platform/macos/crash_handler_macos.mm @@ -72,6 +72,10 @@ static uint64_t load_address() { } static void handle_crash(int sig) { + signal(SIGSEGV, SIG_DFL); + signal(SIGFPE, SIG_DFL); + signal(SIGILL, SIG_DFL); + if (OS::get_singleton() == nullptr) { abort(); } @@ -186,9 +190,9 @@ void CrashHandler::disable() { } #ifdef CRASH_HANDLER_ENABLED - signal(SIGSEGV, nullptr); - signal(SIGFPE, nullptr); - signal(SIGILL, nullptr); + signal(SIGSEGV, SIG_DFL); + signal(SIGFPE, SIG_DFL); + signal(SIGILL, SIG_DFL); #endif disabled = true; 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 c03b4765f8..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; }; @@ -139,7 +140,14 @@ private: NSMenu *apple_menu = nullptr; NSMenu *dock_menu = nullptr; - HashMap<String, NSMenu *> submenu; + struct MenuData { + Callable open; + Callable close; + NSMenu *menu = nullptr; + bool is_open = false; + }; + HashMap<String, MenuData> submenu; + HashMap<NSMenu *, String> submenu_inv; struct WarpEvent { NSTimeInterval timestamp; @@ -197,6 +205,7 @@ private: const NSMenu *_get_menu_root(const String &p_menu_root) const; NSMenu *_get_menu_root(const String &p_menu_root); + bool _is_menu_opened(NSMenu *p_menu) const; WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect); void _update_window_style(WindowData p_wd); @@ -223,6 +232,8 @@ private: public: NSMenu *get_dock_menu() const; void menu_callback(id p_sender); + void menu_open(NSMenu *p_menu); + void menu_close(NSMenu *p_menu); bool has_window(WindowID p_window) const; WindowData &get_window(WindowID p_window); @@ -245,6 +256,7 @@ public: WindowID _get_focused_window_or_popup() const; void mouse_enter_window(WindowID p_window); void mouse_exit_window(WindowID p_window); + void update_presentation_mode(); void window_destroy(WindowID p_window); void window_resize(WindowID p_window, int p_width, int p_height); @@ -253,6 +265,8 @@ public: virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; + virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable()) override; + virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override; virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; @@ -276,6 +290,7 @@ public: virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const override; virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const override; virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const override; + virtual bool global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const override; virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const override; virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override; virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override; @@ -287,11 +302,13 @@ public: virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override; virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override; virtual void global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) override; + virtual void global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) override; virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override; virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override; virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override; virtual void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) override; virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) override; + virtual void global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) override; virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) override; virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override; virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override; @@ -368,6 +385,7 @@ public: virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_title_size(const String &p_title, WindowID p_window) const override; virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID) override; virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 56cb4938ec..0d4a5e784d 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -75,7 +75,7 @@ const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) cons } else { // Submenu. if (submenu.has(p_menu_root)) { - menu = submenu[p_menu_root]; + menu = submenu[p_menu_root].menu; } } if (menu == apple_menu) { @@ -99,9 +99,10 @@ NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) { NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]]; [n_menu setAutoenablesItems:NO]; [n_menu setDelegate:menu_delegate]; - submenu[p_menu_root] = n_menu; + submenu[p_menu_root].menu = n_menu; + submenu_inv[n_menu] = p_menu_root; } - menu = submenu[p_menu_root]; + menu = submenu[p_menu_root].menu; } if (menu == apple_menu) { // Do not allow to change Apple menu. @@ -117,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); @@ -143,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]; @@ -190,7 +191,8 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL context."); } if (gl_manager_angle) { - Error err = gl_manager_angle->window_create(window_id_counter, nullptr, (__bridge void *)[wd.window_view layer], p_rect.size.width, p_rect.size.height); + CALayer *layer = [(NSView *)wd.window_view layer]; + Error err = gl_manager_angle->window_create(window_id_counter, nullptr, (__bridge void *)layer, p_rect.size.width, p_rect.size.height); ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL context."); } window_set_vsync_mode(p_vsync_mode, window_id_counter); @@ -401,11 +403,6 @@ void DisplayServerMacOS::_dispatch_input_event(const Ref<InputEvent> &p_event) { if (!in_dispatch_input_event) { in_dispatch_input_event = true; - Variant ev = p_event; - Variant *evp = &ev; - Variant ret; - Callable::CallError ce; - { List<WindowID>::Element *E = popup_list.back(); if (E && Object::cast_to<InputEventKey>(*p_event)) { @@ -413,7 +410,7 @@ void DisplayServerMacOS::_dispatch_input_event(const Ref<InputEvent> &p_event) { if (windows.has(E->get())) { Callable callable = windows[E->get()].input_event_callback; if (callable.is_valid()) { - callable.callp((const Variant **)&evp, 1, ret, ce); + callable.call(p_event); } } in_dispatch_input_event = false; @@ -427,7 +424,7 @@ void DisplayServerMacOS::_dispatch_input_event(const Ref<InputEvent> &p_event) { if (windows.has(event_from_window->get_window_id())) { Callable callable = windows[event_from_window->get_window_id()].input_event_callback; if (callable.is_valid()) { - callable.callp((const Variant **)&evp, 1, ret, ce); + callable.call(p_event); } } } else { @@ -435,7 +432,7 @@ void DisplayServerMacOS::_dispatch_input_event(const Ref<InputEvent> &p_event) { for (KeyValue<WindowID, WindowData> &E : windows) { Callable callable = E.value.input_event_callback; if (callable.is_valid()) { - callable.callp((const Variant **)&evp, 1, ret, ce); + callable.call(p_event); } } } @@ -570,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(); @@ -586,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; } @@ -609,6 +606,26 @@ NSMenu *DisplayServerMacOS::get_dock_menu() const { return dock_menu; } +void DisplayServerMacOS::menu_open(NSMenu *p_menu) { + if (submenu_inv.has(p_menu)) { + MenuData &md = submenu[submenu_inv[p_menu]]; + md.is_open = true; + if (md.open.is_valid()) { + md.open.call(); + } + } +} + +void DisplayServerMacOS::menu_close(NSMenu *p_menu) { + if (submenu_inv.has(p_menu)) { + MenuData &md = submenu[submenu_inv[p_menu]]; + md.is_open = false; + if (md.close.is_valid()) { + md.close.call(); + } + } +} + void DisplayServerMacOS::menu_callback(id p_sender) { if (![p_sender representedObject]) { return; @@ -632,7 +649,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; @@ -674,12 +691,9 @@ void DisplayServerMacOS::send_event(NSEvent *p_event) { void DisplayServerMacOS::send_window_event(const WindowData &wd, WindowEvent p_event) { _THREAD_SAFE_METHOD_ - if (!wd.event_callback.is_null()) { + if (wd.event_callback.is_valid()) { Variant event = int(p_event); - Variant *eventp = &event; - Variant ret; - Callable::CallError ce; - wd.event_callback.callp((const Variant **)&eventp, 1, ret, ce); + wd.event_callback.call(event); } } @@ -749,6 +763,7 @@ void DisplayServerMacOS::window_destroy(WindowID p_window) { } #endif windows.erase(p_window); + update_presentation_mode(); } void DisplayServerMacOS::window_resize(WindowID p_window, int p_width, int p_height) { @@ -815,6 +830,24 @@ bool DisplayServerMacOS::_has_help_menu() const { } } +bool DisplayServerMacOS::_is_menu_opened(NSMenu *p_menu) const { + if (submenu_inv.has(p_menu)) { + const MenuData &md = submenu[submenu_inv[p_menu]]; + if (md.is_open) { + return true; + } + } + for (NSInteger i = (p_menu == [NSApp mainMenu]) ? 1 : 0; i < [p_menu numberOfItems]; i++) { + const NSMenuItem *menu_item = [p_menu itemAtIndex:i]; + if ([menu_item submenu]) { + if (_is_menu_opened([menu_item submenu])) { + return true; + } + } + } + return false; +} + NSMenuItem *DisplayServerMacOS::_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out) { NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { @@ -998,6 +1031,23 @@ int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_roo return out; } +void DisplayServerMacOS::global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback, const Callable &p_close_callback) { + _THREAD_SAFE_METHOD_ + + if (p_menu_root != "" && p_menu_root.to_lower() != "_main" && p_menu_root.to_lower() != "_dock") { + // Submenu. + if (!submenu.has(p_menu_root)) { + NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]]; + [n_menu setAutoenablesItems:NO]; + [n_menu setDelegate:menu_delegate]; + submenu[p_menu_root].menu = n_menu; + submenu_inv[n_menu] = p_menu_root; + } + submenu[p_menu_root].open = p_open_callback; + submenu[p_menu_root].close = p_close_callback; + } +} + int DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) { _THREAD_SAFE_METHOD_ @@ -1251,13 +1301,9 @@ String DisplayServerMacOS::global_menu_get_item_submenu(const String &p_menu_roo ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - const NSMenu *sub_menu = [menu_item submenu]; - if (sub_menu) { - for (const KeyValue<String, NSMenu *> &E : submenu) { - if (E.value == sub_menu) { - return E.key; - } - } + NSMenu *sub_menu = [menu_item submenu]; + if (sub_menu && submenu_inv.has(sub_menu)) { + return submenu_inv[sub_menu]; } } } @@ -1318,6 +1364,24 @@ bool DisplayServerMacOS::global_menu_is_item_disabled(const String &p_menu_root, return false; } +bool DisplayServerMacOS::global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + ERR_FAIL_COND_V(p_idx < 0, false); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false); + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + return [menu_item isHidden]; + } + } + return false; +} + String DisplayServerMacOS::global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const { _THREAD_SAFE_METHOD_ @@ -1497,6 +1561,25 @@ void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root } } +void DisplayServerMacOS::global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + ERR_FAIL_NULL(obj); + obj->hover_callback = p_callback; + } + } +} + void DisplayServerMacOS::global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) { _THREAD_SAFE_METHOD_ @@ -1556,6 +1639,23 @@ void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root, _THREAD_SAFE_METHOD_ NSMenu *menu = _get_menu_root(p_menu_root); + if (menu && p_submenu.is_empty()) { + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) { + ERR_PRINT("Can't remove open menu!"); + return; + } + [menu setSubmenu:nil forItem:menu_item]; + } + return; + } + NSMenu *sub_menu = _get_menu_root(p_submenu); if (menu && sub_menu) { if (sub_menu == menu) { @@ -1590,9 +1690,13 @@ void DisplayServerMacOS::global_menu_set_item_accelerator(const String &p_menu_r ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)]; - String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK); - [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + if (p_keycode == Key::NONE) { + [menu_item setKeyEquivalent:@""]; + } else { + [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)]; + String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK); + [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } } } } @@ -1614,6 +1718,23 @@ void DisplayServerMacOS::global_menu_set_item_disabled(const String &p_menu_root } } +void DisplayServerMacOS::global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + [menu_item setHidden:p_hidden]; + } + } +} + void DisplayServerMacOS::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) { _THREAD_SAFE_METHOD_ @@ -1741,6 +1862,11 @@ void DisplayServerMacOS::global_menu_remove_item(const String &p_menu_root, int p_idx++; } ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) { + ERR_PRINT("Can't remove open menu!"); + return; + } [menu removeItemAtIndex:p_idx]; } } @@ -1750,6 +1876,10 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) { NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + if (_is_menu_opened(menu)) { + ERR_PRINT("Can't remove open menu!"); + return; + } [menu removeAllItems]; // Restore Apple menu. if (menu == [NSApp mainMenu]) { @@ -1757,6 +1887,7 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) { [menu setSubmenu:apple_menu forItem:menu_item]; } if (submenu.has(p_menu_root)) { + submenu_inv.erase(submenu[p_menu_root].menu); submenu.erase(p_menu_root); } } @@ -1860,181 +1991,257 @@ Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vect } if (!p_callback.is_null()) { - Variant button = button_pressed; - Variant *buttonp = &button; - Variant fun_ret; - Callable::CallError ce; - p_callback.callp((const Variant **)&buttonp, 1, fun_ret, ce); + p_callback.call(button_pressed); } return OK; } -Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { - _THREAD_SAFE_METHOD_ +@interface FileDialogDropdown : NSObject { + NSSavePanel *dialog; + NSMutableArray *allowed_types; + int cur_index; +} + +- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types; +- (void)popupAction:(id)sender; +- (int)getIndex; + +@end + +@implementation FileDialogDropdown + +- (int)getIndex { + return cur_index; +} + +- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types { + if ((self = [super init])) { + dialog = p_dialog; + allowed_types = p_allowed_types; + cur_index = 0; + } + return self; +} + +- (void)popupAction:(id)sender { + NSUInteger index = [sender indexOfSelectedItem]; + if (index < [allowed_types count]) { + [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]]; + cur_index = index; + } else { + [dialog setAllowedFileTypes:@[]]; + cur_index = -1; + } +} + +@end + +FileDialogDropdown *_make_accessory_view(NSSavePanel *p_panel, const Vector<String> &p_filters) { + NSView *group = [[NSView alloc] initWithFrame:NSZeroRect]; + group.translatesAutoresizingMaskIntoConstraints = NO; + + NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]]; + label.translatesAutoresizingMaskIntoConstraints = NO; + if (@available(macOS 10.14, *)) { + label.textColor = NSColor.secondaryLabelColor; + } + if (@available(macOS 11.10, *)) { + label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; + } + [group addSubview:label]; + + NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]; + popup.translatesAutoresizingMaskIntoConstraints = NO; - NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()]; NSMutableArray *allowed_types = [[NSMutableArray alloc] init]; bool allow_other = false; for (int i = 0; i < p_filters.size(); i++) { Vector<String> tokens = p_filters[i].split(";"); - if (tokens.size() > 0) { - if (tokens[0].strip_edges() == "*.*") { - allow_other = true; - } else { - [allowed_types addObject:[NSString stringWithUTF8String:tokens[0].replace("*.", "").strip_edges().utf8().get_data()]]; + if (tokens.size() >= 1) { + String flt = tokens[0].strip_edges(); + int filter_slice_count = flt.get_slice_count(","); + + NSMutableArray *type_filters = [[NSMutableArray alloc] init]; + for (int j = 0; j < filter_slice_count; j++) { + String str = (flt.get_slice(",", j).strip_edges()); + if (str.strip_edges() == "*.*" || str.strip_edges() == "*") { + allow_other = true; + } else if (!str.is_empty()) { + [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]]; + } + } + + if ([type_filters count] > 0) { + NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()]; + [allowed_types addObject:type_filters]; + [popup addItemWithTitle:name_str]; } } } + FileDialogDropdown *handler = [[FileDialogDropdown alloc] initWithDialog:p_panel fileTypes:allowed_types]; + popup.target = handler; + popup.action = @selector(popupAction:); - Callable callback = p_callback; // Make a copy for async completion handler. - switch (p_mode) { - case FILE_DIALOG_MODE_SAVE_FILE: { - NSSavePanel *panel = [NSSavePanel savePanel]; + [group addSubview:popup]; - [panel setDirectoryURL:[NSURL fileURLWithPath:url]]; - if ([allowed_types count]) { - [panel setAllowedFileTypes:allowed_types]; - } - [panel setAllowsOtherFileTypes:allow_other]; - [panel setExtensionHidden:YES]; - [panel setCanSelectHiddenExtension:YES]; - [panel setCanCreateDirectories:YES]; - [panel setShowsHiddenFiles:p_show_hidden]; - if (p_filename != "") { - NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()]; - [panel setNameFieldStringValue:fileurl]; - } + NSView *view = [[NSView alloc] initWithFrame:NSZeroRect]; + view.translatesAutoresizingMaskIntoConstraints = NO; + [view addSubview:group]; + + NSMutableArray *constraints = [NSMutableArray array]; + [constraints addObject:[popup.topAnchor constraintEqualToAnchor:group.topAnchor constant:10]]; + [constraints addObject:[label.leadingAnchor constraintEqualToAnchor:group.leadingAnchor constant:10]]; + [constraints addObject:[popup.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:10]]; + [constraints addObject:[popup.firstBaselineAnchor constraintEqualToAnchor:label.firstBaselineAnchor]]; + [constraints addObject:[group.trailingAnchor constraintEqualToAnchor:popup.trailingAnchor constant:10]]; + [constraints addObject:[group.bottomAnchor constraintEqualToAnchor:popup.bottomAnchor constant:10]]; + [constraints addObject:[group.topAnchor constraintEqualToAnchor:view.topAnchor]]; + [constraints addObject:[group.centerXAnchor constraintEqualToAnchor:view.centerXAnchor]]; + [constraints addObject:[view.bottomAnchor constraintEqualToAnchor:group.bottomAnchor]]; + [NSLayoutConstraint activateConstraints:constraints]; + + [p_panel setAllowsOtherFileTypes:allow_other]; + if ([allowed_types count] > 0) { + [p_panel setAccessoryView:view]; + [p_panel setAllowedFileTypes:[allowed_types objectAtIndex:0]]; + } + + return handler; +} + +Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { + _THREAD_SAFE_METHOD_ - [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow] - completionHandler:^(NSInteger ret) { - if (ret == NSModalResponseOK) { - // Save bookmark for folder. - if (OS::get_singleton()->is_sandboxed()) { - NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"]; + ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED); + + NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()]; + FileDialogDropdown *handler = nullptr; + + WindowID prev_focus = last_focused_window; + + Callable callback = p_callback; // Make a copy for async completion handler. + if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) { + NSSavePanel *panel = [NSSavePanel savePanel]; + + [panel setDirectoryURL:[NSURL fileURLWithPath:url]]; + handler = _make_accessory_view(panel, p_filters); + [panel setExtensionHidden:YES]; + [panel setCanSelectHiddenExtension:YES]; + [panel setCanCreateDirectories:YES]; + [panel setShowsHiddenFiles:p_show_hidden]; + if (p_filename != "") { + NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()]; + [panel setNameFieldStringValue:fileurl]; + } + + [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow] + completionHandler:^(NSInteger ret) { + if (ret == NSModalResponseOK) { + // Save bookmark for folder. + if (OS::get_singleton()->is_sandboxed()) { + NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"]; + bool skip = false; + for (id bookmark in bookmarks) { + NSError *error = nil; + BOOL isStale = NO; + NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error]; + if (!error && !isStale && ([[exurl path] compare:[[panel directoryURL] path]] == NSOrderedSame)) { + skip = true; + break; + } + } + if (!skip) { + NSError *error = nil; + NSData *bookmark = [[panel directoryURL] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error]; + if (!error) { + NSArray *new_bookmarks = [bookmarks arrayByAddingObject:bookmark]; + [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"]; + } + } + } + // Callback. + Vector<String> files; + String url; + url.parse_utf8([[[panel URL] path] UTF8String]); + files.push_back(url); + if (!callback.is_null()) { + callback.call(true, files, [handler getIndex]); + } + } else { + if (!callback.is_null()) { + callback.call(false, Vector<String>(), [handler getIndex]); + } + } + if (prev_focus != INVALID_WINDOW_ID) { + callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus); + } + }]; + } else { + NSOpenPanel *panel = [NSOpenPanel openPanel]; + + [panel setDirectoryURL:[NSURL fileURLWithPath:url]]; + handler = _make_accessory_view(panel, p_filters); + [panel setExtensionHidden:YES]; + [panel setCanSelectHiddenExtension:YES]; + [panel setCanCreateDirectories:YES]; + [panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)]; + [panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)]; + [panel setShowsHiddenFiles:p_show_hidden]; + if (p_filename != "") { + NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()]; + [panel setNameFieldStringValue:fileurl]; + } + [panel setAllowsMultipleSelection:(p_mode == FILE_DIALOG_MODE_OPEN_FILES)]; + + [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow] + completionHandler:^(NSInteger ret) { + if (ret == NSModalResponseOK) { + // Save bookmark for folder. + NSArray *urls = [(NSOpenPanel *)panel URLs]; + if (OS::get_singleton()->is_sandboxed()) { + NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"]; + NSMutableArray *new_bookmarks = [bookmarks mutableCopy]; + for (NSUInteger i = 0; i != [urls count]; ++i) { bool skip = false; for (id bookmark in bookmarks) { NSError *error = nil; BOOL isStale = NO; NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error]; - if (!error && !isStale && ([[exurl path] compare:[[panel directoryURL] path]] == NSOrderedSame)) { + if (!error && !isStale && ([[exurl path] compare:[[urls objectAtIndex:i] path]] == NSOrderedSame)) { skip = true; break; } } if (!skip) { NSError *error = nil; - NSData *bookmark = [[panel directoryURL] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error]; + NSData *bookmark = [[urls objectAtIndex:i] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error]; if (!error) { - NSArray *new_bookmarks = [bookmarks arrayByAddingObject:bookmark]; - [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"]; + [new_bookmarks addObject:bookmark]; } } } - // Callback. - Vector<String> files; + [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"]; + } + // Callback. + Vector<String> files; + for (NSUInteger i = 0; i != [urls count]; ++i) { String url; - url.parse_utf8([[[panel URL] path] UTF8String]); + url.parse_utf8([[[urls objectAtIndex:i] path] UTF8String]); files.push_back(url); - if (!callback.is_null()) { - Variant v_status = true; - Variant v_files = files; - Variant *v_args[2] = { &v_status, &v_files }; - Variant ret; - Callable::CallError ce; - callback.callp((const Variant **)&v_args, 2, ret, ce); - } - } else { - if (!callback.is_null()) { - Variant v_status = false; - Variant v_files = Vector<String>(); - Variant *v_args[2] = { &v_status, &v_files }; - Variant ret; - Callable::CallError ce; - callback.callp((const Variant **)&v_args, 2, ret, ce); - } } - }]; - } break; - case FILE_DIALOG_MODE_OPEN_ANY: - case FILE_DIALOG_MODE_OPEN_FILE: - case FILE_DIALOG_MODE_OPEN_FILES: - case FILE_DIALOG_MODE_OPEN_DIR: { - NSOpenPanel *panel = [NSOpenPanel openPanel]; - - [panel setDirectoryURL:[NSURL fileURLWithPath:url]]; - if ([allowed_types count]) { - [panel setAllowedFileTypes:allowed_types]; - } - [panel setAllowsOtherFileTypes:allow_other]; - [panel setExtensionHidden:YES]; - [panel setCanSelectHiddenExtension:YES]; - [panel setCanCreateDirectories:YES]; - [panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)]; - [panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)]; - [panel setShowsHiddenFiles:p_show_hidden]; - if (p_filename != "") { - NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()]; - [panel setNameFieldStringValue:fileurl]; - } - [panel setAllowsMultipleSelection:(p_mode == FILE_DIALOG_MODE_OPEN_FILES)]; - - [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow] - completionHandler:^(NSInteger ret) { - if (ret == NSModalResponseOK) { - // Save bookmark for folder. - NSArray *urls = [(NSOpenPanel *)panel URLs]; - if (OS::get_singleton()->is_sandboxed()) { - NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"]; - NSMutableArray *new_bookmarks = [bookmarks mutableCopy]; - for (NSUInteger i = 0; i != [urls count]; ++i) { - bool skip = false; - for (id bookmark in bookmarks) { - NSError *error = nil; - BOOL isStale = NO; - NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error]; - if (!error && !isStale && ([[exurl path] compare:[[urls objectAtIndex:i] path]] == NSOrderedSame)) { - skip = true; - break; - } - } - if (!skip) { - NSError *error = nil; - NSData *bookmark = [[urls objectAtIndex:i] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error]; - if (!error) { - [new_bookmarks addObject:bookmark]; - } - } - } - [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"]; - } - // Callback. - Vector<String> files; - for (NSUInteger i = 0; i != [urls count]; ++i) { - String url; - url.parse_utf8([[[urls objectAtIndex:i] path] UTF8String]); - files.push_back(url); - } - if (!callback.is_null()) { - Variant v_status = true; - Variant v_files = files; - Variant *v_args[2] = { &v_status, &v_files }; - Variant ret; - Callable::CallError ce; - callback.callp((const Variant **)&v_args, 2, ret, ce); - } - } else { - if (!callback.is_null()) { - Variant v_status = false; - Variant v_files = Vector<String>(); - Variant *v_args[2] = { &v_status, &v_files }; - Variant ret; - Callable::CallError ce; - callback.callp((const Variant **)&v_args, 2, ret, ce); - } + if (!callback.is_null()) { + callback.call(true, files, [handler getIndex]); } - }]; - } break; + } else { + if (!callback.is_null()) { + callback.call(false, Vector<String>(), [handler getIndex]); + } + } + if (prev_focus != INVALID_WINDOW_ID) { + callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus); + } + }]; } return OK; @@ -2062,11 +2269,7 @@ Error DisplayServerMacOS::dialog_input_text(String p_title, String p_description ret.parse_utf8([[input stringValue] UTF8String]); if (!p_callback.is_null()) { - Variant text = ret; - Variant *textp = &text; - Variant fun_ret; - Callable::CallError ce; - p_callback.callp((const Variant **)&textp, 1, fun_ret, ce); + p_callback.call(ret); } return OK; @@ -2626,6 +2829,47 @@ void DisplayServerMacOS::window_set_title(const String &p_title, WindowID p_wind [wd.window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]]; } +Size2i DisplayServerMacOS::window_get_title_size(const String &p_title, WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + Size2i size; + ERR_FAIL_COND_V(!windows.has(p_window), size); + + const WindowData &wd = windows[p_window]; + if (wd.fullscreen || wd.borderless) { + return size; + } + if ([wd.window_object respondsToSelector:@selector(isMiniaturized)]) { + if ([wd.window_object isMiniaturized]) { + return size; + } + } + + float scale = screen_get_max_scale(); + + if (wd.window_button_view) { + size.x = ([wd.window_button_view getOffset].x + [wd.window_button_view frame].size.width); + size.y = ([wd.window_button_view getOffset].y + [wd.window_button_view frame].size.height); + } else { + NSButton *cb = [wd.window_object standardWindowButton:NSWindowCloseButton]; + NSButton *mb = [wd.window_object standardWindowButton:NSWindowMiniaturizeButton]; + float cb_frame = NSMinX([cb frame]); + float mb_frame = NSMinX([mb frame]); + bool is_rtl = ([wd.window_object windowTitlebarLayoutDirection] == NSUserInterfaceLayoutDirectionRightToLeft); + + float window_buttons_spacing = (is_rtl) ? (cb_frame - mb_frame) : (mb_frame - cb_frame); + size.x = window_buttons_spacing * 4; + size.y = [cb frame].origin.y + [cb frame].size.height; + } + + NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont titleBarFontOfSize:0], NSFontAttributeName, nil]; + NSSize text_size = [[[NSAttributedString alloc] initWithString:[NSString stringWithUTF8String:p_title.utf8().get_data()] attributes:attributes] size]; + size.x += text_size.width; + size.y = MAX(size.y, text_size.height); + + return size * scale; +} + void DisplayServerMacOS::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -2868,6 +3112,15 @@ Size2i DisplayServerMacOS::window_get_max_size(WindowID p_window) const { return wd.max_size; } +void DisplayServerMacOS::update_presentation_mode() { + for (const KeyValue<WindowID, WindowData> &wd : windows) { + if (wd.value.fullscreen && wd.value.exclusive_fullscreen) { + return; + } + } + [NSApp setPresentationOptions:NSApplicationPresentationDefault]; +} + void DisplayServerMacOS::window_set_min_size(const Size2i p_size, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -2978,7 +3231,7 @@ void DisplayServerMacOS::window_set_mode(WindowMode p_mode, WindowID p_window) { [wd.window_object toggleFullScreen:nil]; if (old_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { - [NSApp setPresentationOptions:NSApplicationPresentationDefault]; + update_presentation_mode(); } wd.fullscreen = false; @@ -3176,7 +3429,9 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win } break; case WINDOW_FLAG_BORDERLESS: { // OrderOut prevents a lose focus bug with the window. + bool was_visible = false; if ([wd.window_object isVisible]) { + was_visible = true; [wd.window_object orderOut:nil]; } wd.borderless = p_enabled; @@ -3191,7 +3446,7 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win [wd.window_object setFrame:frameRect display:NO]; } _update_window_style(wd); - if ([wd.window_object isVisible]) { + if (was_visible || [wd.window_object isVisible]) { if ([wd.window_object isMiniaturized]) { return; } else if (wd.no_focus) { @@ -3307,14 +3562,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; } } @@ -3603,7 +3858,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); @@ -3765,12 +4020,7 @@ 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). - - Variant tag = call.tag; - Variant *tagp = &tag; - Variant ret; - Callable::CallError ce; - call.callback.callp((const Variant **)&tagp, 1, ret, ce); + call.callback.call(call.tag); } if (!drop_events) { @@ -3874,7 +4124,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(); @@ -3890,7 +4140,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]; @@ -4127,15 +4377,19 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM nsappname = [[NSProcessInfo processInfo] processName]; } + menu_delegate = [[GodotMenuDelegate alloc] init]; + // Setup Dock menu. dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"]; [dock_menu setAutoenablesItems:NO]; + [dock_menu setDelegate:menu_delegate]; // Setup Apple menu. apple_menu = [[NSMenu alloc] initWithTitle:@""]; title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname]; [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""]; [apple_menu setAutoenablesItems:NO]; + [apple_menu setDelegate:menu_delegate]; [apple_menu addItem:[NSMenuItem separatorItem]]; @@ -4165,8 +4419,6 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM [main_menu setSubmenu:apple_menu forItem:menu_item]; [main_menu setAutoenablesItems:NO]; - menu_delegate = [[GodotMenuDelegate alloc] init]; - //!!!!!!!!!!!!!!!!!!!!!!!!!! //TODO - do Vulkan and OpenGL support checks, driver selection and fallback rendering_driver = p_rendering_driver; diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml index 99187fe60e..85fefe65c0 100644 --- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml +++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml @@ -66,7 +66,7 @@ Array of the additional command line arguments passed to the code signing tool. </member> <member name="codesign/entitlements/address_book" type="bool" setter="" getter=""> - Enable to allow access to contacts in the user's address book, if it's enabled you should also provide usage message in the [code]privacy/address_book_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_addressbook]com.apple.security.personal-information.addressbook[/url]. + Enable to allow access to contacts in the user's address book, if it's enabled you should also provide usage message in the [member privacy/address_book_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_addressbook]com.apple.security.personal-information.addressbook[/url]. </member> <member name="codesign/entitlements/allow_dyld_environment_variables" type="bool" setter="" getter=""> Allows app to use dynamic linker environment variables to inject code. If you are using add-ons with dynamic or self-modifying native code, enable them according to the add-on documentation. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-dyld-environment-variables]com.apple.security.cs.allow-dyld-environment-variables[/url]. @@ -115,13 +115,13 @@ Enable to allow app to send Apple events to other apps. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_automation_apple-events]com.apple.security.automation.apple-events[/url]. </member> <member name="codesign/entitlements/audio_input" type="bool" setter="" getter=""> - Enable if you need to use the microphone or other audio input sources, if it's enabled you should also provide usage message in the [code]privacy/microphone_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_audio-input]com.apple.security.device.audio-input[/url]. + Enable if you need to use the microphone or other audio input sources, if it's enabled you should also provide usage message in the [member privacy/microphone_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_audio-input]com.apple.security.device.audio-input[/url]. </member> <member name="codesign/entitlements/calendars" type="bool" setter="" getter=""> - Enable to allow access to the user's calendar, if it's enabled you should also provide usage message in the [code]privacy/calendar_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_calendars]com.apple.security.personal-information.calendars[/url]. + Enable to allow access to the user's calendar, if it's enabled you should also provide usage message in the [member privacy/calendar_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_calendars]com.apple.security.personal-information.calendars[/url]. </member> <member name="codesign/entitlements/camera" type="bool" setter="" getter=""> - Enable if you need to use the camera, if it's enabled you should also provide usage message in the [code]privacy/camera_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_camera]com.apple.security.device.camera[/url]. + Enable if you need to use the camera, if it's enabled you should also provide usage message in the [member privacy/camera_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_device_camera]com.apple.security.device.camera[/url]. </member> <member name="codesign/entitlements/custom_file" type="String" setter="" getter=""> Custom entitlements [code].plist[/code] file, if specified the rest of entitlements in the export config are ignored. @@ -133,10 +133,10 @@ Allows app to load arbitrary libraries and frameworks (not signed with the same Team ID as the main executable or by Apple). Enable it if you are using GDExtension add-ons or ad-hoc signing, or want to support user-provided external add-ons. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation]com.apple.security.cs.disable-library-validation[/url]. </member> <member name="codesign/entitlements/location" type="bool" setter="" getter=""> - Enable if you need to use location information from Location Services, if it's enabled you should also provide usage message in the [code]privacy/location_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_location]com.apple.security.personal-information.location[/url]. + Enable if you need to use location information from Location Services, if it's enabled you should also provide usage message in the [member privacy/location_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_location]com.apple.security.personal-information.location[/url]. </member> <member name="codesign/entitlements/photos_library" type="bool" setter="" getter=""> - Enable to allow access to the user's Photos library, if it's enabled you should also provide usage message in the [code]privacy/photos_library_usage_description[/code] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_photos-library]com.apple.security.personal-information.photos-library[/url]. + Enable to allow access to the user's Photos library, if it's enabled you should also provide usage message in the [member privacy/photos_library_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_photos-library]com.apple.security.personal-information.photos-library[/url]. </member> <member name="codesign/identity" type="String" setter="" getter=""> The "Full Name", "Common Name" or SHA-1 hash of the signing identity used to sign [code].app[/code] bundle. diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 639a7699ee..804028053d 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -1047,6 +1047,8 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre args.push_back("--p12-password"); args.push_back(certificate_pass); } + args.push_back("--code-signature-flags"); + args.push_back("runtime"); args.push_back("-v"); /* provide some more feedback */ @@ -1139,7 +1141,6 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_should_error_on_non_code) { -#ifdef MACOS_ENABLED static Vector<String> extensions_to_sign; if (extensions_to_sign.is_empty()) { @@ -1186,7 +1187,6 @@ Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPres current_file = dir_access->get_next(); } -#endif return OK; } @@ -1225,7 +1225,7 @@ Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref<DirAccess> &dir_access if (extensions_to_sign.find(p_in_app_path.get_extension()) > -1) { err = _code_sign(p_preset, p_in_app_path, p_ent_path, false); } - if (is_executable(p_in_app_path)) { + if (dir_access->file_exists(p_in_app_path) && is_executable(p_in_app_path)) { // chmod with 0755 if the file is executable. FileAccess::set_unix_permissions(p_in_app_path, 0755); } 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_content_view.mm b/platform/macos/godot_content_view.mm index 22a9aa14c0..139411249c 100644 --- a/platform/macos/godot_content_view.mm +++ b/platform/macos/godot_content_view.mm @@ -323,12 +323,7 @@ NSString *file = [NSURL URLWithString:url].path; files.push_back(String::utf8([file UTF8String])); } - - Variant v = files; - Variant *vp = &v; - Variant ret; - Callable::CallError ce; - wd.drop_files_callback.callp((const Variant **)&vp, 1, ret, ce); + wd.drop_files_callback.call(files); } return NO; diff --git a/platform/macos/godot_menu_delegate.mm b/platform/macos/godot_menu_delegate.mm index ebfe8b1f6d..a34094edd7 100644 --- a/platform/macos/godot_menu_delegate.mm +++ b/platform/macos/godot_menu_delegate.mm @@ -39,6 +39,30 @@ - (void)doNothing:(id)sender { } +- (void)menuNeedsUpdate:(NSMenu *)menu { + if (DisplayServer::get_singleton()) { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + ds->menu_open(menu); + } +} + +- (void)menuDidClose:(NSMenu *)menu { + if (DisplayServer::get_singleton()) { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + ds->menu_close(menu); + } +} + +- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item { + if (item) { + GodotMenuItem *value = [item representedObject]; + if (value && value->hover_callback.is_valid()) { + // If custom callback is set, use it. + value->hover_callback.call(value->meta); + } + } +} + - (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action { NSString *ev_key = [[event charactersIgnoringModifiers] lowercaseString]; NSUInteger ev_modifiers = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; @@ -49,23 +73,21 @@ if (ev_modifiers == item_modifiers) { GodotMenuItem *value = [menu_item representedObject]; - if (value->key_callback != Callable()) { - // If custom callback is set, use it. - Variant tag = value->meta; - Variant *tagp = &tag; - Variant ret; - Callable::CallError ce; - value->key_callback.callp((const Variant **)&tagp, 1, ret, ce); - } 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. + value->key_callback.call(value->meta); + } 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_menu_item.h b/platform/macos/godot_menu_item.h index 8876ceae6a..b816ea1b3a 100644 --- a/platform/macos/godot_menu_item.h +++ b/platform/macos/godot_menu_item.h @@ -46,6 +46,7 @@ enum GlobalMenuCheckType { @public Callable callback; Callable key_callback; + Callable hover_callback; Variant meta; GlobalMenuCheckType checkable_type; int max_states; diff --git a/platform/macos/godot_window_delegate.mm b/platform/macos/godot_window_delegate.mm index 46355b4ae8..e359630d1d 100644 --- a/platform/macos/godot_window_delegate.mm +++ b/platform/macos/godot_window_delegate.mm @@ -157,7 +157,7 @@ DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); if (wd.exclusive_fullscreen) { - [NSApp setPresentationOptions:NSApplicationPresentationDefault]; + ds->update_presentation_mode(); } wd.fullscreen = false; @@ -256,11 +256,7 @@ ds->window_resize(window_id, wd.size.width, wd.size.height); if (!wd.rect_changed_callback.is_null()) { - Variant size = Rect2i(ds->window_get_position(window_id), ds->window_get_size(window_id)); - Variant *sizep = &size; - Variant ret; - Callable::CallError ce; - wd.rect_changed_callback.callp((const Variant **)&sizep, 1, ret, ce); + wd.rect_changed_callback.call(Rect2i(ds->window_get_position(window_id), ds->window_get_size(window_id))); } } @@ -283,11 +279,7 @@ ds->release_pressed_events(); if (!wd.rect_changed_callback.is_null()) { - Variant size = Rect2i(ds->window_get_position(window_id), ds->window_get_size(window_id)); - Variant *sizep = &size; - Variant ret; - Callable::CallError ce; - wd.rect_changed_callback.callp((const Variant **)&sizep, 1, ret, ce); + wd.rect_changed_callback.call(Rect2i(ds->window_get_position(window_id), ds->window_get_size(window_id))); } } @@ -364,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/api/api.cpp b/platform/web/api/api.cpp index ab7154b0fb..a695091a04 100644 --- a/platform/web/api/api.cpp +++ b/platform/web/api/api.cpp @@ -96,7 +96,7 @@ Ref<JavaScriptObject> JavaScriptBridge::create_callback(const Callable &p_callab Variant JavaScriptBridge::_create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (p_argcount < 1) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 0; + r_error.expected = 1; return Ref<JavaScriptObject>(); } if (p_args[0]->get_type() != Variant::STRING) { diff --git a/platform/web/audio_driver_web.h b/platform/web/audio_driver_web.h index 7bfed834e1..12a61746c3 100644 --- a/platform/web/audio_driver_web.h +++ b/platform/web/audio_driver_web.h @@ -32,6 +32,7 @@ #define AUDIO_DRIVER_WEB_H #include "godot_audio.h" +#include "godot_js.h" #include "core/os/mutex.h" #include "core/os/thread.h" @@ -55,8 +56,8 @@ private: int mix_rate = 0; int channel_count = 0; - static void _state_change_callback(int p_state); - static void _latency_update_callback(float p_latency); + WASM_EXPORT static void _state_change_callback(int p_state); + WASM_EXPORT static void _latency_update_callback(float p_latency); static AudioDriverWeb *singleton; diff --git a/platform/web/detect.py b/platform/web/detect.py index 043ddca65d..d306869889 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", + True, + ), ] @@ -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 aac1401f23..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,16 +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. - Variant *vp = &size; - Variant ret; - Callable::CallError ce; - rect_changed_callback.callp((const Variant **)&vp, 1, ret, ce); + 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; @@ -78,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"); @@ -86,26 +113,26 @@ 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])); - } - Variant v = files; - Variant *vp = &v; - Variant ret; - Callable::CallError ce; - ds->drop_files_callback.callp((const Variant **)&vp, 1, ret, ce); + 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); - Variant *eventp = &event; - Variant ret; - Callable::CallError ce; - ds->window_event_callback.callp((const Variant **)&eventp, 1, ret, ce); + ds->window_event_callback.call(event); } } @@ -129,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(); @@ -160,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); @@ -236,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. @@ -311,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]; @@ -344,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() { @@ -366,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)) { @@ -517,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(); @@ -569,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; @@ -613,16 +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); - Variant *eventp = &event; - Variant ret; - Callable::CallError ce; - ds->input_text_callback.callp((const Variant **)&eventp, 1, ret, ce); + ds->input_text_callback.call(p_text); // Insert key right to reach position. Input *input = Input::get_singleton(); Ref<InputEventKey> k; @@ -649,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, ""); } @@ -700,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) { @@ -715,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; @@ -724,10 +895,7 @@ void DisplayServerWeb::send_window_event_callback(int p_notification) { } if (!ds->window_event_callback.is_null()) { Variant event = int(p_notification); - Variant *eventp = &event; - Variant ret; - Callable::CallError ce; - ds->window_event_callback.callp((const Variant **)&eventp, 1, ret, ce); + ds->window_event_callback.call(event); } } @@ -770,12 +938,8 @@ void DisplayServerWeb::set_icon(const Ref<Image> &p_icon) { void DisplayServerWeb::_dispatch_input_event(const Ref<InputEvent> &p_event) { Callable cb = get_singleton()->input_event_callback; - if (!cb.is_null()) { - Variant ev = p_event; - Variant *evp = &ev; - Variant ret; - Callable::CallError ce; - cb.callp((const Variant **)&evp, 1, ret, ce); + if (cb.is_valid()) { + cb.call(p_event); } } @@ -836,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 a4fd75f33b..51c6ab3c0a 100644 --- a/platform/web/display_server_web.h +++ b/platform/web/display_server_web.h @@ -33,6 +33,8 @@ #include "servers/display_server.h" +#include "godot_js.h" + #include <emscripten.h> #include <emscripten/html5.h> @@ -88,29 +90,44 @@ private: static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape); // events - static void fullscreen_change_callback(int p_fullscreen); - static int mouse_button_callback(int p_pressed, int p_button, double p_x, double p_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); - static int mouse_wheel_callback(double p_delta_x, double p_delta_y); - static void touch_callback(int p_type, int p_count); - static void key_callback(int p_pressed, int p_repeat, int p_modifiers); - static void vk_input_text_callback(const char *p_text, int p_cursor); - static void gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid); - void process_joypads(); + 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(); 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); - static void request_quit_callback(); - static void window_blur_callback(); - static void update_voices_callback(int p_size, const char **p_voice); - static void update_clipboard_callback(const char *p_text); - static void send_window_event_callback(int p_notification); - 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 3341cf8a67..031e67e486 100644 --- a/platform/web/godot_js.h +++ b/platform/web/godot_js.h @@ -31,11 +31,14 @@ #ifndef GODOT_JS_H #define GODOT_JS_H +#define WASM_EXPORT __attribute__((visibility("default"))) + #ifdef __cplusplus extern "C" { #endif #include <stddef.h> +#include <stdint.h> // Config extern void godot_js_config_locale_get(char *p_ptr, int p_ptr_max); @@ -67,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 45bce1b480..d72ad8331b 100644 --- a/platform/web/javascript_bridge_singleton.cpp +++ b/platform/web/javascript_bridge_singleton.cpp @@ -68,11 +68,12 @@ private: int _js_id = 0; Callable _callable; - static int _variant2js(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock); - static void _free_lock(void **p_lock, int p_type); - static Variant _js2variant(int p_type, godot_js_wrapper_ex *p_val); - static void *_alloc_variants(int p_size); - static void _callback(void *p_ref, int p_arg_id, int p_argc); + WASM_EXPORT static int _variant2js(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock); + 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); + 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++) { @@ -257,14 +259,24 @@ void JavaScriptObjectImpl::_callback(void *p_ref, int p_args_id, int p_argc) { arg_arr.push_back(_js2variant(type, &exchange)); } Variant arg = arg_arr; - const Variant *argv[1] = { &arg }; - Callable::CallError err; - Variant ret; - obj->_callable.callp(argv, 1, ret, err); + +#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; void *lock = nullptr; + Variant ret; const Variant *v = &ret; int type = _variant2js((const void **)&v, 0, &exchange, &lock); godot_js_wrapper_object_set_cb_ret(type, &exchange); @@ -276,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; } @@ -289,7 +301,7 @@ Ref<JavaScriptObject> JavaScriptBridge::get_interface(const String &p_interface) Variant JavaScriptBridge::_create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (p_argcount < 1) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 0; + r_error.expected = 1; return Ref<JavaScriptObject>(); } if (p_args[0]->get_type() != Variant::STRING) { 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 cbe59230ee..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); @@ -210,7 +216,7 @@ const GodotJSWrapper = { // This is safe! JavaScript is single threaded (and using it in threads is not a good idea anyway). GodotJSWrapper.cb_ret = null; const args = Array.from(arguments); - const argsProxy = GodotJSWrapper.MyProxy(args); + const argsProxy = new GodotJSWrapper.MyProxy(args); func(p_ref, argsProxy.get_id(), args.length); argsProxy.unref(); const ret = GodotJSWrapper.cb_ret; @@ -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 f038f0248a..cbdcbf565d 100644 --- a/platform/web/os_web.cpp +++ b/platform/web/os_web.cpp @@ -132,6 +132,10 @@ int OS_Web::get_processor_count() const { return godot_js_os_hw_concurrency_get(); } +String OS_Web::get_unique_id() const { + ERR_FAIL_V_MSG("", "OS::get_unique_id() is not available on the Web platform."); +} + bool OS_Web::_check_internal_feature_support(const String &p_feature) { if (p_feature == "web") { return true; @@ -156,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 b9570f9ca1..5a48997c17 100644 --- a/platform/web/os_web.h +++ b/platform/web/os_web.h @@ -33,6 +33,8 @@ #include "audio_driver_web.h" +#include "godot_js.h" + #include "core/input/input.h" #include "drivers/unix/os_unix.h" #include "servers/audio_server.h" @@ -48,11 +50,11 @@ class OS_Web : public OS_Unix { bool idb_needs_sync = false; bool pwa_is_waiting = false; - static void main_loop_callback(); + WASM_EXPORT static void main_loop_callback(); - static void file_access_close_callback(const String &p_file, int p_flags); - static void fs_sync_callback(); - static void update_pwa_state_callback(); + WASM_EXPORT static void file_access_close_callback(const String &p_file, int p_flags); + WASM_EXPORT static void fs_sync_callback(); + WASM_EXPORT static void update_pwa_state_callback(); protected: void initialize() override; @@ -83,14 +85,16 @@ public: int get_process_id() const override; bool is_process_running(const ProcessID &p_pid) const override; int get_processor_count() const override; + String get_unique_id() const override; int get_default_thread_pool_size() const override { return 1; } 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 1a1bba833d..cc5ae9ad45 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -222,24 +222,45 @@ void DisplayServerWindows::tts_stop() { Error DisplayServerWindows::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { _THREAD_SAFE_METHOD_ + ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED); + Vector<Char16String> filter_names; Vector<Char16String> filter_exts; for (const String &E : p_filters) { Vector<String> tokens = E.split(";"); - if (tokens.size() == 2) { - filter_exts.push_back(tokens[0].strip_edges().utf16()); - filter_names.push_back(tokens[1].strip_edges().utf16()); - } else if (tokens.size() == 1) { - filter_exts.push_back(tokens[0].strip_edges().utf16()); - filter_names.push_back(tokens[0].strip_edges().utf16()); + if (tokens.size() >= 1) { + String flt = tokens[0].strip_edges(); + int filter_slice_count = flt.get_slice_count(","); + Vector<String> exts; + for (int j = 0; j < filter_slice_count; j++) { + String str = (flt.get_slice(",", j).strip_edges()); + if (!str.is_empty()) { + exts.push_back(str); + } + } + if (!exts.is_empty()) { + String str = String(";").join(exts); + filter_exts.push_back(str.utf16()); + if (tokens.size() == 2) { + filter_names.push_back(tokens[1].strip_edges().utf16()); + } else { + filter_names.push_back(str.utf16()); + } + } } } + if (filter_names.is_empty()) { + filter_exts.push_back(String("*.*").utf16()); + filter_names.push_back(RTR("All Files").utf16()); + } Vector<COMDLG_FILTERSPEC> filters; for (int i = 0; i < filter_names.size(); i++) { filters.push_back({ (LPCWSTR)filter_names[i].ptr(), (LPCWSTR)filter_exts[i].ptr() }); } + WindowID prev_focus = last_focused_window; + HRESULT hr = S_OK; IFileDialog *pfd = nullptr; if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) { @@ -285,6 +306,9 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String } hr = pfd->Show(windows[window_id].hWnd); + UINT index = 0; + pfd->GetFileTypeIndex(&index); + if (SUCCEEDED(hr)) { Vector<String> file_names; @@ -322,24 +346,17 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String } } if (!p_callback.is_null()) { - Variant v_status = true; - Variant v_files = file_names; - Variant *v_args[2] = { &v_status, &v_files }; - Variant ret; - Callable::CallError ce; - p_callback.callp((const Variant **)&v_args, 2, ret, ce); + p_callback.call(true, file_names, index); } } else { if (!p_callback.is_null()) { - Variant v_status = false; - Variant v_files = Vector<String>(); - Variant *v_args[2] = { &v_status, &v_files }; - Variant ret; - Callable::CallError ce; - p_callback.callp((const Variant **)&v_args, 2, ret, ce); + p_callback.call(false, Vector<String>(), index); } } pfd->Release(); + if (prev_focus != INVALID_WINDOW_ID) { + callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus); + } return OK; } else { @@ -1191,6 +1208,51 @@ void DisplayServerWindows::window_set_title(const String &p_title, WindowID p_wi SetWindowTextW(windows[p_window].hWnd, (LPCWSTR)(p_title.utf16().get_data())); } +Size2i DisplayServerWindows::window_get_title_size(const String &p_title, WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + Size2i size; + ERR_FAIL_COND_V(!windows.has(p_window), size); + + const WindowData &wd = windows[p_window]; + if (wd.fullscreen || wd.minimized || wd.borderless) { + return size; + } + + HDC hdc = GetDCEx(wd.hWnd, NULL, DCX_WINDOW); + if (hdc) { + Char16String s = p_title.utf16(); + SIZE text_size; + if (GetTextExtentPoint32W(hdc, (LPCWSTR)(s.get_data()), s.length(), &text_size)) { + size.x = text_size.cx; + size.y = text_size.cy; + } + + ReleaseDC(wd.hWnd, hdc); + } + RECT rect; + if (DwmGetWindowAttribute(wd.hWnd, DWMWA_CAPTION_BUTTON_BOUNDS, &rect, sizeof(RECT)) == S_OK) { + if (rect.right - rect.left > 0) { + ClientToScreen(wd.hWnd, (POINT *)&rect.left); + ClientToScreen(wd.hWnd, (POINT *)&rect.right); + + if (win81p_PhysicalToLogicalPointForPerMonitorDPI) { + win81p_PhysicalToLogicalPointForPerMonitorDPI(0, (POINT *)&rect.left); + win81p_PhysicalToLogicalPointForPerMonitorDPI(0, (POINT *)&rect.right); + } + + size.x += (rect.right - rect.left); + size.y = MAX(size.y, rect.bottom - rect.top); + } + } + if (icon.is_valid()) { + size.x += 32; + } else { + size.x += 16; + } + return size; +} + void DisplayServerWindows::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -2591,12 +2653,9 @@ void DisplayServerWindows::_drag_event(WindowID p_window, float p_x, float p_y, } void DisplayServerWindows::_send_window_event(const WindowData &wd, WindowEvent p_event) { - if (!wd.event_callback.is_null()) { + if (wd.event_callback.is_valid()) { Variant event = int(p_event); - Variant *eventp = &event; - Variant ret; - Callable::CallError ce; - wd.event_callback.callp((const Variant **)&eventp, 1, ret, ce); + wd.event_callback.call(event); } } @@ -2609,12 +2668,7 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) if (in_dispatch_input_event) { return; } - in_dispatch_input_event = true; - Variant ev = p_event; - Variant *evp = &ev; - Variant ret; - Callable::CallError ce; { List<WindowID>::Element *E = popup_list.back(); @@ -2623,7 +2677,7 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) if (windows.has(E->get())) { Callable callable = windows[E->get()].input_event_callback; if (callable.is_valid()) { - callable.callp((const Variant **)&evp, 1, ret, ce); + callable.call(p_event); } } in_dispatch_input_event = false; @@ -2637,7 +2691,7 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) if (windows.has(event_from_window->get_window_id())) { Callable callable = windows[event_from_window->get_window_id()].input_event_callback; if (callable.is_valid()) { - callable.callp((const Variant **)&evp, 1, ret, ce); + callable.call(p_event); } } } else { @@ -2645,7 +2699,7 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) for (const KeyValue<WindowID, WindowData> &E : windows) { const Callable callable = E.value.input_event_callback; if (callable.is_valid()) { - callable.callp((const Variant **)&evp, 1, ret, ce); + callable.call(p_event); } } } @@ -3708,11 +3762,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA if (rect_changed) { if (!window.rect_changed_callback.is_null()) { - Variant size = Rect2i(window.last_pos.x, window.last_pos.y, window.width, window.height); - const Variant *args[] = { &size }; - Variant ret; - Callable::CallError ce; - window.rect_changed_callback.callp(args, 1, ret, ce); + window.rect_changed_callback.call(Rect2i(window.last_pos.x, window.last_pos.y, window.width, window.height)); } // Update cursor clip region after window rect has changed. @@ -3929,11 +3979,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } if (files.size() && !windows[window_id].drop_files_callback.is_null()) { - Variant v = files; - Variant *vp = &v; - Variant ret; - Callable::CallError ce; - windows[window_id].drop_files_callback.callp((const Variant **)&vp, 1, ret, ce); + windows[window_id].drop_files_callback.call(files); } } break; default: { @@ -4385,6 +4431,7 @@ bool DisplayServerWindows::winink_available = false; GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr; GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr; LogicalToPhysicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_LogicalToPhysicalPointForPerMonitorDPI = nullptr; +PhysicalToLogicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_PhysicalToLogicalPointForPerMonitorDPI = nullptr; typedef enum _SHC_PROCESS_DPI_AWARENESS { SHC_PROCESS_DPI_UNAWARE = 0, @@ -4522,6 +4569,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo"); win81p_LogicalToPhysicalPointForPerMonitorDPI = (LogicalToPhysicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "LogicalToPhysicalPointForPerMonitorDPI"); + win81p_PhysicalToLogicalPointForPerMonitorDPI = (PhysicalToLogicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "PhysicalToLogicalPointForPerMonitorDPI"); winink_available = win8p_GetPointerType && win8p_GetPointerPenInfo; } @@ -4579,9 +4627,22 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win // Init context and rendering device #if defined(GLES3_ENABLED) - if (rendering_driver == "opengl3") { - int gl_version = detect_wgl_version(); - if (gl_version < 30003) { + bool fallback = GLOBAL_GET("rendering/gl_compatibility/fallback_to_angle"); + if (fallback && (rendering_driver == "opengl3")) { + Dictionary gl_info = detect_wgl(); + + bool force_angle = false; + + Array device_list = GLOBAL_GET("rendering/gl_compatibility/force_angle_on_devices"); + for (int i = 0; i < device_list.size(); i++) { + const Dictionary &device = device_list[i]; + if (device.has("vendor") && device.has("name") && device["vendor"].operator String().to_upper() == gl_info["vendor"].operator String().to_upper() && (device["name"] == "*" || device["name"].operator String().to_upper() == gl_info["name"].operator String().to_upper())) { + force_angle = true; + break; + } + } + + if (force_angle || (gl_info["version"].operator int() < 30003)) { WARN_PRINT("Your video card drivers seem not to support the required OpenGL 3.3 version, switching to ANGLE."); rendering_driver = "opengl3_angle"; } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 28f2f7e6ff..48c8c20280 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -262,6 +262,7 @@ typedef struct tagPOINTER_PEN_INFO { typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type); typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info); typedef BOOL(WINAPI *LogicalToPhysicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint); +typedef BOOL(WINAPI *PhysicalToLogicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint); typedef struct { BYTE bWidth; // Width, in pixels, of the image @@ -309,6 +310,7 @@ class DisplayServerWindows : public DisplayServer { // DPI conversion API static LogicalToPhysicalPointForPerMonitorDPIPtr win81p_LogicalToPhysicalPointForPerMonitorDPI; + static PhysicalToLogicalPointForPerMonitorDPIPtr win81p_PhysicalToLogicalPointForPerMonitorDPI; void _update_tablet_ctx(const String &p_old_driver, const String &p_new_driver); String tablet_driver; @@ -569,6 +571,7 @@ public: virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; + virtual Size2i window_get_title_size(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID) override; virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; 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; diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py index b522a75a9c..51652fa814 100644 --- a/platform/windows/platform_windows_builders.py +++ b/platform/windows/platform_windows_builders.py @@ -5,14 +5,24 @@ All such functions are invoked in a subprocess on Windows to prevent build flaki """ import os from detect import get_mingw_bin_prefix +from detect import try_cmd from platform_methods import subprocess_main def make_debug_mingw(target, source, env): mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) - os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0])) - os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(target[0])) - os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0])) + if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]): + os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0])) + else: + os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0])) + if try_cmd("strip --version", env["mingw_prefix"], env["arch"]): + os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(target[0])) + else: + os.system("strip --strip-debug --strip-unneeded {0}".format(target[0])) + if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]): + os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0])) + else: + os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0])) if __name__ == "__main__": diff --git a/platform/windows/wgl_detect_version.cpp b/platform/windows/wgl_detect_version.cpp index 264cd525c5..49da4b58c7 100644 --- a/platform/windows/wgl_detect_version.cpp +++ b/platform/windows/wgl_detect_version.cpp @@ -35,6 +35,7 @@ #include "core/string/print_string.h" #include "core/string/ustring.h" +#include "core/variant/dictionary.h" #include <windows.h> @@ -64,9 +65,11 @@ typedef HGLRC(APIENTRY *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int typedef void *(APIENTRY *PFNWGLGETPROCADDRESS)(LPCSTR); typedef const char *(APIENTRY *PFNWGLGETSTRINGPROC)(unsigned int); -int detect_wgl_version() { - int major = 0; - int minor = 0; +Dictionary detect_wgl() { + Dictionary gl_info; + gl_info["version"] = 0; + gl_info["vendor"] = String(); + gl_info["name"] = String(); PFNWGLCREATECONTEXT gd_wglCreateContext; PFNWGLMAKECURRENT gd_wglMakeCurrent; @@ -75,14 +78,14 @@ int detect_wgl_version() { HMODULE module = LoadLibraryW(L"opengl32.dll"); if (!module) { - return 0; + return gl_info; } gd_wglCreateContext = (PFNWGLCREATECONTEXT)GetProcAddress(module, "wglCreateContext"); gd_wglMakeCurrent = (PFNWGLMAKECURRENT)GetProcAddress(module, "wglMakeCurrent"); gd_wglDeleteContext = (PFNWGLDELETECONTEXT)GetProcAddress(module, "wglDeleteContext"); gd_wglGetProcAddress = (PFNWGLGETPROCADDRESS)GetProcAddress(module, "wglGetProcAddress"); if (!gd_wglCreateContext || !gd_wglMakeCurrent || !gd_wglDeleteContext || !gd_wglGetProcAddress) { - return 0; + return gl_info; } LPCWSTR class_name = L"EngineWGLDetect"; @@ -151,8 +154,8 @@ int detect_wgl_version() { }; const char *version = (const char *)gd_wglGetString(WGL_VERSION); if (version) { - const String device_vendor = String::utf8((const char *)gd_wglGetString(WGL_VENDOR)).strip_edges(); - const String device_name = String::utf8((const char *)gd_wglGetString(WGL_RENDERER)).strip_edges(); + const String device_vendor = String::utf8((const char *)gd_wglGetString(WGL_VENDOR)).strip_edges().trim_suffix(" Corporation"); + const String device_name = String::utf8((const char *)gd_wglGetString(WGL_RENDERER)).strip_edges().trim_suffix("/PCIe/SSE2"); for (int i = 0; prefixes[i]; i++) { size_t length = strlen(prefixes[i]); if (strncmp(version, prefixes[i], length) == 0) { @@ -160,12 +163,17 @@ int detect_wgl_version() { break; } } + int major = 0; + int minor = 0; #ifdef _MSC_VER sscanf_s(version, "%d.%d", &major, &minor); #else sscanf(version, "%d.%d", &major, &minor); #endif print_verbose(vformat("Native OpenGL API detected: %d.%d: %s - %s", major, minor, device_vendor, device_name)); + gl_info["vendor"] = device_vendor; + gl_info["name"] = device_name; + gl_info["version"] = major * 10000 + minor; } } } @@ -183,7 +191,7 @@ int detect_wgl_version() { } UnregisterClassW(class_name, hInstance); - return major * 10000 + minor; + return gl_info; } #endif // WINDOWS_ENABLED && GLES3_ENABLED diff --git a/platform/windows/wgl_detect_version.h b/platform/windows/wgl_detect_version.h index 0be2923ba3..c110b1219e 100644 --- a/platform/windows/wgl_detect_version.h +++ b/platform/windows/wgl_detect_version.h @@ -33,7 +33,9 @@ #if defined(WINDOWS_ENABLED) && defined(GLES3_ENABLED) -int detect_wgl_version(); +class Dictionary; + +Dictionary detect_wgl(); #endif // WINDOWS_ENABLED && GLES3_ENABLED |