mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cb895b27f | ||
|
|
4c03f019d4 | ||
|
|
3d4c894dff |
No files matched your search
@@ -65,6 +65,7 @@ import com.droiddeck.launcher.input.SessionClipboard
|
||||
import com.droiddeck.launcher.input.OnScreenControls
|
||||
import com.droiddeck.launcher.input.PadBridge
|
||||
import com.droiddeck.launcher.input.SteamControllerBle
|
||||
import com.droiddeck.launcher.input.DirectTouchHold
|
||||
import com.droiddeck.launcher.input.PointerGestures
|
||||
import com.droiddeck.launcher.input.SecondScreenDisplay
|
||||
import com.droiddeck.launcher.input.SecondScreenDisplays
|
||||
@@ -162,6 +163,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
}
|
||||
private var watching = true
|
||||
private lateinit var touchpad: TouchpadGestures
|
||||
private lateinit var directTouchHold: DirectTouchHold
|
||||
private var touchMode by mutableStateOf(SessionPrefs.TOUCH_AUTO)
|
||||
private var cursorPos by mutableStateOf(androidx.compose.ui.geometry.Offset(-100f, -100f))
|
||||
private var cursorVisible by mutableStateOf(false)
|
||||
@@ -397,6 +399,9 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
onScreenControls = OnScreenControls(this, bridge, onKeyboard = ::togglePcKeyboard).also { root.addView(it) }
|
||||
keyboard = KeyboardHost(this).also { root.addView(it) }
|
||||
touchpad = TouchpadGestures(PointerGestures.slop(this), pointerListener)
|
||||
directTouchHold = DirectTouchHold(PointerGestures.slop(this), pointerListener) {
|
||||
WaylandCompositor.nativeSendTouch(3, -1, 0, 0)
|
||||
}
|
||||
val noCursor = android.view.PointerIcon.getSystemIcon(this, android.view.PointerIcon.TYPE_NULL)
|
||||
root.pointerIcon = noCursor
|
||||
surfaceView.pointerIcon = noCursor
|
||||
@@ -1065,6 +1070,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
val nextTouchMode = SessionPrefs.touchMode(this)
|
||||
if (touchMode != nextTouchMode) {
|
||||
if (::touchpad.isInitialized) touchpad.cancel()
|
||||
if (::directTouchHold.isInitialized) directTouchHold.cancel()
|
||||
if (CompositorHost.isStarted) {
|
||||
WaylandCompositor.nativeSendTouch(3, -1, 0, 0)
|
||||
listOf(PointerGestures.BTN_LEFT, PointerGestures.BTN_RIGHT, PointerGestures.BTN_MIDDLE).forEach {
|
||||
@@ -1927,6 +1933,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
}
|
||||
if (next == routedTouchMode) return
|
||||
if (::touchpad.isInitialized) touchpad.cancel()
|
||||
if (::directTouchHold.isInitialized) directTouchHold.cancel()
|
||||
if (CompositorHost.isStarted) WaylandCompositor.nativeSendTouch(3, -1, 0, 0)
|
||||
if (next == SessionPrefs.TOUCH_OFF) cursorVisible = false
|
||||
routedTouchMode = next
|
||||
@@ -1963,6 +1970,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
return touchpad.onTouch(event)
|
||||
}
|
||||
val rect = drawnRect() ?: return false
|
||||
if (directTouchHold.onTouch(event)) return true
|
||||
fun sendTouch(action: Int, index: Int) {
|
||||
val x = event.getX(index).coerceIn(rect.left, rect.right)
|
||||
val y = event.getY(index).coerceIn(rect.top, rect.bottom)
|
||||
@@ -2101,6 +2109,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
controls.visibility = View.GONE
|
||||
steamTouchControls?.let { it.releaseAll(); it.visibility = View.GONE }
|
||||
com.droiddeck.launcher.input.SteamTouchDevice.current?.plugged = false
|
||||
applyTouchRouting()
|
||||
return
|
||||
}
|
||||
val buttonsOnly = forced == SessionPrefs.OSC_STEAM_QAM
|
||||
@@ -2113,6 +2122,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
if (steam == null) {
|
||||
steamTouchControls?.let { it.releaseAll(); it.visibility = View.GONE }
|
||||
com.droiddeck.launcher.input.SteamTouchDevice.current?.plugged = false
|
||||
applyTouchRouting()
|
||||
}
|
||||
controls.setButtonsOnly(buttonsOnly)
|
||||
val show = when (forced) {
|
||||
@@ -2174,6 +2184,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
onScreenControls?.releaseAll()
|
||||
padBridge?.releaseAll()
|
||||
touchpad.cancel()
|
||||
directTouchHold.cancel()
|
||||
guestKeysDown.toList().forEach { sendGuestKey(it, false) }
|
||||
keyboard?.hide()
|
||||
sessionClipboard.stop()
|
||||
@@ -2287,6 +2298,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
override fun onPause() {
|
||||
if (runtimeRemovalBlocked) { super.onPause(); return }
|
||||
sessionClipboard.stop()
|
||||
if (::directTouchHold.isInitialized) directTouchHold.cancel()
|
||||
drawerOpen = false
|
||||
pcKeyboardOpen = false
|
||||
achievementsOpen = false
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.droiddeck.launcher.input
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.MotionEvent
|
||||
import kotlin.math.abs
|
||||
|
||||
class DirectTouchHold(
|
||||
private val slopPx: Float,
|
||||
private val listener: PointerGestures.Listener,
|
||||
private val cancelTouch: () -> Unit,
|
||||
) {
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private var x = 0f
|
||||
private var y = 0f
|
||||
private var consumed = false
|
||||
private var pressed = false
|
||||
private val release = Runnable {
|
||||
if (pressed) listener.onButton(PointerGestures.BTN_RIGHT, false, x, y)
|
||||
pressed = false
|
||||
}
|
||||
private val hold = Runnable {
|
||||
cancelTouch()
|
||||
consumed = true
|
||||
listener.onLongPress()
|
||||
listener.onButton(PointerGestures.BTN_RIGHT, true, x, y)
|
||||
pressed = true
|
||||
handler.postDelayed(release, 50L)
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
handler.removeCallbacks(hold)
|
||||
handler.removeCallbacks(release)
|
||||
release.run()
|
||||
consumed = false
|
||||
}
|
||||
|
||||
fun onTouch(event: MotionEvent): Boolean {
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
cancel()
|
||||
x = event.x; y = event.y
|
||||
handler.postDelayed(hold, 500L)
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> if (event.pointerCount > 1 ||
|
||||
abs(event.x - x) > slopPx || abs(event.y - y) > slopPx) handler.removeCallbacks(hold)
|
||||
MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_POINTER_UP,
|
||||
MotionEvent.ACTION_UP -> handler.removeCallbacks(hold)
|
||||
MotionEvent.ACTION_CANCEL -> cancel()
|
||||
}
|
||||
return consumed
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.droiddeck.launcher.input
|
||||
|
||||
import android.os.Looper
|
||||
import android.view.MotionEvent
|
||||
import java.time.Duration
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [28], manifest = Config.NONE)
|
||||
class DirectTouchHoldTest {
|
||||
private val events = mutableListOf<String>()
|
||||
private val hold = DirectTouchHold(8f, object : PointerGestures.Listener {
|
||||
override fun onMove(x: Float, y: Float) {}
|
||||
override fun onButton(button: Int, pressed: Boolean, x: Float, y: Float) {
|
||||
assertEquals(PointerGestures.BTN_RIGHT, button)
|
||||
events += "$pressed at $x,$y"
|
||||
}
|
||||
override fun onWheel(steps: Int) {}
|
||||
override fun onLongPress() {}
|
||||
}) { events += "cancel touch" }
|
||||
|
||||
@Test fun holdReleasesRawTouchBeforeClickAndConsumesTheLift() {
|
||||
assertFalse(touch(MotionEvent.ACTION_DOWN))
|
||||
advance(500)
|
||||
assertEquals(listOf("cancel touch", "true at 40.0,30.0"), events)
|
||||
assertTrue(touch(MotionEvent.ACTION_UP))
|
||||
advance(50)
|
||||
assertEquals("false at 40.0,30.0", events.last())
|
||||
assertFalse(touch(MotionEvent.ACTION_DOWN))
|
||||
hold.cancel()
|
||||
}
|
||||
|
||||
@Test fun tapDragSecondFingerAndCancellationDoNotRightClick() {
|
||||
for (action in listOf(MotionEvent.ACTION_UP, MotionEvent.ACTION_MOVE,
|
||||
MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_CANCEL)) {
|
||||
touch(MotionEvent.ACTION_DOWN)
|
||||
assertFalse(touch(action, 60f))
|
||||
advance(600)
|
||||
assertTrue(events.isEmpty())
|
||||
hold.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun modeChangeCancelsPendingHoldAndReleasesAnActiveClick() {
|
||||
touch(MotionEvent.ACTION_DOWN)
|
||||
hold.cancel()
|
||||
advance(600)
|
||||
assertTrue(events.isEmpty())
|
||||
touch(MotionEvent.ACTION_DOWN)
|
||||
advance(500)
|
||||
hold.cancel()
|
||||
advance(100)
|
||||
assertEquals(listOf("cancel touch", "true at 40.0,30.0", "false at 40.0,30.0"), events)
|
||||
}
|
||||
|
||||
private fun advance(ms: Long) = shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(ms))
|
||||
private fun touch(action: Int, x: Float = 40f): Boolean {
|
||||
val event = MotionEvent.obtain(0, 0, action, x, 30f, 0)
|
||||
return hold.onTouch(event).also { event.recycle() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.droiddeck.launcher.input
|
||||
|
||||
import android.view.View
|
||||
import com.droiddeck.launcher.SessionActivity
|
||||
import com.droiddeck.launcher.session.SessionPrefs
|
||||
import com.droiddeck.launcher.session.SessionService
|
||||
import com.droiddeck.launcher.session.SessionState
|
||||
import java.nio.file.Files
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.util.ReflectionHelpers
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [28], manifest = Config.NONE)
|
||||
class SessionTouchRoutingTest {
|
||||
private lateinit var activity: SessionActivity
|
||||
private lateinit var steam: SteamTouchControls
|
||||
private lateinit var touchpad: TouchpadGestures
|
||||
private val directory = Files.createTempDirectory("touch-routing").toFile()
|
||||
|
||||
@Before fun setUp() {
|
||||
activity = Robolectric.buildActivity(SessionActivity::class.java).get()
|
||||
activity.getSharedPreferences("session", 0).edit().clear().commit()
|
||||
SessionState.mode = SessionService.MODE_STEAM
|
||||
SteamTouchDevice.prepare(directory)
|
||||
steam = SteamTouchControls(activity, SteamTouchDevice.current!!, {}, {})
|
||||
steam.visibility = View.VISIBLE
|
||||
touchpad = TouchpadGestures(8f, object : PointerGestures.Listener {
|
||||
override fun onMove(x: Float, y: Float) {}
|
||||
override fun onButton(button: Int, pressed: Boolean, x: Float, y: Float) {}
|
||||
override fun onWheel(steps: Int) {}
|
||||
override fun onLongPress() {}
|
||||
})
|
||||
field("onScreenControls", OnScreenControls(activity, null).apply { visibility = View.GONE })
|
||||
field("steamTouchControls", steam)
|
||||
field("steamTouchOptions", SteamTouchConfig.Options(inputMode = SteamTouchConfig.INPUT_MOUSE, mouseMode = SteamTouchConfig.MOUSE_ABSOLUTE, trackpadSensitivity = 2f))
|
||||
field("touchpad", touchpad)
|
||||
field("routedTouchMode", SessionPrefs.TOUCH_DIRECT)
|
||||
}
|
||||
|
||||
@After fun tearDown() {
|
||||
SteamTouchDevice.release()
|
||||
directory.deleteRecursively()
|
||||
SessionState.mode = SessionService.MODE_STEAM
|
||||
}
|
||||
|
||||
@Test fun disablingSteamTouchRestoresEverySavedModeEvenWhenRegularControlsAreAlreadyHidden() {
|
||||
for (mode in listOf(SessionPrefs.TOUCH_PAD, SessionPrefs.TOUCH_DIRECT, SessionPrefs.TOUCH_AUTO, SessionPrefs.TOUCH_OFF)) {
|
||||
SessionPrefs.setTouchMode(activity, mode)
|
||||
SessionPrefs.setOscMode(activity, SessionPrefs.OSC_NEVER)
|
||||
steam.visibility = View.VISIBLE
|
||||
invoke("readPrefs")
|
||||
assertEquals(SessionPrefs.TOUCH_DIRECT, ReflectionHelpers.getField<String>(activity, "routedTouchMode"))
|
||||
invoke("updateOnScreenControls")
|
||||
assertEquals(View.GONE, steam.visibility)
|
||||
assertEquals(mode, ReflectionHelpers.getField<String>(activity, "routedTouchMode"))
|
||||
assertEquals(1.15f, touchpad.sensitivity, 0f)
|
||||
}
|
||||
}
|
||||
|
||||
private fun field(name: String, value: Any) = ReflectionHelpers.setField(activity, name, value)
|
||||
private fun invoke(name: String) = ReflectionHelpers.callInstanceMethod<Any?>(activity, name)
|
||||
}
|
||||
@@ -11,20 +11,8 @@ diff --git a/src/syscall/seccomp.c b/src/syscall/seccomp.c
|
||||
index ee7b8acd68660f38fd52f56e951ffb94e9061d68..ff3a79b60e9092a296bb444c919bb3861338727f 100644
|
||||
--- a/src/syscall/seccomp.c
|
||||
+++ b/src/syscall/seccomp.c
|
||||
@@ -738,4 +738,4 @@ int enable_syscall_filtering(const Tracee *tracee)
|
||||
- * the statx one emulates the call when it fails, which only a kernel
|
||||
- * before 4.11 does for want of it - a statx Android's policy traps is
|
||||
- * emulated from SIGSYS instead. Both stops re-read and re-translate
|
||||
- * the path, and are paid on every call.
|
||||
+ * the statx one emulates the syscall before 4.11 and supplies MNT_ID
|
||||
+ * from fdinfo through 5.7. Android policy traps are still emulated from
|
||||
+ * SIGSYS instead. Both stops re-read and re-translate the path, and are
|
||||
+ * paid on every call.
|
||||
@@ -764 +764,4 @@ static void drop_unneeded_exit_stops(void)
|
||||
@@ -764 +764 @@ static void drop_unneeded_exit_stops(void)
|
||||
- if (major > 4 || (major == 4 && minor >= 11))
|
||||
+ /* Linux 4.11 introduced statx; Linux 5.8 added MNT_ID.
|
||||
+ * Keep its exit stop through 5.7 so the missing ID can be
|
||||
+ * recovered from the descriptor's fdinfo. */
|
||||
+ if (major > 5 || (major == 5 && minor >= 8))
|
||||
diff --git a/src/tracee/statx.c b/src/tracee/statx.c
|
||||
index ca972f892e84461cb694dd13a37bfbc67527b320..824771ccf5934e1f870fc36a29fb22fdcad0a856 100644
|
||||
@@ -32,11 +20,7 @@ index ca972f892e84461cb694dd13a37bfbc67527b320..824771ccf5934e1f870fc36a29fb22fd
|
||||
+++ b/src/tracee/statx.c
|
||||
@@ -4,0 +5 @@
|
||||
+#include "tracee/statx_mnt_id.h"
|
||||
@@ -125,0 +127,9 @@ int handle_statx_syscall(Tracee *tracee, bool from_sigsys) {
|
||||
+ /* libglnx chases an already-open fd with AT_EMPTY_PATH and requires a
|
||||
+ * mount ID. Kernels before 5.8 can succeed without returning that field.
|
||||
+ * The tracee is stopped at this syscall exit; accept only the ordinary
|
||||
+ * mount ID and verify the fdinfo inode against the statx result. */
|
||||
@@ -125,0 +127,5 @@ int handle_statx_syscall(Tracee *tracee, bool from_sigsys) {
|
||||
+ if (droiddeck_proot_statx_fill_mnt_id(tracee->pid, (int) dirfd,
|
||||
+ (int) flags, guest_path, (unsigned int) mask,
|
||||
+ &state.statx_buf))
|
||||
|
||||
Reference in new issue
Block a user