From 961394a988c7567612b133092212cbacf4dd98b2 Mon Sep 17 00:00:00 2001 From: Fredia Huya-Kouadio Date: Thu, 7 Mar 2024 19:16:25 -0800 Subject: [PATCH] Add support for launching the Play window in PiP mode --- doc/classes/EditorSettings.xml | 12 +- editor/editor_settings.cpp | 6 + .../java/editor/src/main/AndroidManifest.xml | 5 +- .../editor/EditorMessageDispatcher.kt | 192 ++++++++++++++++++ .../godotengine/editor/EditorWindowInfo.kt | 27 +-- .../org/godotengine/editor/GodotEditor.kt | 155 +++++++++++--- .../java/org/godotengine/editor/GodotGame.kt | 93 ++++++++- .../drawable/ic_play_window_foreground.xml | 25 +++ .../drawable/outline_fullscreen_exit_48.xml | 12 ++ .../pip_button_activated_bg_drawable.xml | 10 + .../res/drawable/pip_button_bg_drawable.xml | 9 + .../pip_button_default_bg_drawable.xml | 10 + .../src/main/res/layout/godot_game_layout.xml | 25 +++ .../res/mipmap-anydpi-v26/ic_play_window.xml | 5 + .../main/res/mipmap-hdpi/ic_play_window.png | Bin 0 -> 1954 bytes .../main/res/mipmap-mdpi/ic_play_window.png | Bin 0 -> 1350 bytes .../main/res/mipmap-xhdpi/ic_play_window.png | Bin 0 -> 2617 bytes .../main/res/mipmap-xxhdpi/ic_play_window.png | Bin 0 -> 3923 bytes .../res/mipmap-xxxhdpi/ic_play_window.png | Bin 0 -> 5480 bytes .../editor/src/main/res/values/dimens.xml | 2 +- .../editor/src/main/res/values/strings.xml | 2 + .../org/godotengine/godot/GodotActivity.kt | 8 - .../godot/utils/ProcessPhoenix.java | 27 ++- 23 files changed, 568 insertions(+), 57 deletions(-) create mode 100644 platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt create mode 100644 platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml create mode 100644 platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml create mode 100644 platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml create mode 100644 platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml create mode 100644 platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml create mode 100644 platform/android/java/editor/src/main/res/layout/godot_game_layout.xml create mode 100644 platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml create mode 100644 platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.png create mode 100644 platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.png create mode 100644 platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.png create mode 100644 platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.png create mode 100644 platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.png diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 4bb7149f2f3..52ea9fc1eaf 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -961,7 +961,17 @@ If [code]true[/code], on Linux/BSD, the editor will check for Wayland first instead of X11 (if available). - The Android window to display the project on when starting the project from the editor. + Specifies how the Play window is launched relative to the Android editor. + - [b]Auto (based on screen size)[/b] (default) will automatically choose how to launch the Play window based on the device and screen metrics. Defaults to [b]Same as Editor[/b] on phones and [b]Side-by-side with Editor[/b] on tablets. + - [b]Same as Editor[/b] will launch the Play window in the same window as the Editor. + - [b]Side-by-side with Editor[/b] will launch the Play window side-by-side with the Editor window. + [b]Note:[/b] Only available in the Android editor. + + + Specifies the picture-in-picture (PiP) mode for the Play window. + - [b]Disabled:[/b] PiP is disabled for the Play window. + - [b]Enabled:[/b] If the device supports it, PiP is always enabled for the Play window. The Play window will contain a button to enter PiP mode. + - [b]Enabled when Play window is same as Editor[/b] (default for Android editor): If the device supports it, PiP is enabled when the Play window is the same as the Editor. The Play window will contain a button to enter PiP mode. [b]Note:[/b] Only available in the Android editor. diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index a4cb3fbb68a..c24b70f0351 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -825,6 +825,12 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { String android_window_hints = "Auto (based on screen size):0,Same as Editor:1,Side-by-side with Editor:2"; EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/android_window", 0, android_window_hints) + int default_play_window_pip_mode = 0; +#ifdef ANDROID_ENABLED + default_play_window_pip_mode = 2; +#endif + EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/play_window_pip_mode", default_play_window_pip_mode, "Disabled:0,Enabled:1,Enabled when Play window is same as Editor:2") + // Auto save _initial_set("run/auto_save/save_before_running", true); diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml index c7d14a3f491..a8757458605 100644 --- a/platform/android/java/editor/src/main/AndroidManifest.xml +++ b/platform/android/java/editor/src/main/AndroidManifest.xml @@ -42,6 +42,7 @@ android:name=".GodotEditor" android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode" android:exported="true" + android:icon="@mipmap/icon" android:launchMode="singleTask" android:screenOrientation="userLandscape"> () + + @SuppressLint("HandlerLeak") + private val dispatcherHandler = object : Handler() { + override fun handleMessage(msg: Message) { + when (msg.what) { + MSG_FORCE_QUIT -> editor.finish() + + MSG_REGISTER_MESSENGER -> { + val editorId = msg.arg1 + val messenger = msg.replyTo + registerMessenger(editorId, messenger) + } + + else -> super.handleMessage(msg) + } + } + } + + /** + * Request the window with the given [editorId] to force quit. + */ + fun requestForceQuit(editorId: Int): Boolean { + val messenger = recipientsMessengers[editorId] ?: return false + return try { + Log.v(TAG, "Requesting 'forceQuit' for $editorId") + val msg = Message.obtain(null, MSG_FORCE_QUIT) + messenger.send(msg) + true + } catch (e: RemoteException) { + Log.e(TAG, "Error requesting 'forceQuit' to $editorId", e) + recipientsMessengers.remove(editorId) + false + } + } + + /** + * Utility method to register a receiver messenger. + */ + private fun registerMessenger(editorId: Int, messenger: Messenger?, messengerDeathCallback: Runnable? = null) { + try { + if (messenger == null) { + Log.w(TAG, "Invalid 'replyTo' payload") + } else if (messenger.binder.isBinderAlive) { + messenger.binder.linkToDeath({ + Log.v(TAG, "Removing messenger for $editorId") + recipientsMessengers.remove(editorId) + messengerDeathCallback?.run() + }, 0) + recipientsMessengers[editorId] = messenger + } + } catch (e: RemoteException) { + Log.e(TAG, "Unable to register messenger from $editorId", e) + recipientsMessengers.remove(editorId) + } + } + + /** + * Utility method to register a [Messenger] attached to this handler with a host. + * + * This is done so that the host can send request to the editor instance attached to this handle. + * + * Note that this is only done when the editor instance is internal (not exported) to prevent + * arbitrary apps from having the ability to send requests. + */ + private fun registerSelfTo(pm: PackageManager, host: Messenger?, selfId: Int) { + try { + if (host == null || !host.binder.isBinderAlive) { + Log.v(TAG, "Host is unavailable") + return + } + + val activityInfo = pm.getActivityInfo(editor.componentName, 0) + if (activityInfo.exported) { + Log.v(TAG, "Not registering self to host as we're exported") + return + } + + Log.v(TAG, "Registering self $selfId to host") + val msg = Message.obtain(null, MSG_REGISTER_MESSENGER) + msg.arg1 = selfId + msg.replyTo = Messenger(dispatcherHandler) + host.send(msg) + } catch (e: RemoteException) { + Log.e(TAG, "Unable to register self with host", e) + } + } + + /** + * Parses the starting intent and retrieve an editor messenger if available + */ + fun parseStartIntent(pm: PackageManager, intent: Intent) { + val messengerBundle = intent.getBundleExtra(EXTRA_MSG_DISPATCHER_PAYLOAD) ?: return + + // Retrieve the sender messenger payload and store it. This can be used to communicate back + // to the sender. + val senderId = messengerBundle.getInt(KEY_EDITOR_ID) + val senderMessenger: Messenger? = messengerBundle.getParcelable(KEY_EDITOR_MESSENGER) + registerMessenger(senderId, senderMessenger) + + // Register ourselves to the sender so that it can communicate with us. + registerSelfTo(pm, senderMessenger, editor.getEditorId()) + } + + /** + * Returns the payload used by the [EditorMessageDispatcher] class to establish an IPC bridge + * across editor instances. + */ + fun getMessageDispatcherPayload(): Bundle { + return Bundle().apply { + putInt(KEY_EDITOR_ID, editor.getEditorId()) + putParcelable(KEY_EDITOR_MESSENGER, Messenger(dispatcherHandler)) + } + } +} diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt index 0da1d01aedf..d3daa1dbbc8 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt @@ -31,23 +31,24 @@ package org.godotengine.editor /** - * Specifies the policy for adjacent launches. + * Specifies the policy for launches. */ -enum class LaunchAdjacentPolicy { +enum class LaunchPolicy { /** - * Adjacent launches are disabled. - */ - DISABLED, - - /** - * Adjacent launches are enabled / disabled based on the device and screen metrics. + * Launch policy is determined by the editor settings or based on the device and screen metrics. */ AUTO, + + /** + * Launches happen in the same window. + */ + SAME, + /** * Adjacent launches are enabled. */ - ENABLED + ADJACENT } /** @@ -57,12 +58,14 @@ data class EditorWindowInfo( val windowClassName: String, val windowId: Int, val processNameSuffix: String, - val launchAdjacentPolicy: LaunchAdjacentPolicy = LaunchAdjacentPolicy.DISABLED + val launchPolicy: LaunchPolicy = LaunchPolicy.SAME, + val supportsPiPMode: Boolean = false ) { constructor( windowClass: Class<*>, windowId: Int, processNameSuffix: String, - launchAdjacentPolicy: LaunchAdjacentPolicy = LaunchAdjacentPolicy.DISABLED - ) : this(windowClass.name, windowId, processNameSuffix, launchAdjacentPolicy) + launchPolicy: LaunchPolicy = LaunchPolicy.SAME, + supportsPiPMode: Boolean = false + ) : this(windowClass.name, windowId, processNameSuffix, launchPolicy, supportsPiPMode) } 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 9cc133046bd..5d6da06f97e 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 @@ -32,6 +32,7 @@ package org.godotengine.editor import android.Manifest import android.app.ActivityManager +import android.app.ActivityOptions import android.content.ComponentName import android.content.Context import android.content.Intent @@ -69,17 +70,24 @@ open class GodotEditor : GodotActivity() { private const val WAIT_FOR_DEBUGGER = false - private const val EXTRA_COMMAND_LINE_PARAMS = "command_line_params" + @JvmStatic + protected val EXTRA_COMMAND_LINE_PARAMS = "command_line_params" + @JvmStatic + protected val EXTRA_PIP_AVAILABLE = "pip_available" + @JvmStatic + protected val EXTRA_LAUNCH_IN_PIP = "launch_in_pip_requested" // Command line arguments private const val EDITOR_ARG = "--editor" private const val EDITOR_ARG_SHORT = "-e" private const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager" private const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p" + private const val BREAKPOINTS_ARG = "--breakpoints" + private const val BREAKPOINTS_ARG_SHORT = "-b" // Info for the various classes used by the editor internal val EDITOR_MAIN_INFO = EditorWindowInfo(GodotEditor::class.java, 777, "") - internal val RUN_GAME_INFO = EditorWindowInfo(GodotGame::class.java, 667, ":GodotGame", LaunchAdjacentPolicy.AUTO) + internal val RUN_GAME_INFO = EditorWindowInfo(GodotGame::class.java, 667, ":GodotGame", LaunchPolicy.AUTO, true) /** * Sets of constants to specify the window to use to run the project. @@ -90,13 +98,26 @@ open class GodotEditor : GodotActivity() { private const val ANDROID_WINDOW_AUTO = 0 private const val ANDROID_WINDOW_SAME_AS_EDITOR = 1 private const val ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR = 2 + + /** + * Sets of constants to specify the Play window PiP mode. + * + * Should match the values in `editor/editor_settings.cpp'` for the + * 'run/window_placement/play_window_pip_mode' setting. + */ + private const val PLAY_WINDOW_PIP_DISABLED = 0 + private const val PLAY_WINDOW_PIP_ENABLED = 1 + private const val PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR = 2 } + private val editorMessageDispatcher = EditorMessageDispatcher(this) private val commandLineParams = ArrayList() private val editorLoadingIndicator: View? by lazy { findViewById(R.id.editor_loading_indicator) } override fun getGodotAppLayout() = R.layout.godot_editor_layout + internal open fun getEditorId() = EDITOR_MAIN_INFO.windowId + override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() @@ -108,6 +129,8 @@ open class GodotEditor : GodotActivity() { Log.d(TAG, "Starting intent $intent with parameters ${params.contentToString()}") updateCommandLineParams(params?.asList() ?: emptyList()) + editorMessageDispatcher.parseStartIntent(packageManager, intent) + if (BuildConfig.BUILD_TYPE == "dev" && WAIT_FOR_DEBUGGER) { Debug.waitForDebugger() } @@ -189,35 +212,67 @@ open class GodotEditor : GodotActivity() { } } - override fun onNewGodotInstanceRequested(args: Array): Int { - val editorWindowInfo = getEditorWindowInfo(args) - - // Launch a new activity + protected fun getNewGodotInstanceIntent(editorWindowInfo: EditorWindowInfo, args: Array): Intent { val newInstance = Intent() .setComponent(ComponentName(this, editorWindowInfo.windowClassName)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .putExtra(EXTRA_COMMAND_LINE_PARAMS, args) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (editorWindowInfo.launchAdjacentPolicy == LaunchAdjacentPolicy.ENABLED || - (editorWindowInfo.launchAdjacentPolicy == LaunchAdjacentPolicy.AUTO && shouldGameLaunchAdjacent())) { + + val launchPolicy = resolveLaunchPolicyIfNeeded(editorWindowInfo.launchPolicy) + val isPiPAvailable = if (editorWindowInfo.supportsPiPMode && hasPiPSystemFeature()) { + val pipMode = getPlayWindowPiPMode() + pipMode == PLAY_WINDOW_PIP_ENABLED || + (pipMode == PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR && launchPolicy == LaunchPolicy.SAME) + } else { + false + } + newInstance.putExtra(EXTRA_PIP_AVAILABLE, isPiPAvailable) + + if (launchPolicy == LaunchPolicy.ADJACENT) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Log.v(TAG, "Adding flag for adjacent launch") newInstance.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT) } + } else if (launchPolicy == LaunchPolicy.SAME) { + if (isPiPAvailable && + (args.contains(BREAKPOINTS_ARG) || args.contains(BREAKPOINTS_ARG_SHORT))) { + Log.v(TAG, "Launching in PiP mode because of breakpoints") + newInstance.putExtra(EXTRA_LAUNCH_IN_PIP, true) + } } + + return newInstance + } + + override fun onNewGodotInstanceRequested(args: Array): Int { + val editorWindowInfo = getEditorWindowInfo(args) + + // Launch a new activity + val sourceView = godotFragment?.view + val activityOptions = if (sourceView == null) { + null + } else { + val startX = sourceView.width / 2 + val startY = sourceView.height / 2 + ActivityOptions.makeScaleUpAnimation(sourceView, startX, startY, 0, 0) + } + + val newInstance = getNewGodotInstanceIntent(editorWindowInfo, args) if (editorWindowInfo.windowClassName == javaClass.name) { Log.d(TAG, "Restarting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}") val godot = godot if (godot != null) { godot.destroyAndKillProcess { - ProcessPhoenix.triggerRebirth(this, newInstance) + ProcessPhoenix.triggerRebirth(this, activityOptions?.toBundle(), newInstance) } } else { - ProcessPhoenix.triggerRebirth(this, newInstance) + ProcessPhoenix.triggerRebirth(this, activityOptions?.toBundle(), newInstance) } } else { Log.d(TAG, "Starting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}") newInstance.putExtra(EXTRA_NEW_LAUNCH, true) - startActivity(newInstance) + .putExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD, editorMessageDispatcher.getMessageDispatcherPayload()) + startActivity(newInstance, activityOptions?.toBundle()) } return editorWindowInfo.windowId } @@ -231,6 +286,12 @@ open class GodotEditor : GodotActivity() { return true } + // Send an inter-process message to request the target editor window to force quit. + if (editorMessageDispatcher.requestForceQuit(editorWindowInfo.windowId)) { + return true + } + + // Fallback to killing the target process. val processName = packageName + editorWindowInfo.processNameSuffix val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val runningProcesses = activityManager.runningAppProcesses @@ -285,29 +346,65 @@ open class GodotEditor : GodotActivity() { java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_pan_and_scale_gestures")) /** - * Whether we should launch the new godot instance in an adjacent window - * @see https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT + * Retrieves the play window pip mode editor setting. */ - private fun shouldGameLaunchAdjacent(): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - try { - when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) { - ANDROID_WINDOW_SAME_AS_EDITOR -> false - ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> true - else -> { - // ANDROID_WINDOW_AUTO - isInMultiWindowMode || isLargeScreen - } - } - } catch (e: NumberFormatException) { - // Fall-back to the 'Auto' behavior - isInMultiWindowMode || isLargeScreen - } + private fun getPlayWindowPiPMode(): Int { + return try { + Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/play_window_pip_mode")) + } catch (e: NumberFormatException) { + PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR + } + } + + /** + * If the launch policy is [LaunchPolicy.AUTO], resolve it into a specific policy based on the + * editor setting or device and screen metrics. + * + * If the launch policy is [LaunchPolicy.PIP] but PIP is not supported, fallback to the default + * launch policy. + */ + private fun resolveLaunchPolicyIfNeeded(policy: LaunchPolicy): LaunchPolicy { + val inMultiWindowMode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + isInMultiWindowMode } else { false } + val defaultLaunchPolicy = if (inMultiWindowMode || isLargeScreen) { + LaunchPolicy.ADJACENT + } else { + LaunchPolicy.SAME + } + + return when (policy) { + LaunchPolicy.AUTO -> { + try { + when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) { + ANDROID_WINDOW_SAME_AS_EDITOR -> LaunchPolicy.SAME + ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> LaunchPolicy.ADJACENT + else -> { + // ANDROID_WINDOW_AUTO + defaultLaunchPolicy + } + } + } catch (e: NumberFormatException) { + Log.w(TAG, "Error parsing the Android window placement editor setting", e) + // Fall-back to the default launch policy + defaultLaunchPolicy + } + } + + else -> { + policy + } + } } + /** + * Returns true the if the device supports picture-in-picture (PiP) + */ + protected open fun hasPiPSystemFeature() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && + packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) // Check if we got the MANAGE_EXTERNAL_STORAGE permission diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt index 2bcfba559c5..33fcbf90302 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt @@ -30,6 +30,14 @@ package org.godotengine.editor +import android.annotation.SuppressLint +import android.app.PictureInPictureParams +import android.content.Intent +import android.graphics.Rect +import android.os.Build +import android.os.Bundle +import android.util.Log +import android.view.View import org.godotengine.godot.GodotLib /** @@ -37,7 +45,90 @@ import org.godotengine.godot.GodotLib */ class GodotGame : GodotEditor() { - override fun getGodotAppLayout() = org.godotengine.godot.R.layout.godot_app_layout + companion object { + private val TAG = GodotGame::class.java.simpleName + } + + private val gameViewSourceRectHint = Rect() + private val pipButton: View? by lazy { + findViewById(R.id.godot_pip_button) + } + + private var pipAvailable = false + + @SuppressLint("ClickableViewAccessibility") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val gameView = findViewById(R.id.godot_fragment_container) + gameView?.addOnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> + gameView.getGlobalVisibleRect(gameViewSourceRectHint) + } + } + + pipButton?.setOnClickListener { enterPiPMode() } + + handleStartIntent(intent) + } + + override fun onNewIntent(newIntent: Intent) { + super.onNewIntent(newIntent) + handleStartIntent(newIntent) + } + + private fun handleStartIntent(intent: Intent) { + pipAvailable = intent.getBooleanExtra(EXTRA_PIP_AVAILABLE, pipAvailable) + updatePiPButtonVisibility() + + val pipLaunchRequested = intent.getBooleanExtra(EXTRA_LAUNCH_IN_PIP, false) + if (pipLaunchRequested) { + enterPiPMode() + } + } + + private fun updatePiPButtonVisibility() { + pipButton?.visibility = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && pipAvailable && !isInPictureInPictureMode) { + View.VISIBLE + } else { + View.GONE + } + } + + private fun enterPiPMode() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && pipAvailable) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val builder = PictureInPictureParams.Builder().setSourceRectHint(gameViewSourceRectHint) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + builder.setSeamlessResizeEnabled(false) + } + setPictureInPictureParams(builder.build()) + } + + Log.v(TAG, "Entering PiP mode") + enterPictureInPictureMode() + } + } + + override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode) + Log.v(TAG, "onPictureInPictureModeChanged: $isInPictureInPictureMode") + updatePiPButtonVisibility() + } + + override fun onStop() { + super.onStop() + + val isInPiPMode = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInPictureInPictureMode + if (isInPiPMode && !isFinishing) { + // We get in this state when PiP is closed, so we terminate the activity. + finish() + } + } + + override fun getGodotAppLayout() = R.layout.godot_game_layout + + override fun getEditorId() = RUN_GAME_INFO.windowId override fun overrideOrientationRequest() = false diff --git a/platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml b/platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml new file mode 100644 index 00000000000..41bc5475c8b --- /dev/null +++ b/platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml b/platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml new file mode 100644 index 00000000000..c8b5a15d195 --- /dev/null +++ b/platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml b/platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml new file mode 100644 index 00000000000..aeaa96ce547 --- /dev/null +++ b/platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml b/platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml new file mode 100644 index 00000000000..e9b2959275f --- /dev/null +++ b/platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml b/platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml new file mode 100644 index 00000000000..a8919689feb --- /dev/null +++ b/platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/platform/android/java/editor/src/main/res/layout/godot_game_layout.xml b/platform/android/java/editor/src/main/res/layout/godot_game_layout.xml new file mode 100644 index 00000000000..d53787c87e7 --- /dev/null +++ b/platform/android/java/editor/src/main/res/layout/godot_game_layout.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml b/platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml new file mode 100644 index 00000000000..a3aabf2ee06 --- /dev/null +++ b/platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.png new file mode 100644 index 0000000000000000000000000000000000000000..a5ce40241f55d0a11a41b7e42406b7f09ae245f5 GIT binary patch literal 1954 zcmV;T2VMAyP)J2)tKmpMSh}AlQ=EQH(8+2xYwjfqjAQn|16Q}~2Ko!UY zsz4@C1v0^-B+&5i6f$vbI{6|tlYF_sHeds`V3YM@5s!jEZ!h0ZmZY5}XD?TioAu44 zzNMYqc>ur`Y{K?v>Vx%b7D@{=?T>6yQdvjt-o5)!DXy&Le6jvbqNG4mH)oOBrdCNX z4~v?{R`T_xEXh3Xi&6r;8nK65ynb60KxdbZT&ry)N%<9I$MJHT*iCI*rM6&G=mXgb z-|T$4MJa(6?>$3wI>D#g&`JWf2xGG<&ZLWZC`#GCl67q;Fwzz9^G&N^eNUbc<2~)ixUU zo(puTjR+T~J;Xs@=$_M(ds)Vt&Z^D)qtpgKwOm>=;>;5GaxsZaSTDPY6prO~$M^!0in@6;*e* zJ_O8PJ5+d;TTZM!@Ya&OWCqQo@v9C>`gotbKOH6N;5o?{S(FfH^`Sz;WC8Y!_oDye zP8*))whpeZiT>oTU9N5*AFMefu_wo{5G{HAmsH}T|9xh{F_F(T>4lPeWKlw(ZMkKJ ziDK{g=JzaaBb`W3BA(6J7r6d)M~V&C-lyVV9BYpSbuu`Gh2s+;U^)Vh-$mve;W7RJ=%gexiqqD zWaBq!kWqZi;1_d{D+eUlTcAbSeD0X#CmNY1E$YBndhFvlYW7ykz&*H^wFiW!C+2|i zf*m8l-U8uN;wK5FY{-<1L4c(0&xW?KMVNZeZCm3XnPy~onS=T5Qhi1Y2 zq>~nj!-IR~?LH}btN^H@;Y%pqxhpkfxT!}MdkO?1@TyfXaTY9*1y+i~gG_=AcDV_8 zWRH1d5h_|ak=i;s$)aS-mQ{Da25hx;7+<6b#mOnXZq|dZ-30=E%hE_}RtYJg`wrgj zRo!VJ$Ej#PY|oX9wUr-tmjdIQ{<|6; zGgl`dR;vVZ>I@3zzYb+X1`P>}TjT6ssPp)C`6k3_l|UZTmiQZt1t&?JI4E2!G$zma z+ll)pKLsFG-ZuT=q;~QAlI#doNZCzSPULNs}Ou zi_4&qqXy23E*v^{EAd+NCxKx{XXw0bRGg@yXZsBsjTkk4e&#PPQFC{Hoc`-Z@qTgq zw1xer1nn5$AEE8EadW3JAQr^L#n!j)6XKvP zYdth>Zcoym&r=3aUZMsO=1>O>JsZn?tn!0SAfrOq{iV2mk;807*qoM6N<$g7dkcGynhq literal 0 HcmV?d00001 diff --git a/platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.png new file mode 100644 index 0000000000000000000000000000000000000000..147adb6127f23332d308425f49d1c3d080f87a93 GIT binary patch literal 1350 zcmV-M1-bf(P)6i25dwY9tyA`tA*4UH$axeFu$M1Lh zo%7vsr%el(A6s0UE+|S{eV;FZ9E7|kH?8(w3V*RX;=^-%X1(DtjaC<;NG_i33t(;J z;EJ1TvpZOW8<9{7<^ zyT`2nwpldsKh0F9=>EYVRche~!MHM2=M!LU_yqK%rI5*AuAW`BB?jQ7ZCF1S~;JqlQ{|S z(_dir8*3DNu1*#ZAQ1urgbBS3uLyWDO%E~Ol*4OVD?CQ@*JU(Lm`eyr1w6O081~gP zf~9{528KppaCp>Xpud&G?K{;7i#C>sa!W2?!GCr)ceoae4@ zA?KIcEh2x!vxHn*S09Z@(wVsH+fQ8JaWj|EG3uL0ik|~s1brG7( z-Six%5`SH5()Cc=WQMY$o)FcNL_m7cS+3}dn}30Zwl26%mXvj1 z2!=<;=y_{bFJ0&SxTn$$u<)y5dX9aBM-8GTDaaoi% zv{CYSo_NRvi?ff2ViB)?TLJl}o8Sx5FBp%aF<&0ZEA*8_K!K^n(RlVJikPSe_}F@k znP6;uNB1E8H|7=kPPqVF*VXp%cewtl({8%@z$w^mydokY1oI0tLyEqR#v9IFqxpVq zydl~Trd&WwRyp+E9;O9%8818C!{XQz(OG2^;%Tjc>j4Dj!F;$oV^2sb;3YzE%x0qn zamldP@!bYDv<|W?CPPi5nFw(Z%|3dbYh=tXsere(J4Wb7`)lYm9HV#gT7iCNS~_SP z#($hw!`*wEjA%&(;Amuv;ZuwvBx;+@loY~5-fMCy|8xse3nP^Jwsg{X{wQ5na7J1I zpYQ~1DEXZ}NtRI6rFMwiRO;j}1l~V+@j4gt8xgE6cI+$hKT9kCXM4{e)5QEGf$Kjq zb7Jx=&<`h>&E88a`biVNBG@~oTmWL_9wT)248j0;ukX7(0%Z-Y+zXwjK>yNntu!9< z;GTeag?^FWR)n^?X@bB@_Hi@^Lp^; zO>i|6_F6;V-&7hrYc4LIpos;bF%HuSo|qw1#>`Ykzy7E)(gC@|;MKy~Gh`95$c1v6 zlss$bD3?D#&VmUZ@iBra7mvyF44Xys!T0z367Cx|VE$qM0!8+|iD~ZA=>Px#07*qo IM6N<$g2L;Q=l}o! literal 0 HcmV?d00001 diff --git a/platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.png new file mode 100644 index 0000000000000000000000000000000000000000..0b1db1b92300b2855b88f2b90465f4fdc949351c GIT binary patch literal 2617 zcmV-93dZ$`P)q1ZyLwiD^)!3LeZ9=rMJ#Axadu)6})2Im`R!wWw z7&Y+`;v)t%;R422(5Q&f_#joPpn#DmA{d^6fGA+6^9^_L-kH7k!ewC>ng2PzbCI1p z^PlhAo!OmT+-}`u$dDmJh71`pWXR}55gU7VWMo2jNkVsM!OPQb(cGvEo9GZ-B4Q8R zqx>8;am{yvW0U#^#U>35j!N+9im<*@4WNaZMw*7=Zbd%s2-;1C4MhT;VX+pILp0)X$?;qdtw$`pqp;TguHqT>wepEs| zZR~#`{2tK7VVXpl>rHQdO#G*;Tx6}C>tQ*)@*O?72dzAV4Leb0I4&e0No>|8a9UiZ zcR+-@wT3Q*;-KFvD|~3hWvDzjI^mYku!O<1p`%3LjP>OYMNam&*3hM}eEF^)O;`{} z53%azMpPP?SQ+rzhMEVToiqg6X<@+K1II@PNdugt7!WXHLv{aA6NW-NEez=HGj^s) z8sMDUN5J};ek1qZ;pw5|WG-{lL~U{)-7E?+yA*x zLSB4tuaKWBbQ%Nxvf&^ptf&?Q(TjreYVz{h145pyrBfL2%-boXxT;nVL2s^Hts$cp ze_V0!pLa_! zya7`-9Tqy>NI6O8g7K^O3+3C3->ykzlQ5qPn6NHQDBn^HZ9v$ZB(?|g9X@lpoILrj z9YT5Ri2&c@x3z>vs1W!*KKX6nsf9a9c438(Px4oJcE95cLmLo5MgF!@9Q!A%756v@ zS5#3$how5Q>C0SUyE8^D-bMDGDIlfSt~1l^oofE|!!Lw=U4u&H&qrtWl;=DAU=E|3A35GTxo{r^wM^~k0v28zHu#L?T3K$v`19oOywAltA zGGD(nzk5HGnSdh!!x82LzKp`!v}_?Sf1xubmkeSk8P|XBgIi!Bhkc1AIqEFZqlN zotw-iV}6?>7VbJJwBagamZk{x@6Re=IrC+mVORrjiC;u7^{qQu-_Xd08l2~2nfRSO zr<+-^H3$q?V4k)}6!s;9(Ay6L4{qT~tBJu3gsz)P*|vb`*(%o$Dvo5a%s1tSb?SY{*m-M`1oASsRx$|WmVMqg}#EZ8f zHy+MaU2A#vo!um_xKb#u6{s6+@a^JMbn}sOLV4uQmvw|84TwuSD|GAw`a8ZH+w+UH z-}8C$ig6@}!x3`g%f5f$j8M)IhBV;#dE2#lFpIm$f%q3ymIFXteFIw&bp?EvuMb=- z6-`*ML%d&cjONbw#}URffE!!E@PFSvD|v3&9@VuNfScyHVUPL36`oqSgX9#6H{JmZ z|0Qi($pg=e@@0@a-ycVG8UvKc?YZT9>`u5@1?4tIxe8WX@#F(Yen zcZ31^vc#)k+@r;nrrJ{y0DJxj4a8T#lC!-d0Xbm(zpT1W*oFl|zAi@?(txR(4h!q; zj=pX@u0O00EmYs1J;PJ!^Dn1_E<-e;nxDGgF=dnZu?LYT-YCl&|Huc{%uY_?{Y4NHb4QL z!;=Wn?9YXcuv108Oh*ADkVABGI{Ev@N7*AUe3{nJDGa!ikp}3DG(cyh0XicM&>3lf z&PW4vMjD_q(g2;22I!15Kxd=@IwK9x8N~qn=iQnbFyN&(LZkuCu{i>set8(Q)5L&> zB4_xUqRr*aji@v(u{r{#e^Alq$(O>QU7HNBynds*dv9NF)9fu-@)agdfHqX4L6er} z-g|%le$bBY#ceQvMp)QHyrj%?(BGD?G({z#p?Xu4S-!-F!=g!j@T_>^Gd^kqG_z>e zwy;#V`ln!36&U|L*lz^idJ?6WIj>9haT)m6}9zzeT!@$?BC360#{ zd*elJ+Pq265^%CdFRust(obJX@Vy>C2){4j@#nolhmZ3%MU3r=c!frcg9cQKpFk66 zW6`LGMKetUXGA^S+^yh~ugD7}kR#yfZJPb{s4gI-iwh+H b+!^tI>^a+11=rSi00000NkvXXu0mjf$TkK* literal 0 HcmV?d00001 diff --git a/platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.png new file mode 100644 index 0000000000000000000000000000000000000000..39d745039076f258d651bcba8110dab63f3a2551 GIT binary patch literal 3923 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

oh|Etx)b+nuXKHLe9wh zxn#bBh}9@WLZ%;jpQ)`c^m7ttMse7g$3{Tq#FXa5W52#sCC(W`HxM!yb7Tz-t*R*+YD zO$%D(P6I@BR0DxlAHL;qFdioMq;giELF}Qje(dsv?=BZ-cwt(F{`pLVVkw0?aan-P zlw`WvtOs5fUSyqoTPh zc^)`Z<(J*HL@1HB5+XZZYg#*V)KwwZhCq*thi#Onu z8y4hvCR2)L^O)zHmu{ocsL=^IdwL8f^5Jl3fYV z3d7e;7YL6>tTa=(;#rR7^HSDI6&+8%x5zuf30N8f{f7s;xPoa4s{||gi*5PtUlP^2 zb>`$!geBa^=SNPP$QvwfM;j<8rS<;=NEqNuy-YQ*-53~ef@Tzd^5Nu-eS>{mroOIb zMMA=*)8Q{)r$EMwtUMxs9x-iA-dN*OmD-iT41wa%NA2^D4X(MJ6t__Q6Yq~>`a{Gu zxT#sc^C^}}=dfPBdBO};(t=^Q_tohAnrPjDMMGY* zKAC3!0WQQh`B3CX*`8_kvIva(_S{~c^z>XBS=VMr3TpbM1I2f`zeDr7=nh2_;rz44 zU;lPDCjOH&W_a0s97j=W87-A2Jnnz}*$xJQx?dlHRzDR*AS83ctLmwHx6tm>1wzhi zemQeLxNT2Z9LB1w%O2m~$y*U6<5w=4>k8#}rsa$-v!HX#622=rZsU#pSq((TK`KQP zbyL%z{#5eb=t4fqw)1B9Pyt+5|qXZ6x6MoXT z(W*8mrnDO#h`88sDSBoOt;Q4no}xL|z+QPJ}qaVGEE;sF>vBWG8#Q2RuUs?kvgWkQAMLRcEXf zzbGydj2^(Dd^(ZBu2e(NQB=3ky{E9gm@eH%0{=sf*50MiO^W$-_u1JhU5I5WLp`)^ Jg|=hVe*v>FM2`Rf literal 0 HcmV?d00001 diff --git a/platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.png new file mode 100644 index 0000000000000000000000000000000000000000..b7a09a15b50c101541aef7e03717a7145f3792d4 GIT binary patch literal 5480 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 Pr~p-EEv0G&%i#Y4gDO5y literal 0 HcmV?d00001 diff --git a/platform/android/java/editor/src/main/res/values/dimens.xml b/platform/android/java/editor/src/main/res/values/dimens.xml index 98bfe401797..1e486872e6f 100644 --- a/platform/android/java/editor/src/main/res/values/dimens.xml +++ b/platform/android/java/editor/src/main/res/values/dimens.xml @@ -1,5 +1,5 @@ - 600dp + 640dp 1024dp diff --git a/platform/android/java/editor/src/main/res/values/strings.xml b/platform/android/java/editor/src/main/res/values/strings.xml index 909711ab180..0ad54ac3a13 100644 --- a/platform/android/java/editor/src/main/res/values/strings.xml +++ b/platform/android/java/editor/src/main/res/values/strings.xml @@ -1,4 +1,6 @@ + Godot Play window Missing storage access permission! + Button used to toggle picture-in-picture mode for the Play window 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 913e3d04c5d..474c6e9b2fb 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt @@ -52,8 +52,6 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { companion object { private val TAG = GodotActivity::class.java.simpleName - @JvmStatic - protected val EXTRA_FORCE_QUIT = "force_quit_requested" @JvmStatic protected val EXTRA_NEW_LAUNCH = "new_launch_requested" } @@ -128,12 +126,6 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { } private fun handleStartIntent(intent: Intent, newLaunch: Boolean) { - val forceQuitRequested = intent.getBooleanExtra(EXTRA_FORCE_QUIT, false) - if (forceQuitRequested) { - Log.d(TAG, "Force quit requested, terminating..") - ProcessPhoenix.forceQuit(this) - return - } if (!newLaunch) { val newLaunchRequested = intent.getBooleanExtra(EXTRA_NEW_LAUNCH, false) if (newLaunchRequested) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java b/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java index b1bce45fbba..d9afdf90b13 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java @@ -24,6 +24,7 @@ package org.godotengine.godot.utils; import android.app.Activity; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -44,6 +45,9 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; */ public final class ProcessPhoenix extends Activity { private static final String KEY_RESTART_INTENTS = "phoenix_restart_intents"; + // -- GODOT start -- + private static final String KEY_RESTART_ACTIVITY_OPTIONS = "phoenix_restart_activity_options"; + // -- GODOT end -- private static final String KEY_MAIN_PROCESS_PID = "phoenix_main_process_pid"; /** @@ -56,12 +60,23 @@ public final class ProcessPhoenix extends Activity { triggerRebirth(context, getRestartIntent(context)); } + // -- GODOT start -- /** * Call to restart the application process using the specified intents. *

* Behavior of the current process after invoking this method is undefined. */ public static void triggerRebirth(Context context, Intent... nextIntents) { + triggerRebirth(context, null, nextIntents); + } + + /** + * Call to restart the application process using the specified intents launched with the given + * {@link ActivityOptions}. + *

+ * Behavior of the current process after invoking this method is undefined. + */ + public static void triggerRebirth(Context context, Bundle activityOptions, Intent... nextIntents) { if (nextIntents.length < 1) { throw new IllegalArgumentException("intents cannot be empty"); } @@ -72,10 +87,12 @@ public final class ProcessPhoenix extends Activity { intent.addFlags(FLAG_ACTIVITY_NEW_TASK); // In case we are called with non-Activity context. intent.putParcelableArrayListExtra(KEY_RESTART_INTENTS, new ArrayList<>(Arrays.asList(nextIntents))); intent.putExtra(KEY_MAIN_PROCESS_PID, Process.myPid()); + if (activityOptions != null) { + intent.putExtra(KEY_RESTART_ACTIVITY_OPTIONS, activityOptions); + } context.startActivity(intent); } - // -- GODOT start -- /** * Finish the activity and kill its process */ @@ -112,9 +129,11 @@ public final class ProcessPhoenix extends Activity { super.onCreate(savedInstanceState); // -- GODOT start -- - ArrayList intents = getIntent().getParcelableArrayListExtra(KEY_RESTART_INTENTS); - startActivities(intents.toArray(new Intent[intents.size()])); - forceQuit(this, getIntent().getIntExtra(KEY_MAIN_PROCESS_PID, -1)); + Intent launchIntent = getIntent(); + ArrayList intents = launchIntent.getParcelableArrayListExtra(KEY_RESTART_INTENTS); + Bundle activityOptions = launchIntent.getBundleExtra(KEY_RESTART_ACTIVITY_OPTIONS); + startActivities(intents.toArray(new Intent[intents.size()]), activityOptions); + forceQuit(this, launchIntent.getIntExtra(KEY_MAIN_PROCESS_PID, -1)); // -- GODOT end -- }