- streamer/windows: vash-broadcast.exe (setup/login/start/stop/status/doctor) with per-instance OBS, tosu and FFmpeg, a supervised worker, OCR room selection and OBS scene reconciliation; built and tested on Linux only - streamer/relay.ts shared with Vash TV; Cloudflare Access proxy for dev/staging - worker status validation and live score helpers for the site - STREAMING_HANDOFF.md and streamer/ALPHA_TEST.md record what is unverified Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
123 lines
3.2 KiB
TypeScript
123 lines
3.2 KiB
TypeScript
/** Test doubles for the desktop helper and tosu. */
|
|
import type { TosuFeed, TosuFrame } from '../tosu';
|
|
import type { CaptureOptions, Desktop, OcrLine, SessionInfo, WindowInfo } from './desktop';
|
|
|
|
export function windowInfo(overrides: Partial<WindowInfo> = {}): WindowInfo {
|
|
return {
|
|
hwnd: 100,
|
|
pid: 10,
|
|
title: 'osu!',
|
|
className: 'WindowsForms10.Window.8.app.0.1',
|
|
exe: 'C:\\vash\\osu\\osu!.exe',
|
|
visible: true,
|
|
minimized: false,
|
|
x: 0,
|
|
y: 0,
|
|
width: 1296,
|
|
height: 959,
|
|
clientX: 8,
|
|
clientY: 31,
|
|
clientWidth: 1280,
|
|
clientHeight: 920,
|
|
...overrides
|
|
};
|
|
}
|
|
|
|
export class FakeDesktop implements Desktop {
|
|
sessionInfo: SessionInfo = {
|
|
sessionId: 1,
|
|
consoleSessionId: 1,
|
|
inputDesktop: 'Default',
|
|
screenWidth: 1920,
|
|
screenHeight: 1080,
|
|
idleMs: 0
|
|
};
|
|
running: { pid: number; path: string }[] = [];
|
|
windowList: WindowInfo[] = [];
|
|
ocrLines: OcrLine[] = [];
|
|
calls: { cmd: string; args: unknown[] }[] = [];
|
|
focusResult = true;
|
|
|
|
private note(cmd: string, ...args: unknown[]) {
|
|
this.calls.push({ cmd, args });
|
|
}
|
|
called(cmd: string) {
|
|
return this.calls.filter((call) => call.cmd === cmd);
|
|
}
|
|
|
|
async session() {
|
|
return this.sessionInfo;
|
|
}
|
|
async windows(under: string) {
|
|
this.note('windows', under);
|
|
return this.windowList;
|
|
}
|
|
async window(hwnd: number) {
|
|
return this.windowList.find((window) => window.hwnd === hwnd) ?? null;
|
|
}
|
|
async processes(under: string) {
|
|
return this.running.filter((process) =>
|
|
process.path.toLowerCase().startsWith(under.toLowerCase())
|
|
);
|
|
}
|
|
async kill(pids: number[]) {
|
|
this.note('kill', pids);
|
|
this.running = this.running.filter((process) => !pids.includes(process.pid));
|
|
return pids.length;
|
|
}
|
|
async close(pid: number) {
|
|
this.note('close', pid);
|
|
this.running = this.running.filter((process) => process.pid !== pid);
|
|
this.windowList = this.windowList.filter((window) => window.pid !== pid);
|
|
return 1;
|
|
}
|
|
async focus(hwnd: number) {
|
|
this.note('focus', hwnd);
|
|
return this.focusResult;
|
|
}
|
|
async move(hwnd: number, x: number, y: number) {
|
|
this.note('move', hwnd, x, y);
|
|
return true;
|
|
}
|
|
async setTitle(hwnd: number, title: string) {
|
|
this.note('title', hwnd, title);
|
|
const window = this.windowList.find((each) => each.hwnd === hwnd);
|
|
if (window) window.title = title;
|
|
return true;
|
|
}
|
|
async capture(options: CaptureOptions) {
|
|
this.note('capture', options);
|
|
const scale = options.scale ?? 1;
|
|
return { width: Math.round(options.width * scale), height: Math.round(options.height * scale) };
|
|
}
|
|
async ocr(path: string) {
|
|
this.note('ocr', path);
|
|
return this.ocrLines;
|
|
}
|
|
async ocrReady() {
|
|
return { language: 'en-US', maxDimension: 2600 };
|
|
}
|
|
async click(x: number, y: number) {
|
|
this.note('click', x, y);
|
|
return true;
|
|
}
|
|
async type(text: string) {
|
|
return text.length;
|
|
}
|
|
async key() {}
|
|
async stayAwake() {
|
|
return true;
|
|
}
|
|
stop() {}
|
|
}
|
|
|
|
/** Stands in for TosuFeed: whatever frame the test sets is the fresh one. */
|
|
export function fakeTosu(frame: TosuFrame | null = null) {
|
|
const feed = { frame, fresh: () => feed.frame };
|
|
return feed as unknown as TosuFeed & { frame: TosuFrame | null };
|
|
}
|
|
|
|
export function tourneyFrame(left: string, right: string): TosuFrame {
|
|
return { ipcState: 1, beatmapId: null, progress: null, teams: { left, right }, clients: [] };
|
|
}
|