From cf84fa0616f7bcf530a427ae7cfe9d1f8f3b2f0e Mon Sep 17 00:00:00 2001 From: Serge <5920850+seerge@users.noreply.github.com> Date: Fri, 16 Feb 2024 15:55:37 +0100 Subject: [PATCH] Gamma Init --- app/Display/ScreenControl.cs | 36 +- app/Properties/Resources.Designer.cs | 10 + app/Properties/Resources.resx | 37 +- app/Resources/icons8-brightness-32.png | Bin 0 -> 420 bytes app/Settings.Designer.cs | 180 +++- app/Settings.cs | 22 + app/WindowsDisplayAPI/ColorDepth.cs | 11 + app/WindowsDisplayAPI/Device.cs | 104 ++ app/WindowsDisplayAPI/Display.cs | 179 ++++ app/WindowsDisplayAPI/DisplayAdapter.cs | 123 +++ app/WindowsDisplayAPI/DisplayCapabilities.cs | 269 ++++++ .../DisplayColorManagementCapabilities.cs | 31 + .../DisplayConfig/PathDisplayAdapter.cs | 190 ++++ .../DisplayConfig/PathDisplaySource.cs | 238 +++++ .../DisplayConfig/PathDisplayTarget.cs | 481 +++++++++ .../DisplayConfig/PathInfo.cs | 910 ++++++++++++++++++ .../DisplayConfig/PathTargetDesktopImage.cs | 140 +++ .../DisplayConfig/PathTargetInfo.cs | 330 +++++++ .../DisplayConfig/PathTargetSignalInfo.cs | 220 +++++ .../DisplayCurveCapabilities.cs | 61 ++ app/WindowsDisplayAPI/DisplayDevice.cs | 143 +++ app/WindowsDisplayAPI/DisplayGammaRamp.cs | 91 ++ .../DisplayLineCapabilities.cs | 51 + .../DisplayPolygonalCapabilities.cs | 56 ++ .../DisplayPossibleSetting.cs | 63 ++ .../DisplayRasterCapabilities.cs | 91 ++ app/WindowsDisplayAPI/DisplayScreen.cs | 299 ++++++ app/WindowsDisplayAPI/DisplaySetting.cs | 345 +++++++ .../DisplayShaderBlendingCapabilities.cs | 41 + .../DisplayTextCapabilities.cs | 91 ++ .../Exceptions/DuplicateModeException.cs | 18 + .../Exceptions/InvalidDisplayException.cs | 32 + .../Exceptions/InvalidEDIDInformation.cs | 18 + .../InvalidRegistryAddressException.cs | 18 + .../Exceptions/MissingDisplayException.cs | 25 + .../Exceptions/MissingModeException.cs | 26 + .../Exceptions/ModeChangeException.cs | 37 + .../Exceptions/NotACloneMemberException.cs | 18 + .../Exceptions/PathChangeException.cs | 27 + .../Exceptions/TargetNotAvailableException.cs | 33 + app/WindowsDisplayAPI/Icon.png | Bin 0 -> 3931 bytes .../ChangeDisplaySettingsExResults.cs | 48 + .../ChangeDisplaySettingsFlags.cs | 18 + .../Native/DeviceContext/DCHandle.cs | 56 ++ .../Native/DeviceContext/DeviceCapability.cs | 51 + .../Native/DeviceContext/DeviceModeFields.cs | 50 + .../DeviceContext/DisplayDeviceStateFlags.cs | 41 + .../DeviceContext/DisplayFixedOutput.cs | 23 + .../Native/DeviceContext/DisplayFlags.cs | 12 + .../DeviceContext/DisplayOrientation.cs | 28 + .../DeviceContext/DisplaySettingsMode.cs | 9 + .../Native/DeviceContext/DisplayTechnology.cs | 13 + .../Native/DeviceContext/MonitorFromFlag.cs | 13 + .../Native/DeviceContext/MonitorInfoFlags.cs | 11 + .../DeviceContext/Structures/DeviceMode.cs | 133 +++ .../DeviceContext/Structures/DisplayDevice.cs | 32 + .../DeviceContext/Structures/GammaRamp.cs | 57 ++ .../DeviceContext/Structures/MonitorInfo.cs | 28 + .../Native/DeviceContextApi.cs | 106 ++ .../DisplayConfigDeviceInfoType.cs | 16 + .../DisplayConfigModeInfoType.cs | 28 + .../DisplayConfigPathInfoFlags.cs | 12 + .../DisplayConfigPathSourceInfoFlags.cs | 11 + .../DisplayConfigPathTargetInfoFlags.cs | 15 + .../DisplayConfig/DisplayConfigPixelFormat.cs | 39 + .../DisplayConfig/DisplayConfigRotation.cs | 34 + .../DisplayConfig/DisplayConfigScaling.cs | 49 + .../DisplayConfigScanLineOrdering.cs | 29 + .../DisplayConfigSourceDPIScale.cs | 18 + .../DisplayConfigTargetDeviceNameFlags.cs | 13 + .../DisplayConfig/DisplayConfigTopologyId.cs | 37 + .../DisplayConfigVideoOutputTechnology.cs | 96 ++ .../DisplayConfig/QueryDeviceConfigFlags.cs | 32 + .../DisplayConfig/SetDisplayConfigFlags.cs | 25 + .../Structures/DisplayConfig2DRegion.cs | 52 + .../Structures/DisplayConfigAdapterName.cs | 21 + .../DisplayConfigDesktopImageInfo.cs | 65 ++ .../DisplayConfigDeviceInfoHeader.cs | 76 ++ .../DisplayConfigGetSourceDPIScale.cs | 27 + .../Structures/DisplayConfigModeInfo.cs | 115 +++ .../Structures/DisplayConfigPathInfo.cs | 30 + .../Structures/DisplayConfigPathSourceInfo.cs | 43 + .../Structures/DisplayConfigPathTargetInfo.cs | 71 ++ .../Structures/DisplayConfigRational.cs | 95 ++ .../DisplayConfigSetSourceDPIScale.cs | 22 + .../DisplayConfigSetTargetPersistence.cs | 25 + .../DisplayConfigSourceDeviceName.cs | 21 + .../Structures/DisplayConfigSourceMode.cs | 66 ++ .../DisplayConfigSupportVirtualResolution.cs | 33 + .../Structures/DisplayConfigTargetBaseType.cs | 19 + .../DisplayConfigTargetDeviceName.cs | 30 + .../Structures/DisplayConfigTargetMode.cs | 48 + .../DisplayConfigTargetPreferredMode.cs | 21 + .../DisplayConfigVideoSignalInfo.cs | 88 ++ .../DisplayConfig/VideoSignalStandard.cs | 203 ++++ .../Native/DisplayConfigApi.cs | 96 ++ .../Native/Structures/LUID.cs | 104 ++ .../Native/Structures/PointL.cs | 74 ++ .../Native/Structures/RectangleL.cs | 73 ++ app/WindowsDisplayAPI/Native/Win32Status.cs | 8 + app/WindowsDisplayAPI/UnAttachedDisplay.cs | 80 ++ .../WindowsDisplayAPI.csproj | 52 + app/WindowsDisplayAPI/readme.txt | 12 + 103 files changed, 7907 insertions(+), 41 deletions(-) create mode 100644 app/Resources/icons8-brightness-32.png create mode 100644 app/WindowsDisplayAPI/ColorDepth.cs create mode 100644 app/WindowsDisplayAPI/Device.cs create mode 100644 app/WindowsDisplayAPI/Display.cs create mode 100644 app/WindowsDisplayAPI/DisplayAdapter.cs create mode 100644 app/WindowsDisplayAPI/DisplayCapabilities.cs create mode 100644 app/WindowsDisplayAPI/DisplayColorManagementCapabilities.cs create mode 100644 app/WindowsDisplayAPI/DisplayConfig/PathDisplayAdapter.cs create mode 100644 app/WindowsDisplayAPI/DisplayConfig/PathDisplaySource.cs create mode 100644 app/WindowsDisplayAPI/DisplayConfig/PathDisplayTarget.cs create mode 100644 app/WindowsDisplayAPI/DisplayConfig/PathInfo.cs create mode 100644 app/WindowsDisplayAPI/DisplayConfig/PathTargetDesktopImage.cs create mode 100644 app/WindowsDisplayAPI/DisplayConfig/PathTargetInfo.cs create mode 100644 app/WindowsDisplayAPI/DisplayConfig/PathTargetSignalInfo.cs create mode 100644 app/WindowsDisplayAPI/DisplayCurveCapabilities.cs create mode 100644 app/WindowsDisplayAPI/DisplayDevice.cs create mode 100644 app/WindowsDisplayAPI/DisplayGammaRamp.cs create mode 100644 app/WindowsDisplayAPI/DisplayLineCapabilities.cs create mode 100644 app/WindowsDisplayAPI/DisplayPolygonalCapabilities.cs create mode 100644 app/WindowsDisplayAPI/DisplayPossibleSetting.cs create mode 100644 app/WindowsDisplayAPI/DisplayRasterCapabilities.cs create mode 100644 app/WindowsDisplayAPI/DisplayScreen.cs create mode 100644 app/WindowsDisplayAPI/DisplaySetting.cs create mode 100644 app/WindowsDisplayAPI/DisplayShaderBlendingCapabilities.cs create mode 100644 app/WindowsDisplayAPI/DisplayTextCapabilities.cs create mode 100644 app/WindowsDisplayAPI/Exceptions/DuplicateModeException.cs create mode 100644 app/WindowsDisplayAPI/Exceptions/InvalidDisplayException.cs create mode 100644 app/WindowsDisplayAPI/Exceptions/InvalidEDIDInformation.cs create mode 100644 app/WindowsDisplayAPI/Exceptions/InvalidRegistryAddressException.cs create mode 100644 app/WindowsDisplayAPI/Exceptions/MissingDisplayException.cs create mode 100644 app/WindowsDisplayAPI/Exceptions/MissingModeException.cs create mode 100644 app/WindowsDisplayAPI/Exceptions/ModeChangeException.cs create mode 100644 app/WindowsDisplayAPI/Exceptions/NotACloneMemberException.cs create mode 100644 app/WindowsDisplayAPI/Exceptions/PathChangeException.cs create mode 100644 app/WindowsDisplayAPI/Exceptions/TargetNotAvailableException.cs create mode 100644 app/WindowsDisplayAPI/Icon.png create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/ChangeDisplaySettingsExResults.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/ChangeDisplaySettingsFlags.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/DCHandle.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/DeviceCapability.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/DeviceModeFields.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/DisplayDeviceStateFlags.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/DisplayFixedOutput.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/DisplayFlags.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/DisplayOrientation.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/DisplaySettingsMode.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/DisplayTechnology.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/MonitorFromFlag.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/MonitorInfoFlags.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/Structures/DeviceMode.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/Structures/DisplayDevice.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/Structures/GammaRamp.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContext/Structures/MonitorInfo.cs create mode 100644 app/WindowsDisplayAPI/Native/DeviceContextApi.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigDeviceInfoType.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigModeInfoType.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPathInfoFlags.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPathSourceInfoFlags.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPathTargetInfoFlags.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPixelFormat.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigRotation.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigScaling.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigScanLineOrdering.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigSourceDPIScale.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigTargetDeviceNameFlags.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigTopologyId.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigVideoOutputTechnology.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/QueryDeviceConfigFlags.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/SetDisplayConfigFlags.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfig2DRegion.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigAdapterName.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigDesktopImageInfo.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigDeviceInfoHeader.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigGetSourceDPIScale.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigModeInfo.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigPathInfo.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigPathSourceInfo.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigPathTargetInfo.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigRational.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSetSourceDPIScale.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSetTargetPersistence.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSourceDeviceName.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSourceMode.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSupportVirtualResolution.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetBaseType.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetDeviceName.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetMode.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetPreferredMode.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigVideoSignalInfo.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfig/VideoSignalStandard.cs create mode 100644 app/WindowsDisplayAPI/Native/DisplayConfigApi.cs create mode 100644 app/WindowsDisplayAPI/Native/Structures/LUID.cs create mode 100644 app/WindowsDisplayAPI/Native/Structures/PointL.cs create mode 100644 app/WindowsDisplayAPI/Native/Structures/RectangleL.cs create mode 100644 app/WindowsDisplayAPI/Native/Win32Status.cs create mode 100644 app/WindowsDisplayAPI/UnAttachedDisplay.cs create mode 100644 app/WindowsDisplayAPI/WindowsDisplayAPI.csproj create mode 100644 app/WindowsDisplayAPI/readme.txt diff --git a/app/Display/ScreenControl.cs b/app/Display/ScreenControl.cs index 6331baaf..609967e7 100644 --- a/app/Display/ScreenControl.cs +++ b/app/Display/ScreenControl.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using WindowsDisplayAPI; namespace GHelper.Display { @@ -7,6 +7,8 @@ namespace GHelper.Display public const int MAX_REFRESH = 1000; + public static DisplayGammaRamp? gamma; + public void AutoScreen(bool force = false) { if (force || AppConfig.Is("screen_auto")) @@ -22,6 +24,35 @@ namespace GHelper.Display } } + public void SaveGamma() + { + var display = WindowsDisplayAPI.Display.GetDisplays().Where(x => x.DisplayScreen.IsPrimary).FirstOrDefault(); + if (display is null) return; + + gamma = display.GammaRamp; + + Logger.WriteLine("R:" + string.Join("-", display.GammaRamp.Red)); + Logger.WriteLine("G:" + string.Join("-", display.GammaRamp.Green)); + Logger.WriteLine("B:" + string.Join("-", display.GammaRamp.Blue)); + } + + public void RestoreGamma() + { + var display = WindowsDisplayAPI.Display.GetDisplays().Where(x => x.DisplayScreen.IsPrimary).FirstOrDefault(); + if (gamma is not null) display!.GammaRamp = gamma; + } + + public void SetGamma(int brightness = 100, int contrast = 100) + { + var display = WindowsDisplayAPI.Display.GetDisplays().Where(x => x.DisplayScreen.IsPrimary).FirstOrDefault(); + var bright = (float)(brightness) / 100; + + Logger.WriteLine("Brightness: " + bright.ToString()); + + display!.GammaRamp = new DisplayGammaRamp(bright, bright, 1); + //ScreenBrightness.Set(60 + (int)(40 * bright)); + } + public void SetScreen(int frequency = -1, int overdrive = -1, int miniled = -1) { var laptopScreen = ScreenNative.FindLaptopScreen(true); @@ -71,7 +102,8 @@ namespace GHelper.Display if (miniled1 >= 0) { miniled = (miniled1 == 1) ? 0 : 1; - } else + } + else { switch (miniled2) { diff --git a/app/Properties/Resources.Designer.cs b/app/Properties/Resources.Designer.cs index 8ff78342..971b35c6 100644 --- a/app/Properties/Resources.Designer.cs +++ b/app/Properties/Resources.Designer.cs @@ -210,6 +210,16 @@ namespace GHelper.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap icons8_brightness_32 { + get { + object obj = ResourceManager.GetObject("icons8-brightness-32", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/app/Properties/Resources.resx b/app/Properties/Resources.resx index 7d0ca668..ea07c07c 100644 --- a/app/Properties/Resources.resx +++ b/app/Properties/Resources.resx @@ -136,6 +136,9 @@ ..\Resources\icons8-bicycle-48 (1).png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\standard.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\eco.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -163,9 +166,15 @@ ..\Resources\icons8-automation-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\icons8-settings-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\brightness-up.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\brightness-down.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\icons8-processor-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -187,15 +196,9 @@ ..\Resources\icons8-laptop-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\Resources\ally.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - ..\Resources\icons8-remove-64.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\Resources\icons8-share-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - ..\Resources\icons8-function-mac-96.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -229,14 +232,11 @@ ..\Resources\icons8-xbox-rt-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\Resources\icons8-controller-96.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - ..\Resources\icons8-fan-48.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\Resources\icons8-settings-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\icons8-controller-96.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ..\Resources\icons8-maus-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -250,8 +250,11 @@ ..\Resources\dot-ultimate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\Resources\brightness-down.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\icons8-heartbeat-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\ally.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ..\Resources\backlight-up.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -319,8 +322,8 @@ ..\Resources\icons8-software-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\Resources\standard.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\icons8-share-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ..\Resources\icons8-soonvibes-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -331,7 +334,7 @@ ..\Resources\icons8-charging-battery-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\Resources\icons8-heartbeat-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\icons8-brightness-32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a \ No newline at end of file diff --git a/app/Resources/icons8-brightness-32.png b/app/Resources/icons8-brightness-32.png new file mode 100644 index 0000000000000000000000000000000000000000..a1deb58f23c30a19b13c9d0de978aa0febe95a22 GIT binary patch literal 420 zcmV;V0bBlwP)pqYHGhrreI&v+R^?*w0u+rLxzy{bg@~^m+ zcl9?-tbAuPZ#*Z!5;(L7>)Zni>C+a=842)eA*}O7GvSS;hq&TARD38?>T94**=>k{ zO@O%K+E=_UD?8M{&ag$lQD{`Siy*F;ozkl`Doi69m^9-4(5nmvCJ_zXpE6K%kb;p# z%Fnh4>r=4Df82&KenfTO0fATq7 + /// Represents a Windows Video Device including Display Devices and Video Controllers + /// + public abstract class Device + { + /// + /// Creates a new Device + /// + /// The device path + /// The device name + /// The device driver registry key + protected Device(string devicePath, string deviceName, string deviceKey) + { + DevicePath = devicePath; + DeviceName = deviceName; + DeviceKey = deviceKey; + } + + /// + /// Gets the registry address of the device driver and configuration + /// + public virtual string DeviceKey { get; } + + /// + /// Gets the Windows device name + /// + public virtual string DeviceName { get; } + + /// + /// Gets the Windows device path + /// + public virtual string DevicePath { get; } + + /// + public override string ToString() + { + return $"{GetType().Name}: {DeviceName}"; + } + +#if !NETSTANDARD + /// + /// Opens the registry key at the address specified by the DeviceKey property + /// + /// A RegistryKey instance for successful call, otherwise null + /// Registry address is invalid or unknown. + 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."); + } + + /// + /// Opens the registry key of the Windows PnP manager for this device + /// + /// A RegistryKey instance for successful call, otherwise null + 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 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Display.cs b/app/WindowsDisplayAPI/Display.cs new file mode 100644 index 00000000..c2636a99 --- /dev/null +++ b/app/WindowsDisplayAPI/Display.cs @@ -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 +{ + /// + /// Represents a Windows Attached Display Device + /// + public class Display : DisplayDevice + { + /// + /// Creates a new Display + /// + /// The DisplayDevice instance to copy information from + protected Display(DisplayDevice device) + : base( + device.DevicePath, + device.DeviceName, + device.DeviceKey, + device.Adapter, + device.IsAvailable, + false + ) + { + } + + /// + /// Gets the display capabilities. + /// + public MonitorCapabilities Capabilities + { + get + { + var handle = DCHandle.CreateFromDevice(ScreenName, DevicePath); + + if (!IsValid || handle?.IsInvalid != false) + { + throw new InvalidDisplayException(DevicePath); + } + + return new MonitorCapabilities(handle); + } + } + + /// + 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; + } + } + + /// + /// Gets or sets the display gamma ramp look up table. + /// + 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)); + } + } + } + + /// + public override bool IsAvailable + { + get => base.IsAvailable && IsValid; + } + + /// + public override bool IsValid + { + get + { + return DisplayAdapter.GetDisplayAdapters() + .SelectMany(adapter => adapter.GetDisplayDevices(base.IsAvailable)) + .Any( + device => device.DevicePath.Equals(DevicePath) && device.DeviceKey.Equals(DeviceKey) + ); + } + } + + /// + 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; + } + } + + /// + /// Returns a list of all attached displays on this machine + /// + /// An enumerable list of Displays + public static IEnumerable GetDisplays() + { + return DisplayAdapter.GetDisplayAdapters() + .SelectMany(adapter => adapter.GetDisplayDevices(true)) + .Select(device => new Display(device)); + } + + /// + public override string ToString() + { + return IsValid ? $"{GetType().Name}: {DisplayName} ({DeviceName})" : $"{GetType().Name}: Invalid"; + } + + /// + /// Returns the corresponding UnAttachedDisplay device for this display. Only valid when this instance is invalidated + /// due to display detachment. + /// + /// + public UnAttachedDisplay ToUnAttachedDisplay() + { + if (IsValid) + { + return null; + } + + return UnAttachedDisplay.GetUnAttachedDisplays() + .FirstOrDefault( + display => display.DevicePath.Equals(DevicePath) && display.DeviceKey.Equals(DeviceKey) + ); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayAdapter.cs b/app/WindowsDisplayAPI/DisplayAdapter.cs new file mode 100644 index 00000000..cd4fb447 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayAdapter.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using System.Linq; +using WindowsDisplayAPI.DisplayConfig; +using WindowsDisplayAPI.Native; + +namespace WindowsDisplayAPI +{ + /// + /// Represents a Windows Video Controller Display Adapter Device + /// + public class DisplayAdapter : Device + { + /// + /// Creates a new DisplayAdapter + /// + /// The device path + /// The device name + /// The device driver registry key + protected DisplayAdapter(string devicePath, string deviceName, string deviceKey) + : base(devicePath, deviceName, deviceKey) + { + } + + /// + /// Returns a list of all display adapters on this machine + /// + /// An enumerable list of DisplayAdapters + public static IEnumerable GetDisplayAdapters() + { + var device = Native.DeviceContext.Structures.DisplayDevice.Initialize(); + var deviceIds = new List(); + + 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(); + } + } + + /// + /// Returns a list of all display devices connected to this adapter + /// + /// An enumerable list of DisplayDevices + public IEnumerable GetDisplayDevices() + { + return GetDisplayDevices(null); + } + + /// + /// Returns the corresponding PathDisplayAdapter instance + /// + /// An instance of PathDisplayAdapter, or null + public PathDisplayAdapter ToPathDisplayAdapter() + { + return PathDisplayAdapter.GetAdapters() + .FirstOrDefault(adapter => + adapter.DevicePath.StartsWith("\\\\?\\" + DevicePath.Replace("\\", "#")) + ); + } + + internal IEnumerable GetDisplayDevices(bool? filterByAvailability) + { + var returned = new Dictionary(); + + 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; + } + } + } + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayCapabilities.cs b/app/WindowsDisplayAPI/DisplayCapabilities.cs new file mode 100644 index 00000000..1e07a012 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayCapabilities.cs @@ -0,0 +1,269 @@ +using System; +using WindowsDisplayAPI.Native; +using WindowsDisplayAPI.Native.DeviceContext; + +namespace WindowsDisplayAPI +{ + /// + /// Contains information about the device capabilities of a display device + /// + 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(); + } + } + + /// + /// Gets the actual color resolution of the device, in bits per pixel. + /// + public int ActualColorDepth + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.ColorResolution) * ColorPlanes; + } + + /// + /// Gets a boolean value indicating if the device is capable of clipping to a rectangle. + /// + public bool ClipToRectangleCapability + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.ClipCapabilities) > 0; + } + + /// + /// Gets the color management capabilities of the device + /// + public DisplayColorManagementCapabilities ColorManagementCapabilities + { + get => (DisplayColorManagementCapabilities) DeviceContextApi.GetDeviceCaps( + _dcHandle, + DeviceCapability.ColorManagementCapabilities + ); + } + + /// + /// Gets the number of color planes. + /// + public int ColorPlanes + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.Planes); + } + + /// + /// Gets the curve capabilities of the device + /// + public DisplayCurveCapabilities CurveCapabilities + { + get => (DisplayCurveCapabilities) DeviceContextApi.GetDeviceCaps( + _dcHandle, + DeviceCapability.CurveCapabilities + ); + } + + /// + /// Gets the diagonal width of the device pixel used for line drawing. + /// + public int DevicePixelDiagonalWidth + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.HypotenuseAspect); + } + + /// + /// Gets the relative height of a device pixel used for line drawing. + /// + public int DevicePixelRelativeHeight + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.VerticalAspect); + } + + /// + /// Gets the relative width of a device pixel used for line drawing. + /// + public int DevicePixelRelativeWidth + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.HorizontalAspect); + } + + /// + /// Gets the diagonal length of the physical screen in millimeters. + /// + public int DiagonalSizeInMM + { + get => (int) Math.Round(Math.Pow(Math.Pow(VerticalSizeInMM, 2) + Math.Pow(HorizontalSizeInMM, 2), 0.5)); + } + + + /// + /// Gets the device driver version. + /// + public int DriverVersion + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.DriverVersion); + } + + /// + /// Gets the effective color resolution of the device, in bits per pixel. + /// + public int EffectiveColorDepth + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.BitsPerPixel) * ColorPlanes; + } + + /// + /// Gets the number of pixels per logical inch along the screen width. + /// + public int HorizontalPixelPerInch + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.HorizontalLogicalPixels); + } + + /// + /// Gets the height of screen in raster lines. + /// + public int HorizontalResolution + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.HorizontalResolution); + } + + /// + /// Gets the width of the physical screen in millimeters. + /// + public int HorizontalSizeInMM + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.HorizontalSizeInMM); + } + + /// + /// Gets the line capabilities of the device + /// + public DisplayLineCapabilities LineCapabilities + { + get => (DisplayLineCapabilities) DeviceContextApi.GetDeviceCaps( + _dcHandle, + DeviceCapability.LineCapabilities + ); + } + + /// + /// Gets the polygon capabilities of the device + /// + public DisplayPolygonalCapabilities PolygonalCapabilities + { + get => (DisplayPolygonalCapabilities) DeviceContextApi.GetDeviceCaps( + _dcHandle, + DeviceCapability.PolygonalCapabilities + ); + } + + /// + /// 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. + /// + public int? PreferredHorizontalDrawingAlignment + { + get + { + var value = DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.PreferredBLTAlignment); + + if (value == 0) + { + return null; + } + + return value; + } + } + + /// + /// Gets the raster capabilities of the device + /// + public DisplayRasterCapabilities RasterCapabilities + { + get => (DisplayRasterCapabilities) DeviceContextApi.GetDeviceCaps( + _dcHandle, + DeviceCapability.RasterCapabilities + ); + } + + /// + /// Gets the shader blending capabilities of the device + /// + public DisplayShaderBlendingCapabilities ShaderBlendingCapabilities + { + get => (DisplayShaderBlendingCapabilities) DeviceContextApi.GetDeviceCaps( + _dcHandle, + DeviceCapability.ShadeBlendingCapabilities + ); + } + + /// + /// Gets the text capabilities of the device + /// + public DisplayTextCapabilities TextCapabilities + { + get => (DisplayTextCapabilities) DeviceContextApi.GetDeviceCaps( + _dcHandle, + DeviceCapability.TextCapabilities + ); + } + + /// + /// Gets the number of pixels per logical inch along the screen height. + /// + public int VerticalPixelPerInch + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.VerticalLogicalPixels); + } + + /// + /// Gets the current vertical refresh rate of the device, in cycles per second (Hz) or null for display hardware's + /// default refresh rate. + /// + public int? VerticalRefreshRateInHz + { + get + { + var value = DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.VerticalRefreshRateInHz); + + if (value <= 1) + { + return null; + } + + return value; + } + } + + /// + /// Gets the width of the screen in pixels. + /// + public int VerticalResolution + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.VerticalResolution); + } + + /// + /// Gets the height of the physical screen in millimeters. + /// + public int VerticalSizeInMM + { + get => DeviceContextApi.GetDeviceCaps(_dcHandle, DeviceCapability.VerticalSizeInMM); + } + + /// + public void Dispose() + { + _dcHandle?.Dispose(); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayColorManagementCapabilities.cs b/app/WindowsDisplayAPI/DisplayColorManagementCapabilities.cs new file mode 100644 index 00000000..ce1f1a74 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayColorManagementCapabilities.cs @@ -0,0 +1,31 @@ +using System; + +namespace WindowsDisplayAPI +{ + /// + /// Contains possible color management capabilities of a display device + /// + [Flags] + public enum DisplayColorManagementCapabilities + { + /// + /// Device does not support ICM. + /// + None = 0, + + /// + /// Device can perform ICM on either the device driver or the device itself. + /// + DeviceICM = 1, + + /// + /// Device supports gamma ramp modification and retrieval + /// + GammaRamp = 2, + + /// + /// Device can accept CMYK color space ICC color profile. + /// + CMYKColor = 4 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayConfig/PathDisplayAdapter.cs b/app/WindowsDisplayAPI/DisplayConfig/PathDisplayAdapter.cs new file mode 100644 index 00000000..dd46ae4a --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayConfig/PathDisplayAdapter.cs @@ -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 +{ + /// + /// Represents a path display adapter + /// + public class PathDisplayAdapter : IEquatable + { + /// + /// Creates a new PathDisplayAdapter + /// + /// The adapter local unique identification + public PathDisplayAdapter(LUID adapterId) + { + AdapterId = adapterId; + } + + /// + /// Gets the display adapter local identification LUID + /// + public LUID AdapterId { get; } + + /// + /// Gets the display adapter device path + /// + /// Error code can be retrieved from Win32Exception.NativeErrorCode property + 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); + } + } + + /// + /// Gets a boolean value indicating the instance validity + /// + public bool IsInvalid + { + get => AdapterId.IsEmpty(); + } + + /// + public bool Equals(PathDisplayAdapter other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return AdapterId == other.AdapterId; + } + + /// + /// Retrieving a list of all adapters from the currently active and inactive paths + /// + /// An array of PathDisplayAdapter instances + public static PathDisplayAdapter[] GetAdapters() + { + var adapters = new Dictionary(); + + 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(); + } + + /// + /// Checks for equality of two PathDisplayAdapter instances + /// + /// The first instance + /// The second instance + /// true if both instances are equal, otherwise false + public static bool operator ==(PathDisplayAdapter left, PathDisplayAdapter right) + { + return Equals(left, right) || left?.Equals(right) == true; + } + + /// + /// Checks for inequality of two PathDisplayAdapter instances + /// + /// The first instance + /// The second instance + /// true if both instances are not equal, otherwise false + public static bool operator !=(PathDisplayAdapter left, PathDisplayAdapter right) + { + return !(left == right); + } + + /// + 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); + } + + /// + public override int GetHashCode() + { + return AdapterId.GetHashCode(); + } + + /// + public override string ToString() + { + return DevicePath; + } + +#if !NETSTANDARD + /// + /// Opens the registry key of the Windows PnP manager for this display adapter + /// + /// A RegistryKey instance for successful call, otherwise null + 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 + + /// + /// Gets the corresponding DisplayAdapter instance + /// + /// An instance of DisplayAdapter, or null + public DisplayAdapter ToDisplayAdapter() + { + return DisplayAdapter.GetDisplayAdapters() + .FirstOrDefault( + adapter => DevicePath.StartsWith("\\\\?\\" + adapter.DevicePath.Replace("\\", "#")) + ); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayConfig/PathDisplaySource.cs b/app/WindowsDisplayAPI/DisplayConfig/PathDisplaySource.cs new file mode 100644 index 00000000..14ffa177 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayConfig/PathDisplaySource.cs @@ -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 +{ + /// + /// Represents a display path source + /// + public class PathDisplaySource : IEquatable + { + private static readonly DisplayConfigSourceDPIScale[] DPIScales = Enum + .GetValues(typeof(DisplayConfigSourceDPIScale)) + .Cast() + .OrderBy(scaling => (uint) scaling) + .ToArray(); + + /// + /// Creates a new PathDisplaySource + /// + /// Display adapter + /// Display source identification + public PathDisplaySource(PathDisplayAdapter adapter, uint sourceId) + { + Adapter = adapter; + SourceId = sourceId; + } + + /// + /// Gets the path display adapter + /// + public PathDisplayAdapter Adapter { get; } + + /// + /// Gets and sets the current source DPI scaling + /// + 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); + } + } + } + + /// + /// Returns the corresponding instance. + /// + public DisplayScreen ToScreen() + { + return DisplayScreen.GetScreens().FirstOrDefault(info => info.ScreenName.Equals(DisplayName)); + } + + /// + /// Gets the display name + /// + /// Error code can be retrieved from Win32Exception.NativeErrorCode property + 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); + } + } + + /// + /// Gets the maximum DPI scaling for this source + /// + 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]; + } + } + + /// + /// Gets the recommended DPI scaling for this source + /// + 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)]; + } + } + + /// + /// Gets the zero based display identification + /// + public uint SourceId { get; } + + /// + public bool Equals(PathDisplaySource other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return Adapter == other.Adapter && SourceId == other.SourceId; + } + + /// + /// Retrieving a list of all display sources from the currently active and inactive paths + /// + /// An array of PathDisplaySource instances + public static PathDisplaySource[] GetDisplaySources() + { + var sources = new Dictionary, 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(); + } + + /// + /// Checks for equality of two PathDisplaySource instances + /// + /// The first instance + /// The second instance + /// true if both instances are equal, otherwise false + public static bool operator ==(PathDisplaySource left, PathDisplaySource right) + { + return Equals(left, right) || left?.Equals(right) == true; + } + + /// + /// Checks for inequality of two PathDisplaySource instances + /// + /// The first instance + /// The second instance + /// true if both instances are not equal, otherwise false + public static bool operator !=(PathDisplaySource left, PathDisplaySource right) + { + return !(left == right); + } + + /// + 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); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((Adapter != null ? Adapter.GetHashCode() : 0) * 397) ^ (int) SourceId; + } + } + + /// + public override string ToString() + { + return DisplayName; + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayConfig/PathDisplayTarget.cs b/app/WindowsDisplayAPI/DisplayConfig/PathDisplayTarget.cs new file mode 100644 index 00000000..16dd4237 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayConfig/PathDisplayTarget.cs @@ -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 +{ + /// + /// Represents a display path target (Display Device) + /// + public class PathDisplayTarget : IEquatable + { + /// + /// Creates a new PathDisplayTarget + /// + /// Display adapter + /// Display target identification + 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; + } + + /// + /// Gets the path display adapter + /// + public PathDisplayAdapter Adapter { get; } + + /// + /// Sets the display boot persistence for the target display device + /// + /// + /// + 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); + } + } + } + + /// + /// 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. + /// + /// Error code can be retrieved from Win32Exception.NativeErrorCode property + /// The target is not available + 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); + } + } + + /// + /// Gets the display device path + /// + /// Error code can be retrieved from Win32Exception.NativeErrorCode property + /// The target is not available + 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); + } + } + + /// + /// Gets the display manufacture 3 character code from the display EDID manufacture identification + /// + /// The target is not available + /// Error code can be retrieved from Win32Exception.NativeErrorCode property + /// The EDID information does not contain this value + 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)}"; + } + } + + /// + /// Gets the display manufacture identification from the display EDID information + /// + /// The target is not available + /// Error code can be retrieved from Win32Exception.NativeErrorCode property + /// The EDID information does not contain this value + 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); + } + } + + /// + /// Gets the display product identification from the display EDID information + /// + /// The target is not available + /// Error code can be retrieved from Win32Exception.NativeErrorCode property + /// The EDID information does not contain this value + 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); + } + } + + /// + /// Gets the display friendly name from the display EDID information + /// + /// The target is not available + /// Error code can be retrieved from Win32Exception.NativeErrorCode property + 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); + } + } + + /// + /// Gets a boolean value indicating the device availability + /// + public bool IsAvailable { get; } + + /// + /// Gets the display device preferred resolution + /// + /// The target is not available + /// Error code can be retrieved from Win32Exception.NativeErrorCode property + 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); + } + } + + /// + /// Gets the display device preferred signal information + /// + /// The target is not available + /// Error code can be retrieved from Win32Exception.NativeErrorCode property + 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); + } + } + + /// + /// Gets the target identification + /// + public uint TargetId { get; } + + /// + /// Gets or sets the device virtual resolution support + /// + /// The target is not available + /// Error code can be retrieved from Win32Exception.NativeErrorCode property + 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); + } + } + } + + /// + public bool Equals(PathDisplayTarget other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return Adapter == other.Adapter && TargetId == other.TargetId; + } + + /// + /// Retrieving a list of all display targets from the currently active and inactive paths + /// + /// An array of PathDisplayTarget instances + public static PathDisplayTarget[] GetDisplayTargets() + { + var targets = new Dictionary, 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(); + } + + /// + /// Checks for equality of two PathDisplayTarget instances + /// + /// The first instance + /// The second instance + /// true if both instances are equal, otherwise false + public static bool operator ==(PathDisplayTarget left, PathDisplayTarget right) + { + return Equals(left, right) || left?.Equals(right) == true; + } + + /// + /// Checks for inequality of two PathDisplayTarget instances + /// + /// The first instance + /// The second instance + /// true if both instances are not equal, otherwise false + public static bool operator !=(PathDisplayTarget left, PathDisplayTarget right) + { + return !(left == right); + } + + /// + 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); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((Adapter != null ? Adapter.GetHashCode() : 0) * 397) ^ (int) TargetId; + } + } + + /// + public override string ToString() + { + return FriendlyName; + } + +#if !NETSTANDARD + /// + /// Opens the registry key of the Windows PnP manager for this display target + /// + /// A RegistryKey instance for successful call, otherwise null + 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 + + /// + /// Returns the corresponding instance + /// + /// An instance of , or null + public DisplayDevice ToDisplayDevice() + { + return + DisplayAdapter.GetDisplayAdapters() + .SelectMany(adapter => adapter.GetDisplayDevices()) + .FirstOrDefault(device => device.DevicePath.Equals(DevicePath)); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayConfig/PathInfo.cs b/app/WindowsDisplayAPI/DisplayConfig/PathInfo.cs new file mode 100644 index 00000000..87e2fa12 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayConfig/PathInfo.cs @@ -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 +{ + /// + /// Represents a path root information + /// + public class PathInfo + { + private readonly uint _cloneGroupId; + private readonly DisplayConfigPixelFormat _pixelFormat; + private readonly Point _position; + private readonly Size _resolution; + + /// + /// Creates a new PathInfo + /// + /// The display source + /// The display position in desktop + /// The display resolution + /// The display pixel format + public PathInfo( + PathDisplaySource displaySource, + Point position, + Size resolution, + DisplayConfigPixelFormat pixelFormat + ) : this(displaySource) + { + _position = position; + _resolution = resolution; + _pixelFormat = pixelFormat; + IsModeInformationAvailable = true; + } + + /// + /// Creates a new PathInfo + /// + /// The display source + /// The display position in desktop + /// The display resolution + /// The display pixel format + /// The display clone group, only valid for virtual aware paths + public PathInfo( + PathDisplaySource displaySource, + Point position, + Size resolution, + DisplayConfigPixelFormat pixelFormat, + uint cloneGroup + ) : this(displaySource, cloneGroup) + { + _position = position; + _resolution = resolution; + _pixelFormat = pixelFormat; + IsModeInformationAvailable = true; + } + + + /// + /// Creates a new PathInfo + /// + /// The display source + /// The display position in desktop + /// The display resolution + /// The display pixel format + /// An array of target information + public PathInfo( + PathDisplaySource displaySource, + Point position, + Size resolution, + DisplayConfigPixelFormat pixelFormat, + PathTargetInfo[] pathTargetInfos + ) : this(displaySource, position, resolution, pixelFormat) + { + TargetsInfo = pathTargetInfos; + } + + + /// + /// Creates a new PathInfo + /// + /// The display source + /// The display position in desktop + /// The display resolution + /// The display pixel format + /// An array of target information + /// The display clone group, only valid for virtual aware paths + public PathInfo( + PathDisplaySource displaySource, + Point position, + Size resolution, + DisplayConfigPixelFormat pixelFormat, + PathTargetInfo[] pathTargetInfos, + uint cloneGroup + ) : this(displaySource, position, resolution, pixelFormat, cloneGroup) + { + TargetsInfo = pathTargetInfos; + } + + /// + /// Creates a new PathInfo + /// + /// The display source + public PathInfo(PathDisplaySource displaySource) + { + DisplaySource = displaySource; + } + + /// + /// Creates a new PathInfo + /// + /// The display source + /// The display clone group, only valid for virtual aware paths + public PathInfo(PathDisplaySource displaySource, uint cloneGroup) : this(displaySource) + { + IsCloneMember = true; + _cloneGroupId = cloneGroup; + } + + /// + /// Creates a new PathInfo + /// + /// The display source + /// An array of target information + public PathInfo( + PathDisplaySource displaySource, + PathTargetInfo[] pathTargetInfos + ) : this(displaySource) + { + TargetsInfo = pathTargetInfos; + } + + /// + /// Creates a new PathInfo + /// + /// The display source + /// An array of target information + /// The display clone group, only valid for virtual aware paths + public PathInfo( + PathDisplaySource displaySource, + PathTargetInfo[] pathTargetInfos, + uint cloneGroup + ) : this(displaySource, cloneGroup) + { + TargetsInfo = pathTargetInfos; + } + + private PathInfo( + DisplayConfigPathSourceInfo sourceInfo, + DisplayConfigSourceMode? sourceMode, + IEnumerable< + Tuple< + DisplayConfigPathInfoFlags, + DisplayConfigPathTargetInfo, + DisplayConfigTargetMode?, + DisplayConfigDesktopImageInfo? + > + > targets + ) + { + DisplaySource = new PathDisplaySource(new PathDisplayAdapter(sourceInfo.AdapterId), sourceInfo.SourceId); + + IsInUse = sourceInfo.StatusFlags.HasFlag(DisplayConfigPathSourceInfoFlags.InUse); + IsModeInformationAvailable = sourceMode.HasValue; + + if (sourceMode.HasValue) + { + _resolution = new Size((int) sourceMode.Value.Width, (int) sourceMode.Value.Height); + _pixelFormat = sourceMode.Value.PixelFormat; + _position = new Point(sourceMode.Value.Position.X, sourceMode.Value.Position.Y); + } + + TargetsInfo = targets.Select(t => new PathTargetInfo(t.Item1, t.Item2, t.Item3, t.Item4)).ToArray(); + + if (TargetsInfo.Any(info => info.IsVirtualModeSupportedByPath) && + sourceInfo.CloneGroupId != DisplayConfigPathSourceInfo.InvalidCloneGroupId + ) + { + _cloneGroupId = sourceInfo.CloneGroupId; + IsCloneMember = true; + } + } + + /// + /// Gets a valid identifier used to show which clone group the path is a member of + /// + /// This path is not a clone member + public uint CloneGroupId + { + get + { + if (!IsCloneMember) + { + throw new NotACloneMemberException( + "The display source is not part of a clone group." + ); + } + + return _cloneGroupId; + } + } + + /// + /// Gets extra information about the representing display source + /// + public PathDisplaySource DisplaySource { get; } + + /// + /// Gets a boolean value indicating if this path is a member of a clone group + /// + public bool IsCloneMember { get; } + + /// + /// Gets a boolean value indicating if this path is the primary GDI path + /// + /// Source mode information is missing + public bool IsGDIPrimary + { + get => Position.IsEmpty; + } + + /// + /// Gets a boolean value indicating if the source is in use by at least one active path + /// + public bool IsInUse { get; } + + /// + /// Gets a boolean value indicating if the source mode information is available + /// + public bool IsModeInformationAvailable { get; } + + /// + /// Gets a boolean value indicating the DisplayConfig (CCD API) availability on this system + /// + public static bool IsSupported + { + get + { + try + { + return DisplayConfigApi.GetDisplayConfigBufferSizes( + QueryDeviceConfigFlags.AllPaths, + out _, + out _ + ) == Win32Status.Success; + } + catch + { + return false; + } + } + } + + /// + /// Gets a boolean value indicating the virtual display mode support on this system + /// + public static bool IsVirtualModeSupported + { + get + { + try + { + return PathDisplayTarget + .GetDisplayTargets() + .Any(t => t.VirtualResolutionSupport); + } + catch + { + return false; + } + } + } + + /// + /// Gets the specifies the pixel format of the source mode + /// + /// Source mode information is missing + public DisplayConfigPixelFormat PixelFormat + { + get + { + if (!IsModeInformationAvailable) + { + throw new MissingModeException( + "Source mode information is missing or not available.", + DisplayConfigModeInfoType.Source + ); + } + + return _pixelFormat; + } + } + + /// + /// Gets the position in the desktop coordinate space of the upper-left corner of this source surface. The source + /// surface that is located at (0, 0) is always the primary source surface. + /// + /// Source mode information is missing + public Point Position + { + get + { + if (!IsModeInformationAvailable) + { + throw new MissingModeException( + "Source mode information is missing or not available.", + DisplayConfigModeInfoType.Source + ); + } + + return _position; + } + } + + /// + /// Gets the size of the source mode + /// + /// Source mode information is missing + public Size Resolution + { + get + { + if (!IsModeInformationAvailable) + { + throw new MissingModeException( + "Source mode information is missing or not available.", + DisplayConfigModeInfoType.Source + ); + } + + return _resolution; + } + } + + /// + /// Gets the list of target information + /// + public PathTargetInfo[] TargetsInfo { get; } = new PathTargetInfo[0]; + + /// + /// Applies an array of paths + /// + /// The array of paths + /// true to allow changes and reordering of the provided paths, otherwise false + /// true to save the paths to the persistence database if call succeed, otherwise false + /// true to force driver mode enumeration before applying the paths + /// Error in changing paths + public static void ApplyPathInfos( + IEnumerable pathInfos, + bool allowChanges = true, + bool saveToDatabase = false, + bool forceModeEnumeration = false + ) + { + var pathInfosArray = pathInfos.ToArray(); + + if (!ValidatePathInfos(pathInfosArray, allowChanges)) + { + throw new PathChangeException("Invalid paths information."); + } + + var displayConfigPathInfos = GetDisplayConfigPathInfos(pathInfosArray, out var displayConfigModeInfos); + + if (displayConfigPathInfos.Length <= 0) + { + return; + } + + var flags = displayConfigModeInfos.Length == 0 + ? SetDisplayConfigFlags.TopologySupplied + : SetDisplayConfigFlags.UseSuppliedDisplayConfig; + + if (allowChanges) + { + flags |= displayConfigModeInfos.Length == 0 + ? SetDisplayConfigFlags.AllowPathOrderChanges + : SetDisplayConfigFlags.AllowChanges; + } + else if (displayConfigModeInfos.Length > 0) + { + flags |= SetDisplayConfigFlags.NoOptimization; + } + + if (saveToDatabase && displayConfigModeInfos.Length > 0) + { + flags |= SetDisplayConfigFlags.SaveToDatabase; + } + + if (forceModeEnumeration && displayConfigModeInfos.Length > 0) + { + flags |= SetDisplayConfigFlags.ForceModeEnumeration; + } + + var result = + DisplayConfigApi.SetDisplayConfig( + (uint) displayConfigPathInfos.Length, + displayConfigPathInfos, + (uint) displayConfigModeInfos.Length, + displayConfigModeInfos.Length > 0 ? displayConfigModeInfos : null, + SetDisplayConfigFlags.Apply | flags + ); + + if (result != Win32Status.Success) + { + throw new PathChangeException( + "An error occurred while applying the paths information.", + new Win32Exception((int) result) + ); + } + } + + /// + /// Applies a saved topology + /// + /// The topology identification to apply + /// true to allows persistence of the changes, otherwise false + /// Error in changing paths + public static void ApplyTopology(DisplayConfigTopologyId topology, bool allowPersistence = false) + { + if (!ValidateTopology(topology)) + { + throw new PathChangeException("Invalid topology request."); + } + + var flags = (SetDisplayConfigFlags) topology; + + if (allowPersistence) + { + flags |= SetDisplayConfigFlags.PathPersistIfRequired; + } + + var result = DisplayConfigApi.SetDisplayConfig( + 0, + null, + 0, + null, + SetDisplayConfigFlags.Apply | flags + ); + + if (result != Win32Status.Success) + { + throw new PathChangeException( + "An error occurred while applying the requested topology.", + new Win32Exception((int) result) + ); + } + } + + /// + /// Retrieves the list of active paths + /// + /// true if the caller expects virtual mode settings, otherwise false + /// An array of PathInfos + public static PathInfo[] GetActivePaths(bool virtualModeAware = false) + { + return GetPathInfos( + virtualModeAware + ? QueryDeviceConfigFlags.OnlyActivePaths | QueryDeviceConfigFlags.VirtualModeAware + : QueryDeviceConfigFlags.OnlyActivePaths, + out _ + ); + } + + /// + /// Retrieves the list of all paths, active or inactive + /// + /// true if the caller expects virtual mode settings, otherwise false + /// An array of PathInfos + public static PathInfo[] GetAllPaths(bool virtualModeAware = false) + { + return GetPathInfos( + virtualModeAware + ? QueryDeviceConfigFlags.AllPaths | QueryDeviceConfigFlags.VirtualModeAware + : QueryDeviceConfigFlags.AllPaths, + out _ + ); + } + + /// + /// Retrieves the list of currently active topology paths + /// + /// An array of PathInfos + public static PathInfo[] GetCurrentDatabasePaths() + { + return GetPathInfos(QueryDeviceConfigFlags.DatabaseCurrent, out _); + } + + /// + /// Gets the current active topology identification + /// + /// The topology identification + public static DisplayConfigTopologyId GetCurrentTopology() + { + GetPathInfos(QueryDeviceConfigFlags.DatabaseCurrent, out var currentDatabaseType); + return currentDatabaseType; + } + + /// + /// Validates an array of paths before applying + /// + /// The array of paths + /// true to allow changes and reordering of the provided paths, otherwise false + /// true if the provided paths are valid, otherwise false + public static bool ValidatePathInfos(IEnumerable pathInfos, bool allowChanges = true) + { + var displayConfigPathInfos = GetDisplayConfigPathInfos(pathInfos, out var displayConfigModeInfos); + + if (displayConfigPathInfos.Length <= 0) + { + return false; + } + + var flags = displayConfigModeInfos.Length == 0 + ? SetDisplayConfigFlags.TopologySupplied + : SetDisplayConfigFlags.UseSuppliedDisplayConfig; + + if (allowChanges) + { + flags |= displayConfigModeInfos.Length == 0 + ? SetDisplayConfigFlags.AllowPathOrderChanges + : SetDisplayConfigFlags.AllowChanges; + } + + return + DisplayConfigApi.SetDisplayConfig( + (uint) displayConfigPathInfos.Length, + displayConfigPathInfos, + (uint) displayConfigModeInfos.Length, + displayConfigModeInfos.Length > 0 ? displayConfigModeInfos : null, + SetDisplayConfigFlags.Validate | flags + ) == + Win32Status.Success; + } + + /// + /// Validates a topology before applying + /// + /// The topology identification + /// true if topology is applicable, otherwise false + /// + public static bool ValidateTopology(DisplayConfigTopologyId topology) + { + if (topology == DisplayConfigTopologyId.None) + { + throw new ArgumentOutOfRangeException(nameof(topology), "Topology should not be empty."); + } + + var flags = (SetDisplayConfigFlags) topology; + + return DisplayConfigApi.SetDisplayConfig( + 0, + null, + 0, + null, + SetDisplayConfigFlags.Validate | flags + ) == + Win32Status.Success; + } + + private static uint AddMode(ref List modes, DisplayConfigModeInfo mode) + { + var existingMode = modes.FindIndex(info => + info.InfoType == mode.InfoType && + info.AdapterId == mode.AdapterId && + info.Id == mode.Id + ); + + if (existingMode > 0) + { + if (modes[existingMode] == mode) + { + return (uint) existingMode; + } + + throw new DuplicateModeException( + "Provided list of path information, contains one or more duplicate but not identical modes." + ); + } + + modes.Add(mode); + + return (uint) modes.Count - 1; + } + + // ReSharper disable once CyclomaticComplexity + private static DisplayConfigPathInfo[] GetDisplayConfigPathInfos( + IEnumerable pathInfos, + out DisplayConfigModeInfo[] modeInfos) + { + var displayConfigPathInfos = new List(); + var displayConfigModeInfos = new List(); + + foreach (var pathInfo in pathInfos) + { + var sourceMode = pathInfo.GetDisplayConfigSourceMode(); + var sourceModeIndex = sourceMode.HasValue + ? AddMode( + ref displayConfigModeInfos, + new DisplayConfigModeInfo( + pathInfo.DisplaySource.Adapter.AdapterId, + pathInfo.DisplaySource.SourceId, + sourceMode.Value + ) + ) + : 0u; + var sourceInfo = pathInfo.IsCloneMember + ? new DisplayConfigPathSourceInfo( + pathInfo.DisplaySource.Adapter.AdapterId, + pathInfo.DisplaySource.SourceId, + sourceMode.HasValue ? (ushort) sourceModeIndex : DisplayConfigSourceMode.InvalidSourceModeIndex, + (ushort) pathInfo.CloneGroupId + ) + : new DisplayConfigPathSourceInfo( + pathInfo.DisplaySource.Adapter.AdapterId, + pathInfo.DisplaySource.SourceId, + sourceMode.HasValue ? sourceModeIndex : DisplayConfigModeInfo.InvalidModeIndex + ); + + if (pathInfo.TargetsInfo == null || pathInfo.TargetsInfo.Length == 0) + { + displayConfigPathInfos.Add( + new DisplayConfigPathInfo(sourceInfo, + DisplayConfigPathInfoFlags.Active + ) + ); + } + else + { + foreach (var target in pathInfo.TargetsInfo) + { + var flags = DisplayConfigPathInfoFlags.None; + + if (target.IsPathActive) + { + flags |= DisplayConfigPathInfoFlags.Active; + } + + if (target.IsVirtualModeSupportedByPath) + { + flags |= DisplayConfigPathInfoFlags.SupportVirtualMode; + } + + var targetMode = target.GetDisplayConfigTargetMode(); + var targetModeIndex = targetMode.HasValue + ? AddMode(ref displayConfigModeInfos, + new DisplayConfigModeInfo( + target.DisplayTarget.Adapter.AdapterId, + target.DisplayTarget.TargetId, targetMode.Value + ) + ) + : 0u; + DisplayConfigPathTargetInfo targetInfo; + + if (target.IsVirtualModeSupportedByPath) + { + sourceInfo = new DisplayConfigPathSourceInfo( + pathInfo.DisplaySource.Adapter.AdapterId, + pathInfo.DisplaySource.SourceId, + sourceMode.HasValue + ? (ushort) sourceModeIndex + : DisplayConfigSourceMode.InvalidSourceModeIndex, + pathInfo.IsCloneMember + ? (ushort) pathInfo.CloneGroupId + : DisplayConfigPathSourceInfo.InvalidCloneGroupId + ); + var desktopMode = target.GetDisplayConfigDesktopImageInfo(); + var desktopModeIndex = desktopMode.HasValue + ? AddMode(ref displayConfigModeInfos, + new DisplayConfigModeInfo( + target.DisplayTarget.Adapter.AdapterId, + target.DisplayTarget.TargetId, + desktopMode.Value + ) + ) + : 0u; + targetInfo = new DisplayConfigPathTargetInfo( + target.DisplayTarget.Adapter.AdapterId, + target.DisplayTarget.TargetId, + targetMode.HasValue + ? (ushort) targetModeIndex + : DisplayConfigTargetMode.InvalidTargetModeIndex, + desktopMode.HasValue + ? (ushort) desktopModeIndex + : DisplayConfigDesktopImageInfo.InvalidDesktopImageModeIndex, + target.OutputTechnology, + target.Rotation, + target.Scaling, + target.ScanLineOrdering == DisplayConfigScanLineOrdering.NotSpecified + ? new DisplayConfigRational() + : new DisplayConfigRational(target.FrequencyInMillihertz, 1000, true), + target.ScanLineOrdering, + true + ); + } + else + { + targetInfo = new DisplayConfigPathTargetInfo( + target.DisplayTarget.Adapter.AdapterId, + target.DisplayTarget.TargetId, + targetMode.HasValue ? targetModeIndex : DisplayConfigModeInfo.InvalidModeIndex, + target.OutputTechnology, + target.Rotation, + target.Scaling, + target.ScanLineOrdering == DisplayConfigScanLineOrdering.NotSpecified + ? new DisplayConfigRational() + : new DisplayConfigRational(target.FrequencyInMillihertz, 1000, true), + target.ScanLineOrdering, + true + ); + } + + displayConfigPathInfos.Add(new DisplayConfigPathInfo(sourceInfo, targetInfo, flags)); + } + } + } + + modeInfos = displayConfigModeInfos.ToArray(); + + return displayConfigPathInfos.ToArray(); + } + + private static PathInfo[] GetPathInfos(QueryDeviceConfigFlags flags, out DisplayConfigTopologyId topologyId) + { + DisplayConfigPathInfo[] displayPaths; + DisplayConfigModeInfo[] displayModes; + uint pathCount; + + while (true) + { + var error = DisplayConfigApi.GetDisplayConfigBufferSizes(flags, + out pathCount, + out var modeCount); + + if (error != Win32Status.Success) + { + throw new Win32Exception((int) error); + } + + displayPaths = new DisplayConfigPathInfo[pathCount]; + displayModes = new DisplayConfigModeInfo[modeCount]; + + if (flags == QueryDeviceConfigFlags.DatabaseCurrent) + { + error = DisplayConfigApi.QueryDisplayConfig( + flags, + ref pathCount, + displayPaths, + ref modeCount, + displayModes, + out topologyId + ); + } + else + { + topologyId = DisplayConfigTopologyId.None; + error = DisplayConfigApi.QueryDisplayConfig( + flags, + ref pathCount, + displayPaths, + ref modeCount, + displayModes, + IntPtr.Zero + ); + } + + if (error == Win32Status.Success) + { + break; + } + + if (error != Win32Status.ErrorInsufficientBuffer) + { + throw new Win32Exception((int) error); + } + } + + var pathInfos = + new Dictionary< + uint, + Tuple< + DisplayConfigPathSourceInfo, + DisplayConfigSourceMode?, + List< + Tuple< + DisplayConfigPathInfoFlags, + DisplayConfigPathTargetInfo, + DisplayConfigTargetMode?, + DisplayConfigDesktopImageInfo? + > + > + > + >(); + + var sourceId = uint.MaxValue; + + for (var i = 0u; i < pathCount; i++) + { + var displayPath = displayPaths[i]; + DisplayConfigSourceMode? sourceMode = null; + var key = sourceId; + var isVirtualSupported = displayPath.Flags.HasFlag(DisplayConfigPathInfoFlags.SupportVirtualMode); + + if (isVirtualSupported && + displayPath.SourceInfo.SourceModeInfoIndex != DisplayConfigSourceMode.InvalidSourceModeIndex && + displayModes[displayPath.SourceInfo.SourceModeInfoIndex].InfoType == + DisplayConfigModeInfoType.Source) + { + sourceMode = displayModes[displayPath.SourceInfo.SourceModeInfoIndex].SourceMode; + key = displayPath.SourceInfo.SourceModeInfoIndex; + } + else if (!isVirtualSupported && + displayPath.SourceInfo.ModeInfoIndex != DisplayConfigModeInfo.InvalidModeIndex && + displayModes[displayPath.SourceInfo.ModeInfoIndex].InfoType == + DisplayConfigModeInfoType.Source) + { + sourceMode = displayModes[displayPath.SourceInfo.ModeInfoIndex].SourceMode; + key = displayPath.SourceInfo.ModeInfoIndex; + } + else + { + sourceId--; + } + + if (!pathInfos.ContainsKey(key)) + { + pathInfos.Add( + key, + Tuple.Create( + displayPath.SourceInfo, + sourceMode, + new List< + Tuple< + DisplayConfigPathInfoFlags, + DisplayConfigPathTargetInfo, + DisplayConfigTargetMode?, + DisplayConfigDesktopImageInfo? + > + >() + ) + ); + } + + DisplayConfigTargetMode? targetMode = null; + + if (isVirtualSupported && + displayPath.TargetInfo.TargetModeInfoIndex != DisplayConfigTargetMode.InvalidTargetModeIndex && + displayModes[displayPath.TargetInfo.TargetModeInfoIndex].InfoType == DisplayConfigModeInfoType.Target + ) + { + targetMode = displayModes[displayPath.TargetInfo.TargetModeInfoIndex].TargetMode; + } + else if (!isVirtualSupported && + displayPath.TargetInfo.ModeInfoIndex != DisplayConfigModeInfo.InvalidModeIndex && + displayModes[displayPath.TargetInfo.ModeInfoIndex].InfoType == DisplayConfigModeInfoType.Target + ) + { + targetMode = displayModes[displayPath.TargetInfo.ModeInfoIndex].TargetMode; + } + + DisplayConfigDesktopImageInfo? desktopImageMode = null; + + if (isVirtualSupported && + displayPath.TargetInfo.DesktopModeInfoIndex != + DisplayConfigDesktopImageInfo.InvalidDesktopImageModeIndex && + displayModes[displayPath.TargetInfo.DesktopModeInfoIndex].InfoType == + DisplayConfigModeInfoType.DesktopImage) + { + desktopImageMode = displayModes[displayPath.TargetInfo.DesktopModeInfoIndex].DesktopImageInfo; + } + + pathInfos[key].Item3.Add( + Tuple.Create(displayPath.Flags, displayPath.TargetInfo, targetMode, desktopImageMode) + ); + } + + return pathInfos.Select( + pair => new PathInfo(pair.Value.Item1, pair.Value.Item2, pair.Value.Item3) + ).ToArray(); + } + + /// + public override string ToString() + { + return $"{DisplaySource}: {Resolution} @ {Position}"; + } + + private DisplayConfigSourceMode? GetDisplayConfigSourceMode() + { + if (IsModeInformationAvailable) + { + return new DisplayConfigSourceMode( + (uint) Resolution.Width, + (uint) Resolution.Height, + PixelFormat, + new PointL(Position) + ); + } + + return null; + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayConfig/PathTargetDesktopImage.cs b/app/WindowsDisplayAPI/DisplayConfig/PathTargetDesktopImage.cs new file mode 100644 index 00000000..a60504f9 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayConfig/PathTargetDesktopImage.cs @@ -0,0 +1,140 @@ +using System; +using System.Drawing; +using WindowsDisplayAPI.Native.DisplayConfig.Structures; +using WindowsDisplayAPI.Native.Structures; + +namespace WindowsDisplayAPI.DisplayConfig +{ + /// + /// Contains information about the target desktop image + /// + public class PathTargetDesktopImage : IEquatable + { + /// + /// Creates a new PathTargetDesktopImage + /// + /// Size of the VidPn source surface that is being displayed on the monitor + /// + /// Where the desktop image will be positioned within monitor surface size. Region must be + /// completely inside the bounds of the monitor surface size. + /// + /// + /// 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. + /// + 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(); + } + + /// + /// 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. + /// + public Rectangle ImageClip { get; } + + /// + /// 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. + /// + public Rectangle ImageRegion { get; } + + /// + /// Gets the size of the VidPn source surface that is being displayed on the monitor + /// + public Size MonitorSurfaceSize { get; } + + /// + 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; + } + + /// + /// Checks for equality of two PathTargetDesktopImage instances + /// + /// The first instance + /// The second instance + /// true if both instances are equal, otherwise false + public static bool operator ==(PathTargetDesktopImage left, PathTargetDesktopImage right) + { + return Equals(left, right) || left?.Equals(right) == true; + } + + /// + /// Checks for inequality of two PathTargetDesktopImage instances + /// + /// The first instance + /// The second instance + /// true if both instances are not equal, otherwise false + public static bool operator !=(PathTargetDesktopImage left, PathTargetDesktopImage right) + { + return !(left == right); + } + + /// + 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); + } + + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = ImageClip.GetHashCode(); + hashCode = (hashCode * 397) ^ ImageRegion.GetHashCode(); + hashCode = (hashCode * 397) ^ MonitorSurfaceSize.GetHashCode(); + + return hashCode; + } + } + + /// + public override string ToString() + { + return $"{ImageClip} => {ImageRegion} @ {MonitorSurfaceSize}"; + } + + internal DisplayConfigDesktopImageInfo GetDisplayConfigDesktopImageInfo() + { + return new DisplayConfigDesktopImageInfo( + new PointL(MonitorSurfaceSize), + new RectangleL(ImageRegion), + new RectangleL(ImageClip) + ); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayConfig/PathTargetInfo.cs b/app/WindowsDisplayAPI/DisplayConfig/PathTargetInfo.cs new file mode 100644 index 00000000..4d51e7b3 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayConfig/PathTargetInfo.cs @@ -0,0 +1,330 @@ +using WindowsDisplayAPI.Exceptions; +using WindowsDisplayAPI.Native.DisplayConfig; +using WindowsDisplayAPI.Native.DisplayConfig.Structures; + +namespace WindowsDisplayAPI.DisplayConfig +{ + /// + /// Represents a path and its target + /// + public class PathTargetInfo + { + private readonly PathTargetDesktopImage _desktopImage; + private readonly PathTargetSignalInfo _signalInfo; + + /// + /// Creates a new PathTargetInfo + /// + /// The display target device + /// A boolean value indicating the target virtual mode support + public PathTargetInfo(PathDisplayTarget displayTarget, bool isVirtualModeSupported = false) + { + DisplayTarget = displayTarget; + IsVirtualModeSupportedByPath = isVirtualModeSupported; + } + + /// + /// Creates a new PathTargetInfo + /// + /// The display target device + /// Display frequency in millihertz + /// Display scan line ordering + /// Display rotation + /// Display scaling + /// A boolean value indicating the target virtual mode support + 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; + } + + /// + /// Creates a new PathTargetInfo + /// + /// The display target device + /// The display signal information + /// A boolean value indicating the target virtual mode support + public PathTargetInfo( + PathDisplayTarget displayTarget, + PathTargetSignalInfo signalInfo, + bool isVirtualModeSupported = false + ) : this(displayTarget, isVirtualModeSupported) + { + _signalInfo = signalInfo; + FrequencyInMillihertz = signalInfo.VerticalSyncFrequencyInMillihertz; + ScanLineOrdering = signalInfo.ScanLineOrdering; + IsSignalInformationAvailable = true; + } + + /// + /// Creates a new PathTargetInfo + /// + /// The display target device + /// The display signal information + /// Display rotation + /// Display scaling + /// A boolean value indicating the target virtual mode support + 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; + } + + /// + /// Creates a new PathTargetInfo + /// + /// The display target device + /// The display signal information + /// The display desktop image information + /// A boolean value indicating the target virtual mode support + public PathTargetInfo( + PathDisplayTarget displayTarget, + PathTargetSignalInfo signalInfo, + PathTargetDesktopImage desktopImage, + bool isVirtualModeSupported = false + ) : this(displayTarget, signalInfo, isVirtualModeSupported) + { + _desktopImage = desktopImage; + IsDesktopImageInformationAvailable = true; + } + + /// + /// Creates a new PathTargetInfo + /// + /// The display target device + /// The display signal information + /// The display desktop image information + /// Display rotation + /// Display scaling + /// A boolean value indicating the target virtual mode support + 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); + } + } + + /// + /// Gets an instance of PathTargetDesktopImage containing information about this target desktop image + /// + /// Target mode information is missing + public PathTargetDesktopImage DesktopImage + { + get + { + if (!IsDesktopImageInformationAvailable) + { + throw new MissingModeException( + "Desktop image information is missing or not available.", + DisplayConfigModeInfoType.DesktopImage + ); + } + + return _desktopImage; + } + } + + /// + /// Gets extra information about the representing display target device + /// + public PathDisplayTarget DisplayTarget { get; } + + /// + /// Gets a boolean value indicating that the output is currently being forced in a boot-persistent manner + /// + public bool ForcedBootAvailability { get; } + + /// + /// Gets a boolean value indicating that the output is currently being forced in a path-persistent manner + /// + public bool ForcedPathAvailability { get; } + + /// + /// Gets a boolean value indicating that the output is currently being forced in a non-persistent manner + /// + public bool ForcedSystemAvailability { get; } + + /// + /// Gets a value that specifies the refresh rate of the target + /// + public ulong FrequencyInMillihertz { get; } + + /// + /// Gets a boolean value indicating if the target is in use on an active path + /// + public bool IsCurrentlyInUse { get; } + + /// + /// Gets a boolean value indicating the presence of the desktop image information + /// + public bool IsDesktopImageInformationAvailable { get; } + + /// + /// Gets a boolean value indicating that the output can be forced on this target even if a monitor is not detected + /// + public bool IsForcible { get; } + + /// + /// Gets a boolean value indicating if this path is or should be active + /// + public bool IsPathActive { get; } = true; + + /// + /// Gets a boolean value indicating the presence of the signal information + /// + public bool IsSignalInformationAvailable { get; } + + /// + /// Gets a boolean value that indicates if the path supports virtual mode + /// + public bool IsVirtualModeSupportedByPath { get; } + + /// + /// Gets the type of the display device connection + /// + public DisplayConfigVideoOutputTechnology OutputTechnology { get; } = DisplayConfigVideoOutputTechnology.Other; + + /// + /// Gets the rotation of the target + /// + public DisplayConfigRotation Rotation { get; } + + /// + /// Gets the value that specifies how the source image is scaled to the target + /// + public DisplayConfigScaling Scaling { get; } + + /// + /// Gets the value that specifies the scan-line ordering of the output on the target + /// + public DisplayConfigScanLineOrdering ScanLineOrdering { get; } + + /// + /// Gets the target device signal information + /// + /// Target mode information is missing + public PathTargetSignalInfo SignalInfo + { + get + { + if (!IsSignalInformationAvailable) + { + throw new MissingModeException( + "Target mode information is missing or not available.", + DisplayConfigModeInfoType.Target + ); + } + + return _signalInfo; + } + } + + /// + 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; + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayConfig/PathTargetSignalInfo.cs b/app/WindowsDisplayAPI/DisplayConfig/PathTargetSignalInfo.cs new file mode 100644 index 00000000..a2ffa611 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayConfig/PathTargetSignalInfo.cs @@ -0,0 +1,220 @@ +using System; +using System.Drawing; +using WindowsDisplayAPI.Native.DisplayConfig; +using WindowsDisplayAPI.Native.DisplayConfig.Structures; + +namespace WindowsDisplayAPI.DisplayConfig +{ + /// + /// Contains information about the target signal info + /// + public class PathTargetSignalInfo : IEquatable + { + /// + /// Creates a new PathTargetSignalInfo + /// + /// Specifies the width and height (in pixels) of the active portion of the video signal. + /// Specifies the width and height (in pixels) of the entire video signal. + /// Vertical synchronization frequency. + /// The scan-line ordering (for example, progressive or interlaced) of the video signal. + /// + /// 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. + /// + /// + /// 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. + /// + 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; + } + + /// + /// Creates a new PathTargetSignalInfo + /// + /// A possible display settings + /// Total signal size + 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; + } + + /// + /// Gets the width and height (in pixels) of the active portion of the video signal + /// + public Size ActiveSize { get; } + + /// + /// Gets the horizontal synchronization frequency + /// + public ulong HorizontalSyncFrequencyInMillihertz { get; } + + /// + /// Gets the pixel clock rate + /// + public ulong PixelRate { get; } + + /// + /// Gets the scan-line ordering (for example, progressive or interlaced) of the video signal + /// + public DisplayConfigScanLineOrdering ScanLineOrdering { get; } + + /// + /// Gets the width and height (in pixels) of the entire video signal + /// + public Size TotalSize { get; } + + /// + /// 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 + /// + public ushort VerticalSyncFrequencyDivider { get; } + + /// + /// Gets the vertical synchronization frequency + /// + public ulong VerticalSyncFrequencyInMillihertz { get; } + + /// + /// 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 + /// + public VideoSignalStandard VideoStandard { get; } + + /// + 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; + } + + /// + /// Checks for equality of two PathTargetSignalInfo instances + /// + /// The first instance + /// The second instance + /// true if both instances are equal, otherwise false + public static bool operator ==(PathTargetSignalInfo left, PathTargetSignalInfo right) + { + return Equals(left, right) || left?.Equals(right) == true; + } + + /// + /// Checks for inequality of two PathTargetSignalInfo instances + /// + /// The first instance + /// The second instance + /// true if both instances are not equal, otherwise false + public static bool operator !=(PathTargetSignalInfo left, PathTargetSignalInfo right) + { + return !(left == right); + } + + /// + 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); + } + + /// + 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; + } + } + + /// + 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 + ); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayCurveCapabilities.cs b/app/WindowsDisplayAPI/DisplayCurveCapabilities.cs new file mode 100644 index 00000000..5ca6806b --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayCurveCapabilities.cs @@ -0,0 +1,61 @@ +using System; + +namespace WindowsDisplayAPI +{ + /// + /// Contains possible curve drawing capabilities of a display device + /// + [Flags] + public enum DisplayCurveCapabilities + { + /// + /// Device does not support curves. + /// + None = 0, + + /// + /// Device can draw circles. + /// + Circles = 1, + + /// + /// Device can draw pie wedges. + /// + Pie = 2, + + /// + /// Device can draw chord arcs. + /// + Chord = 4, + + /// + /// Device can draw ellipses. + /// + Ellipses = 8, + + /// + /// Device can draw wide borders. + /// + Wide = 16, + + /// + /// Device can draw styled borders. + /// + Styled = 32, + + /// + /// Device can draw borders that are wide and styled. + /// + WideStyled = 64, + + /// + /// Device can draw interiors. + /// + Interiors = 128, + + /// + /// Device can draw rounded rectangles. + /// + RoundRectangle = 256 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayDevice.cs b/app/WindowsDisplayAPI/DisplayDevice.cs new file mode 100644 index 00000000..af75eba6 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayDevice.cs @@ -0,0 +1,143 @@ +using System.Linq; +using WindowsDisplayAPI.DisplayConfig; +using WindowsDisplayAPI.Native.DeviceContext; + +namespace WindowsDisplayAPI +{ + /// + /// Represents a Windows Display Device + /// + public class DisplayDevice : Device + { + /// + /// Creates a new DisplayDevice + /// + /// The device path + /// The device name + /// The device driver registry key + protected DisplayDevice(string devicePath, string deviceName, string deviceKey) + : base(devicePath, deviceName, deviceKey) + { + } + + /// + /// Gets the corresponding instance. + /// + public DisplayScreen DisplayScreen + { + get => DisplayScreen.GetScreens().FirstOrDefault(info => info.ScreenName.Equals(ScreenName)); + } + + /// + /// Creates a new DisplayDevice + /// + /// The device path + /// The device name + /// The device driver registry key + /// The device parent DisplayAdapter + /// true if the device is attached, otherwise false + /// true if this instance is valid, otherwise false + protected DisplayDevice( + string devicePath, + string deviceName, + string deviceKey, + DisplayAdapter adapter, + bool isAvailable, + bool isValid) + : this(devicePath, deviceName, deviceKey) + { + Adapter = adapter; + IsAvailable = isAvailable; + IsValid = isValid; + } + + /// + /// Creates a new DisplayDevice + /// + /// The device path + /// The device name + /// The device driver registry key + /// The device parent DisplayAdapter + /// The device source display name + /// The device target display name + /// true if the device is attached, otherwise false + /// true if this instance is valid, otherwise false + 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; + } + + /// + /// Gets the display device driving display adapter instance + /// + public virtual DisplayAdapter Adapter { get; } + + /// + /// Gets the display device target name + /// + public virtual string DisplayName { get; } + + /// + /// Gets the display device source name + /// + public virtual string ScreenName { get; } + + /// + /// Gets a boolean value indicating if this display device is currently attached + /// + public virtual bool IsAvailable { get; } + + /// + /// Gets a boolean value indicating if this instance is no longer valid, this may happen when display device attached + /// status changes + /// + 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 + ); + } + + /// + public override string ToString() + { + return string.IsNullOrWhiteSpace(DeviceName) + ? $"{GetType().Name}: {DisplayName} - IsAvailable: {IsAvailable}" + : $"{GetType().Name}: {DisplayName} ({DeviceName}) - IsAvailable: {IsAvailable}"; + } + + /// + /// Returns the corresponding PathDisplayTarget instance + /// + /// An instance of PathDisplayTarget, or null + public PathDisplayTarget ToPathDisplayTarget() + { + return PathDisplayTarget + .GetDisplayTargets() + .FirstOrDefault(target => target.DevicePath.Equals(DevicePath)); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayGammaRamp.cs b/app/WindowsDisplayAPI/DisplayGammaRamp.cs new file mode 100644 index 00000000..432d954b --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayGammaRamp.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayLineCapabilities.cs b/app/WindowsDisplayAPI/DisplayLineCapabilities.cs new file mode 100644 index 00000000..d6ed9ff2 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayLineCapabilities.cs @@ -0,0 +1,51 @@ +using System; + +namespace WindowsDisplayAPI +{ + /// + /// Contains possible line drawing capabilities of a display device + /// + [Flags] + public enum DisplayLineCapabilities + { + /// + /// Device does not support lines. + /// + None = 0, + + /// + /// Device can draw a poly line. + /// + PolyLine = 2, + + /// + /// Device can draw a marker. + /// + Marker = 4, + + /// + /// Device can draw multiple markers. + /// + PolyMarker = 8, + + /// + /// Device can draw wide lines. + /// + Wide = 16, + + /// + /// Device can draw styled lines. + /// + Styled = 32, + + /// + /// Device can draw lines that are wide and styled. + /// + WideStyled = 64, + + /// + /// Device can draw interiors. + /// + Interiors = 128 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayPolygonalCapabilities.cs b/app/WindowsDisplayAPI/DisplayPolygonalCapabilities.cs new file mode 100644 index 00000000..015113a8 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayPolygonalCapabilities.cs @@ -0,0 +1,56 @@ +using System; + +namespace WindowsDisplayAPI +{ + /// + /// Contains possible polygon drawing capabilities of a display device + /// + [Flags] + public enum DisplayPolygonalCapabilities + { + /// + /// Device does not support polygons. + /// + None = 0, + + /// + /// Device can draw alternate-fill polygons. + /// + Polygon = 1, + + /// + /// Device can draw rectangles. + /// + Rectangle = 2, + + /// + /// Device can draw winding-fill polygons. + /// + WindingFillPolygon = 4, + + /// + /// Device can draw a single scan-line. + /// + ScanLine = 8, + + /// + /// Device can draw wide borders. + /// + Wide = 16, + + /// + /// Device can draw styled borders. + /// + Styled = 32, + + /// + /// Device can draw borders that are wide and styled. + /// + WideStyled = 64, + + /// + /// Device can draw interiors. + /// + Interiors = 128 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayPossibleSetting.cs b/app/WindowsDisplayAPI/DisplayPossibleSetting.cs new file mode 100644 index 00000000..38abc942 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayPossibleSetting.cs @@ -0,0 +1,63 @@ +using System.Drawing; +using WindowsDisplayAPI.Native.DeviceContext; +using WindowsDisplayAPI.Native.DeviceContext.Structures; + +namespace WindowsDisplayAPI +{ + /// + /// Represents a possible display setting + /// + public class DisplayPossibleSetting + { + /// + /// Creates a new DisplayPossibleSetting + /// + /// Display resolution + /// Display frequency + /// Display color depth + /// Indicating if display is using interlaces scan out + 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) + ) + { + } + + /// + /// Gets the color depth of the display monitor in bits per pixel + /// + public ColorDepth ColorDepth { get; } + + /// + /// Gets the frequency of the display monitor in hz + /// + public int Frequency { get; } + + /// + /// Gets a boolean value indicating if the display uses the interlaced signal + /// + public bool IsInterlaced { get; } + + /// + /// Gets the size of the display monitor + /// + public Size Resolution { get; } + + /// + public override string ToString() + { + return $"{Resolution} {(IsInterlaced ? "Interlaced" : "Progressive")} {Frequency}hz @ {ColorDepth}"; + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayRasterCapabilities.cs b/app/WindowsDisplayAPI/DisplayRasterCapabilities.cs new file mode 100644 index 00000000..fa7a34c0 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayRasterCapabilities.cs @@ -0,0 +1,91 @@ +using System; + +namespace WindowsDisplayAPI +{ + /// + /// Contains possible raster capabilities of a display device + /// + [Flags] + public enum DisplayRasterCapabilities + { + /// + /// Capable of transferring bitmaps. + /// + BitmapTransfer = 1, + + /// + /// Requires banding support. + /// + RequiresBanding = 2, + + /// + /// Capable of scaling. + /// + Scaling = 4, + + /// + /// Capable of supporting bitmaps larger than 64 KB. + /// + Bitmap64K = 8, + + /// + /// Specifies GDI 2.0 compatibility. + /// + GDI20Output = 16, + + /// + /// Includes a state block in the device context. + /// + GDI20State = 32, + + /// + /// Capable of saving bitmaps locally in shadow memory. + /// + SaveBitmap = 64, + + /// + /// Capable of modification and retrieval of display independent bitmap data. + /// + DeviceIndependentBitmap = 128, + + /// + /// Specifies a palette-based device. + /// + Palette = 256, + + /// + /// Capable of sending display independent bitmap to device. + /// + DeviceIndependentBitmapToDevice = 512, + + /// + /// Capable of supporting fonts larger than 64 KB. + /// + Font64K = 1024, + + /// + /// Capable of stretching bitmaps. + /// + StretchBitmap = 2048, + + /// + /// Capable of performing flood fills. + /// + FloodFill = 4096, + + /// + /// Capable of stretching display independent bitmaps. + /// + StretchDeviceIndependentBitmap = 8192, + + /// + /// Supports transparent bitmap and DirectX arrays. + /// + DirectXOutput = 16384, + + /// + /// Capable of working with hardware-dependent bitmaps. + /// + DeviceBits = 32768 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayScreen.cs b/app/WindowsDisplayAPI/DisplayScreen.cs new file mode 100644 index 00000000..7aa604e6 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayScreen.cs @@ -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 +{ + /// + /// Contains information about a display source screen + /// + public class DisplayScreen + { + private readonly IntPtr _monitorHandle; + + private DisplayScreen(IntPtr monitorHandle) + { + _monitorHandle = monitorHandle; + } + + /// + /// Gets the source identification number + /// + 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)); + } + } + + /// + /// Gets a list of all active screens + /// + /// An array of instances. + public static DisplayScreen[] GetScreens() + { + var result = new List(); + 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; + } + + /// + /// Gets a instance representing the screen current settings + /// + public DisplaySetting CurrentSetting + { + get => DisplaySetting.GetCurrentFromScreenName(ScreenName); + } + + /// + /// Gets a instance representing this screen saved settings + /// + public DisplaySetting SavedSetting + { + get => DisplaySetting.GetSavedFromScreenName(ScreenName); + } + + /// + /// Disables and detaches all devices connected to this screen + /// + /// Indicating if the changes should be applied immediately, recommended value is false + public void Disable(bool apply) + { + SetSettings(new DisplaySetting(), apply); + } + + /// + /// Enables and attach all devices connected to this screen + /// + /// The display settings that should be applied while enabling the display device + /// Indicating if the changes should be applied immediately, recommended value is false + public void Enable(DisplaySetting displaySetting, bool apply = false) + { + SetSettings(displaySetting, apply); + } + + /// + /// Changes the display device settings to a new instance + /// + /// The display settings that should be applied + /// Indicating if the changes should be applied immediately, recommended value is false + public void SetSettings(DisplaySetting displaySetting, bool apply = false) + { + if (!IsValid) + { + throw new InvalidDisplayException(null); + } + + displaySetting.Save(ScreenName, apply); + } + + /// + /// Get information about the monitor covering the most of a rectangle. + /// + /// The rectangle to get the main monitor information for. + /// An instance of . + public static DisplayScreen FromRectangle(Rectangle rectangle) + { + var monitorHandle = DeviceContextApi.MonitorFromRect( + new RectangleL(rectangle), + MonitorFromFlag.DefaultToNearest + ); + + return monitorHandle == IntPtr.Zero ? null : new DisplayScreen(monitorHandle); + } + + /// + /// Get information about the monitor containing or the nearest to a point. + /// + /// The point to get the main monitor information for. + /// An instance of . + public static DisplayScreen FromPoint(Point point) + { + var monitorHandle = DeviceContextApi.MonitorFromPoint( + new PointL(point), + MonitorFromFlag.DefaultToNearest + ); + + return monitorHandle == IntPtr.Zero ? null : new DisplayScreen(monitorHandle); + } + + /// + /// Get information about the screen covering the most of a window. + /// + /// The window handle to get the main screen information for. + /// An instance of . + 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 + /// + /// Returns the corresponding instance + /// + /// A instance of Screen object + 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 + + /// + /// Get the corresponding instances. + /// + /// An array of instances. + public Display[] GetDisplays() + { + return Display.GetDisplays().Where(display => display.ScreenName.Equals(ScreenName)).ToArray(); + } + + /// + /// Gets the bounds of the monitor + /// + public Rectangle Bounds + { + get => GetMonitorInfo()?.Bounds.ToRectangle() ?? Rectangle.Empty; + } + + /// + /// Gets the source name of the screen + /// + public string ScreenName + { + get => GetMonitorInfo()?.DisplayName; + } + + /// + /// Gets a boolean value indicating if this is the primary display + /// + public bool IsPrimary + { + get => GetMonitorInfo()?.Flags.HasFlag(MonitorInfoFlags.Primary) ?? false; + } + + /// + /// Gets a boolean value indicating if this instance contains valid information. + /// + public bool IsValid + { + get => GetMonitorInfo() != null; + } + + /// + /// Gets the working area of the monitor + /// + public Rectangle WorkingArea + { + get => GetMonitorInfo()?.WorkingArea.ToRectangle() ?? Rectangle.Empty; + } + + /// + /// Returns a list of possible display setting for this screen + /// + /// An enumerable list of instances + public IEnumerable 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); + } + } + + /// + /// Returns the best possible display setting for this screen + /// + /// A instance + 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; + } + + /// + /// Returns the corresponding instance + /// + /// An instance of , or null + 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; + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplaySetting.cs b/app/WindowsDisplayAPI/DisplaySetting.cs new file mode 100644 index 00000000..eeeea865 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplaySetting.cs @@ -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 +{ + /// + /// Holds configurations of a windows display + /// + public class DisplaySetting : DisplayPossibleSetting + { + /// + /// Creates a new instance. + /// + /// The basic configuration information object + /// Display position on desktop + public DisplaySetting(DisplayPossibleSetting validSetting, Point position = default) + : this(validSetting, position, DisplayOrientation.Identity, DisplayFixedOutput.Default) + { + } + + /// + /// Creates a new instance. + /// + /// The basic configuration information object + /// Display position on desktop + /// Display orientation and rotation + /// + /// Display output behavior in case of presenting a low-resolution mode on a + /// higher-resolution display + /// + public DisplaySetting( + DisplayPossibleSetting validSetting, + Point position, + DisplayOrientation orientation, + DisplayFixedOutput outputScalingMode) + : this( + validSetting.Resolution, position, validSetting.ColorDepth, validSetting.Frequency, + validSetting.IsInterlaced, orientation, outputScalingMode + ) + { + } + + /// + /// Creates a new instance. + /// + /// Display resolution + /// Display position on desktop + /// Display frequency + public DisplaySetting(Size resolution, Point position, int frequency) + : this(resolution, position, ColorDepth.Depth32Bit, frequency) + { + } + + /// + /// Creates a new instance. + /// + /// Display resolution + /// Display frequency + public DisplaySetting(Size resolution, int frequency) + : this(resolution, new Point(0, 0), ColorDepth.Depth32Bit, frequency) + { + } + + /// + /// Creates a new instance. + /// + /// Display resolution + /// Display position on desktop + /// Display frequency + /// Display color depth + /// Indicating if display is using interlaces scan out + /// Display orientation and rotation + /// + /// Display output behavior in case of presenting a low-resolution mode on a + /// higher-resolution display + /// + 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; + } + } + + /// + /// Gets a boolean value indicating if this instance is currently enable + /// + public bool IsEnable { get; } = true; + + /// + /// Gets or sets the orientation of the display monitor + /// + public DisplayOrientation Orientation { get; } + + /// + /// Gets output behavior in case of presenting a low-resolution mode on a higher-resolution display + /// + public DisplayFixedOutput OutputScalingMode { get; } + + /// + /// Gets or sets the position of the display monitor + /// + public Point Position { get; } + + /// + /// Applies settings that are saved using SaveDisplaySettings() or other similar methods but not yet applied + /// + 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); + } + } + + /// + /// Returns the current display settings of a screen + /// + /// The name of the screen. + /// An instance of + public static DisplaySetting GetCurrentFromScreenName(string screenName) + { + return new DisplaySetting(GetDeviceMode(screenName, DisplaySettingsMode.CurrentSettings)); + } + + /// + /// Returns the saved display settings of a screen + /// + /// The name of the screen. + /// An instance of + public static DisplaySetting GetSavedFromScreenName(string screenName) + { + return new DisplaySetting(GetDeviceMode(screenName, DisplaySettingsMode.RegistrySettings)); + } + + /// + /// Sets and possibility applies a list of display settings + /// + /// + /// A key value dictionary of and + /// instances. + /// + /// Indicating if the changes should be applied immediately, recommended value is false + public static void SaveDisplaySettings( + Dictionary newSettingPairs, + bool applyNow) + { + SaveDisplaySettings( + newSettingPairs.ToDictionary(pair => pair.Key.ScreenName, pair => pair.Value), + applyNow, + true + ); + } + + /// + /// Sets and possibility applies a list of display settings + /// + /// A key value dictionary of source ids and instance + /// Indicating if the changes should be applied immediately, recommended value is false + public static void SaveDisplaySettings( + Dictionary 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 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; + } + } + + /// + 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; + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayShaderBlendingCapabilities.cs b/app/WindowsDisplayAPI/DisplayShaderBlendingCapabilities.cs new file mode 100644 index 00000000..0a704767 --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayShaderBlendingCapabilities.cs @@ -0,0 +1,41 @@ +using System; + +namespace WindowsDisplayAPI +{ + /// + /// Contains possible shader blending capabilities of a display device + /// + [Flags] + public enum DisplayShaderBlendingCapabilities + { + /// + /// Device does not support any of these capabilities. + /// + None = 0, + + /// + /// Capable of handling constant alpha + /// + ConstantAlpha = 1, + + /// + /// Capable of handling per-pixel alpha. + /// + PerPixelAlpha = 2, + + /// + /// Capable of handling pre-multiplied alpha + /// + PreMultipliedAlpha = 4, + + /// + /// Capable of doing gradient fill rectangles. + /// + RectangleGradient = 16, + + /// + /// Capable of doing gradient fill triangles. + /// + TriangleGradient = 32 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/DisplayTextCapabilities.cs b/app/WindowsDisplayAPI/DisplayTextCapabilities.cs new file mode 100644 index 00000000..bbe8f76c --- /dev/null +++ b/app/WindowsDisplayAPI/DisplayTextCapabilities.cs @@ -0,0 +1,91 @@ +using System; + +namespace WindowsDisplayAPI +{ + /// + /// Contains possible text drawing capabilities of a display device + /// + [Flags] + public enum DisplayTextCapabilities + { + /// + /// Device is capable of character output precision. + /// + CharacterOutputPrecision = 1, + + /// + /// Device is capable of stroke output precision. + /// + StrokeOutputPrecision = 2, + + /// + /// Device is capable of stroke clip precision. + /// + StrokeClipPrecision = 4, + + /// + /// Device is capable of 90-degree character rotation. + /// + CharacterRotation90 = 8, + + /// + /// Device is capable of any character rotation. + /// + CharacterRotationAny = 16, + + /// + /// Device can scale independently in the x-direction and y-direction. + /// + IndependentXYScaling = 32, + + /// + /// Device is capable of doubled character for scaling. + /// + DoubleCharacterScaling = 64, + + /// + /// Device uses integer multiples only for character scaling. + /// + IntegerCharacterScaling = 128, + + /// + /// Device uses any multiples for exact character scaling. + /// + ExactCharacterScaling = 256, + + /// + /// Device can draw double-weight characters. + /// + DoubleWeightCharacter = 512, + + /// + /// Device can italicize. + /// + CanItalicize = 1024, + + /// + /// Device can underline. + /// + CanUnderline = 2048, + + /// + /// Device can draw strikeouts. + /// + CanStrikeout = 4096, + + /// + /// Device can draw raster fonts. + /// + RasterFonts = 8192, + + /// + /// Device can draw vector fonts. + /// + VectorFonts = 16384, + + /// + /// Device cannot scroll using a bit-block transfer. Note that this meaning may be the opposite of what you expect. + /// + BitBlockTransferScrollInAbility = 65536 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Exceptions/DuplicateModeException.cs b/app/WindowsDisplayAPI/Exceptions/DuplicateModeException.cs new file mode 100644 index 00000000..77fd59e9 --- /dev/null +++ b/app/WindowsDisplayAPI/Exceptions/DuplicateModeException.cs @@ -0,0 +1,18 @@ +using System; + +namespace WindowsDisplayAPI.Exceptions +{ + /// + /// Represents errors that occurs because of two similar but not identical path or path target + /// + public class DuplicateModeException : Exception + { + /// + /// Creates a new DuplicateModeException exception + /// + /// The human readable message of the exception + public DuplicateModeException(string message) : base(message) + { + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Exceptions/InvalidDisplayException.cs b/app/WindowsDisplayAPI/Exceptions/InvalidDisplayException.cs new file mode 100644 index 00000000..f3a468d9 --- /dev/null +++ b/app/WindowsDisplayAPI/Exceptions/InvalidDisplayException.cs @@ -0,0 +1,32 @@ +using System; + +namespace WindowsDisplayAPI.Exceptions +{ + /// + /// Represents errors that occurs because of an invalid display instance + /// + public class InvalidDisplayException : Exception + { + /// + /// Creates a new InvalidDisplayException + /// + /// The path of invalidated display device + public InvalidDisplayException(string displayPath) + { + DisplayPath = displayPath; + } + + /// + /// Creates a new InvalidDisplayException + /// + public InvalidDisplayException() : this(null) + { + + } + + /// + /// Gets the path of the display device + /// + public string DisplayPath { get; } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Exceptions/InvalidEDIDInformation.cs b/app/WindowsDisplayAPI/Exceptions/InvalidEDIDInformation.cs new file mode 100644 index 00000000..5a93a8bd --- /dev/null +++ b/app/WindowsDisplayAPI/Exceptions/InvalidEDIDInformation.cs @@ -0,0 +1,18 @@ +using System; + +namespace WindowsDisplayAPI.Exceptions +{ + /// + /// Represents errors that occurs because of missing or invalid EDID information + /// + public class InvalidEDIDInformation : Exception + { + /// + /// Creates a new InvalidEDIDInformation exception + /// + /// The human readable message of the exception + public InvalidEDIDInformation(string message) : base(message) + { + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Exceptions/InvalidRegistryAddressException.cs b/app/WindowsDisplayAPI/Exceptions/InvalidRegistryAddressException.cs new file mode 100644 index 00000000..265e11fa --- /dev/null +++ b/app/WindowsDisplayAPI/Exceptions/InvalidRegistryAddressException.cs @@ -0,0 +1,18 @@ +using System; + +namespace WindowsDisplayAPI.Exceptions +{ + /// + /// Represents errors that occurs because of missing or invalid registry address information + /// + public class InvalidRegistryAddressException : Exception + { + /// + /// Creates a new InvalidRegistryAddressException exception + /// + /// The human readable message of the exception + public InvalidRegistryAddressException(string message) : base(message) + { + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Exceptions/MissingDisplayException.cs b/app/WindowsDisplayAPI/Exceptions/MissingDisplayException.cs new file mode 100644 index 00000000..6346e411 --- /dev/null +++ b/app/WindowsDisplayAPI/Exceptions/MissingDisplayException.cs @@ -0,0 +1,25 @@ +using System; + +namespace WindowsDisplayAPI.Exceptions +{ + /// + /// Represents errors that occurs because of a missing display + /// + public class MissingDisplayException : Exception + { + /// + /// Creates a new MissingDisplayException + /// + /// The path of missing display device + /// The human readable message of the exception + public MissingDisplayException(string message, string displayPath) : base(message) + { + DisplayPath = displayPath; + } + + /// + /// Gets the path of the display device + /// + public string DisplayPath { get; } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Exceptions/MissingModeException.cs b/app/WindowsDisplayAPI/Exceptions/MissingModeException.cs new file mode 100644 index 00000000..25337722 --- /dev/null +++ b/app/WindowsDisplayAPI/Exceptions/MissingModeException.cs @@ -0,0 +1,26 @@ +using System; +using WindowsDisplayAPI.Native.DisplayConfig; + +namespace WindowsDisplayAPI.Exceptions +{ + /// + /// Represents errors that occurs because of missing mode information + /// + public class MissingModeException : Exception + { + /// + /// Creates a new MissingModeException + /// + /// The missing mode type + /// The human readable message of the exception + public MissingModeException(string message, DisplayConfigModeInfoType missingModeType) : base(message) + { + MissingModeType = missingModeType; + } + + /// + /// Gets the missing mode type + /// + public DisplayConfigModeInfoType MissingModeType { get; } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Exceptions/ModeChangeException.cs b/app/WindowsDisplayAPI/Exceptions/ModeChangeException.cs new file mode 100644 index 00000000..4fdae556 --- /dev/null +++ b/app/WindowsDisplayAPI/Exceptions/ModeChangeException.cs @@ -0,0 +1,37 @@ +using System; +using WindowsDisplayAPI.Native.DeviceContext; + +namespace WindowsDisplayAPI.Exceptions +{ + /// + /// Represents errors that occurs during a mode change request + /// + public class ModeChangeException : Exception + { + /// + /// Creates a new ModeChangeException + /// + /// The device responsible for the mode change + /// The error code + /// The human readable message of the exception + public ModeChangeException( + string message, + DisplayDevice device, + ChangeDisplaySettingsExResults errorCode + ) : base(message) + { + Device = device; + ErrorCode = errorCode; + } + + /// + /// Gets the display device responsible for the mode change + /// + public DisplayDevice Device { get; } + + /// + /// Gets the error code + /// + public ChangeDisplaySettingsExResults ErrorCode { get; } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Exceptions/NotACloneMemberException.cs b/app/WindowsDisplayAPI/Exceptions/NotACloneMemberException.cs new file mode 100644 index 00000000..e4333926 --- /dev/null +++ b/app/WindowsDisplayAPI/Exceptions/NotACloneMemberException.cs @@ -0,0 +1,18 @@ +using System; + +namespace WindowsDisplayAPI.Exceptions +{ + /// + /// Represents errors that occurs because of not being in a valid clone group + /// + public class NotACloneMemberException : Exception + { + /// + /// Creates a new NotACloneMemberException + /// + /// The human readable message of the exception + public NotACloneMemberException(string message) : base(message) + { + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Exceptions/PathChangeException.cs b/app/WindowsDisplayAPI/Exceptions/PathChangeException.cs new file mode 100644 index 00000000..d92f13b3 --- /dev/null +++ b/app/WindowsDisplayAPI/Exceptions/PathChangeException.cs @@ -0,0 +1,27 @@ +using System; + +namespace WindowsDisplayAPI.Exceptions +{ + /// + /// Represents errors that occurs because of an invalid path request + /// + public class PathChangeException : Exception + { + /// + /// Creates a new PathChangeException + /// + /// The human readable message of the exception + public PathChangeException(string message) : base(message) + { + } + + /// + /// Creates a new PathChangeException + /// + /// The human readable message of the exception + /// The inner causing exception + public PathChangeException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Exceptions/TargetNotAvailableException.cs b/app/WindowsDisplayAPI/Exceptions/TargetNotAvailableException.cs new file mode 100644 index 00000000..e46400b5 --- /dev/null +++ b/app/WindowsDisplayAPI/Exceptions/TargetNotAvailableException.cs @@ -0,0 +1,33 @@ +using System; +using WindowsDisplayAPI.Native.Structures; + +namespace WindowsDisplayAPI.Exceptions +{ + /// + /// Represents errors that occurs because of path target being inavailable + /// + public class TargetNotAvailableException : Exception + { + /// + /// Creates a new TargetNotAvailableException + /// + /// The human readable message of the exception + /// The driving adapter's identification + /// The target identification number + public TargetNotAvailableException(string message, LUID adapterId, uint targetId) : base(message) + { + AdapterId = adapterId; + TargetId = targetId; + } + + /// + /// Gets the driving adapter's identification + /// + public LUID AdapterId { get; } + + /// + /// Gets the target's identification number + /// + public uint TargetId { get; } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Icon.png b/app/WindowsDisplayAPI/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f67cd2eba5a5d93a7096788d5ed550877cd824c7 GIT binary patch literal 3931 zcmbVPd0dl8((V93jT|$|!VDlpl-rO55<((}$c;wA;eZNDP9Q)MNTT5|5^q!-kV8&E z5nV?v6^#NOD7uOeKv6IviU;8qltY6G`=aA~-|zR$?zg-7r>iT^Q&nBn)!pv_$_6h@ zggycQpy}i79smFg5@BGWIy8jx&qP4O&q9w7VIVI;D5CLMz?I3PvyeUp8?g$P5)>q!hA$A&hKu}R>>L2LjVT@vDGj2M;>NoPf}xlSfy^*2nAY^IY* zFrJJd$GNeh*xpHeR$$VGAVyLQgUB>-aYi~OkRSsbmXL-_;KXtTqy#6EuXah0Jg-KZ zAiqL{F-|7mf(jv1kZwFa3yHVEqZk-lERsO9!Qyd5EOr&r7K61zV=-u3A_|Ko;RqzG zJ@WUT3FM8>j35QLd;abVnmL(7359VaG+HDQ*@*0Hc>G8-mPjO`F}7%1TNH#q2@<(N zS^|nISn=J0J4?Xev*Uzp9v3-pkw)ij6*`$fk$zhOCyq@1!!TFyd!nF}p%Z9vXsite z&Ed?i>nmCy3}F3F7=MZu1SQ6?&;cv~Z!4bx)gxlXcQTZ_f9+@<2t`BkVa|azB;ADkY7_w zV*iPI29t>+;0QDn2G7Ky7%UtfMYG4#QA8#NkH^{5=htuYJD>UArVb4Sg`VFh|7+iT zS3#R`Ui_mApvfP7#NtBD!-u-WsKQqWz+#DyyK7Lw(~%x=#>TD7?!FuA*eO{g47J8)b`3tB1;4k7W0l)$P9RQ%?1P&xBU=0i$2mf4sE~NSI_q4=kn%`ZPpB`+#81b-zc$(dA z_}K7~VY^}HWcQ4jRhI@&iRyi=<&_g@J8S6DRW2`=XEn99ln7oC%;7+zf?{W$bt!*N z6gr_NGsd;CH|(2|^xwtIICfN>;j?tw6Nny)P_lSP#GB~)x-V;%;@P)`(a^lCU5%!R(-s~J%01>Y= z8R90>%sgR$ySXfJ-q+M$WSUSgRZ|7$p}|a$){qXF4YS6VK$#Oc*$i51`2)#r=TfCD zL?2%L>NQ>+0&rjB!cRc}aZj}v_UtD$T3n$befMWMf_$Vby}aajEyZq~pf;jz&-l|X znb(t#)Lt1-M*th!596Px87|2){aQtRV=dCj0NGT5f$0}#aBx(anEuwi$_=1krp z#@NQ_R_|esbn#2wOBi=(jD!*xt`I&m4k1a0A$`gR_H>AYjUcq~6vV4`mh~Kc zs~rN>Vb)GGCijKvk#zeOHp~qMZaz6BkCf-NOsSargEbiY%?_vGI^YKlgAb}LCxL`| z8@~W$(6I^u7Pd}@u98e#H(KLpL@+naTb;M6tL4b#V2;1D`ODzH)?O$(&N_sDJ(zci z71%aVE0!C&U5pQ=4c_2hX4~hh?(#JiSMH%MUZXdQ&vXumMjTpab0Ovq#x(Jc+{i6O z&NPU86eM#|wFY71vcBx%>`7&Z#BS!$T_3BIi|#Xp9R1Ghm%0s<@>vhv9S)Y6=1rM< zrH9Xw&TV!2GCFtBIaFp2tMb+LyU6j@Qwsof<1b3J3GaTCbijWoq;_YV-Hw*DY@1`W z>~OeYjI?i+E+>y)11rVwOsfc&&|IsEa~yBRZY!v#EG#Z@ykGCOkTmB_Nj9mTs{;3- z$-9=MH7hq!=-nCn#J4-CT54AM1L(_~ZfWv_LY8dndbcYvvx*>qLc~ z_DIUxhPoKY8< zkriQ^jJ-Y_V7AJJZ&s|;NbtXX@Zdq6LmC;&PzEbj)YNIhu9!T~i|1fq&QewK+G*VxT2V>RzKm4X z4Y{1G>ygq%CtmqjfL_C@!cJ4u>0=9^lgu^5exmGfV-ghp_Fq{nLc%;#3vTdTT^^LnOxqMVioo?T+E zB{Kw*Dsb@ejZ`^$fxaQDva$PwvZ37Enz~JO?oyI}VZ}8?p3H6g!)Ei70BkfOH(kzM zmKzgUbS^P3qVSCf^7f;kU`!@>%6fdu(*UiUq7~veIsl`QbH_&+!dY8&hLfwz+H%UV z)A;BKm$y?(1HipS8&3@~rmWXx(y!MV{J75dd{t+<)nM-VK5@Xt=gC6{o$)z~e$#(o zePq06@KbdG!2&q+7j`|4DJs#>{`jtJyXL|tV;(*!U%X&ItFEou{4@adoChPN0DKU# zJC#Zm9YC!fW6vL3$tg?|hiEV$wup!Dd}Ooz()kTCSiU=creGPg5iwI>3MC*7ZRqhQy(P)-17CGgEXmv^b^4eRJJ=W>Y4% zve|9Y$O2F^o-R>dxN#zA+6a{sto4`X;kyXae8h+3QXr{`8oi%8b2QJ8`>W4YOS9@O zy{6~;ZWSe$=zyY*%I@I<#g2m`vaF-Op21z#^9|e*q~DDwd&xX8l3Yz0+A?u0Pge)b z-qqWY_~X)>UO6!%a&E8png*KA|9AQRXIJ$w$4$b z9xOSaCRlcFU}y;OIAQU=Pip1C)x|j8^Q*a~4=f6o#%=cV^W#sC$g^(Y6y8=j`r`{; zy-O%}&bCX2`CWY6+4<|ed@=wpJz!tskYs|-x{J2J#ywnmk1?Mhj^`(;C!8k6!pC(d zG{#%D(5xjcc5R^Tvn8a;HoLQ3)lokCLg%c?naWCWxUmuIIjnKoEv#6dqu)-{hTB8F zjyPXE`5VnhrN8e*!J$haX>BmbUy^r1yP93vvX1IqhZhK6rgTVmYSsZ;({tX&#gEe% z#VWhs7c11mK88!<lXzLHZCpL*s_JX=Ej3(Cl{w=RjL~DVMYFG!2^w(WdSmG z1wQUR%_n8+tRQTBj{Aw)Ru5V&cI7#Tmex`NR}EHcgv~r{A9<8tm6+Ogy&`LJr;W|1 z`9bEx--xBeL&c1(pW;y7#lhLmnlP$j_))%}_tDU_~ zGqZ&E?#(G!jSFU5<&R!ZY;Wwhy;HZPW>d{yJKEl`mQv3Q;S^~VFQT2BZW&uepy=ln z=K6x=hYRyo>NHTc``5VDWXKJZ^kRyq6Eszcp&=QMet;BA*<3-dTozksZHn)+-K zB}1)ONA%9gZ^z0K4REaaNhA01dX`k-DCqkwIbTDkK`#CBQeEJ5f4i9~Jg%TNNV%~k zATqC9TNY_c0e?fiBJ2`4-(UGtaVTj?+@8~@i5JgWa3As;_pQs*Xe>%?yk>>@cVDdk gw+((Om|KVe3q7(A8&7Pmo&VhR@z~&AwU)Z;AMFD_b^rhX literal 0 HcmV?d00001 diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/ChangeDisplaySettingsExResults.cs b/app/WindowsDisplayAPI/Native/DeviceContext/ChangeDisplaySettingsExResults.cs new file mode 100644 index 00000000..05c15325 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/ChangeDisplaySettingsExResults.cs @@ -0,0 +1,48 @@ +namespace WindowsDisplayAPI.Native.DeviceContext +{ + /// + /// Contains possible values for the result of mode change request + /// + public enum ChangeDisplaySettingsExResults + { + /// + /// Completed successfully + /// + Successful = 0, + + /// + /// Changes needs restart + /// + Restart = 1, + + /// + /// Failed to change and save setings + /// + Failed = -1, + + /// + /// Invalid data provide + /// + BadMode = -2, + + /// + /// Changes not updated + /// + NotUpdated = -3, + + /// + /// Invalid flags provided + /// + BadFlags = -4, + + /// + /// Bad parameters provided + /// + BadParam = -5, + + /// + /// Bad Dual View mode used with mode + /// + BadDualView = -6 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/ChangeDisplaySettingsFlags.cs b/app/WindowsDisplayAPI/Native/DeviceContext/ChangeDisplaySettingsFlags.cs new file mode 100644 index 00000000..28cb310e --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/ChangeDisplaySettingsFlags.cs @@ -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 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/DCHandle.cs b/app/WindowsDisplayAPI/Native/DeviceContext/DCHandle.cs new file mode 100644 index 00000000..123222ec --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/DCHandle.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/DeviceCapability.cs b/app/WindowsDisplayAPI/Native/DeviceContext/DeviceCapability.cs new file mode 100644 index 00000000..1804830e --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/DeviceCapability.cs @@ -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, + } +} diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/DeviceModeFields.cs b/app/WindowsDisplayAPI/Native/DeviceContext/DeviceModeFields.cs new file mode 100644 index 00000000..cd3ed154 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/DeviceModeFields.cs @@ -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, + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/DisplayDeviceStateFlags.cs b/app/WindowsDisplayAPI/Native/DeviceContext/DisplayDeviceStateFlags.cs new file mode 100644 index 00000000..0a43f3e6 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/DisplayDeviceStateFlags.cs @@ -0,0 +1,41 @@ +using System; + +namespace WindowsDisplayAPI.Native.DeviceContext +{ + [Flags] + internal enum DisplayDeviceStateFlags : uint + { + /// + /// The device is part of the desktop. + /// + AttachedToDesktop = 0x1, + MultiDriver = 0x2, + + /// + /// The device is part of the desktop. + /// + PrimaryDevice = 0x4, + + /// + /// Represents a pseudo device used to mirror application drawing for remoting or other purposes. + /// + MirroringDriver = 0x8, + + /// + /// The device is VGA compatible. + /// + VGACompatible = 0x10, + + /// + /// The device is removable; it cannot be the primary display. + /// + Removable = 0x20, + + /// + /// The device has more display modes than its output devices support. + /// + ModesPruned = 0x8000000, + Remote = 0x4000000, + Disconnect = 0x2000000 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/DisplayFixedOutput.cs b/app/WindowsDisplayAPI/Native/DeviceContext/DisplayFixedOutput.cs new file mode 100644 index 00000000..d8f06018 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/DisplayFixedOutput.cs @@ -0,0 +1,23 @@ +namespace WindowsDisplayAPI.Native.DeviceContext +{ + /// + /// Contains possible values for the display fixed output + /// + public enum DisplayFixedOutput : uint + { + /// + /// Default behavior + /// + Default = 0, + + /// + /// Stretches the output to fit to the display + /// + Stretch = 1, + + /// + /// Centers the output in the middle of the display + /// + Center = 2 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/DisplayFlags.cs b/app/WindowsDisplayAPI/Native/DeviceContext/DisplayFlags.cs new file mode 100644 index 00000000..43d47c20 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/DisplayFlags.cs @@ -0,0 +1,12 @@ +using System; + +namespace WindowsDisplayAPI.Native.DeviceContext +{ + [Flags] + internal enum DisplayFlags : uint + { + None = 0, + Grayscale = 1, + Interlaced = 2 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/DisplayOrientation.cs b/app/WindowsDisplayAPI/Native/DeviceContext/DisplayOrientation.cs new file mode 100644 index 00000000..a94e5fea --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/DisplayOrientation.cs @@ -0,0 +1,28 @@ +namespace WindowsDisplayAPI.Native.DeviceContext +{ + /// + /// Contains possible values for the display orientation + /// + public enum DisplayOrientation : uint + { + /// + /// No rotation + /// + Identity = 0, + + /// + /// 90 degree rotation + /// + Rotate90Degree = 1, + + /// + /// 180 degree rotation + /// + Rotate180Degree = 2, + + /// + /// 270 degree rotation + /// + Rotate270Degree = 3 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/DisplaySettingsMode.cs b/app/WindowsDisplayAPI/Native/DeviceContext/DisplaySettingsMode.cs new file mode 100644 index 00000000..3f712c03 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/DisplaySettingsMode.cs @@ -0,0 +1,9 @@ +namespace WindowsDisplayAPI.Native.DeviceContext +{ + internal enum DisplaySettingsMode + { + CurrentSettings = -1, + + RegistrySettings = -2 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/DisplayTechnology.cs b/app/WindowsDisplayAPI/Native/DeviceContext/DisplayTechnology.cs new file mode 100644 index 00000000..74b83397 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/DisplayTechnology.cs @@ -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, + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/MonitorFromFlag.cs b/app/WindowsDisplayAPI/Native/DeviceContext/MonitorFromFlag.cs new file mode 100644 index 00000000..79fd0464 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/MonitorFromFlag.cs @@ -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, + } +} diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/MonitorInfoFlags.cs b/app/WindowsDisplayAPI/Native/DeviceContext/MonitorInfoFlags.cs new file mode 100644 index 00000000..5cc558de --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/MonitorInfoFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace WindowsDisplayAPI.Native.DeviceContext +{ + [Flags] + internal enum MonitorInfoFlags : uint + { + None = 0, + Primary = 1 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/Structures/DeviceMode.cs b/app/WindowsDisplayAPI/Native/DeviceContext/Structures/DeviceMode.cs new file mode 100644 index 00000000..14236eab --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/Structures/DeviceMode.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/Structures/DisplayDevice.cs b/app/WindowsDisplayAPI/Native/DeviceContext/Structures/DisplayDevice.cs new file mode 100644 index 00000000..b1c63d29 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/Structures/DisplayDevice.cs @@ -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)) + }; + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/Structures/GammaRamp.cs b/app/WindowsDisplayAPI/Native/DeviceContext/Structures/GammaRamp.cs new file mode 100644 index 00000000..7916b3e6 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/Structures/GammaRamp.cs @@ -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; + } + } +} diff --git a/app/WindowsDisplayAPI/Native/DeviceContext/Structures/MonitorInfo.cs b/app/WindowsDisplayAPI/Native/DeviceContext/Structures/MonitorInfo.cs new file mode 100644 index 00000000..3a21e9bc --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContext/Structures/MonitorInfo.cs @@ -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)) + }; + } + } +} diff --git a/app/WindowsDisplayAPI/Native/DeviceContextApi.cs b/app/WindowsDisplayAPI/Native/DeviceContextApi.cs new file mode 100644 index 00000000..7f98dea6 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DeviceContextApi.cs @@ -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 + ); + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigDeviceInfoType.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigDeviceInfoType.cs new file mode 100644 index 00000000..7b33e374 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigDeviceInfoType.cs @@ -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 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigModeInfoType.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigModeInfoType.cs new file mode 100644 index 00000000..1c473338 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigModeInfoType.cs @@ -0,0 +1,28 @@ +namespace WindowsDisplayAPI.Native.DisplayConfig +{ + /// + /// Possbile types of modes + /// + public enum DisplayConfigModeInfoType : uint + { + /// + /// Invalid value for mode type + /// + Invalid = 0, + + /// + /// Source mode type + /// + Source = 1, + + /// + /// Target mode type + /// + Target = 2, + + /// + /// Display image type + /// + DesktopImage = 3 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPathInfoFlags.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPathInfoFlags.cs new file mode 100644 index 00000000..6e6710dc --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPathInfoFlags.cs @@ -0,0 +1,12 @@ +using System; + +namespace WindowsDisplayAPI.Native.DisplayConfig +{ + [Flags] + internal enum DisplayConfigPathInfoFlags : uint + { + None = 0, + Active = 1, + SupportVirtualMode = 8 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPathSourceInfoFlags.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPathSourceInfoFlags.cs new file mode 100644 index 00000000..a90212a7 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPathSourceInfoFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace WindowsDisplayAPI.Native.DisplayConfig +{ + [Flags] + internal enum DisplayConfigPathSourceInfoFlags : uint + { + None = 0, + InUse = 1 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPathTargetInfoFlags.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPathTargetInfoFlags.cs new file mode 100644 index 00000000..b98e1c74 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPathTargetInfoFlags.cs @@ -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 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPixelFormat.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPixelFormat.cs new file mode 100644 index 00000000..377f1b5b --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigPixelFormat.cs @@ -0,0 +1,39 @@ +namespace WindowsDisplayAPI.Native.DisplayConfig +{ + /// + /// Possible pixel formats + /// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553963(v=vs.85).aspx + /// + public enum DisplayConfigPixelFormat : uint + { + /// + /// Pixel format is not specified + /// + NotSpecified = 0, + + /// + /// Indicates 8 bits per pixel format. + /// + PixelFormat8Bpp = 1, + + /// + /// Indicates 16 bits per pixel format. + /// + PixelFormat16Bpp = 2, + + /// + /// Indicates 24 bits per pixel format. + /// + PixelFormat24Bpp = 3, + + /// + /// Indicates 32 bits per pixel format. + /// + PixelFormat32Bpp = 4, + + /// + /// Indicates that the current display is not an 8, 16, 24, or 32 bits per pixel GDI desktop mode. + /// + PixelFormatNonGDI = 5 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigRotation.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigRotation.cs new file mode 100644 index 00000000..d267db39 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigRotation.cs @@ -0,0 +1,34 @@ +namespace WindowsDisplayAPI.Native.DisplayConfig +{ + /// + /// Rotation modes + /// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553970(v=vs.85).aspx + /// + public enum DisplayConfigRotation : uint + { + /// + /// Rotation mode is not specified + /// + NotSpecified = 0, + + /// + /// Indicates that rotation is 0 degrees—landscape mode. + /// + Identity = 1, + + /// + /// Indicates that rotation is 90 degrees clockwise—portrait mode. + /// + Rotate90 = 2, + + /// + /// Indicates that rotation is 180 degrees clockwise—inverted landscape mode. + /// + Rotate180 = 3, + + /// + /// Indicates that rotation is 270 degrees clockwise—inverted portrait mode. + /// + Rotate270 = 4 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigScaling.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigScaling.cs new file mode 100644 index 00000000..7d9c64c8 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigScaling.cs @@ -0,0 +1,49 @@ +namespace WindowsDisplayAPI.Native.DisplayConfig +{ + /// + /// Scaling modes + /// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553974(v=vs.85).aspx + /// + public enum DisplayConfigScaling : uint + { + /// + /// Scaling mode is not specified + /// + NotSpecified = 0, + + /// + /// 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. + /// + Identity = 1, + + /// + /// Indicates the centering transformation; the source content is presented unscaled, centered with respect to the + /// spatial resolution of the target mode. + /// + Centered = 2, + + /// + /// Indicates the content is scaled to fit the path's target. + /// + Stretched = 3, + + /// + /// Indicates the aspect-ratio centering transformation. + /// + AspectRatioCenteredMax = 4, + + /// + /// 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. + /// + Custom = 5, + + /// + /// Indicates that the caller does not have any preference for the scaling. + /// + Preferred = 128 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigScanLineOrdering.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigScanLineOrdering.cs new file mode 100644 index 00000000..493edd97 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigScanLineOrdering.cs @@ -0,0 +1,29 @@ +namespace WindowsDisplayAPI.Native.DisplayConfig +{ + /// + /// Possible values for display scan line ordering + /// https://msdn.microsoft.com/en-us/library/windows/hardware/ff553977(v=vs.85).aspx + /// + public enum DisplayConfigScanLineOrdering : uint + { + /// + /// Indicates that scan-line ordering of the output is unspecified. + /// + NotSpecified = 0, + + /// + /// Indicates that the output is a progressive image. + /// + Progressive = 1, + + /// + /// Indicates that the output is an interlaced image that is created beginning with the upper field. + /// + InterlacedWithUpperFieldFirst = 2, + + /// + /// Indicates that the output is an interlaced image that is created beginning with the lower field. + /// + InterlacedWithLowerFieldFirst = 3 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigSourceDPIScale.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigSourceDPIScale.cs new file mode 100644 index 00000000..c3ae741e --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigSourceDPIScale.cs @@ -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 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigTargetDeviceNameFlags.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigTargetDeviceNameFlags.cs new file mode 100644 index 00000000..2116ab58 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigTargetDeviceNameFlags.cs @@ -0,0 +1,13 @@ +using System; + +namespace WindowsDisplayAPI.Native.DisplayConfig +{ + [Flags] + internal enum DisplayConfigTargetDeviceNameFlags : uint + { + None = 0, + FriendlyNameFromEDID = 1, + FriendlyNameForced = 2, + EDIDIdsValid = 4 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigTopologyId.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigTopologyId.cs new file mode 100644 index 00000000..b877ec8b --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigTopologyId.cs @@ -0,0 +1,37 @@ +using System; + +namespace WindowsDisplayAPI.Native.DisplayConfig +{ + /// + /// Possible topology identifications + /// https://msdn.microsoft.com/en-us/library/windows/hardware/ff554001(v=vs.85).aspx + /// + [Flags] + public enum DisplayConfigTopologyId : uint + { + /// + /// Invalid topology identification + /// + None = 0, + + /// + /// Indicates that the display topology is an internal configuration. + /// + Internal = 0x00000001, + + /// + /// Indicates that the display topology is clone-view configuration. + /// + Clone = 0x00000002, + + /// + /// Indicates that the display topology is an extended configuration. + /// + Extend = 0x00000004, + + /// + /// Indicates that the display topology is an external configuration. + /// + External = 0x00000008 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigVideoOutputTechnology.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigVideoOutputTechnology.cs new file mode 100644 index 00000000..0334aa07 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/DisplayConfigVideoOutputTechnology.cs @@ -0,0 +1,96 @@ +namespace WindowsDisplayAPI.Native.DisplayConfig +{ + /// + /// Possible target's connector types + /// https://msdn.microsoft.com/en-us/library/windows/hardware/ff554003(v=vs.85).aspx + /// + public enum DisplayConfigVideoOutputTechnology : uint + { + /// + /// Indicates a connector that is not one of the types that is indicated by the following enumerators in this + /// enumeration. + /// + Other = 0xFFFFFFFF, + + /// + /// Indicates an HD15 (VGA) connector. + /// + HD15 = 0, + + /// + /// Indicates an S-video connector. + /// + SVideo = 1, + + /// + /// Indicates a composite video connector group. + /// + CompositeVideo = 2, + + /// + /// Indicates a component video connector group. + /// + ComponentVideo = 3, + + /// + /// Indicates a Digital Video Interface (DVI) connector. + /// + DVI = 4, + + /// + /// Indicates a High-Definition Multimedia Interface (HDMI) connector. + /// + HDMI = 5, + + /// + /// Indicates a Low Voltage Differential Swing (LVDS) connector. + /// + LVDS = 6, + + /// + /// Indicates a Japanese D connector. + /// + DJPN = 8, + + /// + /// Indicates an SDI connector. + /// + SDI = 9, + + /// + /// Indicates an external display port, which is a display port that connects externally to a display device. + /// + DisplayPortExternal = 10, + + /// + /// Indicates an embedded display port that connects internally to a display device. + /// + DisplayPortEmbedded = 11, + + /// + /// Indicates an external Unified Display Interface (UDI), which is a UDI that connects externally to a display device. + /// + UDIExternal = 12, + + /// + /// Indicates an embedded UDI that connects internally to a display device. + /// + UDIEmbedded = 13, + + /// + /// Indicates a dongle cable that supports standard definition television (SDTV). + /// + SDTVDongle = 14, + + /// + /// Indicates that the VidPN target is a Miracast wireless display device. + /// + Miracast = 15, + + /// + /// Indicates that the video output device connects internally to a display device (for example, the internal + /// connection in a laptop computer). + /// + Internal = 0x80000000 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/QueryDeviceConfigFlags.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/QueryDeviceConfigFlags.cs new file mode 100644 index 00000000..b3a3b8f1 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/QueryDeviceConfigFlags.cs @@ -0,0 +1,32 @@ +using System; + +namespace WindowsDisplayAPI.Native.DisplayConfig +{ + /// + /// Possible values for QueryDisplayConfig() flags property + /// https://msdn.microsoft.com/en-us/library/windows/hardware/ff569215(v=vs.85).aspx + /// + [Flags] + public enum QueryDeviceConfigFlags : uint + { + /// + /// All the possible path combinations of sources to targets. + /// + AllPaths = 0x00000001, + + /// + /// Currently active paths only. + /// + OnlyActivePaths = 0x00000002, + + /// + /// Active path as defined in the CCD database for the currently connected displays. + /// + DatabaseCurrent = 0x00000004, + + /// + /// Virtual Mode Aware + /// + VirtualModeAware = 0x0000010 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/SetDisplayConfigFlags.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/SetDisplayConfigFlags.cs new file mode 100644 index 00000000..7ff76a5a --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/SetDisplayConfigFlags.cs @@ -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 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfig2DRegion.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfig2DRegion.cs new file mode 100644 index 00000000..1e4c4b4f --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfig2DRegion.cs @@ -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 + { + [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); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigAdapterName.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigAdapterName.cs new file mode 100644 index 00000000..4306c882 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigAdapterName.cs @@ -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()); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigDesktopImageInfo.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigDesktopImageInfo.cs new file mode 100644 index 00000000..c3b76a8c --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigDesktopImageInfo.cs @@ -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 + { + 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); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigDeviceInfoHeader.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigDeviceInfoHeader.cs new file mode 100644 index 00000000..c348b6d9 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigDeviceInfoHeader.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigGetSourceDPIScale.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigGetSourceDPIScale.cs new file mode 100644 index 00000000..35ca6274 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigGetSourceDPIScale.cs @@ -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()); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigModeInfo.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigModeInfo.cs new file mode 100644 index 00000000..016fc241 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigModeInfo.cs @@ -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 + { + 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); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigPathInfo.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigPathInfo.cs new file mode 100644 index 00000000..feb06eda --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigPathInfo.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigPathSourceInfo.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigPathSourceInfo.cs new file mode 100644 index 00000000..4126f803 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigPathSourceInfo.cs @@ -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)); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigPathTargetInfo.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigPathTargetInfo.cs new file mode 100644 index 00000000..aa28cbd3 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigPathTargetInfo.cs @@ -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)); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigRational.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigRational.cs new file mode 100644 index 00000000..c2b5b97f --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigRational.cs @@ -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 + { + [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); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSetSourceDPIScale.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSetSourceDPIScale.cs new file mode 100644 index 00000000..631e78d4 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSetSourceDPIScale.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSetTargetPersistence.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSetTargetPersistence.cs new file mode 100644 index 00000000..59706eb9 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSetTargetPersistence.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSourceDeviceName.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSourceDeviceName.cs new file mode 100644 index 00000000..87244bca --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSourceDeviceName.cs @@ -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()); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSourceMode.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSourceMode.cs new file mode 100644 index 00000000..9a70ca3d --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSourceMode.cs @@ -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 + { + 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); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSupportVirtualResolution.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSupportVirtualResolution.cs new file mode 100644 index 00000000..9f4d0747 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigSupportVirtualResolution.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetBaseType.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetBaseType.cs new file mode 100644 index 00000000..7e9c8a94 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetBaseType.cs @@ -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()); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetDeviceName.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetDeviceName.cs new file mode 100644 index 00000000..0514c78f --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetDeviceName.cs @@ -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()); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetMode.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetMode.cs new file mode 100644 index 00000000..b7ed79d9 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetMode.cs @@ -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 + { + 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); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetPreferredMode.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetPreferredMode.cs new file mode 100644 index 00000000..639ebc1a --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigTargetPreferredMode.cs @@ -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()); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigVideoSignalInfo.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigVideoSignalInfo.cs new file mode 100644 index 00000000..a07980db --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/Structures/DisplayConfigVideoSignalInfo.cs @@ -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 + { + [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); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfig/VideoSignalStandard.cs b/app/WindowsDisplayAPI/Native/DisplayConfig/VideoSignalStandard.cs new file mode 100644 index 00000000..fb8d3810 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfig/VideoSignalStandard.cs @@ -0,0 +1,203 @@ +namespace WindowsDisplayAPI.Native.DisplayConfig +{ + /// + /// Possible video signal standards + /// https://msdn.microsoft.com/en-us/library/windows/hardware/ff546632(v=vs.85).aspx + /// + public enum VideoSignalStandard : ushort + { + /// + /// Indicates that the variable has not yet been assigned a meaningful value. + /// + Uninitialized = 0, + + /// + /// Represents the Video Electronics Standards Association (VESA) Display Monitor Timing (DMT) standard. + /// + // ReSharper disable once InconsistentNaming + VESA_DMT = 1, + + /// + /// Represents the VESA Generalized Timing Formula (GTF) standard. + /// + // ReSharper disable once InconsistentNaming + VESA_GTF = 2, + + /// + /// Represents the VESA Coordinated Video Timing (CVT) standard. + /// + // ReSharper disable once InconsistentNaming + VESA_CVT = 3, + + /// + /// Represents the IBM standard. + /// + IBM = 4, + + /// + /// Represents the Apple standard. + /// + Apple = 5, + + /// + /// Represents the National Television Standards Committee (NTSC) standard. + /// + // ReSharper disable once InconsistentNaming + NTSC_M = 6, + + /// + /// Represents the NTSC japanese standard. + /// + // ReSharper disable once InconsistentNaming + NTSC_J = 7, + + /// + /// Represents the NTSC standard. + /// + // ReSharper disable once InconsistentNaming + NTSC_443 = 8, + + /// + /// Represents the Phase Alteration Line (PAL) standard. + /// + // ReSharper disable once InconsistentNaming + PAL_B = 9, + + /// + /// Represents the PAL standard. + /// + // ReSharper disable once InconsistentNaming + PAL_B1 = 10, + + /// + /// Represents the PAL standard. + /// + // ReSharper disable once InconsistentNaming + PAL_G = 11, + + /// + /// Represents the PAL standard. + /// + // ReSharper disable once InconsistentNaming + PAL_H = 12, + + /// + /// Represents the PAL standard. + /// + // ReSharper disable once InconsistentNaming + PAL_I = 13, + + /// + /// Represents the PAL standard. + /// + // ReSharper disable once InconsistentNaming + PAL_D = 14, + + /// + /// Represents the PAL standard. + /// + // ReSharper disable once InconsistentNaming + PAL_N = 15, + + /// + /// Represents the PAL standard. + /// + // ReSharper disable once InconsistentNaming + PAL_NC = 16, + + /// + /// Represents the Systeme Electronic Pour Couleur Avec Memoire (SECAM) standard. + /// + // ReSharper disable once InconsistentNaming + SECAM_B = 17, + + /// + /// Represents the SECAM standard. + /// + // ReSharper disable once InconsistentNaming + SECAM_D = 18, + + /// + /// Represents the SECAM standard. + /// + // ReSharper disable once InconsistentNaming + SECAM_G = 19, + + /// + /// Represents the SECAM standard. + /// + // ReSharper disable once InconsistentNaming + SECAM_H = 20, + + /// + /// Represents the SECAM standard. + /// + // ReSharper disable once InconsistentNaming + SECAM_K = 21, + + /// + /// Represents the SECAM standard. + /// + // ReSharper disable once InconsistentNaming + SECAM_K1 = 22, + + /// + /// Represents the SECAM standard. + /// + // ReSharper disable once InconsistentNaming + SECAM_L = 23, + + /// + /// Represents the SECAM standard. + /// + // ReSharper disable once InconsistentNaming + SECAM_L1 = 24, + + /// + /// Represents the Electronics Industries Association (EIA) standard. + /// + // ReSharper disable once InconsistentNaming + EIA_861 = 25, + + /// + /// Represents the EIA standard. + /// + // ReSharper disable once InconsistentNaming + EIA_861A = 26, + + /// + /// Represents the EIA standard. + /// + // ReSharper disable once InconsistentNaming + EIA_861B = 27, + + /// + /// Represents the PAL standard. + /// + // ReSharper disable once InconsistentNaming + PAL_K = 28, + + /// + /// Represents the PAL standard. + /// + // ReSharper disable once InconsistentNaming + PAL_K1 = 29, + + /// + /// Represents the PAL standard. + /// + // ReSharper disable once InconsistentNaming + PAL_L = 30, + + /// + /// Represents the PAL standard. + /// + // ReSharper disable once InconsistentNaming + PAL_M = 31, + + /// + /// Represents any video standard other than those represented by the previous constants in this enumeration. + /// + Other = 255 + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/DisplayConfigApi.cs b/app/WindowsDisplayAPI/Native/DisplayConfigApi.cs new file mode 100644 index 00000000..cd017676 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/DisplayConfigApi.cs @@ -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 + ); + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/Structures/LUID.cs b/app/WindowsDisplayAPI/Native/Structures/LUID.cs new file mode 100644 index 00000000..4b17cb5f --- /dev/null +++ b/app/WindowsDisplayAPI/Native/Structures/LUID.cs @@ -0,0 +1,104 @@ +using System; +using System.Runtime.InteropServices; + +namespace WindowsDisplayAPI.Native.Structures +{ + /// + /// Locally unique identifier is a 64-bit value guaranteed to be unique only on the system on which it was generated. + /// + [StructLayout(LayoutKind.Sequential)] + public struct LUID : IEquatable + { + /// + /// 32Bit unsigned integer, low + /// + public readonly uint LowPart; + + /// + /// 32Bit signed integer, high + /// + public readonly int HighPart; + + /// + /// Creates a new LUID + /// + /// 32Bit unsigned integer, low + /// 32Bit signed integer, high + public LUID(uint lowPart, int highPart) + { + LowPart = lowPart; + HighPart = highPart; + } + + /// + public override string ToString() + { + return $"{{ {LowPart:X} - {HighPart:X} }}"; + } + + /// + public bool Equals(LUID other) + { + return LowPart == other.LowPart && HighPart == other.HighPart; + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + return obj is LUID luid && Equals(luid); + } + + /// + /// Checks for equality between two objects of same type + /// + /// The first object + /// The second object + /// true, if both objects are equal, otherwise false + public static bool operator ==(LUID left, LUID right) + { + return Equals(left, right) || left.Equals(right); + } + + /// + /// Checks for inequality between two objects of same type + /// + /// The first object + /// The second object + /// true, if both objects are not equal, otherwise false + public static bool operator !=(LUID left, LUID right) + { + return !(left == right); + } + + /// + public override int GetHashCode() + { + unchecked + { + return ((int) LowPart * 397) ^ HighPart; + } + } + + /// + /// Checks if this type is empty and holds no real data + /// + /// true if empty, otherwise false + public bool IsEmpty() + { + return LowPart == 0 && HighPart == 0; + } + + /// + /// Returns an empty instance of this type + /// + public static LUID Empty + { + get => default; + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/Structures/PointL.cs b/app/WindowsDisplayAPI/Native/Structures/PointL.cs new file mode 100644 index 00000000..b9c96083 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/Structures/PointL.cs @@ -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 + { + [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); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/Structures/RectangleL.cs b/app/WindowsDisplayAPI/Native/Structures/RectangleL.cs new file mode 100644 index 00000000..393c2847 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/Structures/RectangleL.cs @@ -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 + { + [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); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/Native/Win32Status.cs b/app/WindowsDisplayAPI/Native/Win32Status.cs new file mode 100644 index 00000000..cdcea807 --- /dev/null +++ b/app/WindowsDisplayAPI/Native/Win32Status.cs @@ -0,0 +1,8 @@ +namespace WindowsDisplayAPI.Native +{ + internal enum Win32Status + { + Success = 0x0, + ErrorInsufficientBuffer = 0x7A + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/UnAttachedDisplay.cs b/app/WindowsDisplayAPI/UnAttachedDisplay.cs new file mode 100644 index 00000000..ad6d52f9 --- /dev/null +++ b/app/WindowsDisplayAPI/UnAttachedDisplay.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Linq; + +namespace WindowsDisplayAPI +{ + /// + /// Represents a Windows UnAttached Display Device + /// + public class UnAttachedDisplay : DisplayDevice + { + /// + /// Creates a new UnAttachedDisplay + /// + /// The DisplayDevice instance to copy information from + protected UnAttachedDisplay(DisplayDevice device) + : base( + device.DevicePath, + device.DeviceName, + device.DeviceKey, + device.Adapter, + device.ScreenName, + device.DisplayName, + device.IsAvailable, + false + ) + { + } + + /// + public override bool IsAvailable + { + get => base.IsAvailable || !IsValid; + } + + /// + public override bool IsValid + { + get + { + return DisplayAdapter.GetDisplayAdapters() + .SelectMany(adapter => adapter.GetDisplayDevices(base.IsAvailable)) + .Any( + device => device.DevicePath.Equals(DevicePath) && device.DeviceKey.Equals(DeviceKey) + ); + } + } + + /// + /// Returns a list of all unattached displays on this machine + /// + /// An enumerable list of UnAttachedDisplay + public static IEnumerable GetUnAttachedDisplays() + { + return DisplayAdapter.GetDisplayAdapters() + .SelectMany(adapter => adapter.GetDisplayDevices(false)) + .Select(device => new UnAttachedDisplay(device)); + } + + /// + public override string ToString() + { + return IsValid ? $"{GetType().Name}: {DisplayName} ({DeviceName})" : $"{GetType().Name}: Invalid"; + } + + /// + /// Returns the corresponding Display device for this unattached display. Only functions when this instance is invalidated + /// due to display attachment. + /// + /// + public Display ToDisplay() + { + return IsValid + ? null + : Display.GetDisplays() + .FirstOrDefault( + display => display.DevicePath.Equals(DevicePath) && display.DeviceKey.Equals(DeviceKey) + ); + } + } +} \ No newline at end of file diff --git a/app/WindowsDisplayAPI/WindowsDisplayAPI.csproj b/app/WindowsDisplayAPI/WindowsDisplayAPI.csproj new file mode 100644 index 00000000..d6811e87 --- /dev/null +++ b/app/WindowsDisplayAPI/WindowsDisplayAPI.csproj @@ -0,0 +1,52 @@ + + + + netstandard2.0;net45 + 1.3.0.13 + falahati.net + WindowsDisplayAPI is a .Net wrapper for Windows Display and Windows CCD APIs + Soroush Falahati + Copyright © Soroush Falahati 2020 (falahati.net) + AnyCPU + WindowsDisplayAPI + https://github.com/falahati/WindowsDisplayAPI + https://github.com/falahati/WindowsDisplayAPI/blob/master/LICENSE + https://github.com/falahati/WindowsDisplayAPI/blob/master/WindowsDisplayAPI/Icon.png?raw=true + true + true + AnyCPU + Windows Display API Wrapper (CCD) + WindowsDisplayAPI + + + dev + 4 + ..\Debug + + + True + dev + true + ..\Release + ..\Release\WindowsDisplayAPI.xml + + + + all + runtime; build; native; contentfiles; analyzers + + + + + true + \ + + + true + \ + + + + + + \ No newline at end of file diff --git a/app/WindowsDisplayAPI/readme.txt b/app/WindowsDisplayAPI/readme.txt new file mode 100644 index 00000000..4a9109c7 --- /dev/null +++ b/app/WindowsDisplayAPI/readme.txt @@ -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) \ No newline at end of file