diff --git a/WebRTCController/app/src/main/java/com/ttstd/controller/activity/main/MainActivity.java b/WebRTCController/app/src/main/java/com/ttstd/controller/activity/main/MainActivity.java index b4b4fe1..681e85f 100644 --- a/WebRTCController/app/src/main/java/com/ttstd/controller/activity/main/MainActivity.java +++ b/WebRTCController/app/src/main/java/com/ttstd/controller/activity/main/MainActivity.java @@ -214,10 +214,11 @@ public class MainActivity extends BaseMvvmActivity 直接回登录页(与 WebSocket 4001/4003 行为一致)。 - UnauthorizedInterceptor.setHandler(msg -> { + // 注意:该回调运行在 OkHttp 拦截器线程,必须切回主线程再操作 Toast / UI。 + UnauthorizedInterceptor.setHandler(msg -> runOnUiThread(() -> { Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show(); logoutAndReset(); - }); + })); // 令牌就绪 -> 建立信令 WebSocket viewModel.getAccessToken().observe(this, token -> { @@ -303,7 +304,9 @@ public class MainActivity extends BaseMvvmActivity items) { for (BindingItem item : items) { int idx = -1; @@ -323,7 +326,9 @@ public class MainActivity extends BaseMvvmActivity Boolean.compare(b.isOnline(), a.isOnline())); } - /** 设备列表为空时显示占位提示。 */ + /** + * 设备列表为空时显示占位提示。 + */ private void updateDeviceEmpty() { boolean empty = deviceList.isEmpty(); binding.tvDeviceEmpty.setVisibility(empty ? View.VISIBLE : View.GONE); diff --git a/WebRTCControllerWeb/src/components/ControlBar.vue b/WebRTCControllerWeb/src/components/ControlBar.vue index c63d42a..04f91f0 100644 --- a/WebRTCControllerWeb/src/components/ControlBar.vue +++ b/WebRTCControllerWeb/src/components/ControlBar.vue @@ -29,11 +29,19 @@ const selectedResolution = ref(0); const fpsOptions = ref([15, 24, 30, 60]); const selectedFps = ref(0); // 0 表示尚未同步到被控端当前帧率 +// 记录当前处于按下状态的按键:pointerleave/pointerup 在指针未按下时也会触发, +// 若无此跟踪,鼠标仅扫过按钮就会向被控端发送孤立的 KEY UP。 +const pressedButtons = new Set(); + function pressDown(code) { if (!store.dataChannelOpen) return; + if (pressedButtons.has(code)) return; + pressedButtons.add(code); sendKeyDown(code); } function pressUp(code) { + if (!pressedButtons.has(code)) return; + pressedButtons.delete(code); if (!store.dataChannelOpen) return; sendKeyUp(code); } @@ -71,18 +79,26 @@ function lookupKeyCode(e) { } // 固定按键:按下/抬起分离(如电源键需要真实按下-抬起配对) +// pressedKeys 跟踪已按下的物理键,避免长按自动重复触发重复 DOWN、 +// 以及无对应 DOWN 的孤立 UP(如在窗口外按下、回到窗口内才抬起)。 +const pressedKeys = new Set(); + function onKeyCaptureDown(e) { if (!store.dataChannelOpen) return; const m = lookupKeyCode(e); if (!m) return; e.preventDefault(); + if (pressedKeys.has(m.code)) return; // 浏览器长按自动重复,忽略 + pressedKeys.add(m.code); sendKeyDown(m.code); } function onKeyCaptureUp(e) { - if (!store.dataChannelOpen) return; const m = lookupKeyCode(e); if (!m) return; e.preventDefault(); + if (!pressedKeys.has(m.code)) return; + pressedKeys.delete(m.code); + if (!store.dataChannelOpen) return; sendKeyUp(m.code); } diff --git a/WebRTCControllerWeb/src/components/RemoteScreen.vue b/WebRTCControllerWeb/src/components/RemoteScreen.vue index 8ff4248..10cc18b 100644 --- a/WebRTCControllerWeb/src/components/RemoteScreen.vue +++ b/WebRTCControllerWeb/src/components/RemoteScreen.vue @@ -45,6 +45,9 @@ const TOUCH_SLOP = 0.02; const SAMPLE_MS = 16; let startX = 0, startY = 0, startTime = 0, isLongPressed = false, longTimer = null, lastMoveTs = 0; +// 指针是否处于按下状态:未按下时 pointermove/pointerup 不得产生任何指令, +// 否则鼠标仅悬停划过画面就会向被控端发送大量 ACTION_MOVE。 +let isDown = false; function clearLong() { if (longTimer) { clearTimeout(longTimer); longTimer = null; } @@ -53,6 +56,7 @@ function clearLong() { function onDown(e) { if (!active.value) return; overlayRef.value.setPointerCapture && overlayRef.value.setPointerCapture(e.pointerId); + isDown = true; isLongPressed = false; const p = mapRelative(e.clientX, e.clientY); startX = p.x; startY = p.y; startTime = Date.now(); lastMoveTs = 0; @@ -65,7 +69,7 @@ function onDown(e) { } function onMove(e) { - if (!active.value) return; + if (!active.value || !isDown) return; const p = mapRelative(e.clientX, e.clientY); if (Math.abs(p.x - startX) > TOUCH_SLOP || Math.abs(p.y - startY) > TOUCH_SLOP) clearLong(); const now = Date.now(); @@ -76,7 +80,8 @@ function onMove(e) { } function onUp(e) { - if (!active.value) return; + if (!active.value || !isDown) return; + isDown = false; clearLong(); const p = mapRelative(e.clientX, e.clientY); sendMotionEvent(1, p.x, p.y); // ACTION_UP @@ -88,6 +93,15 @@ function onUp(e) { else sendSwipe(startX, startY, p.x, p.y, Math.max(dur, 1)); } } + +function onCancel() { + clearLong(); + // 按下过程中指针被取消(如系统手势接管):补发 ACTION_UP 释放,避免被控端卡在按下态。 + if (isDown && active.value) { + isDown = false; + sendMotionEvent(1, startX, startY); // ACTION_UP + } +}