mirror of
https://github.com/nomi-san/parsec-vdd.git
synced 2026-08-07 13:50:45 +00:00
feat: add device enumeration for PnP displays (#82)
- Legacy EnumDisplayDevices is bound to the calling thread, it's not working on RDP session.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
@@ -202,6 +203,58 @@ namespace ParsecVDisplay
|
||||
return Native.CM_Locate_DevNodeA(out devInst, deviceId, 0) == 0;
|
||||
}
|
||||
|
||||
public struct ClassDevice
|
||||
{
|
||||
public string InstanceId;
|
||||
public uint DevInst;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate present devices of a given setup class. Returns the device
|
||||
/// instance ID (e.g. "DISPLAY\PSCCDD0\5&abc&UID256") and the
|
||||
/// CM devnode handle for each. This goes through the PnP device tree
|
||||
/// rather than GDI, so it is session-independent — useful when the app
|
||||
/// runs inside an RDP session and EnumDisplayDevices is blind to the
|
||||
/// console session's displays.
|
||||
/// </summary>
|
||||
public static List<ClassDevice> EnumerateClass(string classGuid)
|
||||
{
|
||||
var list = new List<ClassDevice>();
|
||||
|
||||
var guid = Guid.Parse(classGuid);
|
||||
var devInfo = Native.SetupDiGetClassDevsA(ref guid, null, null, Native.DIGCF_PRESENT);
|
||||
if (!devInfo.IsValidHandle())
|
||||
return list;
|
||||
|
||||
try
|
||||
{
|
||||
var devInfoData = new Native.SP_DEVINFO_DATA();
|
||||
devInfoData.cbSize = sizeof(Native.SP_DEVINFO_DATA);
|
||||
|
||||
const int bufSize = Native.MAX_DEVICE_ID_LEN;
|
||||
byte* buf = stackalloc byte[bufSize];
|
||||
|
||||
for (uint i = 0; Native.SetupDiEnumDeviceInfo(devInfo, i, &devInfoData); i++)
|
||||
{
|
||||
int required = 0;
|
||||
if (Native.SetupDiGetDeviceInstanceIdA(devInfo, &devInfoData, buf, bufSize, &required))
|
||||
{
|
||||
list.Add(new ClassDevice
|
||||
{
|
||||
InstanceId = Marshal.PtrToStringAnsi((IntPtr)buf),
|
||||
DevInst = devInfoData.DevInst,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Native.SetupDiDestroyDeviceInfoList(devInfo);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static bool IsValidHandle(this IntPtr handle)
|
||||
{
|
||||
return handle != IntPtr.Zero
|
||||
@@ -346,6 +399,15 @@ namespace ParsecVDisplay
|
||||
int PropertyBufferSize,
|
||||
int* RequiredSize);
|
||||
|
||||
[DllImport("setupapi.dll", SetLastError = true, EntryPoint = "SetupDiGetDeviceInstanceIdA", CharSet = CharSet.Ansi)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool SetupDiGetDeviceInstanceIdA(
|
||||
IntPtr DeviceInfoSet,
|
||||
SP_DEVINFO_DATA* DeviceInfoData,
|
||||
byte* DeviceInstanceId,
|
||||
int DeviceInstanceIdSize,
|
||||
int* RequiredSize);
|
||||
|
||||
[DllImport("setupapi.dll")]
|
||||
public static extern uint CM_Get_DevNode_Status(
|
||||
uint* pulStatus,
|
||||
|
||||
+37
-2
@@ -291,14 +291,20 @@ namespace ParsecVDisplay
|
||||
|
||||
public void TakeScreenshot(string saveFile)
|
||||
{
|
||||
if (string.IsNullOrEmpty(DeviceName))
|
||||
return;
|
||||
|
||||
var devMode = new Native.DEVMODE();
|
||||
devMode.dmSize = (short)Marshal.SizeOf(typeof(Native.DEVMODE));
|
||||
Native.EnumDisplaySettings(DeviceName, -1, ref devMode);
|
||||
if (!Native.EnumDisplaySettings(DeviceName, -1, ref devMode))
|
||||
return;
|
||||
|
||||
int x = devMode.dmPositionX;
|
||||
int y = devMode.dmPositionY;
|
||||
int width = devMode.dmPelsWidth;
|
||||
int height = devMode.dmPelsHeight;
|
||||
if (width <= 0 || height <= 0)
|
||||
return;
|
||||
|
||||
using (var bmp = new Bitmap(width, height))
|
||||
using (var gfx = Graphics.FromImage(bmp))
|
||||
@@ -386,6 +392,35 @@ namespace ParsecVDisplay
|
||||
}
|
||||
}
|
||||
|
||||
// Supplement with PnP-tree enumeration for Parsec monitors the GDI
|
||||
// pass didn't see. Under RDP, EnumDisplayDevices is bound to the
|
||||
// RDP session's window station and is blind to the console session's
|
||||
// virtual displays — but SetupDi walks the kernel device tree and
|
||||
// works regardless. These are added with Active=false (no GDI
|
||||
// handle → can't ChangeDisplaySettings), so the UI shows them as
|
||||
// [offline] but the count is correct.
|
||||
const string CLASS_MONITOR_GUID = "{4d36e96e-e325-11ce-bfc1-08002be10318}";
|
||||
foreach (var dev in Device.EnumerateClass(CLASS_MONITOR_GUID))
|
||||
{
|
||||
if (dev.InstanceId.IndexOf("PSCCDD0", StringComparison.OrdinalIgnoreCase) < 0)
|
||||
continue;
|
||||
if (displayMap.ContainsKey(dev.InstanceId))
|
||||
continue;
|
||||
|
||||
var phantom = new Display
|
||||
{
|
||||
Active = false,
|
||||
Address = ParseDisplayAddress(dev.InstanceId),
|
||||
DeviceName = string.Empty,
|
||||
DisplayName = "PSCCDD0",
|
||||
};
|
||||
|
||||
if (Device.GetParentDeviceInstance(dev.DevInst, out uint parentInst, out phantom.AdapterInstance))
|
||||
phantom.AdapterArrival = Device.GetDeviceLastArrival(parentInst);
|
||||
|
||||
displayMap.Add(dev.InstanceId, phantom);
|
||||
}
|
||||
|
||||
var displays = displayMap.Values.ToList();
|
||||
|
||||
// Sort displays by adapter arrival (older adapter → lower number),
|
||||
@@ -395,7 +430,7 @@ namespace ParsecVDisplay
|
||||
displays.Sort((a, b) =>
|
||||
{
|
||||
if (a.AdapterInstance == b.AdapterInstance)
|
||||
return a.DeviceName.CompareTo(b.DeviceName);
|
||||
return a.Address.CompareTo(b.Address);
|
||||
return a.AdapterArrival.CompareTo(b.AdapterArrival);
|
||||
});
|
||||
|
||||
|
||||
@@ -48,6 +48,10 @@ namespace ParsecVDisplay
|
||||
|
||||
public void MirrorScreen(string displayDevice)
|
||||
{
|
||||
// Phantom (PnP-only) displays have no GDI handle; nothing to mirror.
|
||||
if (string.IsNullOrEmpty(displayDevice))
|
||||
return;
|
||||
|
||||
if (!IsMirroring)
|
||||
{
|
||||
IsMirroring = true;
|
||||
|
||||
Reference in New Issue
Block a user