fix(overlay): stop Hyprland/Sway tracker polls from blocking the main thread (#289)

This commit is contained in:
sudacode committed 2026-10-11 01:02:02 -07:00
1 parent 9c351a8603
commit c18644392d
6 files changed
+378 -94

No files matched your search

+109 -11
View File
@@ -179,21 +179,119 @@ test('resolveHyprlandWindowGeometry uses monitor bounds for client-requested ful
});
});
test('HyprlandWindowTracker re-emits focus callback on active window events for z-order refresh', () => {
type PendingCommand = {
command: string;
args: string[];
resolve: (output: string) => void;
};
// Runner whose commands stay pending until the test resolves them.
function makeDeferredRunner() {
const pending: PendingCommand[] = [];
const runCommand = (command: string, args: string[]) =>
new Promise<string>((resolve) => {
pending.push({ command, args, resolve });
});
return { pending, runCommand };
}
function flush(): Promise<void> {
return new Promise((resolve) => setImmediate(resolve));
}
test('HyprlandWindowTracker re-emits focus callback on active window events for z-order refresh', async () => {
const calls: string[] = [];
const tracker = new HyprlandWindowTracker();
const privateTracker = tracker as unknown as {
handleSocketEvent: (event: string) => void;
pollGeometry: () => void;
};
privateTracker.pollGeometry = () => {
calls.push('poll');
};
const tracker = new HyprlandWindowTracker(undefined, async (command, args) => {
calls.push(`${command} ${args.join(' ')}`);
return '[]';
});
tracker.onWindowFocusChange = (focused) => {
calls.push(`focus:${focused}`);
};
privateTracker.handleSocketEvent('activewindowv2>>0xmpv');
(tracker as unknown as { handleSocketEvent: (event: string) => void }).handleSocketEvent(
'activewindowv2>>0xmpv',
);
await flush();
assert.deepEqual(calls, ['poll', 'focus:false']);
assert.deepEqual(calls, ['hyprctl -j clients', 'focus:false']);
});
test('HyprlandWindowTracker coalesces polls requested while hyprctl is still running', async () => {
const { pending, runCommand } = makeDeferredRunner();
const tracker = new HyprlandWindowTracker(undefined, runCommand);
const polls = [tracker.refreshNow(), tracker.refreshNow(), tracker.refreshNow()];
await flush();
assert.equal(pending.length, 1);
pending[0]!.resolve(JSON.stringify([makeClient({ at: [10, 20] })]));
await flush();
assert.equal(pending.length, 2);
pending[1]!.resolve(JSON.stringify([makeClient({ at: [30, 40] })]));
await Promise.all(polls);
assert.equal(pending.length, 2);
assert.deepEqual(tracker.getGeometry(), { x: 30, y: 40, width: 1280, height: 720 });
});
test('HyprlandWindowTracker ignores a poll that resolves after stop', async () => {
const { pending, runCommand } = makeDeferredRunner();
const tracker = new HyprlandWindowTracker(undefined, runCommand);
let found = 0;
tracker.onWindowFound = () => {
found += 1;
};
const poll = tracker.refreshNow();
tracker.stop();
await flush();
pending[0]!.resolve(JSON.stringify([makeClient()]));
await poll;
assert.equal(found, 0);
assert.equal(tracker.getGeometry(), null);
});
test('HyprlandWindowTracker reads each mpv command line once and re-reads after the pid leaves', async () => {
let clients = [makeClient({ pid: 10 })];
const psPids: string[] = [];
const tracker = new HyprlandWindowTracker('/tmp/subminer.sock', async (command, args) => {
if (command === 'hyprctl') {
return JSON.stringify(clients);
}
psPids.push(args[1]!);
return 'mpv --input-ipc-server=/tmp/subminer.sock video.mkv\n';
});
await tracker.refreshNow();
await tracker.refreshNow();
assert.equal(tracker.isTracking(), true);
assert.deepEqual(psPids, ['10']);
clients = [];
await tracker.refreshNow();
clients = [makeClient({ pid: 10 })];
await tracker.refreshNow();
assert.deepEqual(psPids, ['10', '10']);
});
test('HyprlandWindowTracker still tracks mpv when ps fails for another pid, and retries that pid', async () => {
const psPids: string[] = [];
const tracker = new HyprlandWindowTracker('/tmp/subminer.sock', async (command, args) => {
if (command === 'hyprctl') {
return JSON.stringify([makeClient({ pid: 10 }), makeClient({ pid: 20 })]);
}
psPids.push(args[1]!);
if (args[1] === '20') {
throw new Error('ps exited 1');
}
return 'mpv --input-ipc-server=/tmp/subminer.sock video.mkv\n';
});
await tracker.refreshNow();
assert.equal(tracker.isTracking(), true);
await tracker.refreshNow();
assert.deepEqual(psPids.sort(), ['10', '20', '20']);
});