PowerControl: Properly detect internal displays being connected

This commit is contained in:
Kamil Trzciński 2023-01-05 22:46:02 +01:00
parent 64f4050de5
commit 7c1099be48
3 changed files with 380 additions and 71 deletions

View file

@ -0,0 +1,360 @@
using System.Runtime.InteropServices;
namespace ExternalHelpers
{
// Taken from: https://stackoverflow.com/a/66160049
public class DisplayConfig
{
public static bool? IsInternalConnected
{
get
{
return GetDisplayConfigPathInfos()?.Any((path) => path.flags.HasFlag(DISPLAYCONFIG_PATH.DISPLAYCONFIG_PATH_ACTIVE) && path.targetInfo.outputTechnology.HasFlag(DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL));
}
}
private static DISPLAYCONFIG_PATH_INFO[]? GetDisplayConfigPathInfos()
{
var err = GetDisplayConfigBufferSizes(QDC.QDC_ONLY_ACTIVE_PATHS, out var pathCount, out var modeCount);
if (err != 0)
return null;
var paths = new DISPLAYCONFIG_PATH_INFO[pathCount];
var modes = new DISPLAYCONFIG_MODE_INFO[modeCount];
err = QueryDisplayConfig(QDC.QDC_ONLY_ACTIVE_PATHS, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero);
if (err != 0)
return null;
return paths;
}
private enum DISPLAYCONFIG_DEVICE_INFO_TYPE
{
DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME = 1,
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME = 2,
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE = 3,
DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME = 4,
DISPLAYCONFIG_DEVICE_INFO_SET_TARGET_PERSISTENCE = 5,
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE = 6,
DISPLAYCONFIG_DEVICE_INFO_GET_SUPPORT_VIRTUAL_RESOLUTION = 7,
DISPLAYCONFIG_DEVICE_INFO_SET_SUPPORT_VIRTUAL_RESOLUTION = 8,
DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO = 9,
DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE = 10,
DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL = 11,
}
private enum DISPLAYCONFIG_COLOR_ENCODING
{
DISPLAYCONFIG_COLOR_ENCODING_RGB = 0,
DISPLAYCONFIG_COLOR_ENCODING_YCBCR444 = 1,
DISPLAYCONFIG_COLOR_ENCODING_YCBCR422 = 2,
DISPLAYCONFIG_COLOR_ENCODING_YCBCR420 = 3,
DISPLAYCONFIG_COLOR_ENCODING_INTENSITY = 4,
}
private enum DISPLAYCONFIG_SCALING
{
DISPLAYCONFIG_SCALING_IDENTITY = 1,
DISPLAYCONFIG_SCALING_CENTERED = 2,
DISPLAYCONFIG_SCALING_STRETCHED = 3,
DISPLAYCONFIG_SCALING_ASPECTRATIOCENTEREDMAX = 4,
DISPLAYCONFIG_SCALING_CUSTOM = 5,
DISPLAYCONFIG_SCALING_PREFERRED = 128,
}
private enum DISPLAYCONFIG_ROTATION
{
DISPLAYCONFIG_ROTATION_IDENTITY = 1,
DISPLAYCONFIG_ROTATION_ROTATE90 = 2,
DISPLAYCONFIG_ROTATION_ROTATE180 = 3,
}
private enum DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY
{
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER = -1,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HD15 = 0,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SVIDEO = 1,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPOSITE_VIDEO = 2,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPONENT_VIDEO = 3,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DVI = 4,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI = 5,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_LVDS = 6,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_D_JPN = 8,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDI = 9,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL = 10,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED = 11,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EXTERNAL = 12,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EMBEDDED = 13,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDTVDONGLE = 14,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_MIRACAST = 15,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INDIRECT_WIRED = 16,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INDIRECT_VIRTUAL = 17,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL = unchecked((int)0x80000000),
}
private enum DISPLAYCONFIG_TOPOLOGY_ID
{
DISPLAYCONFIG_TOPOLOGY_INTERNAL = 0x00000001,
DISPLAYCONFIG_TOPOLOGY_CLONE = 0x00000002,
DISPLAYCONFIG_TOPOLOGY_EXTEND = 0x00000004,
DISPLAYCONFIG_TOPOLOGY_EXTERNAL = 0x00000008,
}
private enum DISPLAYCONFIG_PATH
{
DISPLAYCONFIG_PATH_ACTIVE = 0x00000001,
DISPLAYCONFIG_PATH_PREFERRED_UNSCALED = 0x00000004,
DISPLAYCONFIG_PATH_SUPPORT_VIRTUAL_MODE = 0x00000008,
}
private enum DISPLAYCONFIG_SOURCE_FLAGS
{
DISPLAYCONFIG_SOURCE_IN_USE = 0x00000001,
}
private enum DISPLAYCONFIG_TARGET_FLAGS
{
DISPLAYCONFIG_TARGET_IN_USE = 0x00000001,
DISPLAYCONFIG_TARGET_FORCIBLE = 0x00000002,
DISPLAYCONFIG_TARGET_FORCED_AVAILABILITY_BOOT = 0x00000004,
DISPLAYCONFIG_TARGET_FORCED_AVAILABILITY_PATH = 0x00000008,
DISPLAYCONFIG_TARGET_FORCED_AVAILABILITY_SYSTEM = 0x00000010,
DISPLAYCONFIG_TARGET_IS_HMD = 0x00000020,
}
private enum QDC
{
QDC_ALL_PATHS = 0x00000001,
QDC_ONLY_ACTIVE_PATHS = 0x00000002,
QDC_DATABASE_CURRENT = 0x00000004,
QDC_VIRTUAL_MODE_AWARE = 0x00000010,
QDC_INCLUDE_HMD = 0x00000020,
}
private enum DISPLAYCONFIG_SCANLINE_ORDERING
{
DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED = 0,
DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE = 1,
DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED = 2,
DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_UPPERFIELDFIRST = DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED,
DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_LOWERFIELDFIRST = 3,
}
private enum DISPLAYCONFIG_PIXELFORMAT
{
DISPLAYCONFIG_PIXELFORMAT_8BPP = 1,
DISPLAYCONFIG_PIXELFORMAT_16BPP = 2,
DISPLAYCONFIG_PIXELFORMAT_24BPP = 3,
DISPLAYCONFIG_PIXELFORMAT_32BPP = 4,
DISPLAYCONFIG_PIXELFORMAT_NONGDI = 5,
}
private enum DISPLAYCONFIG_MODE_INFO_TYPE
{
DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE = 1,
DISPLAYCONFIG_MODE_INFO_TYPE_TARGET = 2,
DISPLAYCONFIG_MODE_INFO_TYPE_DESKTOP_IMAGE = 3,
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_DEVICE_INFO_HEADER
{
public DISPLAYCONFIG_DEVICE_INFO_TYPE type;
public int size;
public LUID adapterId;
public uint id;
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO
{
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
public uint value;
public DISPLAYCONFIG_COLOR_ENCODING colorEncoding;
public int bitsPerColorChannel;
public bool advancedColorSupported => (value & 0x1) == 0x1;
public bool advancedColorEnabled => (value & 0x2) == 0x2;
public bool wideColorEnforced => (value & 0x4) == 0x4;
public bool advancedColorForceDisabled => (value & 0x8) == 0x8;
}
[StructLayout(LayoutKind.Sequential)]
private struct POINTL
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct LUID
{
public uint LowPart;
public int HighPart;
public long Value => ((long)HighPart << 32) | LowPart;
public override string ToString() => Value.ToString();
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_SOURCE_MODE
{
public uint width;
public uint height;
public DISPLAYCONFIG_PIXELFORMAT pixelFormat;
public POINTL position;
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_RATIONAL
{
public uint Numerator;
public uint Denominator;
public override string ToString() => Numerator + " / " + Denominator;
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_2DREGION
{
public uint cx;
public uint cy;
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_DESKTOP_IMAGE_INFO
{
public POINTL PathSourceSize;
public RECT DesktopImageRegion;
public RECT DesktopImageClip;
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_VIDEO_SIGNAL_INFO
{
public ulong pixelRate;
public DISPLAYCONFIG_RATIONAL hSyncFreq;
public DISPLAYCONFIG_RATIONAL vSyncFreq;
public DISPLAYCONFIG_2DREGION activeSize;
public DISPLAYCONFIG_2DREGION totalSize;
public uint videoStandard;
public DISPLAYCONFIG_SCANLINE_ORDERING scanLineOrdering;
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_TARGET_MODE
{
public DISPLAYCONFIG_VIDEO_SIGNAL_INFO targetVideoSignalInfo;
}
[StructLayout(LayoutKind.Explicit)]
private struct DISPLAYCONFIG_MODE_INFO_union
{
[FieldOffset(0)]
public DISPLAYCONFIG_TARGET_MODE targetMode;
[FieldOffset(0)]
public DISPLAYCONFIG_SOURCE_MODE sourceMode;
[FieldOffset(0)]
public DISPLAYCONFIG_DESKTOP_IMAGE_INFO desktopImageInfo;
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_PATH_SOURCE_INFO
{
public LUID adapterId;
public uint id;
public uint modeInfoIdx;
public DISPLAYCONFIG_SOURCE_FLAGS statusFlags;
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_PATH_TARGET_INFO
{
public LUID adapterId;
public uint id;
public uint modeInfoIdx;
public DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY outputTechnology;
public DISPLAYCONFIG_ROTATION rotation;
public DISPLAYCONFIG_SCALING scaling;
public DISPLAYCONFIG_RATIONAL refreshRate;
public DISPLAYCONFIG_SCANLINE_ORDERING scanLineOrdering;
public bool targetAvailable;
public DISPLAYCONFIG_TARGET_FLAGS statusFlags;
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_PATH_INFO
{
public DISPLAYCONFIG_PATH_SOURCE_INFO sourceInfo;
public DISPLAYCONFIG_PATH_TARGET_INFO targetInfo;
public DISPLAYCONFIG_PATH flags;
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_MODE_INFO
{
public DISPLAYCONFIG_MODE_INFO_TYPE infoType;
public uint id;
public LUID adapterId;
public DISPLAYCONFIG_MODE_INFO_union info;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DISPLAYCONFIG_SOURCE_DEVICE_NAME
{
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string viewGdiDeviceName;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS
{
public uint value;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DISPLAYCONFIG_TARGET_DEVICE_NAME
{
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
public DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS flags;
public DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY outputTechnology;
public ushort edidManufactureId;
public ushort edidProductCodeId;
public uint connectorInstance;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string monitorFriendlyDeviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string monitorDevicePat;
}
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32")]
private static extern int GetDisplayConfigBufferSizes(QDC flags, out int numPathArrayElements, out int numModeInfoArrayElements);
[DllImport("user32")]
private static extern int QueryDisplayConfig(QDC flags, ref int numPathArrayElements, [In, Out] DISPLAYCONFIG_PATH_INFO[] pathArray, ref int numModeInfoArrayElements, [In, Out] DISPLAYCONFIG_MODE_INFO[] modeInfoArray, out DISPLAYCONFIG_TOPOLOGY_ID currentTopologyId);
[DllImport("user32")]
private static extern int QueryDisplayConfig(QDC flags, ref int numPathArrayElements, [In, Out] DISPLAYCONFIG_PATH_INFO[] pathArray, ref int numModeInfoArrayElements, [In, Out] DISPLAYCONFIG_MODE_INFO[] modeInfoArray, IntPtr currentTopologyId);
[DllImport("user32")]
private static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO requestPacket);
[DllImport("user32")]
private static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_SOURCE_DEVICE_NAME requestPacket);
[DllImport("user32")]
private static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_TARGET_DEVICE_NAME requestPacket);
}
}

View file

@ -17,9 +17,6 @@ namespace PowerControl
public const int KeyPressRepeatTime = 400;
public const int KeyPressNextRepeatTime = 90;
private SynchronizationContext? context;
System.Windows.Forms.Timer contextTimer;
Container components = new Container();
System.Windows.Forms.NotifyIcon notifyIcon;
StartupManager startupManager = new StartupManager(Title);
@ -29,6 +26,8 @@ namespace PowerControl
System.Windows.Forms.Timer osdDismissTimer;
bool isOSDToggled = false;
bool wasInternalDisplayConnected;
hidapi.HidDevice neptuneDevice = new hidapi.HidDevice(0x28de, 0x1205, 64);
SDCInput neptuneDeviceState = new SDCInput();
DateTime? neptuneDeviceNextKey;
@ -38,8 +37,8 @@ namespace PowerControl
static Controller()
{
Dependencies.ValidateHidapi(TitleWithVersion);
Dependencies.ValidateRTSSSharedMemoryNET(TitleWithVersion);
//Dependencies.ValidateHidapi(TitleWithVersion);
//Dependencies.ValidateRTSSSharedMemoryNET(TitleWithVersion);
}
public Controller()
@ -55,11 +54,6 @@ namespace PowerControl
if (Instance.WantsRunOnStartup)
startupManager.Startup = true;
InitializeDisplayContext();
contextTimer?.Start();
SystemEvents.DisplaySettingsChanged += DisplayChangesHandler;
var contextMenu = new System.Windows.Forms.ContextMenuStrip(components);
rootMenu.Init();
@ -205,6 +199,9 @@ namespace PowerControl
dismissNeptuneInput();
});
}
wasInternalDisplayConnected = ExternalHelpers.DisplayConfig.IsInternalConnected.GetValueOrDefault(false);
SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;
}
private void OsdTimer_Tick(object? sender, EventArgs e)
@ -399,27 +396,22 @@ namespace PowerControl
}
}
private void InitializeDisplayContext()
private void SystemEvents_DisplaySettingsChanged(object? sender, EventArgs e)
{
DeviceManager.LoadDisplays();
contextTimer = new System.Windows.Forms.Timer();
contextTimer.Interval = 200;
contextTimer.Tick += (_, _) =>
{
context = SynchronizationContext.Current;
contextTimer.Stop();
};
}
var isInternalDisplayConnected = ExternalHelpers.DisplayConfig.IsInternalConnected.GetValueOrDefault(false);
if (wasInternalDisplayConnected == isInternalDisplayConnected)
return;
private void DisplayChangesHandler(object? sender, EventArgs e)
{
if (DeviceManager.RefreshDisplays())
{
context?.Post((object? state) =>
Log.TraceLine("SystemEvents_DisplaySettingsChanged: wasConnected={0}, isConnected={1}",
wasInternalDisplayConnected, isInternalDisplayConnected);
wasInternalDisplayConnected = isInternalDisplayConnected;
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(
new Action(() =>
{
rootMenu.Update();
}, null);
}
})
);
}
}
}

View file

@ -11,30 +11,6 @@ namespace PowerControl.Helpers
{
internal class DeviceManager
{
private static Screen[] screens = new Screen[0];
public static int NumberOfDisplays
{
get { return screens.Length; }
}
public static void LoadDisplays()
{
screens = Screen.AllScreens;
}
public static bool RefreshDisplays()
{
var newScreens = Screen.AllScreens;
if (HaveScreensChanged(newScreens))
{
screens = newScreens;
return true;
}
return false;
}
public static string[]? GetDevices(Guid? classGuid)
{
string? filter = null;
@ -150,25 +126,6 @@ namespace PowerControl.Helpers
}
}
private static bool HaveScreensChanged(Screen[] newScreens)
{
if (screens.Length != newScreens.Length)
{
return true;
}
for (int i = 0; i < screens.Length; i++)
{
if (screens[i].DeviceName != newScreens[i].DeviceName ||
screens[i].Primary != newScreens[i].Primary)
{
return true;
}
}
return false;
}
[DllImport("setupapi.dll", CharSet = CharSet.Auto)]
static extern int CM_Locate_DevNode(out IntPtr pdnDevInst, string pDeviceID, int ulFlags);