using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using WindowsDisplayAPI.Exceptions; using WindowsDisplayAPI.Native; using WindowsDisplayAPI.Native.DisplayConfig; using WindowsDisplayAPI.Native.DisplayConfig.Structures; using WindowsDisplayAPI.Native.Structures; namespace WindowsDisplayAPI.DisplayConfig { /// /// Represents a path root information /// public class PathInfo { private readonly uint _cloneGroupId; private readonly DisplayConfigPixelFormat _pixelFormat; private readonly Point _position; private readonly Size _resolution; /// /// Creates a new PathInfo /// /// The display source /// The display position in desktop /// The display resolution /// The display pixel format public PathInfo( PathDisplaySource displaySource, Point position, Size resolution, DisplayConfigPixelFormat pixelFormat ) : this(displaySource) { _position = position; _resolution = resolution; _pixelFormat = pixelFormat; IsModeInformationAvailable = true; } /// /// Creates a new PathInfo /// /// The display source /// The display position in desktop /// The display resolution /// The display pixel format /// The display clone group, only valid for virtual aware paths public PathInfo( PathDisplaySource displaySource, Point position, Size resolution, DisplayConfigPixelFormat pixelFormat, uint cloneGroup ) : this(displaySource, cloneGroup) { _position = position; _resolution = resolution; _pixelFormat = pixelFormat; IsModeInformationAvailable = true; } /// /// Creates a new PathInfo /// /// The display source /// The display position in desktop /// The display resolution /// The display pixel format /// An array of target information public PathInfo( PathDisplaySource displaySource, Point position, Size resolution, DisplayConfigPixelFormat pixelFormat, PathTargetInfo[] pathTargetInfos ) : this(displaySource, position, resolution, pixelFormat) { TargetsInfo = pathTargetInfos; } /// /// Creates a new PathInfo /// /// The display source /// The display position in desktop /// The display resolution /// The display pixel format /// An array of target information /// The display clone group, only valid for virtual aware paths public PathInfo( PathDisplaySource displaySource, Point position, Size resolution, DisplayConfigPixelFormat pixelFormat, PathTargetInfo[] pathTargetInfos, uint cloneGroup ) : this(displaySource, position, resolution, pixelFormat, cloneGroup) { TargetsInfo = pathTargetInfos; } /// /// Creates a new PathInfo /// /// The display source public PathInfo(PathDisplaySource displaySource) { DisplaySource = displaySource; } /// /// Creates a new PathInfo /// /// The display source /// The display clone group, only valid for virtual aware paths public PathInfo(PathDisplaySource displaySource, uint cloneGroup) : this(displaySource) { IsCloneMember = true; _cloneGroupId = cloneGroup; } /// /// Creates a new PathInfo /// /// The display source /// An array of target information public PathInfo( PathDisplaySource displaySource, PathTargetInfo[] pathTargetInfos ) : this(displaySource) { TargetsInfo = pathTargetInfos; } /// /// Creates a new PathInfo /// /// The display source /// An array of target information /// The display clone group, only valid for virtual aware paths public PathInfo( PathDisplaySource displaySource, PathTargetInfo[] pathTargetInfos, uint cloneGroup ) : this(displaySource, cloneGroup) { TargetsInfo = pathTargetInfos; } private PathInfo( DisplayConfigPathSourceInfo sourceInfo, DisplayConfigSourceMode? sourceMode, IEnumerable< Tuple< DisplayConfigPathInfoFlags, DisplayConfigPathTargetInfo, DisplayConfigTargetMode?, DisplayConfigDesktopImageInfo? > > targets ) { DisplaySource = new PathDisplaySource(new PathDisplayAdapter(sourceInfo.AdapterId), sourceInfo.SourceId); IsInUse = sourceInfo.StatusFlags.HasFlag(DisplayConfigPathSourceInfoFlags.InUse); IsModeInformationAvailable = sourceMode.HasValue; if (sourceMode.HasValue) { _resolution = new Size((int) sourceMode.Value.Width, (int) sourceMode.Value.Height); _pixelFormat = sourceMode.Value.PixelFormat; _position = new Point(sourceMode.Value.Position.X, sourceMode.Value.Position.Y); } TargetsInfo = targets.Select(t => new PathTargetInfo(t.Item1, t.Item2, t.Item3, t.Item4)).ToArray(); if (TargetsInfo.Any(info => info.IsVirtualModeSupportedByPath) && sourceInfo.CloneGroupId != DisplayConfigPathSourceInfo.InvalidCloneGroupId ) { _cloneGroupId = sourceInfo.CloneGroupId; IsCloneMember = true; } } /// /// Gets a valid identifier used to show which clone group the path is a member of /// /// This path is not a clone member public uint CloneGroupId { get { if (!IsCloneMember) { throw new NotACloneMemberException( "The display source is not part of a clone group." ); } return _cloneGroupId; } } /// /// Gets extra information about the representing display source /// public PathDisplaySource DisplaySource { get; } /// /// Gets a boolean value indicating if this path is a member of a clone group /// public bool IsCloneMember { get; } /// /// Gets a boolean value indicating if this path is the primary GDI path /// /// Source mode information is missing public bool IsGDIPrimary { get => Position.IsEmpty; } /// /// Gets a boolean value indicating if the source is in use by at least one active path /// public bool IsInUse { get; } /// /// Gets a boolean value indicating if the source mode information is available /// public bool IsModeInformationAvailable { get; } /// /// Gets a boolean value indicating the DisplayConfig (CCD API) availability on this system /// public static bool IsSupported { get { try { return DisplayConfigApi.GetDisplayConfigBufferSizes( QueryDeviceConfigFlags.AllPaths, out _, out _ ) == Win32Status.Success; } catch { return false; } } } /// /// Gets a boolean value indicating the virtual display mode support on this system /// public static bool IsVirtualModeSupported { get { try { return PathDisplayTarget .GetDisplayTargets() .Any(t => t.VirtualResolutionSupport); } catch { return false; } } } /// /// Gets the specifies the pixel format of the source mode /// /// Source mode information is missing public DisplayConfigPixelFormat PixelFormat { get { if (!IsModeInformationAvailable) { throw new MissingModeException( "Source mode information is missing or not available.", DisplayConfigModeInfoType.Source ); } return _pixelFormat; } } /// /// Gets the position in the desktop coordinate space of the upper-left corner of this source surface. The source /// surface that is located at (0, 0) is always the primary source surface. /// /// Source mode information is missing public Point Position { get { if (!IsModeInformationAvailable) { throw new MissingModeException( "Source mode information is missing or not available.", DisplayConfigModeInfoType.Source ); } return _position; } } /// /// Gets the size of the source mode /// /// Source mode information is missing public Size Resolution { get { if (!IsModeInformationAvailable) { throw new MissingModeException( "Source mode information is missing or not available.", DisplayConfigModeInfoType.Source ); } return _resolution; } } /// /// Gets the list of target information /// public PathTargetInfo[] TargetsInfo { get; } = new PathTargetInfo[0]; /// /// Applies an array of paths /// /// The array of paths /// true to allow changes and reordering of the provided paths, otherwise false /// true to save the paths to the persistence database if call succeed, otherwise false /// true to force driver mode enumeration before applying the paths /// Error in changing paths public static void ApplyPathInfos( IEnumerable pathInfos, bool allowChanges = true, bool saveToDatabase = false, bool forceModeEnumeration = false ) { var pathInfosArray = pathInfos.ToArray(); if (!ValidatePathInfos(pathInfosArray, allowChanges)) { throw new PathChangeException("Invalid paths information."); } var displayConfigPathInfos = GetDisplayConfigPathInfos(pathInfosArray, out var displayConfigModeInfos); if (displayConfigPathInfos.Length <= 0) { return; } var flags = displayConfigModeInfos.Length == 0 ? SetDisplayConfigFlags.TopologySupplied : SetDisplayConfigFlags.UseSuppliedDisplayConfig; if (allowChanges) { flags |= displayConfigModeInfos.Length == 0 ? SetDisplayConfigFlags.AllowPathOrderChanges : SetDisplayConfigFlags.AllowChanges; } else if (displayConfigModeInfos.Length > 0) { flags |= SetDisplayConfigFlags.NoOptimization; } if (saveToDatabase && displayConfigModeInfos.Length > 0) { flags |= SetDisplayConfigFlags.SaveToDatabase; } if (forceModeEnumeration && displayConfigModeInfos.Length > 0) { flags |= SetDisplayConfigFlags.ForceModeEnumeration; } var result = DisplayConfigApi.SetDisplayConfig( (uint) displayConfigPathInfos.Length, displayConfigPathInfos, (uint) displayConfigModeInfos.Length, displayConfigModeInfos.Length > 0 ? displayConfigModeInfos : null, SetDisplayConfigFlags.Apply | flags ); if (result != Win32Status.Success) { throw new PathChangeException( "An error occurred while applying the paths information.", new Win32Exception((int) result) ); } } /// /// Applies a saved topology /// /// The topology identification to apply /// true to allows persistence of the changes, otherwise false /// Error in changing paths public static void ApplyTopology(DisplayConfigTopologyId topology, bool allowPersistence = false) { if (!ValidateTopology(topology)) { throw new PathChangeException("Invalid topology request."); } var flags = (SetDisplayConfigFlags) topology; if (allowPersistence) { flags |= SetDisplayConfigFlags.PathPersistIfRequired; } var result = DisplayConfigApi.SetDisplayConfig( 0, null, 0, null, SetDisplayConfigFlags.Apply | flags ); if (result != Win32Status.Success) { throw new PathChangeException( "An error occurred while applying the requested topology.", new Win32Exception((int) result) ); } } /// /// Retrieves the list of active paths /// /// true if the caller expects virtual mode settings, otherwise false /// An array of PathInfos public static PathInfo[] GetActivePaths(bool virtualModeAware = false) { return GetPathInfos( virtualModeAware ? QueryDeviceConfigFlags.OnlyActivePaths | QueryDeviceConfigFlags.VirtualModeAware : QueryDeviceConfigFlags.OnlyActivePaths, out _ ); } /// /// Retrieves the list of all paths, active or inactive /// /// true if the caller expects virtual mode settings, otherwise false /// An array of PathInfos public static PathInfo[] GetAllPaths(bool virtualModeAware = false) { return GetPathInfos( virtualModeAware ? QueryDeviceConfigFlags.AllPaths | QueryDeviceConfigFlags.VirtualModeAware : QueryDeviceConfigFlags.AllPaths, out _ ); } /// /// Retrieves the list of currently active topology paths /// /// An array of PathInfos public static PathInfo[] GetCurrentDatabasePaths() { return GetPathInfos(QueryDeviceConfigFlags.DatabaseCurrent, out _); } /// /// Gets the current active topology identification /// /// The topology identification public static DisplayConfigTopologyId GetCurrentTopology() { GetPathInfos(QueryDeviceConfigFlags.DatabaseCurrent, out var currentDatabaseType); return currentDatabaseType; } /// /// Validates an array of paths before applying /// /// The array of paths /// true to allow changes and reordering of the provided paths, otherwise false /// true if the provided paths are valid, otherwise false public static bool ValidatePathInfos(IEnumerable pathInfos, bool allowChanges = true) { var displayConfigPathInfos = GetDisplayConfigPathInfos(pathInfos, out var displayConfigModeInfos); if (displayConfigPathInfos.Length <= 0) { return false; } var flags = displayConfigModeInfos.Length == 0 ? SetDisplayConfigFlags.TopologySupplied : SetDisplayConfigFlags.UseSuppliedDisplayConfig; if (allowChanges) { flags |= displayConfigModeInfos.Length == 0 ? SetDisplayConfigFlags.AllowPathOrderChanges : SetDisplayConfigFlags.AllowChanges; } return DisplayConfigApi.SetDisplayConfig( (uint) displayConfigPathInfos.Length, displayConfigPathInfos, (uint) displayConfigModeInfos.Length, displayConfigModeInfos.Length > 0 ? displayConfigModeInfos : null, SetDisplayConfigFlags.Validate | flags ) == Win32Status.Success; } /// /// Validates a topology before applying /// /// The topology identification /// true if topology is applicable, otherwise false /// public static bool ValidateTopology(DisplayConfigTopologyId topology) { if (topology == DisplayConfigTopologyId.None) { throw new ArgumentOutOfRangeException(nameof(topology), "Topology should not be empty."); } var flags = (SetDisplayConfigFlags) topology; return DisplayConfigApi.SetDisplayConfig( 0, null, 0, null, SetDisplayConfigFlags.Validate | flags ) == Win32Status.Success; } private static uint AddMode(ref List modes, DisplayConfigModeInfo mode) { var existingMode = modes.FindIndex(info => info.InfoType == mode.InfoType && info.AdapterId == mode.AdapterId && info.Id == mode.Id ); if (existingMode > 0) { if (modes[existingMode] == mode) { return (uint) existingMode; } throw new DuplicateModeException( "Provided list of path information, contains one or more duplicate but not identical modes." ); } modes.Add(mode); return (uint) modes.Count - 1; } // ReSharper disable once CyclomaticComplexity private static DisplayConfigPathInfo[] GetDisplayConfigPathInfos( IEnumerable pathInfos, out DisplayConfigModeInfo[] modeInfos) { var displayConfigPathInfos = new List(); var displayConfigModeInfos = new List(); foreach (var pathInfo in pathInfos) { var sourceMode = pathInfo.GetDisplayConfigSourceMode(); var sourceModeIndex = sourceMode.HasValue ? AddMode( ref displayConfigModeInfos, new DisplayConfigModeInfo( pathInfo.DisplaySource.Adapter.AdapterId, pathInfo.DisplaySource.SourceId, sourceMode.Value ) ) : 0u; var sourceInfo = pathInfo.IsCloneMember ? new DisplayConfigPathSourceInfo( pathInfo.DisplaySource.Adapter.AdapterId, pathInfo.DisplaySource.SourceId, sourceMode.HasValue ? (ushort) sourceModeIndex : DisplayConfigSourceMode.InvalidSourceModeIndex, (ushort) pathInfo.CloneGroupId ) : new DisplayConfigPathSourceInfo( pathInfo.DisplaySource.Adapter.AdapterId, pathInfo.DisplaySource.SourceId, sourceMode.HasValue ? sourceModeIndex : DisplayConfigModeInfo.InvalidModeIndex ); if (pathInfo.TargetsInfo == null || pathInfo.TargetsInfo.Length == 0) { displayConfigPathInfos.Add( new DisplayConfigPathInfo(sourceInfo, DisplayConfigPathInfoFlags.Active ) ); } else { foreach (var target in pathInfo.TargetsInfo) { var flags = DisplayConfigPathInfoFlags.None; if (target.IsPathActive) { flags |= DisplayConfigPathInfoFlags.Active; } if (target.IsVirtualModeSupportedByPath) { flags |= DisplayConfigPathInfoFlags.SupportVirtualMode; } var targetMode = target.GetDisplayConfigTargetMode(); var targetModeIndex = targetMode.HasValue ? AddMode(ref displayConfigModeInfos, new DisplayConfigModeInfo( target.DisplayTarget.Adapter.AdapterId, target.DisplayTarget.TargetId, targetMode.Value ) ) : 0u; DisplayConfigPathTargetInfo targetInfo; if (target.IsVirtualModeSupportedByPath) { sourceInfo = new DisplayConfigPathSourceInfo( pathInfo.DisplaySource.Adapter.AdapterId, pathInfo.DisplaySource.SourceId, sourceMode.HasValue ? (ushort) sourceModeIndex : DisplayConfigSourceMode.InvalidSourceModeIndex, pathInfo.IsCloneMember ? (ushort) pathInfo.CloneGroupId : DisplayConfigPathSourceInfo.InvalidCloneGroupId ); var desktopMode = target.GetDisplayConfigDesktopImageInfo(); var desktopModeIndex = desktopMode.HasValue ? AddMode(ref displayConfigModeInfos, new DisplayConfigModeInfo( target.DisplayTarget.Adapter.AdapterId, target.DisplayTarget.TargetId, desktopMode.Value ) ) : 0u; targetInfo = new DisplayConfigPathTargetInfo( target.DisplayTarget.Adapter.AdapterId, target.DisplayTarget.TargetId, targetMode.HasValue ? (ushort) targetModeIndex : DisplayConfigTargetMode.InvalidTargetModeIndex, desktopMode.HasValue ? (ushort) desktopModeIndex : DisplayConfigDesktopImageInfo.InvalidDesktopImageModeIndex, target.OutputTechnology, target.Rotation, target.Scaling, target.ScanLineOrdering == DisplayConfigScanLineOrdering.NotSpecified ? new DisplayConfigRational() : new DisplayConfigRational(target.FrequencyInMillihertz, 1000, true), target.ScanLineOrdering, true ); } else { targetInfo = new DisplayConfigPathTargetInfo( target.DisplayTarget.Adapter.AdapterId, target.DisplayTarget.TargetId, targetMode.HasValue ? targetModeIndex : DisplayConfigModeInfo.InvalidModeIndex, target.OutputTechnology, target.Rotation, target.Scaling, target.ScanLineOrdering == DisplayConfigScanLineOrdering.NotSpecified ? new DisplayConfigRational() : new DisplayConfigRational(target.FrequencyInMillihertz, 1000, true), target.ScanLineOrdering, true ); } displayConfigPathInfos.Add(new DisplayConfigPathInfo(sourceInfo, targetInfo, flags)); } } } modeInfos = displayConfigModeInfos.ToArray(); return displayConfigPathInfos.ToArray(); } private static PathInfo[] GetPathInfos(QueryDeviceConfigFlags flags, out DisplayConfigTopologyId topologyId) { DisplayConfigPathInfo[] displayPaths; DisplayConfigModeInfo[] displayModes; uint pathCount; while (true) { var error = DisplayConfigApi.GetDisplayConfigBufferSizes(flags, out pathCount, out var modeCount); if (error != Win32Status.Success) { throw new Win32Exception((int) error); } displayPaths = new DisplayConfigPathInfo[pathCount]; displayModes = new DisplayConfigModeInfo[modeCount]; if (flags == QueryDeviceConfigFlags.DatabaseCurrent) { error = DisplayConfigApi.QueryDisplayConfig( flags, ref pathCount, displayPaths, ref modeCount, displayModes, out topologyId ); } else { topologyId = DisplayConfigTopologyId.None; error = DisplayConfigApi.QueryDisplayConfig( flags, ref pathCount, displayPaths, ref modeCount, displayModes, IntPtr.Zero ); } if (error == Win32Status.Success) { break; } if (error != Win32Status.ErrorInsufficientBuffer) { throw new Win32Exception((int) error); } } var pathInfos = new Dictionary< uint, Tuple< DisplayConfigPathSourceInfo, DisplayConfigSourceMode?, List< Tuple< DisplayConfigPathInfoFlags, DisplayConfigPathTargetInfo, DisplayConfigTargetMode?, DisplayConfigDesktopImageInfo? > > > >(); var sourceId = uint.MaxValue; for (var i = 0u; i < pathCount; i++) { var displayPath = displayPaths[i]; DisplayConfigSourceMode? sourceMode = null; var key = sourceId; var isVirtualSupported = displayPath.Flags.HasFlag(DisplayConfigPathInfoFlags.SupportVirtualMode); if (isVirtualSupported && displayPath.SourceInfo.SourceModeInfoIndex != DisplayConfigSourceMode.InvalidSourceModeIndex && displayModes[displayPath.SourceInfo.SourceModeInfoIndex].InfoType == DisplayConfigModeInfoType.Source) { sourceMode = displayModes[displayPath.SourceInfo.SourceModeInfoIndex].SourceMode; key = displayPath.SourceInfo.SourceModeInfoIndex; } else if (!isVirtualSupported && displayPath.SourceInfo.ModeInfoIndex != DisplayConfigModeInfo.InvalidModeIndex && displayModes[displayPath.SourceInfo.ModeInfoIndex].InfoType == DisplayConfigModeInfoType.Source) { sourceMode = displayModes[displayPath.SourceInfo.ModeInfoIndex].SourceMode; key = displayPath.SourceInfo.ModeInfoIndex; } else { sourceId--; } if (!pathInfos.ContainsKey(key)) { pathInfos.Add( key, Tuple.Create( displayPath.SourceInfo, sourceMode, new List< Tuple< DisplayConfigPathInfoFlags, DisplayConfigPathTargetInfo, DisplayConfigTargetMode?, DisplayConfigDesktopImageInfo? > >() ) ); } DisplayConfigTargetMode? targetMode = null; if (isVirtualSupported && displayPath.TargetInfo.TargetModeInfoIndex != DisplayConfigTargetMode.InvalidTargetModeIndex && displayModes[displayPath.TargetInfo.TargetModeInfoIndex].InfoType == DisplayConfigModeInfoType.Target ) { targetMode = displayModes[displayPath.TargetInfo.TargetModeInfoIndex].TargetMode; } else if (!isVirtualSupported && displayPath.TargetInfo.ModeInfoIndex != DisplayConfigModeInfo.InvalidModeIndex && displayModes[displayPath.TargetInfo.ModeInfoIndex].InfoType == DisplayConfigModeInfoType.Target ) { targetMode = displayModes[displayPath.TargetInfo.ModeInfoIndex].TargetMode; } DisplayConfigDesktopImageInfo? desktopImageMode = null; if (isVirtualSupported && displayPath.TargetInfo.DesktopModeInfoIndex != DisplayConfigDesktopImageInfo.InvalidDesktopImageModeIndex && displayModes[displayPath.TargetInfo.DesktopModeInfoIndex].InfoType == DisplayConfigModeInfoType.DesktopImage) { desktopImageMode = displayModes[displayPath.TargetInfo.DesktopModeInfoIndex].DesktopImageInfo; } pathInfos[key].Item3.Add( Tuple.Create(displayPath.Flags, displayPath.TargetInfo, targetMode, desktopImageMode) ); } return pathInfos.Select( pair => new PathInfo(pair.Value.Item1, pair.Value.Item2, pair.Value.Item3) ).ToArray(); } /// public override string ToString() { return $"{DisplaySource}: {Resolution} @ {Position}"; } private DisplayConfigSourceMode? GetDisplayConfigSourceMode() { if (IsModeInformationAvailable) { return new DisplayConfigSourceMode( (uint) Resolution.Width, (uint) Resolution.Height, PixelFormat, new PointL(Position) ); } return null; } } }