diff --git a/src/Askaiser.Marionette/DisplayScreen.cs b/src/Askaiser.Marionette/DisplayScreen.cs new file mode 100644 index 0000000..918ae6e --- /dev/null +++ b/src/Askaiser.Marionette/DisplayScreen.cs @@ -0,0 +1,286 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Askaiser.Marionette +{ + internal static class DisplayScreen + { + public static Task GetMonitors() + { + return Task.Run(() => GetMonitorsInternal().ToArray()); + } + + private static IEnumerable GetMonitorsInternal() + { + var monitors = new List(); + var monitorIndex = 0; + + var onMonitorCallback = new MonitorEnumProcedure((IntPtr monitorHandle, IntPtr _, ref RectangleL _, IntPtr _) => + { + var monitorInfo = MonitorInfo.Initialize(); + var deviceMode = new DeviceMode(DeviceModeFields.None); + + if (GetMonitorInfo(monitorHandle, ref monitorInfo) && EnumDisplaySettings(monitorInfo.DisplayName, DisplaySettingsMode.CurrentSettings, ref deviceMode)) + { + var width = (int)deviceMode.PixelsWidth; + var height = (int)deviceMode.PixelsHeight; + + monitors.Add(new MonitorDescription( + Index: monitorIndex++, + Left: monitorInfo.Bounds.Left, + Top: monitorInfo.Bounds.Top, + Right: monitorInfo.Bounds.Left + width, + Bottom: monitorInfo.Bounds.Top + height)); + } + + return 1; + }); + + EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, onMonitorCallback, IntPtr.Zero); + + if (monitors.Count == 0) + { + throw new InvalidOperationException("No monitors were found."); + } + + return monitors; + } + + #region Interop + + [DllImport("user32.dll")] + private static extern bool EnumDisplayMonitors( + [In] IntPtr dcHandle, + [In] IntPtr clip, + MonitorEnumProcedure callback, + IntPtr callbackObject); + + [DllImport("user32.dll")] + private static extern bool GetMonitorInfo( + IntPtr monitorHandle, + ref MonitorInfo monitorInfo); + + [DllImport("user32.dll", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] + private static extern bool EnumDisplaySettings( + string deviceName, + DisplaySettingsMode mode, + ref DeviceMode devMode); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int MonitorEnumProcedure( + IntPtr monitorHandle, + IntPtr dcHandle, + ref RectangleL rect, + IntPtr callbackObject); + + [StructLayout(LayoutKind.Sequential)] + private readonly struct RectangleL + { + [MarshalAs(UnmanagedType.U4)] + public readonly int Left; + + [MarshalAs(UnmanagedType.U4)] + public readonly int Top; + + [MarshalAs(UnmanagedType.U4)] + public readonly int Right; + + [MarshalAs(UnmanagedType.U4)] + public readonly int Bottom; + } + + [StructLayout(LayoutKind.Sequential)] + private 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)), + }; + } + } + + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd183565(v=vs.85).aspx + [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)] + private readonly 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; + + [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() + { + this.SpecificationVersion = 0x0320; + this.Size = (ushort)Marshal.SizeOf(this.GetType()); + this.Fields = fields; + } + } + + // https://msdn.microsoft.com/en-us/library/vs/alm/dd162807(v=vs.85).aspx + [StructLayout(LayoutKind.Sequential)] + private readonly struct PointL + { + [MarshalAs(UnmanagedType.I4)] + public readonly int X; + + [MarshalAs(UnmanagedType.I4)] + public readonly int Y; + } + + [Flags] + private enum MonitorInfoFlags : uint + { + None = 0, + Primary = 1, + } + + private enum DisplaySettingsMode + { + CurrentSettings = -1, + RegistrySettings = -2, + } + + [Flags] + private 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, + } + + [Flags] + private enum DisplayFlags : uint + { + None = 0, + Grayscale = 1, + Interlaced = 2, + } + + private enum DisplayOrientation : uint + { + Identity = 0, + Rotate90Degree = 1, + Rotate180Degree = 2, + Rotate270Degree = 3, + } + + private enum DisplayFixedOutput : uint + { + Default = 0, + Stretch = 1, + Center = 2, + } + + #endregion + } +} diff --git a/src/Askaiser.Marionette/GraphicsScreenshot.cs b/src/Askaiser.Marionette/GraphicsScreenshot.cs deleted file mode 100644 index 600a36b..0000000 --- a/src/Askaiser.Marionette/GraphicsScreenshot.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace Askaiser.Marionette -{ - internal static class GraphicsScreenshot - { - [DllImport("user32.dll", EntryPoint = "EnumDisplayMonitors")] - private static extern bool EnumDisplayMonitors( - [In] IntPtr dcHandle, - [In] IntPtr clip, - MonitorEnumProcedure callback, - IntPtr callbackObject); - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - private delegate int MonitorEnumProcedure( - IntPtr monitorHandle, - IntPtr dcHandle, - ref RectangleL rect, - IntPtr callbackObject); - - [StructLayout(LayoutKind.Sequential)] - private readonly struct RectangleL - { - [MarshalAs(UnmanagedType.U4)] - public readonly int Left; - - [MarshalAs(UnmanagedType.U4)] - public readonly int Top; - - [MarshalAs(UnmanagedType.U4)] - public readonly int Right; - - [MarshalAs(UnmanagedType.U4)] - public readonly int Bottom; - } - - public static Task GetMonitors() - { - return Task.Run(() => GetMonitorsInternal().ToArray()); - } - - private static IEnumerable GetMonitorsInternal() - { - var monitors = new List(); - var monitorIndex = 0; - - var onMonitorCallback = new MonitorEnumProcedure((IntPtr _, IntPtr _, ref RectangleL rect, IntPtr _) => - { - monitors.Add(new MonitorDescription(monitorIndex++, rect.Left, rect.Top, rect.Right, rect.Bottom)); - return 1; - }); - - EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, onMonitorCallback, IntPtr.Zero); - - if (monitors.Count == 0) - { - throw new InvalidOperationException("No monitors were found."); - } - - return monitors; - } - - public static Task Take(Rectangle monitor) - { - return Task.Run(() => TakeInternal(monitor)); - } - - private static Bitmap TakeInternal(Rectangle monitor) - { - var bitmap = new Bitmap(monitor.Width, monitor.Height); - using (var graphics = Graphics.FromImage(bitmap)) - { - graphics.CopyFromScreen(monitor.Left, monitor.Top, 0, 0, bitmap.Size, CopyPixelOperation.SourceCopy); - } - - return bitmap; - } - } -} diff --git a/src/Askaiser.Marionette/MonitorService.cs b/src/Askaiser.Marionette/MonitorService.cs index 26bd135..1b1cdd8 100644 --- a/src/Askaiser.Marionette/MonitorService.cs +++ b/src/Askaiser.Marionette/MonitorService.cs @@ -42,7 +42,7 @@ public async Task GetMonitors() return this._monitors; } - this._monitors = await GraphicsScreenshot.GetMonitors().ConfigureAwait(false); + this._monitors = await DisplayScreen.GetMonitors().ConfigureAwait(false); } return this._monitors; @@ -75,7 +75,7 @@ public async Task GetScreenshot(MonitorDescription monitor) return cachedScreenshot; } - var screenshot = await GraphicsScreenshot.Take(monitor).ConfigureAwait(false); + var screenshot = await ScreenshotService.Take(monitor).ConfigureAwait(false); var screenshotStream = new MemoryStream(); diff --git a/src/Askaiser.Marionette/ScreenshotService.cs b/src/Askaiser.Marionette/ScreenshotService.cs new file mode 100644 index 0000000..5786dca --- /dev/null +++ b/src/Askaiser.Marionette/ScreenshotService.cs @@ -0,0 +1,24 @@ +using System.Drawing; +using System.Threading.Tasks; + +namespace Askaiser.Marionette +{ + internal static class ScreenshotService + { + public static Task Take(Rectangle monitor) + { + return Task.Run(() => TakeInternal(monitor)); + } + + private static Bitmap TakeInternal(Rectangle monitor) + { + var bitmap = new Bitmap(monitor.Width, monitor.Height); + using (var graphics = Graphics.FromImage(bitmap)) + { + graphics.CopyFromScreen(monitor.Left, monitor.Top, 0, 0, bitmap.Size, CopyPixelOperation.SourceCopy); + } + + return bitmap; + } + } +}