mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-30 14:39:42 +08:00
72645cb373
* [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.
142 lines
4.9 KiB
C#
142 lines
4.9 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using System.Runtime.InteropServices;
|
|
using Microsoft.Win32.SafeHandles;
|
|
|
|
namespace SharpEmu.HLE.Host.Windows;
|
|
|
|
/// <summary>
|
|
/// Minimal Win32 HID interop used to talk to a DualSense controller
|
|
/// directly, without any external input library.
|
|
/// </summary>
|
|
internal static partial class WindowsHidNative
|
|
{
|
|
internal const int DigcfPresent = 0x02;
|
|
internal const int DigcfDeviceInterface = 0x10;
|
|
internal const uint GenericRead = 0x80000000;
|
|
internal const uint GenericWrite = 0x40000000;
|
|
internal const uint FileShareRead = 0x1;
|
|
internal const uint FileShareWrite = 0x2;
|
|
internal const uint OpenExisting = 3;
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct SpDeviceInterfaceData
|
|
{
|
|
public int CbSize;
|
|
public Guid InterfaceClassGuid;
|
|
public int Flags;
|
|
public nint Reserved;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct HiddAttributes
|
|
{
|
|
public int Size;
|
|
public ushort VendorId;
|
|
public ushort ProductId;
|
|
public ushort VersionNumber;
|
|
}
|
|
|
|
[LibraryImport("hid.dll")]
|
|
internal static partial void HidD_GetHidGuid(out Guid hidGuid);
|
|
|
|
[LibraryImport("hid.dll")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
internal static partial bool HidD_GetAttributes(SafeFileHandle hidDeviceObject, ref HiddAttributes attributes);
|
|
|
|
[LibraryImport("hid.dll")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
internal static partial bool HidD_GetFeature(SafeFileHandle hidDeviceObject, [In, Out] byte[] reportBuffer, int reportBufferLength);
|
|
|
|
[LibraryImport("setupapi.dll", EntryPoint = "SetupDiGetClassDevsW")]
|
|
internal static partial nint SetupDiGetClassDevs(ref Guid classGuid, nint enumerator, nint hwndParent, int flags);
|
|
|
|
[LibraryImport("setupapi.dll")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
internal static partial bool SetupDiEnumDeviceInterfaces(
|
|
nint deviceInfoSet,
|
|
nint deviceInfoData,
|
|
ref Guid interfaceClassGuid,
|
|
int memberIndex,
|
|
ref SpDeviceInterfaceData deviceInterfaceData);
|
|
|
|
[LibraryImport("setupapi.dll", EntryPoint = "SetupDiGetDeviceInterfaceDetailW")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
internal static partial bool SetupDiGetDeviceInterfaceDetail(
|
|
nint deviceInfoSet,
|
|
ref SpDeviceInterfaceData deviceInterfaceData,
|
|
nint deviceInterfaceDetailData,
|
|
int deviceInterfaceDetailDataSize,
|
|
out int requiredSize,
|
|
nint deviceInfoData);
|
|
|
|
[LibraryImport("setupapi.dll")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
internal static partial bool SetupDiDestroyDeviceInfoList(nint deviceInfoSet);
|
|
|
|
[LibraryImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
|
internal static partial SafeFileHandle CreateFile(
|
|
string fileName,
|
|
uint desiredAccess,
|
|
uint shareMode,
|
|
nint securityAttributes,
|
|
uint creationDisposition,
|
|
uint flagsAndAttributes,
|
|
nint templateFile);
|
|
|
|
/// <summary>
|
|
/// Enumerates the device paths of all present HID interfaces.
|
|
/// </summary>
|
|
internal static List<string> EnumerateHidDevicePaths()
|
|
{
|
|
var paths = new List<string>();
|
|
HidD_GetHidGuid(out var hidGuid);
|
|
var deviceInfoSet = SetupDiGetClassDevs(ref hidGuid, 0, 0, DigcfPresent | DigcfDeviceInterface);
|
|
if (deviceInfoSet == -1 || deviceInfoSet == 0)
|
|
{
|
|
return paths;
|
|
}
|
|
|
|
try
|
|
{
|
|
var interfaceData = new SpDeviceInterfaceData
|
|
{
|
|
CbSize = Marshal.SizeOf<SpDeviceInterfaceData>(),
|
|
};
|
|
|
|
for (var index = 0; SetupDiEnumDeviceInterfaces(deviceInfoSet, 0, ref hidGuid, index, ref interfaceData); index++)
|
|
{
|
|
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref interfaceData, 0, 0, out var requiredSize, 0);
|
|
if (requiredSize <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var detailBuffer = Marshal.AllocHGlobal(requiredSize);
|
|
try
|
|
{
|
|
// SP_DEVICE_INTERFACE_DETAIL_DATA_W.cbSize is 8 on x64
|
|
// (DWORD + aligned WCHAR[1]); the path string follows it.
|
|
Marshal.WriteInt32(detailBuffer, 8);
|
|
if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref interfaceData, detailBuffer, requiredSize, out _, 0) &&
|
|
Marshal.PtrToStringUni(detailBuffer + 4) is { Length: > 0 } path)
|
|
{
|
|
paths.Add(path);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
Marshal.FreeHGlobal(detailBuffer);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
|
}
|
|
|
|
return paths;
|
|
}
|
|
}
|