Skip to content
Open
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
82 changes: 67 additions & 15 deletions app/src/main/java/com/droiddeck/launcher/SessionActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
}
private var unfoldedPanel by mutableStateOf<OnScreenControls?>(null)
private var unfoldedColumn: android.widget.LinearLayout? = null
private var unfoldedDeckSurface: com.droiddeck.launcher.ui.InputSurfaceView? = null
private var unfoldedDeckPanel by mutableStateOf<com.droiddeck.launcher.input.DeckPanel?>(null)
private var deckPanelLayouts: Map<com.droiddeck.launcher.input.DeckPanel, com.droiddeck.launcher.input.SavedInputLayout?> = emptyMap()
private lateinit var sessionRoot: FrameLayout
private var inputRouter: InputRouter? = null
private var physicalInputBindings: com.droiddeck.launcher.input.PhysicalInputBindings? = null
Expand Down Expand Up @@ -564,6 +567,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
private var sessionActivityDisplayId = Display.DEFAULT_DISPLAY
private lateinit var displayManager: DisplayManager
private var secondScreenPresentation: SecondScreenPresentation? = null
private val inputWindowDisplays = mutableListOf<Int>()
private val bottomScreenListener: () -> Unit = {
if (secondScreenPresentation?.display?.displayId?.let(com.droiddeck.launcher.input.DualScreen::taken) == true) closeSecondScreen(reset = false)
updateBottomGame()
Expand Down Expand Up @@ -1221,7 +1225,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
textureLodBias = if (drawerGameProfileId == null) textureLodBias else drawerTextureLodBias,
frameGen = frameGen,
lossless = lossless,
oscMode = oscMode, onScreenButtonsVisible = onScreenButtonsVisible, suspendPolicy = suspendPolicy,
oscMode = oscMode, suspendPolicy = suspendPolicy,
steamDownloadsInBackground = steamDownloadsInBackground, touchMode = touchMode,
micAvailable = SessionState.micAvailable && !SessionState.suspended,
micEnabled = SessionState.micEnabled,
Expand Down Expand Up @@ -1442,6 +1446,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
}
},
onClose = { drawerOpen = false },
onDismissScreen = if (docked) ({ selectSecondScreenMode(SecondScreenMode.NONE) }) else null,
components = drawerComponents,
onComponentsRefresh = {
refreshDrawerComponents()
Expand Down Expand Up @@ -2825,6 +2830,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
return onScreenControls?.let { takes(it, it::takesTouchAt) } == true ||
steamTouchControls?.let { takes(it, it::takesTouchAt) } == true ||
unfoldedPanel?.let { takes(it, it::takesTouchAt) } == true ||
unfoldedDeckSurface?.let { takes(it, it::takesTouchAt) } == true ||
unfoldedColumn?.getChildAt(0)?.let { takes(it) { _, _ -> true } } == true
}

Expand Down Expand Up @@ -3377,6 +3383,10 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
}
var presentation = secondScreenPresentation
if (presentation?.display?.displayId != target.displayId) {
if (target.displayId in inputWindowDisplays) {
panelMode(mode)
return
}
dropDockedDrawer()
presentation?.closeControls()
presentation = secondScreenPresentation(target)
Expand Down Expand Up @@ -3517,8 +3527,15 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
val main = window.decorView.display
if (role == InputRole.PRIMARY) return bottomGame?.display ?: main
if (bottomGame != null) return main
return displayManager.getDisplay(selectedSecondScreenDisplay)
?: secondScreenDisplays.firstOrNull()?.let { displayManager.getDisplay(it.id) }
return com.droiddeck.launcher.input.SecondScreenDisplays.editorCandidates(selectedSecondScreenDisplay,
secondScreenDisplays.map { it.id }, com.droiddeck.launcher.input.DualScreen::taken).firstNotNullOfOrNull(displayManager::getDisplay)
}

fun inputWindowShown(displayId: Int) { inputWindowDisplays += displayId }

fun inputWindowClosed(displayId: Int) {
inputWindowDisplays -= displayId
if (started && displayId !in inputWindowDisplays && secondScreenMode != SecondScreenMode.NONE && secondScreenPresentation == null) showSecondScreen(secondScreenMode)
}

private fun refreshInputProfile() {
Expand All @@ -3529,6 +3546,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
adaptiveSticks = primaryInputLayout?.adaptiveSticks
quickButtons = primaryInputLayout?.takeIf { it.editable }?.let { l -> l.quickButtons.associateWith { it !in l.style.hidden } }.orEmpty()
secondaryInputLayout = library.panelLayout(desktop, secondScreenMode.panel(this))
deckPanelLayouts = com.droiddeck.launcher.input.DeckPanel.entries.associateWith { library.panelLayout(desktop, it.bindings) }
inputUsesLocalActions = InputControl.padControls.any { inputProfile.action(InputRole.PHYSICAL, it) !is InputAction.Native }
inputUsesPointer = inputProfile.bindings.values.any { bindings ->
bindings.values.any { it is InputAction.Pointer || it is InputAction.MouseButton || it is InputAction.Scroll }
Expand All @@ -3542,6 +3560,8 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
onScreenControls?.updateLayout(primaryInputLayout)
bottomInputControls?.updateLayout(primaryInputLayout)
unfoldedPanel?.updateLayout(primaryInputLayout)
if (unfoldedPanel != null) applyUnfoldedControls()
showUnfoldedDeckPanel()
updateOnScreenPad()
secondScreenPresentation?.refreshControls()
}
Expand Down Expand Up @@ -3593,11 +3613,15 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
Log.i(TAG, if (wanted) "unfolded controls: game ${width}x$gameHeight, controls ${width}x${height - gameHeight}"
else "unfolded controls: game full screen")
}
val dock = com.droiddeck.launcher.input.OnScreenLayout.Dock(!com.droiddeck.launcher.session.SessionDisplay.foldable(this), SessionState.deckPad)
val portrait = !com.droiddeck.launcher.session.SessionDisplay.foldable(this)
val dock = if (SessionState.deckPad) com.droiddeck.launcher.input.OnScreenLayout.Dock.deck(portrait, deckPanelLayouts[com.droiddeck.launcher.input.DeckPanel.GAMEPAD])
else com.droiddeck.launcher.input.OnScreenLayout.Dock(portrait, false)
var panel = unfoldedPanel
if (panel != null && (!wanted || panel.dock != dock)) {
sessionRoot.removeView(unfoldedColumn)
unfoldedColumn = null
unfoldedDeckSurface = null
unfoldedDeckPanel = null
unfoldedPanel = null
panel = null
if (!wanted) updateOnScreenControls()
Expand All @@ -3614,14 +3638,36 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
sessionRoot.addView(column, sessionRoot.indexOfChild(surfaceView) + 1)
unfoldedColumn = column
unfoldedPanel = panel
showUnfoldedDeckPanel()
updateOnScreenControls()
}
unfoldedColumn?.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, height - gameHeight, Gravity.BOTTOM)
}

private fun unfoldedBar(): View = com.droiddeck.launcher.ui.bottomScreenBarView(this) {
com.droiddeck.launcher.ui.sessionBarModel(getString(R.string.deck_tab_gamepad), { chooseUnfoldedControls(false) },
{ drawerOpen = true }, ::tapSteamButton, ::tapQam)
val deck = unfoldedDeckPanel
com.droiddeck.launcher.ui.sessionBarModel(getString(if (deck != null) R.string.second_mode_deck_controls else R.string.deck_tab_gamepad),
{ chooseUnfoldedControls(false) }, { drawerOpen = true }, ::tapSteamButton, ::tapQam,
tabs = deck?.let { com.droiddeck.launcher.ui.deckPanelTabs(this, it, ::selectDeckPanel) })
}

private fun showUnfoldedDeckPanel() {
val column = unfoldedColumn
val panel = unfoldedPanel
val selected = SessionPrefs.deckPanel(this).takeIf { column != null && panel?.dock?.grips == true }
unfoldedDeckPanel = selected
val layout = selected?.takeIf { it != com.droiddeck.launcher.input.DeckPanel.GAMEPAD }?.let { deckPanelLayouts[it] }
if (layout != null && unfoldedDeckSurface?.tag == layout) return
unfoldedDeckSurface?.let { it.releaseInputs(); column?.removeView(it) }
unfoldedDeckSurface = null
if (column == null || panel == null) return
if (layout == null) { panel.visibility = View.VISIBLE; return }
panel.releaseAll()
panel.visibility = View.GONE
unfoldedDeckSurface = com.droiddeck.launcher.ui.InputSurfaceView(this, padBridge, layout.elements, InputRole.PRIMARY, layout.style, haptics = true).also {
it.tag = layout
column.addView(it, android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f))
}
}

private fun bottomKeyboardPresentation(): SecondScreenPresentation? {
Expand Down Expand Up @@ -4244,7 +4290,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
updateOnScreenPad()
val c = onScreenControls
glassesSlot = when {
c == null || c.visibility != View.VISIBLE -> 0
c == null || c.visibility != View.VISIBLE || c.isPinnedOnly -> 0
c.isButtonsOnly -> -1
primaryInputLayout?.let { l -> l.elements.any { it.quickButton == InputCommand.KEYBOARD && !l.style.hides(it) } }
?: (controllerSettings?.keyboardButton != false) -> 2
Expand All @@ -4259,12 +4305,17 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
?.let { FileUtils.readString(it)?.trim()?.lowercase() }
?: SessionPrefs.oscMode(this)
val controls = onScreenControls ?: return
fun showPinned() {
controls.setPinnedOnly(true)
val pinned = controls.hasPinned && !pipUi && !inputSettingsOpen
if (!pinned) controls.releaseAll()
controls.visibility = if (pinned) View.VISIBLE else View.GONE
}
if (pipUi || unfoldedPanel != null || pcKeyboardOpen || inputSettingsOpen ||
(SessionState.mode == SessionService.MODE_STEAM && !SessionPrefs.builtinPad(this))) {
onScreenButtonsVisible = false
hideBottomControls()
controls.releaseAll()
controls.visibility = View.GONE
showPinned()
steamTouchControls?.let { it.cancelEditing(); it.releaseAll(); it.visibility = View.GONE }
com.droiddeck.launcher.input.SteamTouchDevice.current?.plugged = false
applyTouchRouting()
Expand Down Expand Up @@ -4297,7 +4348,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
val drawn = show && !keyboardCoversPad()
if (steam != null) {
hideBottomControls()
if (controls.visibility == View.VISIBLE) { controls.releaseAll(); controls.visibility = View.GONE }
showPinned()
if (!drawn) { steam.cancelEditing(); steam.releaseAll() }
steam.visibility = if (drawn) View.VISIBLE else View.GONE
com.droiddeck.launcher.input.SteamTouchDevice.current?.plugged = show
Expand All @@ -4306,7 +4357,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
return
}
val bottomRoot = bottomGame?.surfaceView?.parent as? android.view.ViewGroup
if (drawn && bottomRoot != null) {
if (bottomRoot != null && (drawn || controls.hasPinned)) {
controls.releaseAll(); controls.visibility = View.GONE
var bottom = bottomInputControls
if (bottom?.parent !== bottomRoot) {
Expand All @@ -4319,13 +4370,14 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
}
bottom.updateLayout(primaryInputLayout)
bottom.setButtonsOnly(buttonsOnly)
bottom.setPinnedOnly(!drawn)
bottom.visibility = View.VISIBLE
return
}
hideBottomControls()
if (drawn == (controls.visibility == View.VISIBLE)) return
if (!drawn) controls.releaseAll()
controls.visibility = if (drawn) View.VISIBLE else View.GONE
val wasDrawn = controls.visibility == View.VISIBLE && !controls.isPinnedOnly
if (!drawn) showPinned() else { controls.setPinnedOnly(false); controls.visibility = View.VISIBLE }
if (drawn == wasDrawn) return
Log.i(TAG, "on-screen controls " + when {
!drawn -> "hidden"
buttonsOnly -> "Steam + QAM"
Expand Down Expand Up @@ -5063,7 +5115,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
com.droiddeck.launcher.ui.BottomScreenBar(bar(getString(SecondScreenMode.KEYBOARD_TRACKPAD.label), listOf(keyboardExtra)))
SecondScreenMode.DECK_CONTROLS -> com.droiddeck.launcher.ui.BottomScreenBar(bar(getString(SecondScreenMode.DECK_CONTROLS.label),
tabs = com.droiddeck.launcher.ui.deckPanelTabs(this@SessionActivity, SessionPrefs.deckPanel(this@SessionActivity), ::selectDeckPanel)))
else -> Unit
else -> com.droiddeck.launcher.ui.BottomScreenBar(bar(getString(secondScreenMode.label)))
}
androidx.compose.runtime.key(profileLayout) {
androidx.compose.ui.viewinterop.AndroidView(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.hardware.display.DisplayManager
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import android.os.SystemClock
import android.util.Log
import android.view.Display
import android.view.accessibility.AccessibilityEvent
Expand All @@ -33,6 +34,8 @@ object BottomScreenGuests {
else -> null
}

internal fun current(arrivedAt: Long, ownShownAt: Long?): Boolean = ownShownAt == null || arrivedAt >= ownShownAt

internal fun ignorable(type: Int): Boolean = type == AccessibilityWindowInfo.TYPE_INPUT_METHOD ||
type == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY ||
type == AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER ||
Expand All @@ -43,7 +46,7 @@ object BottomScreenGuests {
}

class BottomScreenWatch(private val service: AccessibilityService) {
private class Arrival(val pkg: String)
private class Arrival(val pkg: String, val at: Long)

private val thread = HandlerThread("bottom-screen").apply { start() }
private val handler = Handler(thread.looper)
Expand All @@ -52,6 +55,8 @@ class BottomScreenWatch(private val service: AccessibilityService) {
private var homes: Set<String> = emptySet()
private var keyboards: Set<String> = emptySet()
private var holders: Map<Int, String?> = emptyMap()
private var ownWindows: Map<Int, Set<Int>> = emptyMap()
private val ownShownAt = mutableMapOf<Int, Long>()
private val check = Runnable { evaluate() }

fun start() {
Expand All @@ -72,7 +77,7 @@ class BottomScreenWatch(private val service: AccessibilityService) {
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> {
val pkg = event.packageName?.toString() ?: return
if (event.displayId == Display.DEFAULT_DISPLAY || pkg == own || pkg == SYSTEM_UI) return
arrivals[event.displayId] = Arrival(pkg)
arrivals[event.displayId] = Arrival(pkg, event.eventTime)
}
AccessibilityEvent.TYPE_WINDOWS_CHANGED -> Unit
else -> return
Expand All @@ -97,19 +102,25 @@ class BottomScreenWatch(private val service: AccessibilityService) {
val all = runCatching { service.windowsOnAllDisplays }.getOrNull() ?: return
val displays = service.getSystemService(DisplayManager::class.java) ?: return
val taken = mutableMapOf<Int, String?>()
val shown = mutableMapOf<Int, Set<Int>>()
val now = SystemClock.uptimeMillis()
for (i in 0 until all.size()) {
val id = all.keyAt(i)
if (id == Display.DEFAULT_DISPLAY) continue
val display = displays.getDisplay(id)?.takeIf(DualScreen::builtIn) ?: continue
val windows = all.valueAt(i)
val topPkg = windows.asSequence()
.filter { !BottomScreenGuests.ignorable(it.type) && cover(it, display) >= BottomScreenGuests.MIN_COVER }
.sortedByDescending { it.layer }
.firstNotNullOfOrNull { packageOf(it)?.takeUnless { pkg -> pkg == own } }
val arrival = seen[id]
if (topPkg != null && arrival != null) arrivals.remove(id, arrival)
.map { it to packageOf(it) }
val ours = windows.filter { it.second == own }.map { it.first.id }.toSet()
if (!ownWindows[id].orEmpty().containsAll(ours)) ownShownAt[id] = now
shown[id] = ours
val topPkg = windows.firstNotNullOfOrNull { it.second?.takeUnless { pkg -> pkg == own } }
val arrival = seen[id]?.takeIf { BottomScreenGuests.current(it.at, ownShownAt[id]) }
seen[id]?.takeIf { topPkg != null || arrival == null }?.let { arrivals.remove(id, it) }
BottomScreenGuests.holder(topPkg, arrival?.pkg?.takeUnless { it in keyboards }, homes)?.let { taken[id] = it }
}
ownWindows = shown
if (current !== this) return
if (taken != holders) Log.i(TAG, "bottom screen holders: $taken")
holders = taken
Expand Down
18 changes: 3 additions & 15 deletions app/src/main/java/com/droiddeck/launcher/input/ControllerPrefs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ object ControllerPrefs {
keyboardButton = p.getBoolean("keyboardButton", true),
selectSteam = p.getBoolean("selectSteam", false),
joyConVr = joyConVr(context),
snap = p.getBoolean("snap", true),
snap = snap(context),
customLayout = p.all.keys.any { it.startsWith("layout.") },
mapping = mappable.associate { (id, _) -> id to target(context, id) },
deadZone = deadZone(context),
Expand All @@ -87,11 +87,6 @@ object ControllerPrefs {
prefs(context).edit().putString("map.$id", target).apply()
}

fun resetMapping(context: Context) {
val p = prefs(context)
p.edit().apply { p.all.keys.filter { it.startsWith("map.") }.forEach { remove(it) } }.apply()
}

fun setTint(context: Context, tint: Int) {
prefs(context).edit().putInt("tint", tint).apply()
}
Expand Down Expand Up @@ -129,10 +124,6 @@ object ControllerPrefs {
prefs(context).edit().putBoolean("steamButton", on).apply()
}

fun setQamButton(context: Context, on: Boolean) {
prefs(context).edit().putBoolean("qamButton", on).apply()
}

fun setKeyboardButton(context: Context, on: Boolean) {
prefs(context).edit().putBoolean("keyboardButton", on).apply()
}
Expand All @@ -147,6 +138,8 @@ object ControllerPrefs {
prefs(context).edit().putBoolean("joyConVr", on).apply()
}

fun snap(context: Context): Boolean = prefs(context).getBoolean("snap", true)

fun setSnap(context: Context, on: Boolean) {
prefs(context).edit().putBoolean("snap", on).apply()
}
Expand All @@ -172,9 +165,4 @@ object ControllerPrefs {
fun resetAll(context: Context) {
prefs(context).edit().clear().putBoolean("rumble", true).apply()
}

fun resetAllLayouts(context: Context) {
val p = prefs(context)
p.edit().apply { p.all.keys.filter { it.startsWith("layout.") }.forEach { remove(it) } }.apply()
}
}
Loading
Loading