using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Xml.Linq; namespace BSO { /// /// Vollbild im Fenster: macht das Spielfenster randlos und so gross wie den Bildschirm. Das exklusive DirectX-9-Vollbild /// des Spiels laeuft unter neuen Windows-Versionen schlecht (Alt-Tab, Abstuerze) - so geht Alt-Tab sofort. Nur wenn /// das Spiel im Fenstermodus laeuft (Settings.xml: Fullscreen="FALSE"). F11 schaltet zwischen Vollbild und Fenster. /// Am schaerfsten ist es, wenn im Spiel die Aufloesung des Bildschirms eingestellt ist (das Bild wird sonst skaliert). /// public static class BorderlessWindow { [StructLayout(LayoutKind.Sequential)] struct Rect { public int Left, Top, Right, Bottom; } [StructLayout(LayoutKind.Sequential)] struct MonitorInfo { public int Size; public Rect Monitor, Work; public uint Flags; } [DllImport("user32.dll")] static extern nint GetWindowLongPtrW(nint window, int index); [DllImport("user32.dll")] static extern nint SetWindowLongPtrW(nint window, int index, nint value); [DllImport("user32.dll")] static extern bool GetWindowRect(nint window, out Rect rect); [DllImport("user32.dll")] static extern bool GetClientRect(nint window, out Rect rect); [DllImport("user32.dll")] static extern bool SetWindowPos(nint window, nint after, int x, int y, int w, int h, uint flags); [DllImport("user32.dll")] static extern nint MonitorFromWindow(nint window, uint flags); [DllImport("user32.dll")] static extern bool GetMonitorInfoW(nint monitor, ref MonitorInfo info); const int GwlStyle = -16, GwlExStyle = -20; const long Frame = 0x00C00000 | 0x00040000 | 0x00020000 | 0x00010000; // Titelleiste, Rahmen, Min/Max const long ExFrame = 0x1 | 0x100 | 0x200 | 0x20000; // Rahmen-Kanten const uint MonitorNearest = 2; const uint SwpFrameChanged = 0x20, SwpNoZOrder = 0x4, SwpNoOwnerZOrder = 0x200, SwpShowWindow = 0x40; static bool checkedSettings, allowed, enabled = true, isBorderless; static nint window; static long savedStyle, savedExStyle; static Rect savedRect; static readonly Stopwatch checkClock = new(); /// Einmal pro Bild im Hauptthread (dem Thread des Fensters). public static void Update() { if (!checkedSettings) { checkedSettings = true; allowed = !GameFullscreen(); if (!allowed) Bootstrap.Log("Spiel laeuft im eigenen Vollbild - BSO-Vollbild bleibt aus"); } if (!allowed) return; if (Input.Pressed(Input.F11)) { enabled = !enabled; Bootstrap.Log(enabled ? "Vollbild an" : "Vollbild aus (Fenster)"); if (!enabled && isBorderless) Restore(); } // Jede Sekunde pruefen - das Spiel stellt sein Fenster z.B. nach Aenderungen in den Optionen neu ein if (checkClock.IsRunning && checkClock.ElapsedMilliseconds < 1000 && !Input.Pressed(Input.F11)) return; checkClock.Restart(); if (window == 0) window = Process.GetCurrentProcess().MainWindowHandle; if (window == 0 || !enabled) return; Rect monitor = MonitorRect(); GetWindowRect(window, out Rect now); bool hasFrame = (GetWindowLongPtrW(window, GwlStyle) & Frame) != 0; if (isBorderless && !hasFrame && Same(now, monitor)) return; MakeBorderless(monitor); } static void MakeBorderless(Rect monitor) { long style = GetWindowLongPtrW(window, GwlStyle), exStyle = GetWindowLongPtrW(window, GwlExStyle); if ((style & Frame) != 0) // nur das echte Fenster merken, nicht unser randloses { savedStyle = style; savedExStyle = exStyle; GetWindowRect(window, out savedRect); } SetWindowLongPtrW(window, GwlStyle, (nint)(style & ~Frame)); SetWindowLongPtrW(window, GwlExStyle, (nint)(exStyle & ~ExFrame)); SetWindowPos(window, 0, monitor.Left, monitor.Top, monitor.Right - monitor.Left, monitor.Bottom - monitor.Top, SwpFrameChanged | SwpNoZOrder | SwpNoOwnerZOrder | SwpShowWindow); isBorderless = true; GetClientRect(window, out Rect client); Bootstrap.Log($"Vollbild im Fenster: {client.Right}x{client.Bottom} (Bildschirm {monitor.Right - monitor.Left}x{monitor.Bottom - monitor.Top})"); } static void Restore() { isBorderless = false; if (window == 0 || savedStyle == 0) return; SetWindowLongPtrW(window, GwlStyle, (nint)savedStyle); SetWindowLongPtrW(window, GwlExStyle, (nint)savedExStyle); SetWindowPos(window, 0, savedRect.Left, savedRect.Top, savedRect.Right - savedRect.Left, savedRect.Bottom - savedRect.Top, SwpFrameChanged | SwpNoZOrder | SwpNoOwnerZOrder | SwpShowWindow); } static Rect MonitorRect() { var info = new MonitorInfo { Size = Marshal.SizeOf() }; GetMonitorInfoW(MonitorFromWindow(window, MonitorNearest), ref info); return info.Monitor; } static bool Same(Rect a, Rect b) => a.Left == b.Left && a.Top == b.Top && a.Right == b.Right && a.Bottom == b.Bottom; /// Fullscreen-Einstellung des Spiels (%LOCALAPPDATA%\European Bus Simulator 2012\Settings.xml). static bool GameFullscreen() { try { string file = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "European Bus Simulator 2012", "Settings.xml"); string value = (string)XDocument.Load(file).Root?.Element("Graphics")?.Attribute("Fullscreen"); return string.Equals(value, "TRUE", StringComparison.OrdinalIgnoreCase); } catch (Exception e) { Bootstrap.Log("Settings.xml nicht lesbar: " + e.Message); return false; } } } }