From eeba11e643eeebdc9313509e6a899923928f5a54 Mon Sep 17 00:00:00 2001 From: JianMiau Date: Tue, 25 Aug 2026 15:24:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=A7=A3=E9=8E=96=E8=9E=A2?= =?UTF-8?q?=E5=B9=95=E5=BE=8C=E4=B8=8D=E8=83=BD=E6=8B=96=E6=9B=B3=E3=80=81?= =?UTF-8?q?=E4=B8=8D=E8=83=BD=E5=8F=B3=E9=8D=B5=EF=BC=9A=E4=BA=92=E5=8B=95?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E4=BE=9D=E8=B3=B4=20mousedown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根本原因: 2.0.13 的事件紀錄證實:鎖定螢幕再解鎖後,renderer 只收得到 mouseup 與 mousemove,左右鍵的 mousedown 全部被吃掉,直到重啟才恢復(符合 Windows 對 永不啟用視窗的 WM_MOUSEACTIVATE 回傳 MA_NOACTIVATEANDEAT 的行為,只丟掉按下 那一則訊息)。而左鍵靠 mousedown 設 pressed、右鍵靠 mousedown 開選單,兩者 因此同時失效。 影響: 每次鎖定/解鎖後寵物就不能拖曳、不能右鍵,只能重啟。 修法: 右鍵選單改在 mouseup 開(也是 Windows 慣例);mousemove 的 buttons 位元帶著 「左鍵按著」而沒有 pressed 時補一個 synthetic pressed,拖曳照常;沒有 mousedown 的左鍵 mouseup 當成單擊。補上對應單元測試與 README 說明。 版號 2.0.14。 Co-Authored-By: Claude Fable 5 --- README.md | 1 + package-lock.json | 4 ++-- package.json | 2 +- renderer/renderer.js | 24 +++++++++++++++++++++--- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d1e1c62..6b8385b 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,7 @@ Claude Code ──hook(stdin JSON)──▶ hook/claude-pet-hook.js ──PO - **拖曳結束一定通知 renderer**:視窗永遠不取得焦點,Windows 對背景視窗的滑鼠 capture 有限制,放開的那一下若落在視窗外 renderer 收不到 mouseup。主程序不管因為 mouseup、逾時(20 秒)或其他原因結束拖曳,都送 `pet:drag-ended` 讓 renderer 清掉按住/拖曳狀態。 - **右鍵選單開著時暫停置頂重宣告**:否則 `moveTop()` 會把寵物視窗抬到選單上面,人物方框整塊接滑鼠,被蓋住的選單項目就點不到。 - **診斷**:`GET /state` 除了動畫狀態,還會回 `bounds`、`mouse`(目前是否點穿、游標的視窗相對座標、是否在方框/氣泡內、矩形來源是 renderer 還是備援、切換次數、輪詢次數、renderer 錯誤數、最近幾次拖曳結束的原因、鎖定/解鎖/睡眠/喚醒事件)與 `rendererMouse`(renderer 這邊的 pressed/drag/hovered/overlay)。renderer 收到的每個 mousedown/mouseup、hover 進出、例外,以及拖曳開始/結束、選單開關、鎖定/解鎖都會寫進事件紀錄——「點不到寵物」時先看紀錄裡有沒有 `mousedown`,就能分清是 OS 沒把事件送進來,還是 renderer 收到了但狀態卡住。 +- **解鎖螢幕後 mousedown 會被吃掉**:實測(v2.0.13 的事件紀錄)鎖定再解鎖後,renderer 只收得到 mouseup 與 mousemove,左右鍵的 mousedown 全部消失,直到重啟才恢復——症狀就是「不能拖曳、不能右鍵」。行為符合 Windows 對永不啟用(`WS_EX_NOACTIVATE`)視窗的 `WM_MOUSEACTIVATE` 回傳 `MA_NOACTIVATEANDEAT`(只丟掉按下那一則訊息),確切觸發條件尚未定位。因應是讓所有互動都不依賴 mousedown:右鍵選單在 mouseup 開(也是 Windows 慣例);mousemove 的 `buttons` 帶著「左鍵按著」而沒有 pressed 時補一個 pressed,拖曳照常;沒有 mousedown 的左鍵 mouseup 當成單擊。 - **鎖定/解鎖後重新套用**:監聽 `powerMonitor` 的 `unlock-screen` 與 `resume`,解鎖或喚醒後重新套用目前的點穿狀態、`showInactive()` 並重宣告置頂(session 切換後 Windows 可能把視窗的輸入或 z-order 狀態弄掉)。 - **拖曳**:由主程序用 `screen.getCursorScreenPoint()` 每 16 ms 更新視窗位置,游標離開視窗也不會掉;方向由水平位移決定。 - **置頂要定期重新宣告**:Windows 有時候會把視窗踢出 topmost band,卻留著 `WS_EX_TOPMOST` 樣式位元——從外部檢查看到 `TOPMOST=true`,但 z-order 排在一般視窗(例如 VS Code)之下,寵物就被蓋住,而 Electron 這邊仍以為自己是置頂的,不會自己修好。所以每 2 秒重新宣告一次,並在螢幕配置變動時立刻補一次。`setAlwaysOnTop` 在狀態沒變時可能不會真的呼叫 `SetWindowPos`,因此再補一個 `moveTop()` 強制插回 topmost band 最上面;視窗是 `WS_EX_NOACTIVATE` + `focusable: false`,不會搶焦點。 diff --git a/package-lock.json b/package-lock.json index 9dab6ef..4f710d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-pet", - "version": "2.0.13", + "version": "2.0.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-pet", - "version": "2.0.13", + "version": "2.0.14", "devDependencies": { "electron": "^43.4.1", "electron-builder": "^26.0.12" diff --git a/package.json b/package.json index 8b05ba0..57da1ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-pet", - "version": "2.0.13", + "version": "2.0.14", "description": "Codex Pets 相容的 Claude Code 桌面寵物(spriteVersionNumber 2:9 個動作列 + 16 方向追視)", "main": "main.js", "private": true, diff --git a/renderer/renderer.js b/renderer/renderer.js index e613d4e..7f30763 100644 --- a/renderer/renderer.js +++ b/renderer/renderer.js @@ -676,6 +676,13 @@ function refreshHitTest() { } window.addEventListener("mousemove", (e) => { + // 鎖定螢幕再解鎖後,Windows/Chromium 會把送給這個視窗的每一個 mousedown 吃掉, + // mouseup 與 mousemove 卻照送(實測見事件紀錄)。mousemove 的 buttons 位元仍然帶著 + // 「左鍵按著」,用它補一個 pressed,拖曳才不會因為少了 down 就失效。 + if (!pressed && !drag && (e.buttons & 1)) { + pressed = { x: e.clientX, y: e.clientY, sx: e.screenX, sy: e.screenY, moved: false, synthetic: true }; + log(`mousedown missing; synthesized from buttons at ${e.clientX},${e.clientY}`); + } if (pressed) { if (!pressed.moved && Math.hypot(e.screenX - pressed.sx, e.screenY - pressed.sy) > 4) { pressed.moved = true; @@ -700,15 +707,25 @@ window.addEventListener("mousedown", (e) => { log(`mousedown b=${e.button} at ${e.clientX},${e.clientY} pressed=${!!pressed} drag=${!!drag}`); if (e.button === 0) { pressed = { x: e.clientX, y: e.clientY, sx: e.screenX, sy: e.screenY, moved: false }; - } else if (e.button === 2) { - window.pet.send("pet:context-menu"); } report(); }); +// 點擊與右鍵都以 mouseup 為準:解鎖後 mousedown 會被吃掉(見上),mouseup 不會; +// 右鍵選單在放開時開也是 Windows 的慣例。 window.addEventListener("mouseup", (e) => { log(`mouseup b=${e.button} at ${e.clientX},${e.clientY} pressed=${!!pressed} moved=${!!pressed?.moved}`); - if (e.button !== 0 || !pressed) return; + if (e.button === 2) { + window.pet.send("pet:context-menu"); + return; + } + if (e.button !== 0) return; + if (!pressed) { + log("mouseup without mousedown; treating as click"); + onClick(); + report(); + return; + } const wasDrag = pressed.moved; pressed = null; if (wasDrag) { @@ -718,6 +735,7 @@ window.addEventListener("mouseup", (e) => { } else { onClick(); } + report(); }); window.addEventListener("contextmenu", (e) => e.preventDefault());