diff --git a/DisplayMagicianShared/DisplayMagicianShared.csproj b/DisplayMagicianShared/DisplayMagicianShared.csproj
index e9902b1..bd68597 100644
--- a/DisplayMagicianShared/DisplayMagicianShared.csproj
+++ b/DisplayMagicianShared/DisplayMagicianShared.csproj
@@ -85,6 +85,7 @@
DisplayView.cs
+
diff --git a/DisplayMagicianShared/Windows/CCD.cs b/DisplayMagicianShared/Windows/CCD.cs
index 58b301e..a311708 100644
--- a/DisplayMagicianShared/Windows/CCD.cs
+++ b/DisplayMagicianShared/Windows/CCD.cs
@@ -393,7 +393,7 @@ namespace DisplayMagicianShared.Windows
public override bool Equals(object obj) => obj is DISPLAYCONFIG_RATIONAL other && this.Equals(other);
public bool Equals(DISPLAYCONFIG_RATIONAL other)
=> Numerator == other.Numerator &&
- Denominator == other.Denominator;
+ Denominator == other.Denominator;
public override int GetHashCode()
{
diff --git a/DisplayMagicianShared/Windows/GDI.cs b/DisplayMagicianShared/Windows/GDI.cs
new file mode 100644
index 0000000..9007019
--- /dev/null
+++ b/DisplayMagicianShared/Windows/GDI.cs
@@ -0,0 +1,675 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DisplayMagicianShared.Windows
+{
+ // 90% of this file is cribbed from WindowsDisplayAPI by Soroush Falahati
+ // The other 10% is from MikedouglasDev's ChangeScreenResolution
+ // https://github.com/mikedouglasdev/changescreenresolution/blob/master/ChangeScreenResolutionSolution/ChangeScreenResolution/SafeNativeMethods.cs
+ // and GemingLeader here: https://www.c-sharpcorner.com/uploadfile/GemingLeader/changing-display-settings-programmatically/
+
+
+ public enum CHANGE_DISPLAY_RESULTS
+ {
+ ///
+ /// Completed successfully
+ ///
+ Successful = 0,
+
+ ///
+ /// Changes needs restart
+ ///
+ Restart = 1,
+
+ ///
+ /// Failed to change and save setings
+ ///
+ Failed = -1,
+
+ ///
+ /// Invalid data provided
+ ///
+ 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
+ }
+
+ [Flags]
+ public enum CHANGE_DISPLAY_SETTINGS_FLAGS : UInt32
+ {
+ CDS_NONE = 0,
+ CDS_UPDATEREGISTRY = 0x00000001,
+ CDS_TEST = 0x00000002,
+ CDS_FULLSCREEN = 0x00000004,
+ CDS_GLOBAL = 0x00000008,
+ CDS_SET_PRIMARY = 0x00000010,
+ CDS_VIDEOPARAMETERS = 0x00000020,
+ CDS_ENABLE_UNSAFE_MODES = 0x00000100,
+ CDS_DISABLE_UNSAFE_MODES = 0x00000200,
+ CDS_RESET = 0x40000000,
+ CDS_RESET_EX = 0x20000000,
+ CDS_NORESET = 0x10000000
+ }
+
+ public enum DEVICE_CAPABILITY : Int32
+ {
+ 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,
+ }
+
+ [Flags]
+ public enum DEVICE_MODE_FIELDS : UInt32
+ {
+ 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,
+ }
+
+ [Flags]
+ public enum DISPLAY_DEVICE_STATE_FLAGS : UInt32
+ {
+ ///
+ /// 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
+ }
+
+ public enum DISPLAY_FIXED_OUTPUT : UInt32
+ {
+ ///
+ /// 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
+ }
+
+ [Flags]
+ public enum DISPLAY_FLAGS : UInt32
+ {
+ None = 0,
+ Grayscale = 1,
+ Interlaced = 2
+ }
+
+ public enum DISPLAY_ORIENTATION : UInt32
+ {
+ ///
+ /// No rotation
+ ///
+ Rotate0Degree = 0,
+
+ ///
+ /// 90 degree rotation
+ ///
+ Rotate90Degree = 1,
+
+ ///
+ /// 180 degree rotation
+ ///
+ Rotate180Degree = 2,
+
+ ///
+ /// 270 degree rotation
+ ///
+ Rotate270Degree = 3
+ }
+
+ public enum DISPLAY_SETTINGS_MODE : Int32
+ {
+ CurrentSettings = -1, // Retrieves current display mode
+ RegistrySettings = -2 // Retrieves current display mode stored within the registry.
+ }
+
+ public enum DISPLAY_TECHNOLOGY : Int32
+ {
+ Plotter = 0,
+ RasterDisplay = 1,
+ RasterPrinter = 2,
+ RasterCamera = 3,
+ CharacterStream = 4,
+ MetaFile = 5,
+ DisplayFile = 6,
+ }
+
+ public enum MONITOR_FROM_FLAG : UInt32
+ {
+ DefaultToNull = 0,
+ DefaultToPrimary = 1,
+ DefaultToNearest = 2,
+ }
+
+ [Flags]
+ public enum MONITOR_INFO_FLAGS : UInt32
+ {
+ None = 0,
+ Primary = 1
+ }
+
+
+
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd183565(v=vs.85).aspx
+ // https://www.c-sharpcorner.com/uploadfile/GemingLeader/changing-display-settings-programmatically/
+ [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
+ public struct DEVICE_MODE : IEquatable
+ {
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+ [FieldOffset(0)]
+ public string DeviceName;
+
+ [MarshalAs(UnmanagedType.U2)]
+ [FieldOffset(32)]
+ public UInt16 SpecificationVersion;
+
+ [MarshalAs(UnmanagedType.U2)]
+ [FieldOffset(34)]
+ public UInt16 DriverVersion;
+
+ [MarshalAs(UnmanagedType.U2)]
+ [FieldOffset(36)]
+ public UInt16 Size;
+
+ [MarshalAs(UnmanagedType.U2)]
+ [FieldOffset(38)]
+ public UInt16 DriverExtra;
+
+ [MarshalAs(UnmanagedType.U4)]
+ [FieldOffset(40)]
+ public DEVICE_MODE_FIELDS Fields;
+
+ [MarshalAs(UnmanagedType.Struct)]
+ [FieldOffset(44)]
+ public POINTL Position;
+
+ [MarshalAs(UnmanagedType.U4)]
+ [FieldOffset(52)]
+ public DISPLAY_ORIENTATION DisplayOrientation;
+
+ [MarshalAs(UnmanagedType.U4)]
+ [FieldOffset(56)]
+ public DISPLAY_FIXED_OUTPUT DisplayFixedOutput;
+
+ [MarshalAs(UnmanagedType.I2)]
+ [FieldOffset(60)]
+ public Int16 Color;
+
+ [MarshalAs(UnmanagedType.I2)]
+ [FieldOffset(62)]
+ public Int16 Duplex;
+
+ [MarshalAs(UnmanagedType.I2)]
+ [FieldOffset(64)]
+ public Int16 YResolution;
+
+ [MarshalAs(UnmanagedType.I2)]
+ [FieldOffset(66)]
+ public Int16 TrueTypeOption;
+
+ [MarshalAs(UnmanagedType.I2)]
+ [FieldOffset(68)]
+ public Int16 Collate;
+
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+ [FieldOffset(72)]
+ public string FormName;
+
+ [MarshalAs(UnmanagedType.U2)]
+ [FieldOffset(102)]
+ public UInt16 LogicalInchPixels;
+
+ [MarshalAs(UnmanagedType.U4)]
+ [FieldOffset(104)]
+ public UInt32 BitsPerPixel;
+
+ [MarshalAs(UnmanagedType.U4)]
+ [FieldOffset(108)]
+ public UInt32 PixelsWidth;
+
+ [MarshalAs(UnmanagedType.U4)]
+ [FieldOffset(112)]
+ public UInt32 PixelsHeight;
+
+ [MarshalAs(UnmanagedType.U4)]
+ [FieldOffset(116)]
+ public DISPLAY_FLAGS DisplayFlags;
+
+ [MarshalAs(UnmanagedType.U4)]
+ [FieldOffset(120)]
+ public UInt32 DisplayFrequency;
+
+ public override bool Equals(object obj) => obj is DEVICE_MODE other && this.Equals(other);
+
+ public bool Equals(DEVICE_MODE other)
+ => DeviceName.Equals(other.DeviceName) &&
+ SpecificationVersion == other.SpecificationVersion &&
+ DriverVersion.Equals(other.DriverVersion) &&
+ Size.Equals(other.Size) &&
+ DriverExtra.Equals(other.DriverExtra) &&
+ Fields.Equals(other.Fields) &&
+ Position.Equals(other.Position) &&
+ DisplayOrientation.Equals(other.DisplayOrientation) &&
+ DisplayFixedOutput.Equals(other.DisplayFixedOutput) &&
+ Color.Equals(other.Color) &&
+ Duplex.Equals(other.Duplex) &&
+ YResolution.Equals(other.YResolution) &&
+ TrueTypeOption.Equals(other.TrueTypeOption) &&
+ Collate.Equals(other.Collate) &&
+ FormName.Equals(other.FormName) &&
+ LogicalInchPixels.Equals(other.LogicalInchPixels) &&
+ BitsPerPixel.Equals(other.BitsPerPixel) &&
+ PixelsWidth.Equals(other.PixelsWidth) &&
+ PixelsHeight.Equals(other.PixelsHeight) &&
+ DisplayFlags.Equals(other.DisplayFlags) &&
+ DisplayFrequency == other.DisplayFrequency;
+
+ public override int GetHashCode()
+ {
+ return (DeviceName, SpecificationVersion, DriverVersion, Size, DriverExtra, Fields, Position, DisplayOrientation, DisplayFixedOutput, Color, Duplex,
+ YResolution, TrueTypeOption, Collate, FormName, LogicalInchPixels, BitsPerPixel, PixelsWidth, PixelsHeight, DisplayFlags, DisplayFrequency).GetHashCode();
+ }
+
+ public static bool operator ==(DEVICE_MODE lhs, DEVICE_MODE rhs) => lhs.Equals(rhs);
+
+ public static bool operator !=(DEVICE_MODE lhs, DEVICE_MODE rhs) => !(lhs == rhs);
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public struct DISPLAY_DEVICE : IEquatable
+ {
+ [MarshalAs(UnmanagedType.U4)]
+ public UInt32 Size;
+
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+ public string DeviceName;
+
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
+ public string DeviceString;
+
+ [MarshalAs(UnmanagedType.U4)]
+ public DISPLAY_DEVICE_STATE_FLAGS StateFlags;
+
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
+ public string DeviceId;
+
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
+ public string DeviceKey;
+
+ public override bool Equals(object obj) => obj is DISPLAY_DEVICE other && this.Equals(other);
+
+ public bool Equals(DISPLAY_DEVICE other)
+ => Size == other.Size &&
+ DeviceName == other.DeviceName &&
+ DeviceString == other.DeviceString &&
+ StateFlags == other.StateFlags &&
+ DeviceId == other.DeviceId &&
+ DeviceKey == other.DeviceKey;
+
+ public override int GetHashCode()
+ {
+ return (Size, DeviceName, DeviceString, StateFlags, DeviceId, DeviceKey).GetHashCode();
+ }
+
+ public static bool operator ==(DISPLAY_DEVICE lhs, DISPLAY_DEVICE rhs) => lhs.Equals(rhs);
+
+ public static bool operator !=(DISPLAY_DEVICE lhs, DISPLAY_DEVICE rhs) => !(lhs == rhs);
+
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct GAMMA_RAMP : IEquatable
+ {
+ public const int DataPoints = 256;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = DataPoints)]
+ public UInt16[] Red;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = DataPoints)]
+ public UInt16[] Green;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = DataPoints)]
+ public UInt16[] Blue;
+
+ public override bool Equals(object obj) => obj is GAMMA_RAMP other && this.Equals(other);
+
+ public bool Equals(GAMMA_RAMP other)
+ => Red.SequenceEqual(other.Red) &&
+ Green.SequenceEqual(other.Green) &&
+ Blue.SequenceEqual(other.Blue);
+
+ public override int GetHashCode()
+ {
+ return (Red, Green, Blue).GetHashCode();
+ }
+
+ public static bool operator ==(GAMMA_RAMP lhs, GAMMA_RAMP rhs) => lhs.Equals(rhs);
+
+ public static bool operator !=(GAMMA_RAMP lhs, GAMMA_RAMP rhs) => !(lhs == rhs);
+
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct MONITOR_INFO : IEquatable
+ {
+ internal UInt32 Size;
+ public RECTL Bounds;
+ public RECTL WorkingArea;
+ public MONITOR_INFO_FLAGS Flags;
+
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+ public string DisplayName;
+
+ public override bool Equals(object obj) => obj is MONITOR_INFO other && this.Equals(other);
+
+ public bool Equals(MONITOR_INFO other)
+ => Size == other.Size &&
+ Bounds.Equals(other.Bounds) &&
+ WorkingArea.Equals(other.WorkingArea) &&
+ Flags == other.Flags &&
+ DisplayName == other.DisplayName;
+
+ public override int GetHashCode()
+ {
+ return (Size, Bounds, WorkingArea, Flags, DisplayName).GetHashCode();
+ }
+
+ public static bool operator ==(MONITOR_INFO lhs, MONITOR_INFO rhs) => lhs.Equals(rhs);
+
+ public static bool operator !=(MONITOR_INFO lhs, MONITOR_INFO rhs) => !(lhs == rhs);
+ }
+
+ public struct GDI_DISPLAY_SETTING : IEquatable
+ {
+ public bool IsEnabled;
+ public bool IsPrimary;
+ public DISPLAY_DEVICE Device;
+ public DEVICE_MODE DeviceMode;
+
+ public override bool Equals(object obj) => obj is GDI_DISPLAY_SETTING other && this.Equals(other);
+
+ public bool Equals(GDI_DISPLAY_SETTING other)
+ => IsEnabled == other.IsEnabled &&
+ IsPrimary == other.IsPrimary &&
+ Device.Equals(other.Device) &&
+ DeviceMode.Equals(other.DeviceMode);
+
+ public override int GetHashCode()
+ {
+ return (IsEnabled, IsPrimary, Device, DeviceMode).GetHashCode();
+ }
+
+ public static bool operator ==(GDI_DISPLAY_SETTING lhs, GDI_DISPLAY_SETTING rhs) => lhs.Equals(rhs);
+
+ public static bool operator !=(GDI_DISPLAY_SETTING lhs, GDI_DISPLAY_SETTING rhs) => !(lhs == rhs);
+ }
+
+
+ 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(
+ GDIImport.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(
+ GDIImport.GetDC(windowHandle),
+ true
+ );
+ }
+
+ public static DCHandle CreateGlobal()
+ {
+ return new DCHandle(
+ GDIImport.CreateDC("DISPLAY", null, null, IntPtr.Zero),
+ true
+ );
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ return _created
+ ? GDIImport.DeleteDC(this)
+ : GDIImport.ReleaseDC(IntPtr.Zero, this);
+ }
+ }
+
+ class GDIImport
+ {
+ [DllImport("user32", CharSet = CharSet.Ansi)]
+ public static extern CHANGE_DISPLAY_RESULTS ChangeDisplaySettingsEx(
+ string deviceName,
+ ref DEVICE_MODE devMode,
+ IntPtr handler,
+ CHANGE_DISPLAY_SETTINGS_FLAGS flags,
+ IntPtr param
+ );
+
+ [DllImport("user32", CharSet = CharSet.Ansi)]
+ public static extern CHANGE_DISPLAY_RESULTS ChangeDisplaySettingsEx(
+ string deviceName,
+ IntPtr devModePointer,
+ IntPtr handler,
+ CHANGE_DISPLAY_SETTINGS_FLAGS flags,
+ IntPtr param
+ );
+
+ [DllImport("user32", CharSet = CharSet.Ansi)]
+ public static extern bool EnumDisplaySettings(
+ string deviceName,
+ DISPLAY_SETTINGS_MODE mode,
+ ref DEVICE_MODE 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(DCHandle dcHandle);
+
+
+ [DllImport("user32", CharSet = CharSet.Unicode)]
+ internal static extern bool EnumDisplayDevices(
+ string deviceName,
+ UInt32 deviceNumber,
+ ref DISPLAY_DEVICE displayDevice,
+ UInt32 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, DEVICE_CAPABILITY index);
+
+ [DllImport("gdi32")]
+ internal static extern bool GetDeviceGammaRamp(DCHandle dcHandle, ref GAMMA_RAMP ramp);
+
+ [DllImport("user32")]
+ internal static extern bool GetMonitorInfo(
+ IntPtr monitorHandle,
+ ref MONITOR_INFO monitorInfo
+ );
+
+ [DllImport("user32")]
+ internal static extern IntPtr MonitorFromPoint(
+ [In] POINTL point,
+ MONITOR_FROM_FLAG flag
+ );
+
+ [DllImport("user32")]
+ internal static extern IntPtr MonitorFromRect(
+ [In] RECTL rectangle,
+ MONITOR_FROM_FLAG flag
+ );
+
+ [DllImport("user32")]
+ internal static extern IntPtr MonitorFromWindow(
+ [In] IntPtr windowHandle,
+ MONITOR_FROM_FLAG flag
+ );
+
+ [DllImport("user32")]
+ internal static extern bool ReleaseDC([In] IntPtr windowHandle, [In] DCHandle dcHandle);
+
+ [DllImport("gdi32")]
+ internal static extern bool SetDeviceGammaRamp(DCHandle dcHandle, ref GAMMA_RAMP ramp);
+
+ [UnmanagedFunctionPointer(CallingConvention.StdCall)]
+ internal delegate int MonitorEnumProcedure(
+ IntPtr monitorHandle,
+ IntPtr dcHandle,
+ ref RECTL rect,
+ IntPtr callbackObject
+ );
+ }
+}
\ No newline at end of file
diff --git a/DisplayMagicianShared/Windows/WinLibrary.cs b/DisplayMagicianShared/Windows/WinLibrary.cs
index 685e9d7..c97b337 100644
--- a/DisplayMagicianShared/Windows/WinLibrary.cs
+++ b/DisplayMagicianShared/Windows/WinLibrary.cs
@@ -7,6 +7,7 @@ using Microsoft.Win32.SafeHandles;
using DisplayMagicianShared;
using System.ComponentModel;
using System.Text.RegularExpressions;
+using System.Collections.Generic;
namespace DisplayMagicianShared.Windows
{
@@ -41,6 +42,7 @@ namespace DisplayMagicianShared.Windows
public DISPLAYCONFIG_PATH_INFO[] DisplayConfigPaths;
public DISPLAYCONFIG_MODE_INFO[] DisplayConfigModes;
public ADVANCED_HDR_INFO_PER_PATH[] DisplayHDRStates;
+ public Dictionary GdiDisplaySettings;
// Note: We purposely have left out the DisplaySources from the Equals as it's order keeps changing after each reboot and after each profile swap
// and it is informational only and doesn't contribute to the configuration (it's used for generating the Screens structure, and therefore for
// generating the profile icon.
@@ -52,11 +54,12 @@ namespace DisplayMagicianShared.Windows
=> DisplayConfigPaths.SequenceEqual(other.DisplayConfigPaths) &&
DisplayConfigModes.SequenceEqual(other.DisplayConfigModes) &&
DisplayHDRStates.SequenceEqual(other.DisplayHDRStates) &&
+ GdiDisplaySettings.SequenceEqual(other.GdiDisplaySettings) &&
DisplayIdentifiers.SequenceEqual(other.DisplayIdentifiers);
public override int GetHashCode()
{
- return (DisplayConfigPaths, DisplayConfigModes, DisplayHDRStates).GetHashCode();
+ return (DisplayConfigPaths, DisplayConfigModes, DisplayHDRStates, GdiDisplaySettings, DisplayIdentifiers).GetHashCode();
}
public static bool operator ==(WINDOWS_DISPLAY_CONFIG lhs, WINDOWS_DISPLAY_CONFIG rhs) => lhs.Equals(rhs);
@@ -128,6 +131,24 @@ namespace DisplayMagicianShared.Windows
return _instance;
}
+ public WINDOWS_DISPLAY_CONFIG CreateDefaultConfig()
+ {
+ WINDOWS_DISPLAY_CONFIG myDefaultConfig = new WINDOWS_DISPLAY_CONFIG();
+
+ // Fill in the minimal amount we need to avoid null references
+ // so that we won't break json.net when we save a default config
+
+ myDefaultConfig.DisplayAdapters = new Dictionary();
+ myDefaultConfig.DisplayConfigModes = new DISPLAYCONFIG_MODE_INFO[0];
+ myDefaultConfig.DisplayConfigPaths = new DISPLAYCONFIG_PATH_INFO[0];
+ myDefaultConfig.DisplayHDRStates = new ADVANCED_HDR_INFO_PER_PATH[0];
+ myDefaultConfig.DisplayIdentifiers = new List();
+ myDefaultConfig.DisplaySources = new Dictionary();
+ myDefaultConfig.GdiDisplaySettings = new Dictionary();
+
+ return myDefaultConfig;
+ }
+
private void PatchAdapterIDs(ref WINDOWS_DISPLAY_CONFIG savedDisplayConfig, Dictionary currentAdapterMap)
{
@@ -389,10 +410,55 @@ namespace DisplayMagicianShared.Windows
hdrInfoCount++;
}
+ // Get the list of all display adapters in this machine through GDI
+ Dictionary gdiDeviceSettings = new Dictionary();
+ UInt32 displayDeviceNum = 0;
+ DISPLAY_DEVICE displayDevice = new DISPLAY_DEVICE();
+ displayDevice.Size = (UInt32)Marshal.SizeOf();
+ while (GDIImport.EnumDisplayDevices(null, displayDeviceNum, ref displayDevice, 0))
+ {
+ // Now we try and grab the GDI Device Settings for each display device
+ SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Getting the current Display Settings for {displayDevice.DeviceName}");
+ if (displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.AttachedToDesktop) || displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.MultiDriver))
+ {
+ // If the display device is attached to the Desktop, or a type of display that is represented by a psudeo mirroring application, then skip this display
+ // e.g. some sort of software interfaced display that doesn't have a physical plug, or maybe uses USB for communication
+ SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Getting the current Display Settings for {displayDevice.DeviceName}");
+ DEVICE_MODE currentMode = new DEVICE_MODE();
+ currentMode.Size = (UInt16)Marshal.SizeOf();
+ bool gdiWorked = GDIImport.EnumDisplaySettings(displayDevice.DeviceName, DISPLAY_SETTINGS_MODE.CurrentSettings, ref currentMode);
+ if (gdiWorked)
+ {
+ SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Got the current Display Settings from display {displayDevice.DeviceName}.");
+ GDI_DISPLAY_SETTING myDisplaySetting = new GDI_DISPLAY_SETTING();
+ myDisplaySetting.IsEnabled = true; // Always true if we get here
+ myDisplaySetting.Device = displayDevice;
+ myDisplaySetting.DeviceMode = currentMode;
+ if (displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.PrimaryDevice))
+ {
+ // This is a primary device, so we'll set that too.
+ myDisplaySetting.IsPrimary = true;
+ }
+ gdiDeviceSettings[displayDevice.DeviceName] = myDisplaySetting;
+ }
+ else
+ {
+ SharedLogger.logger.Warn($"WinLibrary/GetWindowsDisplayConfig: WARNING - Unabled to get current display mode settings from display {displayDevice.DeviceName}.");
+ }
+ }
+ else
+ {
+ SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: The display {displayDevice.DeviceName} is either not attached to the desktop, or is not a mirroring driver. Skipping this display device as we cannot use it.");
+ }
+
+ displayDeviceNum++;
+ }
+
// Store the active paths and modes in our display config object
windowsDisplayConfig.DisplayConfigPaths = paths;
windowsDisplayConfig.DisplayConfigModes = modes;
windowsDisplayConfig.DisplayHDRStates = hdrInfos;
+ windowsDisplayConfig.GdiDisplaySettings = gdiDeviceSettings;
windowsDisplayConfig.DisplayIdentifiers = GetCurrentDisplayIdentifiers();
return windowsDisplayConfig;
@@ -620,6 +686,8 @@ namespace DisplayMagicianShared.Windows
SharedLogger.logger.Warn($"WinLibrary/GetWindowsDisplayConfig: WARNING - DisplayConfigGetDeviceInfo returned WIN32STATUS {err} when trying to get the adapter device path for target #{path.TargetInfo.AdapterId}");
}
+ // show the
+
// get display target preferred mode
var targetPreferredInfo = new DISPLAYCONFIG_TARGET_PREFERRED_MODE();
targetPreferredInfo.Header.Type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE;
@@ -745,6 +813,76 @@ namespace DisplayMagicianShared.Windows
SharedLogger.logger.Warn($"WinLibrary/GetWindowsDisplayConfig: WARNING - DisplayConfigGetDeviceInfo returned WIN32STATUS {err} when trying to find out the SDL white level for display #{path.TargetInfo.Id}");
}
}
+
+ // Get the list of all display adapters in this machine through GDI
+ Dictionary gdiDeviceSettings = new Dictionary();
+ UInt32 displayDeviceNum = 0;
+ DISPLAY_DEVICE displayDevice = new DISPLAY_DEVICE();
+ displayDevice.Size = (UInt32)Marshal.SizeOf();
+ stringToReturn += $"----++++==== GDI Device Information ====++++----\n";
+ while (GDIImport.EnumDisplayDevices(null, displayDeviceNum, ref displayDevice, 0))
+ {
+ // Now we try and grab the GDI Device Info for each display device
+ stringToReturn += $"****** Display Device Info for Display {displayDevice.DeviceName} *******\n";
+ stringToReturn += $" Display Device ID: {displayDevice.DeviceId}\n";
+ stringToReturn += $" Display Device Key: {displayDevice.DeviceKey}\n";
+ stringToReturn += $" Display Device Name: {displayDevice.DeviceName}\n";
+ stringToReturn += $" Display Device String: {displayDevice.DeviceString}\n";
+ stringToReturn += $" Is Attached to Desktop: {displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.AttachedToDesktop)}\n";
+ stringToReturn += $" Is Disconnected: {displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.Disconnect)}\n";
+ stringToReturn += $" Is a Mirroing Device: {displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.MirroringDriver)}\n";
+ stringToReturn += $" Has Modes Pruned: {displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.ModesPruned)}\n";
+ stringToReturn += $" Is Multi-driver: {displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.MultiDriver)}\n";
+ stringToReturn += $" Is Primary Display Device: {displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.PrimaryDevice)}\n";
+ stringToReturn += $" Is Remote Display Device: {displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.Remote)}\n";
+ stringToReturn += $" Is Removable Display Device: {displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.Removable)}\n";
+ stringToReturn += $" Is VGA Compatible Display Device: {displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.VGACompatible)}\n";
+ stringToReturn += $"\n";
+
+
+ SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Getting the current Display Settings for {displayDevice.DeviceName}");
+ if (displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.AttachedToDesktop) || displayDevice.StateFlags.HasFlag(DISPLAY_DEVICE_STATE_FLAGS.MultiDriver))
+ {
+ // If the display device is attached to the Desktop, or a type of display that is represented by a psudeo mirroring application, then skip this display
+ // e.g. some sort of software interfaced display that doesn't have a physical plug, or maybe uses USB for communication
+ SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Getting the current Display Settings for {displayDevice.DeviceName}");
+ stringToReturn += $" Display Device Settings for attached Display {displayDevice.DeviceName} :\n";
+ DEVICE_MODE currentMode = new DEVICE_MODE();
+ currentMode.Size = (UInt16)Marshal.SizeOf();
+ bool gdiWorked = GDIImport.EnumDisplaySettings(displayDevice.DeviceName, DISPLAY_SETTINGS_MODE.CurrentSettings, ref currentMode);
+ if (gdiWorked)
+ {
+ SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Got the current Display Settings from display {displayDevice.DeviceName}.");
+ // Now we try and grab the GDI Device Settings for each display device
+ stringToReturn += $" Bits Per Pixel: {currentMode.BitsPerPixel}\n";
+ stringToReturn += $" Device Name: {currentMode.DeviceName}\n";
+ stringToReturn += $" Display Fixed Output: {currentMode.DisplayFixedOutput}\n";
+ stringToReturn += $" Grayscale Display: {currentMode.DisplayFlags.HasFlag(DISPLAY_FLAGS.Grayscale)}\n";
+ stringToReturn += $" Interlaced Display: {currentMode.DisplayFlags.HasFlag(DISPLAY_FLAGS.Interlaced)}\n";
+ stringToReturn += $" Refresh Rate: {currentMode.DisplayFrequency}Hz\n";
+ stringToReturn += $" Display Rotation: {currentMode.DisplayOrientation.ToString("G")}\n";
+ stringToReturn += $" Driver Extra: {currentMode.DriverExtra}\n";
+ stringToReturn += $" Driver Version: {currentMode.DriverVersion}\n";
+ stringToReturn += $" All Display Fields populated by driver: {currentMode.Fields.HasFlag(DEVICE_MODE_FIELDS.AllDisplay)}\n";
+ stringToReturn += $" Display Width and Height in Pixels: {currentMode.PixelsWidth} x {currentMode.PixelsHeight}\n";
+ stringToReturn += $" Display Position: X:{currentMode.Position.X}, Y:{currentMode.Position.Y}\n";
+ stringToReturn += $" Specification Version: {currentMode.SpecificationVersion}\n";
+ stringToReturn += $"\n";
+ }
+ else
+ {
+ SharedLogger.logger.Warn($"WinLibrary/GetWindowsDisplayConfig: WARNING - Unabled to get current display mode settings from display {displayDevice.DeviceName}.");
+ stringToReturn += $" No display settings found.\n\n";
+ }
+ }
+ else
+ {
+ SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: The display {displayDevice.DeviceName} is either not attached to the desktop, or is not a mirroring driver. Skipping this display device as we cannot use it.");
+ }
+
+ displayDeviceNum++;
+ }
+
return stringToReturn;
}
@@ -793,7 +931,7 @@ namespace DisplayMagicianShared.Windows
foreach (ADVANCED_HDR_INFO_PER_PATH myHDRstate in displayConfig.DisplayHDRStates)
{
- SharedLogger.logger.Trace($"Trying to get information whether HDR color is in use now on Display {myHDRstate.Id}.");
+ SharedLogger.logger.Trace($"WinLibrary/SetActiveConfig: Trying to get information whether HDR color is in use now on Display {myHDRstate.Id}.");
// Get advanced HDR info
var colorInfo = new DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO();
colorInfo.Header.Type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
@@ -807,7 +945,7 @@ namespace DisplayMagicianShared.Windows
if (myHDRstate.AdvancedColorInfo.AdvancedColorSupported && colorInfo.AdvancedColorEnabled != myHDRstate.AdvancedColorInfo.AdvancedColorEnabled)
{
- SharedLogger.logger.Trace($"HDR is available for use on Display {myHDRstate.Id}, and we want it set to {myHDRstate.AdvancedColorInfo.AdvancedColorEnabled} but is currently {colorInfo.AdvancedColorEnabled}.");
+ SharedLogger.logger.Trace($"WinLibrary/SetActiveConfig: HDR is available for use on Display {myHDRstate.Id}, and we want it set to {myHDRstate.AdvancedColorInfo.AdvancedColorEnabled} but is currently {colorInfo.AdvancedColorEnabled}.");
var setColorState = new DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE();
setColorState.Header.Type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE;
@@ -836,6 +974,61 @@ namespace DisplayMagicianShared.Windows
SharedLogger.logger.Warn($"WinLibrary/GetWindowsDisplayConfig: WARNING - DisplayConfigGetDeviceInfo returned WIN32STATUS {err} when trying to find out if HDR is supported for display #{myHDRstate.Id}");
}
+ }
+
+ // Attempt to set the display adapters in this machine through GDI
+ SharedLogger.logger.Trace($"WinLibrary/SetActiveConfig: Attempting to change Display Device settings through GDI API");
+ foreach (var myGdiDisplaySettings in displayConfig.GdiDisplaySettings)
+ {
+ string displayDeviceName = myGdiDisplaySettings.Key;
+ GDI_DISPLAY_SETTING displayDeviceSettings = myGdiDisplaySettings.Value;
+ SharedLogger.logger.Trace($"WinLibrary/SetActiveConfig: Trying to change Device Mode for Display {displayDeviceName}.");
+ DEVICE_MODE modeToUse = displayDeviceSettings.DeviceMode;
+ CHANGE_DISPLAY_RESULTS result = GDIImport.ChangeDisplaySettingsEx(displayDeviceName, ref modeToUse, IntPtr.Zero, CHANGE_DISPLAY_SETTINGS_FLAGS.CDS_UPDATEREGISTRY, IntPtr.Zero);
+ if (result == CHANGE_DISPLAY_RESULTS.Successful)
+ {
+ SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Successfully changed display {displayDeviceName} to use the new mode!");
+ }
+ else if (result == CHANGE_DISPLAY_RESULTS.BadDualView)
+ {
+ SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: The settings change was unsuccessful because the system is DualView capable. Display {displayDeviceName} not updated to new mode.");
+ return false;
+ }
+ else if (result == CHANGE_DISPLAY_RESULTS.BadFlags)
+ {
+ SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: An invalid set of flags was passed in. Display {displayDeviceName} not updated to use the new mode.");
+ return false;
+ }
+ else if (result == CHANGE_DISPLAY_RESULTS.BadMode)
+ {
+ SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: The graphics mode is not supported. Display {displayDeviceName} not updated to use the new mode.");
+ return false;
+ }
+ else if (result == CHANGE_DISPLAY_RESULTS.BadParam)
+ {
+ SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: An invalid parameter was passed in. This can include an invalid flag or combination of flags. Display {displayDeviceName} not updated to use the new mode.");
+ return false;
+ }
+ else if (result == CHANGE_DISPLAY_RESULTS.Failed)
+ {
+ SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: The display driver failed to apply the specified graphics mode. Display {displayDeviceName} not updated to use the new mode.");
+ return false;
+ }
+ else if (result == CHANGE_DISPLAY_RESULTS.NotUpdated)
+ {
+ SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: Unable to write new settings to the registry. Display {displayDeviceName} not updated to use the new mode.");
+ return false;
+ }
+ else if (result == CHANGE_DISPLAY_RESULTS.Restart)
+ {
+ SharedLogger.logger.Error($"WinLibrary/GetWindowsDisplayConfig: The computer must be restarted for the graphics mode to work. Display {displayDeviceName} not updated to use the new mode.");
+ return false;
+ }
+ else
+ {
+ SharedLogger.logger.Trace($"WinLibrary/GetWindowsDisplayConfig: Display {displayDeviceName} not updated to use the new mode.");
+ }
+
}
return true;
}