diff --git a/.github/workflows/build-gamescope.yml b/.github/workflows/build-gamescope.yml index afba4a948..57dd57501 100644 --- a/.github/workflows/build-gamescope.yml +++ b/.github/workflows/build-gamescope.yml @@ -19,7 +19,7 @@ permissions: contents: write concurrency: - group: gamescope-${{ github.event_name }}-${{ github.ref }}-${{ inputs.tag || 'artifact' }} + group: ${{ inputs.tag && format('gamescope-tag-{0}', inputs.tag) || format('gamescope-{0}-{1}', github.event_name, github.ref) }} cancel-in-progress: ${{ github.event_name == 'push' }} jobs: diff --git a/app/src/main/java/com/droiddeck/launcher/FrontEndActivity.kt b/app/src/main/java/com/droiddeck/launcher/FrontEndActivity.kt index 83e9538ec..22301e220 100644 --- a/app/src/main/java/com/droiddeck/launcher/FrontEndActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/FrontEndActivity.kt @@ -324,11 +324,8 @@ class FrontEndActivity : ComponentActivity() { SteamIntegration.Feature.HOME -> if (HomeApp.isDefault(this)) HomeApp.openSystemHomeSettings(this) else offerHomeScreen() SteamIntegration.Feature.SHORTCUTS -> { if (!integrationState.shortcuts) shortcutsTried = true - if (!HomeApp.isDefault(this)) configureIntegration(SteamIntegration.Feature.HOME) - else { - SteamIntegration.setShortcutsListed(this, true) - integrationSettings(SteamIntegration.shortcutSettings(this), android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS) - } + SteamIntegration.setShortcutsListed(this, true) + integrationSettings(SteamIntegration.shortcutSettings(this), android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS) } } } diff --git a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt index 56f5101be..6aa3354ba 100644 --- a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt @@ -224,6 +224,8 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { private var steamImportAsk by mutableStateOf(null) private var steamImportAnswered: String? = null private var directHoldBehavior by mutableStateOf(com.droiddeck.launcher.input.TouchGestureConfig.MOUSE_BEHAVIOR_CLICK) + private var relativeMouseMode by mutableIntStateOf(0) + @Volatile private var appliedRelativeMouseMode: Int? = null private var screenKeyPickerOpen by mutableStateOf(false) private var screenKeyBind: List? = null private var screenKeyDir: Int? = null @@ -257,20 +259,29 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { private val cursorPosBuf = IntArray(2) private var cursorSerial = 0 private var clientHidesCursor = false + private var mouseOwnsCursor = false private var pointerLocked = false + private var desktopGrabTapAt = 0L + private var desktopGrabTaps = 0 + private var desktopMousePlaced = false private val pointerLockListener = WaylandCompositor.PointerLockListener { locked, _, _ -> uiHandler.post { if (pointerLocked == locked) return@post gesturePointerReady = false pointerLocked = locked Log.i(TAG, "pointer lock " + if (locked) "taken by a program" else "released") - if (locked) openEvdevMouse() else closeEvdevMouse() + if (locked) desktopGrabTaps = 0 + if (programLocksPointer()) openEvdevMouse() else closeEvdevMouse() padMouseCheckedAt = 0L if (::padMouse.isInitialized) padMouseMode() if (locked && ::touchpad.isInitialized) { drawnRect()?.let { touchpad.place(it.centerX(), it.centerY()) } cursorVisible = false } + if (!locked && mouseOwnsCursor) { + uiHandler.removeCallbacks(cursorResync) + uiHandler.post(cursorResync) + } updatePointerCapture() } } @@ -283,6 +294,13 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { override fun run() { if (syncClientCursor() && clientHidesCursor) { cursorVisible = false + if (!mouseOwnsCursor) return + } + if (mouseOwnsCursor) { + if (pointerLocked) return + alignCursorToGuest() + if (!clientHidesCursor) cursorVisible = true + uiHandler.postDelayed(this, 60) return } if (touchDrivesPointer()) return @@ -564,6 +582,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { override fun onInputDeviceRemoved(deviceId: Int) { physicalShortcuts.onDeviceRemoved(deviceId) Log.i(TAG, "input device $deviceId disconnected") + dropMouseCursor() backQamInput.onDeviceRemoved(deviceId) joyConVr?.refresh() padBridge?.onDeviceRemoved(deviceId) @@ -644,6 +663,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { override fun dispatchPointerCaptureChanged(hasCapture: Boolean) { super.dispatchPointerCaptureChanged(hasCapture) Log.i(TAG, "pointer capture " + if (hasCapture) "on" else "off") + if (!hasCapture) uiHandler.postDelayed(::updatePointerCapture, 250) } } sessionRoot = root @@ -681,6 +701,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { bridge.setOnPlayerInput { lastPadInputMs = android.os.SystemClock.uptimeMillis() uiHandler.removeCallbacks(cursorHide) + mouseOwnsCursor = false cursorVisible = false } onScreenControls = OnScreenControls(this, bridge, onKeyboard = ::togglePcKeyboard).also { controls -> @@ -700,7 +721,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { } keys.onAdd = { screenKeyPickerOpen = true } keys.onSelect = { codes -> screenKeyEdit = screenKeys?.stateOf(codes) } - keys.onEditingChanged = { if (!it) screenKeyEdit = null } + keys.onEditingChanged = { touchEditorChanged(); if (!it) screenKeyEdit = null } keys.steamStyle = { steamTouchControls?.style } keys.visibility = if (screenKeysShown) View.VISIBLE else View.GONE root.addView(keys) @@ -1316,7 +1337,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { onEvdevMouse = { on -> SessionPrefs.setEvdevMouse(this@SessionActivity, on) evdevMouseOn = on - if (on) { if (pointerLocked) openEvdevMouse() } else releaseEvdevMouse() + if (on) { if (programLocksPointer()) openEvdevMouse() } else releaseEvdevMouse() }, directCursor = directCursorOn, onDirectCursor = { on -> @@ -1354,7 +1375,14 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { com.droiddeck.launcher.input.GesturePrefs.save(this@SessionActivity, gestureGame, base.copy(directHoldBehavior = value)) refreshGestureConfig() }, - onScreenKeys = { drawerOpen = false; screenKeys?.startEditing() }, + relativeMouseMode = relativeMouseMode, + onRelativeMouseMode = { mode -> + val base = com.droiddeck.launcher.input.GesturePrefs.read(this@SessionActivity, gestureGame) + com.droiddeck.launcher.input.GesturePrefs.save(this@SessionActivity, gestureGame, base.copy(relativeMouseMode = mode)) + relativeMouseMode = mode + setRelativeMouse(mode) + }, + onScreenKeys = { if (screenKeysShown) { drawerOpen = false; screenKeys?.startEditing() } }, screenKeysShown = screenKeysShown, onScreenKeysShown = { shown -> screenKeysShown = shown @@ -2647,7 +2675,9 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { if (joyConVr?.onKeyEvent(event) == true) return true if (fromController && event.keyCode != KeyEvent.KEYCODE_BACK && padMouseMode() && padMouse.onKey(event)) return true if (event.keyCode != KeyEvent.KEYCODE_BACK && padBridge?.onKeyEvent(event) == true) return true - if (CompositorHost.isStarted && event.keyCode != KeyEvent.KEYCODE_BACK && !fromController) { + val keyboardKey = event.keyCode != KeyEvent.KEYCODE_BACK || + com.droiddeck.launcher.input.GuestKeys.isKeyboardEscape(event.keyCode, event.scanCode) + if (CompositorHost.isStarted && keyboardKey && !fromController) { val down = event.action == KeyEvent.ACTION_DOWN if (down && event.repeatCount > 0) return true if (com.droiddeck.launcher.input.GuestKeys.send(event, ::sendGuestKey)) return true @@ -2694,6 +2724,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { if (event.isFromSource(android.view.InputDevice.SOURCE_MOUSE) && !drawerOpen) { if (appUiOwnsPointer()) { if (super.dispatchGenericMotionEvent(event)) return true + if (touchEditorOpen) return false if (onMouse(event)) return true return false } @@ -2847,9 +2878,48 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { private fun layoutNames(): List = com.droiddeck.launcher.input.ControllerPrefs.screenLayouts(this).keys.sorted() + private val relativeMouseRequests = java.util.concurrent.Executors.newSingleThreadExecutor { runnable -> + Thread(runnable, "relative-mouse").apply { isDaemon = true } + } + @Volatile private var desiredRelativeMouseMode = 0 + + @Volatile private var relativeMouseRetries = 0 + + private fun setRelativeMouse(mode: Int) { + desiredRelativeMouseMode = mode + relativeMouseRetries = 0 + requestRelativeMouse() + } + + private fun requestRelativeMouse() { + runCatching { relativeMouseRequests.execute { applyRelativeMouse() } } + } + + private fun applyRelativeMouse() { + val mode = desiredRelativeMouseMode + if (appliedRelativeMouseMode == mode) return + val request = org.json.JSONObject() + .put("kind", "exec") + .put("argv", org.json.JSONArray(listOf("gamescopectl", "relative_mouse", mode.toString()))) + .put("timeout", 5.0) + val result = runCatching { AgentGuest.call(this, request, 8_000, trusted = true) } + val answer = result.getOrNull() + val applied = answer?.optBoolean("ok") == true + && answer.optInt("exitCode", -1) == 0 + && !answer.optBoolean("timedOut") + if (applied) appliedRelativeMouseMode = mode + else { + Log.w(TAG, "gamescope did not take relative mouse $mode: ${result.exceptionOrNull() ?: answer}") + if (relativeMouseRetries++ < RELATIVE_MOUSE_MAX_RETRIES) uiHandler.postDelayed({ requestRelativeMouse() }, 2_000) + } + if (desiredRelativeMouseMode != mode) requestRelativeMouse() + } + private fun refreshGestureConfig() { gestureConfig = com.droiddeck.launcher.input.GesturePrefs.read(this, gestureGame) directHoldBehavior = gestureConfig.directHoldBehavior + relativeMouseMode = gestureConfig.relativeMouseMode + setRelativeMouse(relativeMouseMode) gestureTouchStreamGate.reset() if (::touchscreenGestures.isInitialized) touchscreenGestures.configure(gestureConfig) if (::directTouchHold.isInitialized) directTouchHold.configure(gestureConfig) @@ -3018,26 +3088,38 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { cursorVisible = false } - private fun movePointer(x: Float, y: Float) { + private fun movePointer(x: Float, y: Float, fromMouse: Boolean = false) { val rect = drawnRect() ?: return val cx = x.coerceIn(rect.left, rect.right) val cy = y.coerceIn(rect.top, rect.bottom) WaylandCompositor.nativeSendPointer(1, InputSpace.x(cx, outputView().width), InputSpace.y(cy, outputView().height)) - showCursor(cx, cy) + showCursor(cx, cy, fromMouse) } - private fun showCursor(x: Float, y: Float) { + private fun moveCapturedMouse(dx: Float, dy: Float) { + val rect = drawnRect() ?: return + val scale = sceneScale(rect) + WaylandCompositor.sendPointerDelta(dx, dy) + showCursor( + (cursorPos.x + dx / scale).coerceIn(rect.left, rect.right), + (cursorPos.y + dy / scale).coerceIn(rect.top, rect.bottom), + fromMouse = true, + ) + } + + private fun showCursor(x: Float, y: Float, fromMouse: Boolean = false) { cursorPos = androidx.compose.ui.geometry.Offset(x, y) syncClientCursor() + mouseOwnsCursor = fromMouse uiHandler.removeCallbacks(cursorResync) cursorResyncIdle = 0 - if (!touchDrivesPointer()) uiHandler.postDelayed(cursorResync, 60) + if (fromMouse || !touchDrivesPointer()) uiHandler.postDelayed(cursorResync, 60) if (clientHidesCursor) { cursorVisible = false; return } - if (directTouchCursorHidden()) { cursorVisible = false; return } + if (!fromMouse && directTouchCursorHidden()) { cursorVisible = false; return } if (android.os.SystemClock.uptimeMillis() - lastPadInputMs < CURSOR_PAD_HOLD_MS) return cursorVisible = true uiHandler.removeCallbacks(cursorHide) - if (SessionState.mode == SessionService.MODE_STEAM && SessionState.steamUi != "desktop") uiHandler.postDelayed(cursorHide, 2500) + if (!fromMouse && SessionState.mode == SessionService.MODE_STEAM && SessionState.steamUi != "desktop") uiHandler.postDelayed(cursorHide, 2500) } private fun touchDrivesPointer(): Boolean = @@ -3562,6 +3644,11 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { } private var ignoreGameTouch = false + private var lockedTouchTurns = false + private var touchEditorOpen by mutableStateOf(false) + private var lockedSwipeUsed = false + private var lockedSwipeX = 0f + private var lockedSwipeY = 0f private var dismissTouch = false override fun onTouchEvent(event: MotionEvent): Boolean { @@ -3604,11 +3691,15 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { } com.droiddeck.launcher.input.TouchRouter.Route.SCROLL -> scrollTouch.onTouch(event) else -> { + if (event.actionMasked == MotionEvent.ACTION_DOWN) { + val inPicture = drawnRect()?.let { !topPictureMapped || it.contains(event.x, event.y) } ?: false + lockedTouchTurns = programLocksPointer() && inPicture && route != com.droiddeck.launcher.input.TouchRouter.Route.TOUCHPAD + } if (com.droiddeck.launcher.input.TouchRouter.steamKeyboardZone(snapshot)) { val wasActive = threeFingerTap.active if (threeFingerTap.onTouch(event)) padBridge?.triggerSteamKeyboard() if (threeFingerTap.active && !wasActive) { - if (route == com.droiddeck.launcher.input.TouchRouter.Route.TOUCHPAD) touchpad.cancel() + if (route == com.droiddeck.launcher.input.TouchRouter.Route.TOUCHPAD || lockedTouchTurns) touchpad.cancel() else { directTouchHold.cancel() WaylandCompositor.nativeSendTouch(3, -1, 0, 0) @@ -3616,7 +3707,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { } if (threeFingerTap.active || wasActive) return true } - if (route == com.droiddeck.launcher.input.TouchRouter.Route.TOUCHPAD) { + if (route == com.droiddeck.launcher.input.TouchRouter.Route.TOUCHPAD || lockedTouchTurns) { val rect = drawnRect() ?: return false if (touchpad.bounds != rect) { val fresh = touchpad.bounds.width() <= 1f @@ -3668,8 +3759,25 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { } private fun forwardTouch(event: MotionEvent, rect: android.graphics.RectF): Boolean { + val swipeX = lockedSwipeX + val swipeY = lockedSwipeY + lockedSwipeX = event.x + lockedSwipeY = event.y + if ((event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL) && lockedSwipeUsed) { + lockedSwipeUsed = false + directTouchHold.cancel() + return true + } + if (event.actionMasked == MotionEvent.ACTION_DOWN) lockedSwipeUsed = false + if (programLocksPointer() && event.actionMasked == MotionEvent.ACTION_MOVE) { + lockedSwipeUsed = true + directTouchHold.cancelPendingHold() + val scale = sceneScale(rect) + sendSceneDelta((event.x - swipeX) * scale, (event.y - swipeY) * scale) + return true + } if (event.actionMasked == MotionEvent.ACTION_DOWN) { - showCursor(event.x.coerceIn(rect.left, rect.right), event.y.coerceIn(rect.top, rect.bottom)) + if (!pointerLocked) showCursor(event.x.coerceIn(rect.left, rect.right), event.y.coerceIn(rect.top, rect.bottom)) if (WaylandCompositor.nativeFocusTakesTouch()) { listOf(PointerGestures.BTN_LEFT, PointerGestures.BTN_RIGHT, PointerGestures.BTN_MIDDLE).forEach { WaylandCompositor.nativeSendSceneInput(3, it, 0) @@ -3683,12 +3791,14 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { private fun onMouse(event: MotionEvent): Boolean { when (event.actionMasked) { MotionEvent.ACTION_HOVER_MOVE, MotionEvent.ACTION_MOVE, MotionEvent.ACTION_HOVER_ENTER -> - if (evdevMouseActive) sendMouseDelta(event.x, event.y) else movePointer(event.x, event.y) + if (evdevMouseActive) sendMouseDelta(event.x, event.y) else movePointer(event.x, event.y, fromMouse = true) MotionEvent.ACTION_BUTTON_PRESS, MotionEvent.ACTION_BUTTON_RELEASE -> { - if (evdevMouseActive) sendMouseDelta(event.x, event.y) else movePointer(event.x, event.y) + if (evdevMouseActive) sendMouseDelta(event.x, event.y) else movePointer(event.x, event.y, fromMouse = true) val button = when (event.actionButton) { MotionEvent.BUTTON_SECONDARY -> PointerGestures.BTN_RIGHT MotionEvent.BUTTON_TERTIARY -> PointerGestures.BTN_MIDDLE + MotionEvent.BUTTON_BACK -> BTN_SIDE + MotionEvent.BUTTON_FORWARD -> BTN_EXTRA else -> PointerGestures.BTN_LEFT } sendSceneButton(button, event.actionMasked == MotionEvent.ACTION_BUTTON_PRESS) @@ -3703,23 +3813,79 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { return true } - private fun mouseAttached(): Boolean = InputDevice.getDeviceIds().any { id -> + private fun isHardwareMouse(d: InputDevice?): Boolean = + d != null && !d.isVirtual && (d.supportsSource(InputDevice.SOURCE_MOUSE) || d.supportsSource(InputDevice.SOURCE_MOUSE_RELATIVE)) + + private fun mouseAttached(): Boolean = InputDevice.getDeviceIds().any { isHardwareMouse(InputDevice.getDevice(it)) } + + private fun relativeMouseAttached(): Boolean = !isInMultiWindowMode && !glassesOutputActive && InputDevice.getDeviceIds().any { id -> val d = InputDevice.getDevice(id) - d != null && !d.isVirtual && d.supportsSource(InputDevice.SOURCE_MOUSE) + isHardwareMouse(d) && d?.supportsSource(InputDevice.SOURCE_TOUCHPAD) != true } private fun updatePointerCapture() { if (!::surfaceView.isInitialized) return val view = window.decorView - val want = pointerLocked && resumed && hasWindowFocus() && !drawerOpen && !pcKeyboardOpen && !achievementsOpen && !pipUi && !bottomOutputActive && mouseAttached() + val want = (pointerLocked || relativeMouseAttached()) && resumed && hasWindowFocus() && !drawerOpen && !pcKeyboardOpen && !achievementsOpen && !pipUi && !bottomOutputActive && !touchEditorOpen && mouseAttached() + releaseDesktopMouseGrab() if (want == view.hasPointerCapture()) return if (want) view.requestPointerCapture() else view.releasePointerCapture() } + private fun programLocksPointer(): Boolean = + pointerLocked && SessionState.mode != SessionService.MODE_DESKTOP + + private fun desktopMouseGrabAllowed(): Boolean = + SessionState.mode == SessionService.MODE_DESKTOP && relativeMouseAttached() && !bottomOutputActive + + private fun tapDesktopGrabKey() { + val now = android.os.SystemClock.uptimeMillis() + if (now - desktopGrabTapAt < DESKTOP_GRAB_TAP_MS) return + desktopGrabTapAt = now + desktopGrabTaps++ + WaylandCompositor.nativeSendKey(KEY_RIGHTCTRL, 1) + uiHandler.postDelayed({ WaylandCompositor.nativeSendKey(KEY_RIGHTCTRL, 0) }, DESKTOP_GRAB_KEY_HOLD_MS) + } + + private fun grabDesktopMouse() { + if (!desktopMouseGrabAllowed()) return + if (desktopGrabTaps >= DESKTOP_GRAB_MAX_TAPS) { + if (android.os.SystemClock.uptimeMillis() - desktopGrabTapAt < DESKTOP_GRAB_RETRY_MS) return + desktopGrabTaps = 0 + } + if (!desktopMousePlaced) { + desktopMousePlaced = true + drawnRect()?.let { movePointer(it.centerX(), it.centerY()) } + } + tapDesktopGrabKey() + } + + private fun releaseDesktopMouseGrab() { + if (pointerLocked && SessionState.mode == SessionService.MODE_DESKTOP && !desktopMouseGrabAllowed()) tapDesktopGrabKey() + } + + private fun dropMouseCursor() { + if (!mouseOwnsCursor || mouseAttached()) return + mouseOwnsCursor = false + uiHandler.removeCallbacks(cursorResync) + cursorVisible = false + } + + override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean, newConfig: android.content.res.Configuration) { + super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig) + updatePointerCapture() + } + private var platformPointerShown: Boolean? = null + private fun touchEditorChanged() { + touchEditorOpen = screenKeys?.isEditing == true || steamTouchControls?.isEditing == true + updatePointerCapture() + applyPlatformPointer(appUiOwnsPointer()) + } + private fun appUiOwnsPointer(): Boolean = - drawerOpen || pcKeyboardOpen || achievementsOpen || pipUi || glassesOutputActive || bottomOutputActive + drawerOpen || pcKeyboardOpen || achievementsOpen || pipUi || glassesOutputActive || bottomOutputActive || touchEditorOpen private fun applyPlatformPointer(shown: Boolean) { if (!::sessionRoot.isInitialized || !::surfaceView.isInitialized) return @@ -3732,7 +3898,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { } private fun onCapturedPointer(event: MotionEvent): Boolean { - if (!pointerLocked || !event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) return false + if (!event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) return false notePress(event) when (event.actionMasked) { MotionEvent.ACTION_MOVE -> { @@ -3742,7 +3908,11 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { dx += event.getHistoricalX(i) dy += event.getHistoricalY(i) } - sendSceneDelta(dx, dy) + if (pointerLocked) sendSceneDelta(dx, dy) + else { + grabDesktopMouse() + moveCapturedMouse(dx, dy) + } } MotionEvent.ACTION_BUTTON_PRESS, MotionEvent.ACTION_BUTTON_RELEASE -> { val button = when (event.actionButton) { @@ -3773,6 +3943,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { val device = com.droiddeck.launcher.input.SteamTouchDevice.current ?: return null steamTouchControls?.let { view -> if (view.device === device) return view + view.cancelEditing() view.releaseAll() (view.parent as? android.view.ViewGroup)?.removeView(view) steamTouchControls = null @@ -3781,6 +3952,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { val view = com.droiddeck.launcher.input.SteamTouchControls(this, device, onMenu = { drawerOpen = true }, onKeyboard = ::togglePcKeyboard) view.visibility = View.GONE + view.onEditingChanged = { touchEditorChanged() } view.onOptions = { options -> steamTouchOptions = options applyTouchRouting() @@ -3827,7 +3999,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { onScreenButtonsVisible = false controls.releaseAll() controls.visibility = View.GONE - steamTouchControls?.let { it.releaseAll(); it.visibility = View.GONE } + steamTouchControls?.let { it.cancelEditing(); it.releaseAll(); it.visibility = View.GONE } com.droiddeck.launcher.input.SteamTouchDevice.current?.plugged = false screenKeys?.followSteam = false applyTouchRouting() @@ -3841,7 +4013,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { uiHandler.postDelayed(steamTouchRetry, 1000) } if (steam == null) { - steamTouchControls?.let { it.releaseAll(); it.visibility = View.GONE } + steamTouchControls?.let { it.cancelEditing(); it.releaseAll(); it.visibility = View.GONE } com.droiddeck.launcher.input.SteamTouchDevice.current?.plugged = false applyTouchRouting() } @@ -3860,7 +4032,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { if (steam != null) { screenKeys?.followSteam = drawn if (controls.visibility == View.VISIBLE) { controls.releaseAll(); controls.visibility = View.GONE } - if (!drawn) steam.releaseAll() + if (!drawn) { steam.cancelEditing(); steam.releaseAll() } steam.visibility = if (drawn) View.VISIBLE else View.GONE com.droiddeck.launcher.input.SteamTouchDevice.current?.plugged = show applyTouchRouting() @@ -3935,8 +4107,9 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { private fun keyboardCoversPad() = SessionState.mode == SessionService.MODE_STEAM && AgentGuest.steamKeyboard - private fun sendGuestKey(code: Int, down: Boolean) { + private fun sendGuestKey(key: Int, down: Boolean) { if (down && pipUi) return + val code = if (key == KEY_RIGHTCTRL && SessionState.mode == SessionService.MODE_DESKTOP) KEY_LEFTCTRL else key if (down) guestKeysDown.add(code) else guestKeysDown.remove(code) if (down || code !in gestureKeysDown) WaylandCompositor.nativeSendKey(code, if (down) 1 else 0) } @@ -4135,6 +4308,9 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { override fun onResume() { super.onResume() + com.droiddeck.launcher.input.SystemKeyCapture.attach(this, { hasWindowFocus() && !appUiOwnsPointer() && CompositorHost.isStarted }) { event -> + com.droiddeck.launcher.input.GuestKeys.send(event, ::sendGuestKey) + } if (refreshActivityDisplayId() && started) { SessionService.setActivityDisplay(this, sessionActivityDisplayId) } @@ -4178,6 +4354,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { } override fun onPause() { + com.droiddeck.launcher.input.SystemKeyCapture.detach(this) com.droiddeck.launcher.input.HomeButtonShortcuts.detach(this) if (runtimeRemovalBlocked) { super.onPause(); return } physicalShortcuts.clear() @@ -4420,6 +4597,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { if (holder !== outputHolder()) return glassesOutputActive = glassesScreen?.holder === holder bottomOutputActive = bottomGame?.holder === holder + updatePointerCapture() if (bottomOutputActive) bottomRecoveries = 0 if (CompositorHost.isStarted) WaylandCompositor.nativeSetCursorComposite(glassesOutputActive || bottomOutputActive) surfaceView.visibility = if (glassesOutputActive || bottomOutputActive) View.INVISIBLE else View.VISIBLE @@ -4794,6 +4972,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { override fun onDestroy() { com.droiddeck.launcher.input.HomeButtonShortcuts.detach(this) + relativeMouseRequests.shutdownNow() backQamInput.cancel() steamVrRecovery.close() uiHandler.removeCallbacks(steamTouchRetry) @@ -4908,6 +5087,14 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { private const val PAD_SCROLL_HEIGHTS_PER_S = 3f private const val AXIS_UNITS_PER_PIXEL = 3f private const val KEY_LEFTCTRL = 29 + private const val KEY_RIGHTCTRL = 97 + private const val BTN_SIDE = 0x113 + private const val BTN_EXTRA = 0x114 + private const val DESKTOP_GRAB_TAP_MS = 500L + private const val DESKTOP_GRAB_MAX_TAPS = 8 + private const val DESKTOP_GRAB_RETRY_MS = 30_000L + private const val DESKTOP_GRAB_KEY_HOLD_MS = 40L + private const val RELATIVE_MOUSE_MAX_RETRIES = 5 private const val DRAWER_HAT_THRESHOLD = 0.5f private const val DRAWER_STICK_ENTER_THRESHOLD = 0.55f private const val DRAWER_STICK_EXIT_THRESHOLD = 0.35f diff --git a/app/src/main/java/com/droiddeck/launcher/input/DirectTouchHold.kt b/app/src/main/java/com/droiddeck/launcher/input/DirectTouchHold.kt index d38eb9758..ee8d68ef0 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/DirectTouchHold.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/DirectTouchHold.kt @@ -66,6 +66,10 @@ class DirectTouchHold( held = false } + fun cancelPendingHold() { + handler.removeCallbacks(hold) + } + fun onTouch(event: MotionEvent): Boolean { if (event.actionMasked == MotionEvent.ACTION_DOWN) { if (liftPending) { diff --git a/app/src/main/java/com/droiddeck/launcher/input/GuestKeys.kt b/app/src/main/java/com/droiddeck/launcher/input/GuestKeys.kt index fbc3739cd..191760003 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/GuestKeys.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/GuestKeys.kt @@ -7,7 +7,7 @@ object GuestKeys { fun send(event: KeyEvent, sendKey: (code: Int, down: Boolean) -> Unit): Boolean { val down = event.action == KeyEvent.ACTION_DOWN if (!down && event.action != KeyEvent.ACTION_UP) return false - var evdev = EvdevKeys.fromKeyCode(event.keyCode) + var evdev = if (isKeyboardEscape(event.keyCode, event.scanCode)) KEY_ESC else EvdevKeys.fromKeyCode(event.keyCode) val ch = event.unicodeChar val plain = if (ch > 0) EvdevKeys.unshiftedChar(ch) else 0 if (evdev <= 0 && ch > 0) { @@ -27,5 +27,9 @@ object GuestKeys { return true } + fun isKeyboardEscape(keyCode: Int, scanCode: Int): Boolean = + keyCode == KeyEvent.KEYCODE_BACK && scanCode == KEY_ESC + + private const val KEY_ESC = 1 private const val KEY_LEFTSHIFT = 42 } diff --git a/app/src/main/java/com/droiddeck/launcher/input/HomeButtonService.kt b/app/src/main/java/com/droiddeck/launcher/input/HomeButtonService.kt index f9d9d34f4..2f17ef1ea 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/HomeButtonService.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/HomeButtonService.kt @@ -45,6 +45,62 @@ object HomeButtonShortcuts { fun onDeviceRemoved(device: Int) = input.onDeviceRemoved(device) } +object SystemKeyCapture { + private var owner: Any? = null + private var active: () -> Boolean = { false } + private var send: (KeyEvent) -> Unit = {} + private val held = HashMap, KeyEvent>() + private val releasing = HashSet>() + internal var isKeyboard: (KeyEvent) -> Boolean = ::fromKeyboard + + fun attach(owner: Any, active: () -> Boolean, send: (KeyEvent) -> Unit) { + this.owner = owner + this.active = active + this.send = send + } + + fun detach(owner: Any) { + if (this.owner !== owner) return + held.values.forEach { send(KeyEvent.changeAction(it, KeyEvent.ACTION_UP)) } + releasing += held.keys + held.clear() + this.owner = null + active = { false } + send = {} + } + + internal fun captures(keyCode: Int, metaState: Int): Boolean = when { + keyCode == KeyEvent.KEYCODE_META_LEFT || keyCode == KeyEvent.KEYCODE_META_RIGHT -> true + metaState and KeyEvent.META_META_ON != 0 -> true + metaState and KeyEvent.META_ALT_ON != 0 -> keyCode == KeyEvent.KEYCODE_TAB || keyCode == KeyEvent.KEYCODE_ESCAPE + else -> false + } + + private fun fromKeyboard(event: KeyEvent): Boolean = + event.device?.let { !it.isVirtual && it.keyboardType == android.view.InputDevice.KEYBOARD_TYPE_ALPHABETIC } == true + + fun onKeyEvent(event: KeyEvent): Boolean { + val key = event.deviceId to event.keyCode + if (event.action == KeyEvent.ACTION_DOWN && event.repeatCount == 0) { + releasing -= key + } else if (key in releasing) { + if (event.action == KeyEvent.ACTION_UP) releasing -= key + return true + } + val taken = when (event.action) { + KeyEvent.ACTION_DOWN -> { + val take = key in held || (event.repeatCount == 0 && active() && isKeyboard(event) && captures(event.keyCode, event.metaState)) + if (take) held[key] = KeyEvent(event) + take + } + KeyEvent.ACTION_UP -> held.remove(key) != null + else -> false + } + if (taken) send(event) + return taken + } +} + class HomeButtonService : AccessibilityService(), InputManager.InputDeviceListener { private var bottomScreen: BottomScreenWatch? = null @@ -58,7 +114,8 @@ class HomeButtonService : AccessibilityService(), InputManager.InputDeviceListen bottomScreen = null } - override fun onKeyEvent(event: KeyEvent) = HomeButtonShortcuts.onKeyEvent(this, event) + override fun onKeyEvent(event: KeyEvent) = + SystemKeyCapture.onKeyEvent(event) || HomeButtonShortcuts.onKeyEvent(this, event) override fun onAccessibilityEvent(event: AccessibilityEvent?) { event?.let { bottomScreen?.onEvent(it) } } override fun onInterrupt() { HomeButtonShortcuts.release() } override fun onInputDeviceAdded(deviceId: Int) {} diff --git a/app/src/main/java/com/droiddeck/launcher/input/OnScreenControls.kt b/app/src/main/java/com/droiddeck/launcher/input/OnScreenControls.kt index 35c98c4b0..ab275177d 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/OnScreenControls.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/OnScreenControls.kt @@ -519,6 +519,7 @@ class OnScreenControls( @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { if (editing) return onEditTouch(event) + if (event.getToolType(event.actionIndex) == MotionEvent.TOOL_TYPE_MOUSE) return false when (event.actionMasked) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> { val index = event.actionIndex diff --git a/app/src/main/java/com/droiddeck/launcher/input/ScreenKeys.kt b/app/src/main/java/com/droiddeck/launcher/input/ScreenKeys.kt index be1f658f3..2e27eecbe 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/ScreenKeys.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/ScreenKeys.kt @@ -56,6 +56,7 @@ class ScreenKeys(context: Context, private val sendKey: (Int, Boolean) -> Unit) private var guideY = -1f private val addPress = Runnable { addX = pressX; addY = pressY; onAdd?.invoke() } private var editing = false + val isEditing get() = editing private var selected: Entry? = null private var dragX = 0f private var dragY = 0f @@ -811,6 +812,7 @@ class ScreenKeys(context: Context, private val sendKey: (Int, Boolean) -> Unit) override fun onTouchEvent(event: MotionEvent): Boolean = if (editing) editTouch(event) else playTouch(event) private fun playTouch(event: MotionEvent): Boolean { + if (event.getToolType(event.actionIndex) == MotionEvent.TOOL_TYPE_MOUSE) return false when (event.actionMasked) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> { val index = event.actionIndex diff --git a/app/src/main/java/com/droiddeck/launcher/input/SteamTouchControls.kt b/app/src/main/java/com/droiddeck/launcher/input/SteamTouchControls.kt index 75f524dfb..3e6e7944a 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/SteamTouchControls.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/SteamTouchControls.kt @@ -107,6 +107,7 @@ class SteamTouchControls( var onTouchMiss: ((MotionEvent) -> Unit)? = null private var editing = false + var onEditingChanged: ((Boolean) -> Unit)? = null private var editElements: MutableList = mutableListOf() private var hidden: MutableList = mutableListOf() private var selected = -1 @@ -617,6 +618,7 @@ class SteamTouchControls( } return true } + if (event.getToolType(event.actionIndex) == MotionEvent.TOOL_TYPE_MOUSE) return false val index = event.actionIndex lastTouchMs = android.os.SystemClock.uptimeMillis() if (faded) { faded = false; invalidate() } @@ -705,6 +707,7 @@ class SteamTouchControls( fun startEditing() { releaseAll() editing = true + onEditingChanged?.invoke(true) editSets.clear() selected = -1 pendingBindings.clear() @@ -755,8 +758,13 @@ class SteamTouchControls( actionSet = saved } + fun cancelEditing() { + if (editing) stopEditing() + } + private fun stopEditing() { editing = false + onEditingChanged?.invoke(false) trayOpen = false trayDrag = null selected = -1 diff --git a/app/src/main/java/com/droiddeck/launcher/input/TouchGestureConfig.kt b/app/src/main/java/com/droiddeck/launcher/input/TouchGestureConfig.kt index f386ebb17..d3be47056 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/TouchGestureConfig.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/TouchGestureConfig.kt @@ -39,6 +39,7 @@ data class TouchGestureConfig( val showGestureDebugOverlay: Boolean = false, val gestureThreshold: Int = DEFAULT_GESTURE_THRESHOLD, val mouseDragMovementMode: String = MOUSE_DRAG_MOVEMENT_DIRECT, + val relativeMouseMode: Int = RELATIVE_MOUSE_AUTO, ) { fun toJson(): String { @@ -75,6 +76,7 @@ data class TouchGestureConfig( put(KEY_SHOW_GESTURE_DEBUG_OVERLAY, showGestureDebugOverlay) put(KEY_GESTURE_THRESHOLD, gestureThreshold) put(KEY_MOUSE_DRAG_MOVEMENT_MODE, mouseDragMovementMode) + put(KEY_RELATIVE_MOUSE_MODE, relativeMouseMode) }.toString() } @@ -91,6 +93,10 @@ data class TouchGestureConfig( const val MOUSE_DRAG_MOVEMENT_DIRECT = "direct" const val MOUSE_DRAG_MOVEMENT_RELATIVE = "relative" + + const val RELATIVE_MOUSE_AUTO = 0 + const val RELATIVE_MOUSE_ON = 1 + const val RELATIVE_MOUSE_OFF = 2 const val ACTION_SHOW_KEYBOARD = "show_keyboard" const val ACTION_KEY_ESC = "key_ESC" const val ACTION_KEY_TILDE = "key_TILDE" @@ -114,6 +120,7 @@ data class TouchGestureConfig( private const val KEY_LONG_PRESS_MOUSE_BEHAVIOR = "longPressMouseBehavior" private const val KEY_LONG_PRESS_DELAY = "longPressDelay" private const val KEY_DIRECT_HOLD_BEHAVIOR = "directHoldBehavior" + private const val KEY_RELATIVE_MOUSE_MODE = "relativeMouseMode" private const val KEY_DOUBLE_TAP_ENABLED = "doubleTapEnabled" private const val KEY_DOUBLE_TAP_DELAY = "doubleTapDelay" private const val KEY_TWO_FINGER_DRAG_ENABLED = "twoFingerDragEnabled" @@ -191,6 +198,7 @@ data class TouchGestureConfig( mouseDragMovementMode = normalizeMouseDragMovementMode( obj.optString(KEY_MOUSE_DRAG_MOVEMENT_MODE, MOUSE_DRAG_MOVEMENT_DIRECT), ), + relativeMouseMode = normalizeRelativeMouseMode(obj.optInt(KEY_RELATIVE_MOUSE_MODE, RELATIVE_MOUSE_AUTO)), ) } catch (_: Exception) { fallbackDefaults() @@ -205,6 +213,10 @@ data class TouchGestureConfig( return if (value == MOUSE_BEHAVIOR_CLICK) MOUSE_BEHAVIOR_CLICK else MOUSE_BEHAVIOR_HOLD } + private fun normalizeRelativeMouseMode(mode: Int): Int { + return if (mode in RELATIVE_MOUSE_AUTO..RELATIVE_MOUSE_OFF) mode else RELATIVE_MOUSE_AUTO + } + private fun normalizeMouseDragMovementMode(mode: String?): String { return if (mode == MOUSE_DRAG_MOVEMENT_RELATIVE) { MOUSE_DRAG_MOVEMENT_RELATIVE diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt index a4498d78e..12c848d1c 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt @@ -775,6 +775,12 @@ class SessionService : Service() { return sessionDir } + private fun relativeMouseForSession(): Int { + val game = if (SessionState.mode == MODE_RUN) SessionState.program?.let { "program:$it" } + else SessionState.gameId?.toLongOrNull()?.toString() + return com.droiddeck.launcher.input.GesturePrefs.read(this, game).relativeMouseMode + } + private fun addClientEnvironment(guest: MutableList, steamHere: Boolean, agentEnvironment: List) { guest.add("/usr/bin/env") guest.add("-i") @@ -846,6 +852,7 @@ class SessionService : Service() { guest.add("BL_FSYNC_FIRST=" + (if (SessionPrefs.fsyncFirst(this)) "1" else "0")) guest.add("BL_GAMESCOPE_REALTIME=" + (if (SessionPrefs.gamescopeRealtime(this)) "1" else "0")) if (SessionPrefs.cursorComposite(this)) guest.add("BL_CURSOR_COMPOSITE=1") + relativeMouseForSession().takeIf { it != 0 }?.let { guest.add("gamescope_relative_mouse=$it") } if (com.droiddeck.launcher.core.ZramSupport.supported()) { guest.add("BL_ZRAM=1") if (steamHere) SessionPrefs.ramCapMb(this).takeIf { it > 0 }?.let { guest.add("BL_RAM_CAP_MB=$it") } 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 24a2638ee..d6356a7ee 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt @@ -282,6 +282,8 @@ class DrawerActions( val onLowLatency: (Boolean) -> Unit = {}, val evdevMouse: Boolean = false, val onEvdevMouse: (Boolean) -> Unit = {}, + val relativeMouseMode: Int = 0, + val onRelativeMouseMode: (Int) -> Unit = {}, val directCursor: Boolean = false, val onDirectCursor: (Boolean) -> Unit = {}, val onTouch: (String) -> Unit, @@ -599,6 +601,8 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont ChoiceRow(host, "touch", stringResource(R.string.mode_touch), null, listOf(SessionPrefs.TOUCH_AUTO to stringResource(R.string.drawer_touch_auto, a.touchAuto), SessionPrefs.TOUCH_PAD to stringResource(R.string.mode_touch_touchpad), SessionPrefs.TOUCH_DIRECT to stringResource(R.string.mode_touch_direct), SessionPrefs.TOUCH_GESTURES to stringResource(R.string.mode_touch_gestures), SessionPrefs.TOUCH_OFF to stringResource(R.string.widgets_off)), a.touchMode, chipModifier = focus.track(page, "touch"), onPick = a.onTouch) + if (a.touchActive == SessionPrefs.TOUCH_PAD) + RelativeMouseRow(host, a, focus.track(page, "relative-mouse")) if (a.touchActive == SessionPrefs.TOUCH_DIRECT) ToggleRow(host, "direct-cursor", stringResource(R.string.drawer_direct_cursor), null, a.directCursor, @@ -620,7 +624,7 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp), ) { - SecondaryButton(stringResource(R.string.ctrl_edit), + SecondaryButton(stringResource(R.string.ctrl_edit), enabled = a.screenKeysShown, modifier = focus.track(page, "screen-keys"), onClick = a.onScreenKeys) a.onScreenLayouts?.let { open -> SecondaryButton(stringResource(R.string.screen_key_layout), @@ -679,6 +683,8 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont ToggleRow(host, "evdev-mouse", stringResource(R.string.drawer_evdev_mouse), null, a.evdevMouse, chipModifier = focus.track(page, "evdev-mouse"), onChange = a.onEvdevMouse) + if (a.touchActive != SessionPrefs.TOUCH_PAD) + RelativeMouseRow(host, a, focus.track(page, "relative-mouse")) } SettingsGroup(stringResource(R.string.drawer_keyboard)) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth().padding(8.dp)) { @@ -1197,6 +1203,15 @@ private fun SecondScreenGroup(host: MenuHost, a: DrawerActions, track: (String) } } +@Composable +private fun RelativeMouseRow(host: MenuHost, a: DrawerActions, modifier: Modifier) { + ChoiceRow(host, "relative-mouse", stringResource(R.string.drawer_relative_mouse), null, + listOf(com.droiddeck.launcher.input.TouchGestureConfig.RELATIVE_MOUSE_AUTO to stringResource(R.string.drawer_relative_mouse_auto), + com.droiddeck.launcher.input.TouchGestureConfig.RELATIVE_MOUSE_ON to stringResource(R.string.drawer_relative_mouse_on), + com.droiddeck.launcher.input.TouchGestureConfig.RELATIVE_MOUSE_OFF to stringResource(R.string.drawer_relative_mouse_off)), + a.relativeMouseMode, chipModifier = modifier, onPick = a.onRelativeMouseMode) +} + @Composable private fun DrawerFexPresetRow(host: MenuHost, a: DrawerActions, modifier: Modifier) { val appId = a.selectedGameProfileId diff --git a/app/src/main/java/com/droiddeck/launcher/ui/SteamIntegrationGroup.kt b/app/src/main/java/com/droiddeck/launcher/ui/SteamIntegrationGroup.kt index 8e2380e94..2827e3c7c 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/SteamIntegrationGroup.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/SteamIntegrationGroup.kt @@ -60,7 +60,7 @@ internal fun SteamIntegration.State.enabled(feature: Feature) = when (feature) { Feature.WIFI -> wifi && locationPermission && locationOn Feature.BRIGHTNESS -> brightness Feature.HOME -> home - Feature.SHORTCUTS -> home && shortcuts + Feature.SHORTCUTS -> shortcuts } internal fun SteamIntegration.State.enabledCount() = Feature.entries.count { enabled(it) } @@ -105,14 +105,12 @@ private fun IntegrationRow( val bg by animateColorAsState(if (flash) pal.signal.copy(alpha = 0.12f) else Color.Transparent, Motion.tw(400), label = "integrationFlash") val wifiLocationOff = feature == Feature.WIFI && state.wifi && state.locationPermission && !state.locationOn val wifiDenied = feature == Feature.WIFI && !state.locationPermission && state.locationBlocked - val needsHome = feature == Feature.SHORTCUTS && !state.home - val restricted = feature == Feature.SHORTCUTS && !needsHome && !state.shortcuts && state.shortcutsRestricted - val maybeRestricted = feature == Feature.SHORTCUTS && !needsHome && !state.shortcuts && !restricted && state.shortcutsTried && + val restricted = feature == Feature.SHORTCUTS && !state.shortcuts && state.shortcutsRestricted + val maybeRestricted = feature == Feature.SHORTCUTS && !state.shortcuts && !restricted && state.shortcutsTried && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU val hint = stringResource(when { wifiLocationOff -> R.string.integration_wifi_location_off wifiDenied -> R.string.integration_wifi_denied - needsHome -> R.string.integration_shortcuts_needs_home restricted -> R.string.integration_shortcuts_restricted maybeRestricted -> R.string.integration_shortcuts_maybe_restricted feature == Feature.WIFI -> if (on) R.string.integration_wifi_on else R.string.integration_wifi_hint @@ -141,7 +139,7 @@ private fun IntegrationRow( restricted || maybeRestricted -> SecondaryButton(stringResource(R.string.integration_app_info), compact = true, onClick = onAppInfo) } val checked = if (feature == Feature.WIFI) state.wifi && state.locationPermission else on - ToggleSwitch(checked, enabled = !needsHome && !restricted, label = stringResource(featureTitle(feature)), + ToggleSwitch(checked, enabled = !restricted, label = stringResource(featureTitle(feature)), modifier = Modifier.testTag("integration-action-${feature.name.lowercase()}")) { onFeature(feature) } } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 15701fcbf..b3068a6f5 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -755,6 +755,10 @@ لمس بزمن استجابة منخفض تتجاوز اللمسات تجميع الإطارات، فيعمل اللمس في Steam بمعدل 1000 هرتز. استهلاك معالج أعلى قليلًا. قناة فأرة خام + فأرة نسبية + تلقائي + تشغيل + إيقاف المؤشر لوحة مفاتيح الكمبيوتر Android @@ -3658,19 +3662,18 @@ أسماء شبكات Wi-Fi السطوع DroidDeck كشاشة رئيسية - اختصارات زر الشاشة الرئيسية + مفاتيح النظام إظهار الشبكات في Steam · يتطلب الوصول إلى الموقع الجغرافي، دون تتبّع أبدًا تظهر الشبكات في Steam الموقع الجغرافي متوقف رُفض الوصول إلى الموقع الجغرافي · اسمح به في معلومات التطبيق شريط التمرير في Steam يضبط سطوع الشاشة زر الشاشة الرئيسية يفتح قائمة Steam أثناء الجلسة - اضغط مطوّلًا على زر الشاشة الرئيسية لاختصارات Steam Guide - يتطلب DroidDeck كشاشة رئيسية + يبقى Alt+Tab ومفتاح Win وزر الرئيسية داخل الجلسة غير متاح في Android؟ معلومات التطبيق → ⋮ → السماح بالإعدادات المحظورة محظور من Android · معلومات التطبيق → ⋮ → السماح بالإعدادات المحظورة - زر الشاشة الرئيسية في DroidDeck - يمرّر ضغطات زر الشاشة الرئيسية إلى Steam Guide عندما تكون جلسة Steam في المقدمة ويكون DroidDeck تطبيق الشاشة الرئيسية الافتراضي. لا يقرأ محتوى الشاشة. + مفاتيح نظام DroidDeck + إبقاء Alt+Tab ومفتاح Win وزر الرئيسية داخل جلسة DroidDeck النشطة بدلاً من Android. فعّل «السطوع» من الإعداد → تكامل Steam جسر Mali: متوقف جسر Mali: يعمل diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 97a3c76c8..4f608ee52 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -773,6 +773,10 @@ Táctil de baja latencia Los toques omiten la agrupación de fotogramas; los controles táctiles de Steam van a 1000 Hz. Usa algo más de CPU. Canal de ratón sin procesar + Ratón relativo + Automático + Activado + Desactivado Cursor Teclado de PC Android @@ -3523,19 +3527,18 @@ Nombres de Wi-Fi Brillo DroidDeck como inicio - Atajos del botón de inicio + Teclas del sistema Muestra las redes en Steam · requiere acceso a la ubicación, nunca se rastrea Las redes se muestran en Steam La ubicación está desactivada Ubicación denegada · permítela en Información de la app El control deslizante de Steam ajusta el brillo de la pantalla Inicio abre el menú de Steam en una sesión - Mantén Inicio para las combinaciones de Steam Guide - Requiere DroidDeck como inicio + Alt+Tab, la tecla Win y el botón Inicio se quedan en la sesión ¿Aparece en gris en Android? Información de la app → ⋮ → Permitir configuración restringida Bloqueado por Android · Información de la app → ⋮ → Permitir configuración restringida - Botón de inicio de DroidDeck - Pasa las pulsaciones del botón de inicio a Steam Guide mientras una sesión de Steam está en primer plano y DroidDeck es la app de inicio predeterminada. No lee el contenido de la pantalla. + Teclas del sistema de DroidDeck + Mantiene Alt+Tab, la tecla Win y el botón Inicio dentro de una sesión activa de DroidDeck en lugar de Android. Activa Brillo en Configuración → Integración con Steam Puente Mali: desactivado Puente Mali: activado diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 04988d297..4f5c248d6 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -747,6 +747,10 @@ Докосване с ниско закъснение Докосванията пропускат групирането на кадри; управлението с докосване в Steam работи при 1000 Hz. Малко повече CPU. Суров канал за мишка + Относителна мишка + Автоматично + Включено + Изключено Курсор Клавиатура за компютър Android @@ -3514,19 +3518,18 @@ Имена на Wi-Fi Яркост DroidDeck като начален екран - Преки пътища с бутона за начален екран + Системни клавиши Показване на мрежите в Steam · изисква достъп до местоположението, никога не се проследява Мрежите се показват в Steam Местоположението е изключено Местоположението е отказано · разрешете го в „Информация за приложението“ Плъзгачът на Steam задава яркостта на екрана Бутонът за начален екран отваря менюто на Steam в сесия - Задръжте бутона за начален екран за комбинации със Steam Guide - Изисква DroidDeck като начален екран + Alt+Tab, клавишът Win и бутонът Начало остават в сесията Неактивно в Android? Информация за приложението → ⋮ → Разрешаване на ограничените настройки Блокирано от Android · Информация за приложението → ⋮ → Разрешаване на ограничените настройки - Бутон за начален екран на DroidDeck - Предава натисканията на бутона за начален екран към Steam Guide, докато сесия на Steam е на фокус и DroidDeck е приложението за начален екран по подразбиране. Не чете съдържанието на екрана. + Системни клавиши на DroidDeck + Задържа Alt+Tab, клавиша Win и бутона Начало в активната сесия на DroidDeck вместо в Android. Включете „Яркост“ в Настройка → Интеграция със Steam Mali мост: изкл. Mali мост: вкл. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 0e830c950..954f6e48e 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -751,6 +751,10 @@ Dotek s nízkým zpožděním Doteky přeskakují dávkování snímků, takže ovládání dotykem ve Steamu běží na 1000 Hz. O něco víc CPU. Surový kanál myši + Relativní myš + Automaticky + Zapnuto + Vypnuto Kurzor Klávesnice PC Android @@ -3586,19 +3590,18 @@ Názvy Wi-Fi Jas DroidDeck jako domovská obrazovka - Zkratky tlačítka Domů + Systémové klávesy Zobrazit sítě ve Steamu · vyžaduje přístup k poloze, nikdy se nesleduje Sítě se zobrazují ve Steamu Poloha je vypnutá Poloha zamítnuta · povolte ji v Informacích o aplikaci Posuvník Steamu nastavuje jas obrazovky Tlačítko Domů v relaci otevře nabídku Steam - Podržte Domů pro kombinace Steam Guide - Vyžaduje DroidDeck jako domovskou obrazovku + Alt+Tab, klávesa Win a tlačítko Domů zůstanou v relaci V Androidu zašedlé? Informace o aplikaci → ⋮ → Povolit omezená nastavení Blokováno Androidem · Informace o aplikaci → ⋮ → Povolit omezená nastavení - Tlačítko Domů pro DroidDeck - Předává stisky tlačítka Domů do Steam Guide, když je aktivní relace Steamu a DroidDeck je výchozí aplikací domovské obrazovky. Nečte obsah obrazovky. + Systémové klávesy DroidDeck + Ponechá Alt+Tab, klávesu Win a tlačítko Domů v aktivní relaci DroidDeck místo Androidu. Zapněte Jas v Příprava → Integrace se Steamem Most Mali: vypnuto Most Mali: zapnuto diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 50b0376c1..05cf267e7 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -747,6 +747,10 @@ Berøring med lav forsinkelse Berøringer springer billedsamling over; Steams berøringsstyring kører ved 1000 Hz. Lidt mere CPU. Rå musekanal + Relativ mus + Automatisk + Til + Fra Markør PC-tastatur Android @@ -3514,19 +3518,18 @@ Wi-Fi-navne Lysstyrke DroidDeck som startskærm - Genveje til Hjem-knappen + Systemtaster Vis netværk i Steam · kræver placeringsadgang, spores aldrig Netværk vises i Steam Placering er slået fra Placering afvist · tillad den i Appinfo Steams skyder indstiller skærmens lysstyrke Hjem åbner Steam-menuen i en session - Hold Hjem nede for Steam Guide-kombinationer - Kræver DroidDeck som startskærm + Alt+Tab, Win-tasten og Hjem-knappen bliver i sessionen Nedtonet i Android? Appinfo → ⋮ → Tillad begrænsede indstillinger Blokeret af Android · Appinfo → ⋮ → Tillad begrænsede indstillinger - DroidDeck Hjem-knap - Sender tryk på Hjem-knappen til Steam Guide, mens en Steam-session er i fokus, og DroidDeck er standardstartskærmen. Læser ikke skærmindhold. + DroidDeck-systemtaster + Holder Alt+Tab, Win-tasten og Hjem-knappen i en aktiv DroidDeck-session i stedet for Android. Slå Lysstyrke til under Opsætning → Steam-integration Mali-bro: fra Mali-bro: til diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2b06f28c3..bf8236f70 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -747,6 +747,10 @@ Touch mit geringer Latenz Touch überspringt das Zusammenfassen von Frames, Steam-Touch meldet mit 1000 Hz. Die CPU-Last ist etwas höher. Roher Mauskanal + Relative Maus + Automatisch + An + Aus Zeiger PC-Tastatur Android @@ -3514,19 +3518,18 @@ Wi-Fi-Namen Helligkeit DroidDeck als Startbildschirm - Kürzel der Home-Taste + Systemtasten Netzwerke in Steam anzeigen · braucht Standortzugriff, kein Tracking Netzwerke werden in Steam angezeigt Standort ist aus Standort verweigert · erlaube ihn in App-Info Steams Regler stellt die Bildschirmhelligkeit ein Home öffnet in einer Sitzung das Steam-Menü - Home halten für Steam-Guide-Tastenkombinationen - Benötigt DroidDeck als Startbildschirm + Alt+Tab, Win-Taste und Home-Taste bleiben in der Sitzung In Android ausgegraut? App-Info → ⋮ → Eingeschränkte Einstellungen zulassen Von Android blockiert · App-Info → ⋮ → Eingeschränkte Einstellungen zulassen - DroidDeck-Home-Taste - Leitet Drücke der Home-Taste an Steam Guide weiter, während eine Steam-Sitzung im Fokus ist und DroidDeck die Standard-Startbildschirm-App ist. Liest keine Bildschirminhalte. + DroidDeck-Systemtasten + Hält Alt+Tab, die Win-Taste und die Home-Taste in einer aktiven DroidDeck-Sitzung statt bei Android. Aktiviere Helligkeit unter Einrichtung → Steam-Integration Mali-Brücke: aus Mali-Brücke: an diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 05c59698d..ad9263c98 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -747,6 +747,10 @@ Αφή χαμηλής καθυστέρησης Οι αφές παρακάμπτουν την ομαδοποίηση καρέ, οπότε ο χειρισμός αφής του Steam τρέχει στα 1000 Hz. Λίγο περισσότερο CPU. Ακατέργαστο κανάλι ποντικιού + Σχετικό ποντίκι + Αυτόματα + Ενεργό + Ανενεργό Δείκτης Πληκτρολόγιο PC Android @@ -3514,19 +3518,18 @@ Ονόματα Wi-Fi Φωτεινότητα DroidDeck ως Αρχική - Συντομεύσεις κουμπιού Αρχικής + Πλήκτρα συστήματος Εμφάνιση δικτύων στο Steam · χρειάζεται πρόσβαση στην Τοποθεσία, χωρίς ποτέ παρακολούθηση Τα δίκτυα εμφανίζονται στο Steam Η Τοποθεσία είναι απενεργοποιημένη Η Τοποθεσία απορρίφθηκε · επιτρέψτε την στις Πληροφορίες εφαρμογής Το ρυθμιστικό του Steam ορίζει τη φωτεινότητα οθόνης Η Αρχική ανοίγει το μενού Steam σε συνεδρία - Κρατήστε πατημένη την Αρχική για συνδυασμούς Steam Guide - Απαιτεί το DroidDeck ως Αρχική + Τα Alt+Tab, Win και Αρχική μένουν στη συνεδρία Γκρίζο στο Android; Πληροφορίες εφαρμογής → ⋮ → Να επιτρέπονται οι περιορισμένες ρυθμίσεις Αποκλείεται από το Android · Πληροφορίες εφαρμογής → ⋮ → Να επιτρέπονται οι περιορισμένες ρυθμίσεις - Κουμπί Αρχικής DroidDeck - Μεταβιβάζει τα πατήματα του κουμπιού Αρχικής στο Steam Guide όσο μια συνεδρία Steam είναι εστιασμένη και το DroidDeck είναι η προεπιλεγμένη εφαρμογή Αρχικής. Δεν διαβάζει το περιεχόμενο της οθόνης. + Πλήκτρα συστήματος DroidDeck + Διατηρεί τα Alt+Tab, το πλήκτρο Win και το κουμπί Αρχικής στην ενεργή συνεδρία DroidDeck αντί για το Android. Ενεργοποιήστε τη Φωτεινότητα στη Ρύθμιση → Ενσωμάτωση Steam Γέφυρα Mali: ανενεργή Γέφυρα Mali: ενεργή diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index efce7a1eb..f7cc0570d 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -749,6 +749,10 @@ Táctil de baja latencia Los toques omiten la agrupación de fotogramas; los controles táctiles de Steam van a 1000 Hz. Usa algo más de CPU. Canal de ratón sin procesar + Ratón relativo + Automático + Activado + Desactivado Cursor Teclado de PC Android @@ -3550,19 +3554,18 @@ Nombres de Wi-Fi Brillo DroidDeck como inicio - Atajos del botón de inicio + Teclas del sistema Muestra las redes en Steam · requiere acceso a la ubicación, nunca se rastrea Las redes se muestran en Steam La ubicación está desactivada Ubicación denegada · permítela en Información de la aplicación El control deslizante de Steam ajusta el brillo de la pantalla Inicio abre el menú de Steam en una sesión - Mantén Inicio para las combinaciones de Steam Guide - Requiere DroidDeck como inicio + Alt+Tab, la tecla Win y el botón Inicio se quedan en la sesión ¿Aparece en gris en Android? Información de la aplicación → ⋮ → Permitir ajustes restringidos Bloqueado por Android · Información de la aplicación → ⋮ → Permitir ajustes restringidos - Botón de inicio de DroidDeck - Pasa las pulsaciones del botón de inicio a Steam Guide mientras una sesión de Steam está en primer plano y DroidDeck es la app de inicio predeterminada. No lee el contenido de la pantalla. + Teclas del sistema de DroidDeck + Mantiene Alt+Tab, la tecla Win y el botón Inicio en una sesión activa de DroidDeck en lugar de Android. Activa Brillo en Configuración → Integración con Steam Puente Mali: desactivado Puente Mali: activado diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index cedf29361..921ab377c 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -747,6 +747,10 @@ Matalan viiveen kosketus Kosketukset ohittavat ruutujen niputuksen, joten Steamin kosketusohjaus toimii 1000 Hz:llä. Hieman enemmän CPU:ta. Raaka hiirikanava + Suhteellinen hiiri + Automaattinen + Päällä + Pois Osoitin PC-näppäimistö Android @@ -3514,19 +3518,18 @@ Wi-Fi-nimet Kirkkaus DroidDeck aloitusnäyttönä - Koti-painikkeen pikakomennot + Järjestelmänäppäimet Näytä verkot Steamissa · vaatii sijaintiluvan, ei koskaan seurata Verkot näkyvät Steamissa Sijainti on pois päältä Sijainti estetty · salli se Sovelluksen tiedoissa Steamin liukusäädin säätää näytön kirkkautta Koti avaa Steam-valikon istunnossa - Pidä Koti-painiketta Steam Guide -yhdistelmiä varten - Vaatii DroidDeckin aloitusnäytöksi + Alt+Tab, Win-näppäin ja Koti-painike pysyvät istunnossa Harmaana Androidissa? Sovelluksen tiedot → ⋮ → Salli rajoitetut asetukset Androidin estämä · Sovelluksen tiedot → ⋮ → Salli rajoitetut asetukset - DroidDeckin Koti-painike - Välittää Koti-painikkeen painallukset Steam Guidelle, kun Steam-istunto on aktiivinen ja DroidDeck on oletusaloitusnäyttösovellus. Ei lue näytön sisältöä. + DroidDeckin järjestelmänäppäimet + Pitää Alt+Tabin, Win-näppäimen ja Koti-painikkeen aktiivisessa DroidDeck-istunnossa Androidin sijaan. Ota Kirkkaus käyttöön kohdassa Käyttöönotto → Steam-integraatio Mali-silta: pois Mali-silta: päällä diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 48c29a1df..3c3a65ebf 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -749,6 +749,10 @@ Tactile à faible latence Les touchers ignorent le regroupement d\'images ; les commandes tactiles Steam passent à 1000 Hz. Un peu plus de CPU. Canal souris brut + Souris relative + Automatique + Activé + Désactivé Curseur Clavier PC Android @@ -3550,19 +3554,18 @@ Noms des réseaux Wi-Fi Luminosité DroidDeck comme écran d\'accueil - Raccourcis du bouton Accueil + Touches système Afficher les réseaux dans Steam · nécessite l\'accès à la position, jamais suivie Les réseaux s\'affichent dans Steam La localisation est désactivée Position refusée · autorisez-la dans Infos sur l\'appli Le curseur de Steam règle la luminosité de l\'écran Accueil ouvre le menu Steam pendant une session - Maintenez Accueil pour les combinaisons Steam Guide - Nécessite DroidDeck comme écran d\'accueil + Alt+Tab, la touche Win et le bouton Accueil restent dans la session Grisé dans Android ? Infos sur l\'appli → ⋮ → Autoriser les paramètres restreints Bloqué par Android · Infos sur l\'appli → ⋮ → Autoriser les paramètres restreints - Bouton Accueil DroidDeck - Transmet les appuis sur le bouton Accueil à Steam Guide lorsqu\'une session Steam est au premier plan et que DroidDeck est l\'application d\'écran d\'accueil par défaut. Ne lit pas le contenu de l\'écran. + Touches système DroidDeck + Garde Alt+Tab, la touche Win et le bouton Accueil dans une session DroidDeck active plutôt que dans Android. Activez Luminosité dans Paramètres → Intégration Steam Pont Mali : désactivé Pont Mali : activé diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 6c4842741..68fe01045 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -747,6 +747,10 @@ कम लेटेंसी टच टच फ़्रेम बैचिंग छोड़ देते हैं, इसलिए Steam का टच कंट्रोल 1000 Hz पर चलता है। थोड़ा ज़्यादा CPU। रॉ माउस चैनल + सापेक्ष माउस + स्वचालित + चालू + बंद कर्सर पीसी कीबोर्ड Android @@ -3514,19 +3518,18 @@ Wi-Fi नाम ब्राइटनेस DroidDeck होम के रूप में - होम बटन शॉर्टकट + सिस्टम कुंजियाँ Steam में नेटवर्क दिखाएं · लोकेशन ऐक्सेस चाहिए, कभी ट्रैक नहीं किया जाता नेटवर्क Steam में दिखते हैं लोकेशन बंद है लोकेशन अस्वीकार · ऐप्लिकेशन की जानकारी में इसकी अनुमति दें Steam का स्लाइडर स्क्रीन ब्राइटनेस सेट करता है सेशन में होम, Steam मेनू खोलता है - Steam Guide कॉर्ड के लिए होम दबाए रखें - DroidDeck को होम के रूप में होना चाहिए + Alt+Tab, Win कुंजी और होम बटन सत्र में ही रहते हैं Android में धूसर है? ऐप्लिकेशन की जानकारी → ⋮ → प्रतिबंधित सेटिंग की अनुमति दें Android ने ब्लॉक किया · ऐप्लिकेशन की जानकारी → ⋮ → प्रतिबंधित सेटिंग की अनुमति दें - DroidDeck होम बटन - जब Steam सेशन फ़ोकस में हो और DroidDeck डिफ़ॉल्ट होम ऐप हो, तब होम बटन दबाने को Steam Guide तक भेजता है। स्क्रीन की सामग्री नहीं पढ़ता। + DroidDeck सिस्टम कुंजियाँ + Alt+Tab, Win कुंजी और होम बटन को Android के बजाय सक्रिय DroidDeck सत्र में रखता है। Setup → Steam इंटीग्रेशन में ब्राइटनेस चालू करें Mali ब्रिज: बंद Mali ब्रिज: चालू diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 7f0168419..cc32a257a 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -747,6 +747,10 @@ Alacsony késleltetésű érintés Az érintések kihagyják a képkockák kötegelését, így a Steam érintésvezérlése 1000 Hz-en fut. Kicsit több CPU. Nyers egércsatorna + Relatív egér + Automatikus + Be + Ki Kurzor PC-billentyűzet Android @@ -3514,19 +3518,18 @@ Wi-Fi-nevek Fényerő DroidDeck kezdőképernyőként - Kezdőképernyő gomb parancsikonjai + Rendszerbillentyűk Hálózatok megjelenítése a Steamben · helyhozzáférést igényel, követés nélkül A hálózatok megjelennek a Steamben A hely ki van kapcsolva Hely megtagadva · engedélyezze az Alkalmazásinformációban A Steam csúszkája állítja a képernyő fényerejét A Kezdőképernyő gomb megnyitja a Steam menüt egy munkamenetben - Tartsa lenyomva a Kezdőképernyő gombot a Steam Guide-kombinációkhoz - A DroidDecknek kell kezdőképernyőnek lennie + Az Alt+Tab, a Win billentyű és a Kezdőlap gomb a munkamenetben marad Szürke az Androidban? Alkalmazásinformáció → ⋮ → Korlátozott beállítások engedélyezése Az Android letiltotta · Alkalmazásinformáció → ⋮ → Korlátozott beállítások engedélyezése - DroidDeck Kezdőképernyő gomb - A Kezdőképernyő gomb lenyomásait továbbítja a Steam Guide-nak, amíg egy Steam-munkamenet van fókuszban, és a DroidDeck az alapértelmezett kezdőképernyő alkalmazás. Nem olvassa a képernyő tartalmát. + DroidDeck rendszerbillentyűk + Az Alt+Tab, a Win billentyű és a Kezdőlap gomb az aktív DroidDeck-munkamenetben marad az Android helyett. Kapcsolja be a Fényerőt itt: Beállítás → Steam-integráció Mali híd: ki Mali híd: be diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 559c2448e..1ef496df4 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -745,6 +745,10 @@ Sentuh latensi rendah Sentuhan melewati pengelompokan frame, jadi kontrol sentuh Steam berjalan pada 1000 Hz. Sedikit lebih banyak CPU. Kanal mouse mentah + Mouse relatif + Otomatis + Aktif + Nonaktif Kursor Keyboard PC Android @@ -3487,19 +3491,18 @@ Nama Wi-Fi Kecerahan DroidDeck sebagai Beranda - Pintasan tombol Home + Tombol sistem Tampilkan jaringan di Steam · memerlukan akses Lokasi, tidak pernah dilacak Jaringan tampil di Steam Lokasi nonaktif Lokasi ditolak · izinkan di Info aplikasi Penggeser Steam mengatur kecerahan layar Home membuka menu Steam dalam sesi - Tahan Home untuk kombinasi Steam Guide - Memerlukan DroidDeck sebagai Beranda + Alt+Tab, tombol Win, dan tombol Beranda tetap di sesi Berwarna abu-abu di Android? Info aplikasi → ⋮ → Izinkan setelan terbatas Diblokir oleh Android · Info aplikasi → ⋮ → Izinkan setelan terbatas - Tombol Home DroidDeck - Meneruskan tekanan tombol Home ke Steam Guide saat sesi Steam sedang aktif dan DroidDeck adalah aplikasi Beranda default. Tidak membaca konten layar. + Tombol sistem DroidDeck + Menjaga Alt+Tab, tombol Win, dan tombol Beranda tetap di sesi DroidDeck yang aktif, bukan di Android. Aktifkan Kecerahan di Penyiapan → Integrasi Steam Jembatan Mali: mati Jembatan Mali: nyala diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c1041da26..a1419b704 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -749,6 +749,10 @@ Touch a bassa latenza I tocchi saltano il raggruppamento dei frame; i controlli touch di Steam passano a 1000 Hz. Un po\' più di CPU. Canale mouse grezzo + Mouse relativo + Automatico + Attivo + Disattivato Cursore Tastiera PC Android @@ -3550,19 +3554,18 @@ Nomi Wi-Fi Luminosità DroidDeck come Home - Scorciatoie del tasto Home + Tasti di sistema Mostra le reti in Steam · richiede l\'accesso alla posizione, mai tracciata Le reti sono visibili in Steam La posizione è disattivata Posizione negata · consentila in Informazioni app Il cursore di Steam regola la luminosità dello schermo Home apre il menu di Steam durante una sessione - Tieni premuto Home per le combinazioni di Steam Guide - Richiede DroidDeck come Home + Alt+Tab, tasto Win e tasto Home restano nella sessione Disattivato in grigio su Android? Informazioni app → ⋮ → Consenti impostazioni con limitazioni Bloccato da Android · Informazioni app → ⋮ → Consenti impostazioni con limitazioni - Tasto Home di DroidDeck - Inoltra le pressioni del tasto Home a Steam Guide mentre una sessione Steam è in primo piano e DroidDeck è l\'app Home predefinita. Non legge il contenuto dello schermo. + Tasti di sistema DroidDeck + Mantiene Alt+Tab, il tasto Win e il tasto Home in una sessione DroidDeck attiva anziché in Android. Attiva Luminosità in Configurazione → Integrazione con Steam Ponte Mali: disattivato Ponte Mali: attivo diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 20440a483..b5a2fc424 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -745,6 +745,10 @@ 低遅延タッチ タッチはフレームの結合を省略し、Steam のタッチは 1000 Hz で報告されます。CPU 使用率がわずかに上がります。 生のマウス経路 + 相対マウス + 自動 + オン + オフ カーソル PCキーボード Android @@ -3487,19 +3491,18 @@ Wi-Fi名 明るさ DroidDeckをホームに設定 - ホームボタンのショートカット + システムキー Steamにネットワークを表示 · 位置情報へのアクセスが必要ですが、追跡はしません Steamにネットワークが表示されます 位置情報がオフです 位置情報が拒否されています · アプリ情報で許可してください Steamのスライダーで画面の明るさを調整します セッション中はホームでSteamメニューを開きます - ホームを長押しでSteamガイドの同時押し操作を使用 - DroidDeckをホームに設定する必要があります + Alt+Tab、Win キー、ホームボタンをセッション内で使えます Androidでグレー表示されていますか?アプリ情報 → ⋮ → 制限付き設定を許可 Androidによってブロックされています · アプリ情報 → ⋮ → 制限付き設定を許可 - DroidDeckホームボタン - Steamセッションがフォーカスされ、DroidDeckがデフォルトのホームアプリの場合に、ホームボタンの操作をSteamガイドに渡します。画面のコンテンツは読み取りません。 + DroidDeck システムキー + Alt+Tab、Win キー、ホームボタンを Android ではなく操作中の DroidDeck セッションで使えるようにします。 「セットアップ」→「Steam連携」で明るさをオンにしてください Mali ブリッジ: オフ Mali ブリッジ: オン diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index c42e84395..4ede4632a 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -745,6 +745,10 @@ 저지연 터치 터치가 프레임 병합을 건너뛰고 Steam 터치가 1000 Hz로 보고됩니다. CPU 사용량이 약간 늘어납니다. 원시 마우스 채널 + 상대 마우스 + 자동 + 켜기 + 끄기 커서 PC 키보드 Android @@ -3487,19 +3491,18 @@ Wi-Fi 이름 밝기 DroidDeck을 홈으로 - 홈 버튼 단축키 + 시스템 키 Steam에 네트워크 표시 · 위치 접근 권한 필요, 위치는 추적하지 않음 Steam에 네트워크가 표시됨 위치가 꺼져 있음 위치 거부됨 · 앱 정보에서 허용하세요 Steam 슬라이더로 화면 밝기 조절 세션 중 홈 버튼으로 Steam 메뉴 열기 - 홈 버튼을 길게 눌러 Steam Guide 조합 사용 - DroidDeck을 홈으로 설정해야 함 + Alt+Tab, Win 키, 홈 버튼이 세션 안에서 작동합니다 Android에서 회색으로 표시되나요? 앱 정보 → ⋮ → 제한된 설정 허용 Android에서 차단됨 · 앱 정보 → ⋮ → 제한된 설정 허용 - DroidDeck 홈 버튼 - Steam 세션에 포커스가 있고 DroidDeck이 기본 홈 앱일 때 홈 버튼 입력을 Steam Guide로 전달합니다. 화면 콘텐츠는 읽지 않습니다. + DroidDeck 시스템 키 + Alt+Tab, Win 키, 홈 버튼을 Android 대신 사용 중인 DroidDeck 세션에서 작동하게 합니다. 설정 → Steam 연동에서 밝기를 켜세요 Mali 브리지: 끔 Mali 브리지: 켬 diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index fe23d493c..4dcdb4e3f 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -745,6 +745,10 @@ Sentuh kependaman rendah Sentuhan melangkau pengumpulan bingkai, jadi kawalan sentuh Steam berjalan pada 1000 Hz. Sedikit lebih banyak CPU. Saluran tetikus mentah + Tetikus relatif + Automatik + Hidup + Mati Kursor Papan kekunci PC Android @@ -3487,19 +3491,18 @@ Nama Wi-Fi Kecerahan DroidDeck sebagai Laman Utama - Pintasan butang Laman Utama + Kekunci sistem Paparkan rangkaian dalam Steam · memerlukan akses Lokasi, tidak pernah dijejak Rangkaian dipaparkan dalam Steam Lokasi dimatikan Lokasi ditolak · benarkannya dalam Maklumat apl Peluncur Steam menetapkan kecerahan skrin Laman Utama membuka menu Steam dalam sesi - Tahan Laman Utama untuk kord Steam Guide - Memerlukan DroidDeck sebagai Laman Utama + Alt+Tab, kekunci Win dan butang Utama kekal dalam sesi Dikelabukan dalam Android? Maklumat apl → ⋮ → Benarkan tetapan terhad Disekat oleh Android · Maklumat apl → ⋮ → Benarkan tetapan terhad - Butang Laman Utama DroidDeck - Menghantar tekanan butang Laman Utama kepada Steam Guide semasa sesi Steam difokuskan dan DroidDeck ialah apl Laman Utama lalai. Tidak membaca kandungan skrin. + Kekunci sistem DroidDeck + Mengekalkan Alt+Tab, kekunci Win dan butang Utama dalam sesi DroidDeck yang aktif, bukan Android. Hidupkan Kecerahan dalam Persediaan → Integrasi Steam Jambatan Mali: mati Jambatan Mali: hidup diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 942a8917e..a2b8f0ed8 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -747,6 +747,10 @@ Berøring med lav forsinkelse Berøringer hopper over bildeinnsamling; Steams berøringsstyring går ved 1000 Hz. Litt mer CPU. Rå musekanal + Relativ mus + Automatisk + På + Av Peker PC-tastatur Android @@ -3514,19 +3518,18 @@ Wi-Fi-navn Lysstyrke DroidDeck som startskjerm - Snarveier for Hjem-knappen + Systemtaster Vis nettverk i Steam · krever posisjonstilgang, spores aldri Nettverk vises i Steam Posisjon er av Posisjon avslått · tillat den i Appinfo Steams glidebryter styrer skjermens lysstyrke Hjem åpner Steam-menyen i en sesjon - Hold Hjem for Steam Guide-kombinasjoner - Krever DroidDeck som startskjerm + Alt+Tab, Win-tasten og Hjem-knappen blir i økten Nedtonet i Android? Appinfo → ⋮ → Tillat begrensede innstillinger Blokkert av Android · Appinfo → ⋮ → Tillat begrensede innstillinger - DroidDeck Hjem-knapp - Sender trykk på Hjem-knappen til Steam Guide mens en Steam-sesjon er i fokus og DroidDeck er standard startskjerm-app. Leser ikke skjerminnhold. + DroidDeck-systemtaster + Holder Alt+Tab, Win-tasten og Hjem-knappen i en aktiv DroidDeck-økt i stedet for Android. Slå på Lysstyrke i Oppsett → Steam-integrering Mali-bro: av Mali-bro: på diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index c28f02aa2..aacc9dd20 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -747,6 +747,10 @@ Aanraking met lage latentie Aanrakingen slaan framebundeling over; Steam-aanraakbediening op 1000 Hz. Iets meer CPU. Ruw muiskanaal + Relatieve muis + Automatisch + Aan + Uit Cursor Pc-toetsenbord Android @@ -3514,19 +3518,18 @@ Wi-Fi-namen Helderheid DroidDeck als startscherm - Sneltoetsen voor de startknop + Systeemtoetsen Netwerken tonen in Steam · vereist locatietoegang, nooit gevolgd Netwerken worden getoond in Steam Locatie staat uit Locatie geweigerd · sta het toe in App-info De schuifregelaar van Steam stelt de schermhelderheid in Home opent het Steam-menu in een sessie - Houd Home ingedrukt voor Steam Guide-combinaties - Vereist DroidDeck als startscherm + Alt+Tab, Win-toets en Home-knop blijven in de sessie Grijs in Android? App-info → ⋮ → Beperkte instellingen toestaan Geblokkeerd door Android · App-info → ⋮ → Beperkte instellingen toestaan - DroidDeck-startknop - Geeft drukken op de startknop door aan Steam Guide terwijl een Steam-sessie actief is en DroidDeck de standaard startscherm-app is. Leest geen scherminhoud. + DroidDeck-systeemtoetsen + Houdt Alt+Tab, de Win-toets en de Home-knop in een actieve DroidDeck-sessie in plaats van Android. Zet Helderheid aan in Installatie → Steam-integratie Mali-brug: uit Mali-brug: aan diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 3cf8a495c..bf3d93d8b 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -751,6 +751,10 @@ Dotyk o niskim opóźnieniu Dotknięcia pomijają łączenie klatek; sterowanie dotykiem Steam działa z 1000 Hz. Nieco więcej CPU. Surowy kanał myszy + Mysz względna + Automatycznie + Włączone + Wyłączone Kursor Klawiatura PC Android @@ -3586,19 +3590,18 @@ Nazwy Wi-Fi Jasność DroidDeck jako ekran główny - Skróty przycisku ekranu głównego + Klawisze systemowe Pokazuj sieci w Steam · wymaga dostępu do lokalizacji, nigdy nie jest śledzona Sieci są widoczne w Steam Lokalizacja jest wyłączona Odmówiono dostępu do lokalizacji · zezwól w Informacjach o aplikacji Suwak Steam ustawia jasność ekranu Przycisk ekranu głównego otwiera menu Steam w sesji - Przytrzymaj przycisk ekranu głównego, aby użyć skrótów Steam Guide - Wymaga DroidDeck jako ekranu głównego + Alt+Tab, klawisz Win i przycisk Home pozostają w sesji Wyszarzone w Androidzie? Informacje o aplikacji → ⋮ → Zezwól na ustawienia z ograniczeniami Zablokowane przez Androida · Informacje o aplikacji → ⋮ → Zezwól na ustawienia z ograniczeniami - Przycisk ekranu głównego DroidDeck - Przekazuje naciśnięcia przycisku ekranu głównego do Steam Guide, gdy aktywna jest sesja Steam, a DroidDeck jest domyślną aplikacją ekranu głównego. Nie odczytuje zawartości ekranu. + Klawisze systemowe DroidDeck + Zatrzymuje Alt+Tab, klawisz Win i przycisk Home w aktywnej sesji DroidDeck zamiast w Androidzie. Włącz Jasność w Konfiguracja → Integracja ze Steam Most Mali: wył. Most Mali: wł. diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 10d4955ae..ca404e28f 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -749,6 +749,10 @@ Toque de baixa latência Os toques ignoram o agrupamento de fotogramas; os controlos táteis do Steam passam a 1000 Hz. Um pouco mais de CPU. Canal de rato em bruto + Rato relativo + Automático + Ativado + Desativado Cursor Teclado de PC Android @@ -3550,19 +3554,18 @@ Nomes de Wi-Fi Brilho DroidDeck como página inicial - Atalhos do botão Página inicial + Teclas do sistema Mostra redes no Steam · requer acesso à localização, nunca monitorizada As redes aparecem no Steam A localização está desativada Localização recusada · permita-a em Informações da aplicação O controlo de deslize do Steam ajusta o brilho do ecrã Página inicial abre o menu do Steam numa sessão - Mantenha Página inicial premido para combinações do botão Guide do Steam - Requer o DroidDeck como página inicial + Alt+Tab, tecla Win e botão Início ficam na sessão Desativado a cinzento no Android? Informações da aplicação → ⋮ → Permitir definições restritas Bloqueado pelo Android · Informações da aplicação → ⋮ → Permitir definições restritas - Botão Página inicial do DroidDeck - Envia os toques no botão Página inicial para o Guide do Steam enquanto uma sessão do Steam está em foco e o DroidDeck é a app de página inicial predefinida. Não lê o conteúdo do ecrã. + Teclas do sistema DroidDeck + Mantém Alt+Tab, a tecla Win e o botão Início numa sessão ativa do DroidDeck em vez do Android. Ative Brilho em Configuração → Integração com o Steam Ponte Mali: desligada Ponte Mali: ligada diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 9c7db8b27..5376a6f73 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -749,6 +749,10 @@ Toque de baixa latência Os toques ignoram o agrupamento de quadros; os controles de toque da Steam vão a 1000 Hz. Um pouco mais de CPU. Canal de mouse bruto + Mouse relativo + Automático + Ativado + Desativado Cursor Teclado de PC Android @@ -3550,19 +3554,18 @@ Nomes de Wi-Fi Brilho DroidDeck como tela inicial - Atalhos do botão Início + Teclas do sistema Mostra redes na Steam · requer acesso à localização, nunca rastreada As redes aparecem na Steam A localização está desativada Localização negada · permita em Informações do app O controle deslizante da Steam ajusta o brilho da tela Início abre o menu Steam em uma sessão - Segure Início para combinações do botão Guide da Steam - Requer DroidDeck como tela inicial + Alt+Tab, tecla Win e botão Início ficam na sessão Esmaecido no Android? Informações do app → ⋮ → Permitir configurações restritas Bloqueado pelo Android · Informações do app → ⋮ → Permitir configurações restritas - Botão Início do DroidDeck - Repassa os toques no botão Início para o Guide da Steam enquanto uma sessão da Steam está em foco e o DroidDeck é o app de tela inicial padrão. Não lê o conteúdo da tela. + Teclas do sistema DroidDeck + Mantém Alt+Tab, a tecla Win e o botão Início em uma sessão ativa do DroidDeck em vez do Android. Ative Brilho em Configuração → Integração com a Steam Ponte Mali: desligada Ponte Mali: ligada diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 20c0ffaac..0104424cd 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -749,6 +749,10 @@ Atingere cu latență mică Atingerile sar peste gruparea cadrelor, deci controlul tactil Steam rulează la 1000 Hz. Consum CPU puțin mai mare. Canal brut de mouse + Mouse relativ + Automat + Pornit + Oprit Cursor Tastatură de PC Android @@ -3550,19 +3554,18 @@ Nume Wi-Fi Luminozitate DroidDeck ca Acasă - Comenzi rapide pentru butonul Acasă + Taste de sistem Afișează rețelele în Steam · necesită acces la locație, fără urmărire Rețelele apar în Steam Locația este dezactivată Locație refuzată · permite-o în Informații despre aplicație Glisorul din Steam setează luminozitatea ecranului Acasă deschide meniul Steam într-o sesiune - Ține apăsat Acasă pentru combinațiile Steam Guide - Necesită DroidDeck ca Acasă + Alt+Tab, tasta Win și butonul Acasă rămân în sesiune Inactiv în Android? Informații despre aplicație → ⋮ → Permite setările restricționate Blocat de Android · Informații despre aplicație → ⋮ → Permite setările restricționate - Butonul Acasă DroidDeck - Transmite apăsările butonului Acasă către Steam Guide cât timp o sesiune Steam este activă și DroidDeck este aplicația Acasă implicită. Nu citește conținutul ecranului. + Taste de sistem DroidDeck + Păstrează Alt+Tab, tasta Win și butonul Acasă într-o sesiune DroidDeck activă în loc de Android. Activează Luminozitate în Configurare → Integrare Steam Punte Mali: oprită Punte Mali: pornită diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b66fc39ac..38d47aa15 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -751,6 +751,10 @@ Сенсор с низкой задержкой Касания идут без объединения кадров; сенсорное управление Steam работает на 1000 Гц. Немного больше нагрузки на CPU. Сырой канал мыши + Относительная мышь + Автоматически + Включено + Выключено Курсор Клавиатура ПК Android @@ -3586,19 +3590,18 @@ Названия сетей Wi-Fi Яркость DroidDeck как главный экран - Сочетания кнопки «Домой» + Системные клавиши Показывать сети в Steam · нужен доступ к местоположению, без отслеживания Сети отображаются в Steam Местоположение отключено Доступ к местоположению запрещён · разрешите его в «О приложении» Ползунок Steam задаёт яркость экрана «Домой» открывает меню Steam в сессии - Удерживайте «Домой» для сочетаний Steam Guide - Нужен DroidDeck как главный экран + Alt+Tab, клавиша Win и кнопка «Домой» работают в сеансе Неактивно в Android? «О приложении» → ⋮ → «Разрешить доступ к настройкам с ограниченным доступом» Заблокировано Android · «О приложении» → ⋮ → «Разрешить доступ к настройкам с ограниченным доступом» - Кнопка «Домой» DroidDeck - Передаёт нажатия кнопки «Домой» в Steam Guide, пока активна сессия Steam и DroidDeck является главным экраном по умолчанию. Не считывает содержимое экрана. + Системные клавиши DroidDeck + Оставляет Alt+Tab, клавишу Win и кнопку «Домой» в активном сеансе DroidDeck, а не в Android. Включите «Яркость» в «Настройка → Интеграция со Steam» Мост Mali: выкл. Мост Mali: вкл. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 1871da130..f94dc7d29 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -747,6 +747,10 @@ Pekning med låg latens Pekningar hoppar över bildrutebatchning; Steams pekstyrning går i 1000 Hz. Något mer CPU. Rå muskanal + Relativ mus + Automatiskt + På + Av Markör PC-tangentbord Android @@ -3514,19 +3518,18 @@ Wi-Fi-namn Ljusstyrka DroidDeck som startskärm - Genvägar för hemknappen + Systemtangenter Visa nätverk i Steam · kräver platsåtkomst, spåras aldrig Nätverk visas i Steam Plats är avstängd Plats nekad · tillåt den i Appinfo Steams reglage ställer in skärmens ljusstyrka Hem öppnar Steam-menyn i en session - Håll Hem för Steam Guide-kombinationer - Kräver DroidDeck som startskärm + Alt+Tab, Win-tangenten och Hem-knappen stannar i sessionen Nedtonat i Android? Appinfo → ⋮ → Tillåt begränsade inställningar Blockerat av Android · Appinfo → ⋮ → Tillåt begränsade inställningar - DroidDeck hemknapp - Skickar tryck på hemknappen till Steam Guide medan en Steam-session är i fokus och DroidDeck är standardappen för startskärmen. Läser inte skärminnehåll. + DroidDeck-systemtangenter + Håller Alt+Tab, Win-tangenten och Hem-knappen i en aktiv DroidDeck-session i stället för Android. Slå på Ljusstyrka under Konfiguration → Steam-integration Mali-brygga: av Mali-brygga: på diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 0ba12e144..faaf350e2 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -745,6 +745,10 @@ การสัมผัสความหน่วงต่ำ การสัมผัสข้ามการรวมเฟรม ทำให้ระบบสัมผัสของ Steam ทำงานที่ 1000 Hz ใช้ CPU เพิ่มขึ้นเล็กน้อย ช่องเมาส์ดิบ + เมาส์แบบสัมพัทธ์ + อัตโนมัติ + เปิด + ปิด เคอร์เซอร์ แป้นพิมพ์พีซี Android @@ -3487,19 +3491,18 @@ ชื่อ Wi-Fi ความสว่าง DroidDeck เป็นหน้าหลัก - ทางลัดปุ่มหน้าหลัก + ปุ่มระบบ แสดงเครือข่ายใน Steam · ต้องใช้สิทธิ์ตำแหน่ง ไม่มีการติดตาม เครือข่ายแสดงใน Steam ตำแหน่งปิดอยู่ ปฏิเสธตำแหน่ง · อนุญาตในข้อมูลแอป แถบเลื่อนของ Steam ปรับความสว่างหน้าจอ ปุ่มหน้าหลักเปิดเมนู Steam ในเซสชัน - กดปุ่มหน้าหลักค้างไว้เพื่อใช้คอร์ด Steam Guide - ต้องตั้ง DroidDeck เป็นหน้าหลัก + Alt+Tab ปุ่ม Win และปุ่มโฮมทำงานในเซสชัน เป็นสีเทาใน Android? ข้อมูลแอป → ⋮ → อนุญาตการตั้งค่าที่จำกัด ถูกบล็อกโดย Android · ข้อมูลแอป → ⋮ → อนุญาตการตั้งค่าที่จำกัด - ปุ่มหน้าหลัก DroidDeck - ส่งการกดปุ่มหน้าหลักไปยัง Steam Guide ขณะเซสชัน Steam อยู่ในโฟกัสและ DroidDeck เป็นแอปหน้าหลักเริ่มต้น ไม่อ่านเนื้อหาบนหน้าจอ + ปุ่มระบบของ DroidDeck + ให้ Alt+Tab ปุ่ม Win และปุ่มโฮมทำงานในเซสชัน DroidDeck ที่ใช้งานอยู่แทน Android เปิดความสว่างในการตั้งค่า → การผสานรวม Steam บริดจ์ Mali: ปิด บริดจ์ Mali: เปิด diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index d0335a10a..1b5613944 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -747,6 +747,10 @@ Düşük gecikmeli dokunma Dokunmalar kare gruplamasını atlar; Steam dokunmatik kontrolleri 1000 Hz çalışır. CPU kullanımı biraz artar. Ham fare kanalı + Göreli fare + Otomatik + Açık + Kapalı İmleç PC klavyesi Android @@ -3514,19 +3518,18 @@ Wi-Fi adları Parlaklık Ana ekran olarak DroidDeck - Ana ekran düğmesi kısayolları + Sistem tuşları Ağları Steam\'de göster · Konum erişimi gerekir, konum asla izlenmez Ağlar Steam\'de görünür Konum kapalı Konum reddedildi · Uygulama bilgileri\'nden izin verin Steam\'in kaydırıcısı ekran parlaklığını ayarlar Ana ekran düğmesi oturumda Steam menüsünü açar - Steam Guide kombinasyonları için Ana ekran düğmesini basılı tutun - Ana ekran olarak DroidDeck gerekir + Alt+Tab, Win tuşu ve Ana Ekran düğmesi oturumda kalır Android\'de gri mi görünüyor? Uygulama bilgileri → ⋮ → Kısıtlanmış ayarlara izin ver Android tarafından engellendi · Uygulama bilgileri → ⋮ → Kısıtlanmış ayarlara izin ver - DroidDeck Ana ekran düğmesi - Bir Steam oturumu odaktayken ve DroidDeck varsayılan Ana ekran uygulamasıyken Ana ekran düğmesine basışları Steam Guide\'a iletir. Ekran içeriğini okumaz. + DroidDeck sistem tuşları + Alt+Tab, Win tuşu ve Ana Ekran düğmesini Android yerine etkin DroidDeck oturumunda tutar. Kurulum → Steam entegrasyonu bölümünde Parlaklık\'ı açın Mali köprüsü: kapalı Mali köprüsü: açık diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 4a13cf936..d80343472 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -751,6 +751,10 @@ Дотик із низькою затримкою Дотики обходять групування кадрів, тож сенсорне керування Steam працює на 1000 Hz. Трохи більше навантаження на CPU. Сирий канал миші + Відносна миша + Автоматично + Увімкнено + Вимкнено Курсор Клавіатура ПК Android @@ -3586,19 +3590,18 @@ Назви мереж Wi-Fi Яскравість DroidDeck як головний екран - Комбінації кнопки «Додому» + Системні клавіші Показувати мережі в Steam · потрібен доступ до місцезнаходження, без стеження Мережі відображаються в Steam Місцезнаходження вимкнено Доступ до місцезнаходження заборонено · дозвольте його в «Про додаток» Повзунок Steam задає яскравість екрана «Додому» відкриває меню Steam у сеансі - Утримуйте «Додому» для комбінацій Steam Guide - Потрібен DroidDeck як головний екран + Alt+Tab, клавіша Win і кнопка «Додому» працюють у сеансі Неактивно в Android? «Про додаток» → ⋮ → «Дозволити обмежені налаштування» Заблоковано Android · «Про додаток» → ⋮ → «Дозволити обмежені налаштування» - Кнопка «Додому» DroidDeck - Передає натискання кнопки «Додому» в Steam Guide, поки активний сеанс Steam і DroidDeck є типовим головним екраном. Не зчитує вміст екрана. + Системні клавіші DroidDeck + Залишає Alt+Tab, клавішу Win і кнопку «Додому» в активному сеансі DroidDeck, а не в Android. Увімкніть «Яскравість» у «Налаштування → Інтеграція зі Steam» Міст Mali: вимк. Міст Mali: увімк. diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index c2023e4bf..6a2d94d83 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -745,6 +745,10 @@ Cảm ứng độ trễ thấp Cảm ứng bỏ qua việc gộp khung hình, nên điều khiển cảm ứng của Steam chạy ở 1000 Hz. Tốn thêm một chút CPU. Kênh chuột thô + Chuột tương đối + Tự động + Bật + Tắt Con trỏ Bàn phím PC Android @@ -3487,19 +3491,18 @@ Tên Wi-Fi Độ sáng DroidDeck làm màn hình chính - Phím tắt nút Home + Phím hệ thống Hiện mạng trong Steam · cần quyền Vị trí, không bao giờ theo dõi Mạng hiện trong Steam Vị trí đang tắt Vị trí bị từ chối · hãy cho phép trong Thông tin ứng dụng Thanh trượt của Steam đặt độ sáng màn hình Home mở menu Steam trong phiên - Giữ Home để dùng tổ hợp Steam Guide - Cần DroidDeck làm màn hình chính + Alt+Tab, phím Win và nút Trang chủ giữ trong phiên Bị làm mờ trong Android? Thông tin ứng dụng → ⋮ → Cho phép chế độ cài đặt bị hạn chế Bị Android chặn · Thông tin ứng dụng → ⋮ → Cho phép chế độ cài đặt bị hạn chế - Nút Home DroidDeck - Chuyển các lần nhấn nút Home đến Steam Guide khi phiên Steam đang được chọn và DroidDeck là ứng dụng màn hình chính mặc định. Không đọc nội dung màn hình. + Phím hệ thống DroidDeck + Giữ Alt+Tab, phím Win và nút Trang chủ trong phiên DroidDeck đang dùng thay vì Android. Bật Độ sáng trong Thiết lập → Tích hợp Steam Cầu Mali: tắt Cầu Mali: bật diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 21f54a9a0..4d23bbf24 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -745,6 +745,10 @@ 低延迟触控 触控跳过帧合并,Steam 触控以 1000 Hz 上报。CPU 占用略高。 原生鼠标通道 + 相对鼠标 + 自动 + 开 + 关 光标 PC 键盘 Android @@ -3490,19 +3494,18 @@ Wi-Fi 名称 亮度 将 DroidDeck 设为主屏幕 - 主屏幕按钮快捷键 + 系统按键 在 Steam 中显示网络 · 需要位置信息权限,绝不跟踪 网络会显示在 Steam 中 位置信息已关闭 位置信息权限被拒绝 · 请在“应用信息”中允许 Steam 的滑块可调节屏幕亮度 在会话中按主屏幕按钮会打开 Steam 菜单 - 按住主屏幕按钮使用 Steam Guide 按钮组合键 - 需要将 DroidDeck 设为主屏幕 + Alt+Tab、Win 键和主页键留在会话中 在 Android 中显示为灰色?应用信息 → ⋮ → 允许受限设置 已被 Android 阻止 · 应用信息 → ⋮ → 允许受限设置 - DroidDeck 主屏幕按钮 - 当 Steam 会话处于焦点且 DroidDeck 为默认主屏幕应用时,将主屏幕按钮的按下操作传递给 Steam Guide 按钮。不会读取屏幕内容。 + DroidDeck 系统按键 + 让 Alt+Tab、Win 键和主页键在当前 DroidDeck 会话中生效,而不是交给 Android。 请在“配置”→“Steam 集成”中开启亮度 DXVK DXVK:自动 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 3b05a08e7..d1024c1c5 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -745,6 +745,10 @@ 低延遲觸控 觸控略過幀合併,Steam 觸控以 1000 Hz 回報。CPU 使用率略高。 原生滑鼠通道 + 相對滑鼠 + 自動 + 開 + 關 游標 電腦鍵盤 Android @@ -3487,19 +3491,18 @@ Wi-Fi 名稱 亮度 將 DroidDeck 用作主畫面 - 主畫面按鈕快速鍵 + 系統按鍵 在 Steam 中顯示網絡 · 需要位置存取權,絕不追蹤 網絡會顯示在 Steam 中 位置資訊已關閉 位置存取遭拒 · 請在「應用程式資訊」中允許 Steam 的滑桿會設定螢幕亮度 在會話中,主畫面按鈕會開啟 Steam 選單 - 按住主畫面按鈕以使用 Steam Guide 組合鍵 - 需要將 DroidDeck 設為主畫面 + Alt+Tab、Win 鍵和主頁鍵留在工作階段中 在 Android 中呈灰色?應用程式資訊 → ⋮ → 允許受限制的設定 已被 Android 封鎖 · 應用程式資訊 → ⋮ → 允許受限制的設定 - DroidDeck 主畫面按鈕 - 當 Steam 會話處於焦點且 DroidDeck 為預設主畫面應用程式時,將主畫面按鈕的按壓傳送給 Steam Guide。不會讀取螢幕內容。 + DroidDeck 系統按鍵 + 讓 Alt+Tab、Win 鍵和主頁鍵在目前的 DroidDeck 工作階段中生效,而不是交給 Android。 請在「初始設定 → Steam 整合」中開啟「亮度」 DXVK DXVK:自動 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index db7b5245a..3065f3a45 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -745,6 +745,10 @@ 低延遲觸控 觸控略過影格合併,Steam 觸控以 1000 Hz 回報。CPU 使用率略高。 原生滑鼠通道 + 相對滑鼠 + 自動 + 開 + 關 游標 電腦鍵盤 Android @@ -3487,19 +3491,18 @@ Wi-Fi 名稱 亮度 將 DroidDeck 設為主畫面 - 主畫面按鈕快速鍵 + 系統按鍵 在 Steam 中顯示網路 · 需要位置存取權,絕不追蹤 網路會顯示在 Steam 中 位置資訊已關閉 位置存取遭拒 · 請在「應用程式資訊」中允許 Steam 的滑桿會設定螢幕亮度 在工作階段中,主畫面按鈕會開啟 Steam 選單 - 按住主畫面按鈕以使用 Steam Guide 組合鍵 - 需要將 DroidDeck 設為主畫面 + Alt+Tab、Win 鍵和主畫面鍵留在工作階段中 在 Android 中呈灰色?應用程式資訊 → ⋮ → 允許受限制的設定 已被 Android 封鎖 · 應用程式資訊 → ⋮ → 允許受限制的設定 - DroidDeck 主畫面按鈕 - 當 Steam 工作階段處於焦點且 DroidDeck 為預設主畫面應用程式時,將主畫面按鈕的按壓傳送給 Steam Guide。不會讀取螢幕內容。 + DroidDeck 系統按鍵 + 讓 Alt+Tab、Win 鍵和主畫面鍵在目前的 DroidDeck 工作階段中生效,而不是交給 Android。 請在「初始設定 → Steam 整合」中開啟「亮度」 DXVK DXVK:自動 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b307063cb..21231e223 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -815,6 +815,10 @@ Low-latency touch Touches skip frame batching; Steam touch controls at 1000 Hz. Slightly more CPU. Raw mouse channel + Relative mouse + Auto + On + Off Cursor PC keyboard Android @@ -3693,19 +3697,18 @@ Wi-Fi names Brightness DroidDeck as Home - Home button shortcuts + System keys Show networks in Steam · needs Location access, never tracked Networks show in Steam Location is off Location denied · allow it in App info Steam’s slider sets screen brightness Home opens the Steam menu in a session - Hold Home for Steam Guide chords - Needs DroidDeck as Home + Alt+Tab, Win key and Home button stay in the session Greyed out in Android? App info → ⋮ → Allow restricted settings Blocked by Android · App info → ⋮ → Allow restricted settings - DroidDeck Home button - Pass Home button presses to Steam Guide while a Steam session is focused and DroidDeck is the default Home app. Does not read screen content. + DroidDeck system keys + Keep Alt+Tab, the Win key and the Home button inside a focused DroidDeck session instead of Android. Turn on Brightness in Setup → Steam integration Restoring your software Reinstalling the apps you added after a DroidDeck update. diff --git a/app/src/test/java/com/droiddeck/launcher/input/GesturePrefsTest.kt b/app/src/test/java/com/droiddeck/launcher/input/GesturePrefsTest.kt index b210be520..9f0381112 100644 --- a/app/src/test/java/com/droiddeck/launcher/input/GesturePrefsTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/input/GesturePrefsTest.kt @@ -10,6 +10,13 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [28], manifest = Config.NONE) class GesturePrefsTest { + @Test fun relativeMouseModeSurvivesSerializationAndRejectsUnknownValues() { + val on = TouchGestureConfig(relativeMouseMode = TouchGestureConfig.RELATIVE_MOUSE_ON) + assertEquals(TouchGestureConfig.RELATIVE_MOUSE_ON, TouchGestureConfig.fromJson(on.toJson()).relativeMouseMode) + assertEquals(TouchGestureConfig.RELATIVE_MOUSE_AUTO, TouchGestureConfig.fromJson("""{"relativeMouseMode":9}""").relativeMouseMode) + assertEquals(TouchGestureConfig.RELATIVE_MOUSE_AUTO, TouchGestureConfig.fromJson("{}").relativeMouseMode) + } + @Test fun removedOptionsAreIgnoredWhenLoadingExistingSettings() { val old = """{"holdMouseButtonWhileTouchingEnabled":true,"holdMouseButtonWhileTouchingAction":"right_click", "showClickHighlight":true,"showCursorInTouchscreenMode":true,"tapAction":"key_E","longPressDelay":650}""" diff --git a/app/src/test/java/com/droiddeck/launcher/input/SystemKeyCaptureTest.kt b/app/src/test/java/com/droiddeck/launcher/input/SystemKeyCaptureTest.kt new file mode 100644 index 000000000..f0562660e --- /dev/null +++ b/app/src/test/java/com/droiddeck/launcher/input/SystemKeyCaptureTest.kt @@ -0,0 +1,71 @@ +package com.droiddeck.launcher.input + +import android.view.KeyEvent +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +class SystemKeyCaptureTest { + @Test fun systemCombinationsAreCaptured() { + assertTrue(SystemKeyCapture.captures(KeyEvent.KEYCODE_META_LEFT, 0)) + assertTrue(SystemKeyCapture.captures(KeyEvent.KEYCODE_META_RIGHT, 0)) + assertTrue(SystemKeyCapture.captures(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON or KeyEvent.META_ALT_LEFT_ON)) + assertTrue(SystemKeyCapture.captures(KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_ALT_ON)) + assertTrue(SystemKeyCapture.captures(KeyEvent.KEYCODE_D, KeyEvent.META_META_ON)) + } + + @Test fun onlyTheEscapeScanCodeTurnsBackIntoEscape() { + assertTrue(GuestKeys.isKeyboardEscape(KeyEvent.KEYCODE_BACK, 1)) + assertFalse(GuestKeys.isKeyboardEscape(KeyEvent.KEYCODE_BACK, 158)) + assertFalse(GuestKeys.isKeyboardEscape(KeyEvent.KEYCODE_ESCAPE, 1)) + } + + @Test fun ordinaryKeysStayWithTheNormalPath() { + assertFalse(SystemKeyCapture.captures(KeyEvent.KEYCODE_TAB, 0)) + assertFalse(SystemKeyCapture.captures(KeyEvent.KEYCODE_A, 0)) + assertFalse(SystemKeyCapture.captures(KeyEvent.KEYCODE_F, KeyEvent.META_ALT_ON)) + assertFalse(SystemKeyCapture.captures(KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_ON)) + } +} + +@RunWith(RobolectricTestRunner::class) +class SystemKeyCaptureHeldKeysTest { + private val sent = mutableListOf() + + private fun key(action: Int, repeat: Int = 0) = + KeyEvent(0L, 0L, action, KeyEvent.KEYCODE_META_LEFT, repeat) + + private fun attached(): Any = Any().also { + SystemKeyCapture.isKeyboard = { true } + SystemKeyCapture.attach(it, { true }) { event -> sent += event.action } + } + + @Test fun keyHeldThroughDetachIsReleasedOnceAndItsPhysicalReleaseIsConsumed() { + val owner = attached() + assertTrue(SystemKeyCapture.onKeyEvent(key(KeyEvent.ACTION_DOWN))) + SystemKeyCapture.detach(owner) + assertEquals(listOf(KeyEvent.ACTION_DOWN, KeyEvent.ACTION_UP), sent) + assertTrue(SystemKeyCapture.onKeyEvent(key(KeyEvent.ACTION_DOWN, repeat = 1))) + assertTrue(SystemKeyCapture.onKeyEvent(key(KeyEvent.ACTION_UP))) + assertEquals(2, sent.size) + assertFalse(SystemKeyCapture.onKeyEvent(key(KeyEvent.ACTION_UP))) + } + + @Test fun aKeyAlreadyHeldWhenCaptureStartsStaysWithAndroid() { + val owner = attached() + assertFalse(SystemKeyCapture.onKeyEvent(key(KeyEvent.ACTION_DOWN, repeat = 1))) + assertFalse(SystemKeyCapture.onKeyEvent(key(KeyEvent.ACTION_UP))) + SystemKeyCapture.detach(owner) + assertEquals(0, sent.size) + } + + @Test fun newPressAfterDetachIsNotSwallowed() { + val owner = attached() + SystemKeyCapture.onKeyEvent(key(KeyEvent.ACTION_DOWN)) + SystemKeyCapture.detach(owner) + assertFalse(SystemKeyCapture.onKeyEvent(key(KeyEvent.ACTION_DOWN))) + } +} diff --git a/app/src/test/java/com/droiddeck/launcher/ui/SteamIntegrationGroupTest.kt b/app/src/test/java/com/droiddeck/launcher/ui/SteamIntegrationGroupTest.kt index b11fbd2fe..807b729f6 100644 --- a/app/src/test/java/com/droiddeck/launcher/ui/SteamIntegrationGroupTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/ui/SteamIntegrationGroupTest.kt @@ -53,11 +53,8 @@ class SteamIntegrationGroupTest { compose.onNodeWithText("3 of 4 on").assertExists() } - @Test fun shortcutsWaitForHome() { + @Test fun shortcutsDoNotNeedHome() { show() - compose.onNodeWithTag("integration-action-shortcuts").assertIsNotEnabled() - compose.onNodeWithText("Needs DroidDeck as Home").assertExists() - compose.runOnIdle { state.value = state.value.copy(home = true) } compose.onNodeWithTag("integration-action-shortcuts").assertIsEnabled() } diff --git a/tools/gamescope/PATCHES.md b/tools/gamescope/PATCHES.md index 0dba1a96d..611af30fb 100644 --- a/tools/gamescope/PATCHES.md +++ b/tools/gamescope/PATCHES.md @@ -225,3 +225,38 @@ PropertyNotify, and it listens for those from map on, so the style Wine sets bef was never known; `map_win` now reads it with the other properties, which the layered-launcher and Xalia rules rely on too. `tools/tests/test_gamescope_override_stacking.py` compiles the rule from this patch. + +## 0127 steamcompmgr: a three way relative mouse switch + +`relative_mouse` is read where the decision is made: 0 leaves it to the rule below, 1 forces relative +motion for the focused game except Steam's own windows and composites the cursor, 2 never goes relative. +For games that confine a visible cursor and warp it, which the automatic rule cannot see. + +## 0128 steamcompmgr: a hidden cursor under any pointer constraint is relative + +The rule only counted a lock with a cursor position hint. A game that hides the cursor and confines the +pointer through a grab (Wine's ClipCursor) has a constraint with no hint, so the nested pointer stayed +absolute, the constraint held the X pointer still and no raw motion was produced: the camera froze. + +## 0129 steamcompmgr, wayland: release a pointer lock at the game's pointer + +A game can move the X pointer while it holds the lock (Wine's ClipCursor recentres it), and the nested host +never hears about it. On unlock the host's next absolute motion dropped the pointer back at its old position, +often near a corner. The unlock now sends the X pointer as a cursor position hint and commits it first. A cursor +forced On by `relative_mouse` also stays visible while idle, since the host cursor is hidden during the lock. + +## 0130 steamcompmgr: fit the X screen to a larger fullscreen window + +A game can open a fullscreen window larger than its X screen (Valheim at 1366x768 on 1280x720). The X server +keeps the pointer inside the screen, so the outer part of the window could not be reached and motion past the +edge had to be undone. The server's mode now follows such a window, and returns once the focused window fits, +focus moves to Steam or no window has focus. + +## 0131 steamcompmgr, wlserver: follow a game's pointer warps + +Under a nested host the seat's absolute position came from the host, so a game that warped a visible X pointer +to the screen centre read the distance back to the host's position on the next motion, and the view spun. When +the X pointer stays far from the seat position while input moves, gamescope takes the host's lock and applies +each frame's motion to the X pointer itself until the game stops warping. Absolute motion also sends relative +motion, as a real compositor does, so XInput2 raw motion reaches games that read only that. A touch moves the +pointer without relative motion, so tapping the screen does not turn the camera. diff --git a/tools/gamescope/patches/0127-relative-mouse-convar.patch b/tools/gamescope/patches/0127-relative-mouse-convar.patch new file mode 100644 index 000000000..d0fccec25 --- /dev/null +++ b/tools/gamescope/patches/0127-relative-mouse-convar.patch @@ -0,0 +1,30 @@ +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -3062,8 +3062,13 @@ + #endif + + gamescope::ConVar cv_cursor_composite{ "cursor_composite", 1, "0 = Never composite a cursor. 1 = Composite cursor when not nested. 2 = Always composite a cursor manually" }; ++static gamescope::ConVar cv_relative_mouse( "relative_mouse", 0, ++"0 = Leave it to the game and the nested hints. 1 = Send host pointer motion as relative movement, for games that confine the cursor and warp it back to read movement. 2 = Never go relative." ); + bool ShouldDrawCursor() + { ++ if ( cv_relative_mouse.Get() == 1 ) ++ return true; ++ + global_focus_t *pFocus = GetCurrentFocus(); + gamescope::INestedHints *pNestedHints = pFocus ? pFocus->GetNestedHints() : nullptr; + return gamescope::CursorShouldBeComposited( cv_cursor_composite.Get(), g_bForceRelativeMouse, +@@ -10810,7 +10815,12 @@ + + const bool bExcludedAppId = uAppId && gamescope::Algorithm::Contains( s_uRelativeMouseFilteredAppids, uAppId ); + +- const bool bRelativeMouseMode = bImageEmpty && bHasPointerConstraint && !bExcludedAppId; ++ const int nRelativeMouse = cv_relative_mouse.Get(); ++ const bool bForcedRelativeMouse = nRelativeMouse == 1 && !window_is_steam( pPaintFocus->inputFocusWindow ); ++ ++ const bool bRelativeMouseMode = nRelativeMouse == 2 ++ ? false ++ : ( bForcedRelativeMouse || ( bImageEmpty && bHasPointerConstraint && !bExcludedAppId ) ); + + pPaintFocus->GetNestedHints()->SetRelativeMouseMode( bRelativeMouseMode ); + } diff --git a/tools/gamescope/patches/0128-relative-mouse-any-constraint.patch b/tools/gamescope/patches/0128-relative-mouse-any-constraint.patch new file mode 100644 index 000000000..a7e60f3b8 --- /dev/null +++ b/tools/gamescope/patches/0128-relative-mouse-any-constraint.patch @@ -0,0 +1,12 @@ +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -10804,7 +10804,8 @@ + ( pPaintFocus->cursor && pPaintFocus->cursor->imageEmpty() ) && + ( !window_is_steam( pPaintFocus->inputFocusWindow ) ); + +- const bool bHasPointerConstraint = pPaintFocus->cursor && pPaintFocus->cursor->IsConstrained(); ++ const bool bHasPointerConstraint = pPaintFocus->cursor && ++ ( pPaintFocus->cursor->IsConstrained() || wlserver.HasMouseConstraint() ); + + uint32_t uAppId = pPaintFocus->inputFocusWindow + ? pPaintFocus->inputFocusWindow->appID diff --git a/tools/gamescope/patches/0129-release-lock-at-the-game-pointer.patch b/tools/gamescope/patches/0129-release-lock-at-the-game-pointer.patch new file mode 100644 index 000000000..3cf32b6c8 --- /dev/null +++ b/tools/gamescope/patches/0129-release-lock-at-the-game-pointer.patch @@ -0,0 +1,145 @@ +diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp +index 953765a..572fb0e 100644 +--- a/src/Backends/WaylandBackend.cpp ++++ b/src/Backends/WaylandBackend.cpp +@@ -464,6 +464,7 @@ namespace gamescope + + virtual void SetCursorImage( std::shared_ptr info ) override; + virtual void SetRelativeMouseMode( bool bRelative ) override; ++ virtual void SetCursorPositionHint( double flX, double flY ) override; + virtual void SetVisible( bool bVisible ) override; + virtual void SetTitle( std::shared_ptr szTitle ) override; + virtual void SetIcon( std::shared_ptr> uIconPixels ) override; +@@ -719,6 +720,7 @@ namespace gamescope + + void SetCursorImage( std::shared_ptr info ); + void SetRelativeMouseMode( wl_surface *pSurface, bool bRelative ); ++ void SetCursorPositionHint( wl_fixed_t fSurfaceX, wl_fixed_t fSurfaceY ); + void UpdateCursor(); + + friend CWaylandConnector; +@@ -1270,6 +1272,16 @@ namespace gamescope + // TODO: Do more tracking across multiple connectors, and activity here if we ever want to use this. + m_pBackend->SetRelativeMouseMode( m_Planes[0].GetSurface(), bRelative ); + } ++ void CWaylandConnector::SetCursorPositionHint( double flX, double flY ) ++ { ++ auto oState = m_Planes[0].GetCurrentState(); ++ if ( !oState || !oState->uFractionalScale ) ++ return; ++ ++ const double flSurfaceX = ( flX * g_nOutputWidth - oState->nDestX ) * 120.0 / oState->uFractionalScale; ++ const double flSurfaceY = ( flY * g_nOutputHeight - oState->nDestY ) * 120.0 / oState->uFractionalScale; ++ m_pBackend->SetCursorPositionHint( wl_fixed_from_double( flSurfaceX ), wl_fixed_from_double( flSurfaceY ) ); ++ } + void CWaylandConnector::SetVisible( bool bVisible ) + { + if ( m_bVisible == bVisible ) +@@ -2484,6 +2496,15 @@ namespace gamescope + + UpdateCursor(); + } ++ void CWaylandBackend::SetCursorPositionHint( wl_fixed_t fSurfaceX, wl_fixed_t fSurfaceY ) ++ { ++ if ( !m_pLockedPointer ) ++ return; ++ ++ zwp_locked_pointer_v1_set_cursor_position_hint( m_pLockedPointer, fSurfaceX, fSurfaceY ); ++ wl_surface_commit( m_pLockedSurface ); ++ } ++ + void CWaylandBackend::SetRelativeMouseMode( wl_surface *pSurface, bool bRelative ) + { + if ( !m_pPointer ) +diff --git a/src/backend.h b/src/backend.h +index d264917..37b936e 100644 +--- a/src/backend.h ++++ b/src/backend.h +@@ -271,6 +271,7 @@ namespace gamescope + + virtual void SetCursorImage( std::shared_ptr info ) = 0; + virtual void SetRelativeMouseMode( bool bRelative ) = 0; ++ virtual void SetCursorPositionHint( double flX, double flY ) {} + virtual void SetVisible( bool bVisible ) = 0; + virtual void SetTitle( std::shared_ptr szTitle ) = 0; + virtual void SetIcon( std::shared_ptr> uIconPixels ) = 0; +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index f1346e5..2c92afe 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -2078,6 +2078,18 @@ void MouseCursor::UpdatePosition() + } + } + ++void MouseCursor::QueryRootPosition( int *pX, int *pY ) ++{ ++ Window root_ret, child_ret; ++ int win_x = 0, win_y = 0; ++ unsigned int mask; ++ *pX = m_x; ++ *pY = m_y; ++ XQueryPointer( m_ctx->dpy, DefaultRootWindow( m_ctx->dpy ), &root_ret, &child_ret, pX, pY, &win_x, &win_y, &mask ); ++} ++ ++static bool relative_mouse_forced(); ++ + void MouseCursor::checkSuspension() + { + getTexture(); +@@ -2085,7 +2097,12 @@ void MouseCursor::checkSuspension() + if ( ShouldDrawCursor() ) + { + const bool suspended = int64_t( get_time_in_nanos() ) - int64_t( wlserver.ulLastMovedCursorTime ) > int64_t( cursorHideTime ); +- if (!wlserver.bCursorHidden && suspended) { ++ if ( relative_mouse_forced() && wlserver.bCursorHidden ) ++ { ++ wlserver.bCursorHidden = false; ++ hasRepaintNonBasePlane = true; ++ } ++ if (!wlserver.bCursorHidden && suspended && !relative_mouse_forced()) { + wlserver.bCursorHidden = true; + + steamcompmgr_win_t *window = m_ctx->focus.inputFocusWindow; +@@ -3064,6 +3081,11 @@ static void paint_pipewire() + gamescope::ConVar cv_cursor_composite{ "cursor_composite", 1, "0 = Never composite a cursor. 1 = Composite cursor when not nested. 2 = Always composite a cursor manually" }; + static gamescope::ConVar cv_relative_mouse( "relative_mouse", 0, + "0 = Leave it to the game and the nested hints. 1 = Send host pointer motion as relative movement, for games that confine the cursor and warp it back to read movement. 2 = Never go relative." ); ++static bool relative_mouse_forced() ++{ ++ return cv_relative_mouse.Get() == 1; ++} ++ + bool ShouldDrawCursor() + { + if ( cv_relative_mouse.Get() == 1 ) +@@ -10828,6 +10850,18 @@ steamcompmgr_main(int argc, char **argv) + ? false + : ( bForcedRelativeMouse || ( bImageEmpty && bHasPointerConstraint && !bExcludedAppId ) ); + ++ static bool s_bRelativeMouseMode = false; ++ if ( s_bRelativeMouseMode && !bRelativeMouseMode && pPaintFocus->cursor && ++ g_nOutputWidth && g_nOutputHeight && focusedWindowScaleX > 0.0f && focusedWindowScaleY > 0.0f ) ++ { ++ int nRootX, nRootY; ++ pPaintFocus->cursor->QueryRootPosition( &nRootX, &nRootY ); ++ pPaintFocus->GetNestedHints()->SetCursorPositionHint( ++ ( nRootX / focusedWindowScaleX - focusedWindowOffsetX ) / g_nOutputWidth, ++ ( nRootY / focusedWindowScaleY - focusedWindowOffsetY ) / g_nOutputHeight ); ++ } ++ s_bRelativeMouseMode = bRelativeMouseMode; ++ + pPaintFocus->GetNestedHints()->SetRelativeMouseMode( bRelativeMouseMode ); + } + +diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp +index 7a7119f..ff54d8e 100644 +--- a/src/steamcompmgr.hpp ++++ b/src/steamcompmgr.hpp +@@ -85,6 +85,7 @@ public: + } + + void UpdatePosition(); ++ void QueryRootPosition( int *pX, int *pY ); + + bool isHidden() { return wlserver.bCursorHidden || m_imageEmpty; } + bool imageEmpty() const { return m_imageEmpty; } diff --git a/tools/gamescope/patches/0130-fit-x-screen-to-larger-fullscreen-window.patch b/tools/gamescope/patches/0130-fit-x-screen-to-larger-fullscreen-window.patch new file mode 100644 index 000000000..48f3b399f --- /dev/null +++ b/tools/gamescope/patches/0130-fit-x-screen-to-larger-fullscreen-window.patch @@ -0,0 +1,94 @@ +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 2c92afe..17bae3a 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -4662,6 +4662,49 @@ static void set_wm_state( xwayland_ctx_t *ctx, Window win, uint32_t state ) + sizeof(wmState) / sizeof(wmState[0])); + } + ++static void ++fit_root_to_focus_window( xwayland_ctx_t *ctx ) ++{ ++ steamcompmgr_win_t *w = ctx->focus.focusWindow; ++ const bool bGame = w && !window_is_steam( w ); ++ const int nWindowX = bGame ? w->xwayland().a.x : 0; ++ const int nWindowY = bGame ? w->xwayland().a.y : 0; ++ const int nWindowWidth = bGame ? w->xwayland().a.width : 0; ++ const int nWindowHeight = bGame ? w->xwayland().a.height : 0; ++ const int nBaseWidth = ctx->nFitRestoreWidth ? ctx->nFitRestoreWidth : ctx->root_width; ++ const int nBaseHeight = ctx->nFitRestoreWidth ? ctx->nFitRestoreHeight : ctx->root_height; ++ const bool bCoversBase = nWindowX == 0 && nWindowY == 0 && nWindowWidth >= nBaseWidth && nWindowHeight >= nBaseHeight; ++ const bool bPlausible = nWindowWidth <= 8192 && nWindowHeight <= 8192; ++ ++ int nWidth = 0; ++ int nHeight = 0; ++ if ( bGame && bCoversBase && bPlausible && ( nWindowWidth > nBaseWidth || nWindowHeight > nBaseHeight ) ) ++ { ++ if ( !ctx->nFitRestoreWidth ) ++ { ++ ctx->nFitRestoreWidth = ctx->root_width; ++ ctx->nFitRestoreHeight = ctx->root_height; ++ } ++ nWidth = nWindowWidth; ++ nHeight = nWindowHeight; ++ } ++ else if ( ctx->nFitRestoreWidth && nWindowWidth <= ctx->nFitRestoreWidth && nWindowHeight <= ctx->nFitRestoreHeight ) ++ { ++ nWidth = ctx->nFitRestoreWidth; ++ nHeight = ctx->nFitRestoreHeight; ++ ctx->nFitRestoreWidth = 0; ++ ctx->nFitRestoreHeight = 0; ++ } ++ ++ if ( !nWidth || ( nWidth == ctx->root_width && nHeight == ctx->root_height ) ) ++ return; ++ ++ ctx->bFitPending = true; ++ wlserver_lock(); ++ wlserver_set_xwayland_server_mode( ctx->xwayland_server->get_index(), nWidth, nHeight, g_nOutputRefresh ); ++ wlserver_unlock(); ++} ++ + void xwayland_ctx_t::DetermineAndApplyFocus( const std::vector< steamcompmgr_win_t* > &vecPossibleFocusWindows ) + { + xwayland_ctx_t *ctx = this; +@@ -4736,6 +4779,8 @@ void xwayland_ctx_t::DetermineAndApplyFocus( const std::vector< steamcompmgr_win + inputFocus = ctx->focus.focusWindow; + } + ++ fit_root_to_focus_window( ctx ); ++ + if ( ctx->focus.focusWindow ) + { + if ( prevFocusWindow != ctx->focus.focusWindow ) +@@ -5001,7 +5046,7 @@ void xwayland_ctx_t::DetermineAndApplyFocus( const std::vector< steamcompmgr_win + fs_width = ctx->root_width * steam_height_scale; + } + +- if ( w->GetGeometry().nWidth != fs_width || w->GetGeometry().nHeight != fs_height || globalScaleRatio != 1.0f ) ++ if ( !ctx->bFitPending && ( w->GetGeometry().nWidth != fs_width || w->GetGeometry().nHeight != fs_height || globalScaleRatio != 1.0f ) ) + XResizeWindow(ctx->dpy, ctx->focus.focusWindow->xwayland().id, fs_width, fs_height); + } + else +@@ -6232,6 +6277,7 @@ configure_win(xwayland_ctx_t *ctx, XConfigureEvent *ce) + { + if (ce->window == ctx->root) + { ++ ctx->bFitPending = false; + ctx->root_width = ce->width; + ctx->root_height = ce->height; + MakeFocusDirty(); +diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp +index 3a75650..15c2e6b 100644 +--- a/src/xwayland_ctx.hpp ++++ b/src/xwayland_ctx.hpp +@@ -87,6 +87,9 @@ struct xwayland_ctx_t final : public gamescope::IWaitable + Window ourWindow; + + focus_t focus; ++ int nFitRestoreWidth = 0; ++ int nFitRestoreHeight = 0; ++ bool bFitPending = false; + Window currentKeyboardFocusWindow; + Window focusControlWindow; + diff --git a/tools/gamescope/patches/0131-follow-game-pointer-warps.patch b/tools/gamescope/patches/0131-follow-game-pointer-warps.patch new file mode 100644 index 000000000..9bbc592f5 --- /dev/null +++ b/tools/gamescope/patches/0131-follow-game-pointer-warps.patch @@ -0,0 +1,216 @@ +diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp +index 17bae3a..c468fb3 100644 +--- a/src/steamcompmgr.cpp ++++ b/src/steamcompmgr.cpp +@@ -32,6 +32,7 @@ + #include "backend.h" + #include "gamescope_shared.h" + #include "xwayland_ctx.hpp" ++#include + #include + #include + #include +@@ -2088,6 +2089,7 @@ void MouseCursor::QueryRootPosition( int *pX, int *pY ) + XQueryPointer( m_ctx->dpy, DefaultRootWindow( m_ctx->dpy ), &root_ret, &child_ret, pX, pY, &win_x, &win_y, &mask ); + } + ++static bool s_bFollowClientPointer = false; + static bool relative_mouse_forced(); + + void MouseCursor::checkSuspension() +@@ -3083,12 +3085,89 @@ static gamescope::ConVar cv_relative_mouse( "relative_mouse", 0, + "0 = Leave it to the game and the nested hints. 1 = Send host pointer motion as relative movement, for games that confine the cursor and warp it back to read movement. 2 = Never go relative." ); + static bool relative_mouse_forced() + { +- return cv_relative_mouse.Get() == 1; ++ return cv_relative_mouse.Get() == 1 || s_bFollowClientPointer; ++} ++ ++static void update_client_pointer_follow( global_focus_t *pFocus, bool bAllowed ) ++{ ++ static int s_nAwayFrames = 0; ++ static int s_nQuietFrames = 0; ++ static uint64_t s_ulLastAway = 0; ++ static double s_flLastX = 0.0; ++ static double s_flLastY = 0.0; ++ ++ int nRootX = 0; ++ int nRootY = 0; ++ if ( bAllowed && pFocus->cursor ) ++ pFocus->cursor->QueryRootPosition( &nRootX, &nRootY ); ++ else ++ bAllowed = false; ++ ++ wlserver_lock(); ++ const double flX = wlserver.mouse_surface_cursorx; ++ const double flY = wlserver.mouse_surface_cursory; ++ if ( !bAllowed ) ++ { ++ s_nAwayFrames = 0; ++ s_bFollowClientPointer = false; ++ wlserver.bFollowClientPointer = false; ++ } ++ else if ( !s_bFollowClientPointer ) ++ { ++ const bool bMoved = flX != s_flLastX || flY != s_flLastY; ++ if ( bMoved && ( std::abs( nRootX - flX ) > 48.0 || std::abs( nRootY - flY ) > 48.0 ) ) ++ { ++ const uint64_t ulNow = get_time_in_nanos(); ++ if ( ulNow - s_ulLastAway > 500'000'000ul ) ++ s_nAwayFrames = 0; ++ s_ulLastAway = ulNow; ++ s_nAwayFrames++; ++ } ++ ++ if ( s_nAwayFrames >= 3 ) ++ { ++ s_nAwayFrames = 0; ++ s_nQuietFrames = 0; ++ s_bFollowClientPointer = true; ++ wlserver.bFollowClientPointer = true; ++ wlserver.flFollowDX = 0.0; ++ wlserver.flFollowDY = 0.0; ++ wlserver_mousewarp( nRootX, nRootY, 0, true ); ++ } ++ } ++ else ++ { ++ const bool bWarped = std::abs( nRootX - flX ) > 2.0 || std::abs( nRootY - flY ) > 2.0; ++ const double flDX = std::trunc( wlserver.flFollowDX ); ++ const double flDY = std::trunc( wlserver.flFollowDY ); ++ if ( bWarped ) ++ s_nQuietFrames = 0; ++ else if ( flDX != 0.0 || flDY != 0.0 ) ++ s_nQuietFrames++; ++ ++ if ( flDX != 0.0 || flDY != 0.0 || bWarped ) ++ { ++ wlserver.flFollowDX -= flDX; ++ wlserver.flFollowDY -= flDY; ++ wlserver_mousewarp( nRootX, nRootY, 0, true ); ++ if ( flDX != 0.0 || flDY != 0.0 ) ++ wlserver_mousewarp( nRootX + flDX, nRootY + flDY, 0, true ); ++ } ++ ++ if ( s_nQuietFrames >= 60 ) ++ { ++ s_bFollowClientPointer = false; ++ wlserver.bFollowClientPointer = false; ++ } ++ } ++ s_flLastX = wlserver.mouse_surface_cursorx; ++ s_flLastY = wlserver.mouse_surface_cursory; ++ wlserver_unlock(); + } + + bool ShouldDrawCursor() + { +- if ( cv_relative_mouse.Get() == 1 ) ++ if ( relative_mouse_forced() ) + return true; + + global_focus_t *pFocus = GetCurrentFocus(); +@@ -10892,9 +10971,15 @@ steamcompmgr_main(int argc, char **argv) + const int nRelativeMouse = cv_relative_mouse.Get(); + const bool bForcedRelativeMouse = nRelativeMouse == 1 && !window_is_steam( pPaintFocus->inputFocusWindow ); + ++ if ( vblank ) ++ { ++ update_client_pointer_follow( pPaintFocus, ++ nRelativeMouse != 2 && !bExcludedAppId && !bHasPointerConstraint && !window_is_steam( pPaintFocus->inputFocusWindow ) ); ++ } ++ + const bool bRelativeMouseMode = nRelativeMouse == 2 + ? false +- : ( bForcedRelativeMouse || ( bImageEmpty && bHasPointerConstraint && !bExcludedAppId ) ); ++ : ( bForcedRelativeMouse || s_bFollowClientPointer || ( bImageEmpty && bHasPointerConstraint && !bExcludedAppId ) ); + + static bool s_bRelativeMouseMode = false; + if ( s_bRelativeMouseMode && !bRelativeMouseMode && pPaintFocus->cursor && +diff --git a/src/wlserver.cpp b/src/wlserver.cpp +index 5733871..cc75f8a 100644 +--- a/src/wlserver.cpp ++++ b/src/wlserver.cpp +@@ -1,3 +1,4 @@ ++#include + #include + #define _GNU_SOURCE 1 + +@@ -3086,6 +3087,16 @@ void wlserver_mousemotion( double dx, double dy, uint32_t time ) + + wlserver_perform_rel_pointer_motion( dx, dy ); + ++ if ( wlserver.bFollowClientPointer ) ++ { ++ wlserver.flFollowDX += dx; ++ wlserver.flFollowDY += dy; ++ wlserver.ulLastMovedCursorTime = get_time_in_nanos(); ++ wlserver.bCursorHidden = !wlserver.bCursorHasImage; ++ wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); ++ return; ++ } ++ + if ( !wlserver_apply_constraint( &dx, &dy ) ) + { + wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); +@@ -3110,10 +3121,18 @@ void wlserver_mousemotion( double dx, double dy, uint32_t time ) + wlr_seat_pointer_notify_frame( wlserver.wlr.seat ); + } + +-void wlserver_mousewarp( double x, double y, uint32_t time, bool bSynthetic ) ++void wlserver_mousewarp( double x, double y, uint32_t time, bool bSynthetic, bool bFromTouch ) + { + assert( wlserver_is_lock_held() ); + ++ if ( !bSynthetic && wlserver.bFollowClientPointer ) ++ return; ++ ++ const double flStepX = x - wlserver.mouse_surface_cursorx; ++ const double flStepY = y - wlserver.mouse_surface_cursory; ++ if ( !bSynthetic && !bFromTouch && ( flStepX != 0.0 || flStepY != 0.0 ) && std::abs( flStepX ) < 256.0 && std::abs( flStepY ) < 256.0 ) ++ wlserver_perform_rel_pointer_motion( flStepX, flStepY ); ++ + wlserver.mouse_surface_cursorx = x; + wlserver.mouse_surface_cursory = y; + +@@ -3304,7 +3323,7 @@ void wlserver_touchmotion( double x, double y, int touch_id, uint32_t time, bool + { + g_bPendingTouchMovement = true; + +- wlserver_mousewarp( tx, ty, time, false ); ++ wlserver_mousewarp( tx, ty, time, false, !bAlwaysWarpCursor ); + } + } + +@@ -3352,7 +3371,7 @@ void wlserver_touchdown( double x, double y, int touch_id, uint32_t time, gamesc + + if ( eMode != gamescope::TouchClickModes::Trackpad ) + { +- wlserver_mousewarp( tx, ty, time, false ); ++ wlserver_mousewarp( tx, ty, time, false, true ); + } + + uint32_t button = TouchClickModeToLinuxButton( eMode ); +diff --git a/src/wlserver.hpp b/src/wlserver.hpp +index 79a0723..ca8d015 100644 +--- a/src/wlserver.hpp ++++ b/src/wlserver.hpp +@@ -168,6 +168,9 @@ struct wlserver_t { + uint64_t ulLastMovedCursorTime = 0; + bool bCursorHidden = true; + bool bCursorHasImage = true; ++ bool bFollowClientPointer = false; ++ double flFollowDX = 0.0; ++ double flFollowDY = 0.0; + + bool button_held[ WLSERVER_BUTTON_COUNT ]; + std::set touch_down_ids; +@@ -271,7 +274,7 @@ void wlserver_clear_dropdowns(); + void wlserver_notify_dropdown( struct wlr_surface *wlrsurface, int nX, int nY ); + void wlserver_mousemotion( double x, double y, uint32_t time ); + void wlserver_mousehide(); +-void wlserver_mousewarp( double x, double y, uint32_t time, bool bSynthetic ); ++void wlserver_mousewarp( double x, double y, uint32_t time, bool bSynthetic, bool bFromTouch = false ); + void wlserver_mousebutton( int button, bool press, uint32_t time ); + void wlserver_mousewheel( double x, double y, uint32_t time ); + bool wlserver_input_held(); diff --git a/tools/gamescope/release.env b/tools/gamescope/release.env index a73f1985b..cf61f9453 100644 --- a/tools/gamescope/release.env +++ b/tools/gamescope/release.env @@ -1,3 +1,3 @@ GAMESCOPE_REPO=Droid-Deck/DroidDeck-Components -GAMESCOPE_TAG=gamescope-3.16.29-p19 -GAMESCOPE_SHA256=e7333c6a2b95708b199c2c5bd0973e3f0e711e2dc746950cc648c2515ea2926b +GAMESCOPE_TAG=gamescope-3.16.29-p20 +GAMESCOPE_SHA256=30a94becf21f3de4d1d7239c47b5168aab973f4e95981402a550c228929ba827