From 9dc9622ed3bf76393aa562983e2e1021d168e69b Mon Sep 17 00:00:00 2001 From: cjcxj Date: Thu, 27 Aug 2026 09:29:24 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(input):=20=E4=B8=89=E6=8C=87=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E5=B9=B3=E7=A7=BB/=E7=BC=A9=E6=94=BE=EF=BC=88?= =?UTF-8?q?=E6=8C=81=E4=B9=85=E5=8C=96=E5=BC=80=E5=85=B3=E5=8F=96=E4=BB=A3?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E6=97=A7=E5=BC=80=E5=85=B3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 第三指按下即进入平移/缩放,1/2 指保持游戏输入,无需旧开关 - 进入时取消已下发的 1/2 指触摸/鼠标并发送 LI_TOUCH_EVENT_CANCEL_ALL,防粘键 - 游戏菜单新增持久化开关“三指平移/缩放”(checkbox_three_finger_pan_zoom,默认开) - 关闭开关时回退旧行为:三指快击唤键盘 - 移除游戏菜单旧“平移与缩放”入口(按键映射 SPECIAL_KEY_PAN_ZOOM_MODE 保留) --- .../java/com/limelight/TouchInputHandler.kt | 90 +++++++++++++++++-- .../java/com/limelight/gamemenu/GameMenu.kt | 17 ++-- .../preferences/PreferenceConfiguration.kt | 5 ++ app/src/main/res/values-zh-rCN/strings.xml | 6 +- app/src/main/res/values/strings.xml | 6 +- 5 files changed, 105 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/limelight/TouchInputHandler.kt b/app/src/main/java/com/limelight/TouchInputHandler.kt index 4a13ba3a67..bc91e7478c 100644 --- a/app/src/main/java/com/limelight/TouchInputHandler.kt +++ b/app/src/main/java/com/limelight/TouchInputHandler.kt @@ -79,6 +79,13 @@ class TouchInputHandler(private val game: Game) { private var twoFingerStartX = 0f private var twoFingerStartY = 0f + // 三指平移/缩放状态(无需"平移与缩放"开关,第三指按下即进入,全部抬起才退出) + private var threeFingerMode = false + private var threeFingerDownTime = 0L + private var threeFingerMoved = false + private val threeFingerStartX = FloatArray(3) + private val threeFingerStartY = FloatArray(3) + private var lastAbsTouchUpTime = 0L private var lastAbsTouchDownTime = 0L private var lastAbsTouchUpX = 0f @@ -456,6 +463,75 @@ class TouchInputHandler(private val game: Game) { lastButtonState = buttonState } else { // This case is for fingers + val actionMasked = event.actionMasked + val pointerCount = event.pointerCount + + // 三指直接平移/缩放(游戏菜单"三指平移/缩放"开关控制,默认开启): + // 第三指按下即进入,事件全量交给 PanZoomHandler(平移/缩放联动 + // streamView 与本地光标),全部抬起才退出。原有的"三指快击唤键盘"行为保留: + // 无位移、300ms 内抬起、且唤键盘指数配置为 3 时仍触发键盘切换;只要手指 + // 动了(平移/缩放意图)就不再算快击。 + if (game.prefConfig.enableThreeFingerPanZoom && (pointerCount >= 3 || threeFingerMode)) { + if (!threeFingerMode) { + threeFingerMode = true + threeFingerDownTime = event.eventTime + threeFingerMoved = false + repeat(3) { i -> + threeFingerStartX[i] = event.getX(i) + threeFingerStartY[i] = event.getY(i) + } + // 取消此前 1/2 指已发给主机的触摸/鼠标状态,避免游戏侧"粘键/粘指" + twoFingerTapPending = false + twoFingerMoved = true + for (ctx in touchContextMap) ctx?.cancelTouch() + nativeTouchPointerMap.clear() + game.conn?.sendTouchEvent( + MoonBridge.LI_TOUCH_EVENT_CANCEL_ALL, 0, + 0f, 0f, 0f, 0f, 0f, + MoonBridge.LI_ROT_UNKNOWN + ) + } + + when (actionMasked) { + MotionEvent.ACTION_MOVE -> { + // 任一手指相对落点时位移超过阈值 => 视为真正的平移/缩放手势 + if (!threeFingerMoved && pointerCount >= 3) { + for (i in 0 until 3) { + val dx = event.getX(i) - threeFingerStartX[i] + val dy = event.getY(i) - threeFingerStartY[i] + if (sqrt(dx * dx + dy * dy) > THREE_FINGER_MOVE_THRESHOLD) { + threeFingerMoved = true + break + } + } + } + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + threeFingerMode = false + if (actionMasked == MotionEvent.ACTION_UP && !threeFingerMoved && + game.prefConfig.nativeTouchFingersToToggleKeyboard == 3 && + event.eventTime - threeFingerDownTime < MULTI_FINGER_TAP_THRESHOLD + ) { + game.toggleKeyboard() + } + } + else -> {} + } + + game.panZoomHandler.handleTouchEvent(event) + return true + } + + // 三指平移/缩放关闭时回退原行为:第三指落下即取消已下发的 1/2 指触摸, + // 快击(300ms 内抬起)仍可唤键盘(由下方 UP 分支的 multiFingerDownTime 判定) + if (!game.prefConfig.enableThreeFingerPanZoom && + actionMasked == MotionEvent.ACTION_POINTER_DOWN && pointerCount == 3 + ) { + multiFingerDownTime = event.eventTime + for (ctx in touchContextMap) ctx?.cancelTouch() + return true + } + if (event.actionMasked == MotionEvent.ACTION_DOWN) { enhancedTouchRouteOwner.finish() nativeTouchPointerMap.clear() @@ -490,6 +566,10 @@ class TouchInputHandler(private val game: Game) { nativeTouchPointerMap.clear() } + if (!game.prefConfig.touchscreenTrackpad && game.prefConfig.enableEnhancedTouch && trySendTouchEvent(view, event)) { + return true + } + if (game.virtualController != null && (game.virtualController?.controllerMode == VirtualController.ControllerMode.MoveButtons || game.virtualController?.controllerMode == VirtualController.ControllerMode.ResizeButtons) @@ -499,13 +579,6 @@ class TouchInputHandler(private val game: Game) { val actionIndex = event.actionIndex - // 三指手势特殊处理 - if (event.actionMasked == MotionEvent.ACTION_POINTER_DOWN && event.pointerCount == 3) { - multiFingerDownTime = event.eventTime - for (ctx in touchContextMap) ctx?.cancelTouch() - return true - } - val context = getTouchContext(actionIndex) ?: return false when (event.actionMasked) { @@ -1401,10 +1474,11 @@ class TouchInputHandler(private val game: Game) { const val TOUCH_CONTEXT_LENGTH = 2 private const val TWO_FINGER_TAP_THRESHOLD = 100L private const val TWO_FINGER_MOVE_THRESHOLD = 40f - // Matches the verified HarmonyOS DS5 touchpad behavior (24vp tap slop, +// Matches the verified HarmonyOS DS5 touchpad behavior (24vp tap slop, // 50ms clickpad hold). private const val SCREEN_DS5_TAP_MOVEMENT_DP = 24f private const val SCREEN_DS5_CLICK_RELEASE_MS = 50L + private const val THREE_FINGER_MOVE_THRESHOLD = 40f private const val STYLUS_DOWN_DEAD_ZONE_DELAY = 100L private const val STYLUS_DOWN_DEAD_ZONE_RADIUS = 20f private const val STYLUS_UP_DEAD_ZONE_DELAY = 150L diff --git a/app/src/main/java/com/limelight/gamemenu/GameMenu.kt b/app/src/main/java/com/limelight/gamemenu/GameMenu.kt index 834aa96075..a3f2b896c8 100644 --- a/app/src/main/java/com/limelight/gamemenu/GameMenu.kt +++ b/app/src/main/java/com/limelight/gamemenu/GameMenu.kt @@ -2156,19 +2156,25 @@ class GameMenu( inlineControl = InlineControl.Segmented(buildTouchModeSegments(compactLabels = true)) )) + // 三指平移/缩放(默认开启,状态持久化;关闭时回退"三指快击唤键盘") normalOptions.add(MenuOption( - label = getString(R.string.game_menu_enable_pan_zoom).trim(), + label = getString(R.string.game_menu_enable_three_finger_pan_zoom).trim(), isWithGameFocus = false, runnable = Runnable { + val enabled = !game.prefConfig.enableThreeFingerPanZoom + game.prefConfig.enableThreeFingerPanZoom = enabled + android.preference.PreferenceManager.getDefaultSharedPreferences(game).edit { + putBoolean(PreferenceConfiguration.THREE_FINGER_PAN_ZOOM_PREF_STRING, enabled) + } Toast.makeText(game, - if (game.getisTouchOverrideEnabled()) getString(R.string.toast_pan_zoom_disabled) else getString(R.string.toast_pan_zoom_enabled), + if (enabled) getString(R.string.toast_three_finger_pan_zoom_enabled) + else getString(R.string.toast_three_finger_pan_zoom_disabled), Toast.LENGTH_SHORT).show() - game.setisTouchOverrideEnabled(!game.getisTouchOverrideEnabled()) }, - iconKey = "game_menu_mouse_emulation", + iconKey = "game_menu_enable_three_finger_pan_zoom", isShowIcon = true, isKeepDialog = true, - inlineControl = InlineControl.Toggle(game.getisTouchOverrideEnabled()) + inlineControl = InlineControl.Toggle(game.prefConfig.enableThreeFingerPanZoom) )) // 王冠功能 @@ -2301,6 +2307,7 @@ class GameMenu( "game_menu_cancel" to R.drawable.ic_cancel_cute, "mouse_mode" to R.drawable.ic_mouse_cute, "game_menu_mouse_emulation" to R.drawable.ic_mouse_emulation_cute, + "game_menu_enable_three_finger_pan_zoom" to R.drawable.ic_mouse_emulation_cute, "crown_function_menu" to R.drawable.ic_super_crown, "crown_visibility" to R.drawable.ic_ui_settings, "crown_touch" to R.drawable.ic_touch_settings, diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.kt b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.kt index be7fa3b96b..d2df639ada 100644 --- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.kt +++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.kt @@ -106,6 +106,7 @@ class PreferenceConfiguration { var enhanceTouchZoneDivider = 0 //Assigned to NativeTouchContext.ENHANCED_TOUCH_ZONE_DIVIDER var pointerVelocityFactor = 0f //Assigned to NativeTouchContext.POINTER_VELOCITY_FACTOR var nativeTouchFingersToToggleKeyboard = 0 // Number of fingers to tap to toggle local on-screen keyboard in native touch mode. + var enableThreeFingerPanZoom = true // 三指平移/缩放(游戏菜单开关持久化,默认开) var videoFormat: FormatOption = FormatOption.AUTO var deadzonePercentage = 0 @@ -647,6 +648,7 @@ class PreferenceConfiguration { const val SYNC_TOUCH_EVENT_WITH_DISPLAY_PREF_STRING = "checkbox_sync_touch_event_with_display" const val ENABLE_KEYBOARD_TOGGLE_IN_NATIVE_TOUCH = "checkbox_enable_keyboard_toggle_in_native_touch" const val NATIVE_TOUCH_FINGERS_TO_TOGGLE_KEYBOARD_PREF_STRING = "seekbar_keyboard_toggle_fingers_native_touch" + const val THREE_FINGER_PAN_ZOOM_PREF_STRING = "checkbox_three_finger_pan_zoom" const val AUDIO_CONFIG_PREF_STRING = "list_audio_config" /** Audio codec preference: "auto" | "opus" | "ac3" | "eac3" */ const val AUDIO_CODEC_PREF_STRING = "list_audio_codec" @@ -1257,6 +1259,9 @@ class PreferenceConfiguration { config.nativeTouchFingersToToggleKeyboard = -1 // completely disable keyboard toggle in multi-point touch } + // 三指平移/缩放(游戏菜单"三指平移/缩放"开关,持久化) + config.enableThreeFingerPanZoom = prefs.getBoolean(THREE_FINGER_PAN_ZOOM_PREF_STRING, true) + // Enhance touch settings config.enhancedTouchOnWhichSide = prefs.getBoolean(ENHANCED_TOUCH_ON_RIGHT_PREF_STRING, true) // by default, enhanced touch zone is on the right side. config.enhanceTouchZoneDivider = prefs.getInt(ENHANCED_TOUCH_ZONE_DIVIDER_PREF_STRING, 50) // decides where to divide native touch zone & enhance touch zone diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 5b50ab2471..9b20853d1b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1476,9 +1476,9 @@ 两者 卡片配置 请在设置中开启麦克风重定向 - 平移与缩放 - 已关闭平移/缩放 - 已开启平移/缩放 + 三指平移/缩放 + 已关闭三指平移/缩放 + 已开启三指平移/缩放 发送超级命令时发生错误: %s 未获得安装权限,下载已取消 检查更新失败,请稍后重试 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c94f302d98..bb88e57bf7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1755,9 +1755,9 @@ Tap again to replace it. Adjust game menu opacity, currently %1$d%% , Enable microphone redirection in Settings first - Pan and Zoom - Pan/Zoom disabled - Pan/Zoom enabled + Three-Finger Pan and Zoom + Three-finger Pan/Zoom disabled + Three-finger Pan/Zoom enabled Couldn\'t send super command: %s Install permission denied, download cancelled Failed to check for updates, please try later From 44c09e79a8d556ed9dc316d03d312ee209acc9a7 Mon Sep 17 00:00:00 2001 From: cjcxj Date: Fri, 28 Aug 2026 08:52:36 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(input):=20CodeRabbit=20review=20-=20?= =?UTF-8?q?=E4=B8=89=E6=8C=87=E5=BC=80=E5=85=B3=E5=A4=8D=E9=80=89=E6=A1=86?= =?UTF-8?q?=E5=8F=AF=E7=82=B9=20+=20=E5=B0=BE=E5=B7=B4=E6=AE=B5=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. GameMenu:三指平移/缩放开关的 InlineControl.Toggle 缺少 toggleAction, 点击复选框时 handleInlineToggle 直接 return 不生效;切换逻辑抽为 局部 Runnable 并同时挂到 runnable(行点击)与 toggleAction(复选框点击), 与王冠开关的并存模式一致。 2. TouchInputHandler:三指模式尾巴段(部分手指抬起、pointerCount 降到 2/1)不再检测移动,抬一指后继续双指平移仍可能被当作"快击"误触键盘 切换。进入模式时记录原始 pointerId,移动检测按 ID 追踪剩余手指, 任一剩余手指位移超阈值即标记为有意图手势。 --- .../java/com/limelight/TouchInputHandler.kt | 16 +++++++--- .../java/com/limelight/gamemenu/GameMenu.kt | 30 +++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/limelight/TouchInputHandler.kt b/app/src/main/java/com/limelight/TouchInputHandler.kt index bc91e7478c..c05f0da362 100644 --- a/app/src/main/java/com/limelight/TouchInputHandler.kt +++ b/app/src/main/java/com/limelight/TouchInputHandler.kt @@ -85,6 +85,8 @@ class TouchInputHandler(private val game: Game) { private var threeFingerMoved = false private val threeFingerStartX = FloatArray(3) private val threeFingerStartY = FloatArray(3) + /** 进入三指模式时的原始 pointerId,用于模式尾巴段(部分手指已抬起)仍能按 ID 追踪移动 */ + private val threeFingerStartPointerIds = IntArray(3) private var lastAbsTouchUpTime = 0L private var lastAbsTouchDownTime = 0L @@ -479,6 +481,7 @@ class TouchInputHandler(private val game: Game) { repeat(3) { i -> threeFingerStartX[i] = event.getX(i) threeFingerStartY[i] = event.getY(i) + threeFingerStartPointerIds[i] = event.getPointerId(i) } // 取消此前 1/2 指已发给主机的触摸/鼠标状态,避免游戏侧"粘键/粘指" twoFingerTapPending = false @@ -494,11 +497,16 @@ class TouchInputHandler(private val game: Game) { when (actionMasked) { MotionEvent.ACTION_MOVE -> { - // 任一手指相对落点时位移超过阈值 => 视为真正的平移/缩放手势 - if (!threeFingerMoved && pointerCount >= 3) { + // 任一手指相对落点时位移超过阈值 => 视为真正的平移/缩放手势。 + // 按原始 pointerId 追踪:模式尾巴段(部分手指已抬起、pointerCount + // 降到 2/1)剩余手指的移动同样计入,避免"抬一指后双指平移仍被 + // 算作快击"而误触键盘切换。 + if (!threeFingerMoved) { for (i in 0 until 3) { - val dx = event.getX(i) - threeFingerStartX[i] - val dy = event.getY(i) - threeFingerStartY[i] + val idx = event.findPointerIndex(threeFingerStartPointerIds[i]) + if (idx < 0) continue // 该指已抬起 + val dx = event.getX(idx) - threeFingerStartX[i] + val dy = event.getY(idx) - threeFingerStartY[i] if (sqrt(dx * dx + dy * dy) > THREE_FINGER_MOVE_THRESHOLD) { threeFingerMoved = true break diff --git a/app/src/main/java/com/limelight/gamemenu/GameMenu.kt b/app/src/main/java/com/limelight/gamemenu/GameMenu.kt index a3f2b896c8..1d20bef6e5 100644 --- a/app/src/main/java/com/limelight/gamemenu/GameMenu.kt +++ b/app/src/main/java/com/limelight/gamemenu/GameMenu.kt @@ -2157,24 +2157,30 @@ class GameMenu( )) // 三指平移/缩放(默认开启,状态持久化;关闭时回退"三指快击唤键盘") + // 行点击与复选框点击共用同一切换逻辑:runnable 走行点击, + // toggleAction 走复选框(InlineControl.Toggle 无 toggleAction 时点击复选框不生效) + val threeFingerPanZoomToggle = Runnable { + val enabled = !game.prefConfig.enableThreeFingerPanZoom + game.prefConfig.enableThreeFingerPanZoom = enabled + android.preference.PreferenceManager.getDefaultSharedPreferences(game).edit { + putBoolean(PreferenceConfiguration.THREE_FINGER_PAN_ZOOM_PREF_STRING, enabled) + } + Toast.makeText(game, + if (enabled) getString(R.string.toast_three_finger_pan_zoom_enabled) + else getString(R.string.toast_three_finger_pan_zoom_disabled), + Toast.LENGTH_SHORT).show() + } normalOptions.add(MenuOption( label = getString(R.string.game_menu_enable_three_finger_pan_zoom).trim(), isWithGameFocus = false, - runnable = Runnable { - val enabled = !game.prefConfig.enableThreeFingerPanZoom - game.prefConfig.enableThreeFingerPanZoom = enabled - android.preference.PreferenceManager.getDefaultSharedPreferences(game).edit { - putBoolean(PreferenceConfiguration.THREE_FINGER_PAN_ZOOM_PREF_STRING, enabled) - } - Toast.makeText(game, - if (enabled) getString(R.string.toast_three_finger_pan_zoom_enabled) - else getString(R.string.toast_three_finger_pan_zoom_disabled), - Toast.LENGTH_SHORT).show() - }, + runnable = threeFingerPanZoomToggle, iconKey = "game_menu_enable_three_finger_pan_zoom", isShowIcon = true, isKeepDialog = true, - inlineControl = InlineControl.Toggle(game.prefConfig.enableThreeFingerPanZoom) + inlineControl = InlineControl.Toggle( + checked = game.prefConfig.enableThreeFingerPanZoom, + toggleAction = threeFingerPanZoomToggle + ) )) // 王冠功能