mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-17 20:57:11 -04:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2a7723b1b | ||
|
|
7243228039 |
No files matched your search
@@ -151,6 +151,15 @@
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.SecondScreenActivity"
|
||||
android:exported="false"
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@style/SessionTheme"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density" />
|
||||
|
||||
<activity
|
||||
android:name=".gpu.SteamVrRestartActivity"
|
||||
android:exported="false"
|
||||
|
||||
@@ -107,7 +107,7 @@ import com.droiddeck.launcher.ui.HudText
|
||||
import com.droiddeck.launcher.ui.LoadingOverlay
|
||||
import com.droiddeck.launcher.ui.SessionDrawer
|
||||
import com.droiddeck.launcher.ui.SessionPausedOverlay
|
||||
import com.droiddeck.launcher.ui.SecondScreenPresentation
|
||||
import com.droiddeck.launcher.ui.SecondScreenWindow
|
||||
import com.droiddeck.launcher.ui.DroidDeckTheme
|
||||
import com.droiddeck.launcher.wayland.CompositorHost
|
||||
import com.droiddeck.launcher.wayland.WaylandCompositor
|
||||
@@ -343,7 +343,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
private var selectedSecondScreenDisplay by mutableStateOf(SessionState.secondScreenDisplay)
|
||||
private var started = false
|
||||
private lateinit var displayManager: DisplayManager
|
||||
private var secondScreenPresentation: SecondScreenPresentation? = null
|
||||
private var secondScreenPresentation: SecondScreenWindow? = null
|
||||
private val displayListener = object : DisplayManager.DisplayListener {
|
||||
override fun onDisplayAdded(displayId: Int) = refreshSecondScreenDisplays()
|
||||
override fun onDisplayRemoved(displayId: Int) = refreshSecondScreenDisplays()
|
||||
@@ -2118,7 +2118,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
var presentation = secondScreenPresentation
|
||||
if (presentation?.display?.displayId != target.displayId) {
|
||||
presentation?.closeControls()
|
||||
presentation = SecondScreenPresentation(
|
||||
presentation = SecondScreenWindow(
|
||||
this, target,
|
||||
sendPointer = ::moveSecondScreenPointer,
|
||||
sendButton = { button, pressed ->
|
||||
|
||||
@@ -1425,6 +1425,7 @@ class SessionService : Service() {
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
if (rootIntent?.component?.className == com.droiddeck.launcher.ui.SecondScreenActivity::class.java.name) return
|
||||
if (pipTask || xrActivityVisible) {
|
||||
Log.i(TAG, "panel task dismissed - retaining the session under its active surface policy")
|
||||
activityVisible = false
|
||||
|
||||
+9
-48
@@ -1,21 +1,17 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
import com.droiddeck.launcher.R
|
||||
import android.app.Presentation
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Display
|
||||
import android.view.Gravity
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewConfiguration
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Button
|
||||
import android.widget.FrameLayout
|
||||
@@ -37,9 +33,9 @@ import com.termux.terminal.TerminalSessionClient
|
||||
import com.termux.view.TerminalView
|
||||
import com.termux.view.TerminalViewClient
|
||||
|
||||
class SecondScreenPresentation(
|
||||
internal class SecondScreenControls(
|
||||
context: Context,
|
||||
private val sessionActivity: SessionActivity,
|
||||
display: Display,
|
||||
private val sendPointer: (x: Float, y: Float, width: Float, height: Float) -> Unit,
|
||||
private val sendButton: (button: Int, pressed: Boolean) -> Unit,
|
||||
private val sendWheel: (steps: Int) -> Unit,
|
||||
@@ -47,12 +43,8 @@ class SecondScreenPresentation(
|
||||
private val onQam: () -> Unit,
|
||||
private val onClose: () -> Unit,
|
||||
private val pad: PadBridge? = null,
|
||||
) : Presentation(sessionActivity, display) {
|
||||
private val root = LinearLayout(context).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setBackgroundColor(Color.rgb(15, 18, 22))
|
||||
isFocusableInTouchMode = true
|
||||
}
|
||||
) : LinearLayout(context) {
|
||||
private val root get() = this
|
||||
private var currentMode = SecondScreenMode.NONE
|
||||
private var keyboardHost: KeyboardHost? = null
|
||||
private var terminalSession: TerminalSession? = null
|
||||
@@ -60,35 +52,22 @@ class SecondScreenPresentation(
|
||||
private var terminalPid = -1
|
||||
private var ctrlActive = false
|
||||
private var altActive = false
|
||||
private var created = false
|
||||
|
||||
init {
|
||||
setCancelable(false)
|
||||
setCanceledOnTouchOutside(false)
|
||||
setOnDismissListener { closeTerminal() }
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
created = true
|
||||
window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
setContentView(root)
|
||||
applyImmersiveMode()
|
||||
renderMode()
|
||||
orientation = VERTICAL
|
||||
setBackgroundColor(Color.rgb(15, 18, 22))
|
||||
isFocusableInTouchMode = true
|
||||
}
|
||||
|
||||
fun showMode(mode: SecondScreenMode) {
|
||||
if (currentMode == mode && isShowing) return
|
||||
if (currentMode == mode) return
|
||||
currentMode = mode
|
||||
if (created) renderMode()
|
||||
renderMode()
|
||||
}
|
||||
|
||||
fun closeControls() {
|
||||
closeTerminal()
|
||||
keyboardHost?.hide()
|
||||
keyboardHost = null
|
||||
dismiss()
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
@@ -98,14 +77,6 @@ class SecondScreenPresentation(
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
if (hasFocus) {
|
||||
applyImmersiveMode()
|
||||
sessionActivity.refreshClipboard()
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderMode() {
|
||||
keyboardHost?.hide()
|
||||
keyboardHost = null
|
||||
@@ -291,16 +262,6 @@ class SecondScreenPresentation(
|
||||
|
||||
private fun weightParams() = LinearLayout.LayoutParams(-1, 0, 1f)
|
||||
|
||||
private fun applyImmersiveMode() {
|
||||
@Suppress("DEPRECATION")
|
||||
window?.decorView?.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
|
||||
}
|
||||
|
||||
private inner class TrackpadView(context: Context) : View(context) {
|
||||
private val gestures = TouchpadGestures(ViewConfiguration.get(context).scaledTouchSlop.toFloat(),
|
||||
object : PointerGestures.Listener {
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Display
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import com.droiddeck.launcher.SessionActivity
|
||||
import com.droiddeck.launcher.input.PadBridge
|
||||
import com.droiddeck.launcher.input.SecondScreenMode
|
||||
import com.droiddeck.launcher.session.SessionState
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class SecondScreenWindow(
|
||||
private val sessionActivity: SessionActivity,
|
||||
val display: Display,
|
||||
private val sendPointer: (Float, Float, Float, Float) -> Unit,
|
||||
private val sendButton: (Int, Boolean) -> Unit,
|
||||
private val sendWheel: (Int) -> Unit,
|
||||
private val onSteamMenu: () -> Unit,
|
||||
private val onQam: () -> Unit,
|
||||
private val onClose: () -> Unit,
|
||||
private val pad: PadBridge? = null,
|
||||
) {
|
||||
private val token = tokens.incrementAndGet()
|
||||
private var activity = WeakReference<SecondScreenActivity>(null)
|
||||
private var controls: SecondScreenControls? = null
|
||||
private var mode = SecondScreenMode.NONE
|
||||
private var launching = false
|
||||
private var closed = false
|
||||
val window get() = activity.get()?.window
|
||||
val isShowing get() = !closed && (launching || activity.get()?.isFinishing == false)
|
||||
|
||||
fun show() {
|
||||
if (isShowing || closed) return
|
||||
owners[token] = WeakReference(this)
|
||||
launching = true
|
||||
try {
|
||||
val intent = Intent(sessionActivity, SecondScreenActivity::class.java)
|
||||
.putExtra(OWNER, token)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
||||
sessionActivity.startActivity(intent, ActivityOptions.makeBasic().setLaunchDisplayId(display.displayId).toBundle())
|
||||
} catch (error: Exception) {
|
||||
launching = false
|
||||
owners.remove(token)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
fun showMode(mode: SecondScreenMode) {
|
||||
this.mode = mode
|
||||
controls?.showMode(mode)
|
||||
}
|
||||
|
||||
fun closeControls() {
|
||||
closed = true
|
||||
launching = false
|
||||
owners.remove(token)
|
||||
controls?.closeControls()
|
||||
controls = null
|
||||
activity.get()?.finishAndRemoveTask()
|
||||
activity.clear()
|
||||
}
|
||||
|
||||
internal fun attach(host: SecondScreenActivity): View {
|
||||
controls?.closeControls()
|
||||
activity = WeakReference(host)
|
||||
launching = false
|
||||
return SecondScreenControls(host, sessionActivity, sendPointer, sendButton, sendWheel,
|
||||
onSteamMenu, onQam, onClose, pad).also {
|
||||
controls = it
|
||||
it.showMode(mode)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun detach(host: SecondScreenActivity) {
|
||||
if (activity.get() !== host) return
|
||||
controls?.closeControls()
|
||||
controls = null
|
||||
activity.clear()
|
||||
if (host.isFinishing && !closed) onClose()
|
||||
}
|
||||
|
||||
internal fun refreshClipboard() = sessionActivity.refreshClipboard()
|
||||
|
||||
companion object {
|
||||
private const val OWNER = "secondScreenOwner"
|
||||
private val tokens = AtomicLong()
|
||||
private val owners = mutableMapOf<Long, WeakReference<SecondScreenWindow>>()
|
||||
internal fun find(intent: Intent): SecondScreenWindow? =
|
||||
owners[intent.getLongExtra(OWNER, 0)]?.get()?.takeUnless { it.closed }
|
||||
}
|
||||
}
|
||||
|
||||
class SecondScreenActivity : Activity() {
|
||||
private var owner: SecondScreenWindow? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val host = SecondScreenWindow.find(intent)
|
||||
if (host == null || !SessionState.running) {
|
||||
finishAndRemoveTask()
|
||||
return
|
||||
}
|
||||
owner = host
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
setContentView(host.attach(this))
|
||||
applyImmersiveMode()
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
if (hasFocus) {
|
||||
applyImmersiveMode()
|
||||
owner?.refreshClipboard()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Back is handled by the session controls")
|
||||
override fun onBackPressed() = Unit
|
||||
|
||||
override fun onDestroy() {
|
||||
owner?.detach(this)
|
||||
owner = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun applyImmersiveMode() {
|
||||
@Suppress("DEPRECATION")
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.view.Display
|
||||
import com.droiddeck.launcher.SessionActivity
|
||||
import com.droiddeck.launcher.session.SessionService
|
||||
import com.droiddeck.launcher.session.SessionState
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.LooperMode
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = Application::class, manifest = Config.NONE, sdk = [28, 33])
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
class SecondScreenActivityTest {
|
||||
@Before fun setUp() {
|
||||
SessionState.running = true
|
||||
SessionState.stopRequested = false
|
||||
}
|
||||
|
||||
@After fun tearDown() {
|
||||
SessionState.running = false
|
||||
SessionState.stopRequested = false
|
||||
}
|
||||
|
||||
@Test fun removingControlsTaskDoesNotStopTheRunningSession() {
|
||||
val service = Robolectric.buildService(SessionService::class.java).get()
|
||||
service.onTaskRemoved(Intent(service, SecondScreenActivity::class.java))
|
||||
assertTrue(SessionState.running)
|
||||
assertFalse(SessionState.stopRequested)
|
||||
}
|
||||
|
||||
@Test fun activityRestoredWithoutItsSessionOwnerFinishes() {
|
||||
val activity = Robolectric.buildActivity(SecondScreenActivity::class.java).create().get()
|
||||
assertTrue(activity.isFinishing)
|
||||
assertTrue(SessionState.running)
|
||||
}
|
||||
|
||||
@Test fun closingBeforeTheSecondaryActivityStartsRejectsTheLateLaunch() {
|
||||
val session = Robolectric.buildActivity(SessionActivity::class.java).get()
|
||||
val display = session.getSystemService(DisplayManager::class.java).getDisplay(Display.DEFAULT_DISPLAY)
|
||||
val controls = SecondScreenWindow(session, display, { _, _, _, _ -> }, { _, _ -> }, {}, {}, {}, {})
|
||||
controls.show()
|
||||
val intent = shadowOf(session).nextStartedActivity
|
||||
controls.closeControls()
|
||||
val activity = Robolectric.buildActivity(SecondScreenActivity::class.java, intent).create().get()
|
||||
assertTrue(activity.isFinishing)
|
||||
assertFalse(controls.isShowing)
|
||||
assertTrue(SessionState.running)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user