Gamma Init

This commit is contained in:
Serge
2024-02-16 15:55:37 +01:00
parent 42a598f177
commit cf84fa0616
103 changed files with 7907 additions and 41 deletions

View File

@@ -0,0 +1,11 @@
namespace WindowsDisplayAPI
{
public enum ColorDepth
{
Depth4Bit = 4,
Depth8Bit = 8,
Depth16Bit = 16, // 0x00000010
Depth24Bit = 24, // 0x00000018
Depth32Bit = 32 // 0x00000020
}
}

View File

@@ -0,0 +1,104 @@
namespace WindowsDisplayAPI
{
/// <summary>
/// Represents a Windows Video Device including Display Devices and Video Controllers
/// </summary>
public abstract class Device
{
/// <summary>
/// Creates a new Device
/// </summary>
/// <param name="devicePath">The device path</param>
/// <param name="deviceName">The device name</param>
/// <param name="deviceKey">The device driver registry key</param>
protected Device(string devicePath, string deviceName, string deviceKey)
{
DevicePath = devicePath;
DeviceName = deviceName;
DeviceKey = deviceKey;
}
/// <summary>
/// Gets the registry address of the device driver and configuration
/// </summary>
public virtual string DeviceKey { get; }
/// <summary>
/// Gets the Windows device name
/// </summary>
public virtual string DeviceName { get; }
/// <summary>
/// Gets the Windows device path
/// </summary>
public virtual string DevicePath { get; }
/// <inheritdoc />
public override string ToString()
{
return $"{GetType().Name}: {DeviceName}";
}
#if !NETSTANDARD
/// <summary>
/// Opens the registry key at the address specified by the DeviceKey property
/// </summary>
/// <returns>A RegistryKey instance for successful call, otherwise null</returns>
/// <exception cref="WindowsDisplayAPI.Exceptions.InvalidRegistryAddressException">Registry address is invalid or unknown.</exception>
public Microsoft.Win32.RegistryKey OpenDeviceKey()
{
if (string.IsNullOrWhiteSpace(DeviceKey)) {
return null;
}
const string machineRootName = "\\Registry\\Machine\\";
const string userRootName = "\\Registry\\Current\\";
if (DeviceKey.StartsWith(machineRootName, System.StringComparison.InvariantCultureIgnoreCase)) {
return Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
DeviceKey.Substring(machineRootName.Length),
Microsoft.Win32.RegistryKeyPermissionCheck.ReadSubTree
);
}
if (DeviceKey.StartsWith(userRootName, System.StringComparison.InvariantCultureIgnoreCase)) {
return Microsoft.Win32.Registry.Users.OpenSubKey(
DeviceKey.Substring(userRootName.Length),
Microsoft.Win32.RegistryKeyPermissionCheck.ReadSubTree
);
}
throw new Exceptions.InvalidRegistryAddressException("Registry address is invalid or unknown.");
}
/// <summary>
/// Opens the registry key of the Windows PnP manager for this device
/// </summary>
/// <returns>A RegistryKey instance for successful call, otherwise null</returns>
public Microsoft.Win32.RegistryKey OpenDevicePnPKey()
{
if (string.IsNullOrWhiteSpace(DevicePath)) {
return null;
}
var path = DevicePath;
if (path.StartsWith("\\\\?\\"))
{
path = path.Substring(4).Replace("#", "\\");
if (path.EndsWith("}"))
{
var guidIndex = path.LastIndexOf("{", System.StringComparison.InvariantCulture);
if (guidIndex > 0) {
path = path.Substring(0, guidIndex);
}
}
}
return Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
"SYSTEM\\CurrentControlSet\\Enum\\" + path,
Microsoft.Win32.RegistryKeyPermissionCheck.ReadSubTree
);
}
#endif
}
}

View File

@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Linq;
using WindowsDisplayAPI.Exceptions;
using WindowsDisplayAPI.Native;
using WindowsDisplayAPI.Native.DeviceContext;
using WindowsDisplayAPI.Native.DeviceContext.Structures;
namespace WindowsDisplayAPI
{
/// <summary>
/// Represents a Windows Attached Display Device
/// </summary>
public class Display : DisplayDevice
{
/// <summary>
/// Creates a new Display
/// </summary>
/// <param name="device">The DisplayDevice instance to copy information from</param>
protected Display(DisplayDevice device)
: base(
device.DevicePath,
device.DeviceName,
device.DeviceKey,
device.Adapter,
device.IsAvailable,
false
)
{
}
/// <summary>
/// Gets the display capabilities.
/// </summary>
public MonitorCapabilities Capabilities
{
get
{
var handle = DCHandle.CreateFromDevice(ScreenName, DevicePath);
if (!IsValid || handle?.IsInvalid != false)
{
throw new InvalidDisplayException(DevicePath);
}
return new MonitorCapabilities(handle);
}
}
/// <inheritdoc />
public override string DisplayName
{
get
{
if (IsValid)
{
return DisplayAdapter.GetDisplayAdapters()
.SelectMany(adapter => adapter.GetDisplayDevices(base.IsAvailable))
.FirstOrDefault(
device => device.DevicePath.Equals(DevicePath) && device.DeviceKey.Equals(DeviceKey)
)?.DisplayName;
}
return ToUnAttachedDisplay()?.DisplayName;
}
}
/// <summary>
/// Gets or sets the display gamma ramp look up table.
/// </summary>
public DisplayGammaRamp GammaRamp
{
get
{
var handle = DCHandle.CreateFromDevice(ScreenName, DevicePath);
if (!IsValid || handle?.IsInvalid != false)
{
throw new InvalidDisplayException(DevicePath);
}
var gammaRamp = new GammaRamp();
return DeviceContextApi.GetDeviceGammaRamp(handle, ref gammaRamp)
? new DisplayGammaRamp(gammaRamp)
: null;
}
set
{
var handle = DCHandle.CreateFromDevice(ScreenName, DevicePath);
if (!IsValid || handle?.IsInvalid != false)
{
throw new InvalidDisplayException(DevicePath);
}
var gammaRamp = value.AsRamp();
if (!DeviceContextApi.SetDeviceGammaRamp(handle, ref gammaRamp))
{
//throw new ArgumentException("Invalid argument or value passed.", nameof(value));
}
}
}
/// <inheritdoc />
public override bool IsAvailable
{
get => base.IsAvailable && IsValid;
}
/// <inheritdoc />
public override bool IsValid
{
get
{
return DisplayAdapter.GetDisplayAdapters()
.SelectMany(adapter => adapter.GetDisplayDevices(base.IsAvailable))
.Any(
device => device.DevicePath.Equals(DevicePath) && device.DeviceKey.Equals(DeviceKey)
);
}
}
/// <inheritdoc />
public override string ScreenName
{
get
{
if (IsValid)
{
return
DisplayAdapter.GetDisplayAdapters()
.SelectMany(adapter => adapter.GetDisplayDevices(base.IsAvailable))
.FirstOrDefault(
device => device.DevicePath.Equals(DevicePath) && device.DeviceKey.Equals(DeviceKey)
)?.ScreenName;
}
return ToUnAttachedDisplay()?.ScreenName;
}
}
/// <summary>
/// Returns a list of all attached displays on this machine
/// </summary>
/// <returns>An enumerable list of Displays</returns>
public static IEnumerable<Display> GetDisplays()
{
return DisplayAdapter.GetDisplayAdapters()
.SelectMany(adapter => adapter.GetDisplayDevices(true))
.Select(device => new Display(device));
}
/// <inheritdoc />
public override string ToString()
{
return IsValid ? $"{GetType().Name}: {DisplayName} ({DeviceName})" : $"{GetType().Name}: Invalid";
}
/// <summary>
/// Returns the corresponding UnAttachedDisplay device for this display. Only valid when this instance is invalidated
/// due to display detachment.
/// </summary>
/// <returns></returns>
public UnAttachedDisplay ToUnAttachedDisplay()
{
if (IsValid)
{
return null;
}
return UnAttachedDisplay.GetUnAttachedDisplays()
.FirstOrDefault(
display => display.DevicePath.Equals(DevicePath) && display.DeviceKey.Equals(DeviceKey)
);
}
}
}

View File

@@ -0,0 +1,123 @@
using System.Collections.Generic;
using System.Linq;
using WindowsDisplayAPI.DisplayConfig;
using WindowsDisplayAPI.Native;
namespace WindowsDisplayAPI
{
/// <summary>
/// Represents a Windows Video Controller Display Adapter Device
/// </summary>
public class DisplayAdapter : Device
{
/// <summary>
/// Creates a new DisplayAdapter
/// </summary>
/// <param name="devicePath">The device path</param>
/// <param name="deviceName">The device name</param>
/// <param name="deviceKey">The device driver registry key</param>
protected DisplayAdapter(string devicePath, string deviceName, string deviceKey)
: base(devicePath, deviceName, deviceKey)
{
}
/// <summary>
/// Returns a list of all display adapters on this machine
/// </summary>
/// <returns>An enumerable list of DisplayAdapters</returns>
public static IEnumerable<DisplayAdapter> GetDisplayAdapters()
{
var device = Native.DeviceContext.Structures.DisplayDevice.Initialize();
var deviceIds = new List<string>();
for (uint i = 0; DeviceContextApi.EnumDisplayDevices(null, i, ref device, 0); i++)
{
if (!deviceIds.Contains(device.DeviceId))
{
deviceIds.Add(device.DeviceId);
yield return new DisplayAdapter(device.DeviceId, device.DeviceString, device.DeviceKey);
}
device = Native.DeviceContext.Structures.DisplayDevice.Initialize();
}
}
/// <summary>
/// Returns a list of all display devices connected to this adapter
/// </summary>
/// <returns>An enumerable list of DisplayDevices</returns>
public IEnumerable<DisplayDevice> GetDisplayDevices()
{
return GetDisplayDevices(null);
}
/// <summary>
/// Returns the corresponding PathDisplayAdapter instance
/// </summary>
/// <returns>An instance of PathDisplayAdapter, or null</returns>
public PathDisplayAdapter ToPathDisplayAdapter()
{
return PathDisplayAdapter.GetAdapters()
.FirstOrDefault(adapter =>
adapter.DevicePath.StartsWith("\\\\?\\" + DevicePath.Replace("\\", "#"))
);
}
internal IEnumerable<DisplayDevice> GetDisplayDevices(bool? filterByAvailability)
{
var returned = new Dictionary<string, string>();
var adapterIndex = -1;
while (true)
{
adapterIndex++;
var adapter = Native.DeviceContext.Structures.DisplayDevice.Initialize();
if (!DeviceContextApi.EnumDisplayDevices(null, (uint) adapterIndex, ref adapter, 0))
{
break;
}
if (!DevicePath.Equals(adapter.DeviceId))
{
continue;
}
var displayIndex = -1;
while (true)
{
displayIndex++;
var display = Native.DeviceContext.Structures.DisplayDevice.Initialize();
if (!DeviceContextApi.EnumDisplayDevices(adapter.DeviceName, (uint) displayIndex, ref display, 1))
{
break;
}
var displayDevice = DisplayDevice.FromDeviceInformation(this, adapter, display);
if (!filterByAvailability.HasValue)
{
yield return displayDevice;
}
else if (displayDevice.IsAvailable == filterByAvailability.Value)
{
if (returned.ContainsKey(display.DeviceId) &&
returned[display.DeviceId].Equals(display.DeviceKey)
)
{
continue;
}
returned.Add(display.DeviceId, display.DeviceKey);
yield return displayDevice;
}
}
}
}
}
}

View File

@@ -0,0 +1,269 @@
using System;
using WindowsDisplayAPI.Native;
using WindowsDisplayAPI.Native.DeviceContext;
namespace WindowsDisplayAPI
{
/// <summary>
/// Contains information about the device capabilities of a display device
/// </summary>
public sealed class MonitorCapabilities : IDisposable
{
private readonly DCHandle _dcHandle;
internal MonitorCapabilities(DCHandle dcHandle)
{
_dcHandle = dcHandle;
var tech = (DisplayTechnology) DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.Technology);
if (tech != DisplayTechnology.RasterDisplay)
{
throw new NotSupportedException();
}
}
/// <summary>
/// Gets the actual color resolution of the device, in bits per pixel.
/// </summary>
public int ActualColorDepth
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.ColorResolution) * ColorPlanes;
}
/// <summary>
/// Gets a boolean value indicating if the device is capable of clipping to a rectangle.
/// </summary>
public bool ClipToRectangleCapability
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.ClipCapabilities) > 0;
}
/// <summary>
/// Gets the color management capabilities of the device
/// </summary>
public DisplayColorManagementCapabilities ColorManagementCapabilities
{
get => (DisplayColorManagementCapabilities) DeviceContextApi.GetDeviceCaps(
_dcHandle,
DeviceCapability.ColorManagementCapabilities
);
}
/// <summary>
/// Gets the number of color planes.
/// </summary>
public int ColorPlanes
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.Planes);
}
/// <summary>
/// Gets the curve capabilities of the device
/// </summary>
public DisplayCurveCapabilities CurveCapabilities
{
get => (DisplayCurveCapabilities) DeviceContextApi.GetDeviceCaps(
_dcHandle,
DeviceCapability.CurveCapabilities
);
}
/// <summary>
/// Gets the diagonal width of the device pixel used for line drawing.
/// </summary>
public int DevicePixelDiagonalWidth
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.HypotenuseAspect);
}
/// <summary>
/// Gets the relative height of a device pixel used for line drawing.
/// </summary>
public int DevicePixelRelativeHeight
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.VerticalAspect);
}
/// <summary>
/// Gets the relative width of a device pixel used for line drawing.
/// </summary>
public int DevicePixelRelativeWidth
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.HorizontalAspect);
}
/// <summary>
/// Gets the diagonal length of the physical screen in millimeters.
/// </summary>
public int DiagonalSizeInMM
{
get => (int) Math.Round(Math.Pow(Math.Pow(VerticalSizeInMM, 2) + Math.Pow(HorizontalSizeInMM, 2), 0.5));
}
/// <summary>
/// Gets the device driver version.
/// </summary>
public int DriverVersion
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.DriverVersion);
}
/// <summary>
/// Gets the effective color resolution of the device, in bits per pixel.
/// </summary>
public int EffectiveColorDepth
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.BitsPerPixel) * ColorPlanes;
}
/// <summary>
/// Gets the number of pixels per logical inch along the screen width.
/// </summary>
public int HorizontalPixelPerInch
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.HorizontalLogicalPixels);
}
/// <summary>
/// Gets the height of screen in raster lines.
/// </summary>
public int HorizontalResolution
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.HorizontalResolution);
}
/// <summary>
/// Gets the width of the physical screen in millimeters.
/// </summary>
public int HorizontalSizeInMM
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.HorizontalSizeInMM);
}
/// <summary>
/// Gets the line capabilities of the device
/// </summary>
public DisplayLineCapabilities LineCapabilities
{
get => (DisplayLineCapabilities) DeviceContextApi.GetDeviceCaps(
_dcHandle,
DeviceCapability.LineCapabilities
);
}
/// <summary>
/// Gets the polygon capabilities of the device
/// </summary>
public DisplayPolygonalCapabilities PolygonalCapabilities
{
get => (DisplayPolygonalCapabilities) DeviceContextApi.GetDeviceCaps(
_dcHandle,
DeviceCapability.PolygonalCapabilities
);
}
/// <summary>
/// Gets the preferred horizontal drawing alignment, expressed as a multiple of pixels. For best drawing performance,
/// windows should be horizontally aligned to a multiple of this value. A value of null indicates that the device is
/// accelerated, and any alignment may be used.
/// </summary>
public int? PreferredHorizontalDrawingAlignment
{
get
{
var value = DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.PreferredBLTAlignment);
if (value == 0)
{
return null;
}
return value;
}
}
/// <summary>
/// Gets the raster capabilities of the device
/// </summary>
public DisplayRasterCapabilities RasterCapabilities
{
get => (DisplayRasterCapabilities) DeviceContextApi.GetDeviceCaps(
_dcHandle,
DeviceCapability.RasterCapabilities
);
}
/// <summary>
/// Gets the shader blending capabilities of the device
/// </summary>
public DisplayShaderBlendingCapabilities ShaderBlendingCapabilities
{
get => (DisplayShaderBlendingCapabilities) DeviceContextApi.GetDeviceCaps(
_dcHandle,
DeviceCapability.ShadeBlendingCapabilities
);
}
/// <summary>
/// Gets the text capabilities of the device
/// </summary>
public DisplayTextCapabilities TextCapabilities
{
get => (DisplayTextCapabilities) DeviceContextApi.GetDeviceCaps(
_dcHandle,
DeviceCapability.TextCapabilities
);
}
/// <summary>
/// Gets the number of pixels per logical inch along the screen height.
/// </summary>
public int VerticalPixelPerInch
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.VerticalLogicalPixels);
}
/// <summary>
/// Gets the current vertical refresh rate of the device, in cycles per second (Hz) or null for display hardware's
/// default refresh rate.
/// </summary>
public int? VerticalRefreshRateInHz
{
get
{
var value = DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.VerticalRefreshRateInHz);
if (value <= 1)
{
return null;
}
return value;
}
}
/// <summary>
/// Gets the width of the screen in pixels.
/// </summary>
public int VerticalResolution
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.VerticalResolution);
}
/// <summary>
/// Gets the height of the physical screen in millimeters.
/// </summary>
public int VerticalSizeInMM
{
get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.VerticalSizeInMM);
}
/// <inheritdoc />
public void Dispose()
{
_dcHandle?.Dispose();
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
namespace WindowsDisplayAPI
{
/// <summary>
/// Contains possible color management capabilities of a display device
/// </summary>
[Flags]
public enum DisplayColorManagementCapabilities
{
/// <summary>
/// Device does not support ICM.
/// </summary>
None = 0,
/// <summary>
/// Device can perform ICM on either the device driver or the device itself.
/// </summary>
DeviceICM = 1,
/// <summary>
/// Device supports gamma ramp modification and retrieval
/// </summary>
GammaRamp = 2,
/// <summary>
/// Device can accept CMYK color space ICC color profile.
/// </summary>
CMYKColor = 4
}
}

View File

@@ -0,0 +1,190 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using WindowsDisplayAPI.Native;
using WindowsDisplayAPI.Native.DisplayConfig.Structures;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.DisplayConfig
{
/// <summary>
/// Represents a path display adapter
/// </summary>
public class PathDisplayAdapter : IEquatable<PathDisplayAdapter>
{
/// <summary>
/// Creates a new PathDisplayAdapter
/// </summary>
/// <param name="adapterId">The adapter local unique identification</param>
public PathDisplayAdapter(LUID adapterId)
{
AdapterId = adapterId;
}
/// <summary>
/// Gets the display adapter local identification LUID
/// </summary>
public LUID AdapterId { get; }
/// <summary>
/// Gets the display adapter device path
/// </summary>
/// <exception cref="Win32Exception">Error code can be retrieved from Win32Exception.NativeErrorCode property</exception>
public string DevicePath
{
get
{
var adapterName = new DisplayConfigAdapterName(AdapterId);
var result = DisplayConfigApi.DisplayConfigGetDeviceInfo(ref adapterName);
if (result == Win32Status.Success)
{
return adapterName.AdapterDevicePath;
}
throw new Win32Exception((int) result);
}
}
/// <summary>
/// Gets a boolean value indicating the instance validity
/// </summary>
public bool IsInvalid
{
get => AdapterId.IsEmpty();
}
/// <inheritdoc />
public bool Equals(PathDisplayAdapter other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return AdapterId == other.AdapterId;
}
/// <summary>
/// Retrieving a list of all adapters from the currently active and inactive paths
/// </summary>
/// <returns>An array of PathDisplayAdapter instances</returns>
public static PathDisplayAdapter[] GetAdapters()
{
var adapters = new Dictionary<LUID, PathDisplayAdapter>();
foreach (var pathInfo in PathInfo.GetAllPaths())
{
if (!pathInfo.DisplaySource.Adapter.IsInvalid &&
!adapters.ContainsKey(pathInfo.DisplaySource.Adapter.AdapterId))
{
adapters.Add(pathInfo.DisplaySource.Adapter.AdapterId, pathInfo.DisplaySource.Adapter);
}
foreach (var pathTargetInfo in pathInfo.TargetsInfo)
{
if (!pathTargetInfo.DisplayTarget.Adapter.IsInvalid &&
!adapters.ContainsKey(pathTargetInfo.DisplayTarget.Adapter.AdapterId))
{
adapters.Add(pathTargetInfo.DisplayTarget.Adapter.AdapterId, pathTargetInfo.DisplayTarget.Adapter);
}
}
}
return adapters.Values.ToArray();
}
/// <summary>
/// Checks for equality of two PathDisplayAdapter instances
/// </summary>
/// <param name="left">The first instance</param>
/// <param name="right">The second instance</param>
/// <returns>true if both instances are equal, otherwise false</returns>
public static bool operator ==(PathDisplayAdapter left, PathDisplayAdapter right)
{
return Equals(left, right) || left?.Equals(right) == true;
}
/// <summary>
/// Checks for inequality of two PathDisplayAdapter instances
/// </summary>
/// <param name="left">The first instance</param>
/// <param name="right">The second instance</param>
/// <returns>true if both instances are not equal, otherwise false</returns>
public static bool operator !=(PathDisplayAdapter left, PathDisplayAdapter right)
{
return !(left == right);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
return obj.GetType() == GetType() && Equals((PathDisplayAdapter) obj);
}
/// <inheritdoc />
public override int GetHashCode()
{
return AdapterId.GetHashCode();
}
/// <inheritdoc />
public override string ToString()
{
return DevicePath;
}
#if !NETSTANDARD
/// <summary>
/// Opens the registry key of the Windows PnP manager for this display adapter
/// </summary>
/// <returns>A RegistryKey instance for successful call, otherwise null</returns>
public Microsoft.Win32.RegistryKey OpenDevicePnPKey()
{
if (string.IsNullOrWhiteSpace(DevicePath))
return null;
var path = DevicePath;
if (path.StartsWith("\\\\?\\"))
{
path = path.Substring(4).Replace("#", "\\");
if (path.EndsWith("}"))
{
var guidIndex = path.LastIndexOf("{", StringComparison.InvariantCulture);
if (guidIndex > 0)
path = path.Substring(0, guidIndex);
}
}
return Microsoft.Win32.Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Enum\\" + path,
Microsoft.Win32.RegistryKeyPermissionCheck.ReadSubTree);
}
#endif
/// <summary>
/// Gets the corresponding DisplayAdapter instance
/// </summary>
/// <returns>An instance of DisplayAdapter, or null</returns>
public DisplayAdapter ToDisplayAdapter()
{
return DisplayAdapter.GetDisplayAdapters()
.FirstOrDefault(
adapter => DevicePath.StartsWith("\\\\?\\" + adapter.DevicePath.Replace("\\", "#"))
);
}
}
}

View File

@@ -0,0 +1,238 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using WindowsDisplayAPI.Native;
using WindowsDisplayAPI.Native.DisplayConfig;
using WindowsDisplayAPI.Native.DisplayConfig.Structures;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.DisplayConfig
{
/// <summary>
/// Represents a display path source
/// </summary>
public class PathDisplaySource : IEquatable<PathDisplaySource>
{
private static readonly DisplayConfigSourceDPIScale[] DPIScales = Enum
.GetValues(typeof(DisplayConfigSourceDPIScale))
.Cast<DisplayConfigSourceDPIScale>()
.OrderBy(scaling => (uint) scaling)
.ToArray();
/// <summary>
/// Creates a new PathDisplaySource
/// </summary>
/// <param name="adapter">Display adapter</param>
/// <param name="sourceId">Display source identification</param>
public PathDisplaySource(PathDisplayAdapter adapter, uint sourceId)
{
Adapter = adapter;
SourceId = sourceId;
}
/// <summary>
/// Gets the path display adapter
/// </summary>
public PathDisplayAdapter Adapter { get; }
/// <summary>
/// Gets and sets the current source DPI scaling
/// </summary>
public DisplayConfigSourceDPIScale CurrentDPIScale
{
get
{
var dpiScale = new DisplayConfigGetSourceDPIScale(Adapter.AdapterId, SourceId);
var result = DisplayConfigApi.DisplayConfigGetDeviceInfo(ref dpiScale);
if (result != Win32Status.Success)
{
throw new Win32Exception((int) result);
}
var currentScaleIndex = Math.Abs(dpiScale.MinimumScaleSteps) + dpiScale.CurrentScaleSteps;
return DPIScales[currentScaleIndex];
}
set
{
var currentScaleStep = Array.IndexOf(DPIScales, value) - Array.IndexOf(DPIScales, RecommendedDPIScale);
var dpiScale = new DisplayConfigSetSourceDPIScale(Adapter.AdapterId, SourceId, currentScaleStep);
var result = DisplayConfigApi.DisplayConfigSetDeviceInfo(ref dpiScale);
if (result != Win32Status.Success)
{
throw new Win32Exception((int) result);
}
}
}
/// <summary>
/// Returns the corresponding <see cref="DisplayScreen"/> instance.
/// </summary>
public DisplayScreen ToScreen()
{
return DisplayScreen.GetScreens().FirstOrDefault(info => info.ScreenName.Equals(DisplayName));
}
/// <summary>
/// Gets the display name
/// </summary>
/// <exception cref="Win32Exception">Error code can be retrieved from Win32Exception.NativeErrorCode property</exception>
public string DisplayName
{
get
{
var sourceName = new DisplayConfigSourceDeviceName(Adapter.AdapterId, SourceId);
var result = DisplayConfigApi.DisplayConfigGetDeviceInfo(ref sourceName);
if (result == Win32Status.Success)
{
return sourceName.DeviceName;
}
throw new Win32Exception((int) result);
}
}
/// <summary>
/// Gets the maximum DPI scaling for this source
/// </summary>
public DisplayConfigSourceDPIScale MaximumDPIScale
{
get
{
var dpiScale = new DisplayConfigGetSourceDPIScale(Adapter.AdapterId, SourceId);
var result = DisplayConfigApi.DisplayConfigGetDeviceInfo(ref dpiScale);
if (result != Win32Status.Success)
{
throw new Win32Exception((int) result);
}
var currentScaleIndex = Math.Abs(dpiScale.MinimumScaleSteps) + dpiScale.MaximumScaleSteps;
return DPIScales[currentScaleIndex];
}
}
/// <summary>
/// Gets the recommended DPI scaling for this source
/// </summary>
public DisplayConfigSourceDPIScale RecommendedDPIScale
{
get
{
var dpiScale = new DisplayConfigGetSourceDPIScale(Adapter.AdapterId, SourceId);
var result = DisplayConfigApi.DisplayConfigGetDeviceInfo(ref dpiScale);
if (result != Win32Status.Success)
{
throw new Win32Exception((int) result);
}
return DPIScales[Math.Abs(dpiScale.MinimumScaleSteps)];
}
}
/// <summary>
/// Gets the zero based display identification
/// </summary>
public uint SourceId { get; }
/// <inheritdoc />
public bool Equals(PathDisplaySource other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return Adapter == other.Adapter && SourceId == other.SourceId;
}
/// <summary>
/// Retrieving a list of all display sources from the currently active and inactive paths
/// </summary>
/// <returns>An array of PathDisplaySource instances</returns>
public static PathDisplaySource[] GetDisplaySources()
{
var sources = new Dictionary<Tuple<LUID, uint>, PathDisplaySource>();
foreach (var pathInfo in PathInfo.GetAllPaths())
{
var key = Tuple.Create(
pathInfo.DisplaySource.Adapter.AdapterId,
pathInfo.DisplaySource.SourceId
);
if (!pathInfo.DisplaySource.Adapter.IsInvalid && !sources.ContainsKey(key))
{
sources.Add(key, pathInfo.DisplaySource);
}
}
return sources.Values.ToArray();
}
/// <summary>
/// Checks for equality of two PathDisplaySource instances
/// </summary>
/// <param name="left">The first instance</param>
/// <param name="right">The second instance</param>
/// <returns>true if both instances are equal, otherwise false</returns>
public static bool operator ==(PathDisplaySource left, PathDisplaySource right)
{
return Equals(left, right) || left?.Equals(right) == true;
}
/// <summary>
/// Checks for inequality of two PathDisplaySource instances
/// </summary>
/// <param name="left">The first instance</param>
/// <param name="right">The second instance</param>
/// <returns>true if both instances are not equal, otherwise false</returns>
public static bool operator !=(PathDisplaySource left, PathDisplaySource right)
{
return !(left == right);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
return obj.GetType() == GetType() && Equals((PathDisplaySource) obj);
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
return ((Adapter != null ? Adapter.GetHashCode() : 0) * 397) ^ (int) SourceId;
}
}
/// <inheritdoc />
public override string ToString()
{
return DisplayName;
}
}
}

View File

@@ -0,0 +1,481 @@
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
{
/// <summary>
/// Represents a display path target (Display Device)
/// </summary>
public class PathDisplayTarget : IEquatable<PathDisplayTarget>
{
/// <summary>
/// Creates a new PathDisplayTarget
/// </summary>
/// <param name="adapter">Display adapter</param>
/// <param name="targetId">Display target identification</param>
public PathDisplayTarget(PathDisplayAdapter adapter, uint targetId) : this(adapter, targetId, false)
{
IsAvailable = GetDisplayTargets().Any(target => target == this);
}
internal PathDisplayTarget(PathDisplayAdapter adapter, uint targetId, bool isAvailable)
{
Adapter = adapter;
TargetId = targetId;
IsAvailable = isAvailable;
}
/// <summary>
/// Gets the path display adapter
/// </summary>
public PathDisplayAdapter Adapter { get; }
/// <summary>
/// Sets the display boot persistence for the target display device
/// </summary>
/// <exception cref="TargetNotAvailableException"></exception>
/// <exception cref="Win32Exception"></exception>
public bool BootPersistence
{
set
{
if (!IsAvailable)
{
throw new TargetNotAvailableException("Extra information about the target is not available.",
Adapter.AdapterId, TargetId);
}
var targetPersistence = new DisplayConfigSetTargetPersistence(Adapter.AdapterId, TargetId, value);
var result = DisplayConfigApi.DisplayConfigSetDeviceInfo(ref targetPersistence);
if (result != Win32Status.Success)
{
throw new Win32Exception((int) result);
}
}
}
/// <summary>
/// Gets the one-based instance number of this particular target only when the adapter has multiple targets of this
/// type. The connector instance is a consecutive one-based number that is unique within each adapter. If this is the
/// only target of this type on the adapter, this value is zero.
/// </summary>
/// <exception cref="Win32Exception">Error code can be retrieved from Win32Exception.NativeErrorCode property</exception>
/// <exception cref="TargetNotAvailableException">The target is not available</exception>
public int ConnectorInstance
{
get
{
if (!IsAvailable)
{
throw new TargetNotAvailableException("Extra information about the target is not available.",
Adapter.AdapterId, TargetId);
}
var targetName = new DisplayConfigTargetDeviceName(Adapter.AdapterId, TargetId);
var result = DisplayConfigApi.DisplayConfigGetDeviceInfo(ref targetName);
if (result == Win32Status.Success)
{
return (int) targetName.ConnectorInstance;
}
throw new Win32Exception((int) result);
}
}
/// <summary>
/// Gets the display device path
/// </summary>
/// <exception cref="Win32Exception">Error code can be retrieved from Win32Exception.NativeErrorCode property</exception>
/// <exception cref="TargetNotAvailableException">The target is not available</exception>
public string DevicePath
{
get
{
if (!IsAvailable)
{
throw new TargetNotAvailableException("Extra information about the target is not available.",
Adapter.AdapterId, TargetId);
}
var targetName = new DisplayConfigTargetDeviceName(Adapter.AdapterId, TargetId);
var result = DisplayConfigApi.DisplayConfigGetDeviceInfo(ref targetName);
if (result == Win32Status.Success)
{
return targetName.MonitorDevicePath;
}
throw new Win32Exception((int) result);
}
}
/// <summary>
/// Gets the display manufacture 3 character code from the display EDID manufacture identification
/// </summary>
/// <exception cref="TargetNotAvailableException">The target is not available</exception>
/// <exception cref="Win32Exception">Error code can be retrieved from Win32Exception.NativeErrorCode property</exception>
/// <exception cref="InvalidEDIDInformation">The EDID information does not contain this value</exception>
public string EDIDManufactureCode
{
get
{
var edidCode = EDIDManufactureId;
edidCode = ((edidCode & 0xff00) >> 8) | ((edidCode & 0x00ff) << 8);
var byte1 = (byte) 'A' + (edidCode & 0x1f) - 1;
var byte2 = (byte) 'A' + ((edidCode >> 5) & 0x1f) - 1;
var byte3 = (byte) 'A' + ((edidCode >> 10) & 0x1f) - 1;
return $"{Convert.ToChar(byte3)}{Convert.ToChar(byte2)}{Convert.ToChar(byte1)}";
}
}
/// <summary>
/// Gets the display manufacture identification from the display EDID information
/// </summary>
/// <exception cref="TargetNotAvailableException">The target is not available</exception>
/// <exception cref="Win32Exception">Error code can be retrieved from Win32Exception.NativeErrorCode property</exception>
/// <exception cref="InvalidEDIDInformation">The EDID information does not contain this value</exception>
public int EDIDManufactureId
{
get
{
if (!IsAvailable)
{
throw new TargetNotAvailableException("Extra information about the target is not available.",
Adapter.AdapterId, TargetId);
}
var targetName = new DisplayConfigTargetDeviceName(Adapter.AdapterId, TargetId);
var result = DisplayConfigApi.DisplayConfigGetDeviceInfo(ref targetName);
if (result == Win32Status.Success)
{
if (targetName.Flags.HasFlag(DisplayConfigTargetDeviceNameFlags.EDIDIdsValid))
{
return targetName.EDIDManufactureId;
}
throw new InvalidEDIDInformation("EDID does not contain necessary information.");
}
throw new Win32Exception((int) result);
}
}
/// <summary>
/// Gets the display product identification from the display EDID information
/// </summary>
/// <exception cref="TargetNotAvailableException">The target is not available</exception>
/// <exception cref="Win32Exception">Error code can be retrieved from Win32Exception.NativeErrorCode property</exception>
/// <exception cref="InvalidEDIDInformation">The EDID information does not contain this value</exception>
public int EDIDProductCode
{
get
{
if (!IsAvailable)
{
throw new TargetNotAvailableException("Extra information about the target is not available.",
Adapter.AdapterId, TargetId);
}
var targetName = new DisplayConfigTargetDeviceName(Adapter.AdapterId, TargetId);
var result = DisplayConfigApi.DisplayConfigGetDeviceInfo(ref targetName);
if (result == Win32Status.Success)
{
if (targetName.Flags.HasFlag(DisplayConfigTargetDeviceNameFlags.EDIDIdsValid))
{
return targetName.EDIDProductCodeId;
}
throw new InvalidEDIDInformation("EDID does not contain necessary information.");
}
throw new Win32Exception((int) result);
}
}
/// <summary>
/// Gets the display friendly name from the display EDID information
/// </summary>
/// <exception cref="TargetNotAvailableException">The target is not available</exception>
/// <exception cref="Win32Exception">Error code can be retrieved from Win32Exception.NativeErrorCode property</exception>
public string FriendlyName
{
get
{
if (!IsAvailable)
{
throw new TargetNotAvailableException("Extra information about the target is not available.",
Adapter.AdapterId, TargetId);
}
var targetName = new DisplayConfigTargetDeviceName(Adapter.AdapterId, TargetId);
var result = DisplayConfigApi.DisplayConfigGetDeviceInfo(ref targetName);
if (result == Win32Status.Success)
{
return targetName.MonitorFriendlyDeviceName;
}
throw new Win32Exception((int) result);
}
}
/// <summary>
/// Gets a boolean value indicating the device availability
/// </summary>
public bool IsAvailable { get; }
/// <summary>
/// Gets the display device preferred resolution
/// </summary>
/// <exception cref="TargetNotAvailableException">The target is not available</exception>
/// <exception cref="Win32Exception">Error code can be retrieved from Win32Exception.NativeErrorCode property</exception>
public Size PreferredResolution
{
get
{
if (!IsAvailable)
{
throw new TargetNotAvailableException("Extra information about the target is not available.",
Adapter.AdapterId, TargetId);
}
var targetPreferredMode = new DisplayConfigTargetPreferredMode(Adapter.AdapterId, TargetId);
var result = DisplayConfigApi.DisplayConfigGetDeviceInfo(ref targetPreferredMode);
if (result == Win32Status.Success)
{
return new Size((int) targetPreferredMode.Width, (int) targetPreferredMode.Height);
}
throw new Win32Exception((int) result);
}
}
/// <summary>
/// Gets the display device preferred signal information
/// </summary>
/// <exception cref="TargetNotAvailableException">The target is not available</exception>
/// <exception cref="Win32Exception">Error code can be retrieved from Win32Exception.NativeErrorCode property</exception>
public PathTargetSignalInfo PreferredSignalMode
{
get
{
if (!IsAvailable)
{
throw new TargetNotAvailableException("Extra information about the target is not available.",
Adapter.AdapterId, TargetId);
}
var targetPreferredMode = new DisplayConfigTargetPreferredMode(Adapter.AdapterId, TargetId);
var result = DisplayConfigApi.DisplayConfigGetDeviceInfo(ref targetPreferredMode);
if (result == Win32Status.Success)
{
return new PathTargetSignalInfo(targetPreferredMode.TargetMode.TargetVideoSignalInfo);
}
throw new Win32Exception((int) result);
}
}
/// <summary>
/// Gets the target identification
/// </summary>
public uint TargetId { get; }
/// <summary>
/// Gets or sets the device virtual resolution support
/// </summary>
/// <exception cref="TargetNotAvailableException">The target is not available</exception>
/// <exception cref="Win32Exception">Error code can be retrieved from Win32Exception.NativeErrorCode property</exception>
public bool VirtualResolutionSupport
{
get
{
if (!IsAvailable)
{
throw new TargetNotAvailableException("Extra information about the target is not available.",
Adapter.AdapterId, TargetId);
}
var targetSupportVirtualResolution = new DisplayConfigSupportVirtualResolution(Adapter.AdapterId,
TargetId);
var result = DisplayConfigApi.DisplayConfigGetDeviceInfo(ref targetSupportVirtualResolution);
if (result == Win32Status.Success)
{
return !targetSupportVirtualResolution.DisableMonitorVirtualResolution;
}
throw new Win32Exception((int) result);
}
set
{
if (!IsAvailable)
{
throw new TargetNotAvailableException("Extra information about the target is not available.",
Adapter.AdapterId, TargetId);
}
var targetSupportVirtualResolution = new DisplayConfigSupportVirtualResolution(Adapter.AdapterId,
TargetId, !value);
var result = DisplayConfigApi.DisplayConfigSetDeviceInfo(ref targetSupportVirtualResolution);
if (result != Win32Status.Success)
{
throw new Win32Exception((int) result);
}
}
}
/// <inheritdoc />
public bool Equals(PathDisplayTarget other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return Adapter == other.Adapter && TargetId == other.TargetId;
}
/// <summary>
/// Retrieving a list of all display targets from the currently active and inactive paths
/// </summary>
/// <returns>An array of PathDisplayTarget instances</returns>
public static PathDisplayTarget[] GetDisplayTargets()
{
var targets = new Dictionary<Tuple<LUID, uint>, PathDisplayTarget>();
foreach (var pathInfo in PathInfo.GetAllPaths())
foreach (var pathTargetInfo in pathInfo.TargetsInfo.Where(info => info.DisplayTarget.IsAvailable))
{
var key = Tuple.Create(
pathTargetInfo.DisplayTarget.Adapter.AdapterId,
pathTargetInfo.DisplayTarget.TargetId
);
if (!pathTargetInfo.DisplayTarget.Adapter.IsInvalid && !targets.ContainsKey(key))
{
targets.Add(key, pathTargetInfo.DisplayTarget);
}
}
return targets.Values.ToArray();
}
/// <summary>
/// Checks for equality of two PathDisplayTarget instances
/// </summary>
/// <param name="left">The first instance</param>
/// <param name="right">The second instance</param>
/// <returns>true if both instances are equal, otherwise false</returns>
public static bool operator ==(PathDisplayTarget left, PathDisplayTarget right)
{
return Equals(left, right) || left?.Equals(right) == true;
}
/// <summary>
/// Checks for inequality of two PathDisplayTarget instances
/// </summary>
/// <param name="left">The first instance</param>
/// <param name="right">The second instance</param>
/// <returns>true if both instances are not equal, otherwise false</returns>
public static bool operator !=(PathDisplayTarget left, PathDisplayTarget right)
{
return !(left == right);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
return obj.GetType() == GetType() && Equals((PathDisplayTarget) obj);
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
return ((Adapter != null ? Adapter.GetHashCode() : 0) * 397) ^ (int) TargetId;
}
}
/// <inheritdoc />
public override string ToString()
{
return FriendlyName;
}
#if !NETSTANDARD
/// <summary>
/// Opens the registry key of the Windows PnP manager for this display target
/// </summary>
/// <returns>A RegistryKey instance for successful call, otherwise null</returns>
public Microsoft.Win32.RegistryKey OpenDevicePnPKey()
{
if (string.IsNullOrWhiteSpace(DevicePath)) {
return null;
}
var path = DevicePath;
if (path.StartsWith("\\\\?\\"))
{
path = path.Substring(4).Replace("#", "\\");
if (path.EndsWith("}"))
{
var guidIndex = path.LastIndexOf("{", StringComparison.InvariantCulture);
if (guidIndex > 0) {
path = path.Substring(0, guidIndex);
}
}
}
return Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
"SYSTEM\\CurrentControlSet\\Enum\\" + path,
Microsoft.Win32.RegistryKeyPermissionCheck.ReadSubTree
);
}
#endif
/// <summary>
/// Returns the corresponding <see cref="DisplayDevice"/> instance
/// </summary>
/// <returns>An instance of <see cref="DisplayDevice"/>, or null</returns>
public DisplayDevice ToDisplayDevice()
{
return
DisplayAdapter.GetDisplayAdapters()
.SelectMany(adapter => adapter.GetDisplayDevices())
.FirstOrDefault(device => device.DevicePath.Equals(DevicePath));
}
}
}

View File

@@ -0,0 +1,910 @@
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
{
/// <summary>
/// Represents a path root information
/// </summary>
public class PathInfo
{
private readonly uint _cloneGroupId;
private readonly DisplayConfigPixelFormat _pixelFormat;
private readonly Point _position;
private readonly Size _resolution;
/// <summary>
/// Creates a new PathInfo
/// </summary>
/// <param name="displaySource">The display source</param>
/// <param name="position">The display position in desktop</param>
/// <param name="resolution">The display resolution</param>
/// <param name="pixelFormat">The display pixel format</param>
public PathInfo(
PathDisplaySource displaySource,
Point position,
Size resolution,
DisplayConfigPixelFormat pixelFormat
) : this(displaySource)
{
_position = position;
_resolution = resolution;
_pixelFormat = pixelFormat;
IsModeInformationAvailable = true;
}
/// <summary>
/// Creates a new PathInfo
/// </summary>
/// <param name="displaySource">The display source</param>
/// <param name="position">The display position in desktop</param>
/// <param name="resolution">The display resolution</param>
/// <param name="pixelFormat">The display pixel format</param>
/// <param name="cloneGroup">The display clone group, only valid for virtual aware paths</param>
public PathInfo(
PathDisplaySource displaySource,
Point position,
Size resolution,
DisplayConfigPixelFormat pixelFormat,
uint cloneGroup
) : this(displaySource, cloneGroup)
{
_position = position;
_resolution = resolution;
_pixelFormat = pixelFormat;
IsModeInformationAvailable = true;
}
/// <summary>
/// Creates a new PathInfo
/// </summary>
/// <param name="displaySource">The display source</param>
/// <param name="position">The display position in desktop</param>
/// <param name="resolution">The display resolution</param>
/// <param name="pixelFormat">The display pixel format</param>
/// <param name="pathTargetInfos">An array of target information</param>
public PathInfo(
PathDisplaySource displaySource,
Point position,
Size resolution,
DisplayConfigPixelFormat pixelFormat,
PathTargetInfo[] pathTargetInfos
) : this(displaySource, position, resolution, pixelFormat)
{
TargetsInfo = pathTargetInfos;
}
/// <summary>
/// Creates a new PathInfo
/// </summary>
/// <param name="displaySource">The display source</param>
/// <param name="position">The display position in desktop</param>
/// <param name="resolution">The display resolution</param>
/// <param name="pixelFormat">The display pixel format</param>
/// <param name="pathTargetInfos">An array of target information</param>
/// <param name="cloneGroup">The display clone group, only valid for virtual aware paths</param>
public PathInfo(
PathDisplaySource displaySource,
Point position,
Size resolution,
DisplayConfigPixelFormat pixelFormat,
PathTargetInfo[] pathTargetInfos,
uint cloneGroup
) : this(displaySource, position, resolution, pixelFormat, cloneGroup)
{
TargetsInfo = pathTargetInfos;
}
/// <summary>
/// Creates a new PathInfo
/// </summary>
/// <param name="displaySource">The display source</param>
public PathInfo(PathDisplaySource displaySource)
{
DisplaySource = displaySource;
}
/// <summary>
/// Creates a new PathInfo
/// </summary>
/// <param name="displaySource">The display source</param>
/// <param name="cloneGroup">The display clone group, only valid for virtual aware paths</param>
public PathInfo(PathDisplaySource displaySource, uint cloneGroup) : this(displaySource)
{
IsCloneMember = true;
_cloneGroupId = cloneGroup;
}
/// <summary>
/// Creates a new PathInfo
/// </summary>
/// <param name="displaySource">The display source</param>
/// <param name="pathTargetInfos">An array of target information</param>
public PathInfo(
PathDisplaySource displaySource,
PathTargetInfo[] pathTargetInfos
) : this(displaySource)
{
TargetsInfo = pathTargetInfos;
}
/// <summary>
/// Creates a new PathInfo
/// </summary>
/// <param name="displaySource">The display source</param>
/// <param name="pathTargetInfos">An array of target information</param>
/// <param name="cloneGroup">The display clone group, only valid for virtual aware paths</param>
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;
}
}
/// <summary>
/// Gets a valid identifier used to show which clone group the path is a member of
/// </summary>
/// <exception cref="NotACloneMemberException">This path is not a clone member</exception>
public uint CloneGroupId
{
get
{
if (!IsCloneMember)
{
throw new NotACloneMemberException(
"The display source is not part of a clone group."
);
}
return _cloneGroupId;
}
}
/// <summary>
/// Gets extra information about the representing display source
/// </summary>
public PathDisplaySource DisplaySource { get; }
/// <summary>
/// Gets a boolean value indicating if this path is a member of a clone group
/// </summary>
public bool IsCloneMember { get; }
/// <summary>
/// Gets a boolean value indicating if this path is the primary GDI path
/// </summary>
/// <exception cref="MissingModeException">Source mode information is missing</exception>
public bool IsGDIPrimary
{
get => Position.IsEmpty;
}
/// <summary>
/// Gets a boolean value indicating if the source is in use by at least one active path
/// </summary>
public bool IsInUse { get; }
/// <summary>
/// Gets a boolean value indicating if the source mode information is available
/// </summary>
public bool IsModeInformationAvailable { get; }
/// <summary>
/// Gets a boolean value indicating the DisplayConfig (CCD API) availability on this system
/// </summary>
public static bool IsSupported
{
get
{
try
{
return DisplayConfigApi.GetDisplayConfigBufferSizes(
QueryDeviceConfigFlags.AllPaths,
out _,
out _
) == Win32Status.Success;
}
catch
{
return false;
}
}
}
/// <summary>
/// Gets a boolean value indicating the virtual display mode support on this system
/// </summary>
public static bool IsVirtualModeSupported
{
get
{
try
{
return PathDisplayTarget
.GetDisplayTargets()
.Any(t => t.VirtualResolutionSupport);
}
catch
{
return false;
}
}
}
/// <summary>
/// Gets the specifies the pixel format of the source mode
/// </summary>
/// <exception cref="MissingModeException">Source mode information is missing</exception>
public DisplayConfigPixelFormat PixelFormat
{
get
{
if (!IsModeInformationAvailable)
{
throw new MissingModeException(
"Source mode information is missing or not available.",
DisplayConfigModeInfoType.Source
);
}
return _pixelFormat;
}
}
/// <summary>
/// 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.
/// </summary>
/// <exception cref="MissingModeException">Source mode information is missing</exception>
public Point Position
{
get
{
if (!IsModeInformationAvailable)
{
throw new MissingModeException(
"Source mode information is missing or not available.",
DisplayConfigModeInfoType.Source
);
}
return _position;
}
}
/// <summary>
/// Gets the size of the source mode
/// </summary>
/// <exception cref="MissingModeException">Source mode information is missing</exception>
public Size Resolution
{
get
{
if (!IsModeInformationAvailable)
{
throw new MissingModeException(
"Source mode information is missing or not available.",
DisplayConfigModeInfoType.Source
);
}
return _resolution;
}
}
/// <summary>
/// Gets the list of target information
/// </summary>
public PathTargetInfo[] TargetsInfo { get; } = new PathTargetInfo[0];
/// <summary>
/// Applies an array of paths
/// </summary>
/// <param name="pathInfos">The array of paths</param>
/// <param name="allowChanges">true to allow changes and reordering of the provided paths, otherwise false</param>
/// <param name="saveToDatabase">true to save the paths to the persistence database if call succeed, otherwise false</param>
/// <param name="forceModeEnumeration">true to force driver mode enumeration before applying the paths</param>
/// <exception cref="PathChangeException">Error in changing paths</exception>
public static void ApplyPathInfos(
IEnumerable<PathInfo> 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)
);
}
}
/// <summary>
/// Applies a saved topology
/// </summary>
/// <param name="topology">The topology identification to apply</param>
/// <param name="allowPersistence">true to allows persistence of the changes, otherwise false</param>
/// <exception cref="PathChangeException">Error in changing paths</exception>
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)
);
}
}
/// <summary>
/// Retrieves the list of active paths
/// </summary>
/// <param name="virtualModeAware">true if the caller expects virtual mode settings, otherwise false</param>
/// <returns>An array of PathInfos</returns>
public static PathInfo[] GetActivePaths(bool virtualModeAware = false)
{
return GetPathInfos(
virtualModeAware
? QueryDeviceConfigFlags.OnlyActivePaths | QueryDeviceConfigFlags.VirtualModeAware
: QueryDeviceConfigFlags.OnlyActivePaths,
out _
);
}
/// <summary>
/// Retrieves the list of all paths, active or inactive
/// </summary>
/// <param name="virtualModeAware">true if the caller expects virtual mode settings, otherwise false</param>
/// <returns>An array of PathInfos</returns>
public static PathInfo[] GetAllPaths(bool virtualModeAware = false)
{
return GetPathInfos(
virtualModeAware
? QueryDeviceConfigFlags.AllPaths | QueryDeviceConfigFlags.VirtualModeAware
: QueryDeviceConfigFlags.AllPaths,
out _
);
}
/// <summary>
/// Retrieves the list of currently active topology paths
/// </summary>
/// <returns>An array of PathInfos</returns>
public static PathInfo[] GetCurrentDatabasePaths()
{
return GetPathInfos(QueryDeviceConfigFlags.DatabaseCurrent, out _);
}
/// <summary>
/// Gets the current active topology identification
/// </summary>
/// <returns>The topology identification</returns>
public static DisplayConfigTopologyId GetCurrentTopology()
{
GetPathInfos(QueryDeviceConfigFlags.DatabaseCurrent, out var currentDatabaseType);
return currentDatabaseType;
}
/// <summary>
/// Validates an array of paths before applying
/// </summary>
/// <param name="pathInfos">The array of paths</param>
/// <param name="allowChanges">true to allow changes and reordering of the provided paths, otherwise false</param>
/// <returns>true if the provided paths are valid, otherwise false</returns>
public static bool ValidatePathInfos(IEnumerable<PathInfo> 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;
}
/// <summary>
/// Validates a topology before applying
/// </summary>
/// <param name="topology">The topology identification</param>
/// <returns>true if topology is applicable, otherwise false</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
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<DisplayConfigModeInfo> 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<PathInfo> pathInfos,
out DisplayConfigModeInfo[] modeInfos)
{
var displayConfigPathInfos = new List<DisplayConfigPathInfo>();
var displayConfigModeInfos = new List<DisplayConfigModeInfo>();
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();
}
/// <inheritdoc />
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;
}
}
}

View File

@@ -0,0 +1,140 @@
using System;
using System.Drawing;
using WindowsDisplayAPI.Native.DisplayConfig.Structures;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.DisplayConfig
{
/// <summary>
/// Contains information about the target desktop image
/// </summary>
public class PathTargetDesktopImage : IEquatable<PathTargetDesktopImage>
{
/// <summary>
/// Creates a new PathTargetDesktopImage
/// </summary>
/// <param name="monitorSurfaceSize">Size of the VidPn source surface that is being displayed on the monitor</param>
/// <param name="imageRegion">
/// Where the desktop image will be positioned within monitor surface size. Region must be
/// completely inside the bounds of the monitor surface size.
/// </param>
/// <param name="imageClip">
/// Which part of the desktop image for this clone group will be displayed on this path. This
/// currently must be set to the desktop size.
/// </param>
public PathTargetDesktopImage(Size monitorSurfaceSize, Rectangle imageRegion, Rectangle imageClip)
{
ImageClip = imageClip;
ImageRegion = imageRegion;
MonitorSurfaceSize = monitorSurfaceSize;
}
internal PathTargetDesktopImage(DisplayConfigDesktopImageInfo desktopImage)
{
MonitorSurfaceSize = desktopImage.PathSourceSize.ToSize();
ImageRegion = desktopImage.DesktopImageRegion.ToRectangle();
ImageClip = desktopImage.DesktopImageClip.ToRectangle();
}
/// <summary>
/// Gets part of the desktop image for this clone group that will be displayed on this path. This currently must be set
/// to the desktop size.
/// </summary>
public Rectangle ImageClip { get; }
/// <summary>
/// Gets the part that the desktop image will be positioned within monitor surface size. Region must be completely
/// inside the bounds of the monitor surface size.
/// </summary>
public Rectangle ImageRegion { get; }
/// <summary>
/// Gets the size of the VidPn source surface that is being displayed on the monitor
/// </summary>
public Size MonitorSurfaceSize { get; }
/// <inheritdoc />
public bool Equals(PathTargetDesktopImage other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return ImageClip == other.ImageClip &&
ImageRegion == other.ImageRegion &&
MonitorSurfaceSize == other.MonitorSurfaceSize;
}
/// <summary>
/// Checks for equality of two PathTargetDesktopImage instances
/// </summary>
/// <param name="left">The first instance</param>
/// <param name="right">The second instance</param>
/// <returns>true if both instances are equal, otherwise false</returns>
public static bool operator ==(PathTargetDesktopImage left, PathTargetDesktopImage right)
{
return Equals(left, right) || left?.Equals(right) == true;
}
/// <summary>
/// Checks for inequality of two PathTargetDesktopImage instances
/// </summary>
/// <param name="left">The first instance</param>
/// <param name="right">The second instance</param>
/// <returns>true if both instances are not equal, otherwise false</returns>
public static bool operator !=(PathTargetDesktopImage left, PathTargetDesktopImage right)
{
return !(left == right);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
return obj.GetType() == GetType() && Equals((PathTargetDesktopImage) obj);
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
var hashCode = ImageClip.GetHashCode();
hashCode = (hashCode * 397) ^ ImageRegion.GetHashCode();
hashCode = (hashCode * 397) ^ MonitorSurfaceSize.GetHashCode();
return hashCode;
}
}
/// <inheritdoc />
public override string ToString()
{
return $"{ImageClip} => {ImageRegion} @ {MonitorSurfaceSize}";
}
internal DisplayConfigDesktopImageInfo GetDisplayConfigDesktopImageInfo()
{
return new DisplayConfigDesktopImageInfo(
new PointL(MonitorSurfaceSize),
new RectangleL(ImageRegion),
new RectangleL(ImageClip)
);
}
}
}

View File

@@ -0,0 +1,330 @@
using WindowsDisplayAPI.Exceptions;
using WindowsDisplayAPI.Native.DisplayConfig;
using WindowsDisplayAPI.Native.DisplayConfig.Structures;
namespace WindowsDisplayAPI.DisplayConfig
{
/// <summary>
/// Represents a path and its target
/// </summary>
public class PathTargetInfo
{
private readonly PathTargetDesktopImage _desktopImage;
private readonly PathTargetSignalInfo _signalInfo;
/// <summary>
/// Creates a new PathTargetInfo
/// </summary>
/// <param name="displayTarget">The display target device</param>
/// <param name="isVirtualModeSupported">A boolean value indicating the target virtual mode support</param>
public PathTargetInfo(PathDisplayTarget displayTarget, bool isVirtualModeSupported = false)
{
DisplayTarget = displayTarget;
IsVirtualModeSupportedByPath = isVirtualModeSupported;
}
/// <summary>
/// Creates a new PathTargetInfo
/// </summary>
/// <param name="displayTarget">The display target device</param>
/// <param name="frequencyInMillihertz">Display frequency in millihertz</param>
/// <param name="scanLineOrdering">Display scan line ordering</param>
/// <param name="rotation">Display rotation</param>
/// <param name="scaling">Display scaling</param>
/// <param name="isVirtualModeSupported">A boolean value indicating the target virtual mode support</param>
public PathTargetInfo(
PathDisplayTarget displayTarget,
ulong frequencyInMillihertz,
DisplayConfigScanLineOrdering scanLineOrdering = DisplayConfigScanLineOrdering.NotSpecified,
DisplayConfigRotation rotation = DisplayConfigRotation.NotSpecified,
DisplayConfigScaling scaling = DisplayConfigScaling.Preferred,
bool isVirtualModeSupported = false
) : this(displayTarget, isVirtualModeSupported)
{
FrequencyInMillihertz = frequencyInMillihertz;
ScanLineOrdering = scanLineOrdering;
Rotation = rotation;
Scaling = scaling;
}
/// <summary>
/// Creates a new PathTargetInfo
/// </summary>
/// <param name="displayTarget">The display target device</param>
/// <param name="signalInfo">The display signal information</param>
/// <param name="isVirtualModeSupported">A boolean value indicating the target virtual mode support</param>
public PathTargetInfo(
PathDisplayTarget displayTarget,
PathTargetSignalInfo signalInfo,
bool isVirtualModeSupported = false
) : this(displayTarget, isVirtualModeSupported)
{
_signalInfo = signalInfo;
FrequencyInMillihertz = signalInfo.VerticalSyncFrequencyInMillihertz;
ScanLineOrdering = signalInfo.ScanLineOrdering;
IsSignalInformationAvailable = true;
}
/// <summary>
/// Creates a new PathTargetInfo
/// </summary>
/// <param name="displayTarget">The display target device</param>
/// <param name="signalInfo">The display signal information</param>
/// <param name="rotation">Display rotation</param>
/// <param name="scaling">Display scaling</param>
/// <param name="isVirtualModeSupported">A boolean value indicating the target virtual mode support</param>
public PathTargetInfo(
PathDisplayTarget displayTarget,
PathTargetSignalInfo signalInfo,
DisplayConfigRotation rotation = DisplayConfigRotation.NotSpecified,
DisplayConfigScaling scaling = DisplayConfigScaling.Preferred,
bool isVirtualModeSupported = false
) : this(
displayTarget,
0,
DisplayConfigScanLineOrdering.NotSpecified,
rotation,
scaling,
isVirtualModeSupported
)
{
_signalInfo = signalInfo;
FrequencyInMillihertz = signalInfo.VerticalSyncFrequencyInMillihertz;
ScanLineOrdering = signalInfo.ScanLineOrdering;
IsSignalInformationAvailable = true;
}
/// <summary>
/// Creates a new PathTargetInfo
/// </summary>
/// <param name="displayTarget">The display target device</param>
/// <param name="signalInfo">The display signal information</param>
/// <param name="desktopImage">The display desktop image information</param>
/// <param name="isVirtualModeSupported">A boolean value indicating the target virtual mode support</param>
public PathTargetInfo(
PathDisplayTarget displayTarget,
PathTargetSignalInfo signalInfo,
PathTargetDesktopImage desktopImage,
bool isVirtualModeSupported = false
) : this(displayTarget, signalInfo, isVirtualModeSupported)
{
_desktopImage = desktopImage;
IsDesktopImageInformationAvailable = true;
}
/// <summary>
/// Creates a new PathTargetInfo
/// </summary>
/// <param name="displayTarget">The display target device</param>
/// <param name="signalInfo">The display signal information</param>
/// <param name="desktopImage">The display desktop image information</param>
/// <param name="rotation">Display rotation</param>
/// <param name="scaling">Display scaling</param>
/// <param name="isVirtualModeSupported">A boolean value indicating the target virtual mode support</param>
public PathTargetInfo(
PathDisplayTarget displayTarget,
PathTargetSignalInfo signalInfo,
PathTargetDesktopImage desktopImage,
DisplayConfigRotation rotation = DisplayConfigRotation.NotSpecified,
DisplayConfigScaling scaling = DisplayConfigScaling.Preferred,
bool isVirtualModeSupported = false
) : this(displayTarget, signalInfo, rotation, scaling, isVirtualModeSupported)
{
_desktopImage = desktopImage;
IsDesktopImageInformationAvailable = true;
}
internal PathTargetInfo(
DisplayConfigPathInfoFlags pathFlags,
DisplayConfigPathTargetInfo targetInfo,
DisplayConfigTargetMode? targetMode,
DisplayConfigDesktopImageInfo? desktopImageMode
)
{
IsPathActive = pathFlags.HasFlag(DisplayConfigPathInfoFlags.Active);
IsVirtualModeSupportedByPath = pathFlags.HasFlag(DisplayConfigPathInfoFlags.SupportVirtualMode);
DisplayTarget = new PathDisplayTarget(
new PathDisplayAdapter(targetInfo.AdapterId),
targetInfo.TargetId,
targetInfo.TargetAvailable
);
OutputTechnology = targetInfo.OutputTechnology;
Rotation = targetInfo.Rotation;
Scaling = targetInfo.Scaling;
ScanLineOrdering = targetInfo.ScanLineOrdering;
FrequencyInMillihertz = targetInfo.RefreshRate.ToValue(1000);
ForcedBootAvailability = targetInfo.StatusFlags.HasFlag(
DisplayConfigPathTargetInfoFlags.AvailabilityBoot
);
ForcedPathAvailability = targetInfo.StatusFlags.HasFlag(
DisplayConfigPathTargetInfoFlags.AvailabilityPath
);
ForcedSystemAvailability = targetInfo.StatusFlags.HasFlag(
DisplayConfigPathTargetInfoFlags.AvailabilitySystem
);
IsCurrentlyInUse = targetInfo.StatusFlags.HasFlag(
DisplayConfigPathTargetInfoFlags.InUse
);
IsForcible = targetInfo.StatusFlags.HasFlag(
DisplayConfigPathTargetInfoFlags.Forcible
);
IsSignalInformationAvailable = targetMode.HasValue;
if (targetMode.HasValue)
{
_signalInfo = new PathTargetSignalInfo(targetMode.Value.TargetVideoSignalInfo);
}
IsDesktopImageInformationAvailable = desktopImageMode.HasValue;
if (desktopImageMode.HasValue)
{
_desktopImage = new PathTargetDesktopImage(desktopImageMode.Value);
}
}
/// <summary>
/// Gets an instance of PathTargetDesktopImage containing information about this target desktop image
/// </summary>
/// <exception cref="MissingModeException">Target mode information is missing</exception>
public PathTargetDesktopImage DesktopImage
{
get
{
if (!IsDesktopImageInformationAvailable)
{
throw new MissingModeException(
"Desktop image information is missing or not available.",
DisplayConfigModeInfoType.DesktopImage
);
}
return _desktopImage;
}
}
/// <summary>
/// Gets extra information about the representing display target device
/// </summary>
public PathDisplayTarget DisplayTarget { get; }
/// <summary>
/// Gets a boolean value indicating that the output is currently being forced in a boot-persistent manner
/// </summary>
public bool ForcedBootAvailability { get; }
/// <summary>
/// Gets a boolean value indicating that the output is currently being forced in a path-persistent manner
/// </summary>
public bool ForcedPathAvailability { get; }
/// <summary>
/// Gets a boolean value indicating that the output is currently being forced in a non-persistent manner
/// </summary>
public bool ForcedSystemAvailability { get; }
/// <summary>
/// Gets a value that specifies the refresh rate of the target
/// </summary>
public ulong FrequencyInMillihertz { get; }
/// <summary>
/// Gets a boolean value indicating if the target is in use on an active path
/// </summary>
public bool IsCurrentlyInUse { get; }
/// <summary>
/// Gets a boolean value indicating the presence of the desktop image information
/// </summary>
public bool IsDesktopImageInformationAvailable { get; }
/// <summary>
/// Gets a boolean value indicating that the output can be forced on this target even if a monitor is not detected
/// </summary>
public bool IsForcible { get; }
/// <summary>
/// Gets a boolean value indicating if this path is or should be active
/// </summary>
public bool IsPathActive { get; } = true;
/// <summary>
/// Gets a boolean value indicating the presence of the signal information
/// </summary>
public bool IsSignalInformationAvailable { get; }
/// <summary>
/// Gets a boolean value that indicates if the path supports virtual mode
/// </summary>
public bool IsVirtualModeSupportedByPath { get; }
/// <summary>
/// Gets the type of the display device connection
/// </summary>
public DisplayConfigVideoOutputTechnology OutputTechnology { get; } = DisplayConfigVideoOutputTechnology.Other;
/// <summary>
/// Gets the rotation of the target
/// </summary>
public DisplayConfigRotation Rotation { get; }
/// <summary>
/// Gets the value that specifies how the source image is scaled to the target
/// </summary>
public DisplayConfigScaling Scaling { get; }
/// <summary>
/// Gets the value that specifies the scan-line ordering of the output on the target
/// </summary>
public DisplayConfigScanLineOrdering ScanLineOrdering { get; }
/// <summary>
/// Gets the target device signal information
/// </summary>
/// <exception cref="MissingModeException">Target mode information is missing</exception>
public PathTargetSignalInfo SignalInfo
{
get
{
if (!IsSignalInformationAvailable)
{
throw new MissingModeException(
"Target mode information is missing or not available.",
DisplayConfigModeInfoType.Target
);
}
return _signalInfo;
}
}
/// <inheritdoc />
public override string ToString()
{
return $"{DisplayTarget}: {FrequencyInMillihertz / 1000}hz{(IsCurrentlyInUse ? " [In Use]" : "")}";
}
internal DisplayConfigDesktopImageInfo? GetDisplayConfigDesktopImageInfo()
{
if (IsDesktopImageInformationAvailable)
{
return DesktopImage.GetDisplayConfigDesktopImageInfo();
}
return null;
}
internal DisplayConfigTargetMode? GetDisplayConfigTargetMode()
{
if (IsSignalInformationAvailable)
{
return new DisplayConfigTargetMode(SignalInfo.GetDisplayConfigVideoSignalInfo());
}
return null;
}
}
}

View File

@@ -0,0 +1,220 @@
using System;
using System.Drawing;
using WindowsDisplayAPI.Native.DisplayConfig;
using WindowsDisplayAPI.Native.DisplayConfig.Structures;
namespace WindowsDisplayAPI.DisplayConfig
{
/// <summary>
/// Contains information about the target signal info
/// </summary>
public class PathTargetSignalInfo : IEquatable<PathTargetSignalInfo>
{
/// <summary>
/// Creates a new PathTargetSignalInfo
/// </summary>
/// <param name="activeSize">Specifies the width and height (in pixels) of the active portion of the video signal.</param>
/// <param name="totalSize">Specifies the width and height (in pixels) of the entire video signal.</param>
/// <param name="verticalSyncFrequencyInMillihertz">Vertical synchronization frequency.</param>
/// <param name="scanLineOrdering">The scan-line ordering (for example, progressive or interlaced) of the video signal.</param>
/// <param name="videoStandard">
/// The video standard (if any) that defines the video signal. Supported by WDDM 1.3 and later
/// display mini-port drivers running on Windows 8.1 and later.
/// </param>
/// <param name="verticalSyncFrequencyDivider">
/// The ratio of the VSync rate of a monitor that displays through a Miracast
/// connected session to the VSync rate of the Miracast sink. The ratio of the VSync rate of a monitor that displays
/// through a Miracast connected session to the VSync rate of the Miracast sink. Supported by WDDM 1.3 and later
/// display mini-port drivers running on Windows 8.1 and later.
/// </param>
public PathTargetSignalInfo(
Size activeSize,
Size totalSize,
ulong verticalSyncFrequencyInMillihertz,
DisplayConfigScanLineOrdering scanLineOrdering,
VideoSignalStandard videoStandard = VideoSignalStandard.Uninitialized,
ushort verticalSyncFrequencyDivider = 0
)
{
ActiveSize = activeSize;
ScanLineOrdering = scanLineOrdering;
TotalSize = totalSize;
VerticalSyncFrequencyDivider = verticalSyncFrequencyDivider;
VerticalSyncFrequencyInMillihertz = verticalSyncFrequencyInMillihertz;
VideoStandard = videoStandard;
PixelRate = (ulong) (verticalSyncFrequencyInMillihertz / 1000d * totalSize.Width * totalSize.Height);
HorizontalSyncFrequencyInMillihertz = (ulong) totalSize.Height * verticalSyncFrequencyInMillihertz;
}
/// <summary>
/// Creates a new PathTargetSignalInfo
/// </summary>
/// <param name="displaySetting">A possible display settings</param>
/// <param name="totalSignalSize">Total signal size</param>
public PathTargetSignalInfo(DisplayPossibleSetting displaySetting, Size totalSignalSize) :
this(
displaySetting.Resolution, totalSignalSize,
(uint) (displaySetting.Frequency * 1000),
displaySetting.IsInterlaced
? DisplayConfigScanLineOrdering.InterlacedWithUpperFieldFirst
: DisplayConfigScanLineOrdering.Progressive
)
{
}
internal PathTargetSignalInfo(DisplayConfigVideoSignalInfo signalInfo)
{
PixelRate = signalInfo.PixelRate;
HorizontalSyncFrequencyInMillihertz = signalInfo.HorizontalSyncFrequency.ToValue(1000);
VerticalSyncFrequencyInMillihertz = signalInfo.VerticalSyncFrequency.ToValue(1000);
ActiveSize = new Size((int) signalInfo.ActiveSize.Width, (int) signalInfo.ActiveSize.Height);
TotalSize = new Size((int) signalInfo.TotalSize.Width, (int) signalInfo.TotalSize.Height);
VideoStandard = signalInfo.VideoStandard;
VerticalSyncFrequencyDivider = signalInfo.VerticalSyncFrequencyDivider;
ScanLineOrdering = signalInfo.ScanLineOrdering;
}
/// <summary>
/// Gets the width and height (in pixels) of the active portion of the video signal
/// </summary>
public Size ActiveSize { get; }
/// <summary>
/// Gets the horizontal synchronization frequency
/// </summary>
public ulong HorizontalSyncFrequencyInMillihertz { get; }
/// <summary>
/// Gets the pixel clock rate
/// </summary>
public ulong PixelRate { get; }
/// <summary>
/// Gets the scan-line ordering (for example, progressive or interlaced) of the video signal
/// </summary>
public DisplayConfigScanLineOrdering ScanLineOrdering { get; }
/// <summary>
/// Gets the width and height (in pixels) of the entire video signal
/// </summary>
public Size TotalSize { get; }
/// <summary>
/// Gets the ratio of the VSync rate of a monitor that displays through a Miracast connected session to the VSync rate
/// of the Miracast sink. The ratio of the VSync rate of a monitor that displays through a Miracast connected session
/// to the VSync rate of the Miracast sink. Supported by WDDM 1.3 and later display mini-port drivers running on Windows
/// 8.1 and later
/// </summary>
public ushort VerticalSyncFrequencyDivider { get; }
/// <summary>
/// Gets the vertical synchronization frequency
/// </summary>
public ulong VerticalSyncFrequencyInMillihertz { get; }
/// <summary>
/// Gets the video standard (if any) that defines the video signal. Supported by WDDM 1.3 and later display mini-port
/// drivers running on Windows 8.1 and later
/// </summary>
public VideoSignalStandard VideoStandard { get; }
/// <inheritdoc />
public bool Equals(PathTargetSignalInfo other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return ActiveSize == other.ActiveSize &&
HorizontalSyncFrequencyInMillihertz == other.HorizontalSyncFrequencyInMillihertz &&
PixelRate == other.PixelRate &&
ScanLineOrdering == other.ScanLineOrdering &&
TotalSize == other.TotalSize &&
VerticalSyncFrequencyDivider == other.VerticalSyncFrequencyDivider &&
VerticalSyncFrequencyInMillihertz == other.VerticalSyncFrequencyInMillihertz &&
VideoStandard == other.VideoStandard;
}
/// <summary>
/// Checks for equality of two PathTargetSignalInfo instances
/// </summary>
/// <param name="left">The first instance</param>
/// <param name="right">The second instance</param>
/// <returns>true if both instances are equal, otherwise false</returns>
public static bool operator ==(PathTargetSignalInfo left, PathTargetSignalInfo right)
{
return Equals(left, right) || left?.Equals(right) == true;
}
/// <summary>
/// Checks for inequality of two PathTargetSignalInfo instances
/// </summary>
/// <param name="left">The first instance</param>
/// <param name="right">The second instance</param>
/// <returns>true if both instances are not equal, otherwise false</returns>
public static bool operator !=(PathTargetSignalInfo left, PathTargetSignalInfo right)
{
return !(left == right);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
return obj.GetType() == GetType() && Equals((PathTargetSignalInfo) obj);
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
var hashCode = ActiveSize.GetHashCode();
hashCode = (hashCode * 397) ^ (int) HorizontalSyncFrequencyInMillihertz;
hashCode = (hashCode * 397) ^ PixelRate.GetHashCode();
hashCode = (hashCode * 397) ^ (int) ScanLineOrdering;
hashCode = (hashCode * 397) ^ TotalSize.GetHashCode();
hashCode = (hashCode * 397) ^ VerticalSyncFrequencyDivider.GetHashCode();
hashCode = (hashCode * 397) ^ (int) VerticalSyncFrequencyInMillihertz;
hashCode = (hashCode * 397) ^ (int) VideoStandard;
return hashCode;
}
}
/// <inheritdoc />
public override string ToString()
{
return $"{ActiveSize} @ {VerticalSyncFrequencyInMillihertz / 1000}hz {VideoStandard}";
}
internal DisplayConfigVideoSignalInfo GetDisplayConfigVideoSignalInfo()
{
return new DisplayConfigVideoSignalInfo(
PixelRate,
new DisplayConfigRational(HorizontalSyncFrequencyInMillihertz, 1000, true),
new DisplayConfigRational(VerticalSyncFrequencyInMillihertz, 1000, true),
new DisplayConfig2DRegion((uint) ActiveSize.Width, (uint) ActiveSize.Height),
new DisplayConfig2DRegion((uint) TotalSize.Width, (uint) TotalSize.Height),
VideoStandard,
VerticalSyncFrequencyDivider,
ScanLineOrdering
);
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
namespace WindowsDisplayAPI
{
/// <summary>
/// Contains possible curve drawing capabilities of a display device
/// </summary>
[Flags]
public enum DisplayCurveCapabilities
{
/// <summary>
/// Device does not support curves.
/// </summary>
None = 0,
/// <summary>
/// Device can draw circles.
/// </summary>
Circles = 1,
/// <summary>
/// Device can draw pie wedges.
/// </summary>
Pie = 2,
/// <summary>
/// Device can draw chord arcs.
/// </summary>
Chord = 4,
/// <summary>
/// Device can draw ellipses.
/// </summary>
Ellipses = 8,
/// <summary>
/// Device can draw wide borders.
/// </summary>
Wide = 16,
/// <summary>
/// Device can draw styled borders.
/// </summary>
Styled = 32,
/// <summary>
/// Device can draw borders that are wide and styled.
/// </summary>
WideStyled = 64,
/// <summary>
/// Device can draw interiors.
/// </summary>
Interiors = 128,
/// <summary>
/// Device can draw rounded rectangles.
/// </summary>
RoundRectangle = 256
}
}

View File

@@ -0,0 +1,143 @@
using System.Linq;
using WindowsDisplayAPI.DisplayConfig;
using WindowsDisplayAPI.Native.DeviceContext;
namespace WindowsDisplayAPI
{
/// <summary>
/// Represents a Windows Display Device
/// </summary>
public class DisplayDevice : Device
{
/// <summary>
/// Creates a new DisplayDevice
/// </summary>
/// <param name="devicePath">The device path</param>
/// <param name="deviceName">The device name</param>
/// <param name="deviceKey">The device driver registry key</param>
protected DisplayDevice(string devicePath, string deviceName, string deviceKey)
: base(devicePath, deviceName, deviceKey)
{
}
/// <summary>
/// Gets the corresponding <see cref="DisplayScreen" /> instance.
/// </summary>
public DisplayScreen DisplayScreen
{
get => DisplayScreen.GetScreens().FirstOrDefault(info => info.ScreenName.Equals(ScreenName));
}
/// <summary>
/// Creates a new DisplayDevice
/// </summary>
/// <param name="devicePath">The device path</param>
/// <param name="deviceName">The device name</param>
/// <param name="deviceKey">The device driver registry key</param>
/// <param name="adapter">The device parent DisplayAdapter</param>
/// <param name="isAvailable">true if the device is attached, otherwise false</param>
/// <param name="isValid">true if this instance is valid, otherwise false</param>
protected DisplayDevice(
string devicePath,
string deviceName,
string deviceKey,
DisplayAdapter adapter,
bool isAvailable,
bool isValid)
: this(devicePath, deviceName, deviceKey)
{
Adapter = adapter;
IsAvailable = isAvailable;
IsValid = isValid;
}
/// <summary>
/// Creates a new DisplayDevice
/// </summary>
/// <param name="devicePath">The device path</param>
/// <param name="deviceName">The device name</param>
/// <param name="deviceKey">The device driver registry key</param>
/// <param name="adapter">The device parent DisplayAdapter</param>
/// <param name="displayName">The device source display name</param>
/// <param name="displayFullName">The device target display name</param>
/// <param name="isAvailable">true if the device is attached, otherwise false</param>
/// <param name="isValid">true if this instance is valid, otherwise false</param>
protected DisplayDevice(
string devicePath,
string deviceName,
string deviceKey,
DisplayAdapter adapter,
string displayName,
string displayFullName,
bool isAvailable,
bool isValid)
: this(devicePath, deviceName, deviceKey, adapter, isAvailable, isValid)
{
ScreenName = displayName;
DisplayName = displayFullName;
}
/// <summary>
/// Gets the display device driving display adapter instance
/// </summary>
public virtual DisplayAdapter Adapter { get; }
/// <summary>
/// Gets the display device target name
/// </summary>
public virtual string DisplayName { get; }
/// <summary>
/// Gets the display device source name
/// </summary>
public virtual string ScreenName { get; }
/// <summary>
/// Gets a boolean value indicating if this display device is currently attached
/// </summary>
public virtual bool IsAvailable { get; }
/// <summary>
/// Gets a boolean value indicating if this instance is no longer valid, this may happen when display device attached
/// status changes
/// </summary>
public virtual bool IsValid { get; }
internal static DisplayDevice FromDeviceInformation(
DisplayAdapter adapter,
Native.DeviceContext.Structures.DisplayDevice sourceDevice,
Native.DeviceContext.Structures.DisplayDevice targetDevice
)
{
return new DisplayDevice(
targetDevice.DeviceId,
targetDevice.DeviceString,
targetDevice.DeviceKey,
adapter,
sourceDevice.DeviceName,
targetDevice.DeviceName,
targetDevice.StateFlags.HasFlag(DisplayDeviceStateFlags.AttachedToDesktop),
true
);
}
/// <inheritdoc />
public override string ToString()
{
return string.IsNullOrWhiteSpace(DeviceName)
? $"{GetType().Name}: {DisplayName} - IsAvailable: {IsAvailable}"
: $"{GetType().Name}: {DisplayName} ({DeviceName}) - IsAvailable: {IsAvailable}";
}
/// <summary>
/// Returns the corresponding PathDisplayTarget instance
/// </summary>
/// <returns>An instance of PathDisplayTarget, or null</returns>
public PathDisplayTarget ToPathDisplayTarget()
{
return PathDisplayTarget
.GetDisplayTargets()
.FirstOrDefault(target => target.DevicePath.Equals(DevicePath));
}
}
}

View File

@@ -0,0 +1,91 @@
using System;
using System.Diagnostics;
using WindowsDisplayAPI.Native.DeviceContext.Structures;
namespace WindowsDisplayAPI
{
public class DisplayGammaRamp
{
public DisplayGammaRamp(ushort[] red, ushort[] green, ushort[] blue)
{
if (red?.Length != GammaRamp.DataPoints)
{
throw new ArgumentOutOfRangeException(nameof(red));
}
if (green?.Length != GammaRamp.DataPoints)
{
throw new ArgumentOutOfRangeException(nameof(green));
}
if (blue?.Length != GammaRamp.DataPoints)
{
throw new ArgumentOutOfRangeException(nameof(blue));
}
Red = red;
Green = green;
Blue = blue;
}
public DisplayGammaRamp(double brightness = 0.5, double contrast = 0.5, double gamma = 1)
: this(
CalculateLUT(brightness, contrast, gamma),
CalculateLUT(brightness, contrast, gamma),
CalculateLUT(brightness, contrast, gamma)
)
{
}
public DisplayGammaRamp(
double redBrightness,
double redContrast,
double redGamma,
double greenBrightness,
double greenContrast,
double greenGamma,
double blueBrightness,
double blueContrast,
double blueGamma
)
: this(
CalculateLUT(redBrightness, redContrast, redGamma),
CalculateLUT(greenBrightness, greenContrast, greenGamma),
CalculateLUT(blueBrightness, blueContrast, blueGamma)
)
{
}
internal DisplayGammaRamp(GammaRamp ramp) :
this(ramp.Red, ramp.Green, ramp.Blue)
{
}
public ushort[] Blue { get; }
public ushort[] Green { get; }
public ushort[] Red { get; }
private static ushort[] CalculateLUT(double brightness, double contrast, double gamma)
{
// Fill the gamma curve
var result = new ushort[GammaRamp.DataPoints];
string bits = "";
for (var i = 0; i < result.Length; i++)
{
result[i] = (ushort)((0.5 + brightness / 2) * ushort.MaxValue * i / (float)(result.Length - 1));
bits += result[i].ToString() + " ";
}
Debug.WriteLine(bits);
return result;
}
internal GammaRamp AsRamp()
{
return new GammaRamp(Red, Green, Blue);
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
namespace WindowsDisplayAPI
{
/// <summary>
/// Contains possible line drawing capabilities of a display device
/// </summary>
[Flags]
public enum DisplayLineCapabilities
{
/// <summary>
/// Device does not support lines.
/// </summary>
None = 0,
/// <summary>
/// Device can draw a poly line.
/// </summary>
PolyLine = 2,
/// <summary>
/// Device can draw a marker.
/// </summary>
Marker = 4,
/// <summary>
/// Device can draw multiple markers.
/// </summary>
PolyMarker = 8,
/// <summary>
/// Device can draw wide lines.
/// </summary>
Wide = 16,
/// <summary>
/// Device can draw styled lines.
/// </summary>
Styled = 32,
/// <summary>
/// Device can draw lines that are wide and styled.
/// </summary>
WideStyled = 64,
/// <summary>
/// Device can draw interiors.
/// </summary>
Interiors = 128
}
}

View File

@@ -0,0 +1,56 @@
using System;
namespace WindowsDisplayAPI
{
/// <summary>
/// Contains possible polygon drawing capabilities of a display device
/// </summary>
[Flags]
public enum DisplayPolygonalCapabilities
{
/// <summary>
/// Device does not support polygons.
/// </summary>
None = 0,
/// <summary>
/// Device can draw alternate-fill polygons.
/// </summary>
Polygon = 1,
/// <summary>
/// Device can draw rectangles.
/// </summary>
Rectangle = 2,
/// <summary>
/// Device can draw winding-fill polygons.
/// </summary>
WindingFillPolygon = 4,
/// <summary>
/// Device can draw a single scan-line.
/// </summary>
ScanLine = 8,
/// <summary>
/// Device can draw wide borders.
/// </summary>
Wide = 16,
/// <summary>
/// Device can draw styled borders.
/// </summary>
Styled = 32,
/// <summary>
/// Device can draw borders that are wide and styled.
/// </summary>
WideStyled = 64,
/// <summary>
/// Device can draw interiors.
/// </summary>
Interiors = 128
}
}

View File

@@ -0,0 +1,63 @@
using System.Drawing;
using WindowsDisplayAPI.Native.DeviceContext;
using WindowsDisplayAPI.Native.DeviceContext.Structures;
namespace WindowsDisplayAPI
{
/// <summary>
/// Represents a possible display setting
/// </summary>
public class DisplayPossibleSetting
{
/// <summary>
/// Creates a new DisplayPossibleSetting
/// </summary>
/// <param name="resolution">Display resolution</param>
/// <param name="frequency">Display frequency</param>
/// <param name="colorDepth">Display color depth</param>
/// <param name="isInterlaced">Indicating if display is using interlaces scan out</param>
protected DisplayPossibleSetting(Size resolution, int frequency, ColorDepth colorDepth, bool isInterlaced)
{
ColorDepth = colorDepth;
Resolution = resolution;
IsInterlaced = isInterlaced;
Frequency = frequency;
}
internal DisplayPossibleSetting(DeviceMode deviceMode)
: this(
new Size((int) deviceMode.PixelsWidth, (int) deviceMode.PixelsHeight),
(int) deviceMode.DisplayFrequency,
(ColorDepth) deviceMode.BitsPerPixel,
deviceMode.DisplayFlags.HasFlag(DisplayFlags.Interlaced)
)
{
}
/// <summary>
/// Gets the color depth of the display monitor in bits per pixel
/// </summary>
public ColorDepth ColorDepth { get; }
/// <summary>
/// Gets the frequency of the display monitor in hz
/// </summary>
public int Frequency { get; }
/// <summary>
/// Gets a boolean value indicating if the display uses the interlaced signal
/// </summary>
public bool IsInterlaced { get; }
/// <summary>
/// Gets the size of the display monitor
/// </summary>
public Size Resolution { get; }
/// <inheritdoc />
public override string ToString()
{
return $"{Resolution} {(IsInterlaced ? "Interlaced" : "Progressive")} {Frequency}hz @ {ColorDepth}";
}
}
}

View File

@@ -0,0 +1,91 @@
using System;
namespace WindowsDisplayAPI
{
/// <summary>
/// Contains possible raster capabilities of a display device
/// </summary>
[Flags]
public enum DisplayRasterCapabilities
{
/// <summary>
/// Capable of transferring bitmaps.
/// </summary>
BitmapTransfer = 1,
/// <summary>
/// Requires banding support.
/// </summary>
RequiresBanding = 2,
/// <summary>
/// Capable of scaling.
/// </summary>
Scaling = 4,
/// <summary>
/// Capable of supporting bitmaps larger than 64 KB.
/// </summary>
Bitmap64K = 8,
/// <summary>
/// Specifies GDI 2.0 compatibility.
/// </summary>
GDI20Output = 16,
/// <summary>
/// Includes a state block in the device context.
/// </summary>
GDI20State = 32,
/// <summary>
/// Capable of saving bitmaps locally in shadow memory.
/// </summary>
SaveBitmap = 64,
/// <summary>
/// Capable of modification and retrieval of display independent bitmap data.
/// </summary>
DeviceIndependentBitmap = 128,
/// <summary>
/// Specifies a palette-based device.
/// </summary>
Palette = 256,
/// <summary>
/// Capable of sending display independent bitmap to device.
/// </summary>
DeviceIndependentBitmapToDevice = 512,
/// <summary>
/// Capable of supporting fonts larger than 64 KB.
/// </summary>
Font64K = 1024,
/// <summary>
/// Capable of stretching bitmaps.
/// </summary>
StretchBitmap = 2048,
/// <summary>
/// Capable of performing flood fills.
/// </summary>
FloodFill = 4096,
/// <summary>
/// Capable of stretching display independent bitmaps.
/// </summary>
StretchDeviceIndependentBitmap = 8192,
/// <summary>
/// Supports transparent bitmap and DirectX arrays.
/// </summary>
DirectXOutput = 16384,
/// <summary>
/// Capable of working with hardware-dependent bitmaps.
/// </summary>
DeviceBits = 32768
}
}

View File

@@ -0,0 +1,299 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using WindowsDisplayAPI.DisplayConfig;
using WindowsDisplayAPI.Exceptions;
using WindowsDisplayAPI.Native;
using WindowsDisplayAPI.Native.DeviceContext;
using WindowsDisplayAPI.Native.DeviceContext.Structures;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI
{
/// <summary>
/// Contains information about a display source screen
/// </summary>
public class DisplayScreen
{
private readonly IntPtr _monitorHandle;
private DisplayScreen(IntPtr monitorHandle)
{
_monitorHandle = monitorHandle;
}
/// <summary>
/// Gets the source identification number
/// </summary>
public int SourceId
{
get
{
var name = ScreenName;
if (string.IsNullOrWhiteSpace(name))
{
return 0;
}
var index = ScreenName.IndexOf("DISPLAY", StringComparison.Ordinal);
return index < 0 ? 0 : int.Parse(name.Substring(index + 7));
}
}
/// <summary>
/// Gets a list of all active screens
/// </summary>
/// <returns>An array of <see cref="DisplayScreen" /> instances.</returns>
public static DisplayScreen[] GetScreens()
{
var result = new List<DisplayScreen>();
var callback = new DeviceContextApi.MonitorEnumProcedure(
(IntPtr handle, IntPtr dcHandle, ref RectangleL rect, IntPtr callbackObject) =>
{
result.Add(new DisplayScreen(handle));
return 1;
}
);
return DeviceContextApi.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, callback, IntPtr.Zero)
? result.ToArray()
: null;
}
/// <summary>
/// Gets a <see cref="DisplaySetting"/> instance representing the screen current settings
/// </summary>
public DisplaySetting CurrentSetting
{
get => DisplaySetting.GetCurrentFromScreenName(ScreenName);
}
/// <summary>
/// Gets a <see cref="DisplaySetting"/> instance representing this screen saved settings
/// </summary>
public DisplaySetting SavedSetting
{
get => DisplaySetting.GetSavedFromScreenName(ScreenName);
}
/// <summary>
/// Disables and detaches all devices connected to this screen
/// </summary>
/// <param name="apply">Indicating if the changes should be applied immediately, recommended value is false</param>
public void Disable(bool apply)
{
SetSettings(new DisplaySetting(), apply);
}
/// <summary>
/// Enables and attach all devices connected to this screen
/// </summary>
/// <param name="displaySetting">The display settings that should be applied while enabling the display device</param>
/// <param name="apply">Indicating if the changes should be applied immediately, recommended value is false</param>
public void Enable(DisplaySetting displaySetting, bool apply = false)
{
SetSettings(displaySetting, apply);
}
/// <summary>
/// Changes the display device settings to a new <see cref="DisplaySetting"/> instance
/// </summary>
/// <param name="displaySetting">The display settings that should be applied</param>
/// <param name="apply">Indicating if the changes should be applied immediately, recommended value is false</param>
public void SetSettings(DisplaySetting displaySetting, bool apply = false)
{
if (!IsValid)
{
throw new InvalidDisplayException(null);
}
displaySetting.Save(ScreenName, apply);
}
/// <summary>
/// Get information about the monitor covering the most of a rectangle.
/// </summary>
/// <param name="rectangle">The rectangle to get the main monitor information for.</param>
/// <returns>An instance of <see cref="DisplayScreen" />.</returns>
public static DisplayScreen FromRectangle(Rectangle rectangle)
{
var monitorHandle = DeviceContextApi.MonitorFromRect(
new RectangleL(rectangle),
MonitorFromFlag.DefaultToNearest
);
return monitorHandle == IntPtr.Zero ? null : new DisplayScreen(monitorHandle);
}
/// <summary>
/// Get information about the monitor containing or the nearest to a point.
/// </summary>
/// <param name="point">The point to get the main monitor information for.</param>
/// <returns>An instance of <see cref="DisplayScreen" />.</returns>
public static DisplayScreen FromPoint(Point point)
{
var monitorHandle = DeviceContextApi.MonitorFromPoint(
new PointL(point),
MonitorFromFlag.DefaultToNearest
);
return monitorHandle == IntPtr.Zero ? null : new DisplayScreen(monitorHandle);
}
/// <summary>
/// Get information about the screen covering the most of a window.
/// </summary>
/// <param name="hWnd">The window handle to get the main screen information for.</param>
/// <returns>An instance of <see cref="DisplayScreen" />.</returns>
public static DisplayScreen FromWindow(IntPtr hWnd)
{
if (hWnd == IntPtr.Zero)
{
throw new ArgumentException("Invalid window handle provided.", nameof(hWnd));
}
var monitorHandle = DeviceContextApi.MonitorFromWindow(
hWnd,
MonitorFromFlag.DefaultToNearest
);
return monitorHandle == IntPtr.Zero ? null : new DisplayScreen(monitorHandle);
}
#if !NETSTANDARD
/// <summary>
/// Returns the corresponding <see cref="System.Windows.Forms.Screen" /> instance
/// </summary>
/// <returns>A instance of Screen object</returns>
public System.Windows.Forms.Screen GetWinFormScreen()
{
if (!IsValid)
throw new Exceptions.InvalidDisplayException();
try
{
return System.Windows.Forms.Screen.AllScreens.FirstOrDefault(screen => screen.DeviceName.Equals(ScreenName));
}
catch
{
// ignored
}
return null;
}
#endif
/// <summary>
/// Get the corresponding <see cref="Display" /> instances.
/// </summary>
/// <returns>An array of <see cref="Display" /> instances.</returns>
public Display[] GetDisplays()
{
return Display.GetDisplays().Where(display => display.ScreenName.Equals(ScreenName)).ToArray();
}
/// <summary>
/// Gets the bounds of the monitor
/// </summary>
public Rectangle Bounds
{
get => GetMonitorInfo()?.Bounds.ToRectangle() ?? Rectangle.Empty;
}
/// <summary>
/// Gets the source name of the screen
/// </summary>
public string ScreenName
{
get => GetMonitorInfo()?.DisplayName;
}
/// <summary>
/// Gets a boolean value indicating if this is the primary display
/// </summary>
public bool IsPrimary
{
get => GetMonitorInfo()?.Flags.HasFlag(MonitorInfoFlags.Primary) ?? false;
}
/// <summary>
/// Gets a boolean value indicating if this instance contains valid information.
/// </summary>
public bool IsValid
{
get => GetMonitorInfo() != null;
}
/// <summary>
/// Gets the working area of the monitor
/// </summary>
public Rectangle WorkingArea
{
get => GetMonitorInfo()?.WorkingArea.ToRectangle() ?? Rectangle.Empty;
}
/// <summary>
/// Returns a list of possible display setting for this screen
/// </summary>
/// <returns>An enumerable list of <see cref="DisplayPossibleSetting"/> instances</returns>
public IEnumerable<DisplayPossibleSetting> GetPossibleSettings()
{
if (!IsValid)
{
yield break;
}
var index = -1;
while (true)
{
index++;
var deviceMode = new DeviceMode(DeviceModeFields.None);
if (!DeviceContextApi.EnumDisplaySettings(ScreenName, (DisplaySettingsMode)index, ref deviceMode))
{
break;
}
yield return new DisplayPossibleSetting(deviceMode);
}
}
/// <summary>
/// Returns the best possible display setting for this screen
/// </summary>
/// <returns>A <see cref="DisplayPossibleSetting"/> instance</returns>
public DisplayPossibleSetting GetPreferredSetting()
{
return IsValid
? GetPossibleSettings()
.OrderByDescending(setting => (int)setting.ColorDepth)
.ThenByDescending(setting => (ulong)setting.Resolution.Width * (ulong)setting.Resolution.Height)
.ThenByDescending(setting => setting.Frequency)
.FirstOrDefault()
: null;
}
/// <summary>
/// Returns the corresponding <see cref="PathDisplaySource"/> instance
/// </summary>
/// <returns>An instance of <see cref="PathDisplaySource"/>, or null</returns>
public PathDisplaySource ToPathDisplaySource()
{
return PathDisplaySource
.GetDisplaySources()
.FirstOrDefault(source => source.DisplayName.Equals(ScreenName));
}
private MonitorInfo? GetMonitorInfo()
{
var monitorInfo = MonitorInfo.Initialize();
if (DeviceContextApi.GetMonitorInfo(_monitorHandle, ref monitorInfo))
{
return monitorInfo;
}
return null;
}
}
}

View File

@@ -0,0 +1,345 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using WindowsDisplayAPI.Exceptions;
using WindowsDisplayAPI.Native;
using WindowsDisplayAPI.Native.DeviceContext;
using WindowsDisplayAPI.Native.DeviceContext.Structures;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI
{
/// <summary>
/// Holds configurations of a windows display
/// </summary>
public class DisplaySetting : DisplayPossibleSetting
{
/// <summary>
/// Creates a new <see cref="DisplaySetting" /> instance.
/// </summary>
/// <param name="validSetting">The basic configuration information object</param>
/// <param name="position">Display position on desktop</param>
public DisplaySetting(DisplayPossibleSetting validSetting, Point position = default)
: this(validSetting, position, DisplayOrientation.Identity, DisplayFixedOutput.Default)
{
}
/// <summary>
/// Creates a new <see cref="DisplaySetting" /> instance.
/// </summary>
/// <param name="validSetting">The basic configuration information object</param>
/// <param name="position">Display position on desktop</param>
/// <param name="orientation">Display orientation and rotation</param>
/// <param name="outputScalingMode">
/// Display output behavior in case of presenting a low-resolution mode on a
/// higher-resolution display
/// </param>
public DisplaySetting(
DisplayPossibleSetting validSetting,
Point position,
DisplayOrientation orientation,
DisplayFixedOutput outputScalingMode)
: this(
validSetting.Resolution, position, validSetting.ColorDepth, validSetting.Frequency,
validSetting.IsInterlaced, orientation, outputScalingMode
)
{
}
/// <summary>
/// Creates a new <see cref="DisplaySetting" /> instance.
/// </summary>
/// <param name="resolution">Display resolution</param>
/// <param name="position">Display position on desktop</param>
/// <param name="frequency">Display frequency</param>
public DisplaySetting(Size resolution, Point position, int frequency)
: this(resolution, position, ColorDepth.Depth32Bit, frequency)
{
}
/// <summary>
/// Creates a new <see cref="DisplaySetting" /> instance.
/// </summary>
/// <param name="resolution">Display resolution</param>
/// <param name="frequency">Display frequency</param>
public DisplaySetting(Size resolution, int frequency)
: this(resolution, new Point(0, 0), ColorDepth.Depth32Bit, frequency)
{
}
/// <summary>
/// Creates a new <see cref="DisplaySetting" /> instance.
/// </summary>
/// <param name="resolution">Display resolution</param>
/// <param name="position">Display position on desktop</param>
/// <param name="frequency">Display frequency</param>
/// <param name="colorDepth">Display color depth</param>
/// <param name="isInterlaced">Indicating if display is using interlaces scan out</param>
/// <param name="orientation">Display orientation and rotation</param>
/// <param name="outputScalingMode">
/// Display output behavior in case of presenting a low-resolution mode on a
/// higher-resolution display
/// </param>
public DisplaySetting(
Size resolution,
Point position,
ColorDepth colorDepth,
int frequency,
bool isInterlaced = false,
DisplayOrientation orientation = DisplayOrientation.Identity,
DisplayFixedOutput outputScalingMode = DisplayFixedOutput.Default
) : base(resolution, frequency, colorDepth, isInterlaced)
{
Position = position;
Orientation = orientation;
OutputScalingMode = outputScalingMode;
}
internal DisplaySetting() : base(default)
{
IsEnable = false;
}
private DisplaySetting(DeviceMode deviceMode) : base(deviceMode)
{
Position = new Point(deviceMode.Position.X, deviceMode.Position.Y);
Orientation = deviceMode.DisplayOrientation;
OutputScalingMode = deviceMode.DisplayFixedOutput;
if (Resolution.IsEmpty && Position.IsEmpty)
{
IsEnable = false;
}
}
/// <summary>
/// Gets a boolean value indicating if this instance is currently enable
/// </summary>
public bool IsEnable { get; } = true;
/// <summary>
/// Gets or sets the orientation of the display monitor
/// </summary>
public DisplayOrientation Orientation { get; }
/// <summary>
/// Gets output behavior in case of presenting a low-resolution mode on a higher-resolution display
/// </summary>
public DisplayFixedOutput OutputScalingMode { get; }
/// <summary>
/// Gets or sets the position of the display monitor
/// </summary>
public Point Position { get; }
/// <summary>
/// Applies settings that are saved using SaveDisplaySettings() or other similar methods but not yet applied
/// </summary>
public static void ApplySavedSettings()
{
var result = DeviceContextApi.ChangeDisplaySettingsEx(
null,
IntPtr.Zero,
IntPtr.Zero,
ChangeDisplaySettingsFlags.Reset,
IntPtr.Zero
);
if (result != ChangeDisplaySettingsExResults.Successful)
{
throw new ModeChangeException($"[{result}]: Applying saved settings failed.", null, result);
}
}
/// <summary>
/// Returns the current display settings of a screen
/// </summary>
/// <param name="screenName">The name of the screen.</param>
/// <returns>An instance of <see cref="DisplaySetting" /></returns>
public static DisplaySetting GetCurrentFromScreenName(string screenName)
{
return new DisplaySetting(GetDeviceMode(screenName, DisplaySettingsMode.CurrentSettings));
}
/// <summary>
/// Returns the saved display settings of a screen
/// </summary>
/// <param name="screenName">The name of the screen.</param>
/// <returns>An instance of <see cref="DisplaySetting" /></returns>
public static DisplaySetting GetSavedFromScreenName(string screenName)
{
return new DisplaySetting(GetDeviceMode(screenName, DisplaySettingsMode.RegistrySettings));
}
/// <summary>
/// Sets and possibility applies a list of display settings
/// </summary>
/// <param name="newSettingPairs">
/// A key value dictionary of <see cref="DisplayDevice" /> and <see cref="DisplaySetting" />
/// instances.
/// </param>
/// <param name="applyNow">Indicating if the changes should be applied immediately, recommended value is false</param>
public static void SaveDisplaySettings(
Dictionary<DisplayScreen, DisplaySetting> newSettingPairs,
bool applyNow)
{
SaveDisplaySettings(
newSettingPairs.ToDictionary(pair => pair.Key.ScreenName, pair => pair.Value),
applyNow,
true
);
}
/// <summary>
/// Sets and possibility applies a list of display settings
/// </summary>
/// <param name="newSettingPairs">A key value dictionary of source ids and <see cref="DisplaySetting" /> instance</param>
/// <param name="applyNow">Indicating if the changes should be applied immediately, recommended value is false</param>
public static void SaveDisplaySettings(
Dictionary<int, DisplaySetting> newSettingPairs,
bool applyNow)
{
SaveDisplaySettings(
newSettingPairs.ToDictionary(pair => $"\\\\.\\DISPLAY{pair.Key:D}", pair => pair.Value),
applyNow,
true
);
}
private static DeviceMode GetDeviceMode(string screenName, DisplaySettingsMode flags)
{
var deviceMode = new DeviceMode(DeviceModeFields.None);
return !string.IsNullOrWhiteSpace(screenName) &&
DeviceContextApi.EnumDisplaySettings(
screenName,
flags,
ref deviceMode
)
? deviceMode
: default;
}
private static void SaveDisplaySettings(
Dictionary<string, DisplaySetting> newSettings,
bool applyNow,
bool retry
)
{
var screens = DisplayScreen.GetScreens()
.Where(screen => screen.IsValid)
.ToList();
var rollbackSettings = screens
.ToDictionary(screen => screen.ScreenName, screen => screen.CurrentSetting);
try
{
foreach (var newSetting in newSettings)
{
screens.Remove(
screens.FirstOrDefault(
screen => screen.ScreenName.Equals(newSetting.Key)
)
);
newSetting.Value.Save(newSetting.Key, false);
}
// Disable missing monitors
foreach (var screen in screens.Where(screen => screen.IsValid))
{
screen.Disable(false);
}
if (applyNow)
{
ApplySavedSettings();
}
}
catch (ModeChangeException)
{
if (retry)
{
SaveDisplaySettings(rollbackSettings, false, false);
}
throw;
}
}
/// <inheritdoc />
public override string ToString()
{
return IsEnable
? $"{Resolution} {(IsInterlaced ? "Interlaced" : "Progressive")} {Frequency}hz @ {ColorDepth} @ {Position}"
: "Disabled";
}
internal void Save(string screenName, bool reset)
{
var deviceMode = GetDeviceMode(screenName);
var flags = ChangeDisplaySettingsFlags.UpdateRegistry | ChangeDisplaySettingsFlags.Global;
flags |= reset ? ChangeDisplaySettingsFlags.Reset : ChangeDisplaySettingsFlags.NoReset;
if (IsEnable && Position.X == 0 && Position.Y == 0)
{
flags |= ChangeDisplaySettingsFlags.SetPrimary;
}
var result = DeviceContextApi.ChangeDisplaySettingsEx(
screenName,
ref deviceMode,
IntPtr.Zero,
flags,
IntPtr.Zero
);
if (result != ChangeDisplaySettingsExResults.Successful)
{
throw new ModeChangeException($"[{result}]: Applying saved settings failed.", null, result);
}
}
private DeviceMode GetDeviceMode(string screenName)
{
DeviceMode deviceMode;
if (IsEnable)
{
var flags = DisplayFlags.None;
if (IsInterlaced)
{
flags |= DisplayFlags.Interlaced;
}
deviceMode = new DeviceMode(
screenName,
new PointL(Position),
Orientation,
OutputScalingMode,
(uint) ColorDepth,
(uint) Resolution.Width,
(uint) Resolution.Height,
flags,
(uint) Frequency
);
}
else
{
deviceMode = new DeviceMode(
screenName,
DeviceModeFields.PelsWidth | DeviceModeFields.PelsHeight | DeviceModeFields.Position
);
}
if (string.IsNullOrWhiteSpace(deviceMode.DeviceName))
{
throw new MissingDisplayException("Display screen is missing or invalid.", null);
}
return deviceMode;
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
namespace WindowsDisplayAPI
{
/// <summary>
/// Contains possible shader blending capabilities of a display device
/// </summary>
[Flags]
public enum DisplayShaderBlendingCapabilities
{
/// <summary>
/// Device does not support any of these capabilities.
/// </summary>
None = 0,
/// <summary>
/// Capable of handling constant alpha
/// </summary>
ConstantAlpha = 1,
/// <summary>
/// Capable of handling per-pixel alpha.
/// </summary>
PerPixelAlpha = 2,
/// <summary>
/// Capable of handling pre-multiplied alpha
/// </summary>
PreMultipliedAlpha = 4,
/// <summary>
/// Capable of doing gradient fill rectangles.
/// </summary>
RectangleGradient = 16,
/// <summary>
/// Capable of doing gradient fill triangles.
/// </summary>
TriangleGradient = 32
}
}

View File

@@ -0,0 +1,91 @@
using System;
namespace WindowsDisplayAPI
{
/// <summary>
/// Contains possible text drawing capabilities of a display device
/// </summary>
[Flags]
public enum DisplayTextCapabilities
{
/// <summary>
/// Device is capable of character output precision.
/// </summary>
CharacterOutputPrecision = 1,
/// <summary>
/// Device is capable of stroke output precision.
/// </summary>
StrokeOutputPrecision = 2,
/// <summary>
/// Device is capable of stroke clip precision.
/// </summary>
StrokeClipPrecision = 4,
/// <summary>
/// Device is capable of 90-degree character rotation.
/// </summary>
CharacterRotation90 = 8,
/// <summary>
/// Device is capable of any character rotation.
/// </summary>
CharacterRotationAny = 16,
/// <summary>
/// Device can scale independently in the x-direction and y-direction.
/// </summary>
IndependentXYScaling = 32,
/// <summary>
/// Device is capable of doubled character for scaling.
/// </summary>
DoubleCharacterScaling = 64,
/// <summary>
/// Device uses integer multiples only for character scaling.
/// </summary>
IntegerCharacterScaling = 128,
/// <summary>
/// Device uses any multiples for exact character scaling.
/// </summary>
ExactCharacterScaling = 256,
/// <summary>
/// Device can draw double-weight characters.
/// </summary>
DoubleWeightCharacter = 512,
/// <summary>
/// Device can italicize.
/// </summary>
CanItalicize = 1024,
/// <summary>
/// Device can underline.
/// </summary>
CanUnderline = 2048,
/// <summary>
/// Device can draw strikeouts.
/// </summary>
CanStrikeout = 4096,
/// <summary>
/// Device can draw raster fonts.
/// </summary>
RasterFonts = 8192,
/// <summary>
/// Device can draw vector fonts.
/// </summary>
VectorFonts = 16384,
/// <summary>
/// Device cannot scroll using a bit-block transfer. Note that this meaning may be the opposite of what you expect.
/// </summary>
BitBlockTransferScrollInAbility = 65536
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace WindowsDisplayAPI.Exceptions
{
/// <summary>
/// Represents errors that occurs because of two similar but not identical path or path target
/// </summary>
public class DuplicateModeException : Exception
{
/// <summary>
/// Creates a new DuplicateModeException exception
/// </summary>
/// <param name="message">The human readable message of the exception</param>
public DuplicateModeException(string message) : base(message)
{
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
namespace WindowsDisplayAPI.Exceptions
{
/// <summary>
/// Represents errors that occurs because of an invalid display instance
/// </summary>
public class InvalidDisplayException : Exception
{
/// <summary>
/// Creates a new InvalidDisplayException
/// </summary>
/// <param name="displayPath">The path of invalidated display device</param>
public InvalidDisplayException(string displayPath)
{
DisplayPath = displayPath;
}
/// <summary>
/// Creates a new InvalidDisplayException
/// </summary>
public InvalidDisplayException() : this(null)
{
}
/// <summary>
/// Gets the path of the display device
/// </summary>
public string DisplayPath { get; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace WindowsDisplayAPI.Exceptions
{
/// <summary>
/// Represents errors that occurs because of missing or invalid EDID information
/// </summary>
public class InvalidEDIDInformation : Exception
{
/// <summary>
/// Creates a new InvalidEDIDInformation exception
/// </summary>
/// <param name="message">The human readable message of the exception</param>
public InvalidEDIDInformation(string message) : base(message)
{
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace WindowsDisplayAPI.Exceptions
{
/// <summary>
/// Represents errors that occurs because of missing or invalid registry address information
/// </summary>
public class InvalidRegistryAddressException : Exception
{
/// <summary>
/// Creates a new InvalidRegistryAddressException exception
/// </summary>
/// <param name="message">The human readable message of the exception</param>
public InvalidRegistryAddressException(string message) : base(message)
{
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace WindowsDisplayAPI.Exceptions
{
/// <summary>
/// Represents errors that occurs because of a missing display
/// </summary>
public class MissingDisplayException : Exception
{
/// <summary>
/// Creates a new MissingDisplayException
/// </summary>
/// <param name="displayPath">The path of missing display device</param>
/// <param name="message">The human readable message of the exception</param>
public MissingDisplayException(string message, string displayPath) : base(message)
{
DisplayPath = displayPath;
}
/// <summary>
/// Gets the path of the display device
/// </summary>
public string DisplayPath { get; }
}
}

View File

@@ -0,0 +1,26 @@
using System;
using WindowsDisplayAPI.Native.DisplayConfig;
namespace WindowsDisplayAPI.Exceptions
{
/// <summary>
/// Represents errors that occurs because of missing mode information
/// </summary>
public class MissingModeException : Exception
{
/// <summary>
/// Creates a new MissingModeException
/// </summary>
/// <param name="missingModeType">The missing mode type</param>
/// <param name="message">The human readable message of the exception</param>
public MissingModeException(string message, DisplayConfigModeInfoType missingModeType) : base(message)
{
MissingModeType = missingModeType;
}
/// <summary>
/// Gets the missing mode type
/// </summary>
public DisplayConfigModeInfoType MissingModeType { get; }
}
}

View File

@@ -0,0 +1,37 @@
using System;
using WindowsDisplayAPI.Native.DeviceContext;
namespace WindowsDisplayAPI.Exceptions
{
/// <summary>
/// Represents errors that occurs during a mode change request
/// </summary>
public class ModeChangeException : Exception
{
/// <summary>
/// Creates a new ModeChangeException
/// </summary>
/// <param name="device">The device responsible for the mode change</param>
/// <param name="errorCode">The error code</param>
/// <param name="message">The human readable message of the exception</param>
public ModeChangeException(
string message,
DisplayDevice device,
ChangeDisplaySettingsExResults errorCode
) : base(message)
{
Device = device;
ErrorCode = errorCode;
}
/// <summary>
/// Gets the display device responsible for the mode change
/// </summary>
public DisplayDevice Device { get; }
/// <summary>
/// Gets the error code
/// </summary>
public ChangeDisplaySettingsExResults ErrorCode { get; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace WindowsDisplayAPI.Exceptions
{
/// <summary>
/// Represents errors that occurs because of not being in a valid clone group
/// </summary>
public class NotACloneMemberException : Exception
{
/// <summary>
/// Creates a new NotACloneMemberException
/// </summary>
/// <param name="message">The human readable message of the exception</param>
public NotACloneMemberException(string message) : base(message)
{
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
namespace WindowsDisplayAPI.Exceptions
{
/// <summary>
/// Represents errors that occurs because of an invalid path request
/// </summary>
public class PathChangeException : Exception
{
/// <summary>
/// Creates a new PathChangeException
/// </summary>
/// <param name="message">The human readable message of the exception</param>
public PathChangeException(string message) : base(message)
{
}
/// <summary>
/// Creates a new PathChangeException
/// </summary>
/// <param name="message">The human readable message of the exception</param>
/// <param name="innerException">The inner causing exception</param>
public PathChangeException(string message, Exception innerException) : base(message, innerException)
{
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Exceptions
{
/// <summary>
/// Represents errors that occurs because of path target being inavailable
/// </summary>
public class TargetNotAvailableException : Exception
{
/// <summary>
/// Creates a new TargetNotAvailableException
/// </summary>
/// <param name="message">The human readable message of the exception</param>
/// <param name="adapterId">The driving adapter's identification</param>
/// <param name="targetId">The target identification number</param>
public TargetNotAvailableException(string message, LUID adapterId, uint targetId) : base(message)
{
AdapterId = adapterId;
TargetId = targetId;
}
/// <summary>
/// Gets the driving adapter's identification
/// </summary>
public LUID AdapterId { get; }
/// <summary>
/// Gets the target's identification number
/// </summary>
public uint TargetId { get; }
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,48 @@
namespace WindowsDisplayAPI.Native.DeviceContext
{
/// <summary>
/// Contains possible values for the result of mode change request
/// </summary>
public enum ChangeDisplaySettingsExResults
{
/// <summary>
/// Completed successfully
/// </summary>
Successful = 0,
/// <summary>
/// Changes needs restart
/// </summary>
Restart = 1,
/// <summary>
/// Failed to change and save setings
/// </summary>
Failed = -1,
/// <summary>
/// Invalid data provide
/// </summary>
BadMode = -2,
/// <summary>
/// Changes not updated
/// </summary>
NotUpdated = -3,
/// <summary>
/// Invalid flags provided
/// </summary>
BadFlags = -4,
/// <summary>
/// Bad parameters provided
/// </summary>
BadParam = -5,
/// <summary>
/// Bad Dual View mode used with mode
/// </summary>
BadDualView = -6
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace WindowsDisplayAPI.Native.DeviceContext
{
[Flags]
internal enum ChangeDisplaySettingsFlags : uint
{
UpdateRegistry = 0x00000001,
Global = 0x00000008,
SetPrimary = 0x00000010,
Reset = 0x40000000,
NoReset = 0x10000000
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Runtime.InteropServices;
namespace WindowsDisplayAPI.Native.DeviceContext
{
internal class DCHandle : SafeHandle
{
private readonly bool _created;
private DCHandle(IntPtr handle, bool created) : base(handle, true)
{
_created = created;
}
public override bool IsInvalid
{
get => handle == IntPtr.Zero;
}
public static DCHandle CreateFromDevice(string screenName, string devicePath)
{
return new DCHandle(
DeviceContextApi.CreateDC(screenName, devicePath, null, IntPtr.Zero),
true
);
}
public static DCHandle CreateFromScreen(string screenName)
{
return CreateFromDevice(screenName, screenName);
}
public static DCHandle CreateFromWindow(IntPtr windowHandle)
{
return new DCHandle(
DeviceContextApi.GetDC(windowHandle),
true
);
}
public static DCHandle CreateGlobal()
{
return new DCHandle(
DeviceContextApi.CreateDC("DISPLAY", null, null, IntPtr.Zero),
true
);
}
protected override bool ReleaseHandle()
{
return _created
? DeviceContextApi.DeleteDC(this.handle)
: DeviceContextApi.ReleaseDC(IntPtr.Zero, this.handle);
}
}
}

View File

@@ -0,0 +1,51 @@
namespace WindowsDisplayAPI.Native.DeviceContext
{
internal enum DeviceCapability
{
DriverVersion = 0,
Technology = 2,
HorizontalSizeInMM = 4,
VerticalSizeInMM = 6,
HorizontalResolution = 8,
VerticalResolution = 10,
BitsPerPixel = 12,
Planes = 14,
NumberOfBrushes = 16,
NumberOfPens = 18,
NumberOfMarkers = 20,
NumberOfFonts = 22,
NumberOfColors = 24,
DeviceDescriptorSize = 26,
CurveCapabilities = 28,
LineCapabilities = 30,
PolygonalCapabilities = 32,
TextCapabilities = 34,
ClipCapabilities = 36,
RasterCapabilities = 38,
HorizontalAspect = 40,
VerticalAspect = 42,
HypotenuseAspect = 44,
//ShadeBlendingCapabilities = 45,
HorizontalLogicalPixels = 88,
VerticalLogicalPixels = 90,
PaletteSize = 104,
ReservedPaletteSize = 106,
ColorResolution = 108,
// Printer Only
PhysicalWidth = 110,
PhysicalHeight = 111,
PhysicalHorizontalMargin = 112,
PhysicalVerticalMargin = 113,
HorizontalScalingFactor = 114,
VerticalScalingFactor = 115,
// Display Only
VerticalRefreshRateInHz = 116,
DesktopVerticalResolution = 117,
DesktopHorizontalResolution = 118,
PreferredBLTAlignment = 119,
ShadeBlendingCapabilities = 120,
ColorManagementCapabilities = 121,
}
}

View File

@@ -0,0 +1,50 @@
using System;
namespace WindowsDisplayAPI.Native.DeviceContext
{
[Flags]
internal enum DeviceModeFields : uint
{
None = 0,
Position = 0x20,
DisplayOrientation = 0x80,
Color = 0x800,
Duplex = 0x1000,
YResolution = 0x2000,
TtOption = 0x4000,
Collate = 0x8000,
FormName = 0x10000,
LogPixels = 0x20000,
BitsPerPixel = 0x40000,
PelsWidth = 0x80000,
PelsHeight = 0x100000,
DisplayFlags = 0x200000,
DisplayFrequency = 0x400000,
DisplayFixedOutput = 0x20000000,
AllDisplay = Position |
DisplayOrientation |
YResolution |
BitsPerPixel |
PelsWidth |
PelsHeight |
DisplayFlags |
DisplayFrequency |
DisplayFixedOutput,
}
}

View File

@@ -0,0 +1,41 @@
using System;
namespace WindowsDisplayAPI.Native.DeviceContext
{
[Flags]
internal enum DisplayDeviceStateFlags : uint
{
/// <summary>
/// The device is part of the desktop.
/// </summary>
AttachedToDesktop = 0x1,
MultiDriver = 0x2,
/// <summary>
/// The device is part of the desktop.
/// </summary>
PrimaryDevice = 0x4,
/// <summary>
/// Represents a pseudo device used to mirror application drawing for remoting or other purposes.
/// </summary>
MirroringDriver = 0x8,
/// <summary>
/// The device is VGA compatible.
/// </summary>
VGACompatible = 0x10,
/// <summary>
/// The device is removable; it cannot be the primary display.
/// </summary>
Removable = 0x20,
/// <summary>
/// The device has more display modes than its output devices support.
/// </summary>
ModesPruned = 0x8000000,
Remote = 0x4000000,
Disconnect = 0x2000000
}
}

View File

@@ -0,0 +1,23 @@
namespace WindowsDisplayAPI.Native.DeviceContext
{
/// <summary>
/// Contains possible values for the display fixed output
/// </summary>
public enum DisplayFixedOutput : uint
{
/// <summary>
/// Default behavior
/// </summary>
Default = 0,
/// <summary>
/// Stretches the output to fit to the display
/// </summary>
Stretch = 1,
/// <summary>
/// Centers the output in the middle of the display
/// </summary>
Center = 2
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace WindowsDisplayAPI.Native.DeviceContext
{
[Flags]
internal enum DisplayFlags : uint
{
None = 0,
Grayscale = 1,
Interlaced = 2
}
}

View File

@@ -0,0 +1,28 @@
namespace WindowsDisplayAPI.Native.DeviceContext
{
/// <summary>
/// Contains possible values for the display orientation
/// </summary>
public enum DisplayOrientation : uint
{
/// <summary>
/// No rotation
/// </summary>
Identity = 0,
/// <summary>
/// 90 degree rotation
/// </summary>
Rotate90Degree = 1,
/// <summary>
/// 180 degree rotation
/// </summary>
Rotate180Degree = 2,
/// <summary>
/// 270 degree rotation
/// </summary>
Rotate270Degree = 3
}
}

View File

@@ -0,0 +1,9 @@
namespace WindowsDisplayAPI.Native.DeviceContext
{
internal enum DisplaySettingsMode
{
CurrentSettings = -1,
RegistrySettings = -2
}
}

View File

@@ -0,0 +1,13 @@
namespace WindowsDisplayAPI.Native.DeviceContext
{
internal enum DisplayTechnology : int
{
Plotter = 0,
RasterDisplay = 1,
RasterPrinter = 2,
RasterCamera = 3,
CharacterStream = 4,
MetaFile = 5,
DisplayFile = 6,
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace WindowsDisplayAPI.Native.DeviceContext
{
internal enum MonitorFromFlag : uint
{
DefaultToNull = 0,
DefaultToPrimary = 1,
DefaultToNearest = 2,
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace WindowsDisplayAPI.Native.DeviceContext
{
[Flags]
internal enum MonitorInfoFlags : uint
{
None = 0,
Primary = 1
}
}

View File

@@ -0,0 +1,133 @@
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DeviceContext.Structures
{
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd183565(v=vs.85).aspx
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
internal struct DeviceMode
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] [FieldOffset(0)]
public readonly string DeviceName;
[MarshalAs(UnmanagedType.U2)] [FieldOffset(32)]
public readonly ushort SpecificationVersion;
[MarshalAs(UnmanagedType.U2)] [FieldOffset(34)]
public readonly ushort DriverVersion;
[MarshalAs(UnmanagedType.U2)] [FieldOffset(36)]
public readonly ushort Size;
[MarshalAs(UnmanagedType.U2)] [FieldOffset(38)]
public readonly ushort DriverExtra;
[MarshalAs(UnmanagedType.U4)] [FieldOffset(40)]
public readonly DeviceModeFields Fields;
[MarshalAs(UnmanagedType.Struct)] [FieldOffset(44)]
public readonly PointL Position;
[MarshalAs(UnmanagedType.U4)] [FieldOffset(52)]
public readonly DisplayOrientation DisplayOrientation;
[MarshalAs(UnmanagedType.U4)] [FieldOffset(56)]
public readonly DisplayFixedOutput DisplayFixedOutput;
[MarshalAs(UnmanagedType.I2)] [FieldOffset(60)]
public readonly short Color;
[MarshalAs(UnmanagedType.I2)] [FieldOffset(62)]
public readonly short Duplex;
[MarshalAs(UnmanagedType.I2)] [FieldOffset(64)]
public readonly short YResolution;
[MarshalAs(UnmanagedType.I2)] [FieldOffset(66)]
public readonly short TrueTypeOption;
[MarshalAs(UnmanagedType.I2)] [FieldOffset(68)]
public readonly short Collate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] [FieldOffset(72)]
private readonly string FormName;
[MarshalAs(UnmanagedType.U2)] [FieldOffset(102)]
public readonly ushort LogicalInchPixels;
[MarshalAs(UnmanagedType.U4)] [FieldOffset(104)]
public readonly uint BitsPerPixel;
[MarshalAs(UnmanagedType.U4)] [FieldOffset(108)]
public readonly uint PixelsWidth;
[MarshalAs(UnmanagedType.U4)] [FieldOffset(112)]
public readonly uint PixelsHeight;
[MarshalAs(UnmanagedType.U4)] [FieldOffset(116)]
public readonly DisplayFlags DisplayFlags;
[MarshalAs(UnmanagedType.U4)] [FieldOffset(120)]
public readonly uint DisplayFrequency;
public DeviceMode(DeviceModeFields fields) : this()
{
SpecificationVersion = 0x0320;
Size = (ushort) Marshal.SizeOf(GetType());
Fields = fields;
}
public DeviceMode(string deviceName, DeviceModeFields fields) : this(fields)
{
DeviceName = deviceName;
}
public DeviceMode(
string deviceName,
PointL position,
DisplayOrientation orientation,
DisplayFixedOutput fixedOutput,
uint bpp,
uint width,
uint height,
DisplayFlags displayFlags,
uint displayFrequency) : this(
deviceName,
DeviceModeFields.Position |
DeviceModeFields.DisplayOrientation |
DeviceModeFields.DisplayFixedOutput |
DeviceModeFields.BitsPerPixel |
DeviceModeFields.PelsWidth |
DeviceModeFields.PelsHeight |
DeviceModeFields.DisplayFlags |
DeviceModeFields.DisplayFrequency
)
{
Position = position;
DisplayOrientation = orientation;
DisplayFixedOutput = fixedOutput;
BitsPerPixel = bpp;
PixelsWidth = width;
PixelsHeight = height;
DisplayFlags = displayFlags;
DisplayFrequency = displayFrequency;
}
public DeviceMode(string deviceName, PointL position, uint bpp, uint width, uint height, uint displayFrequency)
: this(
deviceName,
DeviceModeFields.Position |
DeviceModeFields.BitsPerPixel |
DeviceModeFields.PelsWidth |
DeviceModeFields.PelsHeight |
DeviceModeFields.DisplayFrequency
)
{
Position = position;
BitsPerPixel = bpp;
PixelsWidth = width;
PixelsHeight = height;
DisplayFrequency = displayFrequency;
}
}
}

View File

@@ -0,0 +1,32 @@
using System.Runtime.InteropServices;
namespace WindowsDisplayAPI.Native.DeviceContext.Structures
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct DisplayDevice
{
[MarshalAs(UnmanagedType.U4)] internal uint Size;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public readonly string DeviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public readonly string DeviceString;
[MarshalAs(UnmanagedType.U4)] public readonly DisplayDeviceStateFlags StateFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public readonly string DeviceId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public readonly string DeviceKey;
public static DisplayDevice Initialize()
{
return new DisplayDevice
{
Size = (uint) Marshal.SizeOf(typeof(DisplayDevice))
};
}
}
}

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace WindowsDisplayAPI.Native.DeviceContext.Structures
{
[StructLayout(LayoutKind.Sequential)]
internal struct GammaRamp
{
public const int DataPoints = 256;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = DataPoints)]
public readonly ushort[] Red;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = DataPoints)]
public readonly ushort[] Green;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = DataPoints)]
public readonly ushort[] Blue;
public GammaRamp(ushort[] red, ushort[] green, ushort[] blue)
{
if (red == null)
{
throw new ArgumentNullException(nameof(red));
}
if (green == null)
{
throw new ArgumentNullException(nameof(green));
}
if (blue == null)
{
throw new ArgumentNullException(nameof(blue));
}
if (red.Length != DataPoints)
{
throw new ArgumentOutOfRangeException(nameof(red));
}
if (green.Length != DataPoints)
{
throw new ArgumentOutOfRangeException(nameof(green));
}
if (blue.Length != DataPoints)
{
throw new ArgumentOutOfRangeException(nameof(blue));
}
Red = red;
Green = green;
Blue = blue;
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DeviceContext.Structures
{
[StructLayout(LayoutKind.Sequential)]
internal struct MonitorInfo
{
internal uint Size;
public readonly RectangleL Bounds;
public readonly RectangleL WorkingArea;
public readonly MonitorInfoFlags Flags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public readonly string DisplayName;
public static MonitorInfo Initialize()
{
return new MonitorInfo
{
Size = (uint)Marshal.SizeOf(typeof(MonitorInfo))
};
}
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.DeviceContext;
using WindowsDisplayAPI.Native.DeviceContext.Structures;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native
{
internal class DeviceContextApi
{
[DllImport("user32", CharSet = CharSet.Ansi)]
public static extern ChangeDisplaySettingsExResults ChangeDisplaySettingsEx(
string deviceName,
ref DeviceMode devMode,
IntPtr handler,
ChangeDisplaySettingsFlags flags,
IntPtr param
);
[DllImport("user32", CharSet = CharSet.Ansi)]
public static extern ChangeDisplaySettingsExResults ChangeDisplaySettingsEx(
string deviceName,
IntPtr devModePointer,
IntPtr handler,
ChangeDisplaySettingsFlags flags,
IntPtr param
);
[DllImport("user32", CharSet = CharSet.Ansi)]
public static extern bool EnumDisplaySettings(
string deviceName,
DisplaySettingsMode mode,
ref DeviceMode devMode
);
[DllImport("gdi32", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateDC(string driver, string device, string port, IntPtr deviceMode);
[DllImport("gdi32")]
internal static extern bool DeleteDC(IntPtr dcHandle);
[DllImport("user32", CharSet = CharSet.Unicode)]
internal static extern bool EnumDisplayDevices(
string deviceName,
uint deviceNumber,
ref DeviceContext.Structures.DisplayDevice displayDevice,
uint flags
);
[DllImport("user32")]
internal static extern bool EnumDisplayMonitors(
[In] IntPtr dcHandle,
[In] IntPtr clip,
MonitorEnumProcedure callback,
IntPtr callbackObject
);
[DllImport("user32")]
internal static extern IntPtr GetDC(IntPtr windowHandle);
[DllImport("gdi32")]
internal static extern int GetDeviceCaps(DCHandle dcHandle, DeviceCapability index);
[DllImport("gdi32")]
internal static extern bool GetDeviceGammaRamp(DCHandle dcHandle, ref GammaRamp ramp);
[DllImport("user32")]
internal static extern bool GetMonitorInfo(
IntPtr monitorHandle,
ref MonitorInfo monitorInfo
);
[DllImport("user32")]
internal static extern IntPtr MonitorFromPoint(
[In] PointL point,
MonitorFromFlag flag
);
[DllImport("user32")]
internal static extern IntPtr MonitorFromRect(
[In] RectangleL rectangle,
MonitorFromFlag flag
);
[DllImport("user32")]
internal static extern IntPtr MonitorFromWindow(
[In] IntPtr windowHandle,
MonitorFromFlag flag
);
[DllImport("user32")]
internal static extern bool ReleaseDC([In] IntPtr windowHandle, [In] IntPtr dcHandle);
[DllImport("gdi32")]
internal static extern bool SetDeviceGammaRamp(DCHandle dcHandle, ref GammaRamp ramp);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
internal delegate int MonitorEnumProcedure(
IntPtr monitorHandle,
IntPtr dcHandle,
ref RectangleL rect,
IntPtr callbackObject
);
}
}

View File

@@ -0,0 +1,16 @@
namespace WindowsDisplayAPI.Native.DisplayConfig
{
internal enum DisplayConfigDeviceInfoType
{
SetSourceDPIScale = -4,
GetSourceDPIScale = -3,
GetSourceName = 1,
GetTargetName = 2,
GetTargetPreferredMode = 3,
GetAdapterName = 4,
SetTargetPersistence = 5,
GetTargetBaseType = 6,
GetSupportVirtualResolution = 7,
SetSupportVirtualResolution = 8
}
}

View File

@@ -0,0 +1,28 @@
namespace WindowsDisplayAPI.Native.DisplayConfig
{
/// <summary>
/// Possbile types of modes
/// </summary>
public enum DisplayConfigModeInfoType : uint
{
/// <summary>
/// Invalid value for mode type
/// </summary>
Invalid = 0,
/// <summary>
/// Source mode type
/// </summary>
Source = 1,
/// <summary>
/// Target mode type
/// </summary>
Target = 2,
/// <summary>
/// Display image type
/// </summary>
DesktopImage = 3
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace WindowsDisplayAPI.Native.DisplayConfig
{
[Flags]
internal enum DisplayConfigPathInfoFlags : uint
{
None = 0,
Active = 1,
SupportVirtualMode = 8
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace WindowsDisplayAPI.Native.DisplayConfig
{
[Flags]
internal enum DisplayConfigPathSourceInfoFlags : uint
{
None = 0,
InUse = 1
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace WindowsDisplayAPI.Native.DisplayConfig
{
[Flags]
internal enum DisplayConfigPathTargetInfoFlags : uint
{
None = 0,
InUse = 1,
Forcible = 2,
AvailabilityBoot = 3,
AvailabilityPath = 4,
AvailabilitySystem = 5
}
}

View File

@@ -0,0 +1,39 @@
namespace WindowsDisplayAPI.Native.DisplayConfig
{
/// <summary>
/// Possible pixel formats
/// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553963(v=vs.85).aspx
/// </summary>
public enum DisplayConfigPixelFormat : uint
{
/// <summary>
/// Pixel format is not specified
/// </summary>
NotSpecified = 0,
/// <summary>
/// Indicates 8 bits per pixel format.
/// </summary>
PixelFormat8Bpp = 1,
/// <summary>
/// Indicates 16 bits per pixel format.
/// </summary>
PixelFormat16Bpp = 2,
/// <summary>
/// Indicates 24 bits per pixel format.
/// </summary>
PixelFormat24Bpp = 3,
/// <summary>
/// Indicates 32 bits per pixel format.
/// </summary>
PixelFormat32Bpp = 4,
/// <summary>
/// Indicates that the current display is not an 8, 16, 24, or 32 bits per pixel GDI desktop mode.
/// </summary>
PixelFormatNonGDI = 5
}
}

View File

@@ -0,0 +1,34 @@
namespace WindowsDisplayAPI.Native.DisplayConfig
{
/// <summary>
/// Rotation modes
/// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553970(v=vs.85).aspx
/// </summary>
public enum DisplayConfigRotation : uint
{
/// <summary>
/// Rotation mode is not specified
/// </summary>
NotSpecified = 0,
/// <summary>
/// Indicates that rotation is 0 degrees—landscape mode.
/// </summary>
Identity = 1,
/// <summary>
/// Indicates that rotation is 90 degrees clockwise—portrait mode.
/// </summary>
Rotate90 = 2,
/// <summary>
/// Indicates that rotation is 180 degrees clockwise—inverted landscape mode.
/// </summary>
Rotate180 = 3,
/// <summary>
/// Indicates that rotation is 270 degrees clockwise—inverted portrait mode.
/// </summary>
Rotate270 = 4
}
}

View File

@@ -0,0 +1,49 @@
namespace WindowsDisplayAPI.Native.DisplayConfig
{
/// <summary>
/// Scaling modes
/// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553974(v=vs.85).aspx
/// </summary>
public enum DisplayConfigScaling : uint
{
/// <summary>
/// Scaling mode is not specified
/// </summary>
NotSpecified = 0,
/// <summary>
/// Indicates the identity transformation; the source content is presented with no change. This transformation is
/// available only if the path's source mode has the same spatial resolution as the path's target mode.
/// </summary>
Identity = 1,
/// <summary>
/// Indicates the centering transformation; the source content is presented unscaled, centered with respect to the
/// spatial resolution of the target mode.
/// </summary>
Centered = 2,
/// <summary>
/// Indicates the content is scaled to fit the path's target.
/// </summary>
Stretched = 3,
/// <summary>
/// Indicates the aspect-ratio centering transformation.
/// </summary>
AspectRatioCenteredMax = 4,
/// <summary>
/// Indicates that the caller requests a custom scaling that the caller cannot describe with any of the other values.
/// Only a hardware vendor's value-add application should use this value, because the value-add application might
/// require a private interface to the driver. The application can then use this value to indicate additional context
/// for the driver for the custom value on the specified path.
/// </summary>
Custom = 5,
/// <summary>
/// Indicates that the caller does not have any preference for the scaling.
/// </summary>
Preferred = 128
}
}

View File

@@ -0,0 +1,29 @@
namespace WindowsDisplayAPI.Native.DisplayConfig
{
/// <summary>
/// Possible values for display scan line ordering
/// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553977(v=vs.85).aspx
/// </summary>
public enum DisplayConfigScanLineOrdering : uint
{
/// <summary>
/// Indicates that scan-line ordering of the output is unspecified.
/// </summary>
NotSpecified = 0,
/// <summary>
/// Indicates that the output is a progressive image.
/// </summary>
Progressive = 1,
/// <summary>
/// Indicates that the output is an interlaced image that is created beginning with the upper field.
/// </summary>
InterlacedWithUpperFieldFirst = 2,
/// <summary>
/// Indicates that the output is an interlaced image that is created beginning with the lower field.
/// </summary>
InterlacedWithLowerFieldFirst = 3
}
}

View File

@@ -0,0 +1,18 @@
namespace WindowsDisplayAPI.Native.DisplayConfig
{
public enum DisplayConfigSourceDPIScale : uint
{
Identity = 100,
Scale125Percent = 125,
Scale150Percent = 150,
Scale175Percent = 175,
Scale200Percent = 200,
Scale225Percent = 225,
Scale250Percent = 250,
Scale300Percent = 300,
Scale350Percent = 350,
Scale400Percent = 400,
Scale450Percent = 450,
Scale500Percent = 500
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace WindowsDisplayAPI.Native.DisplayConfig
{
[Flags]
internal enum DisplayConfigTargetDeviceNameFlags : uint
{
None = 0,
FriendlyNameFromEDID = 1,
FriendlyNameForced = 2,
EDIDIdsValid = 4
}
}

View File

@@ -0,0 +1,37 @@
using System;
namespace WindowsDisplayAPI.Native.DisplayConfig
{
/// <summary>
/// Possible topology identifications
/// https://msdn.microsoft.com/en-us/library/windows/hardware/ff554001(v=vs.85).aspx
/// </summary>
[Flags]
public enum DisplayConfigTopologyId : uint
{
/// <summary>
/// Invalid topology identification
/// </summary>
None = 0,
/// <summary>
/// Indicates that the display topology is an internal configuration.
/// </summary>
Internal = 0x00000001,
/// <summary>
/// Indicates that the display topology is clone-view configuration.
/// </summary>
Clone = 0x00000002,
/// <summary>
/// Indicates that the display topology is an extended configuration.
/// </summary>
Extend = 0x00000004,
/// <summary>
/// Indicates that the display topology is an external configuration.
/// </summary>
External = 0x00000008
}
}

View File

@@ -0,0 +1,96 @@
namespace WindowsDisplayAPI.Native.DisplayConfig
{
/// <summary>
/// Possible target's connector types
/// https://msdn.microsoft.com/en-us/library/windows/hardware/ff554003(v=vs.85).aspx
/// </summary>
public enum DisplayConfigVideoOutputTechnology : uint
{
/// <summary>
/// Indicates a connector that is not one of the types that is indicated by the following enumerators in this
/// enumeration.
/// </summary>
Other = 0xFFFFFFFF,
/// <summary>
/// Indicates an HD15 (VGA) connector.
/// </summary>
HD15 = 0,
/// <summary>
/// Indicates an S-video connector.
/// </summary>
SVideo = 1,
/// <summary>
/// Indicates a composite video connector group.
/// </summary>
CompositeVideo = 2,
/// <summary>
/// Indicates a component video connector group.
/// </summary>
ComponentVideo = 3,
/// <summary>
/// Indicates a Digital Video Interface (DVI) connector.
/// </summary>
DVI = 4,
/// <summary>
/// Indicates a High-Definition Multimedia Interface (HDMI) connector.
/// </summary>
HDMI = 5,
/// <summary>
/// Indicates a Low Voltage Differential Swing (LVDS) connector.
/// </summary>
LVDS = 6,
/// <summary>
/// Indicates a Japanese D connector.
/// </summary>
DJPN = 8,
/// <summary>
/// Indicates an SDI connector.
/// </summary>
SDI = 9,
/// <summary>
/// Indicates an external display port, which is a display port that connects externally to a display device.
/// </summary>
DisplayPortExternal = 10,
/// <summary>
/// Indicates an embedded display port that connects internally to a display device.
/// </summary>
DisplayPortEmbedded = 11,
/// <summary>
/// Indicates an external Unified Display Interface (UDI), which is a UDI that connects externally to a display device.
/// </summary>
UDIExternal = 12,
/// <summary>
/// Indicates an embedded UDI that connects internally to a display device.
/// </summary>
UDIEmbedded = 13,
/// <summary>
/// Indicates a dongle cable that supports standard definition television (SDTV).
/// </summary>
SDTVDongle = 14,
/// <summary>
/// Indicates that the VidPN target is a Miracast wireless display device.
/// </summary>
Miracast = 15,
/// <summary>
/// Indicates that the video output device connects internally to a display device (for example, the internal
/// connection in a laptop computer).
/// </summary>
Internal = 0x80000000
}
}

View File

@@ -0,0 +1,32 @@
using System;
namespace WindowsDisplayAPI.Native.DisplayConfig
{
/// <summary>
/// Possible values for QueryDisplayConfig() flags property
/// https://msdn.microsoft.com/en-us/library/windows/hardware/ff569215(v=vs.85).aspx
/// </summary>
[Flags]
public enum QueryDeviceConfigFlags : uint
{
/// <summary>
/// All the possible path combinations of sources to targets.
/// </summary>
AllPaths = 0x00000001,
/// <summary>
/// Currently active paths only.
/// </summary>
OnlyActivePaths = 0x00000002,
/// <summary>
/// Active path as defined in the CCD database for the currently connected displays.
/// </summary>
DatabaseCurrent = 0x00000004,
/// <summary>
/// Virtual Mode Aware
/// </summary>
VirtualModeAware = 0x0000010
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace WindowsDisplayAPI.Native.DisplayConfig
{
[Flags]
internal enum SetDisplayConfigFlags : uint
{
TopologyInternal = 0x00000001,
TopologyClone = 0x00000002,
TopologyExtend = 0x00000004,
TopologyExternal = 0x00000008,
UseDatabaseCurrent = TopologyInternal | TopologyClone | TopologyExtend | TopologyExternal,
TopologySupplied = 0x00000010,
UseSuppliedDisplayConfig = 0x00000020,
Validate = 0x00000040,
Apply = 0x00000080,
NoOptimization = 0x00000100,
SaveToDatabase = 0x00000200,
AllowChanges = 0x00000400,
PathPersistIfRequired = 0x00000800,
ForceModeEnumeration = 0x00001000,
AllowPathOrderChanges = 0x00002000,
VirtualModeAware = 0x00008000
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Runtime.InteropServices;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553913(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfig2DRegion : IEquatable<DisplayConfig2DRegion>
{
[MarshalAs(UnmanagedType.U4)] public readonly uint Width;
[MarshalAs(UnmanagedType.U4)] public readonly uint Height;
public DisplayConfig2DRegion(uint width, uint height)
{
Width = width;
Height = height;
}
public bool Equals(DisplayConfig2DRegion other)
{
return Width == other.Width && Height == other.Height;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is DisplayConfig2DRegion region && Equals(region);
}
public override int GetHashCode()
{
unchecked
{
return ((int) Width * 397) ^ (int) Height;
}
}
public static bool operator ==(DisplayConfig2DRegion left, DisplayConfig2DRegion right)
{
return Equals(left, right) || left.Equals(right);
}
public static bool operator !=(DisplayConfig2DRegion left, DisplayConfig2DRegion right)
{
return !(left == right);
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/vs/alm/ff553915(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct DisplayConfigAdapterName
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
[MarshalAs(UnmanagedType.Struct)] private readonly DisplayConfigDeviceInfoHeader _Header;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public readonly string AdapterDevicePath;
public DisplayConfigAdapterName(LUID adapter) : this()
{
_Header = new DisplayConfigDeviceInfoHeader(adapter, GetType());
}
}
}

View File

@@ -0,0 +1,65 @@
using System;
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/windows/hardware/mt622102(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigDesktopImageInfo : IEquatable<DisplayConfigDesktopImageInfo>
{
public const ushort InvalidDesktopImageModeIndex = 0xffff;
[MarshalAs(UnmanagedType.Struct)] public readonly PointL PathSourceSize;
[MarshalAs(UnmanagedType.Struct)] public readonly RectangleL DesktopImageRegion;
[MarshalAs(UnmanagedType.Struct)] public readonly RectangleL DesktopImageClip;
public DisplayConfigDesktopImageInfo(
PointL pathSourceSize,
RectangleL desktopImageRegion,
RectangleL desktopImageClip)
{
PathSourceSize = pathSourceSize;
DesktopImageRegion = desktopImageRegion;
DesktopImageClip = desktopImageClip;
}
public bool Equals(DisplayConfigDesktopImageInfo other)
{
return PathSourceSize == other.PathSourceSize &&
DesktopImageRegion == other.DesktopImageRegion &&
DesktopImageClip == other.DesktopImageClip;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is DisplayConfigDesktopImageInfo info && Equals(info);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = PathSourceSize.GetHashCode();
hashCode = (hashCode * 397) ^ DesktopImageRegion.GetHashCode();
hashCode = (hashCode * 397) ^ DesktopImageClip.GetHashCode();
return hashCode;
}
}
public static bool operator ==(DisplayConfigDesktopImageInfo left, DisplayConfigDesktopImageInfo right)
{
return Equals(left, right) || left.Equals(right);
}
public static bool operator !=(DisplayConfigDesktopImageInfo left, DisplayConfigDesktopImageInfo right)
{
return !(left == right);
}
}
}

View File

@@ -0,0 +1,76 @@
using System;
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553920(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigDeviceInfoHeader
{
[MarshalAs(UnmanagedType.U4)] public readonly DisplayConfigDeviceInfoType Type;
[MarshalAs(UnmanagedType.U4)] public readonly uint Size;
[MarshalAs(UnmanagedType.Struct)] public readonly LUID AdapterId;
[MarshalAs(UnmanagedType.U4)] public readonly uint Id;
public DisplayConfigDeviceInfoHeader(LUID adapterId, Type requestType) : this()
{
AdapterId = adapterId;
Size = (uint) Marshal.SizeOf(requestType);
if (requestType == typeof(DisplayConfigSourceDeviceName))
{
Type = DisplayConfigDeviceInfoType.GetSourceName;
}
else if (requestType == typeof(DisplayConfigTargetDeviceName))
{
Type = DisplayConfigDeviceInfoType.GetTargetName;
}
else if (requestType == typeof(DisplayConfigTargetPreferredMode))
{
Type = DisplayConfigDeviceInfoType.GetTargetPreferredMode;
}
else if (requestType == typeof(DisplayConfigAdapterName))
{
Type = DisplayConfigDeviceInfoType.GetAdapterName;
}
else if (requestType == typeof(DisplayConfigSetTargetPersistence))
{
Type = DisplayConfigDeviceInfoType.SetTargetPersistence;
}
else if (requestType == typeof(DisplayConfigTargetBaseType))
{
Type = DisplayConfigDeviceInfoType.GetTargetBaseType;
}
else if (requestType == typeof(DisplayConfigGetSourceDPIScale))
{
Type = DisplayConfigDeviceInfoType.GetSourceDPIScale;
}
else if (requestType == typeof(DisplayConfigSetSourceDPIScale))
{
Type = DisplayConfigDeviceInfoType.SetSourceDPIScale;
}
else if (requestType == typeof(DisplayConfigSupportVirtualResolution))
{
// do nothing
}
// throw exception?
}
public DisplayConfigDeviceInfoHeader(LUID adapterId, uint id, Type requestType) : this(adapterId, requestType)
{
Id = id;
}
public DisplayConfigDeviceInfoHeader(
LUID adapterId,
uint id,
Type requestType,
DisplayConfigDeviceInfoType request)
: this(adapterId, id, requestType)
{
Type = request;
}
}
}

View File

@@ -0,0 +1,27 @@
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// Internal undocumented structure
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigGetSourceDPIScale
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
[MarshalAs(UnmanagedType.Struct)] private readonly DisplayConfigDeviceInfoHeader _Header;
[field: MarshalAs(UnmanagedType.U4)]
public int MinimumScaleSteps { get; }
[field: MarshalAs(UnmanagedType.U4)]
public int CurrentScaleSteps { get; }
[field: MarshalAs(UnmanagedType.U4)]
public int MaximumScaleSteps { get; }
public DisplayConfigGetSourceDPIScale(LUID adapter, uint sourceId) : this()
{
_Header = new DisplayConfigDeviceInfoHeader(adapter, sourceId, GetType());
}
}
}

View File

@@ -0,0 +1,115 @@
using System;
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553933(v=vs.85).aspx
[StructLayout(LayoutKind.Explicit)] // Size = 64
internal struct DisplayConfigModeInfo : IEquatable<DisplayConfigModeInfo>
{
public const uint InvalidModeIndex = 0xffffffff;
[MarshalAs(UnmanagedType.U4)] [FieldOffset(0)]
public readonly DisplayConfigModeInfoType InfoType;
[MarshalAs(UnmanagedType.U4)] [FieldOffset(4)]
public readonly uint Id;
[MarshalAs(UnmanagedType.Struct)] [FieldOffset(8)]
public readonly LUID AdapterId;
[MarshalAs(UnmanagedType.Struct)] [FieldOffset(16)]
public readonly DisplayConfigTargetMode TargetMode;
[MarshalAs(UnmanagedType.Struct)] [FieldOffset(16)]
public readonly DisplayConfigSourceMode SourceMode;
[MarshalAs(UnmanagedType.Struct)] [FieldOffset(16)]
public readonly DisplayConfigDesktopImageInfo
DesktopImageInfo;
public DisplayConfigModeInfo(LUID adapterId, uint id, DisplayConfigTargetMode targetMode) : this()
{
AdapterId = adapterId;
Id = id;
TargetMode = targetMode;
InfoType = DisplayConfigModeInfoType.Target;
}
public DisplayConfigModeInfo(LUID adapterId, uint id, DisplayConfigSourceMode sourceMode) : this()
{
AdapterId = adapterId;
Id = id;
SourceMode = sourceMode;
InfoType = DisplayConfigModeInfoType.Source;
}
public DisplayConfigModeInfo(LUID adapterId, uint id, DisplayConfigDesktopImageInfo desktopImageInfo) : this()
{
AdapterId = adapterId;
Id = id;
DesktopImageInfo = desktopImageInfo;
InfoType = DisplayConfigModeInfoType.DesktopImage;
}
public bool Equals(DisplayConfigModeInfo other)
{
return InfoType == other.InfoType &&
Id == other.Id &&
AdapterId == other.AdapterId &&
(InfoType == DisplayConfigModeInfoType.Source && SourceMode == other.SourceMode ||
InfoType == DisplayConfigModeInfoType.Target && TargetMode == other.TargetMode ||
InfoType == DisplayConfigModeInfoType.DesktopImage &&
DesktopImageInfo == other.DesktopImageInfo);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is DisplayConfigModeInfo info && Equals(info);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (int) InfoType;
hashCode = (hashCode * 397) ^ (int) Id;
hashCode = (hashCode * 397) ^ AdapterId.GetHashCode();
switch (InfoType)
{
case DisplayConfigModeInfoType.Source:
hashCode = (hashCode * 397) ^ SourceMode.GetHashCode();
break;
case DisplayConfigModeInfoType.Target:
hashCode = (hashCode * 397) ^ TargetMode.GetHashCode();
break;
case DisplayConfigModeInfoType.DesktopImage:
hashCode = (hashCode * 397) ^ DesktopImageInfo.GetHashCode();
break;
}
return hashCode;
}
}
public static bool operator ==(DisplayConfigModeInfo left, DisplayConfigModeInfo right)
{
return Equals(left, right) || left.Equals(right);
}
public static bool operator !=(DisplayConfigModeInfo left, DisplayConfigModeInfo right)
{
return !(left == right);
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Runtime.InteropServices;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553945(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigPathInfo
{
[MarshalAs(UnmanagedType.Struct)] public readonly DisplayConfigPathSourceInfo SourceInfo;
[MarshalAs(UnmanagedType.Struct)] public readonly DisplayConfigPathTargetInfo TargetInfo;
[MarshalAs(UnmanagedType.U4)] public readonly DisplayConfigPathInfoFlags Flags;
public DisplayConfigPathInfo(
DisplayConfigPathSourceInfo sourceInfo,
DisplayConfigPathTargetInfo targetInfo,
DisplayConfigPathInfoFlags flags)
{
SourceInfo = sourceInfo;
TargetInfo = targetInfo;
Flags = flags;
}
public DisplayConfigPathInfo(DisplayConfigPathSourceInfo sourceInfo, DisplayConfigPathInfoFlags flags)
{
SourceInfo = sourceInfo;
Flags = flags;
TargetInfo = new DisplayConfigPathTargetInfo();
}
}
}

View File

@@ -0,0 +1,43 @@
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553951(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigPathSourceInfo
{
public const ushort InvalidCloneGroupId = 0xffff;
[MarshalAs(UnmanagedType.Struct)] public readonly LUID AdapterId;
[MarshalAs(UnmanagedType.U4)] public readonly uint SourceId;
[MarshalAs(UnmanagedType.U4)] public readonly uint ModeInfoIndex;
[MarshalAs(UnmanagedType.U4)] public readonly DisplayConfigPathSourceInfoFlags StatusFlags;
public ushort SourceModeInfoIndex
{
get => (ushort) ((ModeInfoIndex << 16) >> 16);
}
public ushort CloneGroupId
{
get => (ushort) (ModeInfoIndex >> 16);
}
public DisplayConfigPathSourceInfo(LUID adapterId, uint sourceId, uint modeInfoIndex) : this()
{
AdapterId = adapterId;
SourceId = sourceId;
ModeInfoIndex = modeInfoIndex;
}
public DisplayConfigPathSourceInfo(
LUID adapterId,
uint sourceId,
ushort sourceModeInfoIndex,
ushort cloneGroupId) : this(adapterId, sourceId, 0)
{
ModeInfoIndex = (uint) (sourceModeInfoIndex + (cloneGroupId << 16));
}
}
}

View File

@@ -0,0 +1,71 @@
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553954(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigPathTargetInfo
{
[MarshalAs(UnmanagedType.Struct)] public readonly LUID AdapterId;
[MarshalAs(UnmanagedType.U4)] public readonly uint TargetId;
[MarshalAs(UnmanagedType.U4)] public readonly uint ModeInfoIndex;
[MarshalAs(UnmanagedType.U4)] public readonly DisplayConfigVideoOutputTechnology OutputTechnology;
[MarshalAs(UnmanagedType.U4)] public readonly DisplayConfigRotation Rotation;
[MarshalAs(UnmanagedType.U4)] public readonly DisplayConfigScaling Scaling;
[MarshalAs(UnmanagedType.Struct)] public readonly DisplayConfigRational RefreshRate;
[MarshalAs(UnmanagedType.U4)] public readonly DisplayConfigScanLineOrdering ScanLineOrdering;
[MarshalAs(UnmanagedType.Bool)] public readonly bool TargetAvailable;
[MarshalAs(UnmanagedType.U4)] public readonly DisplayConfigPathTargetInfoFlags StatusFlags;
public ushort TargetModeInfoIndex
{
get => (ushort) ((ModeInfoIndex << 16) >> 16);
}
public ushort DesktopModeInfoIndex
{
get => (ushort) (ModeInfoIndex >> 16);
}
public DisplayConfigPathTargetInfo(
LUID adapterId,
uint targetId,
uint modeInfoIndex,
DisplayConfigVideoOutputTechnology outputTechnology,
DisplayConfigRotation rotation,
DisplayConfigScaling scaling,
DisplayConfigRational refreshRate,
DisplayConfigScanLineOrdering scanLineOrdering,
bool targetAvailable) : this()
{
AdapterId = adapterId;
TargetId = targetId;
ModeInfoIndex = modeInfoIndex;
OutputTechnology = outputTechnology;
Rotation = rotation;
Scaling = scaling;
RefreshRate = refreshRate;
ScanLineOrdering = scanLineOrdering;
TargetAvailable = targetAvailable;
}
public DisplayConfigPathTargetInfo(
LUID adapterId,
uint targetId,
ushort targetModeInfoIndex,
ushort desktopModeInfoIndex,
DisplayConfigVideoOutputTechnology outputTechnology,
DisplayConfigRotation rotation,
DisplayConfigScaling scaling,
DisplayConfigRational refreshRate,
DisplayConfigScanLineOrdering scanLineOrdering,
bool targetAvailable)
: this(
adapterId, targetId, 0, outputTechnology, rotation, scaling, refreshRate, scanLineOrdering,
targetAvailable)
{
ModeInfoIndex = (uint) (targetModeInfoIndex + (desktopModeInfoIndex << 16));
}
}
}

View File

@@ -0,0 +1,95 @@
using System;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553968(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigRational : IEquatable<DisplayConfigRational>
{
[MarshalAs(UnmanagedType.U4)] public readonly uint Numerator;
[MarshalAs(UnmanagedType.U4)] public readonly uint Denominator;
public DisplayConfigRational(uint numerator, uint denominator, bool simplify)
: this((ulong) numerator, denominator, simplify)
{
}
public DisplayConfigRational(ulong numerator, ulong denominator, bool simplify)
{
var gcm = simplify & (numerator != 0) ? Euclidean(numerator, denominator) : 1;
Numerator = (uint) (numerator / gcm);
Denominator = (uint) (denominator / gcm);
}
private static ulong Euclidean(ulong a, ulong b)
{
while (a != 0 && b != 0)
{
if (a > b)
{
a %= b;
}
else
{
b %= a;
}
}
return a == 0 ? b : a;
}
[Pure]
public ulong ToValue(ulong multiplier = 1)
{
if (Numerator == 0)
{
return 0;
}
return Numerator * multiplier / Denominator;
}
public bool Equals(DisplayConfigRational other)
{
if (Numerator == other.Numerator && Denominator == other.Denominator)
{
return true;
}
var left = Numerator / (double) Denominator;
var right = other.Numerator / (double) other.Denominator;
return Math.Abs(left - right) <= Math.Max(Math.Abs(left), Math.Abs(right)) * 1E-15;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is DisplayConfigRational rational && Equals(rational);
}
public override int GetHashCode()
{
unchecked
{
return ((int) Numerator * 397) ^ (int) Denominator;
}
}
public static bool operator ==(DisplayConfigRational left, DisplayConfigRational right)
{
return Equals(left, right) || left.Equals(right);
}
public static bool operator !=(DisplayConfigRational left, DisplayConfigRational right)
{
return !(left == right);
}
}
}

View File

@@ -0,0 +1,22 @@
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// Internal undocumented structure
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigSetSourceDPIScale
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
[MarshalAs(UnmanagedType.Struct)] private readonly DisplayConfigDeviceInfoHeader _Header;
[field: MarshalAs(UnmanagedType.U4)]
public int ScaleSteps { get; }
public DisplayConfigSetSourceDPIScale(LUID adapter, uint sourceId, int scaleSteps) : this()
{
_Header = new DisplayConfigDeviceInfoHeader(adapter, sourceId, GetType());
ScaleSteps = scaleSteps;
}
}
}

View File

@@ -0,0 +1,25 @@
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/vs/alm/ff553981(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigSetTargetPersistence
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
[MarshalAs(UnmanagedType.Struct)] private readonly DisplayConfigDeviceInfoHeader _Header;
[MarshalAs(UnmanagedType.U4)] private readonly uint _BootPersistenceOn;
public bool BootPersistence
{
get => _BootPersistenceOn > 0;
}
public DisplayConfigSetTargetPersistence(LUID adapter, uint targetId, bool bootPersistence) : this()
{
_Header = new DisplayConfigDeviceInfoHeader(adapter, targetId, GetType());
_BootPersistenceOn = bootPersistence ? 1u : 0u;
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/vs/alm/ff553983(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct DisplayConfigSourceDeviceName
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
[MarshalAs(UnmanagedType.Struct)] private readonly DisplayConfigDeviceInfoHeader _Header;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public readonly string DeviceName;
public DisplayConfigSourceDeviceName(LUID adapter, uint sourceId) : this()
{
_Header = new DisplayConfigDeviceInfoHeader(adapter, sourceId, GetType());
}
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553986(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigSourceMode : IEquatable<DisplayConfigSourceMode>
{
public const ushort InvalidSourceModeIndex = 0xffff;
[MarshalAs(UnmanagedType.U4)] public readonly uint Width;
[MarshalAs(UnmanagedType.U4)] public readonly uint Height;
[MarshalAs(UnmanagedType.U4)] public readonly DisplayConfigPixelFormat PixelFormat;
[MarshalAs(UnmanagedType.Struct)] public readonly PointL Position;
public DisplayConfigSourceMode(uint width, uint height, DisplayConfigPixelFormat pixelFormat, PointL position)
{
Width = width;
Height = height;
PixelFormat = pixelFormat;
Position = position;
}
public bool Equals(DisplayConfigSourceMode other)
{
return Width == other.Width &&
Height == other.Height &&
PixelFormat == other.PixelFormat &&
Position == other.Position;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is DisplayConfigSourceMode mode && Equals(mode);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (int) Width;
hashCode = (hashCode * 397) ^ (int) Height;
hashCode = (hashCode * 397) ^ (int) PixelFormat;
hashCode = (hashCode * 397) ^ Position.GetHashCode();
return hashCode;
}
}
public static bool operator ==(DisplayConfigSourceMode left, DisplayConfigSourceMode right)
{
return Equals(left, right) || left.Equals(right);
}
public static bool operator !=(DisplayConfigSourceMode left, DisplayConfigSourceMode right)
{
return !(left == right);
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/vs/alm/mt622103(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct DisplayConfigSupportVirtualResolution
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
[MarshalAs(UnmanagedType.Struct)] private readonly DisplayConfigDeviceInfoHeader _Header;
[MarshalAs(UnmanagedType.U4)] private readonly int _DisableMonitorVirtualResolution;
public bool DisableMonitorVirtualResolution
{
get => _DisableMonitorVirtualResolution > 0;
}
public DisplayConfigSupportVirtualResolution(LUID adapter, uint targetId) : this()
{
_Header = new DisplayConfigDeviceInfoHeader(adapter, targetId, GetType(),
DisplayConfigDeviceInfoType.GetSupportVirtualResolution);
}
public DisplayConfigSupportVirtualResolution(LUID adapter, uint targetId, bool disableMonitorVirtualResolution)
: this()
{
_DisableMonitorVirtualResolution = disableMonitorVirtualResolution ? 1 : 0;
_Header = new DisplayConfigDeviceInfoHeader(adapter, targetId, GetType(),
DisplayConfigDeviceInfoType.SetSupportVirtualResolution);
}
}
}

View File

@@ -0,0 +1,19 @@
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/vs/alm/dn362043(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigTargetBaseType
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
[MarshalAs(UnmanagedType.Struct)] private readonly DisplayConfigDeviceInfoHeader _Header;
[MarshalAs(UnmanagedType.U4)] public readonly DisplayConfigVideoOutputTechnology BaseOutputTechnology;
public DisplayConfigTargetBaseType(LUID adapter, uint targetId) : this()
{
_Header = new DisplayConfigDeviceInfoHeader(adapter, targetId, GetType());
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/vs/alm/ff553989(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct DisplayConfigTargetDeviceName
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
[MarshalAs(UnmanagedType.Struct)] private readonly DisplayConfigDeviceInfoHeader _Header;
[MarshalAs(UnmanagedType.U4)] public readonly DisplayConfigTargetDeviceNameFlags Flags;
[MarshalAs(UnmanagedType.U4)] public readonly DisplayConfigVideoOutputTechnology OutputTechnology;
[MarshalAs(UnmanagedType.U2)] public readonly ushort EDIDManufactureId;
[MarshalAs(UnmanagedType.U2)] public readonly ushort EDIDProductCodeId;
[MarshalAs(UnmanagedType.U4)] public readonly uint ConnectorInstance;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public readonly string MonitorFriendlyDeviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public readonly string MonitorDevicePath;
public DisplayConfigTargetDeviceName(LUID adapter, uint targetId) : this()
{
_Header = new DisplayConfigDeviceInfoHeader(adapter, targetId, GetType());
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Runtime.InteropServices;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553993(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigTargetMode : IEquatable<DisplayConfigTargetMode>
{
public const ushort InvalidTargetModeIndex = 0xffff;
[MarshalAs(UnmanagedType.Struct)] public readonly DisplayConfigVideoSignalInfo TargetVideoSignalInfo;
public DisplayConfigTargetMode(DisplayConfigVideoSignalInfo targetVideoSignalInfo)
{
TargetVideoSignalInfo = targetVideoSignalInfo;
}
public bool Equals(DisplayConfigTargetMode other)
{
return TargetVideoSignalInfo == other.TargetVideoSignalInfo;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is DisplayConfigTargetMode mode && Equals(mode);
}
public override int GetHashCode()
{
return TargetVideoSignalInfo.GetHashCode();
}
public static bool operator ==(DisplayConfigTargetMode left, DisplayConfigTargetMode right)
{
return Equals(left, right) || left.Equals(right);
}
public static bool operator !=(DisplayConfigTargetMode left, DisplayConfigTargetMode right)
{
return !(left == right);
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.Structures;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/vs/alm/ff553996(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigTargetPreferredMode
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
[MarshalAs(UnmanagedType.Struct)] private readonly DisplayConfigDeviceInfoHeader _Header;
[MarshalAs(UnmanagedType.U4)] public readonly uint Width;
[MarshalAs(UnmanagedType.U4)] public readonly uint Height;
[MarshalAs(UnmanagedType.Struct)] public readonly DisplayConfigTargetMode TargetMode;
public DisplayConfigTargetPreferredMode(LUID adapter, uint targetId) : this()
{
_Header = new DisplayConfigDeviceInfoHeader(adapter, targetId, GetType());
}
}
}

View File

@@ -0,0 +1,88 @@
using System;
using System.Runtime.InteropServices;
namespace WindowsDisplayAPI.Native.DisplayConfig.Structures
{
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff554007(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct DisplayConfigVideoSignalInfo : IEquatable<DisplayConfigVideoSignalInfo>
{
[MarshalAs(UnmanagedType.U8)] public readonly ulong PixelRate;
[MarshalAs(UnmanagedType.Struct)] public readonly DisplayConfigRational HorizontalSyncFrequency;
[MarshalAs(UnmanagedType.Struct)] public readonly DisplayConfigRational VerticalSyncFrequency;
[MarshalAs(UnmanagedType.Struct)] public readonly DisplayConfig2DRegion ActiveSize;
[MarshalAs(UnmanagedType.Struct)] public readonly DisplayConfig2DRegion TotalSize;
[MarshalAs(UnmanagedType.U2)] public readonly VideoSignalStandard VideoStandard;
[MarshalAs(UnmanagedType.U2)] public readonly ushort VerticalSyncFrequencyDivider;
[MarshalAs(UnmanagedType.U4)] public readonly DisplayConfigScanLineOrdering ScanLineOrdering;
public DisplayConfigVideoSignalInfo(
ulong pixelRate,
DisplayConfigRational horizontalSyncFrequency,
DisplayConfigRational verticalSyncFrequency,
DisplayConfig2DRegion activeSize,
DisplayConfig2DRegion totalSize,
VideoSignalStandard videoStandard,
ushort verticalSyncFrequencyDivider,
DisplayConfigScanLineOrdering scanLineOrdering)
{
PixelRate = pixelRate;
HorizontalSyncFrequency = horizontalSyncFrequency;
VerticalSyncFrequency = verticalSyncFrequency;
ActiveSize = activeSize;
TotalSize = totalSize;
VideoStandard = videoStandard;
VerticalSyncFrequencyDivider = verticalSyncFrequencyDivider;
ScanLineOrdering = scanLineOrdering;
}
public bool Equals(DisplayConfigVideoSignalInfo other)
{
return PixelRate == other.PixelRate &&
HorizontalSyncFrequency == other.HorizontalSyncFrequency &&
VerticalSyncFrequency == other.VerticalSyncFrequency &&
ActiveSize == other.ActiveSize &&
TotalSize == other.TotalSize &&
VideoStandard == other.VideoStandard &&
VerticalSyncFrequencyDivider == other.VerticalSyncFrequencyDivider &&
ScanLineOrdering == other.ScanLineOrdering;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is DisplayConfigVideoSignalInfo info && Equals(info);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = PixelRate.GetHashCode();
hashCode = (hashCode * 397) ^ HorizontalSyncFrequency.GetHashCode();
hashCode = (hashCode * 397) ^ VerticalSyncFrequency.GetHashCode();
hashCode = (hashCode * 397) ^ ActiveSize.GetHashCode();
hashCode = (hashCode * 397) ^ TotalSize.GetHashCode();
hashCode = (hashCode * 397) ^ (int) VideoStandard;
hashCode = (hashCode * 397) ^ VerticalSyncFrequencyDivider.GetHashCode();
hashCode = (hashCode * 397) ^ (int) ScanLineOrdering;
return hashCode;
}
}
public static bool operator ==(DisplayConfigVideoSignalInfo left, DisplayConfigVideoSignalInfo right)
{
return Equals(left, right) || left.Equals(right);
}
public static bool operator !=(DisplayConfigVideoSignalInfo left, DisplayConfigVideoSignalInfo right)
{
return !(left == right);
}
}
}

View File

@@ -0,0 +1,203 @@
namespace WindowsDisplayAPI.Native.DisplayConfig
{
/// <summary>
/// Possible video signal standards
/// https://msdn.microsoft.com/en-us/library/windows/hardware/ff546632(v=vs.85).aspx
/// </summary>
public enum VideoSignalStandard : ushort
{
/// <summary>
/// Indicates that the variable has not yet been assigned a meaningful value.
/// </summary>
Uninitialized = 0,
/// <summary>
/// Represents the Video Electronics Standards Association (VESA) Display Monitor Timing (DMT) standard.
/// </summary>
// ReSharper disable once InconsistentNaming
VESA_DMT = 1,
/// <summary>
/// Represents the VESA Generalized Timing Formula (GTF) standard.
/// </summary>
// ReSharper disable once InconsistentNaming
VESA_GTF = 2,
/// <summary>
/// Represents the VESA Coordinated Video Timing (CVT) standard.
/// </summary>
// ReSharper disable once InconsistentNaming
VESA_CVT = 3,
/// <summary>
/// Represents the IBM standard.
/// </summary>
IBM = 4,
/// <summary>
/// Represents the Apple standard.
/// </summary>
Apple = 5,
/// <summary>
/// Represents the National Television Standards Committee (NTSC) standard.
/// </summary>
// ReSharper disable once InconsistentNaming
NTSC_M = 6,
/// <summary>
/// Represents the NTSC japanese standard.
/// </summary>
// ReSharper disable once InconsistentNaming
NTSC_J = 7,
/// <summary>
/// Represents the NTSC standard.
/// </summary>
// ReSharper disable once InconsistentNaming
NTSC_443 = 8,
/// <summary>
/// Represents the Phase Alteration Line (PAL) standard.
/// </summary>
// ReSharper disable once InconsistentNaming
PAL_B = 9,
/// <summary>
/// Represents the PAL standard.
/// </summary>
// ReSharper disable once InconsistentNaming
PAL_B1 = 10,
/// <summary>
/// Represents the PAL standard.
/// </summary>
// ReSharper disable once InconsistentNaming
PAL_G = 11,
/// <summary>
/// Represents the PAL standard.
/// </summary>
// ReSharper disable once InconsistentNaming
PAL_H = 12,
/// <summary>
/// Represents the PAL standard.
/// </summary>
// ReSharper disable once InconsistentNaming
PAL_I = 13,
/// <summary>
/// Represents the PAL standard.
/// </summary>
// ReSharper disable once InconsistentNaming
PAL_D = 14,
/// <summary>
/// Represents the PAL standard.
/// </summary>
// ReSharper disable once InconsistentNaming
PAL_N = 15,
/// <summary>
/// Represents the PAL standard.
/// </summary>
// ReSharper disable once InconsistentNaming
PAL_NC = 16,
/// <summary>
/// Represents the Systeme Electronic Pour Couleur Avec Memoire (SECAM) standard.
/// </summary>
// ReSharper disable once InconsistentNaming
SECAM_B = 17,
/// <summary>
/// Represents the SECAM standard.
/// </summary>
// ReSharper disable once InconsistentNaming
SECAM_D = 18,
/// <summary>
/// Represents the SECAM standard.
/// </summary>
// ReSharper disable once InconsistentNaming
SECAM_G = 19,
/// <summary>
/// Represents the SECAM standard.
/// </summary>
// ReSharper disable once InconsistentNaming
SECAM_H = 20,
/// <summary>
/// Represents the SECAM standard.
/// </summary>
// ReSharper disable once InconsistentNaming
SECAM_K = 21,
/// <summary>
/// Represents the SECAM standard.
/// </summary>
// ReSharper disable once InconsistentNaming
SECAM_K1 = 22,
/// <summary>
/// Represents the SECAM standard.
/// </summary>
// ReSharper disable once InconsistentNaming
SECAM_L = 23,
/// <summary>
/// Represents the SECAM standard.
/// </summary>
// ReSharper disable once InconsistentNaming
SECAM_L1 = 24,
/// <summary>
/// Represents the Electronics Industries Association (EIA) standard.
/// </summary>
// ReSharper disable once InconsistentNaming
EIA_861 = 25,
/// <summary>
/// Represents the EIA standard.
/// </summary>
// ReSharper disable once InconsistentNaming
EIA_861A = 26,
/// <summary>
/// Represents the EIA standard.
/// </summary>
// ReSharper disable once InconsistentNaming
EIA_861B = 27,
/// <summary>
/// Represents the PAL standard.
/// </summary>
// ReSharper disable once InconsistentNaming
PAL_K = 28,
/// <summary>
/// Represents the PAL standard.
/// </summary>
// ReSharper disable once InconsistentNaming
PAL_K1 = 29,
/// <summary>
/// Represents the PAL standard.
/// </summary>
// ReSharper disable once InconsistentNaming
PAL_L = 30,
/// <summary>
/// Represents the PAL standard.
/// </summary>
// ReSharper disable once InconsistentNaming
PAL_M = 31,
/// <summary>
/// Represents any video standard other than those represented by the previous constants in this enumeration.
/// </summary>
Other = 255
}
}

View File

@@ -0,0 +1,96 @@
using System;
using System.Runtime.InteropServices;
using WindowsDisplayAPI.Native.DisplayConfig;
using WindowsDisplayAPI.Native.DisplayConfig.Structures;
namespace WindowsDisplayAPI.Native
{
internal class DisplayConfigApi
{
[DllImport("user32")]
public static extern Win32Status DisplayConfigGetDeviceInfo(
ref DisplayConfigSupportVirtualResolution targetSupportVirtualResolution
);
[DllImport("user32")]
public static extern Win32Status DisplayConfigGetDeviceInfo(
ref DisplayConfigGetSourceDPIScale targetSupportVirtualResolution
);
[DllImport("user32")]
public static extern Win32Status DisplayConfigGetDeviceInfo(
ref DisplayConfigTargetDeviceName deviceName
);
[DllImport("user32")]
public static extern Win32Status DisplayConfigGetDeviceInfo(
ref DisplayConfigAdapterName deviceName
);
[DllImport("user32")]
public static extern Win32Status DisplayConfigGetDeviceInfo(
ref DisplayConfigSourceDeviceName deviceName
);
[DllImport("user32")]
public static extern Win32Status DisplayConfigGetDeviceInfo(
ref DisplayConfigTargetPreferredMode targetPreferredMode
);
[DllImport("user32")]
public static extern Win32Status DisplayConfigGetDeviceInfo(
ref DisplayConfigTargetBaseType targetBaseType
);
[DllImport("user32")]
public static extern Win32Status DisplayConfigSetDeviceInfo(
ref DisplayConfigSetTargetPersistence targetPersistence
);
[DllImport("user32")]
public static extern Win32Status DisplayConfigSetDeviceInfo(
ref DisplayConfigSupportVirtualResolution targetSupportVirtualResolution
);
[DllImport("user32")]
public static extern Win32Status DisplayConfigSetDeviceInfo(
ref DisplayConfigSetSourceDPIScale setSourceDpiScale
);
[DllImport("user32")]
public static extern Win32Status GetDisplayConfigBufferSizes(
QueryDeviceConfigFlags flags,
out uint pathArrayElements,
out uint modeInfoArrayElements
);
[DllImport("user32")]
public static extern Win32Status QueryDisplayConfig(
QueryDeviceConfigFlags flags,
ref uint pathArrayElements,
[Out] DisplayConfigPathInfo[] pathInfoArray,
ref uint modeInfoArrayElements,
[Out] DisplayConfigModeInfo[] modeInfoArray,
IntPtr currentTopologyId
);
[DllImport("user32")]
public static extern Win32Status QueryDisplayConfig(
QueryDeviceConfigFlags flags,
ref uint pathArrayElements,
[Out] DisplayConfigPathInfo[] pathInfoArray,
ref uint modeInfoArrayElements,
[Out] DisplayConfigModeInfo[] modeInfoArray,
[Out] out DisplayConfigTopologyId currentTopologyId
);
[DllImport("user32")]
public static extern Win32Status SetDisplayConfig(
[In] uint pathArrayElements,
[In] DisplayConfigPathInfo[] pathInfoArray,
[In] uint modeInfoArrayElements,
[In] DisplayConfigModeInfo[] modeInfoArray,
[In] SetDisplayConfigFlags flags
);
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Runtime.InteropServices;
namespace WindowsDisplayAPI.Native.Structures
{
/// <summary>
/// Locally unique identifier is a 64-bit value guaranteed to be unique only on the system on which it was generated.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct LUID : IEquatable<LUID>
{
/// <summary>
/// 32Bit unsigned integer, low
/// </summary>
public readonly uint LowPart;
/// <summary>
/// 32Bit signed integer, high
/// </summary>
public readonly int HighPart;
/// <summary>
/// Creates a new LUID
/// </summary>
/// <param name="lowPart">32Bit unsigned integer, low</param>
/// <param name="highPart">32Bit signed integer, high</param>
public LUID(uint lowPart, int highPart)
{
LowPart = lowPart;
HighPart = highPart;
}
/// <inheritdoc />
public override string ToString()
{
return $"{{ {LowPart:X} - {HighPart:X} }}";
}
/// <inheritdoc />
public bool Equals(LUID other)
{
return LowPart == other.LowPart && HighPart == other.HighPart;
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is LUID luid && Equals(luid);
}
/// <summary>
/// Checks for equality between two objects of same type
/// </summary>
/// <param name="left">The first object</param>
/// <param name="right">The second object</param>
/// <returns>true, if both objects are equal, otherwise false</returns>
public static bool operator ==(LUID left, LUID right)
{
return Equals(left, right) || left.Equals(right);
}
/// <summary>
/// Checks for inequality between two objects of same type
/// </summary>
/// <param name="left">The first object</param>
/// <param name="right">The second object</param>
/// <returns>true, if both objects are not equal, otherwise false</returns>
public static bool operator !=(LUID left, LUID right)
{
return !(left == right);
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
return ((int) LowPart * 397) ^ HighPart;
}
}
/// <summary>
/// Checks if this type is empty and holds no real data
/// </summary>
/// <returns>true if empty, otherwise false</returns>
public bool IsEmpty()
{
return LowPart == 0 && HighPart == 0;
}
/// <summary>
/// Returns an empty instance of this type
/// </summary>
public static LUID Empty
{
get => default;
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Diagnostics.Contracts;
using System.Drawing;
using System.Runtime.InteropServices;
namespace WindowsDisplayAPI.Native.Structures
{
// https://msdn.microsoft.com/en-us/library/vs/alm/dd162807(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct PointL : IEquatable<PointL>
{
[MarshalAs(UnmanagedType.I4)] public readonly int X;
[MarshalAs(UnmanagedType.I4)] public readonly int Y;
[Pure]
public Point ToPoint()
{
return new Point(X, Y);
}
[Pure]
public Size ToSize()
{
return new Size(X, Y);
}
public PointL(Point point) : this(point.X, point.Y)
{
}
public PointL(Size size) : this(size.Width, size.Height)
{
}
public PointL(int x, int y)
{
X = x;
Y = y;
}
public bool Equals(PointL other)
{
return X == other.X && Y == other.Y;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is PointL point && Equals(point);
}
public override int GetHashCode()
{
unchecked
{
return (X * 397) ^ Y;
}
}
public static bool operator ==(PointL left, PointL right)
{
return Equals(left, right) || left.Equals(right);
}
public static bool operator !=(PointL left, PointL right)
{
return !(left == right);
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Diagnostics.Contracts;
using System.Drawing;
using System.Runtime.InteropServices;
namespace WindowsDisplayAPI.Native.Structures
{
// https://msdn.microsoft.com/en-us/library/vs/alm/dd162907(v=vs.85).aspx
[StructLayout(LayoutKind.Sequential)]
internal struct RectangleL : IEquatable<RectangleL>
{
[MarshalAs(UnmanagedType.U4)] public readonly int Left;
[MarshalAs(UnmanagedType.U4)] public readonly int Top;
[MarshalAs(UnmanagedType.U4)] public readonly int Right;
[MarshalAs(UnmanagedType.U4)] public readonly int Bottom;
[Pure]
public Rectangle ToRectangle()
{
return new Rectangle(Left, Top, Right - Left, Bottom - Top);
}
public RectangleL(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public RectangleL(Rectangle rectangle) : this(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom)
{
}
public bool Equals(RectangleL other)
{
return Left == other.Left && Top == other.Top && Right == other.Right && Bottom == other.Bottom;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is RectangleL rectangle && Equals(rectangle);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = Left;
hashCode = (hashCode * 397) ^ Top;
hashCode = (hashCode * 397) ^ Right;
hashCode = (hashCode * 397) ^ Bottom;
return hashCode;
}
}
public static bool operator ==(RectangleL left, RectangleL right)
{
return Equals(left, right) || left.Equals(right);
}
public static bool operator !=(RectangleL left, RectangleL right)
{
return !(left == right);
}
}
}

View File

@@ -0,0 +1,8 @@
namespace WindowsDisplayAPI.Native
{
internal enum Win32Status
{
Success = 0x0,
ErrorInsufficientBuffer = 0x7A
}
}

View File

@@ -0,0 +1,80 @@
using System.Collections.Generic;
using System.Linq;
namespace WindowsDisplayAPI
{
/// <summary>
/// Represents a Windows UnAttached Display Device
/// </summary>
public class UnAttachedDisplay : DisplayDevice
{
/// <summary>
/// Creates a new UnAttachedDisplay
/// </summary>
/// <param name="device">The DisplayDevice instance to copy information from</param>
protected UnAttachedDisplay(DisplayDevice device)
: base(
device.DevicePath,
device.DeviceName,
device.DeviceKey,
device.Adapter,
device.ScreenName,
device.DisplayName,
device.IsAvailable,
false
)
{
}
/// <inheritdoc />
public override bool IsAvailable
{
get => base.IsAvailable || !IsValid;
}
/// <inheritdoc />
public override bool IsValid
{
get
{
return DisplayAdapter.GetDisplayAdapters()
.SelectMany(adapter => adapter.GetDisplayDevices(base.IsAvailable))
.Any(
device => device.DevicePath.Equals(DevicePath) && device.DeviceKey.Equals(DeviceKey)
);
}
}
/// <summary>
/// Returns a list of all unattached displays on this machine
/// </summary>
/// <returns>An enumerable list of UnAttachedDisplay</returns>
public static IEnumerable<UnAttachedDisplay> GetUnAttachedDisplays()
{
return DisplayAdapter.GetDisplayAdapters()
.SelectMany(adapter => adapter.GetDisplayDevices(false))
.Select(device => new UnAttachedDisplay(device));
}
/// <inheritdoc />
public override string ToString()
{
return IsValid ? $"{GetType().Name}: {DisplayName} ({DeviceName})" : $"{GetType().Name}: Invalid";
}
/// <summary>
/// Returns the corresponding Display device for this unattached display. Only functions when this instance is invalidated
/// due to display attachment.
/// </summary>
/// <returns></returns>
public Display ToDisplay()
{
return IsValid
? null
: Display.GetDisplays()
.FirstOrDefault(
display => display.DevicePath.Equals(DevicePath) && display.DeviceKey.Equals(DeviceKey)
);
}
}
}

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45</TargetFrameworks>
<Version>1.3.0.13</Version>
<Company>falahati.net</Company>
<Description>WindowsDisplayAPI is a .Net wrapper for Windows Display and Windows CCD APIs</Description>
<Authors>Soroush Falahati</Authors>
<Copyright>Copyright © Soroush Falahati 2020 (falahati.net)</Copyright>
<Platforms>AnyCPU</Platforms>
<Product>WindowsDisplayAPI</Product>
<PackageProjectUrl>https://github.com/falahati/WindowsDisplayAPI</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/falahati/WindowsDisplayAPI/blob/master/LICENSE</PackageLicenseUrl>
<PackageIconUrl>https://github.com/falahati/WindowsDisplayAPI/blob/master/WindowsDisplayAPI/Icon.png?raw=true</PackageIconUrl>
<IncludeSymbols>true</IncludeSymbols>
<IncludeSource>true</IncludeSource>
<PlatformTarget>AnyCPU</PlatformTarget>
<Title>Windows Display API Wrapper (CCD)</Title>
<PackageId>WindowsDisplayAPI</PackageId>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<BumpLabel>dev</BumpLabel>
<BumpLabelDigits>4</BumpLabelDigits>
<OutputPath>..\Debug</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<BumpRevision>True</BumpRevision>
<BumpResetLabel>dev</BumpResetLabel>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<OutputPath>..\Release</OutputPath>
<DocumentationFile>..\Release\WindowsDisplayAPI.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference PrivateAssets="all" Include="MSBump" Version="2.3.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Content Include="readme.txt">
<Pack>true</Pack>
<PackagePath>\</PackagePath>
</Content>
<Content Include="Icon.png">
<Pack>true</Pack>
<PackagePath>\</PackagePath>
</Content>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<Reference Include="System.Windows.Forms" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,12 @@
WindowsDisplayAPI Library
------------------------------------------------------------
WindowsDisplayAPI is a library released under the LGPLv3
license, allowing all .Net developers to access and use the
Windows Display and Windows CCD functionalities.
For more information about this library, please check out
our GitHub page:
https://github.com/falahati/WindowsDisplayAPI
2017 Soroush Falahati (https://falahati.net)