From 257fdc73583ed1ba6ee7f2529cbabbbe4a229dc7 Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Tue, 3 Nov 2026 09:59:57 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=A1=86=E9=80=89=E6=88=AA=E5=9B=BE?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E4=B8=8E=E5=A4=9A=E8=A6=86=E7=9B=96=E5=B1=82?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E8=81=9A=E5=90=88\n\n-=20=E4=B8=BB=E5=B1=8F?= =?UTF-8?q?=E6=88=AA=E5=88=B0=E5=BA=94=E7=94=A8=E7=AA=97=E5=8F=A3:=20?= =?UTF-8?q?=E4=B9=8B=E5=89=8D=E6=A1=86=E9=80=89=E7=BB=93=E6=9D=9F=E5=90=8E?= =?UTF-8?q?=E5=85=88=E6=81=A2=E5=A4=8D=E5=BA=94=E7=94=A8=E7=AA=97=E5=8F=A3?= =?UTF-8?q?=E5=86=8D=E6=88=AA=E5=9B=BE,=20=E5=BA=94=E7=94=A8=E5=BC=B9?= =?UTF-8?q?=E5=9B=9E=E7=9B=96=E4=BD=8F=E9=80=89=E5=8C=BA;=20=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=85=88=E9=94=80=E6=AF=81=E8=A6=86=E7=9B=96=E5=B1=82?= =?UTF-8?q?=E5=B9=B6=E6=88=AA=E5=9B=BE,=20=E6=9C=80=E5=90=8E=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E5=BA=94=E7=94=A8=E7=AA=97=E5=8F=A3\n-=20=E5=89=AF?= =?UTF-8?q?=E5=B1=8F=E6=8B=96=E6=8B=BD=E6=97=A0=E5=93=8D=E5=BA=94/Esc=20?= =?UTF-8?q?=E6=97=A0=E6=95=88:=20=E8=BD=AE=E8=AF=A2=E5=8F=AA=E8=AF=BB?= =?UTF-8?q?=E7=AC=AC=E4=B8=80=E4=B8=AA(=E4=B8=BB=E5=B1=8F)=E8=A6=86?= =?UTF-8?q?=E7=9B=96=E5=B1=82=E7=8A=B6=E6=80=81;=20=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E8=81=9A=E5=90=88=E6=89=80=E6=9C=89=E8=A6=86=E7=9B=96=E5=B1=82?= =?UTF-8?q?,=20=E4=BB=BB=E4=B8=80=E5=B1=8F=20Esc=20=E5=8F=AF=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E3=80=81=E4=BB=BB=E4=B8=80=E5=B1=8F=E6=8B=96=E6=8B=BD?= =?UTF-8?q?=E5=8F=AF=E8=AF=86=E5=88=AB\n-=20=E5=A4=9A=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E5=99=A8=E6=88=AA=E5=9B=BE=E6=BA=90=E5=8C=B9=E9=85=8D=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=8C=89=E4=B8=8B=E2=86=92=E5=8F=B3=E6=8E=92=E5=BA=8F?= =?UTF-8?q?=E5=AF=B9=E4=BD=8D=E5=9B=9E=E9=80=80(display=5Fid=20=E7=BC=BA?= =?UTF-8?q?=E5=A4=B1=E6=97=B6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/desktop-pick.ts | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/main/desktop-pick.ts b/src/main/desktop-pick.ts index 0637e82..13dbf5a 100644 --- a/src/main/desktop-pick.ts +++ b/src/main/desktop-pick.ts @@ -742,20 +742,26 @@ export async function pickScreenRegion(): Promise<{ x: number; y: number; width: let picked: { rect: { x: number; y: number; width: number; height: number }; display: (typeof displays)[number] } | null = null; while (Date.now() < deadline) { await new Promise(r => setTimeout(r, 100)); - let state: { down: boolean; wasDown: boolean; cancelled: boolean } | null = null; + // 聚合所有覆盖层的状态: 任一屏按 Esc 取消, 任一屏的按下/松开都要识别 + let down = false; + let wasDown = false; + let anyAlive = false; for (const { win } of overlays) { if (win.isDestroyed()) continue; try { const raw = await win.webContents.executeJavaScript("JSON.stringify({ down: !!window.__down, wasDown: !!window.__wasDown, cancelled: !!window.__cancelled })", true); - state = JSON.parse(raw) as { down: boolean; wasDown: boolean; cancelled: boolean }; - break; + const s = JSON.parse(raw) as { down: boolean; wasDown: boolean; cancelled: boolean }; + anyAlive = true; + down = down || s.down; + wasDown = wasDown || s.wasDown; + if (s.cancelled) { cancelled = true; } } catch {} } - if (!state) continue; - if (state.cancelled) { cancelled = true; break; } + if (cancelled) break; + if (!anyAlive) continue; const cursor = screen.getCursorScreenPoint(); - if (state.down && !downPoint) { downPoint = { x: cursor.x, y: cursor.y }; sawDown = true; } - if (!state.down && sawDown && downPoint) { + if (down && !downPoint) { downPoint = { x: cursor.x, y: cursor.y }; sawDown = true; } + if (!down && sawDown && downPoint) { const display = displayForPoint(downPoint.x, downPoint.y); const rx0 = Math.min(downPoint.x, cursor.x); const ry0 = Math.min(downPoint.y, cursor.y); @@ -765,11 +771,12 @@ export async function pickScreenRegion(): Promise<{ x: number; y: number; width: break; } } - for (const w of appWindows) { if (!w.isDestroyed()) { w.show(); w.restore(); w.focus(); } } - for (const { win } of overlays) if (!win.isDestroyed()) win.destroy(); if (cancelled) throw new RuntimeError("pick_cancelled", "已取消框选"); if (!picked || picked.rect.width <= 2 || picked.rect.height <= 2) throw new RuntimeError("pick_cancelled", "未完成框选"); const { rect, display } = picked; + // 关键顺序: 先销毁覆盖层、完成截图, 最后才恢复应用窗口—— + // 之前是先恢复窗口再截图, 应用弹回盖住选区, 截到的全是应用窗口内容 + for (const { win } of overlays) if (!win.isDestroyed()) win.destroy(); await new Promise(r => setTimeout(r, 300)); const outDir = path.join(app.getPath("userData"), "region-picks"); fs.mkdirSync(outDir, { recursive: true }); @@ -781,9 +788,12 @@ export async function pickScreenRegion(): Promise<{ x: number; y: number; width: types: ["screen"], thumbnailSize: { width: Math.round(display.bounds.width * scale), height: Math.round(display.bounds.height * scale) } }); + const physicalW = Math.round(display.bounds.width * scale); + // display_id 匹配 → 缩略图尺寸匹配 → 显示器数量一致时按下→右排序对位 + const displaysByX = [...displays].sort((a, b) => a.bounds.x - b.bounds.x); const source = sources.find(item => item.display_id === String(display.id)) - || sources.find(item => item.thumbnail.getSize().width === Math.round(display.bounds.width * scale)) - || sources[0]; + || sources.find(item => item.thumbnail.getSize().width === physicalW) + || (sources.length === displays.length ? sources[displaysByX.findIndex(d => d.id === display.id)] : sources[0]); if (!source || source.thumbnail.isEmpty()) { throw new RuntimeError("pick_capture_failed", "框选截图失败:未获取到屏幕画面"); } @@ -796,6 +806,8 @@ export async function pickScreenRegion(): Promise<{ x: number; y: number; width: const cropW = Math.max(1, Math.min(thumbnail.getSize().width - cropX, Math.round(rect.width * actualScale))); const cropH = Math.max(1, Math.min(thumbnail.getSize().height - cropY, Math.round(rect.height * actualScale))); fs.writeFileSync(imagePath, thumbnail.crop({ x: cropX, y: cropY, width: cropW, height: cropH }).toPNG()); + // 截图完成后才恢复应用窗口 + for (const w of appWindows) { if (!w.isDestroyed()) { w.show(); w.restore(); w.focus(); } } // 返回屏幕绝对 DIP 坐标(含负值/副屏偏移)+该屏缩放比, 运行时由此换算点击位置 return { x: rect.x,