[GUI] Add native Vulkan host surface support and more (#337)

* [GUI] Add native Vulkan host surface support and more

* reuse

* [GUI] set width session menu
This commit is contained in:
Berk
2026-07-17 18:57:10 +03:00
committed by GitHub
parent aa25f6e978
commit fbafd3f429
25 changed files with 3661 additions and 585 deletions
+109 -2
View File
@@ -69,11 +69,118 @@ public sealed class PosixHostInput : IHostInput
{
// GLFW only delivers key events to the focused window, so a
// delivering keyboard implies focus.
return _source?.HasKeyboardFocus ?? false;
return _source?.HasKeyboardFocus ?? IsEmbeddedX11WindowFocused();
}
public bool IsKeyDown(int virtualKey)
{
return _source?.IsKeyDown(virtualKey) ?? false;
var source = _source;
if (source is not null)
{
return source.IsKeyDown(virtualKey);
}
return IsEmbeddedX11WindowFocused() && IsEmbeddedX11KeyDown(virtualKey);
}
private static bool IsEmbeddedX11WindowFocused()
{
if (!OperatingSystem.IsLinux())
{
return false;
}
var display = HostSessionControl.EmbeddedHostDisplay;
var window = HostSessionControl.EmbeddedHostWindow;
if (display == 0 || window == 0 || XGetInputFocus(display, out var focusedWindow, out _) == 0 || focusedWindow == 0)
{
return false;
}
return GetTopLevelWindow(display, focusedWindow) == GetTopLevelWindow(display, window);
}
private static bool IsEmbeddedX11KeyDown(int virtualKey)
{
var display = HostSessionControl.EmbeddedHostDisplay;
var keysym = ToX11Keysym(virtualKey);
if (display == 0 || keysym == 0)
{
return false;
}
var keycode = XKeysymToKeycode(display, keysym);
if (keycode == 0)
{
return false;
}
var keymap = new byte[32];
XQueryKeymap(display, keymap);
return (keymap[keycode >> 3] & (1 << (keycode & 7))) != 0;
}
private static nint GetTopLevelWindow(nint display, nint window)
{
var current = window;
for (var depth = 0; depth < 16; depth++)
{
if (XQueryTree(display, current, out var root, out var parent, out var children, out _) == 0)
{
return 0;
}
if (children != 0)
{
XFree(children);
}
if (parent == 0 || parent == root)
{
return current;
}
current = parent;
}
return 0;
}
private static nuint ToX11Keysym(int virtualKey)
{
return virtualKey switch
{
0x08 => 0xFF08, // Backspace
0x09 => 0xFF09, // Tab
0x0D => 0xFF0D, // Return
0x1B => 0xFF1B, // Escape
0x25 => 0xFF51, // Left
0x26 => 0xFF52, // Up
0x27 => 0xFF53, // Right
0x28 => 0xFF54, // Down
>= 0x41 and <= 0x5A => (nuint)virtualKey,
_ => 0,
};
}
[System.Runtime.InteropServices.DllImport("libX11.so.6")]
private static extern int XGetInputFocus(nint display, out nint focus, out int revertTo);
[System.Runtime.InteropServices.DllImport("libX11.so.6")]
private static extern int XQueryKeymap(nint display, [System.Runtime.InteropServices.Out] byte[] keysReturn);
[System.Runtime.InteropServices.DllImport("libX11.so.6")]
private static extern byte XKeysymToKeycode(nint display, nuint keysym);
[System.Runtime.InteropServices.DllImport("libX11.so.6")]
private static extern int XQueryTree(
nint display,
nint window,
out nint root,
out nint parent,
out nint children,
out uint childCount);
[System.Runtime.InteropServices.DllImport("libX11.so.6")]
private static extern int XFree(nint data);
}
@@ -10,7 +10,7 @@ namespace SharpEmu.HLE.Host.Windows;
/// Supports USB (input report 0x01) and Bluetooth (extended report 0x31,
/// activated by requesting feature report 0x05), with hot-plug retry.
/// </summary>
internal static class WindowsDualSenseReader
public static class WindowsDualSenseReader
{
private const ushort SonyVendorId = 0x054C;
private const ushort DualSenseProductId = 0x0CE6;
@@ -35,7 +35,7 @@ internal static class WindowsDualSenseReader
private static byte _playerLeds = 0x04; // center LED = player 1
/// <summary>Starts the background reader once; safe to call repeatedly.</summary>
internal static void EnsureStarted()
public static void EnsureStarted()
{
// The GUI source-links this reader and calls it directly, without the
// host-platform resolution that otherwise guarantees Windows.
@@ -61,7 +61,7 @@ internal static class WindowsDualSenseReader
}
}
internal static bool TryGetState(out HostGamepadState state)
public static bool TryGetState(out HostGamepadState state)
{
lock (Gate)
{
@@ -67,7 +67,19 @@ internal sealed partial class WindowsHostInput : IHostInput
}
GetWindowThreadProcessId(foregroundWindow, out var processId);
return processId == (uint)Environment.ProcessId;
if (processId == (uint)Environment.ProcessId)
{
return true;
}
// The GUI runs the emulator in an isolated child process. Its native
// Vulkan surface is a child of the GUI window, so the foreground
// window belongs to the launcher process rather than this one.
var embeddedHostWindow = HostSessionControl.EmbeddedHostWindow;
var hostTopLevelWindow = embeddedHostWindow == 0
? 0
: GetAncestor(embeddedHostWindow, GetAncestorRoot);
return hostTopLevelWindow != 0 && foregroundWindow == hostTopLevelWindow;
}
public bool IsKeyDown(int virtualKey) =>
@@ -81,4 +93,9 @@ internal sealed partial class WindowsHostInput : IHostInput
[LibraryImport("user32.dll")]
private static partial uint GetWindowThreadProcessId(nint hWnd, out uint processId);
[LibraryImport("user32.dll")]
private static partial nint GetAncestor(nint hWnd, uint gaFlags);
private const uint GetAncestorRoot = 2;
}
@@ -11,7 +11,7 @@ namespace SharpEmu.HLE.Host.Windows;
/// <see cref="HostGamepadState"/> conventions. Supports rumble and hot-plug
/// retry; the first connected slot (of four) is used.
/// </summary>
internal static partial class WindowsXInputReader
public static partial class WindowsXInputReader
{
private const uint ErrorSuccess = 0;
private const int SlotCount = 4;
@@ -43,7 +43,7 @@ internal static partial class WindowsXInputReader
private static byte _triggerRight;
/// <summary>Starts the background reader once; safe to call repeatedly.</summary>
internal static void EnsureStarted()
public static void EnsureStarted()
{
// The GUI source-links this reader and calls it directly, without the
// host-platform resolution that otherwise guarantees Windows.
@@ -69,7 +69,7 @@ internal static partial class WindowsXInputReader
}
}
internal static bool TryGetState(out HostGamepadState state)
public static bool TryGetState(out HostGamepadState state)
{
lock (Gate)
{
+72 -1
View File
@@ -10,17 +10,88 @@ namespace SharpEmu.HLE;
public static class HostSessionControl
{
private static Action<string>? _shutdownHandler;
private static string? _pendingShutdownReason;
private static int _shutdownRequested;
private static long _embeddedHostWindow;
private static long _embeddedHostDisplay;
/// <summary>
/// Indicates that the active host session is being stopped. Runtime code
/// uses this to skip expensive post-exit diagnostics before returning the
/// GUI to its library.
/// </summary>
public static bool IsShutdownRequested => Volatile.Read(ref _shutdownRequested) != 0;
/// <summary>
/// Native GUI surface used by an isolated emulator child. Input backends
/// use it to treat the launcher window as the active game window.
/// </summary>
public static nint EmbeddedHostWindow => unchecked((nint)Interlocked.Read(ref _embeddedHostWindow));
/// <summary>X11 Display* paired with <see cref="EmbeddedHostWindow"/> when available.</summary>
public static nint EmbeddedHostDisplay => unchecked((nint)Interlocked.Read(ref _embeddedHostDisplay));
public static void SetEmbeddedHostSurface(nint window, nint display = 0)
{
Interlocked.Exchange(ref _embeddedHostDisplay, unchecked((long)display));
Interlocked.Exchange(ref _embeddedHostWindow, unchecked((long)window));
}
/// <summary>
/// Starts a fresh session after the previous guest has fully left its
/// execution backend.
/// </summary>
public static void ResetShutdownRequest()
{
Interlocked.Exchange(ref _pendingShutdownReason, null);
Volatile.Write(ref _shutdownRequested, 0);
}
public static void SetShutdownHandler(Action<string>? handler)
{
Volatile.Write(ref _shutdownHandler, handler);
if (handler is null)
{
Interlocked.Exchange(ref _pendingShutdownReason, null);
return;
}
var pendingReason = Interlocked.Exchange(ref _pendingShutdownReason, null);
if (pendingReason is not null)
{
Invoke(handler, pendingReason);
}
}
public static void RequestShutdown(string reason)
{
Volatile.Write(ref _shutdownRequested, 1);
var handler = Volatile.Read(ref _shutdownHandler);
if (handler is not null)
{
Invoke(handler, reason);
return;
}
// Stop can be pressed while the GUI session is starting. Retain the
// request until the native backend installs its cooperative handler.
Volatile.Write(ref _pendingShutdownReason, reason);
handler = Volatile.Read(ref _shutdownHandler);
if (handler is not null)
{
var pendingReason = Interlocked.Exchange(ref _pendingShutdownReason, null);
if (pendingReason is not null)
{
Invoke(handler, pendingReason);
}
}
}
private static void Invoke(Action<string> handler, string reason)
{
try
{
Volatile.Read(ref _shutdownHandler)?.Invoke(reason);
handler(reason);
}
catch (Exception exception)
{