mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Glasses: Play SteamVR beside the glasses button instead of starting SteamVR with them, pad hands drawn as Index controllers and off while other VR controllers are connected, glasses sheet tabs sized like Steam's
This commit is contained in:
1 parent
b2beed6290
commit
b8fe2fbd88
27 files changed
+153
-56
No files matched your search
@@ -600,6 +600,7 @@ class MainActivity : ComponentActivity() {
|
||||
),
|
||||
FrontEndActions(
|
||||
onPlay = { startSteamSession() },
|
||||
onPlaySteamVr = { startSteamSession(steamVr = true) },
|
||||
// The client's desktop UI, with gamescope's Deck integration disabled.
|
||||
onPlayDesktopUi = {
|
||||
startSession(Intent(this, SessionActivity::class.java)
|
||||
@@ -926,8 +927,8 @@ class MainActivity : ComponentActivity() {
|
||||
if (SessionPrefs.runSteamAtStartup(this) && !isFinishing && !isDestroyed) startSteamSession()
|
||||
}
|
||||
|
||||
private fun startSteamSession() {
|
||||
startSession(Intent(this, SessionActivity::class.java), steamSession = true)
|
||||
private fun startSteamSession(steamVr: Boolean = false) {
|
||||
startSession(Intent(this, SessionActivity::class.java).putExtra(SessionActivity.EXTRA_STEAMVR, steamVr), steamSession = true)
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
|
||||
@@ -321,6 +321,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
private var glasses3dForVr = false
|
||||
private var glassesPresent = false
|
||||
private var steamVrAutoTried = false
|
||||
private var steamVrInstallTried = false
|
||||
private var headTracking by mutableStateOf(false)
|
||||
private var glassesSlot by mutableStateOf(0)
|
||||
private var glassesKey: List<Any?> = emptyList()
|
||||
@@ -425,6 +426,9 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
SessionState.programArgs = intent.getStringArrayExtra(SessionService.EXTRA_PROGRAM_ARGS)?.toList().orEmpty()
|
||||
SessionState.steamUi = intent.getStringExtra(SessionService.EXTRA_STEAM_UI)
|
||||
SessionState.steamUrl = intent.getStringExtra(SessionService.EXTRA_STEAM_URL)
|
||||
SessionState.steamVr = intent.getBooleanExtra(EXTRA_STEAMVR, false)
|
||||
} else if (intent.getBooleanExtra(EXTRA_STEAMVR, false) && SessionState.mode == SessionService.MODE_STEAM) {
|
||||
SessionState.steamVr = true
|
||||
}
|
||||
if (intent.action == SessionService.ACTION_AGENT_START) {
|
||||
SessionEvents.record("agent.start_requested", mapOf("mode" to SessionState.mode))
|
||||
@@ -2584,6 +2588,10 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
// Home/notification resumes must retain the external frontend's return destination.
|
||||
if (this.intent.getBooleanExtra(EXTRA_RETURN_HOME, false)) intent.putExtra(EXTRA_RETURN_HOME, true)
|
||||
setIntent(intent)
|
||||
if (intent.getBooleanExtra(EXTRA_STEAMVR, false) && SessionState.mode == SessionService.MODE_STEAM) {
|
||||
SessionState.steamVr = true
|
||||
steamVrAutoTried = false
|
||||
}
|
||||
if (intent.action == SessionService.ACTION_HOME_GUIDE) {
|
||||
handleHomeGuideIntent(intent)
|
||||
} else if (intent.action == SessionService.ACTION_RESUME) {
|
||||
@@ -2815,11 +2823,21 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
|
||||
private fun autoStartSteamVr(now: SteamVr.Status) {
|
||||
if (now == SteamVr.Status.RUNNING) steamVrAutoTried = true
|
||||
if (now != SteamVr.Status.READY || steamVrAutoTried || !glassesSettings.autoSteamVr || !glasses.canControl ||
|
||||
loading.visible || SessionState.mode != SessionService.MODE_STEAM) return
|
||||
steamVrAutoTried = true
|
||||
Log.i(TAG, "starting SteamVR for ${glasses.model}")
|
||||
steamVrAction("start")
|
||||
if (steamVrAutoTried || !SessionState.steamVr || !glasses.canControl || loading.visible ||
|
||||
SessionState.mode != SessionService.MODE_STEAM) return
|
||||
when (now) {
|
||||
SteamVr.Status.READY -> {
|
||||
steamVrAutoTried = true
|
||||
Log.i(TAG, "starting SteamVR for ${glasses.model}")
|
||||
steamVrAction("start")
|
||||
}
|
||||
SteamVr.Status.NOT_INSTALLED -> if (!steamVrInstallTried) {
|
||||
steamVrInstallTried = true
|
||||
Log.i(TAG, "installing SteamVR for ${glasses.model}")
|
||||
steamVrAction("install")
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateGlassesScreen() {
|
||||
@@ -3012,6 +3030,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
|
||||
companion object {
|
||||
const val EXTRA_RETURN_HOME = "returnHome"
|
||||
const val EXTRA_STEAMVR = "steamVr"
|
||||
/** How long the leaving flood may take before the session closes without it. */
|
||||
private const val LEAVE_TIMEOUT_MS = 1_500L
|
||||
private const val TAG = "SessionActivity"
|
||||
|
||||
@@ -133,7 +133,6 @@ class AgentBridgeProvider : ContentProvider() {
|
||||
"screenWidthDm" -> g.setScreenWidthDm(number!!)
|
||||
"screenDistanceDm" -> g.setScreenDistanceDm(number!!)
|
||||
"vrFovDeg" -> g.setVrFovDeg(number!!)
|
||||
"autoSteamVr" -> g.setAutoSteamVr(on())
|
||||
"sbsSupport" -> g.setSbsSupport(number!!)
|
||||
else -> return error("INVALID_KEY", "Unknown glasses setting '$key'")
|
||||
}
|
||||
@@ -170,6 +169,7 @@ class AgentBridgeProvider : ContentProvider() {
|
||||
.put("steamUi", SessionState.steamUi ?: JSONObject.NULL)
|
||||
.put("steamUrl", SessionState.steamUrl ?: JSONObject.NULL)
|
||||
.put("suspended", SessionState.suspended)
|
||||
.put("steamVr", SessionState.steamVr)
|
||||
.put("pip", SessionState.pipActive)
|
||||
.put("firstFrame", SessionState.firstFrameSeen)
|
||||
.put("output", JSONArray().put(SessionState.outputSize.first).put(SessionState.outputSize.second))
|
||||
|
||||
@@ -98,6 +98,7 @@ class AgentStartActivity : Activity() {
|
||||
request.optString("steamUrl").takeIf { it.startsWith("steam://") }?.let {
|
||||
putExtra(SessionService.EXTRA_STEAM_URL, it)
|
||||
}
|
||||
if (request.optBoolean("steamVr")) putExtra(SessionActivity.EXTRA_STEAMVR, true)
|
||||
}
|
||||
}
|
||||
startActivity(sessionIntent)
|
||||
|
||||
@@ -74,7 +74,6 @@ data class GlassesSettings(
|
||||
val screenWidthDm: Int = 20,
|
||||
val screenDistanceDm: Int = 30,
|
||||
val vrFovDeg: Int = VR_FOV_DEFAULT,
|
||||
val autoSteamVr: Boolean = true,
|
||||
) {
|
||||
companion object {
|
||||
const val VR_FOV_MIN = 24
|
||||
@@ -204,7 +203,6 @@ object Glasses {
|
||||
fun setScreenWidthDm(dm: Int) = post { update { it.copy(screenWidthDm = dm.coerceIn(5, 200)) } }
|
||||
fun setScreenDistanceDm(dm: Int) = post { update { it.copy(screenDistanceDm = dm.coerceIn(5, 200)) } }
|
||||
fun setVrFovDeg(deg: Int) = post { update { it.copy(vrFovDeg = deg.coerceIn(GlassesSettings.VR_FOV_MIN, GlassesSettings.VR_FOV_MAX)) } }
|
||||
fun setAutoSteamVr(on: Boolean) = post { update { it.copy(autoSteamVr = on) } }
|
||||
|
||||
fun glassesDisplay(): Display? = _state.value.displayId.takeIf { it != Display.INVALID_DISPLAY }?.let { displays.getDisplay(it) }
|
||||
|
||||
@@ -260,7 +258,7 @@ object Glasses {
|
||||
.put("settings", JSONObject().put("refreshHz", g.refreshHz).put("sbsHz", g.sbsHz).put("sunblock", g.sunblock ?: JSONObject.NULL)
|
||||
.put("gameOnGlasses", g.gameOnGlasses).put("headTracking", g.headTracking).put("auto3d", g.auto3d)
|
||||
.put("picture3d", g.picture3d).put("screenWidthDm", g.screenWidthDm).put("screenDistanceDm", g.screenDistanceDm)
|
||||
.put("vrFovDeg", g.vrFovDeg).put("autoSteamVr", g.autoSteamVr))
|
||||
.put("vrFovDeg", g.vrFovDeg))
|
||||
}
|
||||
|
||||
fun recentFrames(): JSONArray = synchronized(frames) { JSONArray(frames.toList()) }
|
||||
@@ -553,7 +551,6 @@ object Glasses {
|
||||
screenWidthDm = p.getInt("screenWidthDm", 20),
|
||||
screenDistanceDm = p.getInt("screenDistanceDm", 30),
|
||||
vrFovDeg = p.getInt("vrFovDeg", GlassesSettings.VR_FOV_DEFAULT).coerceIn(GlassesSettings.VR_FOV_MIN, GlassesSettings.VR_FOV_MAX),
|
||||
autoSteamVr = p.getBoolean("autoSteamVr", true),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -569,7 +566,6 @@ object Glasses {
|
||||
putInt("screenWidthDm", s.screenWidthDm)
|
||||
putInt("screenDistanceDm", s.screenDistanceDm)
|
||||
putInt("vrFovDeg", s.vrFovDeg)
|
||||
putBoolean("autoSteamVr", s.autoSteamVr)
|
||||
}.apply()
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ object SessionEvents {
|
||||
SessionState.programArgs = emptyList()
|
||||
SessionState.steamUi = null
|
||||
SessionState.steamUrl = null
|
||||
SessionState.steamVr = false
|
||||
SessionState.firstFrameSeen = false
|
||||
SessionState.suspended = false
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ object SessionState {
|
||||
var steamUi: String? = null
|
||||
@Volatile
|
||||
var steamUrl: String? = null
|
||||
@Volatile
|
||||
var steamVr = false
|
||||
/** A session the guest asked for (the desktop's Steam launchers): started by the activity once this one has ended. */
|
||||
var relaunch: android.content.Intent? = null
|
||||
/** HDR10 was asked for and the panel can show it: the compositor was told, and the session
|
||||
|
||||
@@ -194,6 +194,7 @@ internal fun BoxWithConstraintsScope.DropSheet(
|
||||
icon: ImageVector,
|
||||
tabs: List<Pair<String, String>>,
|
||||
covered: () -> Float = { 0f },
|
||||
beside: @Composable (enabled: Boolean) -> Unit = {},
|
||||
rows: @Composable (tab: String) -> Unit,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
@@ -417,7 +418,7 @@ internal fun BoxWithConstraintsScope.DropSheet(
|
||||
if (k.keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { if (k.action == KeyEvent.ACTION_DOWN) close(); true } else false
|
||||
},
|
||||
) {
|
||||
SheetChips(tabs.map { it.second }, state.tab, chipFocus) { i -> state.travel = if (i > state.tab) 1 else -1; state.tab = i }
|
||||
SheetChips(tabs.map { it.second }, state.tab, chipFocus, width = LocalSheetChipWidth.current, end = true) { i -> state.travel = if (i > state.tab) 1 else -1; state.tab = i }
|
||||
}
|
||||
val closeSource = remember { MutableInteractionSource() }
|
||||
val closeHot = rememberHot(closeSource)
|
||||
@@ -437,6 +438,16 @@ internal fun BoxWithConstraintsScope.DropSheet(
|
||||
val enabled = covered() < 0.5f
|
||||
val toggle = { if (state.open) close() else state.open = true }
|
||||
val sideInset = if (narrow) 20.dp else 44.dp
|
||||
val besideShown = enabled && !state.open && !state.shown
|
||||
val r1 = rest
|
||||
if (r1 != null && (besideShown || state.sink.value < 0.99f)) Box(
|
||||
Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(top = sideInset, end = sideInset + with(density) { r1.width.toDp() } + 12.dp)
|
||||
.graphicsLayer { alpha = (1f - state.sink.value) * (1f - covered()) }
|
||||
.zIndex(2f)
|
||||
.focusProperties { canFocus = besideShown },
|
||||
) { beside(besideShown) }
|
||||
Box(
|
||||
Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
|
||||
@@ -153,6 +153,7 @@ class FrontEndState(
|
||||
|
||||
class FrontEndActions(
|
||||
val onPlay: () -> Unit,
|
||||
val onPlaySteamVr: () -> Unit = {},
|
||||
val onPlayDesktopUi: () -> Unit,
|
||||
val onSteamGame: (Library.SteamGame) -> Unit,
|
||||
val onGameShortcut: (Library.SteamGame) -> Unit = {},
|
||||
|
||||
@@ -196,7 +196,7 @@ internal fun SetupPanel(
|
||||
),
|
||||
) {
|
||||
PageHeader(stringResource(R.string.setup_title)) {
|
||||
Box(Modifier.weight(1f)) { SheetChips(tabs, tab, chipFocus, pick) }
|
||||
Box(Modifier.weight(1f)) { SheetChips(tabs, tab, chipFocus, onPick = pick) }
|
||||
}
|
||||
Column(modifier = Modifier.weight(1f).fillMaxWidth().verticalScroll(rememberScrollState()).padding(bottom = 24.dp)) {
|
||||
when (tab) {
|
||||
|
||||
@@ -130,8 +130,6 @@ internal fun GlassesGroups(
|
||||
format = { String.format(Locale.US, "%.1f m", it / 10f) }, modifier = track("glasses-distance")) { Glasses.setScreenDistanceDm(it) }
|
||||
}
|
||||
steamVr()
|
||||
ToggleRow(host, "glasses-auto-steamvr", stringResource(R.string.glasses_auto_steamvr), stringResource(R.string.glasses_auto_steamvr_hint),
|
||||
s.autoSteamVr, chipModifier = track("glasses-auto-steamvr")) { Glasses.setAutoSteamVr(it) }
|
||||
SliderRow(stringResource(R.string.glasses_vr_fov), stringResource(R.string.glasses_vr_fov_hint), s.vrFovDeg,
|
||||
GlassesSettings.VR_FOV_MIN..GlassesSettings.VR_FOV_MAX, step = 1,
|
||||
format = { "$it°" }, modifier = track("glasses-vr-fov")) { Glasses.setVrFovDeg(it) }
|
||||
|
||||
@@ -54,6 +54,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -105,6 +106,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.isSpecified
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.util.lerp
|
||||
import com.droiddeck.launcher.R
|
||||
@@ -157,6 +159,13 @@ internal class SheetBeat(private val pieces: List<Animatable<Float, AnimationVec
|
||||
/** The sheet whose rows are being laid out, so each settings group can take its beat. */
|
||||
internal val LocalSheetBeat = staticCompositionLocalOf<SheetBeat?> { null }
|
||||
|
||||
internal val LocalSheetChipWidth = compositionLocalOf { Dp.Unspecified }
|
||||
|
||||
private val ChipGap = 6.dp
|
||||
|
||||
internal fun sheetChipWidth(row: Dp, count: Int): Dp =
|
||||
if (count > 0 && row / count >= 96.dp) (row - ChipGap * (count - 1)) / count else 0.dp
|
||||
|
||||
/** A settings group's piece of the sheet's entrance; nothing outside a sheet. */
|
||||
@Composable
|
||||
internal fun Modifier.sheetPiece(): Modifier {
|
||||
@@ -368,7 +377,7 @@ internal fun LiftHero(
|
||||
val lipFocus = remember { FocusRequester() }
|
||||
val settingsLabel = stringResource(R.string.drawer_page_settings)
|
||||
var lipFocused by remember { mutableStateOf(false) }
|
||||
val lipWanted = !open && (!shown || lipBack) && (playFocused || playHovered || touchOnly || lipFocused)
|
||||
val lipWanted = !open && (!shown || lipBack) && (playFocused || playHovered || touchOnly || lipFocused) && drop?.let { it.open || it.shown } != true
|
||||
LaunchedEffect(lipWanted) {
|
||||
if (lipWanted) {
|
||||
// Back from the sheet it comes straight away; otherwise a beat after Play has focus.
|
||||
@@ -507,6 +516,12 @@ internal fun LiftHero(
|
||||
// Play's raised place: its centre 3dp under the sheet's edge, same left inset.
|
||||
val raise = restRect?.let { (openTop + with(density) { 3.dp.toPx() } - buttonH / 2f) - it.top } ?: 0f
|
||||
travelPx[0] = (h - openTop).coerceAtLeast(1f)
|
||||
val pill = restRect?.translate(0f, raise)
|
||||
// What the sheet holds: the chips to the right of Play, the rows under them.
|
||||
val chipsX = with(density) {
|
||||
if (narrow || pill == null) 16.dp.toPx() else pill.right + 20.dp.toPx() - inset
|
||||
}
|
||||
val chipWidth = with(density) { sheetChipWidth((w - 2 * inset - chipsX - 64.dp.toPx()).coerceAtLeast(0f).toDp(), tabs.size) }
|
||||
|
||||
// The wall behind sinks back about the pane's centre.
|
||||
val foot = h
|
||||
@@ -532,7 +547,6 @@ internal fun LiftHero(
|
||||
val rimPath = remember { Path() }
|
||||
val corner = with(density) { SheetCorner.toPx() }
|
||||
val rim = with(density) { 1.4.dp.toPx() }
|
||||
val pill = restRect?.translate(0f, raise)
|
||||
Canvas(Modifier.fillMaxSize()) {
|
||||
val f = fold.value
|
||||
val ft = foldTop.value
|
||||
@@ -560,10 +574,6 @@ internal fun LiftHero(
|
||||
drawPath(rimPath, rimColor, style = Stroke(rim))
|
||||
}
|
||||
}
|
||||
// What the sheet holds: the chips to the right of Play, the rows under them.
|
||||
val chipsX = with(density) {
|
||||
if (narrow || pill == null) 16.dp.toPx() else pill.right + 20.dp.toPx() - sheetLeft
|
||||
}
|
||||
val chipsY = with(density) { if (narrow && pill != null) (pill.bottom - openTop) + 10.dp.toPx() else ChipsTop.toPx() }
|
||||
// The rows start under the chips, which are 44dp to touch: from 54dp they ran under the
|
||||
// chips' feet and, scrolled, covered them.
|
||||
@@ -656,7 +666,7 @@ internal fun LiftHero(
|
||||
Box(
|
||||
Modifier
|
||||
.onGloballyPositioned { rest = it.boundsInRoot() }
|
||||
.graphicsLayer { translationY = raise * ride.value }
|
||||
.graphicsLayer { translationY = raise * ride.value; alpha = 1f - (drop?.sink?.value ?: 0f) }
|
||||
.onFocusChanged {
|
||||
// Left out of the sheet (from the chips or a row) folds it, as Up from the chips does.
|
||||
if (it.hasFocus && leftForPlay[0]) { leftForPlay[0] = false; if (openState.value && !launchingState.value) close() }
|
||||
@@ -702,7 +712,7 @@ internal fun LiftHero(
|
||||
Modifier
|
||||
.offset { IntOffset((r0.center.x - handleW / 2f).roundToInt(), (r0.bottom + raise * ride.value + (lipShow.value - 1f) * 8.dp.toPx()).roundToInt()) }
|
||||
.onSizeChanged { handleW = it.width.toFloat() }
|
||||
.graphicsLayer { alpha = lipShow.value * (1f - lipGone.value) }
|
||||
.graphicsLayer { alpha = lipShow.value * (1f - lipGone.value) * (1f - (drop?.sink?.value ?: 0f)) }
|
||||
// 44dp to touch, with a 32dp visible handle.
|
||||
.heightIn(min = 44.dp).testTag("sheet-open")
|
||||
.semantics { contentDescription = settingsLabel }
|
||||
@@ -717,7 +727,7 @@ internal fun LiftHero(
|
||||
}
|
||||
}
|
||||
}
|
||||
overlay { sink.value }
|
||||
CompositionLocalProvider(LocalSheetChipWidth provides chipWidth) { overlay { sink.value } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -785,7 +795,14 @@ private fun Modifier.drawBorderSidesAndFoot(color: Color, corner: Dp, flip: Bool
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
internal fun SheetChips(labels: List<String>, selected: Int, focus: List<FocusRequester>? = null, onPick: (Int) -> Unit) {
|
||||
internal fun SheetChips(
|
||||
labels: List<String>,
|
||||
selected: Int,
|
||||
focus: List<FocusRequester>? = null,
|
||||
width: Dp = Dp.Unspecified,
|
||||
end: Boolean = false,
|
||||
onPick: (Int) -> Unit,
|
||||
) {
|
||||
val pal = LocalPalette.current
|
||||
val bounds = remember { mutableStateMapOf<Int, Rect>() }
|
||||
val tint = rememberUpdatedState(pal.signal)
|
||||
@@ -794,20 +811,24 @@ internal fun SheetChips(labels: List<String>, selected: Int, focus: List<FocusRe
|
||||
var within by remember { mutableStateOf(false) }
|
||||
// LB / RB with a pad on the chips: focus goes with the selection.
|
||||
LaunchedEffect(selected) { if (within) runCatching { chips[selected].requestFocus() } }
|
||||
BoxWithConstraints(Modifier.fillMaxWidth().onFocusChanged { within = it.hasFocus }) {
|
||||
val equal = maxWidth / labels.size.coerceAtLeast(1) >= 96.dp
|
||||
BoxWithConstraints(
|
||||
Modifier.fillMaxWidth().onFocusChanged { within = it.hasFocus },
|
||||
contentAlignment = if (end) Alignment.CenterEnd else Alignment.CenterStart,
|
||||
) {
|
||||
val equal = !width.isSpecified && maxWidth / labels.size.coerceAtLeast(1) >= 96.dp
|
||||
val least = if (width.isSpecified) maxOf(width, 64.dp) else 64.dp
|
||||
val scroll = rememberScrollState()
|
||||
// Room inside the scroll for the focus ring, which the scroll's clip would otherwise cut.
|
||||
Box(if (equal) Modifier.fillMaxWidth() else Modifier.edgeFade(scroll).horizontalScroll(scroll).padding(3.dp)) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(ChipGap),
|
||||
// Up from the rows lands on the picked chip, not whichever sits above: landing picks.
|
||||
modifier = (if (equal) Modifier.fillMaxWidth() else Modifier)
|
||||
.focusProperties { enter = { chips.getOrNull(selected) ?: FocusRequester.Default } }
|
||||
.focusGroup(),
|
||||
) {
|
||||
labels.forEachIndexed { i, label ->
|
||||
val m = (if (equal) Modifier.weight(1f) else Modifier.widthIn(min = 64.dp))
|
||||
val m = (if (equal) Modifier.weight(1f) else Modifier.widthIn(min = least))
|
||||
.onGloballyPositioned { bounds[i] = it.boundsInParent() }
|
||||
.focusRequester(chips[i])
|
||||
SheetChip(label, i == selected, m.onFocusChanged { if (it.isFocused && i != selected) onPick(i) }) { onPick(i) }
|
||||
|
||||
@@ -110,7 +110,13 @@ internal fun SteamHome(s: FrontEndState, a: FrontEndActions, modifier: Modifier)
|
||||
key = "steam", tabs = tabs,
|
||||
drop = drop.takeIf { glasses.present },
|
||||
overlay = { lifted ->
|
||||
if (glasses.present) DropSheet(drop, glasses.model, GlassesIcon, glassesTabs(glasses), covered = lifted) { tab ->
|
||||
if (glasses.present) DropSheet(
|
||||
drop, glasses.model, GlassesIcon, glassesTabs(glasses), covered = lifted,
|
||||
beside = { enabled ->
|
||||
PrimaryButton(stringResource(R.string.games_play_steamvr), enabled = !s.busy, large = true, icon = Icons.Filled.PlayArrow,
|
||||
modifier = Modifier.testTag("play-steamvr")) { if (enabled) a.onPlaySteamVr() }
|
||||
},
|
||||
) { tab ->
|
||||
GlassesGroups(rememberMenuHost(), glasses, glassesSettings, inSession = false, tab = tab)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -902,6 +902,7 @@
|
||||
<string name="content_linux_desktop_hint">KDE Plasma, Firefox y tus emuladores</string>
|
||||
<string name="games_empty">Los juegos que instales en Steam aparecen aquí después de la sesión.</string>
|
||||
<string name="games_play_steam">Jugar en Steam</string>
|
||||
<string name="games_play_steamvr">Jugar en SteamVR</string>
|
||||
<string name="games_launch_settings">Ajustes de inicio</string>
|
||||
<string name="games_launch">Iniciar</string>
|
||||
<string name="games_files">Archivos del juego</string>
|
||||
@@ -2184,8 +2185,6 @@
|
||||
<string name="glasses_head_tracking_hint">Los sensores de las gafas giran la pantalla virtual y SteamVR con tu cabeza.</string>
|
||||
<string name="glasses_recenter">Recentrar la vista</string>
|
||||
<string name="glasses_recenter_button">Recentrar</string>
|
||||
<string name="glasses_auto_steamvr">Iniciar SteamVR con las gafas</string>
|
||||
<string name="glasses_auto_steamvr_hint">En Steam, SteamVR se inicia al conectar las gafas y se detiene al desconectarlas.</string>
|
||||
<string name="glasses_vr_fov">Campo de visión de SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Más bajo acerca SteamVR y llena las gafas; más alto muestra más a tu alrededor.</string>
|
||||
<string name="glasses_playing">Jugando en %1$s</string>
|
||||
|
||||
@@ -775,6 +775,7 @@
|
||||
<string name="content_linux_desktop_hint">KDE Plasma, Firefox et vos émulateurs</string>
|
||||
<string name="games_empty">Les jeux installés sur Steam apparaîtront ici après la fin de la session.</string>
|
||||
<string name="games_play_steam">Lancer Steam</string>
|
||||
<string name="games_play_steamvr">Lancer SteamVR</string>
|
||||
<string name="games_launch_settings">Paramètres de lancement</string>
|
||||
<string name="games_launch">Lancer</string>
|
||||
<string name="games_files">Fichiers du jeu</string>
|
||||
@@ -1014,8 +1015,6 @@
|
||||
<string name="glasses_head_tracking_hint">Les capteurs des lunettes font tourner l\'écran virtuel et SteamVR avec votre tête.</string>
|
||||
<string name="glasses_recenter">Recentrer la vue</string>
|
||||
<string name="glasses_recenter_button">Recentrer</string>
|
||||
<string name="glasses_auto_steamvr">Lancer SteamVR avec les lunettes</string>
|
||||
<string name="glasses_auto_steamvr_hint">Dans Steam, SteamVR démarre quand les lunettes sont branchées et s\'arrête quand elles sont débranchées.</string>
|
||||
<string name="glasses_vr_fov">Champ de vision SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Plus bas rapproche SteamVR et remplit les lunettes ; plus haut montre davantage autour de vous.</string>
|
||||
<string name="glasses_playing">En jeu sur %1$s</string>
|
||||
|
||||
@@ -892,6 +892,7 @@
|
||||
<string name="content_linux_desktop_hint">KDE Plasma、Firefox、エミュレーター</string>
|
||||
<string name="games_empty">Steamでインストールされたゲームは、セッション終了後にここに表示されます。</string>
|
||||
<string name="games_play_steam">Steamを実行</string>
|
||||
<string name="games_play_steamvr">SteamVRを実行</string>
|
||||
<string name="games_launch_settings">起動設定</string>
|
||||
<string name="games_launch">起動</string>
|
||||
<string name="games_files">ゲームファイル</string>
|
||||
@@ -2140,8 +2141,6 @@
|
||||
<string name="glasses_head_tracking_hint">グラスのセンサーで、頭の動きに合わせて仮想スクリーンと SteamVR を動かします。</string>
|
||||
<string name="glasses_recenter">視点をリセット</string>
|
||||
<string name="glasses_recenter_button">中央に戻す</string>
|
||||
<string name="glasses_auto_steamvr">グラス接続時に SteamVR を起動</string>
|
||||
<string name="glasses_auto_steamvr_hint">Steam では、グラスを接続すると SteamVR を起動し、外すと停止します。</string>
|
||||
<string name="glasses_vr_fov">SteamVR の視野角</string>
|
||||
<string name="glasses_vr_fov_hint">小さくすると SteamVR が近くなりグラスいっぱいに広がり、大きくすると周囲が広く見えます。</string>
|
||||
<string name="glasses_playing">%1$s でプレイ中</string>
|
||||
|
||||
@@ -892,6 +892,7 @@
|
||||
<string name="content_linux_desktop_hint">KDE Plasma, Firefox 및 에뮬레이터</string>
|
||||
<string name="games_empty">Steam에서 설치한 게임은 세션 종료 후 여기에 표시됩니다.</string>
|
||||
<string name="games_play_steam">Steam 실행</string>
|
||||
<string name="games_play_steamvr">SteamVR 실행</string>
|
||||
<string name="games_launch_settings">실행 설정</string>
|
||||
<string name="games_launch">실행</string>
|
||||
<string name="games_files">게임 파일</string>
|
||||
@@ -2140,8 +2141,6 @@
|
||||
<string name="glasses_head_tracking_hint">안경의 센서가 머리 움직임에 맞춰 가상 화면과 SteamVR을 돌립니다.</string>
|
||||
<string name="glasses_recenter">시점 재설정</string>
|
||||
<string name="glasses_recenter_button">중앙 맞추기</string>
|
||||
<string name="glasses_auto_steamvr">안경 연결 시 SteamVR 시작</string>
|
||||
<string name="glasses_auto_steamvr_hint">Steam에서 안경을 연결하면 SteamVR을 시작하고, 분리하면 중지합니다.</string>
|
||||
<string name="glasses_vr_fov">SteamVR 시야각</string>
|
||||
<string name="glasses_vr_fov_hint">낮추면 SteamVR이 가까워져 안경을 가득 채우고, 높이면 주변이 더 넓게 보입니다.</string>
|
||||
<string name="glasses_playing">%1$s에서 플레이 중</string>
|
||||
|
||||
@@ -859,6 +859,7 @@
|
||||
<string name="content_linux_desktop_hint">KDE Plasma, Firefox и ваши эмуляторы</string>
|
||||
<string name="games_empty">Игры, установленные в Steam, появятся здесь после завершения сессии.</string>
|
||||
<string name="games_play_steam">Запустить Steam</string>
|
||||
<string name="games_play_steamvr">Запустить SteamVR</string>
|
||||
<string name="games_launch_settings">Настройки запуска</string>
|
||||
<string name="games_launch">Запустить</string>
|
||||
<string name="games_files">Файлы игры</string>
|
||||
@@ -1125,8 +1126,6 @@
|
||||
<string name="glasses_head_tracking_hint">Датчики очков поворачивают виртуальный экран и SteamVR вслед за головой.</string>
|
||||
<string name="glasses_recenter">Сбросить вид</string>
|
||||
<string name="glasses_recenter_button">Центрировать</string>
|
||||
<string name="glasses_auto_steamvr">Запускать SteamVR с очками</string>
|
||||
<string name="glasses_auto_steamvr_hint">В Steam SteamVR запускается при подключении очков и останавливается при отключении.</string>
|
||||
<string name="glasses_vr_fov">Поле зрения SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Меньше — SteamVR ближе и заполняет очки; больше — шире обзор вокруг.</string>
|
||||
<string name="glasses_playing">Игра на %1$s</string>
|
||||
|
||||
@@ -892,6 +892,7 @@
|
||||
<string name="content_linux_desktop_hint">KDE Plasma、Firefox 和你的模拟器</string>
|
||||
<string name="games_empty">你在 Steam 安装的游戏会在会话结束后显示在这里。</string>
|
||||
<string name="games_play_steam">游玩 Steam</string>
|
||||
<string name="games_play_steamvr">游玩 SteamVR</string>
|
||||
<string name="games_launch_settings">启动设置</string>
|
||||
<string name="games_launch">启动</string>
|
||||
<string name="games_files">游戏文件</string>
|
||||
@@ -2140,8 +2141,6 @@
|
||||
<string name="glasses_head_tracking_hint">眼镜自带的传感器会随头部转动虚拟屏幕和 SteamVR。</string>
|
||||
<string name="glasses_recenter">重新居中视角</string>
|
||||
<string name="glasses_recenter_button">重新居中</string>
|
||||
<string name="glasses_auto_steamvr">连接眼镜时启动 SteamVR</string>
|
||||
<string name="glasses_auto_steamvr_hint">在 Steam 中,插入眼镜时启动 SteamVR,拔出时停止。</string>
|
||||
<string name="glasses_vr_fov">SteamVR 视场角</string>
|
||||
<string name="glasses_vr_fov_hint">调低让 SteamVR 更近并填满眼镜;调高可看到更多周围内容。</string>
|
||||
<string name="glasses_playing">正在 %1$s 上游玩</string>
|
||||
|
||||
@@ -892,6 +892,7 @@
|
||||
<string name="content_linux_desktop_hint">KDE Plasma、Firefox 和你的模擬器</string>
|
||||
<string name="games_empty">你在 Steam 安裝的遊戲會在會話結束後顯示在這裡。</string>
|
||||
<string name="games_play_steam">開啟 Steam</string>
|
||||
<string name="games_play_steamvr">開啟 SteamVR</string>
|
||||
<string name="games_launch_settings">啟動設定</string>
|
||||
<string name="games_launch">啟動</string>
|
||||
<string name="games_files">遊戲檔案</string>
|
||||
@@ -2140,8 +2141,6 @@
|
||||
<string name="glasses_head_tracking_hint">眼鏡自帶的感應器會隨頭部轉動虛擬螢幕和 SteamVR。</string>
|
||||
<string name="glasses_recenter">重新置中視角</string>
|
||||
<string name="glasses_recenter_button">重新置中</string>
|
||||
<string name="glasses_auto_steamvr">連接眼鏡時啟動 SteamVR</string>
|
||||
<string name="glasses_auto_steamvr_hint">在 Steam 中,插入眼鏡時啟動 SteamVR,拔出時停止。</string>
|
||||
<string name="glasses_vr_fov">SteamVR 視場角</string>
|
||||
<string name="glasses_vr_fov_hint">調低令 SteamVR 更近並填滿眼鏡;調高可看到更多周圍內容。</string>
|
||||
<string name="glasses_playing">正在 %1$s 上遊玩</string>
|
||||
|
||||
@@ -892,6 +892,7 @@
|
||||
<string name="content_linux_desktop_hint">KDE Plasma、Firefox 和你的模擬器</string>
|
||||
<string name="games_empty">在 Steam 中安裝的遊戲會在工作階段結束後顯示在這裡。</string>
|
||||
<string name="games_play_steam">執行 Steam</string>
|
||||
<string name="games_play_steamvr">執行 SteamVR</string>
|
||||
<string name="games_launch_settings">啟動設定</string>
|
||||
<string name="games_launch">啟動</string>
|
||||
<string name="games_files">遊戲檔案</string>
|
||||
@@ -2140,8 +2141,6 @@
|
||||
<string name="glasses_head_tracking_hint">眼鏡內建的感測器會隨頭部轉動虛擬螢幕和 SteamVR。</string>
|
||||
<string name="glasses_recenter">重新置中視角</string>
|
||||
<string name="glasses_recenter_button">重新置中</string>
|
||||
<string name="glasses_auto_steamvr">連接眼鏡時啟動 SteamVR</string>
|
||||
<string name="glasses_auto_steamvr_hint">在 Steam 中,插上眼鏡時啟動 SteamVR,拔除時停止。</string>
|
||||
<string name="glasses_vr_fov">SteamVR 視野角度</string>
|
||||
<string name="glasses_vr_fov_hint">調低讓 SteamVR 更近並填滿眼鏡;調高可看到更多周圍內容。</string>
|
||||
<string name="glasses_playing">正在 %1$s 上遊玩</string>
|
||||
|
||||
@@ -1002,6 +1002,7 @@
|
||||
<string name="content_linux_desktop_hint">KDE Plasma, Firefox and your emulators</string>
|
||||
<string name="games_empty">Games you install in Steam show up here after the session.</string>
|
||||
<string name="games_play_steam">Play Steam</string>
|
||||
<string name="games_play_steamvr">Play SteamVR</string>
|
||||
<string name="games_launch_settings">Launch settings</string>
|
||||
<string name="games_launch">Launch</string>
|
||||
<string name="games_files">Game files</string>
|
||||
@@ -2397,8 +2398,6 @@
|
||||
<string name="glasses_head_tracking_hint">The glasses\' own sensors turn the virtual screen and SteamVR with your head.</string>
|
||||
<string name="glasses_recenter">Recenter view</string>
|
||||
<string name="glasses_recenter_button">Recenter</string>
|
||||
<string name="glasses_auto_steamvr">Start SteamVR with the glasses</string>
|
||||
<string name="glasses_auto_steamvr_hint">In Steam, SteamVR starts when the glasses are plugged in and stops when they are unplugged.</string>
|
||||
<string name="glasses_vr_fov">SteamVR field of view</string>
|
||||
<string name="glasses_vr_fov_hint">Lower brings SteamVR closer and fills the glasses; higher shows more around you.</string>
|
||||
<string name="glasses_playing">Playing on %1$s</string>
|
||||
|
||||
@@ -76,7 +76,7 @@ artifact or file error, 7 debugging tools disabled.
|
||||
|
||||
```sh
|
||||
tools/droiddeckctl state # schema 2, below
|
||||
tools/droiddeckctl start steam --wait # --ui desktop, --url steam://..., --timeout
|
||||
tools/droiddeckctl start steam --wait # --ui desktop, --url steam://..., --steamvr, --timeout
|
||||
tools/droiddeckctl start steam --wait --reuse # keep a Steam session that is already up
|
||||
tools/droiddeckctl start desktop
|
||||
tools/droiddeckctl run /usr/bin/foo -- arg1 arg2
|
||||
@@ -132,7 +132,7 @@ mode except the desktop).
|
||||
`state` returns `schema`, `build`, `appVersion`, `runtime {installed, version}`,
|
||||
`agent {guest, guestFocus, commands}` and `session`:
|
||||
|
||||
- `id`, `phase`, `running`, `mode`, `program`, `steamUi`, `steamUrl`, `suspended`, `pip`,
|
||||
- `id`, `phase`, `running`, `mode`, `program`, `steamUi`, `steamUrl`, `steamVr`, `suspended`, `pip`,
|
||||
`firstFrame`, `output` [w, h], `refreshHz`, `lastTransitionAt`, `guestPid`, `installing`
|
||||
- `logDir`, `eventsFile`, `artifactsAvailable`, `artifactsComplete`, `failure {code, message, status}`
|
||||
- `focus`: gamescope's focus as the guest agent last published it, or null:
|
||||
|
||||
@@ -139,6 +139,9 @@ Verified on the AYN Thor (SD 8 Gen 2, Turnip): SteamVR 2.17.10 starts with the D
|
||||
the compositor runs driver-direct at about 60 fps, the dashboard (Steam's library in VR, with the
|
||||
controller's laser) shows cleanly in both eyes at the panel's resolution while Steam keeps running, and
|
||||
a pose written to the shared file turns SteamVR's view. Started from the drawer button through the agent.
|
||||
With VITURE glasses connected, the Steam page also offers **Play SteamVR**, which starts Steam and then
|
||||
SteamVR (installing it first if needed), and stops SteamVR when the glasses are unplugged. **Play
|
||||
Steam** never starts it: while SteamVR runs, Steam keeps its own window in front of flat games.
|
||||
|
||||
### Not done yet
|
||||
|
||||
@@ -152,7 +155,8 @@ a pose written to the shared file turns SteamVR's view. Started from the drawer
|
||||
on release. Clicking the left stick hands the sticks to the game as trackpads (walking, turning,
|
||||
teleport) and back. Mapping, right hand: A/R3 = trackpad click, RT = trigger, RB = grip, Start = menu, guide =
|
||||
system. Left hand: X, LT, LB, Select. Both hands activate in SteamVR and their laser shows on the
|
||||
dashboard; guide, Start and A presses show up in the ring the driver reads.
|
||||
dashboard; guide, Start and A presses show up in the ring the driver reads. They are drawn as Index
|
||||
controllers and switch off while another driver's controller is connected.
|
||||
- **Layers**: only the first layer of each frame is drawn. SteamVR's compositor composites the
|
||||
dashboard into it, so this has not mattered yet.
|
||||
- **Timing**: no reprojection. The presenter blits the frame submitted last once `vrcompositor`'s GPU
|
||||
|
||||
@@ -95,6 +95,9 @@ OpenVR to SteamVR's ARM64 `vrclient.so`, so no x86 SteamVR is involved.
|
||||
and KGSL does not order the copy after the client's GPU work. The presenter's blit sat behind the
|
||||
game's frame, so the compositor showed the swapchain image's older picture. The presenter now
|
||||
waits for its blit before it presents.
|
||||
- **Flat games under SteamVR.** With SteamVR running, Steam lists a flat game after its own window in
|
||||
gamescope's base layer, so Big Picture keeps the focus and the game never shows, theatre setting or
|
||||
not. SteamVR is therefore started only by Play SteamVR, never with the glasses alone.
|
||||
- **A new projection needs an event.** `SetDisplayProjectionRaw` changed what `vrserver` reports, but
|
||||
`vrcompositor` kept the frustum it started with, so the glasses' field of view did nothing until
|
||||
SteamVR restarted. The driver now follows it with `VREvent_LensDistortionChanged`.
|
||||
|
||||
+4
-1
@@ -340,6 +340,8 @@ def execute(args: argparse.Namespace, adb: str, serial: str) -> dict:
|
||||
if not args.url.startswith("steam://"):
|
||||
raise ControlError("Steam URL must start with steam://", "INVALID_STEAM_URL")
|
||||
request["steamUrl"] = args.url
|
||||
if args.steamvr:
|
||||
request["steamVr"] = True
|
||||
if args.reuse:
|
||||
# A scenario's per-run "before": keep a session of this mode that is already up, so only
|
||||
# a run after one that ended (a crash, the low-memory killer) starts a new one.
|
||||
@@ -551,6 +553,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
start = commands.add_parser("start", help="start Steam or the desktop")
|
||||
start.add_argument("mode", choices=["steam", "desktop"])
|
||||
start.add_argument("--ui", choices=["bigpicture", "desktop"], default="bigpicture")
|
||||
start.add_argument("--steamvr", action="store_true", help="start SteamVR with the session (VITURE glasses)")
|
||||
start.add_argument("--url", help="steam:// URL to pass to the client")
|
||||
start.add_argument("--wait", action="store_true", help="wait for READY after starting")
|
||||
start.add_argument("--reuse", action="store_true", help="keep a running session of this mode instead of failing")
|
||||
@@ -674,7 +677,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
glasses_ops = glasses.add_subparsers(dest="glasses_op")
|
||||
glasses_ops.add_parser("status")
|
||||
glasses_ops.add_parser("allow", help="ask for USB control of the glasses again")
|
||||
glasses_set = glasses_ops.add_parser("set", help="refreshHz, sbsHz, brightness, volume, sunblock, 3d, gameOnGlasses, headTracking, auto3d, picture3d, screenWidthDm, screenDistanceDm, vrFovDeg, autoSteamVr")
|
||||
glasses_set = glasses_ops.add_parser("set", help="refreshHz, sbsHz, brightness, volume, sunblock, 3d, gameOnGlasses, headTracking, auto3d, picture3d, screenWidthDm, screenDistanceDm, vrFovDeg")
|
||||
glasses_set.add_argument("key")
|
||||
glasses_set.add_argument("value")
|
||||
glasses_sim = glasses_ops.add_parser("simulate", help="feed a moving head pose (yaw, pitch and roll sweeps), or hold one")
|
||||
|
||||
@@ -387,7 +387,8 @@ public:
|
||||
VRProperties()->SetStringProperty(c, Prop_ModelNumber_String, "DroidDeck Pad Hand");
|
||||
VRProperties()->SetStringProperty(c, Prop_ManufacturerName_String, "DroidDeck");
|
||||
VRProperties()->SetStringProperty(c, Prop_TrackingSystemName_String, "droiddeck");
|
||||
VRProperties()->SetStringProperty(c, Prop_RenderModelName_String, "generic_controller");
|
||||
VRProperties()->SetStringProperty(c, Prop_RenderModelName_String,
|
||||
right_ ? "{indexcontroller}valve_controller_knu_1_0_right" : "{indexcontroller}valve_controller_knu_1_0_left");
|
||||
VRProperties()->SetStringProperty(c, Prop_ControllerType_String, "vive_controller");
|
||||
VRProperties()->SetStringProperty(c, Prop_InputProfilePath_String, "{htc}/input/vive_controller_profile.json");
|
||||
VRProperties()->SetInt32Property(c, Prop_ControllerRoleHint_Int32,
|
||||
@@ -414,6 +415,20 @@ public:
|
||||
|
||||
DriverPose_t GetPose() override { return pose_; }
|
||||
|
||||
void Disconnect() {
|
||||
if (id_ == k_unTrackedDeviceIndexInvalid) return;
|
||||
DriverPose_t p = {};
|
||||
p.qWorldFromDriverRotation.w = 1;
|
||||
p.qDriverFromHeadRotation.w = 1;
|
||||
p.qRotation.w = 1;
|
||||
p.result = TrackingResult_Uninitialized;
|
||||
pose_ = p;
|
||||
VRServerDriverHost()->TrackedDevicePoseUpdated(id_, p, sizeof(p));
|
||||
auto in = VRDriverInput();
|
||||
for (VRInputComponentHandle_t h : {system_, menu_, grip_, triggerClick_, padClick_, padTouch_}) in->UpdateBooleanComponent(h, false, 0);
|
||||
for (VRInputComponentHandle_t h : {trigger_, padX_, padY_}) in->UpdateScalarComponent(h, 0.0f, 0);
|
||||
}
|
||||
|
||||
// From the driver's pose thread: the head's orientation and the pad.
|
||||
static constexpr float kReachSpeed = 0.6f, kReachMin = -0.2f, kReachMax = 1.0f;
|
||||
|
||||
@@ -685,7 +700,17 @@ private:
|
||||
if (havePad && pad.axes[7] && !CardboardController::Grabbing(pad, false) && !CardboardController::Grabbing(pad, true))
|
||||
reach_ = std::clamp(reach_ - (pad.axes[7] > 0 ? 1.0f : -1.0f) * CardboardController::kReachSpeed * dt,
|
||||
CardboardController::kReachMin, CardboardController::kReachMax);
|
||||
for (CardboardController *hand : hands_) if (hand) hand->Update(head.qRotation, havePad ? &pad : nullptr, sticksMoveHands_, reach_);
|
||||
if (now - lastScan_ > std::chrono::seconds(1)) {
|
||||
lastScan_ = now;
|
||||
const bool other = OtherControllers();
|
||||
if (other != otherControllers_) {
|
||||
otherControllers_ = other;
|
||||
Log("%s", other ? "other VR controllers connected: pad hands off" : "no other VR controllers: pad hands on");
|
||||
if (other) for (CardboardController *hand : hands_) if (hand) hand->Disconnect();
|
||||
}
|
||||
}
|
||||
if (!otherControllers_)
|
||||
for (CardboardController *hand : hands_) if (hand) hand->Update(head.qRotation, havePad ? &pad : nullptr, sticksMoveHands_, reach_);
|
||||
if (havePad) lastPad_ = pad;
|
||||
lastSent = now;
|
||||
}
|
||||
@@ -693,6 +718,18 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
static bool OtherControllers() {
|
||||
TrackedDevicePose_t poses[k_unMaxTrackedDeviceCount];
|
||||
VRServerDriverHost()->GetRawTrackedDevicePoses(0.0f, poses, k_unMaxTrackedDeviceCount);
|
||||
for (uint32_t i = 0; i < k_unMaxTrackedDeviceCount; i++) {
|
||||
if (!poses[i].bDeviceIsConnected) continue;
|
||||
PropertyContainerHandle_t c = VRProperties()->TrackedDeviceToPropertyContainer(i);
|
||||
if (VRProperties()->GetInt32Property(c, Prop_DeviceClass_Int32) != TrackedDeviceClass_Controller) continue;
|
||||
if (VRProperties()->GetStringProperty(c, Prop_TrackingSystemName_String) != "droiddeck") return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t id_ = k_unTrackedDeviceIndexInvalid;
|
||||
int32_t width_ = 1920, height_ = 1080;
|
||||
std::atomic<float> tanHalf_{1.0f}, eyeAspect_{0.889f}, renderScale_{1.4f};
|
||||
@@ -710,6 +747,8 @@ private:
|
||||
bool sticksMoveHands_ = true;
|
||||
float reach_ = 0.0f;
|
||||
std::chrono::steady_clock::time_point lastReach_ = std::chrono::steady_clock::now();
|
||||
std::chrono::steady_clock::time_point lastScan_{};
|
||||
bool otherControllers_ = false;
|
||||
CardboardController *hands_[2] = {nullptr, nullptr};
|
||||
bool virtualDisplay_ = false, directMode_ = false;
|
||||
VirtualDisplay vd_;
|
||||
|
||||
Reference in new issue
Block a user