diff --git a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt
index 3714ff0f9..8ca903c30 100644
--- a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt
+++ b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt
@@ -521,8 +521,8 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
releaseDrawerDirection()
}
}
- LaunchedEffect(cardboard.enabled, steamVrSwitchedPicture) {
- while (cardboard.enabled || steamVrSwitchedPicture) {
+ LaunchedEffect(cardboard.enabled, steamVrSwitchedPicture, drawerOpen, drawerPage) {
+ while (cardboard.enabled || steamVrSwitchedPicture || (drawerOpen && drawerPage == SessionDrawerPage.STEAMVR)) {
val now = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
SteamVr.status(this@SessionActivity)
}
@@ -559,6 +559,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
onCardboard = { c -> SessionPrefs.setCardboard(this@SessionActivity, c); applyCardboard(c) },
onCardboardRecenter = { headTracker?.recenter() },
steamVr = steamVrStatus,
+ immersiveSteamVr = com.droiddeck.launcher.session.QuestExperimental.isSupported(),
onSteamVr = { action -> steamVrAction(action) },
textureAnisotropy = if (drawerGameProfileId == null) textureAnisotropy else drawerTextureAnisotropy,
textureLodBias = if (drawerGameProfileId == null) textureLodBias else drawerTextureLodBias,
diff --git a/app/src/main/java/com/droiddeck/launcher/ui/SessionDrawerPage.kt b/app/src/main/java/com/droiddeck/launcher/ui/SessionDrawerPage.kt
index 898900f45..576587f7b 100644
--- a/app/src/main/java/com/droiddeck/launcher/ui/SessionDrawerPage.kt
+++ b/app/src/main/java/com/droiddeck/launcher/ui/SessionDrawerPage.kt
@@ -5,6 +5,8 @@ internal enum class SessionDrawerPage(val firstControl: String) {
SECOND_SCREEN("second-screen:none"),
DISPLAY("hud"),
EFFECTS("look"),
+ STEAMVR("steamvr"),
+ CARDBOARD("cardboard"),
GAMES("anisotropy"),
SESSION("suspend"),
}
diff --git a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt
index 775804a62..c784d7a36 100644
--- a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt
+++ b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt
@@ -42,6 +42,8 @@ import androidx.compose.material.icons.outlined.PictureInPictureAlt
import androidx.compose.material.icons.automirrored.outlined.ExitToApp
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.SportsEsports
+import androidx.compose.material.icons.outlined.ViewInAr
+import androidx.compose.material.icons.outlined.Vrpano
import androidx.compose.material3.Icon
import androidx.compose.material3.RadioButton
import androidx.compose.material3.RadioButtonDefaults
@@ -151,6 +153,8 @@ private val SessionDrawerPage.title: Int get() = when (this) {
SessionDrawerPage.CONTROLLER -> R.string.drawer_page_controller
SessionDrawerPage.SECOND_SCREEN -> R.string.drawer_second_screen
SessionDrawerPage.DISPLAY -> R.string.drawer_page_display
+ SessionDrawerPage.STEAMVR -> R.string.steamvr_title
+ SessionDrawerPage.CARDBOARD -> R.string.cardboard_title
SessionDrawerPage.EFFECTS -> R.string.drawer_page_effects
SessionDrawerPage.GAMES -> R.string.game_settings_title
SessionDrawerPage.SESSION -> R.string.mode_session
@@ -160,6 +164,8 @@ private val SessionDrawerPage.icon get() = when (this) {
SessionDrawerPage.CONTROLLER -> Icons.Outlined.SportsEsports
SessionDrawerPage.SECOND_SCREEN -> Icons.Outlined.ScreenShare
SessionDrawerPage.DISPLAY -> Icons.Outlined.DesktopWindows
+ SessionDrawerPage.STEAMVR -> Icons.Outlined.ViewInAr
+ SessionDrawerPage.CARDBOARD -> Icons.Outlined.Vrpano
SessionDrawerPage.EFFECTS -> Icons.Outlined.AutoFixHigh
SessionDrawerPage.GAMES -> Icons.Outlined.Layers
SessionDrawerPage.SESSION -> Icons.Outlined.Settings
@@ -209,6 +215,7 @@ class DrawerActions(
val cardboard: Cardboard = Cardboard.OFF,
val cardboardSensor: Boolean = false,
val steamVr: SteamVr.Status? = null,
+ val immersiveSteamVr: Boolean = false,
val textureAnisotropy: Int? = 0,
val textureLodBias: String? = TextureFiltering.LOD_BIAS_OFF,
val secondScreenMode: SecondScreenMode,
@@ -452,12 +459,13 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont
stringResource(R.string.display_sharpness), null, a.upscaleSharpness, 0..100, step = 5,
format = { "$it%" }, modifier = focus.track(page, "upscale-sharpness"), onChange = a.onUpscaleSharpness)
}
- CardboardGroup(host, a) { key -> focus.track(page, key) }
}
SessionDrawerPage.EFFECTS -> {
ScreenEffectsGroup(host, a, advancedEffectsExpanded,
onAdvancedToggle = { advancedEffectsExpanded = !advancedEffectsExpanded }) { key -> focus.track(page, key) }
}
+ SessionDrawerPage.STEAMVR -> SteamVrGroup(a) { key -> focus.track(page, key) }
+ SessionDrawerPage.CARDBOARD -> CardboardGroup(host, a) { key -> focus.track(page, key) }
SessionDrawerPage.CONTROLLER -> {
SettingsGroup(stringResource(R.string.drawer_page_controller)) {
a.padType?.let { type ->
@@ -915,17 +923,34 @@ private fun CardboardGroup(host: MenuHost, a: DrawerActions, track: (String) ->
}
if (c.headTracking && a.cardboardSensor)
ActionRow(stringResource(R.string.cardboard_recenter), null, stringResource(R.string.cardboard_recenter_button), a.onCardboardRecenter)
+ }
+}
+
+@Composable
+private fun SteamVrGroup(a: DrawerActions, track: (String) -> Modifier) {
+ SettingsGroup(stringResource(R.string.steamvr_title)) {
val vrLabel = stringResource(R.string.cardboard_steamvr)
+ val enterLabel = stringResource(if (a.immersiveSteamVr) R.string.steamvr_enter else R.string.cardboard_steamvr_start)
+ val enterHint = stringResource(if (a.immersiveSteamVr) R.string.steamvr_quest_ready_hint else R.string.cardboard_steamvr_ready_hint)
+ @Composable fun button(label: String, hint: String?, text: String, action: String) {
+ SettingsRow(label, hint) {
+ SecondaryButton(text, modifier = track("steamvr"), onClick = { a.onSteamVr(action) })
+ }
+ }
when (a.steamVr) {
- SteamVr.Status.NOT_INSTALLED -> ActionRow(vrLabel, stringResource(R.string.cardboard_steamvr_install_hint),
- stringResource(R.string.cardboard_steamvr_install), { a.onSteamVr("install") })
+ SteamVr.Status.NOT_INSTALLED -> button(vrLabel, stringResource(R.string.cardboard_steamvr_install_hint),
+ stringResource(R.string.cardboard_steamvr_install), "install")
SteamVr.Status.INSTALLING -> SettingsRow(vrLabel, stringResource(R.string.cardboard_steamvr_installing)) {}
- SteamVr.Status.READY -> ActionRow(vrLabel, stringResource(R.string.cardboard_steamvr_ready_hint),
- stringResource(R.string.cardboard_steamvr_start), { a.onSteamVr("start") })
- SteamVr.Status.RUNNING -> ActionRow(vrLabel, stringResource(R.string.cardboard_steamvr_running_hint),
- stringResource(R.string.cardboard_steamvr_stop), { a.onSteamVr("stop") })
+ SteamVr.Status.READY -> button(vrLabel, enterHint, enterLabel, "start")
+ SteamVr.Status.RUNNING -> {
+ if (a.immersiveSteamVr) button(vrLabel, enterHint, enterLabel, "start")
+ SettingsRow(vrLabel, stringResource(R.string.cardboard_steamvr_running_hint)) {
+ SecondaryButton(stringResource(R.string.cardboard_steamvr_stop),
+ modifier = track(if (a.immersiveSteamVr) "steamvr-stop" else "steamvr"), onClick = { a.onSteamVr("stop") })
+ }
+ }
SteamVr.Status.UNAVAILABLE -> SettingsRow(vrLabel, stringResource(R.string.cardboard_steamvr_unavailable)) {}
- null -> {}
+ null -> SettingsRow(vrLabel, stringResource(R.string.steamvr_checking)) {}
}
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9d6ff4de9..8c2c6763e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -595,6 +595,10 @@
This device has no rotation sensor.
Recenter view
Recenter
+ SteamVR
+ Enter SteamVR
+ Enter immersive VR with headset tracking and Touch controllers.
+ Checking SteamVR…
SteamVR (experimental)
Install
Downloads Valve\'s ARM64 SteamVR (571 MB).
diff --git a/app/src/test/java/com/droiddeck/launcher/ui/SessionDrawerPageTest.kt b/app/src/test/java/com/droiddeck/launcher/ui/SessionDrawerPageTest.kt
index 6c844ead1..bed0fceb2 100644
--- a/app/src/test/java/com/droiddeck/launcher/ui/SessionDrawerPageTest.kt
+++ b/app/src/test/java/com/droiddeck/launcher/ui/SessionDrawerPageTest.kt
@@ -20,7 +20,9 @@ class SessionDrawerPageTest {
assertEquals(sessionDrawerPages(false), connected.filter { it != SessionDrawerPage.SECOND_SCREEN })
for (pages in listOf(connected, sessionDrawerPages(false))) {
assertEquals(SessionDrawerPage.EFFECTS, pages.step(SessionDrawerPage.DISPLAY, 1))
- assertEquals(SessionDrawerPage.GAMES, pages.step(SessionDrawerPage.EFFECTS, 1))
+ assertEquals(SessionDrawerPage.STEAMVR, pages.step(SessionDrawerPage.EFFECTS, 1))
+ assertEquals(SessionDrawerPage.CARDBOARD, pages.step(SessionDrawerPage.STEAMVR, 1))
+ assertEquals(SessionDrawerPage.GAMES, pages.step(SessionDrawerPage.CARDBOARD, 1))
assertEquals(SessionDrawerPage.SESSION, pages.step(SessionDrawerPage.GAMES, 1))
}
}
diff --git a/docs/agent-control.md b/docs/agent-control.md
index e6484c0bb..3b72bc867 100644
--- a/docs/agent-control.md
+++ b/docs/agent-control.md
@@ -176,7 +176,7 @@ Automator show them:
| `play-steam` | Play on the Steam and Games pages |
| `setting-` | every toggle and choice row, by its key (`setting-logs`, `setting-agent-commands`, ...) |
| `tab-` | a page's tab strip (Setup's Overview, Controller, Session, Launcher...), by position |
-| `drawer-tab-controller`, `-second_screen`, `-display`, `-effects`, `-games`, `-session` | the session drawer's pages |
+| `drawer-tab-controller`, `-second_screen`, `-display`, `-effects`, `-steamvr`, `-cardboard`, `-games`, `-session` | the session drawer's pages |
| `drawer-stop`, `drawer-stop-confirm`, `drawer-stop-cancel` | stopping a session from the drawer |
## Scenarios
diff --git a/docs/quest.md b/docs/quest.md
index 11be10e8e..ae924b037 100644
--- a/docs/quest.md
+++ b/docs/quest.md
@@ -67,8 +67,8 @@ Host allocation and preload-chain tests do not establish headset operation.
## Experimental immersive SteamVR
-In a running Steam session, install SteamVR through DroidDeck's SteamVR controls,
-then choose Start. On supported Quest devices this opens the standard app's
+In a running Steam session, open the session menu's SteamVR tab, install SteamVR
+once, then choose Enter SteamVR. Cardboard has its own tab and can remain off. On supported Quest devices this opens the standard app's
OpenXR activity. The guest driver receives headset poses, Touch controller input
and display timing, and returns owned stereo images with their render poses.
SteamVR haptics return through the tracking bridge. No root or separate XR APK