From c1dd8160150ff9ef09d88966edad4bc55022b735 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 11 Apr 2021 00:05:51 +0200 Subject: [PATCH] Extracted ScreenCapture-library from ambilight project --- ScreenCapture.sln | 25 ++ ScreenCapture.sln.DotSettings | 3 + ScreenCapture/DPIAwareness.cs | 37 ++ ScreenCapture/DX11ScreenCapture.cs | 337 ++++++++++++++++++ ScreenCapture/DX11ScreenCaptureService.cs | 71 ++++ ScreenCapture/IScreenCapture.cs | 14 + ScreenCapture/IScreenCaptureService.cs | 12 + ScreenCapture/Model/BlackBarDetection.cs | 119 +++++++ ScreenCapture/Model/CaptureZone.cs | 72 ++++ ScreenCapture/Model/Display.cs | 38 ++ ScreenCapture/Model/GraphicsCard.cs | 34 ++ ScreenCapture/ScreenCapture.csproj | 27 ++ .../ScreenCapture.csproj.DotSettings | 2 + 13 files changed, 791 insertions(+) create mode 100644 ScreenCapture.sln create mode 100644 ScreenCapture.sln.DotSettings create mode 100644 ScreenCapture/DPIAwareness.cs create mode 100644 ScreenCapture/DX11ScreenCapture.cs create mode 100644 ScreenCapture/DX11ScreenCaptureService.cs create mode 100644 ScreenCapture/IScreenCapture.cs create mode 100644 ScreenCapture/IScreenCaptureService.cs create mode 100644 ScreenCapture/Model/BlackBarDetection.cs create mode 100644 ScreenCapture/Model/CaptureZone.cs create mode 100644 ScreenCapture/Model/Display.cs create mode 100644 ScreenCapture/Model/GraphicsCard.cs create mode 100644 ScreenCapture/ScreenCapture.csproj create mode 100644 ScreenCapture/ScreenCapture.csproj.DotSettings diff --git a/ScreenCapture.sln b/ScreenCapture.sln new file mode 100644 index 0000000..eab2945 --- /dev/null +++ b/ScreenCapture.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31025.194 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenCapture", "ScreenCapture\ScreenCapture.csproj", "{90596344-E012-4534-A933-3BD1B55469DC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {90596344-E012-4534-A933-3BD1B55469DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90596344-E012-4534-A933-3BD1B55469DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90596344-E012-4534-A933-3BD1B55469DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90596344-E012-4534-A933-3BD1B55469DC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B5111031-6E65-4331-9E6E-A07165289860} + EndGlobalSection +EndGlobal diff --git a/ScreenCapture.sln.DotSettings b/ScreenCapture.sln.DotSettings new file mode 100644 index 0000000..8e3f720 --- /dev/null +++ b/ScreenCapture.sln.DotSettings @@ -0,0 +1,3 @@ + + DPI + DX \ No newline at end of file diff --git a/ScreenCapture/DPIAwareness.cs b/ScreenCapture/DPIAwareness.cs new file mode 100644 index 0000000..4d8622d --- /dev/null +++ b/ScreenCapture/DPIAwareness.cs @@ -0,0 +1,37 @@ +// ReSharper disable InconsistentNaming +using System.Runtime.InteropServices; + +namespace ScreenCapture +{ + public static class DPIAwareness + { + [DllImport("user32.dll", SetLastError = true)] + internal static extern bool SetProcessDpiAwarenessContext(int dpiFlag); + + [DllImport("SHCore.dll", SetLastError = true)] + internal static extern bool SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness); + + [DllImport("user32.dll")] + internal static extern bool SetProcessDPIAware(); + + internal enum PROCESS_DPI_AWARENESS + { + Process_DPI_Unaware = 0, + Process_System_DPI_Aware = 1, + Process_Per_Monitor_DPI_Aware = 2 + } + + internal enum DPI_AWARENESS_CONTEXT + { + DPI_AWARENESS_CONTEXT_UNAWARE = 16, + DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = 17, + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18, + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34 + } + + public static void Initalize() + { + SetProcessDpiAwarenessContext((int)DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + } + } +} diff --git a/ScreenCapture/DX11ScreenCapture.cs b/ScreenCapture/DX11ScreenCapture.cs new file mode 100644 index 0000000..b60887a --- /dev/null +++ b/ScreenCapture/DX11ScreenCapture.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using SharpGen.Runtime; +using Vortice.Direct3D; +using Vortice.Direct3D11; +using Vortice.DXGI; +using Vortice.Mathematics; +using MapFlags = Vortice.Direct3D11.MapFlags; +using ResultCode = Vortice.DXGI.ResultCode; +using Usage = Vortice.Direct3D11.Usage; + +namespace ScreenCapture +{ + // ReSharper disable once InconsistentNaming + public class DX11ScreenCapture : IScreenCapture + { + #region Constants + + private static readonly FeatureLevel[] FEATURE_LEVELS = + { + FeatureLevel.Level_11_1, + FeatureLevel.Level_11_0, + FeatureLevel.Level_10_1, + FeatureLevel.Level_10_0 + }; + + #endregion + + #region Properties & Fields + + private readonly object _captureLock = new(); + + private int _indexCounter = 0; + + public Display Display { get; } + + public int Timeout { get; set; } = 1000; + + private readonly IDXGIFactory1 _factory; + + private IDXGIOutput? _output; + private IDXGIOutputDuplication? _duplicatedOutput; + private ID3D11Device? _device; + private ID3D11DeviceContext? _context; + private ID3D11Texture2D? _captureTexture; + + private readonly Dictionary _captureZones = new(); + + #endregion + + #region Constructors + + public DX11ScreenCapture(IDXGIFactory1 factory, Display display) + { + this._factory = factory; + this.Display = display; + + Restart(); + } + + #endregion + + #region Methods + + public bool CaptureScreen() + { + bool result = false; + lock (_captureLock) + { + if ((_context == null) || (_duplicatedOutput == null) || (_captureTexture == null)) + { + Restart(); + return false; + } + + try + { + IDXGIResource? screenResource = null; + try + { + _duplicatedOutput.AcquireNextFrame(Timeout, out OutduplFrameInfo duplicateFrameInformation, out screenResource); + if ((screenResource == null) || (duplicateFrameInformation.LastPresentTime == 0)) return false; + + using ID3D11Texture2D screenTexture = screenResource.QueryInterface(); + _context.CopySubresourceRegion(_captureTexture, 0, 0, 0, 0, screenTexture, 0); + } + finally + { + try + { + screenResource?.Dispose(); + _duplicatedOutput?.ReleaseFrame(); + } + catch { /**/ } + } + + result = true; + } + catch (SharpGenException dxException) + { + if ((dxException.ResultCode == ResultCode.AccessLost) + || (dxException.ResultCode == ResultCode.AccessDenied) + || (dxException.ResultCode == ResultCode.InvalidCall)) + { + try + { + Restart(); + } + catch { Thread.Sleep(100); } + } + } + catch { /**/ } + + try + { + UpdateZones(); + } + catch { /**/ } + + return result; + } + } + + private void UpdateZones() + { + if (_context == null) return; + + lock (_captureZones) + { + foreach ((CaptureZone captureZone, (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? scalingTextureView)) in _captureZones.Where(z => z.Key.AutoUpdate || z.Key.IsUpdateRequested)) + { + if (scalingTexture != null) + { + _context.CopySubresourceRegion(scalingTexture, 0, 0, 0, 0, _captureTexture, 0, + new Box(captureZone.X, captureZone.Y, 0, + captureZone.X + captureZone.UnscaledWidth, + captureZone.Y + captureZone.UnscaledHeight, 1)); + _context.GenerateMips(scalingTextureView); + _context.CopySubresourceRegion(stagingTexture, 0, 0, 0, 0, scalingTexture, captureZone.DownscaleLevel); + } + else + _context.CopySubresourceRegion(stagingTexture, 0, 0, 0, 0, _captureTexture, 0, + new Box(captureZone.X, captureZone.Y, 0, + captureZone.X + captureZone.UnscaledWidth, + captureZone.Y + captureZone.UnscaledHeight, 1)); + + MappedSubresource mapSource = _context.Map(stagingTexture, 0, MapMode.Read, MapFlags.None); + IntPtr sourcePtr = mapSource.DataPointer; + lock (captureZone.Buffer) + Marshal.Copy(sourcePtr, captureZone.Buffer, 0, captureZone.Buffer.Length); + + _context.Unmap(stagingTexture, 0); + captureZone.SetUpdated(); + } + } + } + + public CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0) + { + if (_device == null) throw new ApplicationException("ScreenCapture isn't initialized."); + + if (x < 0) throw new ArgumentException("x < 0"); + if (y < 0) throw new ArgumentException("y < 0"); + if (width <= 0) throw new ArgumentException("with <= 0"); + if (height <= 0) throw new ArgumentException("height <= 0"); + if ((x + width) > Display.Width) throw new ArgumentException("x + width > Display width"); + if ((y + height) > Display.Height) throw new ArgumentException("y + height > Display height"); + + int textureWidth = (int)Math.Ceiling(width / 32.0) * 32; + int textureHeight = (int)Math.Ceiling(height / 32.0) * 32; + + int unscaledWidth = width; + int unscaledHeight = height; + if (downscaleLevel > 0) + for (int i = 0; i < downscaleLevel; i++) + { + width /= 2; + height /= 2; + } + + if (width < 1) width = 1; + if (height < 1) height = 1; + + int bufferWidth = (int)Math.Ceiling(width / 32.0) * 32; + int bufferHeight = (int)Math.Ceiling(height / 32.0) * 32; + + byte[] buffer = new byte[bufferWidth * bufferHeight * 4]; + + CaptureZone captureZone = new(_indexCounter++, x, y, width, height, downscaleLevel, unscaledWidth, unscaledHeight, textureWidth, textureHeight, bufferWidth, bufferHeight, buffer); + lock (_captureZones) + InitializeCaptureZone(captureZone); + + return captureZone; + } + + public bool UnregisterCaptureZone(CaptureZone captureZone) + { + lock (_captureZones) + { + if (_captureZones.TryGetValue(captureZone, out (ID3D11Texture2D stagingTexture, ID3D11Texture2D? scalingTexture, ID3D11ShaderResourceView? _scalingTextureView) data)) + { + _captureZones.Remove(captureZone); + data.stagingTexture.Dispose(); + data.scalingTexture?.Dispose(); + data._scalingTextureView?.Dispose(); + + return true; + } + + return false; + } + } + + private void InitializeCaptureZone(in CaptureZone captureZone) + { + Texture2DDescription stagingTextureDesc = new() + { + CpuAccessFlags = CpuAccessFlags.Read, + BindFlags = BindFlags.None, + Format = Format.B8G8R8A8_UNorm, + Width = captureZone.BufferWidth, + Height = captureZone.BufferHeight, + OptionFlags = ResourceOptionFlags.None, + MipLevels = 1, + ArraySize = 1, + SampleDescription = { Count = 1, Quality = 0 }, + Usage = Usage.Staging + }; + ID3D11Texture2D stagingTexture = _device!.CreateTexture2D(stagingTextureDesc); + + ID3D11Texture2D? scalingTexture = null; + ID3D11ShaderResourceView? scalingTextureView = null; + if (captureZone.DownscaleLevel > 0) + { + Texture2DDescription scalingTextureDesc = new() + { + CpuAccessFlags = CpuAccessFlags.None, + BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource, + Format = Format.B8G8R8A8_UNorm, + Width = captureZone.CaptureWidth, + Height = captureZone.CaptureHeight, + OptionFlags = ResourceOptionFlags.GenerateMips, + MipLevels = captureZone.DownscaleLevel + 1, + ArraySize = 1, + SampleDescription = { Count = 1, Quality = 0 }, + Usage = Usage.Default + }; + scalingTexture = _device!.CreateTexture2D(scalingTextureDesc); + scalingTextureView = _device.CreateShaderResourceView(scalingTexture); + } + + _captureZones[captureZone] = (stagingTexture, scalingTexture, scalingTextureView); + } + + public void Restart() + { + lock (_captureLock) + { + try + { + List captureZones = _captureZones.Keys.ToList(); + Dispose(); + + using IDXGIAdapter1 adapter = _factory.GetAdapter1(Display.GraphicsCard.Index); + + D3D11.D3D11CreateDevice(adapter, DriverType.Unknown, DeviceCreationFlags.None, FEATURE_LEVELS, out _device).CheckError(); + _context = _device.ImmediateContext; + + _output = adapter.GetOutput(Display.Index); + using IDXGIOutput5 output1 = _output.QueryInterface(); + + Texture2DDescription captureTextureDesc = new() + { + CpuAccessFlags = CpuAccessFlags.None, + BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource, + Format = Format.B8G8R8A8_UNorm, + Width = Display.Width, + Height = Display.Height, + OptionFlags = ResourceOptionFlags.None, + MipLevels = 1, + ArraySize = 1, + SampleDescription = { Count = 1, Quality = 0 }, + Usage = Usage.Default + }; + _captureTexture = _device.CreateTexture2D(captureTextureDesc); + + lock (_captureZones) + { + foreach (CaptureZone captureZone in captureZones) + InitializeCaptureZone(captureZone); + } + + _duplicatedOutput = output1.DuplicateOutput1(_device, Format.B8G8R8A8_UNorm); // DarthAffe 27.02.2021: This prepares for the use of 10bit color depth + } + catch { Dispose(false); } + } + } + + public void Dispose() => Dispose(true); + + private void Dispose(bool removeCaptureZones) + { + try + { + lock (_captureLock) + { + try { _duplicatedOutput?.Dispose(); } catch { /**/ } + _duplicatedOutput = null; + + try + { + if (removeCaptureZones) + { + List captureZones = _captureZones.Keys.ToList(); + foreach (CaptureZone captureZone in captureZones) + UnregisterCaptureZone(captureZone); + } + } + catch { /**/ } + + try { _output?.Dispose(); } catch { /**/ } + try { _context?.Dispose(); } catch { /**/ } + try { _device?.Dispose(); } catch { /**/ } + try { _captureTexture?.Dispose(); } catch { /**/ } + _context = null; + _captureTexture = null; + } + } + catch { /**/ } + } + + #endregion + } +} diff --git a/ScreenCapture/DX11ScreenCaptureService.cs b/ScreenCapture/DX11ScreenCaptureService.cs new file mode 100644 index 0000000..c24f82c --- /dev/null +++ b/ScreenCapture/DX11ScreenCaptureService.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using Vortice.DXGI; + +namespace ScreenCapture +{ + public class DX11ScreenCaptureService : IScreenCaptureService + { + #region Properties & Fields + + private readonly IDXGIFactory1 _factory; + + private readonly Dictionary _screenCaptures = new(); + + #endregion + + #region Constructors + + public DX11ScreenCaptureService() + { + DXGI.CreateDXGIFactory1(out _factory).CheckError(); + } + + #endregion + + #region Methods + + public IEnumerable GetGraphicsCards() + { + int i = 0; + while (_factory.EnumAdapters1(i, out IDXGIAdapter1 adapter).Success) + { + yield return new GraphicsCard(i, adapter.Description1.Description, adapter.Description1.VendorId, adapter.Description1.DeviceId); + adapter.Dispose(); + i++; + } + } + + public IEnumerable GetDisplays(GraphicsCard graphicsCard) + { + using IDXGIAdapter1? adapter = _factory.GetAdapter1(graphicsCard.Index); + + int i = 0; + while (adapter.EnumOutputs(i, out IDXGIOutput output).Success) + { + int width = output.Description.DesktopCoordinates.Right - output.Description.DesktopCoordinates.Left; + int height = output.Description.DesktopCoordinates.Bottom - output.Description.DesktopCoordinates.Top; + yield return new Display(i, output.Description.DeviceName, width, height, graphicsCard); + output.Dispose(); + i++; + } + } + + public IScreenCapture GetScreenCapture(Display display) + { + if (!_screenCaptures.TryGetValue(display, out DX11ScreenCapture? screenCapture)) + _screenCaptures.Add(display, screenCapture = new DX11ScreenCapture(_factory, display)); + return screenCapture; + } + + public void Dispose() + { + foreach (DX11ScreenCapture screenCapture in _screenCaptures.Values) + screenCapture.Dispose(); + _screenCaptures.Clear(); + + _factory.Dispose(); + } + + #endregion + } +} diff --git a/ScreenCapture/IScreenCapture.cs b/ScreenCapture/IScreenCapture.cs new file mode 100644 index 0000000..d8f7b32 --- /dev/null +++ b/ScreenCapture/IScreenCapture.cs @@ -0,0 +1,14 @@ +using System; + +namespace ScreenCapture +{ + public interface IScreenCapture : IDisposable + { + Display Display { get; } + + bool CaptureScreen(); + CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0); + bool UnregisterCaptureZone(CaptureZone captureZone); + void Restart(); + } +} diff --git a/ScreenCapture/IScreenCaptureService.cs b/ScreenCapture/IScreenCaptureService.cs new file mode 100644 index 0000000..8e686ff --- /dev/null +++ b/ScreenCapture/IScreenCaptureService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace ScreenCapture +{ + public interface IScreenCaptureService : IDisposable + { + IEnumerable GetGraphicsCards(); + IEnumerable GetDisplays(GraphicsCard graphicsCard); + IScreenCapture GetScreenCapture(Display display); + } +} diff --git a/ScreenCapture/Model/BlackBarDetection.cs b/ScreenCapture/Model/BlackBarDetection.cs new file mode 100644 index 0000000..58053ae --- /dev/null +++ b/ScreenCapture/Model/BlackBarDetection.cs @@ -0,0 +1,119 @@ +using System; + +namespace ScreenCapture +{ + public sealed class BlackBarDetection + { + #region Properties & Fields + + private readonly CaptureZone _captureZone; + + private int? _top; + public int Top => _top ??= CalculateTop(); + + private int? _bottom; + public int Bottom => _bottom ??= CalculateBottom(); + + private int? _left; + public int Left => _left ??= CalculateLeft(); + + private int? _right; + public int Right => _right ??= CalculateRight(); + + private int _theshold = 0; + public int Threshold + { + get => _theshold; + set + { + _theshold = value; + InvalidateCache(); + } + } + + #endregion + + #region Constructors + + public BlackBarDetection(CaptureZone captureZone) + { + this._captureZone = captureZone; + } + + #endregion + + #region Methods + + public void InvalidateCache() + { + _top = null; + _bottom = null; + _left = null; + _right = null; + } + + private int CalculateTop() + { + int threshold = Threshold; + int stride = _captureZone.BufferWidth * 4; + int bytesPerRow = _captureZone.Width * 4; + for (int row = 0; row < _captureZone.Height; row++) + { + Span data = new(_captureZone.Buffer, row * stride, bytesPerRow); + for (int i = 0; i < data.Length; i += 4) + if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold)) + return row; + } + + return 0; + } + + private int CalculateBottom() + { + int threshold = Threshold; + int stride = _captureZone.BufferWidth * 4; + int bytesPerRow = _captureZone.Width * 4; + for (int row = _captureZone.Height; row >= 0; row--) + { + Span data = new(_captureZone.Buffer, row * stride, bytesPerRow); + for (int i = 0; i < data.Length; i += 4) + if ((data[i] > threshold) || (data[i + 1] > threshold) || (data[i + 2] > threshold)) + return _captureZone.Height - row; + } + + return 0; + } + + private int CalculateLeft() + { + int threshold = Threshold; + int stride = _captureZone.BufferWidth * 4; + byte[] buffer = _captureZone.Buffer; + for (int column = 0; column < _captureZone.Width; column++) + for (int row = 0; row < _captureZone.Height; row++) + { + int offset = (stride * row) + (column * 4); + if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold)) return column; + } + + return 0; + } + + private int CalculateRight() + { + int threshold = Threshold; + int stride = _captureZone.BufferWidth * 4; + byte[] buffer = _captureZone.Buffer; + for (int column = _captureZone.Width; column >= 0; column--) + for (int row = 0; row < _captureZone.Height; row++) + { + int offset = (stride * row) + (column * 4); + if ((buffer[offset] > threshold) || (buffer[offset + 1] > threshold) || (buffer[offset + 2] > threshold)) return _captureZone.Width - column; + } + + return 0; + } + + #endregion + } +} diff --git a/ScreenCapture/Model/CaptureZone.cs b/ScreenCapture/Model/CaptureZone.cs new file mode 100644 index 0000000..a57d8c1 --- /dev/null +++ b/ScreenCapture/Model/CaptureZone.cs @@ -0,0 +1,72 @@ +namespace ScreenCapture +{ + public sealed class CaptureZone + { + #region Properties & Fields + + public int Id { get; } + + public int X { get; } + public int Y { get; } + public int Width { get; } + public int Height { get; } + + public int DownscaleLevel { get; } + + public int UnscaledWidth { get; } + public int UnscaledHeight { get; } + + public int CaptureWidth { get; } + public int CaptureHeight { get; } + + public int BufferWidth { get; } + public int BufferHeight { get; } + public byte[] Buffer { get; } + + public BlackBarDetection BlackBars { get; } + + public bool AutoUpdate { get; set; } = true; + public bool IsUpdateRequested { get; private set; } + + #endregion + + #region Constructors + + public CaptureZone(int id, int x, int y, int width, int height, int downscaleLevel, int unscaledWidth, int unscaledHeight, int captureWidth, int captureHeight, int bufferWidth, int bufferHeight, byte[] buffer) + { + this.Id = id; + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; + this.UnscaledWidth = unscaledWidth; + this.UnscaledHeight = unscaledHeight; + this.DownscaleLevel = downscaleLevel; + this.CaptureWidth = captureWidth; + this.CaptureHeight = captureHeight; + this.BufferWidth = bufferWidth; + this.BufferHeight = bufferHeight; + this.Buffer = buffer; + + BlackBars = new BlackBarDetection(this); + } + + #endregion + + #region Methods + + public void RequestUpdate() => IsUpdateRequested = true; + + public void SetUpdated() + { + IsUpdateRequested = false; + BlackBars.InvalidateCache(); + } + + public override int GetHashCode() => Id; + public bool Equals(CaptureZone other) => Id == other.Id; + public override bool Equals(object? obj) => obj is CaptureZone other && Equals(other); + + #endregion + } +} \ No newline at end of file diff --git a/ScreenCapture/Model/Display.cs b/ScreenCapture/Model/Display.cs new file mode 100644 index 0000000..4783081 --- /dev/null +++ b/ScreenCapture/Model/Display.cs @@ -0,0 +1,38 @@ +namespace ScreenCapture +{ + public readonly struct Display + { + #region Properties & Fields + + public int Index { get; } + public string DeviceName { get; } + + public int Width { get; } + public int Height { get; } + + public GraphicsCard GraphicsCard { get; } + + #endregion + + #region Constructors + + public Display(int index, string deviceName, int width, int height, GraphicsCard graphicsCard) + { + this.Index = index; + this.DeviceName = deviceName; + this.Width = width; + this.Height = height; + this.GraphicsCard = graphicsCard; + } + + #endregion + + #region Methods + + public bool Equals(Display other) => Index == other.Index; + public override bool Equals(object? obj) => obj is Display other && Equals(other); + public override int GetHashCode() => Index; + + #endregion + } +} diff --git a/ScreenCapture/Model/GraphicsCard.cs b/ScreenCapture/Model/GraphicsCard.cs new file mode 100644 index 0000000..7b3fbad --- /dev/null +++ b/ScreenCapture/Model/GraphicsCard.cs @@ -0,0 +1,34 @@ +namespace ScreenCapture +{ + public readonly struct GraphicsCard + { + #region Properties & Fields + + public int Index { get; } + public string Name { get; } + public int VendorId { get; } + public int DeviceId { get; } + + #endregion + + #region Constructors + + public GraphicsCard(int index, string name, int vendorId, int deviceId) + { + this.Index = index; + this.Name = name; + this.VendorId = vendorId; + this.DeviceId = deviceId; + } + + #endregion + + #region Methods + + public bool Equals(GraphicsCard other) => Index == other.Index; + public override bool Equals(object? obj) => obj is GraphicsCard other && Equals(other); + public override int GetHashCode() => Index; + + #endregion + } +} diff --git a/ScreenCapture/ScreenCapture.csproj b/ScreenCapture/ScreenCapture.csproj new file mode 100644 index 0000000..e87033e --- /dev/null +++ b/ScreenCapture/ScreenCapture.csproj @@ -0,0 +1,27 @@ + + + + net5.0 + enable + true + + + + $(DefineConstants);TRACE;DEBUG + true + full + false + + + + pdbonly + true + $(NoWarn);CS1591;CS1572;CS1573 + $(DefineConstants);RELEASE + + + + + + + diff --git a/ScreenCapture/ScreenCapture.csproj.DotSettings b/ScreenCapture/ScreenCapture.csproj.DotSettings new file mode 100644 index 0000000..dd7e081 --- /dev/null +++ b/ScreenCapture/ScreenCapture.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file