import koffi from 'koffi'; import { logger } from '../util/logger.js'; // Win32 types const HWND = 'int'; const BOOL = 'bool'; const RECT = koffi.struct('RECT', { left: 'long', top: 'long', right: 'long', bottom: 'long', }); // Load user32.dll const user32 = koffi.load('user32.dll'); const FindWindowW = user32.func('FindWindowW', HWND, ['str16', 'str16']); const SetForegroundWindow = user32.func('SetForegroundWindow', BOOL, [HWND]); const ShowWindow = user32.func('ShowWindow', BOOL, [HWND, 'int']); const BringWindowToTop = user32.func('BringWindowToTop', BOOL, [HWND]); const GetForegroundWindow = user32.func('GetForegroundWindow', HWND, []); const GetWindowRect = user32.func('GetWindowRect', BOOL, [HWND, koffi.out(koffi.pointer(RECT))]); const IsWindow = user32.func('IsWindow', BOOL, [HWND]); const keybd_event = user32.func('keybd_event', 'void', ['uint8', 'uint8', 'uint32', 'uint']); const MapVirtualKeyW = user32.func('MapVirtualKeyW', 'uint32', ['uint32', 'uint32']); // Constants const SW_RESTORE = 9; const VK_MENU = 0x12; // Alt key const KEYEVENTF_KEYUP = 0x0002; export class WindowManager { private hwnd: number = 0; constructor(private windowTitle: string) {} findWindow(): number { this.hwnd = FindWindowW(null as unknown as string, this.windowTitle); if (this.hwnd === 0) { logger.warn({ title: this.windowTitle }, 'Window not found'); } else { logger.info({ title: this.windowTitle, hwnd: this.hwnd }, 'Window found'); } return this.hwnd; } focusWindow(): boolean { if (!this.hwnd || !IsWindow(this.hwnd)) { this.findWindow(); } if (!this.hwnd) return false; // Restore if minimized ShowWindow(this.hwnd, SW_RESTORE); // Alt-key trick to bypass SetForegroundWindow restriction const altScan = MapVirtualKeyW(VK_MENU, 0); keybd_event(VK_MENU, altScan, 0, 0); keybd_event(VK_MENU, altScan, KEYEVENTF_KEYUP, 0); BringWindowToTop(this.hwnd); const result = SetForegroundWindow(this.hwnd); if (!result) { logger.warn('SetForegroundWindow failed'); } return result; } getWindowRect(): { left: number; top: number; right: number; bottom: number } | null { if (!this.hwnd || !IsWindow(this.hwnd)) { this.findWindow(); } if (!this.hwnd) return null; const rect = { left: 0, top: 0, right: 0, bottom: 0 }; const success = GetWindowRect(this.hwnd, rect); if (!success) return null; return rect; } isGameFocused(): boolean { const fg = GetForegroundWindow(); return fg === this.hwnd && this.hwnd !== 0; } getHwnd(): number { return this.hwnd; } }