mirror of
https://github.com/AdvDebug/Brovan
synced 2026-08-09 11:57:17 +00:00
Handle quit and window teardown flow
Adds Win32k quit-message support via `NtUserPostQuitMessage` and queue handling so `WM_QUIT` is surfaced through message APIs and wait checks. Improves window teardown by posting `WM_DESTROY`/`WM_NCDESTROY`, retaining destroyed windows briefly for dispatch, and forgetting them after `WM_NCDESTROY`. Host close handling is unified with `HostEventQueue.RequestClose`/`Reset`, and process termination now hides the desktop window before stopping emulation. This also fixes a bug in Vulkan rendering where it assumed the window still existed and crashed with an access violation.
This commit is contained in:
@@ -369,7 +369,6 @@ namespace Brovan.Core.Emulation.OS.SharedHelpers
|
||||
private const uint WM_SIZE = 0x0005;
|
||||
private const uint WM_SETFOCUS = 0x0007;
|
||||
private const uint WM_KILLFOCUS = 0x0008;
|
||||
private const uint WM_CLOSE = 0x0010;
|
||||
private const uint WM_KEYDOWN = 0x0100;
|
||||
private const uint WM_KEYUP = 0x0101;
|
||||
private const uint WM_SYSKEYDOWN = 0x0104;
|
||||
@@ -537,57 +536,57 @@ namespace Brovan.Core.Emulation.OS.SharedHelpers
|
||||
{
|
||||
case X11.KeyPress:
|
||||
case X11.KeyRelease:
|
||||
{
|
||||
ref X11.XKeyEvent key = ref Unsafe.As<X11.XEvent, X11.XKeyEvent>(ref nativeEvent);
|
||||
_modifierState = key.State;
|
||||
{
|
||||
ref X11.XKeyEvent key = ref Unsafe.As<X11.XEvent, X11.XKeyEvent>(ref nativeEvent);
|
||||
_modifierState = key.State;
|
||||
|
||||
uint virtualKey = KeysymToVirtualKey(LookupKeysym((byte)key.Keycode));
|
||||
if (virtualKey == 0)
|
||||
uint virtualKey = KeysymToVirtualKey(LookupKeysym((byte)key.Keycode));
|
||||
if (virtualKey == 0)
|
||||
return;
|
||||
|
||||
bool down = nativeEvent.Type == X11.KeyPress;
|
||||
bool altHeld = (key.State & X11.Mod1Mask) != 0;
|
||||
bool system = altHeld || virtualKey == VK_MENU || virtualKey == VK_F10;
|
||||
|
||||
uint message = down
|
||||
? (system ? WM_SYSKEYDOWN : WM_KEYDOWN)
|
||||
: (system ? WM_SYSKEYUP : WM_KEYUP);
|
||||
|
||||
HostEventQueue.Enqueue(message, virtualKey, BuildKeyLParam(key.Keycode, virtualKey, down, altHeld));
|
||||
return;
|
||||
|
||||
bool down = nativeEvent.Type == X11.KeyPress;
|
||||
bool altHeld = (key.State & X11.Mod1Mask) != 0;
|
||||
bool system = altHeld || virtualKey == VK_MENU || virtualKey == VK_F10;
|
||||
|
||||
uint message = down
|
||||
? (system ? WM_SYSKEYDOWN : WM_KEYDOWN)
|
||||
: (system ? WM_SYSKEYUP : WM_KEYUP);
|
||||
|
||||
HostEventQueue.Enqueue(message, virtualKey, BuildKeyLParam(key.Keycode, virtualKey, down, altHeld));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
case X11.ButtonPress:
|
||||
case X11.ButtonRelease:
|
||||
{
|
||||
ref X11.XButtonEvent button = ref Unsafe.As<X11.XEvent, X11.XButtonEvent>(ref nativeEvent);
|
||||
_modifierState = button.State;
|
||||
TranslateButton(ref button, nativeEvent.Type == X11.ButtonPress);
|
||||
return;
|
||||
}
|
||||
{
|
||||
ref X11.XButtonEvent button = ref Unsafe.As<X11.XEvent, X11.XButtonEvent>(ref nativeEvent);
|
||||
_modifierState = button.State;
|
||||
TranslateButton(ref button, nativeEvent.Type == X11.ButtonPress);
|
||||
return;
|
||||
}
|
||||
|
||||
case X11.MotionNotify:
|
||||
{
|
||||
ref X11.XMotionEvent motion = ref Unsafe.As<X11.XEvent, X11.XMotionEvent>(ref nativeEvent);
|
||||
_modifierState = motion.State;
|
||||
HostEventQueue.Enqueue(WM_MOUSEMOVE, StateToMouseKeys(motion.State), MakeLParam(motion.X, motion.Y));
|
||||
return;
|
||||
}
|
||||
{
|
||||
ref X11.XMotionEvent motion = ref Unsafe.As<X11.XEvent, X11.XMotionEvent>(ref nativeEvent);
|
||||
_modifierState = motion.State;
|
||||
HostEventQueue.Enqueue(WM_MOUSEMOVE, StateToMouseKeys(motion.State), MakeLParam(motion.X, motion.Y));
|
||||
return;
|
||||
}
|
||||
|
||||
case X11.Expose:
|
||||
HostEventQueue.MarkRepaint();
|
||||
return;
|
||||
|
||||
case X11.ConfigureNotify:
|
||||
{
|
||||
ref X11.XConfigureEvent configure = ref Unsafe.As<X11.XEvent, X11.XConfigureEvent>(ref nativeEvent);
|
||||
if (TryGetWindow(configure.Window, out LinuxWindow? window))
|
||||
window?.OnConfigured(configure.Width, configure.Height);
|
||||
{
|
||||
ref X11.XConfigureEvent configure = ref Unsafe.As<X11.XEvent, X11.XConfigureEvent>(ref nativeEvent);
|
||||
if (TryGetWindow(configure.Window, out LinuxWindow? window))
|
||||
window?.OnConfigured(configure.Width, configure.Height);
|
||||
|
||||
HostEventQueue.Enqueue(WM_SIZE, 0, MakeLParam(configure.Width, configure.Height));
|
||||
HostEventQueue.MarkRepaint();
|
||||
return;
|
||||
}
|
||||
HostEventQueue.Enqueue(WM_SIZE, 0, MakeLParam(configure.Width, configure.Height));
|
||||
HostEventQueue.MarkRepaint();
|
||||
return;
|
||||
}
|
||||
|
||||
case X11.FocusIn:
|
||||
HostEventQueue.Enqueue(WM_SETFOCUS, 0, 0);
|
||||
@@ -598,18 +597,13 @@ namespace Brovan.Core.Emulation.OS.SharedHelpers
|
||||
return;
|
||||
|
||||
case X11.ClientMessage:
|
||||
{
|
||||
ref X11.XClientMessageEvent client = ref Unsafe.As<X11.XEvent, X11.XClientMessageEvent>(ref nativeEvent);
|
||||
if (client.MessageType == _wmProtocols && client.Data0 == _wmDeleteWindow)
|
||||
{
|
||||
HostEventQueue.Enqueue(WM_CLOSE, 0, 0);
|
||||
ref X11.XClientMessageEvent client = ref Unsafe.As<X11.XEvent, X11.XClientMessageEvent>(ref nativeEvent);
|
||||
if (client.MessageType == _wmProtocols && client.Data0 == _wmDeleteWindow)
|
||||
HostEventQueue.RequestClose();
|
||||
|
||||
if (TryGetWindow(client.Window, out LinuxWindow? window))
|
||||
window?.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1120,54 +1114,54 @@ namespace Brovan.Core.Emulation.OS.SharedHelpers
|
||||
break;
|
||||
|
||||
case GdiPrimitiveKind.Ellipse:
|
||||
{
|
||||
Normalize(primitive, out int x, out int y, out uint width, out uint height);
|
||||
if (primitive.HasBrush)
|
||||
{
|
||||
X11.XSetForeground(_xDisplay, gc, ResolvePixel(primitive.Brush.ColorRef));
|
||||
X11.XFillArc(_xDisplay, windowHandle, gc, x, y, width, height, 0, 360 * 64);
|
||||
}
|
||||
Normalize(primitive, out int x, out int y, out uint width, out uint height);
|
||||
if (primitive.HasBrush)
|
||||
{
|
||||
X11.XSetForeground(_xDisplay, gc, ResolvePixel(primitive.Brush.ColorRef));
|
||||
X11.XFillArc(_xDisplay, windowHandle, gc, x, y, width, height, 0, 360 * 64);
|
||||
}
|
||||
|
||||
if (primitive.HasPen)
|
||||
{
|
||||
X11.XSetForeground(_xDisplay, gc, ResolvePixel(primitive.Pen.ColorRef));
|
||||
X11.XDrawArc(_xDisplay, windowHandle, gc, x, y, width, height, 0, 360 * 64);
|
||||
}
|
||||
if (primitive.HasPen)
|
||||
{
|
||||
X11.XSetForeground(_xDisplay, gc, ResolvePixel(primitive.Pen.ColorRef));
|
||||
X11.XDrawArc(_xDisplay, windowHandle, gc, x, y, width, height, 0, 360 * 64);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case GdiPrimitiveKind.Polygon:
|
||||
{
|
||||
if (primitive.Points == null || primitive.Points.Length == 0)
|
||||
{
|
||||
if (primitive.Points == null || primitive.Points.Length == 0)
|
||||
break;
|
||||
|
||||
X11.XPoint[] points = ToXPoints(primitive.Points, close: true);
|
||||
if (primitive.HasBrush)
|
||||
{
|
||||
X11.XSetForeground(_xDisplay, gc, ResolvePixel(primitive.Brush.ColorRef));
|
||||
X11.XFillPolygon(_xDisplay, windowHandle, gc, points, primitive.Points.Length, X11.Complex, X11.CoordModeOrigin);
|
||||
}
|
||||
|
||||
if (primitive.HasPen)
|
||||
{
|
||||
X11.XSetForeground(_xDisplay, gc, ResolvePixel(primitive.Pen.ColorRef));
|
||||
X11.XDrawLines(_xDisplay, windowHandle, gc, points, points.Length, X11.CoordModeOrigin);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
X11.XPoint[] points = ToXPoints(primitive.Points, close: true);
|
||||
if (primitive.HasBrush)
|
||||
{
|
||||
X11.XSetForeground(_xDisplay, gc, ResolvePixel(primitive.Brush.ColorRef));
|
||||
X11.XFillPolygon(_xDisplay, windowHandle, gc, points, primitive.Points.Length, X11.Complex, X11.CoordModeOrigin);
|
||||
}
|
||||
|
||||
if (primitive.HasPen)
|
||||
{
|
||||
X11.XSetForeground(_xDisplay, gc, ResolvePixel(primitive.Pen.ColorRef));
|
||||
X11.XDrawLines(_xDisplay, windowHandle, gc, points, points.Length, X11.CoordModeOrigin);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case GdiPrimitiveKind.Polyline:
|
||||
{
|
||||
if (primitive.Points == null || primitive.Points.Length == 0)
|
||||
break;
|
||||
{
|
||||
if (primitive.Points == null || primitive.Points.Length == 0)
|
||||
break;
|
||||
|
||||
X11.XPoint[] points = ToXPoints(primitive.Points, close: false);
|
||||
X11.XSetForeground(_xDisplay, gc, ResolvePixel(primitive.Pen.ColorRef));
|
||||
X11.XDrawLines(_xDisplay, windowHandle, gc, points, points.Length, X11.CoordModeOrigin);
|
||||
break;
|
||||
}
|
||||
X11.XPoint[] points = ToXPoints(primitive.Points, close: false);
|
||||
X11.XSetForeground(_xDisplay, gc, ResolvePixel(primitive.Pen.ColorRef));
|
||||
X11.XDrawLines(_xDisplay, windowHandle, gc, points, points.Length, X11.CoordModeOrigin);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
X11.XFlush(_xDisplay);
|
||||
|
||||
@@ -8,9 +8,29 @@ namespace Brovan.Core.Emulation.OS.SharedHelpers
|
||||
{
|
||||
internal static class HostEventQueue
|
||||
{
|
||||
private const uint WM_CLOSE = 0x0010;
|
||||
|
||||
private static int _pendingRepaint;
|
||||
private static int _closeRequested;
|
||||
private static readonly ConcurrentQueue<(uint Message, ulong WParam, ulong LParam)> PendingInput = new();
|
||||
|
||||
public static void RequestClose()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _closeRequested, 1) != 0)
|
||||
Environment.Exit(0);
|
||||
|
||||
Enqueue(WM_CLOSE, 0, 0);
|
||||
}
|
||||
|
||||
public static void Reset()
|
||||
{
|
||||
Interlocked.Exchange(ref _closeRequested, 0);
|
||||
Interlocked.Exchange(ref _pendingRepaint, 0);
|
||||
while (PendingInput.TryDequeue(out _))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static void MarkRepaint()
|
||||
{
|
||||
Interlocked.Exchange(ref _pendingRepaint, 1);
|
||||
|
||||
@@ -743,8 +743,7 @@ namespace Brovan.Core.Emulation.OS.SharedHelpers
|
||||
{
|
||||
if (msg == WM_CLOSE)
|
||||
{
|
||||
HostEventQueue.Enqueue(msg, 0UL, 0UL);
|
||||
Close();
|
||||
HostEventQueue.RequestClose();
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace Brovan.Core.Emulation.OS.Windows
|
||||
ProcessThreads.ExitCode = (int)ExitCode;
|
||||
Instance.WinHelper.ClearTerminationState(ProcessThreads);
|
||||
}
|
||||
Instance.WinHelper.HideDesktopWindow();
|
||||
Instance.StopEmulation();
|
||||
return NTSTATUS.STATUS_SUCCESS;
|
||||
}
|
||||
@@ -63,6 +64,7 @@ namespace Brovan.Core.Emulation.OS.Windows
|
||||
ProcessThreads.ExitCode = (int)ExitCode;
|
||||
Instance.WinHelper.ClearTerminationState(ProcessThreads);
|
||||
}
|
||||
Instance.WinHelper.HideDesktopWindow();
|
||||
Instance.StopEmulation();
|
||||
return NTSTATUS.STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,18 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k
|
||||
WinWindow Window = Message.Hwnd == 0 ? null : Instance.WinHelper.GetWindow(Message.Hwnd);
|
||||
if (Message.Hwnd != 0 && Window == null)
|
||||
{
|
||||
Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE);
|
||||
Instance.SetRawSyscallReturn(0);
|
||||
return NTSTATUS.STATUS_SUCCESS;
|
||||
bool Teardown = Message.Message == Win32kHelper.WM_DESTROY || Message.Message == Win32kHelper.WM_NCDESTROY;
|
||||
Window = Teardown ? Instance.WinHelper.GetDestroyedWindow(Message.Hwnd) : null;
|
||||
|
||||
if (Window == null)
|
||||
{
|
||||
Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE);
|
||||
Instance.SetRawSyscallReturn(0);
|
||||
return NTSTATUS.STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
if (Message.Message == Win32kHelper.WM_NCDESTROY)
|
||||
Instance.WinHelper.ForgetDestroyedWindow(Message.Hwnd);
|
||||
}
|
||||
|
||||
if (Window == null || Window.WndProc == 0)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using static Brovan.Core.Helpers.BinaryHelpers;
|
||||
|
||||
namespace Brovan.Core.Emulation.OS.Windows.Win32k
|
||||
{
|
||||
internal class NtUserPostQuitMessage : IWinSyscall
|
||||
{
|
||||
public NTSTATUS Handle(BinaryEmulator Instance)
|
||||
{
|
||||
|
||||
ulong ExitCode = Instance.WinHelper.GetArg(0);
|
||||
|
||||
Win32kHelper.PostQuitMessage(Instance, ExitCode);
|
||||
Instance.SetLastWinError(0);
|
||||
Instance.SetBooleanSyscallReturn(true);
|
||||
return NTSTATUS.STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k
|
||||
internal const uint WM_GETTEXT = 0x000D;
|
||||
internal const uint WM_GETTEXTLENGTH = 0x000E;
|
||||
internal const uint WM_NCHITTEST = 0x0084;
|
||||
internal const uint WM_NCDESTROY = 0x0082;
|
||||
internal const uint WM_PAINT = 0x000F;
|
||||
internal const uint WM_SETTEXT = 0x000C;
|
||||
internal const uint WM_KEYDOWN = 0x0100;
|
||||
@@ -102,6 +103,8 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k
|
||||
public readonly Dictionary<ulong, Win32kPenBrush> PenBrushObjects = new();
|
||||
public ulong NextDeviceContext = FirstDeviceContextHandle;
|
||||
public ulong CaptureWindow;
|
||||
public bool QuitPosted;
|
||||
public ulong QuitExitCode;
|
||||
}
|
||||
|
||||
private sealed class Win32kDeviceContext
|
||||
@@ -248,6 +251,13 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void PostQuitMessage(BinaryEmulator Instance, ulong ExitCode)
|
||||
{
|
||||
Win32kState State = GetState(Instance);
|
||||
State.QuitExitCode = ExitCode;
|
||||
State.QuitPosted = true;
|
||||
}
|
||||
|
||||
internal static bool TryGetMessage(BinaryEmulator Instance, ulong HwndFilter, uint MinMessage, uint MaxMessage, bool Remove, out Win32kMessage Message)
|
||||
{
|
||||
DrainHostEvents(Instance);
|
||||
@@ -267,6 +277,14 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k
|
||||
Index++;
|
||||
}
|
||||
|
||||
if (State.QuitPosted)
|
||||
{
|
||||
Message = new Win32kMessage(0, WM_QUIT, State.QuitExitCode, 0, unchecked((uint)Instance.EmulatedTickCount64), 0, 0);
|
||||
if (Remove)
|
||||
State.QuitPosted = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Message = default;
|
||||
return false;
|
||||
}
|
||||
@@ -279,6 +297,9 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k
|
||||
return false;
|
||||
|
||||
Win32kState State = GetState(Instance);
|
||||
if (State.QuitPosted && (WakeMask & QS_POSTMESSAGE) != 0)
|
||||
return true;
|
||||
|
||||
foreach (Win32kMessage Candidate in State.MessageQueue)
|
||||
{
|
||||
if ((GetMessageWakeBits(Candidate.Message) & WakeMask) != 0)
|
||||
|
||||
@@ -1165,6 +1165,9 @@ namespace Brovan.Core.Emulation.OS.Windows
|
||||
public ulong EtwNotificationEventHandle;
|
||||
internal PebLdrTracker LdrTracker;
|
||||
public readonly Dictionary<ulong, WinWindow> WinWindows = new();
|
||||
private const int MaxDestroyedWindows = 64;
|
||||
private readonly Dictionary<ulong, WinWindow> DestroyedWindows = new();
|
||||
private readonly Queue<ulong> DestroyedWindowOrder = new();
|
||||
public readonly Dictionary<ushort, WinWindowClass> WinWindowClassesByAtom = new();
|
||||
private readonly Dictionary<string, ushort> WinWindowClassAtomsByKey = new(StringComparer.OrdinalIgnoreCase);
|
||||
private ushort NextWindowClassAtom = 0xC000;
|
||||
@@ -3863,6 +3866,14 @@ namespace Brovan.Core.Emulation.OS.Windows
|
||||
{
|
||||
}
|
||||
|
||||
public void HideDesktopWindow()
|
||||
{
|
||||
if (DesktopWindow == null || DesktopDisplay is not GuiThreadManager guiManager)
|
||||
return;
|
||||
|
||||
guiManager.EnqueuePresent(DesktopWindowTitle, 0, 0, false, WindowState.Normal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Presents the current foreground Win32k window through the host window manager.
|
||||
/// </summary>
|
||||
@@ -3931,6 +3942,7 @@ namespace Brovan.Core.Emulation.OS.Windows
|
||||
if (DesktopDisplay == null)
|
||||
return;
|
||||
|
||||
HostEventQueue.Reset();
|
||||
DesktopWindow = DesktopDisplay.CreateWindow(new WindowOptions
|
||||
{
|
||||
Title = DesktopWindowTitle,
|
||||
@@ -4051,11 +4063,15 @@ namespace Brovan.Core.Emulation.OS.Windows
|
||||
if (FocusWindow == Hwnd)
|
||||
FocusWindow = 0;
|
||||
|
||||
Win32kHelper.PostMessage(Emulator, Hwnd, Win32kHelper.WM_DESTROY, 0, 0);
|
||||
|
||||
foreach (ulong Child in Window.Children.ToArray())
|
||||
{
|
||||
DestroyWindow(Child);
|
||||
}
|
||||
|
||||
Win32kHelper.PostMessage(Emulator, Hwnd, Win32kHelper.WM_NCDESTROY, 0, 0);
|
||||
|
||||
if (Window.ParentHwnd != 0 && WinWindows.TryGetValue(Window.ParentHwnd, out WinWindow Parent))
|
||||
{
|
||||
Parent.Children.Remove(Hwnd);
|
||||
@@ -4068,10 +4084,36 @@ namespace Brovan.Core.Emulation.OS.Windows
|
||||
Window.Destroyed = true;
|
||||
ClearUserWindowHandleEntry(Window);
|
||||
WinWindows.Remove(Hwnd);
|
||||
RememberDestroyedWindow(Window);
|
||||
PresentDesktop();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RememberDestroyedWindow(WinWindow Window)
|
||||
{
|
||||
if (Window.WndProc == 0)
|
||||
return;
|
||||
|
||||
while (DestroyedWindowOrder.Count >= MaxDestroyedWindows)
|
||||
DestroyedWindows.Remove(DestroyedWindowOrder.Dequeue());
|
||||
|
||||
DestroyedWindows[Window.Hwnd] = Window;
|
||||
DestroyedWindowOrder.Enqueue(Window.Hwnd);
|
||||
}
|
||||
|
||||
public WinWindow GetDestroyedWindow(ulong Hwnd)
|
||||
{
|
||||
if (Hwnd == 0)
|
||||
return null;
|
||||
|
||||
return DestroyedWindows.TryGetValue(Hwnd, out WinWindow Window) ? Window : null;
|
||||
}
|
||||
|
||||
public void ForgetDestroyedWindow(ulong Hwnd)
|
||||
{
|
||||
DestroyedWindows.Remove(Hwnd);
|
||||
}
|
||||
|
||||
public WinHandle OpenFileHandle(string Path, bool FSAccess, AccessMask Permissions)
|
||||
{
|
||||
WinFile hFile = null;
|
||||
|
||||
Reference in New Issue
Block a user