mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-10 16:19:24 -07:00
- switch Windows overlay tracking to native win32 polling with native owner and z-order helpers - keep the visible overlay and stats overlay aligned across focus handoff, transient tracker misses, and minimize/restore cycles - start the visible overlay click-through and hide the initial opaque startup frame until the tracked transparent state settles - add a backlog task for the inconsistent mpv y-t overlay toggle after menu toggles
247 lines
8.0 KiB
TypeScript
247 lines
8.0 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { WindowsWindowTracker } from './windows-tracker';
|
|
import type { MpvPollResult } from './win32';
|
|
|
|
function mpvVisible(
|
|
overrides: Partial<MpvPollResult & { x?: number; y?: number; width?: number; height?: number; focused?: boolean }> = {},
|
|
): MpvPollResult {
|
|
return {
|
|
matches: [
|
|
{
|
|
hwnd: 12345,
|
|
bounds: {
|
|
x: overrides.x ?? 0,
|
|
y: overrides.y ?? 0,
|
|
width: overrides.width ?? 1280,
|
|
height: overrides.height ?? 720,
|
|
},
|
|
area: (overrides.width ?? 1280) * (overrides.height ?? 720),
|
|
isForeground: overrides.focused ?? true,
|
|
},
|
|
],
|
|
focusState: overrides.focused ?? true,
|
|
windowState: 'visible',
|
|
};
|
|
}
|
|
|
|
const mpvNotFound: MpvPollResult = {
|
|
matches: [],
|
|
focusState: false,
|
|
windowState: 'not-found',
|
|
};
|
|
|
|
const mpvMinimized: MpvPollResult = {
|
|
matches: [],
|
|
focusState: false,
|
|
windowState: 'minimized',
|
|
};
|
|
|
|
test('WindowsWindowTracker skips overlapping polls while poll is in flight', () => {
|
|
let pollCalls = 0;
|
|
const tracker = new WindowsWindowTracker(undefined, {
|
|
pollMpvWindows: () => {
|
|
pollCalls += 1;
|
|
return mpvVisible();
|
|
},
|
|
});
|
|
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(pollCalls, 2);
|
|
});
|
|
|
|
test('WindowsWindowTracker updates geometry from poll output', () => {
|
|
const tracker = new WindowsWindowTracker(undefined, {
|
|
pollMpvWindows: () => mpvVisible({ x: 10, y: 20, width: 1280, height: 720 }),
|
|
});
|
|
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
|
|
assert.deepEqual(tracker.getGeometry(), {
|
|
x: 10,
|
|
y: 20,
|
|
width: 1280,
|
|
height: 720,
|
|
});
|
|
assert.equal(tracker.isTargetWindowFocused(), true);
|
|
});
|
|
|
|
test('WindowsWindowTracker clears geometry for poll misses', () => {
|
|
const tracker = new WindowsWindowTracker(undefined, {
|
|
pollMpvWindows: () => mpvNotFound,
|
|
trackingLossGraceMs: 0,
|
|
});
|
|
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.getGeometry(), null);
|
|
assert.equal(tracker.isTargetWindowFocused(), false);
|
|
});
|
|
|
|
test('WindowsWindowTracker keeps the last geometry through a single poll miss', () => {
|
|
let callIndex = 0;
|
|
const outputs = [
|
|
mpvVisible({ x: 10, y: 20, width: 1280, height: 720 }),
|
|
mpvNotFound,
|
|
mpvVisible({ x: 10, y: 20, width: 1280, height: 720 }),
|
|
];
|
|
|
|
const tracker = new WindowsWindowTracker(undefined, {
|
|
pollMpvWindows: () => outputs[callIndex++] ?? outputs.at(-1)!,
|
|
trackingLossGraceMs: 0,
|
|
});
|
|
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.deepEqual(tracker.getGeometry(), { x: 10, y: 20, width: 1280, height: 720 });
|
|
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.deepEqual(tracker.getGeometry(), { x: 10, y: 20, width: 1280, height: 720 });
|
|
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.deepEqual(tracker.getGeometry(), { x: 10, y: 20, width: 1280, height: 720 });
|
|
});
|
|
|
|
test('WindowsWindowTracker drops tracking after grace window expires', () => {
|
|
let callIndex = 0;
|
|
let now = 1_000;
|
|
const outputs = [
|
|
mpvVisible({ x: 10, y: 20, width: 1280, height: 720 }),
|
|
mpvNotFound,
|
|
mpvNotFound,
|
|
mpvNotFound,
|
|
mpvNotFound,
|
|
];
|
|
|
|
const tracker = new WindowsWindowTracker(undefined, {
|
|
pollMpvWindows: () => outputs[callIndex++] ?? outputs.at(-1)!,
|
|
now: () => now,
|
|
trackingLossGraceMs: 500,
|
|
});
|
|
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
|
|
now += 250;
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
|
|
now += 250;
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
|
|
now += 250;
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
|
|
now += 250;
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), false);
|
|
assert.equal(tracker.getGeometry(), null);
|
|
});
|
|
|
|
test('WindowsWindowTracker keeps tracking through repeated poll misses inside grace window', () => {
|
|
let callIndex = 0;
|
|
let now = 1_000;
|
|
const outputs = [
|
|
mpvVisible({ x: 10, y: 20, width: 1280, height: 720 }),
|
|
mpvNotFound,
|
|
mpvNotFound,
|
|
mpvNotFound,
|
|
mpvVisible({ x: 10, y: 20, width: 1280, height: 720 }),
|
|
];
|
|
|
|
const tracker = new WindowsWindowTracker(undefined, {
|
|
pollMpvWindows: () => outputs[callIndex++] ?? outputs.at(-1)!,
|
|
now: () => now,
|
|
trackingLossGraceMs: 1_500,
|
|
});
|
|
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
|
|
now += 250;
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
|
|
now += 250;
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
|
|
now += 250;
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
assert.deepEqual(tracker.getGeometry(), { x: 10, y: 20, width: 1280, height: 720 });
|
|
|
|
now += 250;
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
assert.deepEqual(tracker.getGeometry(), { x: 10, y: 20, width: 1280, height: 720 });
|
|
});
|
|
|
|
test('WindowsWindowTracker keeps tracking through a transient minimized report inside minimized grace window', () => {
|
|
let callIndex = 0;
|
|
let now = 1_000;
|
|
const outputs: MpvPollResult[] = [
|
|
mpvVisible({ x: 10, y: 20, width: 1280, height: 720 }),
|
|
mpvMinimized,
|
|
mpvVisible({ x: 10, y: 20, width: 1280, height: 720 }),
|
|
];
|
|
|
|
const tracker = new WindowsWindowTracker(undefined, {
|
|
pollMpvWindows: () => outputs[callIndex++] ?? outputs.at(-1)!,
|
|
now: () => now,
|
|
minimizedTrackingLossGraceMs: 200,
|
|
});
|
|
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
|
|
now += 100;
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
assert.deepEqual(tracker.getGeometry(), { x: 10, y: 20, width: 1280, height: 720 });
|
|
|
|
now += 100;
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
assert.deepEqual(tracker.getGeometry(), { x: 10, y: 20, width: 1280, height: 720 });
|
|
});
|
|
|
|
test('WindowsWindowTracker keeps tracking through repeated transient minimized reports inside minimized grace window', () => {
|
|
let callIndex = 0;
|
|
let now = 1_000;
|
|
const outputs: MpvPollResult[] = [
|
|
mpvVisible({ x: 10, y: 20, width: 1280, height: 720 }),
|
|
mpvMinimized,
|
|
mpvMinimized,
|
|
mpvVisible({ x: 10, y: 20, width: 1280, height: 720 }),
|
|
];
|
|
|
|
const tracker = new WindowsWindowTracker(undefined, {
|
|
pollMpvWindows: () => outputs[callIndex++] ?? outputs.at(-1)!,
|
|
now: () => now,
|
|
minimizedTrackingLossGraceMs: 500,
|
|
});
|
|
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
|
|
now += 250;
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
assert.equal(tracker.isTargetWindowMinimized(), true);
|
|
assert.deepEqual(tracker.getGeometry(), { x: 10, y: 20, width: 1280, height: 720 });
|
|
|
|
now += 250;
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
assert.equal(tracker.isTargetWindowMinimized(), true);
|
|
assert.deepEqual(tracker.getGeometry(), { x: 10, y: 20, width: 1280, height: 720 });
|
|
|
|
now += 250;
|
|
(tracker as unknown as { pollGeometry: () => void }).pollGeometry();
|
|
assert.equal(tracker.isTracking(), true);
|
|
assert.equal(tracker.isTargetWindowMinimized(), false);
|
|
assert.deepEqual(tracker.getGeometry(), { x: 10, y: 20, width: 1280, height: 720 });
|
|
});
|