mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-10-21 05:01:26 -07:00
fix(overlay): stop Hyprland/Sway tracker polls from blocking the main thread (#289)
This commit is contained in:
6 files changed
+378
-94
No files matched your search
@@ -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']);
|
||||
});
|
||||
Reference in new issue
Block a user