Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for running hybrid apps from the XR editor #103972

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion platform/android/java/editor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ android {
}

dependencies {
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])

implementation "androidx.fragment:fragment:$versions.fragmentVersion"
implementation project(":lib")

Expand All @@ -188,7 +190,9 @@ dependencies {
implementation "org.bouncycastle:bcprov-jdk15to18:1.78"

// Meta dependencies
horizonosImplementation "org.godotengine:godot-openxr-vendors-meta:$versions.openxrVendorsVersion"
// TODO: Revert when official vendors plugin dependency is updated.
horizonosImplementation "io.github.m4gr3d:godot-openxr-vendors-meta:4.0.0-dev-SNAPSHOT"
// horizonosImplementation "org.godotengine:godot-openxr-vendors-meta:$versions.openxrVendorsVersion"
// Pico dependencies
picoosImplementation "org.godotengine:godot-openxr-vendors-pico:$versions.openxrVendorsVersion"
}
11 changes: 11 additions & 0 deletions platform/android/java/editor/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@
<layout
android:defaultWidth="@dimen/editor_default_window_width"
android:defaultHeight="@dimen/editor_default_window_height" />

<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>
</activity>
<activity
android:name=".embed.EmbeddedGodotGame"
Expand All @@ -97,6 +103,11 @@
android:screenOrientation="landscape"
android:resizeableActivity="false"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
<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>
</application>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ 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 kotlin.math.min

/**
Expand Down Expand Up @@ -322,26 +324,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
}
}

Expand Down Expand Up @@ -626,6 +643,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)
Expand All @@ -639,7 +657,7 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
return BuildConfig.FLAVOR == "picoos"
}

return false
return super.supportsFeature(featureTag)
}

internal fun onEditorConnected(connectedEditorId: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import org.godotengine.godot.GodotLib
import org.godotengine.godot.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.
Expand Down Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@ 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.GodotLib
import org.godotengine.godot.utils.GameMenuUtils
import org.godotengine.godot.utils.ProcessPhoenix
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.
Expand Down Expand Up @@ -81,6 +86,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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && hasPiPSystemFeature()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Expand Down Expand Up @@ -238,4 +255,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)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
private val TAG = GodotActivity::class.java.simpleName

@JvmStatic
protected val EXTRA_COMMAND_LINE_PARAMS = "command_line_params"
val EXTRA_COMMAND_LINE_PARAMS = "command_line_params"

@JvmStatic
protected val EXTRA_NEW_LAUNCH = "new_launch_requested"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**************************************************************************/
/* HybridAppUtils.kt */
/**************************************************************************/
/* 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. */
/**************************************************************************/

/**
* Contains utility methods and constants for hybrid apps.
*/
@file:JvmName("HybridAppUtils")

package org.godotengine.godot.xr

import android.util.Log
import org.godotengine.godot.GodotLib

private const val TAG = "HybridAppUtils"

enum class HybridMode(private val nativeValue: Int) {
NONE( -1),
IMMERSIVE(0),
PANEL(1);

companion object {
fun fromNative(nativeValue: Int): HybridMode {
for (mode in HybridMode.entries) {
if (mode.nativeValue == nativeValue) {
return mode
}
}
return NONE
}
}
}

const val HYBRID_APP_FEATURE = "godot_openxr_hybrid_app"
const val HYBRID_APP_PANEL_FEATURE = "godot_openxr_panel_app"
const val HYBRID_APP_PANEL_CATEGORY = "org.godotengine.xr.hybrid.PANEL"
const val HYBRID_APP_IMMERSIVE_CATEGORY = "org.godotengine.xr.hybrid.IMMERSIVE"

fun isHybridAppEnabled() = GodotLib.getGlobal("xr/hybrid_app/enabled").toBoolean()

fun getHybridAppLaunchMode(): HybridMode {
if (!isHybridAppEnabled()) {
return HybridMode.NONE
}

try {
val launchModeValue = GodotLib.getGlobal("xr/hybrid_app/launch_mode").toInt()
return HybridMode.fromNative(launchModeValue)
} catch (e: Exception) {
Log.w(TAG, "Unable to retrieve 'xr/hybrid_app/launch_mode' project setting", e)
return HybridMode.NONE
}
}