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 00000000..a1deb58f
Binary files /dev/null and b/app/Resources/icons8-brightness-32.png differ
diff --git a/app/Settings.Designer.cs b/app/Settings.Designer.cs
index 73fd98a7..2d1919e0 100644
--- a/app/Settings.Designer.cs
+++ b/app/Settings.Designer.cs
@@ -34,6 +34,7 @@ namespace GHelper
tableLayoutMatrix = new TableLayoutPanel();
comboMatrix = new RComboBox();
comboMatrixRunning = new RComboBox();
+ comboInterval = new RComboBox();
buttonMatrix = new RButton();
panelMatrixTitle = new Panel();
pictureMatrix = new PictureBox();
@@ -119,7 +120,14 @@ namespace GHelper
panelAllyTitle = new Panel();
pictureAlly = new PictureBox();
labelAlly = new Label();
- comboInterval = new RComboBox();
+ panelGamma = new Panel();
+ sliderGamma = new Slider();
+ panelGammaTitle = new Panel();
+ buttonGammaLoad = new RButton();
+ buttonGammaSave = new RButton();
+ labelGamma = new Label();
+ pictureGamma = new PictureBox();
+ labelGammaTitle = new Label();
panelMatrix.SuspendLayout();
tableLayoutMatrix.SuspendLayout();
panelMatrixTitle.SuspendLayout();
@@ -158,6 +166,9 @@ namespace GHelper
tableLayoutAlly.SuspendLayout();
panelAllyTitle.SuspendLayout();
((System.ComponentModel.ISupportInitialize)pictureAlly).BeginInit();
+ panelGamma.SuspendLayout();
+ panelGammaTitle.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)pictureGamma).BeginInit();
SuspendLayout();
//
// panelMatrix
@@ -169,7 +180,7 @@ namespace GHelper
panelMatrix.Controls.Add(panelMatrixTitle);
panelMatrix.Controls.Add(checkMatrix);
panelMatrix.Dock = DockStyle.Top;
- panelMatrix.Location = new Point(11, 827);
+ panelMatrix.Location = new Point(11, 950);
panelMatrix.Margin = new Padding(0);
panelMatrix.Name = "panelMatrix";
panelMatrix.Padding = new Padding(20, 20, 20, 10);
@@ -230,6 +241,21 @@ namespace GHelper
comboMatrixRunning.Size = new Size(248, 40);
comboMatrixRunning.TabIndex = 17;
//
+ // comboInterval
+ //
+ comboInterval.BorderColor = Color.White;
+ comboInterval.ButtonColor = Color.FromArgb(255, 255, 255);
+ comboInterval.Dock = DockStyle.Top;
+ comboInterval.Font = new Font("Segoe UI", 9F, FontStyle.Regular, GraphicsUnit.Point);
+ comboInterval.FormattingEnabled = true;
+ comboInterval.ItemHeight = 32;
+ comboInterval.Location = new Point(7, 75);
+ comboInterval.Margin = new Padding(7, 11, 7, 8);
+ comboInterval.Name = "comboInterval";
+ comboInterval.Size = new Size(248, 40);
+ comboInterval.TabIndex = 19;
+ comboInterval.Visible = false;
+ //
// buttonMatrix
//
buttonMatrix.Activated = false;
@@ -301,7 +327,7 @@ namespace GHelper
panelBattery.Controls.Add(sliderBattery);
panelBattery.Controls.Add(panelBatteryTitle);
panelBattery.Dock = DockStyle.Top;
- panelBattery.Location = new Point(11, 1485);
+ panelBattery.Location = new Point(11, 1608);
panelBattery.Margin = new Padding(0);
panelBattery.Name = "panelBattery";
panelBattery.Padding = new Padding(20, 20, 20, 10);
@@ -393,7 +419,7 @@ namespace GHelper
panelFooter.AutoSizeMode = AutoSizeMode.GrowAndShrink;
panelFooter.Controls.Add(tableButtons);
panelFooter.Dock = DockStyle.Top;
- panelFooter.Location = new Point(11, 1660);
+ panelFooter.Location = new Point(11, 1783);
panelFooter.Margin = new Padding(0);
panelFooter.Name = "panelFooter";
panelFooter.Padding = new Padding(20);
@@ -1140,7 +1166,7 @@ namespace GHelper
panelKeyboard.Controls.Add(tableLayoutKeyboard);
panelKeyboard.Controls.Add(panelKeyboardTitle);
panelKeyboard.Dock = DockStyle.Top;
- panelKeyboard.Location = new Point(11, 1143);
+ panelKeyboard.Location = new Point(11, 1266);
panelKeyboard.Margin = new Padding(0);
panelKeyboard.Name = "panelKeyboard";
panelKeyboard.Padding = new Padding(20);
@@ -1316,7 +1342,7 @@ namespace GHelper
panelVersion.Controls.Add(labelCharge);
panelVersion.Controls.Add(checkStartup);
panelVersion.Dock = DockStyle.Top;
- panelVersion.Location = new Point(11, 1604);
+ panelVersion.Location = new Point(11, 1727);
panelVersion.Margin = new Padding(4);
panelVersion.Name = "panelVersion";
panelVersion.Size = new Size(827, 56);
@@ -1341,7 +1367,7 @@ namespace GHelper
panelPeripherals.Controls.Add(tableLayoutPeripherals);
panelPeripherals.Controls.Add(panelPeripheralsTile);
panelPeripherals.Dock = DockStyle.Top;
- panelPeripherals.Location = new Point(11, 1287);
+ panelPeripherals.Location = new Point(11, 1410);
panelPeripherals.Margin = new Padding(0);
panelPeripherals.Name = "panelPeripherals";
panelPeripherals.Padding = new Padding(20, 20, 20, 10);
@@ -1483,7 +1509,7 @@ namespace GHelper
panelAlly.Controls.Add(tableLayoutAlly);
panelAlly.Controls.Add(panelAllyTitle);
panelAlly.Dock = DockStyle.Top;
- panelAlly.Location = new Point(11, 1003);
+ panelAlly.Location = new Point(11, 1126);
panelAlly.Margin = new Padding(0);
panelAlly.Name = "panelAlly";
panelAlly.Padding = new Padding(20, 20, 20, 0);
@@ -1611,20 +1637,118 @@ namespace GHelper
labelAlly.TabIndex = 26;
labelAlly.Text = "Ally Controller";
//
- // comboInterval
+ // panelGamma
//
- comboInterval.BorderColor = Color.White;
- comboInterval.ButtonColor = Color.FromArgb(255, 255, 255);
- comboInterval.Dock = DockStyle.Top;
- comboInterval.Font = new Font("Segoe UI", 9F, FontStyle.Regular, GraphicsUnit.Point);
- comboInterval.FormattingEnabled = true;
- comboInterval.ItemHeight = 32;
- comboInterval.Location = new Point(7, 75);
- comboInterval.Margin = new Padding(7, 11, 7, 8);
- comboInterval.Name = "comboInterval";
- comboInterval.Size = new Size(248, 40);
- comboInterval.TabIndex = 19;
- comboInterval.Visible = false;
+ panelGamma.AutoSize = true;
+ panelGamma.AutoSizeMode = AutoSizeMode.GrowAndShrink;
+ panelGamma.Controls.Add(sliderGamma);
+ panelGamma.Controls.Add(panelGammaTitle);
+ panelGamma.Dock = DockStyle.Top;
+ panelGamma.Location = new Point(11, 827);
+ panelGamma.Margin = new Padding(0);
+ panelGamma.Name = "panelGamma";
+ panelGamma.Padding = new Padding(20, 20, 20, 10);
+ panelGamma.Size = new Size(827, 123);
+ panelGamma.TabIndex = 9;
+ //
+ // sliderGamma
+ //
+ sliderGamma.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
+ sliderGamma.Location = new Point(40, 69);
+ sliderGamma.Margin = new Padding(4);
+ sliderGamma.Max = 100;
+ sliderGamma.Min = 0;
+ sliderGamma.Name = "sliderGamma";
+ sliderGamma.Size = new Size(752, 40);
+ sliderGamma.Step = 10;
+ sliderGamma.TabIndex = 20;
+ sliderGamma.Text = "sliderGamma";
+ sliderGamma.Value = 100;
+ //
+ // panelGammaTitle
+ //
+ panelGammaTitle.Controls.Add(buttonGammaLoad);
+ panelGammaTitle.Controls.Add(buttonGammaSave);
+ panelGammaTitle.Controls.Add(labelGamma);
+ panelGammaTitle.Controls.Add(pictureGamma);
+ panelGammaTitle.Controls.Add(labelGammaTitle);
+ panelGammaTitle.Dock = DockStyle.Top;
+ panelGammaTitle.Location = new Point(20, 20);
+ panelGammaTitle.Margin = new Padding(4);
+ panelGammaTitle.Name = "panelGammaTitle";
+ panelGammaTitle.Padding = new Padding(0, 0, 0, 4);
+ panelGammaTitle.Size = new Size(787, 44);
+ panelGammaTitle.TabIndex = 40;
+ //
+ // buttonGammaLoad
+ //
+ buttonGammaLoad.Activated = false;
+ buttonGammaLoad.BackColor = SystemColors.ControlLight;
+ buttonGammaLoad.BorderColor = Color.Transparent;
+ buttonGammaLoad.BorderRadius = 2;
+ buttonGammaLoad.FlatAppearance.BorderSize = 0;
+ buttonGammaLoad.FlatStyle = FlatStyle.Flat;
+ buttonGammaLoad.Font = new Font("Segoe UI", 7.125F, FontStyle.Bold, GraphicsUnit.Point);
+ buttonGammaLoad.ForeColor = SystemColors.ControlDark;
+ buttonGammaLoad.Location = new Point(500, 4);
+ buttonGammaLoad.Margin = new Padding(0);
+ buttonGammaLoad.Name = "buttonGammaLoad";
+ buttonGammaLoad.Secondary = true;
+ buttonGammaLoad.Size = new Size(76, 36);
+ buttonGammaLoad.TabIndex = 41;
+ buttonGammaLoad.Text = "Load";
+ buttonGammaLoad.UseVisualStyleBackColor = false;
+ //
+ // buttonGammaSave
+ //
+ buttonGammaSave.Activated = false;
+ buttonGammaSave.BackColor = SystemColors.ControlLight;
+ buttonGammaSave.BorderColor = Color.Transparent;
+ buttonGammaSave.BorderRadius = 2;
+ buttonGammaSave.FlatAppearance.BorderSize = 0;
+ buttonGammaSave.FlatStyle = FlatStyle.Flat;
+ buttonGammaSave.Font = new Font("Segoe UI", 7.125F, FontStyle.Bold, GraphicsUnit.Point);
+ buttonGammaSave.ForeColor = SystemColors.ControlDark;
+ buttonGammaSave.Location = new Point(576, 4);
+ buttonGammaSave.Margin = new Padding(0);
+ buttonGammaSave.Name = "buttonGammaSave";
+ buttonGammaSave.Secondary = true;
+ buttonGammaSave.Size = new Size(75, 36);
+ buttonGammaSave.TabIndex = 40;
+ buttonGammaSave.Text = "Save";
+ buttonGammaSave.UseVisualStyleBackColor = false;
+ //
+ // labelGamma
+ //
+ labelGamma.Anchor = AnchorStyles.Top | AnchorStyles.Right;
+ labelGamma.Location = new Point(675, 4);
+ labelGamma.Margin = new Padding(8, 0, 8, 0);
+ labelGamma.Name = "labelGamma";
+ labelGamma.Size = new Size(107, 36);
+ labelGamma.TabIndex = 39;
+ labelGamma.Text = " ";
+ labelGamma.TextAlign = ContentAlignment.TopRight;
+ //
+ // pictureGamma
+ //
+ pictureGamma.BackgroundImage = Properties.Resources.icons8_brightness_32;
+ pictureGamma.BackgroundImageLayout = ImageLayout.Zoom;
+ pictureGamma.Location = new Point(4, 2);
+ pictureGamma.Margin = new Padding(4);
+ pictureGamma.Name = "pictureGamma";
+ pictureGamma.Size = new Size(32, 32);
+ pictureGamma.TabIndex = 38;
+ pictureGamma.TabStop = false;
+ //
+ // labelGammaTitle
+ //
+ labelGammaTitle.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point);
+ labelGammaTitle.Location = new Point(43, 0);
+ labelGammaTitle.Margin = new Padding(8, 0, 8, 0);
+ labelGammaTitle.Name = "labelGammaTitle";
+ labelGammaTitle.Size = new Size(307, 32);
+ labelGammaTitle.TabIndex = 37;
+ labelGammaTitle.Text = "Flicker-free Dimming";
//
// SettingsForm
//
@@ -1632,7 +1756,7 @@ namespace GHelper
AutoScaleMode = AutoScaleMode.Dpi;
AutoSize = true;
AutoSizeMode = AutoSizeMode.GrowAndShrink;
- ClientSize = new Size(849, 1717);
+ ClientSize = new Size(849, 2119);
Controls.Add(panelFooter);
Controls.Add(panelVersion);
Controls.Add(panelBattery);
@@ -1640,6 +1764,7 @@ namespace GHelper
Controls.Add(panelKeyboard);
Controls.Add(panelAlly);
Controls.Add(panelMatrix);
+ Controls.Add(panelGamma);
Controls.Add(panelScreen);
Controls.Add(panelGPU);
Controls.Add(panelPerformance);
@@ -1708,6 +1833,9 @@ namespace GHelper
panelAllyTitle.ResumeLayout(false);
panelAllyTitle.PerformLayout();
((System.ComponentModel.ISupportInitialize)pictureAlly).EndInit();
+ panelGamma.ResumeLayout(false);
+ panelGammaTitle.ResumeLayout(false);
+ ((System.ComponentModel.ISupportInitialize)pictureGamma).EndInit();
ResumeLayout(false);
PerformLayout();
}
@@ -1803,5 +1931,13 @@ namespace GHelper
private RButton buttonController;
private RButton buttonOverlay;
private RComboBox comboInterval;
+ private Panel panelGamma;
+ private Slider sliderGamma;
+ private Panel panelGammaTitle;
+ private Label labelGamma;
+ private PictureBox pictureGamma;
+ private Label labelGammaTitle;
+ private RButton buttonGammaLoad;
+ private RButton buttonGammaSave;
}
}
diff --git a/app/Settings.cs b/app/Settings.cs
index b5f122c1..d56e081f 100644
--- a/app/Settings.cs
+++ b/app/Settings.cs
@@ -253,9 +253,31 @@ namespace GHelper
VisualiseFnLock();
buttonFnLock.Click += ButtonFnLock_Click;
+ sliderGamma.ValueChanged += SliderGamma_ValueChanged;
+ labelGamma.Text = "100%";
+
+ buttonGammaSave.Click += ButtonGammaSave_Click;
+ buttonGammaLoad.Click += ButtonGammaLoad_Click;
+
panelPerformance.Focus();
}
+ private void ButtonGammaLoad_Click(object? sender, EventArgs e)
+ {
+ screenControl.RestoreGamma();
+ }
+
+ private void ButtonGammaSave_Click(object? sender, EventArgs e)
+ {
+ screenControl.SaveGamma();
+ }
+
+ private void SliderGamma_ValueChanged(object? sender, EventArgs e)
+ {
+ screenControl.SetGamma(sliderGamma.Value);
+ labelGamma.Text = sliderGamma.Value + "%";
+ }
+
private void ButtonOverlay_Click(object? sender, EventArgs e)
{
KeyboardHook.KeyKeyKeyPress(Keys.LControlKey, Keys.LShiftKey, Keys.O);
diff --git a/app/WindowsDisplayAPI/ColorDepth.cs b/app/WindowsDisplayAPI/ColorDepth.cs
new file mode 100644
index 00000000..42314733
--- /dev/null
+++ b/app/WindowsDisplayAPI/ColorDepth.cs
@@ -0,0 +1,11 @@
+namespace WindowsDisplayAPI
+{
+ public enum ColorDepth
+ {
+ Depth4Bit = 4,
+ Depth8Bit = 8,
+ Depth16Bit = 16, // 0x00000010
+ Depth24Bit = 24, // 0x00000018
+ Depth32Bit = 32 // 0x00000020
+ }
+}
\ No newline at end of file
diff --git a/app/WindowsDisplayAPI/Device.cs b/app/WindowsDisplayAPI/Device.cs
new file mode 100644
index 00000000..359f8e8e
--- /dev/null
+++ b/app/WindowsDisplayAPI/Device.cs
@@ -0,0 +1,104 @@
+namespace WindowsDisplayAPI
+{
+ ///
+ /// 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 00000000..f67cd2eb
Binary files /dev/null and b/app/WindowsDisplayAPI/Icon.png differ
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