diff --git a/electron/ipc/register/capturePicker.ts b/electron/ipc/register/capturePicker.ts index 236d7e55..4b93088b 100644 --- a/electron/ipc/register/capturePicker.ts +++ b/electron/ipc/register/capturePicker.ts @@ -1,5 +1,9 @@ import { ipcMain } from "electron"; -import { closeCapturePickerWindows, createCapturePickerWindows } from "../../windows"; +import { + closeCapturePickerWindows, + createCapturePickerWindows, + isCapturePickerWebContents, +} from "../../windows"; import { getNativeMacWindowsFrontToBack, stopWindowBoundsCapture } from "../cursor/bounds"; import { setSelectedSource } from "../state"; import { createAreaSource, createWindowSource, normalizeCapturePick } from "../sourceArea"; @@ -115,7 +119,9 @@ export function registerCapturePickerHandlers() { return { success: true, source, record: pick.record }; }); - ipcMain.handle("complete-capture-pick", async (_, input: unknown) => { + ipcMain.handle("complete-capture-pick", async (event, input: unknown) => { + // Only the picker overlays answer a pick. + if (!isCapturePickerWebContents(event.sender)) return; if (!input) { finishPick(null); return; diff --git a/electron/windows.ts b/electron/windows.ts index 1b60bd11..486d3868 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -3,7 +3,7 @@ import fs from "node:fs"; import { createRequire } from "node:module"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import { app, BrowserWindow, ipcMain } from "electron"; +import { app, BrowserWindow, ipcMain, type WebContents } from "electron"; import { supportsHudCaptureProtection, shouldProtectHudCapture, @@ -1261,6 +1261,11 @@ export function closeCapturePickerWindows(): void { } } +/** Whether a message came from one of the open picker overlays. */ +export function isCapturePickerWebContents(contents: WebContents): boolean { + return capturePickerWindows.some((win) => !win.isDestroyed() && win.webContents === contents); +} + /** * Outlines the area being recorded. The outline sits just outside the recorded * rectangle and belongs to Recordly, which recordings exclude, so it never shows diff --git a/src/components/capture-picker/CapturePicker.tsx b/src/components/capture-picker/CapturePicker.tsx index ec7f6195..62215834 100644 --- a/src/components/capture-picker/CapturePicker.tsx +++ b/src/components/capture-picker/CapturePicker.tsx @@ -25,8 +25,15 @@ type Selection = Target | { kind: "area"; rect: AreaRect }; type Drag = | { kind: "press"; startX: number; startY: number } | { kind: "draw"; originX: number; originY: number } - | { kind: "move"; startX: number; startY: number; rect: AreaRect } - | { kind: "resize"; handle: AreaHandle; startX: number; startY: number; rect: AreaRect }; + | { kind: "move"; startX: number; startY: number; rect: AreaRect; moved: boolean } + | { + kind: "resize"; + handle: AreaHandle; + startX: number; + startY: number; + rect: AreaRect; + moved: boolean; + }; const TOOLBAR_HEIGHT = 44; const TOOLBAR_GAP = 12; @@ -190,6 +197,7 @@ export function CapturePicker() { startX: event.clientX, startY: event.clientY, rect: current.rect, + moved: false, }; } else if (current && rectContains(current.rect, event.clientX, event.clientY)) { dragRef.current = { @@ -197,6 +205,7 @@ export function CapturePicker() { startX: event.clientX, startY: event.clientY, rect: current.rect, + moved: false, }; } else { dragRef.current = { kind: "press", startX: event.clientX, startY: event.clientY }; @@ -245,7 +254,8 @@ export function CapturePicker() { const dx = event.clientX - active.startX; const dy = event.clientY - active.startY; // A click inside a picked window keeps it; only real movement turns it into an area. - if (!dragging && Math.hypot(dx, dy) < DRAG_THRESHOLD / 2) return; + if (!active.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD / 2) return; + active.moved = true; setDragging(true); setSelection({ kind: "area", diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index e83e6cfb..b1e71ad3 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -15,6 +15,7 @@ import { import { AnimatePresence, motion } from "motion/react"; import { useEffect, useRef } from "react"; import { Separator } from "@/components/ui/separator"; +import { toast } from "@/components/ui/toast"; import { useScopedT } from "../../contexts/I18nContext"; import { useMicrophoneDevices } from "../../hooks/useMicrophoneDevices"; import { useScreenRecorder } from "../../hooks/useScreenRecorder"; @@ -188,7 +189,10 @@ function LaunchWindowContent() { const handlePickOnScreen = async () => { const result = await window.electronAPI.pickCaptureTarget(); - if (!result.success) return; + if (!result.success) { + if (!result.canceled && result.message) toast.error(result.message); + return; + } if (result.record) { toggleRecording(); } else if (result.source) {