// Window layout, folded in from focus_dosbox.ps1: // 1. the RENDERER (GL bridge) -> always-on-top, so the out-the-window view is // never covered by a head window. // 2. DOSBox-X -> parked at (x,y) with FOREGROUND focus, so its emulation // thread keeps foreground priority -- else the RIO ACK deadline is missed // and the board storms (the RIO focus-sensitivity issue). // A topmost renderer stays visually above the focused-but-non-topmost DOSBox, // yet DOSBox stays the active window. using System; using System.Runtime.InteropServices; using System.Text; namespace VwePod { internal static class Focus { public static string Apply(int x, int y) { var s = new StringBuilder(); // 1) renderer -> topmost IntPtr r = FindRenderer(); if (r != IntPtr.Zero) { SetWindowPos(r, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); s.Append("renderer topmost; "); } else s.Append("renderer NOT found; "); // 2) DOSBox -> parked + foreground IntPtr d = FindWindow("DOSBox-X"); if (d == IntPtr.Zero) return s.Append("DOSBox NOT found").ToString(); SetWindowPos(d, IntPtr.Zero, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); ShowWindow(d, SW_RESTORE); uint fgThread = GetWindowThreadProcessId(GetForegroundWindow(), out _); uint me = GetCurrentThreadId(); AttachThreadInput(me, fgThread, true); BringWindowToTop(d); SetForegroundWindow(d); AttachThreadInput(me, fgThread, false); s.Append("DOSBox at ").Append(x).Append(',').Append(y) .Append(" focused=").Append(GetForegroundWindow() == d); return s.ToString(); } private static IntPtr FindRenderer() { IntPtr h = FindWindow("dpl3-revive renderer"); return h != IntPtr.Zero ? h : FindWindow("VelociRender"); } private static IntPtr FindWindow(string needle) { IntPtr found = IntPtr.Zero; EnumWindows((h, l) => { if (!IsWindowVisible(h)) return true; var b = new byte[512]; int n = GetWindowTextA(h, b, b.Length); if (n > 0 && Encoding.ASCII.GetString(b, 0, n).Contains(needle)) { found = h; return false; } return true; }, IntPtr.Zero); return found; } // ---- native ---- private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); private const uint SWP_NOSIZE = 0x0001, SWP_NOMOVE = 0x0002, SWP_NOZORDER = 0x0004; private const int SW_RESTORE = 9; private delegate bool EnumProc(IntPtr h, IntPtr l); [DllImport("user32.dll")] private static extern bool EnumWindows(EnumProc cb, IntPtr l); [DllImport("user32.dll")] private static extern int GetWindowTextA(IntPtr h, byte[] b, int max); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr h); [DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr h, IntPtr after, int x, int y, int w, int hh, uint flags); [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr h); [DllImport("user32.dll")] private static extern bool BringWindowToTop(IntPtr h); [DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr h, int cmd); [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid); [DllImport("user32.dll")] private static extern bool AttachThreadInput(uint a, uint b, bool attach); [DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId(); } }