// # DosCommandInterface // Is abstraction that allows you to control runned instance of js-dos import { DosModule } from "./js-dos-module"; export interface DosKeyEventConsumer { onPress(keyCode: number): void; onRelease(keyCode: number): void; } export class DosCommandInterface { public dos: DosModule; private em: any; // typeof Module; private api: LowLevelApi; private onready: (ci: DosCommandInterface) => void; private shellInputQueue: string[] = []; private shellInputClients: Array<(value?: unknown) => void> = []; private onstdout?: (data: string) => void = undefined; private keyEventConsumer: DosKeyEventConsumer = { onPress: (keyCode) => this.simulateKeyEvent(keyCode, true), onRelease: (keyCode) => this.simulateKeyEvent(keyCode, false), }; private fullscreenInitialCssStyle?: string; constructor(dos: DosModule, onready: (ci: DosCommandInterface) => void) { this.dos = dos; this.em = (dos as any); this.api = (dos as any); this.api.ping = (msg: string, ...args: any[]) => { this.onping(msg, args); }; this.onready = onready; } // * `width()` - return dosbox window width in pixels public width() { return this.dos.canvas.width; } // * `height()` - return dosbox window height in pixels public height() { return this.dos.canvas.height; } // * `fullscreen()` - enters fullscreen mode // This function can be called anywhere, but for web security reasons its associated request can only be raised // inside the event handler for a user-generated event (for example a key, mouse or touch press/release). public fullscreen() { const requestFn = (element: any) => { if (element.requestFullscreen) { element.requestFullscreen(); } else if (element.webkitRequestFullscreen) { element.webkitRequestFullscreen(); } else if (element.mozRequestFullScreen) { element.mozRequestFullScreen(); } else if (element.msRequestFullscreen) { element.msRequestFullscreen(); } else if (element.webkitEnterFullscreen) { element.webkitEnterFullscreen(); } else { this.fullscreenInitialCssStyle = (element as HTMLElement).style.cssText; (element as HTMLElement).style.cssText = ` position: fixed; left: 0; top: 0; bottom: 0; right: 0; background: black; z-index: 999; `; } }; const parent = this.getParentDiv(); if (parent !== null && parent.className === "dosbox-container") { requestFn(parent); } else { requestFn(this.dos.canvas); } } // * `exitFullscreen()` allows you to leave fullscreen entered with `fullscreen()` call public exitFullscreen() { const requestFn = (element: any) => { if (this.fullscreenInitialCssStyle !== undefined) { (element as HTMLElement).style.cssText = this.fullscreenInitialCssStyle; delete this.fullscreenInitialCssStyle; } else if (document.exitFullscreen) { document.exitFullscreen(); } else if ((document as any).webkitExitFullscreen) { (document as any).webkitExitFullscreen(); } else if ((document as any).webkitExitFullscreen) { (document as any).mozCancelFullScreen(); } else if ((document as any).msExitFullscreen) { (document as any).msExitFullscreen(); } }; const parent = this.getParentDiv(); if (parent !== null && parent.className === "dosbox-container") { requestFn(parent); } else { requestFn(this.dos.canvas); } } // * `listenStdout()` - redirect everything that printed by dosbox into // console to passed function public listenStdout(onstdout: (data: string) => void) { this.onstdout = onstdout; } // * `shell([cmd1, cmd2, ...])` - executes passed commands // in dosbox shell if it's runned, returns Promise that // resolves when commands sequence is executed public shell(...cmd: string[]) { if (cmd.length === 0) { return; } return new Promise((resolve, reject) => { this.shellInputClients.push(resolve); for (const next of cmd) { this.shellInputQueue.push(next); } this.requestShellInput(); }); } // * `screenshot()` - get screnshot of canvas as ImageData public screenshot() { return new Promise((resolve) => { this.api.send("screenshot", "", (data) => { resolve(data); }); }); } // * `exit()` - immediately exit from runtime public exit() { try { this.dos.terminate(); this.api.send("exit"); } catch (e) { return 0; } this.dos.error("Runtime is still alive!"); return -1; } // * `simulateKeyEvent(keyCode, pressed)` - allows to simulate key press OR release on js-dos canvas public simulateKeyEvent(keyCode: number, pressed: boolean): void { const name = pressed ? "keydown" : "keyup"; const event = document.createEvent("KeyboardEvent") as any; const getter: any = { get() { return this.keyCodeVal; }, }; // Chromium Hack Object.defineProperty(event, "keyCode", getter); Object.defineProperty(event, "which", getter); Object.defineProperty(event, "charCode", getter); event.initKeyboardEvent // tslint:disable-next-line ? event.initKeyboardEvent(name, true, true, document.defaultView, false, false, false, false, keyCode, keyCode) : event.initKeyEvent(name, true, true, document.defaultView, false, false, false, false, keyCode, 0); event.keyCodeVal = keyCode; this.dos.canvas && this.dos.canvas.dispatchEvent(event); } // * `simulateKeyPress(keyCode)` - allows to simulate key press AND release on js-dos canvas public simulateKeyPress(keyCode: number): void { this.simulateKeyEvent(keyCode, true); setTimeout(() => this.simulateKeyEvent(keyCode, false), 100); } public getParentDiv(): HTMLDivElement | null { if (this.dos.canvas.parentElement instanceof HTMLDivElement) { return this.dos.canvas.parentElement; } return null; } public getKeyEventConsumer(): DosKeyEventConsumer { return this.keyEventConsumer; } private sendKeyPress(code: number) { this.api.send("sdl_key_event", code + ""); } private requestShellInput() { this.sendKeyPress(13); } private onping(msg: string, args: any[]) { switch (msg) { case "ready": this.onready(this); break; case "frame": this.onframe(); break; case "shell_input": if (this.shellInputQueue.length === 0) { return; } const buffer: number = args[0]; const maxLength: number = args[1]; const cmd = this.shellInputQueue.shift(); const cmdLength = this.em.lengthBytesUTF8(cmd) + 1; if (cmdLength > maxLength) { if (this.dos.onerror !== undefined) { this.dos.onerror("Can't execute cmd '" + cmd + "', because it's bigger then max cmd length " + maxLength); } return; } this.em.stringToUTF8(cmd, buffer, cmdLength); if (this.shellInputQueue.length === 0) { for (const resolve of this.shellInputClients) { resolve(); } this.shellInputClients = []; } else { this.requestShellInput(); } break; case "write_stdout": const data: string = args[0]; if (this.onstdout) { this.onstdout(data); } break; default: /* do nothing */ } } private onframe() { this.dos.tick(); } } interface LowLevelApi { send: (event: string, msg?: any, callback?: (msg: string) => void) => void; ping: (msg: string) => void; }