From b34cdcce6c1b34ac8936d96aac9619a5a6b45ae5 Mon Sep 17 00:00:00 2001 From: Terry MacDonald Date: Tue, 7 Dec 2021 18:33:16 +1300 Subject: [PATCH] Updated to latest WinLibrary version This version of WinLibrary improves handling of multiple display adapters to cope with multiple displays across multiple display adapters. Hopefully fixes #60 (but no guarantees yet) --- DisplayMagicianShared/Windows/CCD.cs | 17 ++- DisplayMagicianShared/Windows/WinLibrary.cs | 130 +++++++++++++++++--- 2 files changed, 126 insertions(+), 21 deletions(-) diff --git a/DisplayMagicianShared/Windows/CCD.cs b/DisplayMagicianShared/Windows/CCD.cs index 1819917..aa114d6 100644 --- a/DisplayMagicianShared/Windows/CCD.cs +++ b/DisplayMagicianShared/Windows/CCD.cs @@ -137,11 +137,20 @@ namespace DisplayMagicianShared.Windows public enum QDC : uint { Zero = 0x0, - QDC_ALL_PATHS = 0x00000001, // Get all paths - QDC_ONLY_ACTIVE_PATHS = 0x00000002, // Get only the active paths currently in use - QDC_DATABASE_CURRENT = 0x00000004, // Get the currently active paths as stored in the display database - QDC_VIRTUAL_MODE_AWARE = 0x00000010, // Get the virtual mode aware paths + // Get all paths + QDC_ALL_PATHS = 0x00000001, + // Get only the active paths currently in use + QDC_ONLY_ACTIVE_PATHS = 0x00000002, + // Get the currently active paths as stored in the display database + QDC_DATABASE_CURRENT = 0x00000004, + // This flag should be bitwise OR'ed with other flags to indicate that the caller is aware of virtual mode support. Supported starting in Windows 10. + QDC_VIRTUAL_MODE_AWARE = 0x00000010, + // This flag should be bitwise OR'ed with QDC_ONLY_ACTIVE_PATHS to indicate that the caller would like to include head-mounted displays (HMDs) in the list of active paths. See Remarks for more information. + // Supported starting in Windows 10 1703 Creators Update. QDC_INCLUDE_HMD = 0x00000020, + // This flag should be bitwise OR'ed with other flags to indicate that the caller is aware of virtual refresh rate support. + // Supported starting in Windows 11. + QDC_VIRTUAL_REFRESH_RATE_AWARE = 0x00000040, } [Flags] diff --git a/DisplayMagicianShared/Windows/WinLibrary.cs b/DisplayMagicianShared/Windows/WinLibrary.cs index c266b7a..68d1041 100644 --- a/DisplayMagicianShared/Windows/WinLibrary.cs +++ b/DisplayMagicianShared/Windows/WinLibrary.cs @@ -168,24 +168,33 @@ namespace DisplayMagicianShared.Windows return myDefaultConfig; } - private void PatchAdapterIDs(ref WINDOWS_DISPLAY_CONFIG savedDisplayConfig, Dictionary currentAdapterMap) + private void PatchAdapterIDs(ref WINDOWS_DISPLAY_CONFIG savedDisplayConfig) { Dictionary adapterOldToNewMap = new Dictionary(); + Dictionary currentAdapterMap = GetCurrentAdapterIDs(); SharedLogger.logger.Trace("WinLibrary/PatchAdapterIDs: Going through the list of adapters we stored in the config to figure out the old adapterIDs"); foreach (KeyValuePair savedAdapter in savedDisplayConfig.DisplayAdapters) { + bool adapterMatched = false; foreach (KeyValuePair currentAdapter in currentAdapterMap) { + SharedLogger.logger.Trace($"WinLibrary/PatchAdapterIDs: Checking if saved adapter {savedAdapter.Key} (AdapterName is {savedAdapter.Value}) is equal to current adapter id {currentAdapter.Key} (AdapterName is {currentAdapter.Value})"); + if (currentAdapter.Value.Equals(savedAdapter.Value)) { // we have found the new LUID Value for the same adapter // So we want to store it SharedLogger.logger.Trace($"WinLibrary/PatchAdapterIDs: We found that saved adapter {savedAdapter.Key} has now been assigned adapter id {currentAdapter.Key} (AdapterName is {savedAdapter.Value})"); adapterOldToNewMap.Add(savedAdapter.Key, currentAdapter.Key); + adapterMatched = true; } } + if (!adapterMatched) + { + SharedLogger.logger.Error($"WinLibrary/PatchAdapterIDs: Saved adapter {savedAdapter.Key} (AdapterName is {savedAdapter.Value}) doesn't have a current match! The adapters have changed since the configuration was last saved."); + } } ulong newAdapterValue = 0; @@ -282,16 +291,18 @@ namespace DisplayMagicianShared.Windows public WINDOWS_DISPLAY_CONFIG GetActiveConfig() { SharedLogger.logger.Trace($"WinLibrary/GetActiveConfig: Getting the currently active config"); - return GetWindowsDisplayConfig(QDC.QDC_ONLY_ACTIVE_PATHS); + // We want to include head mounted devices, inform windows we're virtual mode aware + // We'll leave virtual refresh rate aware until we can reliably detect Windows 11 versions. + return GetWindowsDisplayConfig(QDC.QDC_ONLY_ACTIVE_PATHS | QDC.QDC_INCLUDE_HMD); } - private WINDOWS_DISPLAY_CONFIG GetWindowsDisplayConfig(QDC selector = QDC.QDC_ONLY_ACTIVE_PATHS) + private WINDOWS_DISPLAY_CONFIG GetWindowsDisplayConfig(QDC selector = QDC.QDC_ONLY_ACTIVE_PATHS | QDC.QDC_INCLUDE_HMD) { // Get the size of the largest Active Paths and Modes arrays SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Getting the size of the largest Active Paths and Modes arrays"); int pathCount = 0; int modeCount = 0; - WIN32STATUS err = CCDImport.GetDisplayConfigBufferSizes(QDC.QDC_ONLY_ACTIVE_PATHS, out pathCount, out modeCount); + WIN32STATUS err = CCDImport.GetDisplayConfigBufferSizes(selector, out pathCount, out modeCount); if (err != WIN32STATUS.ERROR_SUCCESS) { SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: ERROR - GetDisplayConfigBufferSizes returned WIN32STATUS {err} when trying to get the maximum path and mode sizes"); @@ -301,14 +312,14 @@ namespace DisplayMagicianShared.Windows SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Getting the current Display Config path and mode arrays"); var paths = new DISPLAYCONFIG_PATH_INFO[pathCount]; var modes = new DISPLAYCONFIG_MODE_INFO[modeCount]; - err = CCDImport.QueryDisplayConfig(QDC.QDC_ONLY_ACTIVE_PATHS, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero); + err = CCDImport.QueryDisplayConfig(selector, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero); if (err == WIN32STATUS.ERROR_INSUFFICIENT_BUFFER) { SharedLogger.logger.Warn($"WinLibrary/GetWindowsDisplayConfig: The displays were modified between GetDisplayConfigBufferSizes and QueryDisplayConfig so we need to get the buffer sizes again."); SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Getting the size of the largest Active Paths and Modes arrays"); // Screen changed in between GetDisplayConfigBufferSizes and QueryDisplayConfig, so we need to get buffer sizes again // as per https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-querydisplayconfig - err = CCDImport.GetDisplayConfigBufferSizes(QDC.QDC_ONLY_ACTIVE_PATHS, out pathCount, out modeCount); + err = CCDImport.GetDisplayConfigBufferSizes(selector, out pathCount, out modeCount); if (err != WIN32STATUS.ERROR_SUCCESS) { SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: ERROR - GetDisplayConfigBufferSizes returned WIN32STATUS {err} when trying to get the maximum path and mode sizes again"); @@ -317,7 +328,7 @@ namespace DisplayMagicianShared.Windows SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Getting the current Display Config path and mode arrays"); paths = new DISPLAYCONFIG_PATH_INFO[pathCount]; modes = new DISPLAYCONFIG_MODE_INFO[modeCount]; - err = CCDImport.QueryDisplayConfig(QDC.QDC_ONLY_ACTIVE_PATHS, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero); + err = CCDImport.QueryDisplayConfig(selector, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero); if (err == WIN32STATUS.ERROR_INSUFFICIENT_BUFFER) { SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: ERROR - The displays were still modified between GetDisplayConfigBufferSizes and QueryDisplayConfig, even though we tried twice. Something is wrong."); @@ -995,7 +1006,7 @@ namespace DisplayMagicianShared.Windows { // Get the all possible windows display configs SharedLogger.logger.Trace($"WinLibrary/SetActiveConfig: Generating a list of all the current display configs"); - WINDOWS_DISPLAY_CONFIG allWindowsDisplayConfig = GetWindowsDisplayConfig(QDC.QDC_ALL_PATHS); + WINDOWS_DISPLAY_CONFIG allWindowsDisplayConfig = GetWindowsDisplayConfig(QDC.QDC_ALL_PATHS | QDC.QDC_INCLUDE_HMD); if (displayConfig.IsCloned) { @@ -1008,7 +1019,7 @@ namespace DisplayMagicianShared.Windows // Now we go through the Paths to update the LUIDs as per Soroush's suggestion SharedLogger.logger.Trace($"WinLibrary/SetActiveConfig: Patching the adapter IDs to make the saved config valid"); - PatchAdapterIDs(ref displayConfig, allWindowsDisplayConfig.DisplayAdapters); + PatchAdapterIDs(ref displayConfig); uint myPathsCount = (uint)displayConfig.DisplayConfigPaths.Length; uint myModesCount = (uint)displayConfig.DisplayConfigModes.Length; @@ -1275,7 +1286,7 @@ namespace DisplayMagicianShared.Windows public bool IsValidConfig(WINDOWS_DISPLAY_CONFIG displayConfig) { // Get the current windows display configs - WINDOWS_DISPLAY_CONFIG allWindowsDisplayConfig = GetWindowsDisplayConfig(QDC.QDC_ALL_PATHS); + WINDOWS_DISPLAY_CONFIG allWindowsDisplayConfig = GetWindowsDisplayConfig(QDC.QDC_ALL_PATHS | QDC.QDC_INCLUDE_HMD); SharedLogger.logger.Trace("WinLibrary/PatchAdapterIDs: Going through the list of adapters we stored in the config to make sure they still exist"); // Firstly check that the Adapter Names are still currently available (i.e. the adapter hasn't been replaced). @@ -1293,7 +1304,7 @@ namespace DisplayMagicianShared.Windows // Now we go through the Paths to update the LUIDs as per Soroush's suggestion SharedLogger.logger.Trace($"WinLibrary/IsPossibleConfig: Attemptong to patch the saved display configuration's adapter IDs so that it will still work (these change at each boot)"); - PatchAdapterIDs(ref displayConfig, allWindowsDisplayConfig.DisplayAdapters); + PatchAdapterIDs(ref displayConfig); SharedLogger.logger.Trace($"WinLibrary/IsPossibleConfig: Testing whether the display configuration is valid "); // Test whether a specified display configuration is supported on the computer @@ -1339,16 +1350,16 @@ namespace DisplayMagicianShared.Windows public List GetCurrentDisplayIdentifiers() { SharedLogger.logger.Trace($"WinLibrary/GetCurrentDisplayIdentifiers: Getting the current display identifiers for the displays in use now"); - return GetSomeDisplayIdentifiers(QDC.QDC_ONLY_ACTIVE_PATHS); + return GetSomeDisplayIdentifiers(QDC.QDC_ONLY_ACTIVE_PATHS | QDC.QDC_INCLUDE_HMD); } public List GetAllConnectedDisplayIdentifiers() { SharedLogger.logger.Trace($"WinLibrary/GetAllConnectedDisplayIdentifiers: Getting all the display identifiers that can possibly be used"); - return GetSomeDisplayIdentifiers(QDC.QDC_ALL_PATHS); + return GetSomeDisplayIdentifiers(QDC.QDC_ALL_PATHS | QDC.QDC_INCLUDE_HMD); } - private List GetSomeDisplayIdentifiers(QDC selector = QDC.QDC_ONLY_ACTIVE_PATHS) + private List GetSomeDisplayIdentifiers(QDC selector = QDC.QDC_ONLY_ACTIVE_PATHS | QDC.QDC_INCLUDE_HMD) { SharedLogger.logger.Debug($"WinLibrary/GetCurrentDisplayIdentifiers: Generating the unique Display Identifiers for the currently active configuration"); @@ -1545,7 +1556,7 @@ namespace DisplayMagicianShared.Windows // Get the size of the largest Active Paths and Modes arrays int pathCount = 0; int modeCount = 0; - WIN32STATUS err = CCDImport.GetDisplayConfigBufferSizes(QDC.QDC_ONLY_ACTIVE_PATHS, out pathCount, out modeCount); + WIN32STATUS err = CCDImport.GetDisplayConfigBufferSizes(QDC.QDC_ONLY_ACTIVE_PATHS | QDC.QDC_INCLUDE_HMD, out pathCount, out modeCount); if (err != WIN32STATUS.ERROR_SUCCESS) { SharedLogger.logger.Error($"WinLibrary/GetCurrentPCIVideoCardVendors: ERROR - GetDisplayConfigBufferSizes returned WIN32STATUS {err} when trying to get the maximum path and mode sizes"); @@ -1555,7 +1566,7 @@ namespace DisplayMagicianShared.Windows SharedLogger.logger.Trace($"WinLibrary/GetSomeDisplayIdentifiers: Getting the current Display Config path and mode arrays"); var paths = new DISPLAYCONFIG_PATH_INFO[pathCount]; var modes = new DISPLAYCONFIG_MODE_INFO[modeCount]; - err = CCDImport.QueryDisplayConfig(QDC.QDC_ONLY_ACTIVE_PATHS, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero); + err = CCDImport.QueryDisplayConfig(QDC.QDC_ONLY_ACTIVE_PATHS | QDC.QDC_INCLUDE_HMD, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero); if (err == WIN32STATUS.ERROR_INSUFFICIENT_BUFFER) { SharedLogger.logger.Warn($"WinLibrary/GetCurrentPCIVideoCardVendors: The displays were modified between GetDisplayConfigBufferSizes and QueryDisplayConfig so we need to get the buffer sizes again."); @@ -1571,7 +1582,7 @@ namespace DisplayMagicianShared.Windows SharedLogger.logger.Trace($"WinLibrary/GetSomeDisplayIdentifiers: Getting the current Display Config path and mode arrays"); paths = new DISPLAYCONFIG_PATH_INFO[pathCount]; modes = new DISPLAYCONFIG_MODE_INFO[modeCount]; - err = CCDImport.QueryDisplayConfig(QDC.QDC_ONLY_ACTIVE_PATHS, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero); + err = CCDImport.QueryDisplayConfig(QDC.QDC_ONLY_ACTIVE_PATHS | QDC.QDC_INCLUDE_HMD, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero); if (err == WIN32STATUS.ERROR_INSUFFICIENT_BUFFER) { SharedLogger.logger.Error($"WinLibrary/GetCurrentPCIVideoCardVendors: ERROR - The displays were still modified between GetDisplayConfigBufferSizes and QueryDisplayConfig, even though we tried twice. Something is wrong."); @@ -1649,6 +1660,91 @@ namespace DisplayMagicianShared.Windows } + public Dictionary GetCurrentAdapterIDs() + { + SharedLogger.logger.Trace($"WinLibrary/GetCurrentAdapterIDs: Getting the current adapter ids for the videocards Windows knows about"); + Dictionary currentAdapterMap = new Dictionary(); + + SharedLogger.logger.Trace($"WinLibrary/GetCurrentAdapterIDs: Testing whether the display configuration is valid (allowing tweaks)."); + // Get the size of the largest All Paths and Modes arrays + int pathCount = 0; + int modeCount = 0; + WIN32STATUS err = CCDImport.GetDisplayConfigBufferSizes(QDC.QDC_ALL_PATHS | QDC.QDC_INCLUDE_HMD, out pathCount, out modeCount); + if (err != WIN32STATUS.ERROR_SUCCESS) + { + SharedLogger.logger.Error($"WinLibrary/GetCurrentAdapterIDs: ERROR - GetDisplayConfigBufferSizes returned WIN32STATUS {err} when trying to get the maximum path and mode sizes"); + throw new WinLibraryException($"GetCurrentAdapterIDs returned WIN32STATUS {err} when trying to get the maximum path and mode sizes"); + } + + SharedLogger.logger.Trace($"WinLibrary/GetCurrentAdapterIDs: Getting the current Display Config path and mode arrays"); + var paths = new DISPLAYCONFIG_PATH_INFO[pathCount]; + var modes = new DISPLAYCONFIG_MODE_INFO[modeCount]; + err = CCDImport.QueryDisplayConfig(QDC.QDC_ALL_PATHS | QDC.QDC_INCLUDE_HMD, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero); + if (err == WIN32STATUS.ERROR_INSUFFICIENT_BUFFER) + { + SharedLogger.logger.Warn($"WinLibrary/GetCurrentAdapterIDs: The displays were modified between GetDisplayConfigBufferSizes and QueryDisplayConfig so we need to get the buffer sizes again."); + SharedLogger.logger.Trace($"WinLibrary/GetCurrentAdapterIDs: Getting the size of the largest Active Paths and Modes arrays"); + // Screen changed in between GetDisplayConfigBufferSizes and QueryDisplayConfig, so we need to get buffer sizes again + // as per https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-querydisplayconfig + err = CCDImport.GetDisplayConfigBufferSizes(QDC.QDC_ALL_PATHS, out pathCount, out modeCount); + if (err != WIN32STATUS.ERROR_SUCCESS) + { + SharedLogger.logger.Error($"WinLibrary/GetCurrentAdapterIDs: ERROR - GetDisplayConfigBufferSizes returned WIN32STATUS {err} when trying to get the maximum path and mode sizes again"); + throw new WinLibraryException($"GetDisplayConfigBufferSizes returned WIN32STATUS {err} when trying to get the maximum path and mode sizes again"); + } + SharedLogger.logger.Trace($"WinLibrary/GetCurrentAdapterIDs: Getting the current Display Config path and mode arrays"); + paths = new DISPLAYCONFIG_PATH_INFO[pathCount]; + modes = new DISPLAYCONFIG_MODE_INFO[modeCount]; + err = CCDImport.QueryDisplayConfig(QDC.QDC_ALL_PATHS | QDC.QDC_INCLUDE_HMD, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero); + if (err == WIN32STATUS.ERROR_INSUFFICIENT_BUFFER) + { + SharedLogger.logger.Error($"WinLibrary/GetCurrentAdapterIDs: ERROR - The displays were still modified between GetDisplayConfigBufferSizes and QueryDisplayConfig, even though we tried twice. Something is wrong."); + throw new WinLibraryException($"The displays were still modified between GetDisplayConfigBufferSizes and QueryDisplayConfig, even though we tried twice. Something is wrong."); + } + else if (err != WIN32STATUS.ERROR_SUCCESS) + { + SharedLogger.logger.Error($"WinLibrary/GetCurrentAdapterIDs: ERROR - QueryDisplayConfig returned WIN32STATUS {err} when trying to query all available displays again"); + throw new WinLibraryException($"QueryDisplayConfig returned WIN32STATUS {err} when trying to query all available displays again."); + } + } + else if (err != WIN32STATUS.ERROR_SUCCESS) + { + SharedLogger.logger.Error($"WinLibrary/GetCurrentAdapterIDs: ERROR - QueryDisplayConfig returned WIN32STATUS {err} when trying to query all available displays"); + throw new WinLibraryException($"QueryDisplayConfig returned WIN32STATUS {err} when trying to query all available displays."); + } + + foreach (var path in paths) + { + if (path.TargetInfo.TargetAvailable == false) + { + // We want to skip this one cause it's not valid + SharedLogger.logger.Trace($"WinLibrary/GetCurrentAdapterIDs: Skipping path due to TargetAvailable not existing in display #{path.TargetInfo.Id}"); + continue; + } + + // get display adapter name + var adapterInfo = new DISPLAYCONFIG_ADAPTER_NAME(); + adapterInfo.Header.Type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME; + adapterInfo.Header.Size = (uint)Marshal.SizeOf(); + adapterInfo.Header.AdapterId = path.TargetInfo.AdapterId; + adapterInfo.Header.Id = path.TargetInfo.Id; + err = CCDImport.DisplayConfigGetDeviceInfo(ref adapterInfo); + if (err == WIN32STATUS.ERROR_SUCCESS) + { + SharedLogger.logger.Trace($"WinLibrary/GetCurrentAdapterIDs: Successfully got the display name info from {path.TargetInfo.Id}."); + currentAdapterMap[path.TargetInfo.AdapterId.Value] = adapterInfo.AdapterDevicePath; + } + else + { + SharedLogger.logger.Warn($"WinLibrary/GetCurrentAdapterIDs: WARNING - DisplayConfigGetDeviceInfo returned WIN32STATUS {err} when trying to get the target info for display #{path.TargetInfo.Id}"); + } + + } + + return currentAdapterMap; + + } + public static bool GDISettingsEqual(Dictionary gdi1, Dictionary gdi2) { if (gdi1.Count == gdi2.Count)