mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-02 07:59:44 +08:00
[Host] Abstract audio output and pad/keyboard input behind the host platform seam (#192)
* [Host] Abstract audio output behind IHostAudioOutput Add IHostAudioOutput (opens streams, names the backend for diagnostics) and IHostAudioStream (submit interleaved stereo 16-bit PCM, Dispose) to the host seam, with the winmm waveOut implementation moving whole into Host/Windows/WindowsWaveOutAudio — same device open, queueing, 32 KB backpressure wait, and buffer lifetime as WinMmAudioPort had. The DllImports become source-generated LibraryImports in the move, matching the other Windows backends. The guest-format conversion (mono/stereo/7.1, s16/float32 -> stereo PCM16) is platform policy, not device code, so it stays in Libs as AudioPcmConversion; AudioOutOutput converts into a pooled buffer and submits the result through the stream. Open failures still degrade to the silent paced port with the same warning, and the port log line now takes its backend name from the platform instead of a hardcoded string. * [Host] Abstract pad and keyboard input behind IHostInput Add IHostInput to the host seam: gamepad state snapshots, rumble / trigger-rumble / lightbar sinks, and the keyboard-fallback queries (window focus, key state). Gamepad state crosses the seam as the new unmanaged HostGamepadState with HostGamepadButtons flags — named after the PlayStation layout the guest API exposes but with the seam's own values, so SCE_PAD_BUTTON bits never leak into host backends and the per-frame poll can stackalloc its snapshot buffer. The DualSense raw-HID reader, the XInput reader, and the Win32 HID interop move whole into Host/Windows (report parsing, hot-plug loops, rumble/lightbar output reports, and log strings unchanged), translating to the neutral flags instead of ORBIS bits and converting their DllImports to source-generated LibraryImports. WindowsHostInput composes them plus the user32 keyboard queries; rumble still fans out to both readers, trigger rumble stays XInput-only, lightbar stays DualSense-only. PadExports keeps all policy: the keyboard mapping (now via named OrbisPadButton constants instead of raw hex), the controller-beats- keyboard-past-deadzone merge, and the new host->ORBIS button translation. The GUI's source-linked reader copies re-point to the moved files (it still cannot reference SharpEmu.HLE wholesale), which requires AllowUnsafeBlocks for the generated marshalling stubs; its navigation code switches to the neutral flags. * [Host] Move the timer-resolution request behind IHostThreading IHostThreading gains RequestTimerResolution (idempotent, best-effort ~1 ms timed-wait granularity; a no-op wherever the platform default is already fine). The winmm timeBeginPeriod call, its once-only latch, and both warning strings move from the Libs-level HostTimerResolution helper into WindowsHostThreading as a source-generated LibraryImport; the vblank pump requests it through the platform instead. HostSystemInfo in SharpEmu.Logging keeps its direct user32/kernel32 imports deliberately: Logging sits below HLE in the dependency chain so it cannot see the host seam, every path is already OS-gated with fallbacks, and it only runs once for the diagnostics banner.
This commit is contained in:
committed by
GitHub
parent
2ad9836d13
commit
72645cb373
@@ -2,9 +2,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.HLE.Host;
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SharpEmu.Libs.Pad;
|
||||
|
||||
@@ -38,8 +38,7 @@ public static class PadExports
|
||||
public static int PadInit(CpuContext ctx)
|
||||
{
|
||||
_initialized = true;
|
||||
DualSenseReader.EnsureStarted();
|
||||
XInputReader.EnsureStarted();
|
||||
HostPlatform.Current.Input.EnsureStarted();
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
@@ -81,15 +80,13 @@ public static class PadExports
|
||||
return ctx.SetReturn(OrbisPadErrorDeviceNotConnected);
|
||||
}
|
||||
|
||||
DualSenseReader.EnsureStarted();
|
||||
XInputReader.EnsureStarted();
|
||||
var input = HostPlatform.Current.Input;
|
||||
input.EnsureStarted();
|
||||
if (Interlocked.Exchange(ref _controlsAnnouncementLogged, 1) == 0)
|
||||
{
|
||||
Console.Error.WriteLine(DualSenseReader.TryGetState(out _)
|
||||
? "[LOADER][INFO] Controls: DualSense connected (keyboard fallback also active)."
|
||||
: XInputReader.TryGetState(out _)
|
||||
? "[LOADER][INFO] Controls: Xbox controller connected (keyboard fallback also active)."
|
||||
: "[LOADER][INFO] Keyboard controls: Arrow keys = D-pad, WASD = left stick, IJKL = right stick, Z/Enter = Cross, X/Esc = Circle, C = Square, V = Triangle, Q = L1, E = R1, R = L2, F = R2, Tab/Backspace = Options. A DualSense or Xbox controller will be used automatically when plugged in.");
|
||||
Console.Error.WriteLine(input.DescribeConnectedGamepad() is { } gamepadName
|
||||
? $"[LOADER][INFO] Controls: {gamepadName} connected (keyboard fallback also active)."
|
||||
: "[LOADER][INFO] Keyboard controls: Arrow keys = D-pad, WASD = left stick, IJKL = right stick, Z/Enter = Cross, X/Esc = Circle, C = Square, V = Triangle, Q = L1, E = R1, R = L2, F = R2, Tab/Backspace = Options. A DualSense or Xbox controller will be used automatically when plugged in.");
|
||||
}
|
||||
|
||||
return ctx.SetReturn(PrimaryPadHandle);
|
||||
@@ -282,7 +279,7 @@ public static class PadExports
|
||||
}
|
||||
|
||||
var triggerMask = parameter[0];
|
||||
XInputReader.SetTriggerRumble(
|
||||
HostPlatform.Current.Input.SetTriggerRumble(
|
||||
(triggerMask & 0x01) != 0 ? DecodeTriggerVibration(parameter[8..64]) : null,
|
||||
(triggerMask & 0x02) != 0 ? DecodeTriggerVibration(parameter[64..120]) : null);
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
@@ -326,8 +323,7 @@ public static class PadExports
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
DualSenseReader.SetRumble(parameter[0], parameter[1]);
|
||||
XInputReader.SetRumble(parameter[0], parameter[1]);
|
||||
HostPlatform.Current.Input.SetRumble(parameter[0], parameter[1]);
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
@@ -357,7 +353,7 @@ public static class PadExports
|
||||
return ctx.SetReturn((int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
DualSenseReader.SetLightbar(color[0], color[1], color[2]);
|
||||
HostPlatform.Current.Input.SetLightbar(color[0], color[1], color[2]);
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
@@ -374,7 +370,7 @@ public static class PadExports
|
||||
return ctx.SetReturn(OrbisPadErrorInvalidHandle);
|
||||
}
|
||||
|
||||
DualSenseReader.ResetLightbar();
|
||||
HostPlatform.Current.Input.ResetLightbar();
|
||||
return ctx.SetReturn(0);
|
||||
}
|
||||
|
||||
@@ -420,37 +416,30 @@ public static class PadExports
|
||||
return _cachedInputState;
|
||||
}
|
||||
|
||||
var acceptsKeyboardInput = IsEmulatorWindowFocused();
|
||||
var buttons = acceptsKeyboardInput ? ReadKeyboardButtons() : 0;
|
||||
var leftX = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x41), IsKeyDown(0x44)) : (byte)128;
|
||||
var leftY = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x57), IsKeyDown(0x53)) : (byte)128;
|
||||
var rightX = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x4A), IsKeyDown(0x4C)) : (byte)128;
|
||||
var rightY = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x49), IsKeyDown(0x4B)) : (byte)128;
|
||||
var l2 = acceptsKeyboardInput && IsKeyDown(0x52) ? (byte)255 : (byte)0;
|
||||
var r2 = acceptsKeyboardInput && IsKeyDown(0x46) ? (byte)255 : (byte)0;
|
||||
var input = HostPlatform.Current.Input;
|
||||
var acceptsKeyboardInput = input.IsHostWindowFocused();
|
||||
var buttons = acceptsKeyboardInput ? ReadKeyboardButtons(input) : 0;
|
||||
var leftX = acceptsKeyboardInput ? ReadAnalogStick(input.IsKeyDown(0x41), input.IsKeyDown(0x44)) : (byte)128;
|
||||
var leftY = acceptsKeyboardInput ? ReadAnalogStick(input.IsKeyDown(0x57), input.IsKeyDown(0x53)) : (byte)128;
|
||||
var rightX = acceptsKeyboardInput ? ReadAnalogStick(input.IsKeyDown(0x4A), input.IsKeyDown(0x4C)) : (byte)128;
|
||||
var rightY = acceptsKeyboardInput ? ReadAnalogStick(input.IsKeyDown(0x49), input.IsKeyDown(0x4B)) : (byte)128;
|
||||
var l2 = acceptsKeyboardInput && input.IsKeyDown(0x52) ? (byte)255 : (byte)0;
|
||||
var r2 = acceptsKeyboardInput && input.IsKeyDown(0x46) ? (byte)255 : (byte)0;
|
||||
|
||||
if (DualSenseReader.TryGetState(out var pad))
|
||||
Span<HostGamepadState> gamepads = stackalloc HostGamepadState[2];
|
||||
var gamepadCount = input.GetGamepadStates(gamepads);
|
||||
for (var index = 0; index < gamepadCount; index++)
|
||||
{
|
||||
buttons |= pad.Buttons;
|
||||
var pad = gamepads[index];
|
||||
buttons |= ToOrbisButtons(pad.Buttons);
|
||||
// The controller stick wins whenever it is deflected past a
|
||||
// small deadzone; otherwise any keyboard value stays.
|
||||
leftX = MergeAxis(pad.LeftX, leftX);
|
||||
leftY = MergeAxis(pad.LeftY, leftY);
|
||||
rightX = MergeAxis(pad.RightX, rightX);
|
||||
rightY = MergeAxis(pad.RightY, rightY);
|
||||
l2 = Math.Max(l2, pad.L2);
|
||||
r2 = Math.Max(r2, pad.R2);
|
||||
}
|
||||
|
||||
if (XInputReader.TryGetState(out var xpad))
|
||||
{
|
||||
buttons |= xpad.Buttons;
|
||||
leftX = MergeAxis(xpad.LeftX, leftX);
|
||||
leftY = MergeAxis(xpad.LeftY, leftY);
|
||||
rightX = MergeAxis(xpad.RightX, rightX);
|
||||
rightY = MergeAxis(xpad.RightY, rightY);
|
||||
l2 = Math.Max(l2, xpad.L2);
|
||||
r2 = Math.Max(r2, xpad.R2);
|
||||
l2 = Math.Max(l2, pad.LeftTrigger);
|
||||
r2 = Math.Max(r2, pad.RightTrigger);
|
||||
}
|
||||
|
||||
_cachedInputState = new PadState(
|
||||
@@ -466,50 +455,49 @@ public static class PadExports
|
||||
return _cachedInputState;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern short GetAsyncKeyState(int vKey);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern nint GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetWindowThreadProcessId(nint hWnd, out uint processId);
|
||||
|
||||
private static bool IsKeyDown(int vk) =>
|
||||
(GetAsyncKeyState(vk) & 0x8000) != 0;
|
||||
|
||||
private static bool IsEmulatorWindowFocused()
|
||||
/// <summary>Maps the host seam's neutral button flags onto SCE_PAD_BUTTON bits.</summary>
|
||||
private static uint ToOrbisButtons(HostGamepadButtons buttons)
|
||||
{
|
||||
var foregroundWindow = GetForegroundWindow();
|
||||
if (foregroundWindow == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GetWindowThreadProcessId(foregroundWindow, out var processId);
|
||||
return processId == (uint)Environment.ProcessId;
|
||||
uint result = 0;
|
||||
if ((buttons & HostGamepadButtons.Up) != 0) result |= OrbisPadButton.Up;
|
||||
if ((buttons & HostGamepadButtons.Down) != 0) result |= OrbisPadButton.Down;
|
||||
if ((buttons & HostGamepadButtons.Left) != 0) result |= OrbisPadButton.Left;
|
||||
if ((buttons & HostGamepadButtons.Right) != 0) result |= OrbisPadButton.Right;
|
||||
if ((buttons & HostGamepadButtons.Cross) != 0) result |= OrbisPadButton.Cross;
|
||||
if ((buttons & HostGamepadButtons.Circle) != 0) result |= OrbisPadButton.Circle;
|
||||
if ((buttons & HostGamepadButtons.Square) != 0) result |= OrbisPadButton.Square;
|
||||
if ((buttons & HostGamepadButtons.Triangle) != 0) result |= OrbisPadButton.Triangle;
|
||||
if ((buttons & HostGamepadButtons.L1) != 0) result |= OrbisPadButton.L1;
|
||||
if ((buttons & HostGamepadButtons.R1) != 0) result |= OrbisPadButton.R1;
|
||||
if ((buttons & HostGamepadButtons.L2) != 0) result |= OrbisPadButton.L2;
|
||||
if ((buttons & HostGamepadButtons.R2) != 0) result |= OrbisPadButton.R2;
|
||||
if ((buttons & HostGamepadButtons.L3) != 0) result |= OrbisPadButton.L3;
|
||||
if ((buttons & HostGamepadButtons.R3) != 0) result |= OrbisPadButton.R3;
|
||||
if ((buttons & HostGamepadButtons.Options) != 0) result |= OrbisPadButton.Options;
|
||||
if ((buttons & HostGamepadButtons.TouchPad) != 0) result |= OrbisPadButton.TouchPad;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static uint ReadKeyboardButtons()
|
||||
private static uint ReadKeyboardButtons(IHostInput input)
|
||||
{
|
||||
uint buttons = 0;
|
||||
// D-pad
|
||||
if (IsKeyDown(0x25)) buttons |= 0x0080; // Left
|
||||
if (IsKeyDown(0x27)) buttons |= 0x0020; // Right
|
||||
if (IsKeyDown(0x26)) buttons |= 0x0010; // Up
|
||||
if (IsKeyDown(0x28)) buttons |= 0x0040; // Down
|
||||
if (input.IsKeyDown(0x25)) buttons |= OrbisPadButton.Left;
|
||||
if (input.IsKeyDown(0x27)) buttons |= OrbisPadButton.Right;
|
||||
if (input.IsKeyDown(0x26)) buttons |= OrbisPadButton.Up;
|
||||
if (input.IsKeyDown(0x28)) buttons |= OrbisPadButton.Down;
|
||||
// Face buttons
|
||||
if (IsKeyDown(0x5A) || IsKeyDown(0x0D)) buttons |= 0x4000; // Z / Enter = Cross
|
||||
if (IsKeyDown(0x58) || IsKeyDown(0x1B)) buttons |= 0x2000; // X / Escape = Circle
|
||||
if (IsKeyDown(0x43)) buttons |= 0x8000; // C = Square
|
||||
if (IsKeyDown(0x56)) buttons |= 0x1000; // V = Triangle
|
||||
if (input.IsKeyDown(0x5A) || input.IsKeyDown(0x0D)) buttons |= OrbisPadButton.Cross; // Z / Enter
|
||||
if (input.IsKeyDown(0x58) || input.IsKeyDown(0x1B)) buttons |= OrbisPadButton.Circle; // X / Escape
|
||||
if (input.IsKeyDown(0x43)) buttons |= OrbisPadButton.Square; // C
|
||||
if (input.IsKeyDown(0x56)) buttons |= OrbisPadButton.Triangle; // V
|
||||
// Shoulder buttons
|
||||
if (IsKeyDown(0x51)) buttons |= 0x0400; // Q = L1
|
||||
if (IsKeyDown(0x45)) buttons |= 0x0800; // E = R1
|
||||
if (IsKeyDown(0x52)) buttons |= 0x0100; // R = L2 (digital)
|
||||
if (IsKeyDown(0x46)) buttons |= 0x0200; // F = R2 (digital)
|
||||
if (input.IsKeyDown(0x51)) buttons |= OrbisPadButton.L1; // Q
|
||||
if (input.IsKeyDown(0x45)) buttons |= OrbisPadButton.R1; // E
|
||||
if (input.IsKeyDown(0x52)) buttons |= OrbisPadButton.L2; // R (digital)
|
||||
if (input.IsKeyDown(0x46)) buttons |= OrbisPadButton.R2; // F (digital)
|
||||
// Options (Start)
|
||||
if (IsKeyDown(0x09) || IsKeyDown(0x08)) buttons |= 0x0008; // Tab / Backspace = Options
|
||||
if (input.IsKeyDown(0x09) || input.IsKeyDown(0x08)) buttons |= OrbisPadButton.Options; // Tab / Backspace
|
||||
return buttons;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user