Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 44 additions & 37 deletions electron/ipc/cursor/bounds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ import {
import type { NativeMacWindowSource, SelectedSource, WindowBounds } from "../types";
import { parseWindowId } from "../utils";
import { resolveWindowsWindowBounds } from "../windowsWindowControl";
import { createNonOverlappingRunner, createSingleFlight } from "./concurrency";

const execFileAsync = promisify(execFile);
const runNativeMacWindowSources = createSingleFlight<NativeMacWindowSource[]>();
const runWindowBoundsRefresh = createNonOverlappingRunner();

/** Return cached macOS windows or share one native enumeration among concurrent callers. */
export async function getNativeMacWindowSources(options?: { maxAgeMs?: number }) {
if (process.platform !== "darwin") {
return [] as NativeMacWindowSource[];
Expand All @@ -29,34 +33,35 @@ export async function getNativeMacWindowSources(options?: { maxAgeMs?: number })
if (cachedNativeMacWindowSources && now - cachedNativeMacWindowSourcesAtMs < maxAgeMs) {
return cachedNativeMacWindowSources;
}
return runNativeMacWindowSources(async () => {
try {
const binaryPath = await ensureNativeWindowListBinary();
const { stdout } = await execFileAsync(binaryPath, [], {
timeout: 30000,
maxBuffer: 10 * 1024 * 1024,
});

try {
const binaryPath = await ensureNativeWindowListBinary();
const { stdout } = await execFileAsync(binaryPath, [], {
timeout: 30000,
maxBuffer: 10 * 1024 * 1024,
});

const parsed = JSON.parse(stdout);
if (!Array.isArray(parsed)) {
return [] as NativeMacWindowSource[];
}

const entries = parsed.filter((entry: unknown): entry is NativeMacWindowSource => {
if (!entry || typeof entry !== "object") {
return false;
const parsed = JSON.parse(stdout);
if (!Array.isArray(parsed)) {
return [] as NativeMacWindowSource[];
}

const candidate = entry as Partial<NativeMacWindowSource>;
return typeof candidate.id === "string" && typeof candidate.name === "string";
});
const entries = parsed.filter((entry: unknown): entry is NativeMacWindowSource => {
if (!entry || typeof entry !== "object") {
return false;
}

setCachedNativeMacWindowSources(entries);
setCachedNativeMacWindowSourcesAtMs(now);
return entries;
} catch {
return cachedNativeMacWindowSources ?? ([] as NativeMacWindowSource[]);
}
const candidate = entry as Partial<NativeMacWindowSource>;
return typeof candidate.id === "string" && typeof candidate.name === "string";
});

setCachedNativeMacWindowSources(entries);
setCachedNativeMacWindowSourcesAtMs(now);
return entries;
} catch {
return cachedNativeMacWindowSources ?? ([] as NativeMacWindowSource[]);
}
});
}

export function getWindowBoundsFromNativeSource(
Expand Down Expand Up @@ -172,22 +177,24 @@ export function stopWindowBoundsCapture() {
}

async function refreshSelectedWindowBounds() {
if (!selectedSource?.id?.startsWith("window:")) {
setSelectedWindowBounds(null);
return;
}
await runWindowBoundsRefresh(async () => {
if (!selectedSource?.id?.startsWith("window:")) {
setSelectedWindowBounds(null);
return;
}

let bounds: WindowBounds | null = null;
let bounds: WindowBounds | null = null;

if (process.platform === "darwin") {
bounds = await resolveMacWindowBounds(selectedSource);
} else if (process.platform === "win32") {
bounds = await resolveWindowsWindowBounds(selectedSource);
} else if (process.platform === "linux") {
bounds = await resolveLinuxWindowBounds(selectedSource);
}
if (process.platform === "darwin") {
bounds = await resolveMacWindowBounds(selectedSource);
} else if (process.platform === "win32") {
bounds = await resolveWindowsWindowBounds(selectedSource);
} else if (process.platform === "linux") {
bounds = await resolveLinuxWindowBounds(selectedSource);
}

setSelectedWindowBounds(bounds);
setSelectedWindowBounds(bounds);
});
}

export function startWindowBoundsCapture() {
Expand Down
48 changes: 48 additions & 0 deletions electron/ipc/cursor/concurrency.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, expect, it, vi } from "vitest";
import { createNonOverlappingRunner, createSingleFlight } from "./concurrency";

function deferred<T>() {
let resolve!: (value: T) => void;
const promise = new Promise<T>((settle) => {
resolve = settle;
});
return { promise, resolve };
}

describe("cursor task concurrency", () => {
it("shares one operation until it settles, then starts another", async () => {
const run = createSingleFlight<string>();
const first = deferred<string>();
const operation = vi
.fn()
.mockReturnValueOnce(first.promise)
.mockResolvedValueOnce("second");

const firstResult = run(operation);
const sharedResult = run(operation);
expect(operation).toHaveBeenCalledTimes(1);

first.resolve("first");
await expect(Promise.all([firstResult, sharedResult])).resolves.toEqual(["first", "first"]);
await expect(run(operation)).resolves.toBe("second");
expect(operation).toHaveBeenCalledTimes(2);
});

it("skips overlap and runs again after settlement", async () => {
const run = createNonOverlappingRunner();
const first = deferred<void>();
const operation = vi
.fn()
.mockReturnValueOnce(first.promise)
.mockResolvedValueOnce(undefined);

const firstResult = run(operation);
await expect(run(operation)).resolves.toBe(false);
expect(operation).toHaveBeenCalledTimes(1);

first.resolve();
await expect(firstResult).resolves.toBe(true);
await expect(run(operation)).resolves.toBe(true);
expect(operation).toHaveBeenCalledTimes(2);
});
});
38 changes: 38 additions & 0 deletions electron/ipc/cursor/concurrency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/** Share one asynchronous operation with every caller until it settles. */
export function createSingleFlight<T>() {
let inFlight: Promise<T> | null = null;

return (operation: () => Promise<T>) => {
if (inFlight) {
return inFlight;
}

inFlight = (async () => {
try {
return await operation();
} finally {
inFlight = null;
}
})();
return inFlight;
};
}

/** Skip overlapping asynchronous operations and allow another after settlement. */
export function createNonOverlappingRunner() {
let running = false;

return async (operation: () => Promise<void>) => {
if (running) {
return false;
}

running = true;
try {
await operation();
return true;
} finally {
running = false;
}
};
}