You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-11 13:10:58 +00:00
Add support for running hybrid apps from the XR editor
This commit is contained in:
@@ -60,6 +60,22 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Intent filter used to intercept hybrid PANEL launch for the current editor project, and route it
|
||||
properly through the editor 'run' logic (e.g: debugger setup) -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="org.godotengine.xr.hybrid.PANEL" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Intent filter used to intercept hybrid IMMERSIVE launch for the current editor project, and route it
|
||||
properly through the editor 'run' logic (e.g: debugger setup) -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="org.godotengine.xr.hybrid.IMMERSIVE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".GodotGame"
|
||||
@@ -101,8 +117,7 @@
|
||||
android:autoRemoveFromRecents="true"
|
||||
android:screenOrientation="landscape"
|
||||
android:resizeableActivity="false"
|
||||
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
|
||||
</activity>
|
||||
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" />
|
||||
|
||||
<!--
|
||||
We remove this meta-data originating from the vendors plugin as we only need the loader for
|
||||
|
||||
@@ -60,14 +60,19 @@ import org.godotengine.editor.utils.verifyApk
|
||||
import org.godotengine.godot.BuildConfig
|
||||
import org.godotengine.godot.GodotActivity
|
||||
import org.godotengine.godot.GodotLib
|
||||
import org.godotengine.godot.editor.utils.EditorUtils
|
||||
import org.godotengine.godot.editor.utils.GameMenuUtils
|
||||
import org.godotengine.godot.editor.utils.GameMenuUtils.GameEmbedMode
|
||||
import org.godotengine.godot.editor.utils.GameMenuUtils.fetchGameEmbedMode
|
||||
import org.godotengine.godot.error.Error
|
||||
import org.godotengine.godot.utils.DialogUtils
|
||||
import org.godotengine.godot.utils.GameMenuUtils
|
||||
import org.godotengine.godot.utils.GameMenuUtils.GameEmbedMode
|
||||
import org.godotengine.godot.utils.GameMenuUtils.fetchGameEmbedMode
|
||||
import org.godotengine.godot.utils.PermissionsUtil
|
||||
import org.godotengine.godot.utils.ProcessPhoenix
|
||||
import org.godotengine.godot.utils.isNativeXRDevice
|
||||
import org.godotengine.godot.xr.HybridMode
|
||||
import org.godotengine.godot.xr.getHybridAppLaunchMode
|
||||
import org.godotengine.godot.xr.HYBRID_APP_PANEL_CATEGORY
|
||||
import org.godotengine.godot.xr.HYBRID_APP_IMMERSIVE_CATEGORY
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
@@ -98,6 +103,8 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
private const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager"
|
||||
private const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p"
|
||||
internal const val XR_MODE_ARG = "--xr-mode"
|
||||
private const val SCENE_ARG = "--scene"
|
||||
private const val PATH_ARG = "--path"
|
||||
|
||||
// Info for the various classes used by the editor.
|
||||
internal val EDITOR_MAIN_INFO = EditorWindowInfo(GodotEditor::class.java, 777, "")
|
||||
@@ -236,6 +243,50 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
setupGameMenuBar()
|
||||
}
|
||||
|
||||
override fun onNewIntent(newIntent: Intent) {
|
||||
if (newIntent.hasCategory(HYBRID_APP_PANEL_CATEGORY) || newIntent.hasCategory(HYBRID_APP_IMMERSIVE_CATEGORY)) {
|
||||
val params = newIntent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
|
||||
Log.d(TAG, "Received hybrid transition intent $newIntent with parameters ${params.contentToString()}")
|
||||
// Override EXTRA_NEW_LAUNCH so the editor is not restarted
|
||||
newIntent.putExtra(EXTRA_NEW_LAUNCH, false)
|
||||
|
||||
godot?.runOnRenderThread {
|
||||
// Look for the scene and xr-mode arguments
|
||||
var scene = ""
|
||||
var xrMode = XR_MODE_DEFAULT
|
||||
var path = ""
|
||||
if (params != null) {
|
||||
val sceneIndex = params.indexOf(SCENE_ARG)
|
||||
if (sceneIndex != -1 && sceneIndex + 1 < params.size) {
|
||||
scene = params[sceneIndex +1]
|
||||
}
|
||||
|
||||
val xrModeIndex = params.indexOf(XR_MODE_ARG)
|
||||
if (xrModeIndex != -1 && xrModeIndex + 1 < params.size) {
|
||||
xrMode = params[xrModeIndex + 1]
|
||||
}
|
||||
|
||||
val pathIndex = params.indexOf(PATH_ARG)
|
||||
if (pathIndex != -1 && pathIndex + 1 < params.size) {
|
||||
path = params[pathIndex + 1]
|
||||
}
|
||||
}
|
||||
|
||||
val sceneArgs = mutableSetOf(XR_MODE_ARG, xrMode).apply {
|
||||
if (path.isNotEmpty() && scene.isEmpty()) {
|
||||
add(PATH_ARG)
|
||||
add(path)
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Running scene $scene with arguments: $sceneArgs")
|
||||
EditorUtils.runScene(scene, sceneArgs.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
super.onNewIntent(newIntent)
|
||||
}
|
||||
|
||||
protected open fun shouldShowGameMenuBar() = gameMenuContainer != null
|
||||
|
||||
private fun setupGameMenuBar() {
|
||||
@@ -327,26 +378,41 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
}
|
||||
}
|
||||
|
||||
return if (hasEditor) {
|
||||
EDITOR_MAIN_INFO
|
||||
} else {
|
||||
// Launching a game.
|
||||
val openxrEnabled = xrMode == XR_MODE_ON ||
|
||||
(xrMode == XR_MODE_DEFAULT && GodotLib.getGlobal("xr/openxr/enabled").toBoolean())
|
||||
if (openxrEnabled && isNativeXRDevice(applicationContext)) {
|
||||
XR_RUN_GAME_INFO
|
||||
} else {
|
||||
if (godot?.isProjectManagerHint() == true || isNativeXRDevice(applicationContext)) {
|
||||
if (hasEditor) {
|
||||
return EDITOR_MAIN_INFO
|
||||
}
|
||||
|
||||
// Launching a game.
|
||||
if (isNativeXRDevice(applicationContext)) {
|
||||
if (xrMode == XR_MODE_ON) {
|
||||
return XR_RUN_GAME_INFO
|
||||
}
|
||||
|
||||
if ((xrMode == XR_MODE_DEFAULT && GodotLib.getGlobal("xr/openxr/enabled").toBoolean())) {
|
||||
val hybridLaunchMode = getHybridAppLaunchMode()
|
||||
|
||||
return if (hybridLaunchMode == HybridMode.PANEL) {
|
||||
RUN_GAME_INFO
|
||||
} else {
|
||||
val resolvedEmbedMode = resolveGameEmbedModeIfNeeded(gameEmbedMode)
|
||||
if (resolvedEmbedMode == GameEmbedMode.DISABLED) {
|
||||
RUN_GAME_INFO
|
||||
} else {
|
||||
EMBEDDED_RUN_GAME_INFO
|
||||
}
|
||||
XR_RUN_GAME_INFO
|
||||
}
|
||||
}
|
||||
|
||||
// Native XR devices don't support embed mode yet.
|
||||
return RUN_GAME_INFO
|
||||
}
|
||||
|
||||
// Project manager doesn't support embed mode.
|
||||
if (godot?.isProjectManagerHint() == true) {
|
||||
return RUN_GAME_INFO
|
||||
}
|
||||
|
||||
// Check for embed mode launch.
|
||||
val resolvedEmbedMode = resolveGameEmbedModeIfNeeded(gameEmbedMode)
|
||||
return if (resolvedEmbedMode == GameEmbedMode.DISABLED) {
|
||||
RUN_GAME_INFO
|
||||
} else {
|
||||
EMBEDDED_RUN_GAME_INFO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,6 +692,7 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
return verifyApk(godot.fileAccessHandler, apkPath)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun supportsFeature(featureTag: String): Boolean {
|
||||
if (featureTag == "xr_editor") {
|
||||
return isNativeXRDevice(applicationContext)
|
||||
@@ -639,11 +706,12 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
return BuildConfig.FLAVOR == "picoos"
|
||||
}
|
||||
|
||||
return false
|
||||
return super.supportsFeature(featureTag)
|
||||
}
|
||||
|
||||
internal fun onEditorConnected(connectedEditorId: Int) {
|
||||
when (connectedEditorId) {
|
||||
internal fun onEditorConnected(editorId: Int) {
|
||||
Log.d(TAG, "Editor $editorId connected!")
|
||||
when (editorId) {
|
||||
EMBEDDED_RUN_GAME_INFO.windowId, RUN_GAME_INFO.windowId -> {
|
||||
runOnUiThread {
|
||||
embeddedGameViewContainerWindow?.isVisible = false
|
||||
@@ -652,12 +720,16 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
|
||||
|
||||
XR_RUN_GAME_INFO.windowId -> {
|
||||
runOnUiThread {
|
||||
updateEmbeddedGameView(true, false)
|
||||
updateEmbeddedGameView(gameRunning = true, gameEmbedded = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun onEditorDisconnected(editorId: Int) {
|
||||
Log.d(TAG, "Editor $editorId disconnected!")
|
||||
}
|
||||
|
||||
private fun updateEmbeddedGameView(gameRunning: Boolean, gameEmbedded: Boolean) {
|
||||
if (gameRunning) {
|
||||
embeddedGameStateLabel?.apply {
|
||||
|
||||
@@ -35,9 +35,11 @@ import android.util.Log
|
||||
import androidx.annotation.CallSuper
|
||||
import org.godotengine.godot.Godot
|
||||
import org.godotengine.godot.GodotLib
|
||||
import org.godotengine.godot.utils.GameMenuUtils
|
||||
import org.godotengine.godot.editor.utils.GameMenuUtils
|
||||
import org.godotengine.godot.utils.PermissionsUtil
|
||||
import org.godotengine.godot.utils.ProcessPhoenix
|
||||
import org.godotengine.godot.xr.HYBRID_APP_FEATURE
|
||||
import org.godotengine.godot.xr.isHybridAppEnabled
|
||||
|
||||
/**
|
||||
* Base class for the Godot play windows.
|
||||
@@ -101,4 +103,14 @@ abstract class BaseGodotGame: GodotEditor() {
|
||||
}
|
||||
|
||||
protected open fun getEditorGameEmbedMode() = GameMenuUtils.GameEmbedMode.AUTO
|
||||
|
||||
@CallSuper
|
||||
override fun supportsFeature(featureTag: String): Boolean {
|
||||
if (HYBRID_APP_FEATURE == featureTag) {
|
||||
// Check if hybrid is enabled
|
||||
return isHybridAppEnabled()
|
||||
}
|
||||
|
||||
return super.supportsFeature(featureTag)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,9 @@ internal class EditorMessageDispatcher(private val editor: BaseGodotEditor) {
|
||||
MSG_REGISTER_MESSENGER -> {
|
||||
val editorId = msg.arg1
|
||||
val messenger = msg.replyTo
|
||||
registerMessenger(editorId, messenger)
|
||||
registerMessenger(editorId, messenger) {
|
||||
editor.onEditorDisconnected(editorId)
|
||||
}
|
||||
}
|
||||
|
||||
MSG_DISPATCH_GAME_MENU_ACTION -> {
|
||||
@@ -211,8 +213,8 @@ internal class EditorMessageDispatcher(private val editor: BaseGodotEditor) {
|
||||
} else if (messenger.binder.isBinderAlive) {
|
||||
messenger.binder.linkToDeath({
|
||||
Log.v(TAG, "Removing messenger for $editorId")
|
||||
cleanEditorConnection(editorId)
|
||||
messengerDeathCallback?.run()
|
||||
cleanEditorConnection(editorId)
|
||||
}, 0)
|
||||
editorConnectionsInfos[editorId] = EditorConnectionInfo(messenger)
|
||||
editor.onEditorConnected(editorId)
|
||||
@@ -234,7 +236,8 @@ internal class EditorMessageDispatcher(private val editor: BaseGodotEditor) {
|
||||
/**
|
||||
* 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.
|
||||
* This is done so that the host can send request (e.g: force-quit when the host exits) 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.
|
||||
|
||||
@@ -37,12 +37,17 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.core.view.isVisible
|
||||
import org.godotengine.editor.embed.GameMenuFragment
|
||||
import org.godotengine.godot.utils.GameMenuUtils
|
||||
import org.godotengine.godot.GodotLib
|
||||
import org.godotengine.godot.editor.utils.GameMenuUtils
|
||||
import org.godotengine.godot.utils.ProcessPhoenix
|
||||
import org.godotengine.godot.utils.isHorizonOSDevice
|
||||
import org.godotengine.godot.utils.isNativeXRDevice
|
||||
import org.godotengine.godot.xr.HYBRID_APP_PANEL_FEATURE
|
||||
import org.godotengine.godot.xr.XRMode
|
||||
import org.godotengine.godot.xr.isHybridAppEnabled
|
||||
|
||||
/**
|
||||
* Drives the 'run project' window of the Godot Editor.
|
||||
@@ -82,6 +87,18 @@ open class GodotGame : BaseGodotGame() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCommandLine(): MutableList<String> {
|
||||
val updatedArgs = super.getCommandLine()
|
||||
if (!updatedArgs.contains(XRMode.REGULAR.cmdLineArg)) {
|
||||
updatedArgs.add(XRMode.REGULAR.cmdLineArg)
|
||||
}
|
||||
if (!updatedArgs.contains(XR_MODE_ARG)) {
|
||||
updatedArgs.add(XR_MODE_ARG)
|
||||
updatedArgs.add("off")
|
||||
}
|
||||
return updatedArgs
|
||||
}
|
||||
|
||||
override fun enterPiPMode() {
|
||||
if (hasPiPSystemFeature()) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
@@ -245,4 +262,19 @@ open class GodotGame : BaseGodotGame() {
|
||||
expandGameMenuButton?.isVisible = shouldShowGameMenuBar() && isMenuBarCollapsable() && collapsed
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun supportsFeature(featureTag: String): Boolean {
|
||||
if (HYBRID_APP_PANEL_FEATURE == featureTag) {
|
||||
// Check if openxr is enabled
|
||||
if (!GodotLib.getGlobal("xr/openxr/enabled").toBoolean()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if hybrid is enabled
|
||||
return isHybridAppEnabled()
|
||||
}
|
||||
|
||||
return super.supportsFeature(featureTag)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
|
||||
import android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
|
||||
import org.godotengine.editor.GodotGame
|
||||
import org.godotengine.editor.R
|
||||
import org.godotengine.godot.utils.GameMenuUtils
|
||||
import org.godotengine.godot.editor.utils.GameMenuUtils
|
||||
|
||||
/**
|
||||
* Host the Godot game from the editor when the embedded mode is enabled.
|
||||
|
||||
Reference in New Issue
Block a user