Files
BSO-Mod/BorderlessWindow.cs
MrMahoandClaude Opus 5.5 35f2d1330d Erste Version
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 17:26:23 +01:00

127 lines
6.2 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Xml.Linq;
namespace BSO
{
/// <summary>
/// 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).
/// </summary>
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();
/// <summary>Einmal pro Bild im Hauptthread (dem Thread des Fensters).</summary>
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<MonitorInfo>() };
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;
/// <summary>Fullscreen-Einstellung des Spiels (%LOCALAPPDATA%\European Bus Simulator 2012\Settings.xml).</summary>
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;
}
}
}
}