加入滑鼠事件紀錄與鎖定/解鎖處理,追查解鎖後點不到寵物

根本原因:
使用者回報登出/鎖定螢幕再回來後就不能拖曳、不能右鍵。現場量測顯示 OS
這一層正常(點穿位元會切、WindowFromPoint 打到寵物視窗),但事件紀錄裡
完全沒有拖曳或選單的痕跡,代表問題在 renderer 有沒有收到滑鼠事件這一層,
而這一層目前沒有任何可觀測資料,無法判斷。

影響:
解鎖後偶發點不到寵物,且發生時查不出卡在哪裡。

修法:
1. renderer 把 mousedown/mouseup(含按鍵與座標、當時的 pressed/drag)與
   hover 進出寫進事件紀錄;pet:state 帶 rendererMouse(pressed/drag/hovered
   /overlay),/state 可直接查。
2. 主程序監聽 powerMonitor 的 lock-screen/unlock-screen/suspend/resume:
   記錄到事件紀錄與 /state.mouse.sessionEvents,解鎖或喚醒後重新套用目前的
   點穿狀態、showInactive 並重宣告置頂。
版號 2.0.12。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 14:56:59 +08:00
co-authored by Claude Fable 5
parent bf059b19cf
commit 30d3e481e4
4 changed files with 37 additions and 5 deletions
+22 -1
View File
@@ -1,5 +1,5 @@
"use strict";
const { app, BrowserWindow, Tray, Menu, screen, ipcMain, nativeImage, shell, dialog } = require("electron");
const { app, BrowserWindow, Tray, Menu, screen, ipcMain, nativeImage, shell, dialog, powerMonitor } = require("electron");
const http = require("node:http");
const fs = require("node:fs");
const path = require("node:path");
@@ -69,6 +69,7 @@ const mouseDiag = {
toggles: 0, lastToggleAt: null, polls: 0, lastPollAt: null,
rel: null, inBox: false, inBubble: false,
rendererErrors: 0, lastRendererError: null, dragEnds: [],
sessionEvents: [], // 鎖定/解鎖/睡眠/喚醒
};
let currentState = { anim: "idle", base: "idle", sessions: 0 };
@@ -374,6 +375,25 @@ function reassertTopmost() {
} catch { /* 視窗正在關閉就算了 */ }
}
// 鎖定/解鎖與睡眠喚醒:session 切換後 Windows 可能把視窗的輸入或 z-order 狀態弄掉,
// 記下來並把目前的點穿狀態與置頂重新套用一次
function watchSessionChanges() {
for (const ev of ["lock-screen", "unlock-screen", "suspend", "resume"]) {
powerMonitor.on(ev, () => {
appendLog(`power: ${ev}`);
mouseDiag.sessionEvents.push({ at: Date.now(), ev });
if (mouseDiag.sessionEvents.length > 10) mouseDiag.sessionEvents.shift();
if (ev !== "unlock-screen" && ev !== "resume") return;
if (!win || win.isDestroyed()) return;
try {
win.setIgnoreMouseEvents(ignoring, { forward: true });
win.showInactive();
} catch { /* 視窗正在關閉 */ }
reassertTopmost();
});
}
}
function startTopmostGuard() {
topmostTimer = setInterval(reassertTopmost, TOPMOST_REASSERT_MS);
// 螢幕配置變動最容易觸發降級,變動後立刻補一次
@@ -735,6 +755,7 @@ if (!app.requestSingleInstanceLock()) {
startServer();
startCursorPolling();
startTopmostGuard();
watchSessionChanges();
appendLog(`started pet=${activePet()?.id || "none"} scale=${config.scale} port=${config.port}`);
});
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "claude-pet",
"version": "2.0.11",
"version": "2.0.12",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-pet",
"version": "2.0.11",
"version": "2.0.12",
"devDependencies": {
"electron": "^43.4.1",
"electron-builder": "^26.0.12"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-pet",
"version": "2.0.11",
"version": "2.0.12",
"description": "Codex Pets 相容的 Claude Code 桌面寵物(spriteVersionNumber 29 個動作列 + 16 方向追視)",
"main": "main.js",
"private": true,
+12 -1
View File
@@ -518,7 +518,11 @@ function handleTest(p) {
function report() {
const base = baseState();
const snapshot = JSON.stringify({ anim: player.anim, base, sessions: sessions.size, pet: petInfo?.id || null });
const snapshot = JSON.stringify({
anim: player.anim, base, sessions: sessions.size, pet: petInfo?.id || null,
// renderer 這邊的滑鼠狀態也露到 /state,查「點不到寵物」時才看得出是不是卡在這裡
rendererMouse: { pressed: !!pressed, drag: !!drag, hovered, overlay: overlay?.anim || null },
});
if (snapshot !== lastReported) {
lastReported = snapshot;
window.pet.send("pet:state", JSON.parse(snapshot));
@@ -650,6 +654,8 @@ function overPetArea(x, y) {
function setHovered(on) {
if (on === hovered) return;
hovered = on;
log(`hover ${on ? "enter" : "leave"} pressed=${!!pressed} drag=${!!drag} overlay=${overlay?.anim || "-"}`);
report();
if (on) {
// 只在「進入」時觸發一次;一次性動作或拖曳進行中不打斷
if (!overlay && !drag) {
@@ -689,15 +695,19 @@ document.addEventListener("mouseout", (e) => {
setHovered(false);
});
// 滑鼠按下/放開都寫進事件紀錄:這是「有沒有收到滑鼠事件」唯一的直接證據
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();
});
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;
const wasDrag = pressed.moved;
pressed = null;
@@ -728,6 +738,7 @@ window.pet.on("pet:drag-ended", () => {
pressed = null;
drag = null;
canvas.classList.remove("grabbing");
report();
});
// 主程序切了點穿狀態:只負責換游標樣式