diff --git a/ScreenCapture.NET.DX9/ScreenCapture.NET.DX9.csproj b/ScreenCapture.NET.DX9/ScreenCapture.NET.DX9.csproj
index 3db631c..caceecc 100644
--- a/ScreenCapture.NET.DX9/ScreenCapture.NET.DX9.csproj
+++ b/ScreenCapture.NET.DX9/ScreenCapture.NET.DX9.csproj
@@ -68,8 +68,4 @@
-
-
-
-
diff --git a/ScreenCapture.NET.X11/Downscale/AverageByteSampler.cs b/ScreenCapture.NET.X11/Downscale/AverageByteSampler.cs
new file mode 100644
index 0000000..e8e7423
--- /dev/null
+++ b/ScreenCapture.NET.X11/Downscale/AverageByteSampler.cs
@@ -0,0 +1,149 @@
+// DarthAffe 07.09.2023: Copied from https://github.com/DarthAffe/RGB.NET/blob/2e0754f474b82ed4d0cae5c6c44378d234f1321b/RGB.NET.Presets/Textures/Sampler/AverageByteSampler.cs
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace ScreenCapture.NET.Downscale;
+
+///
+/// Represents a sampled that averages multiple byte-data entries.
+///
+internal static class AverageByteSampler
+{
+ #region Constants
+
+ private static readonly int INT_VECTOR_LENGTH = Vector.Count;
+
+ #endregion
+
+ #region Methods
+
+ public static unsafe void Sample(in SamplerInfo info, in Span pixelData)
+ {
+ int count = info.Width * info.Height;
+ if (count == 0) return;
+
+ int dataLength = pixelData.Length;
+ Span sums = stackalloc uint[dataLength];
+
+ int elementsPerVector = Vector.Count / dataLength;
+ int valuesPerVector = elementsPerVector * dataLength;
+ if (Vector.IsHardwareAccelerated && (info.Height > 1) && (info.Width >= valuesPerVector) && (dataLength <= Vector.Count))
+ {
+ int chunks = info.Width / elementsPerVector;
+
+ Vector sum1 = Vector.Zero;
+ Vector sum2 = Vector.Zero;
+ Vector sum3 = Vector.Zero;
+ Vector sum4 = Vector.Zero;
+
+ for (int y = 0; y < info.Height; y++)
+ {
+ ReadOnlySpan data = info[y];
+
+ fixed (byte* colorPtr = data)
+ {
+ byte* current = colorPtr;
+ for (int i = 0; i < chunks; i++)
+ {
+ Vector bytes = *(Vector*)current;
+ Vector.Widen(bytes, out Vector short1, out Vector short2);
+ Vector.Widen(short1, out Vector int1, out Vector int2);
+ Vector.Widen(short2, out Vector int3, out Vector int4);
+
+ sum1 = Vector.Add(sum1, int1);
+ sum2 = Vector.Add(sum2, int2);
+ sum3 = Vector.Add(sum3, int3);
+ sum4 = Vector.Add(sum4, int4);
+
+ current += valuesPerVector;
+ }
+ }
+
+ int missingElements = data.Length - (chunks * valuesPerVector);
+ int offset = chunks * valuesPerVector;
+ for (int i = 0; i < missingElements; i += dataLength)
+ for (int j = 0; j < sums.Length; j++)
+ sums[j] += data[offset + i + j];
+ }
+
+ int value = 0;
+ int sumIndex = 0;
+ for (int j = 0; (j < INT_VECTOR_LENGTH) && (value < valuesPerVector); j++)
+ {
+ sums[sumIndex] += sum1[j];
+ ++sumIndex;
+ ++value;
+
+ if (sumIndex >= dataLength)
+ sumIndex = 0;
+ }
+
+ for (int j = 0; (j < INT_VECTOR_LENGTH) && (value < valuesPerVector); j++)
+ {
+ sums[sumIndex] += sum2[j];
+ ++sumIndex;
+ ++value;
+
+ if (sumIndex >= dataLength)
+ sumIndex = 0;
+ }
+
+ for (int j = 0; (j < INT_VECTOR_LENGTH) && (value < valuesPerVector); j++)
+ {
+ sums[sumIndex] += sum3[j];
+ ++sumIndex;
+ ++value;
+
+ if (sumIndex >= dataLength)
+ sumIndex = 0;
+ }
+
+ for (int j = 0; (j < INT_VECTOR_LENGTH) && (value < valuesPerVector); j++)
+ {
+ sums[sumIndex] += sum4[j];
+ ++sumIndex;
+ ++value;
+
+ if (sumIndex >= dataLength)
+ sumIndex = 0;
+ }
+ }
+ else
+ {
+ for (int y = 0; y < info.Height; y++)
+ {
+ ReadOnlySpan data = info[y];
+ for (int i = 0; i < data.Length; i += dataLength)
+ for (int j = 0; j < sums.Length; j++)
+ sums[j] += data[i + j];
+ }
+ }
+
+ float divisor = count * byte.MaxValue;
+ for (int i = 0; i < pixelData.Length; i++)
+ pixelData[i] = (sums[i] / divisor).GetByteValueFromPercentage();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static byte GetByteValueFromPercentage(this float percentage)
+ {
+ if (float.IsNaN(percentage)) return 0;
+
+ percentage = percentage.Clamp(0, 1.0f);
+ return (byte)(percentage >= 1.0f ? 255 : percentage * 256.0f);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float Clamp(this float value, float min, float max)
+ {
+ // ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10%
+ if (value < min) return min;
+ if (value > max) return max;
+ return value;
+ // ReSharper restore ConvertIfStatementToReturnStatement
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/ScreenCapture.NET.X11/Downscale/SamplerInfo.cs b/ScreenCapture.NET.X11/Downscale/SamplerInfo.cs
new file mode 100644
index 0000000..97081e3
--- /dev/null
+++ b/ScreenCapture.NET.X11/Downscale/SamplerInfo.cs
@@ -0,0 +1,63 @@
+// DarthAffe 07.09.2023: Copied from https://github.com/DarthAffe/RGB.NET/blob/2e0754f474b82ed4d0cae5c6c44378d234f1321b/RGB.NET.Core/Rendering/Textures/Sampler/SamplerInfo.cs
+
+using System;
+
+namespace ScreenCapture.NET.Downscale;
+
+///
+/// Represents the information used to sample data.
+///
+/// The type of the data to sample.
+internal readonly ref struct SamplerInfo
+{
+ #region Properties & Fields
+
+ private readonly ReadOnlySpan _data;
+ private readonly int _x;
+ private readonly int _y;
+ private readonly int _stride;
+ private readonly int _dataPerPixel;
+ private readonly int _dataWidth;
+
+ ///
+ /// Gets the width of the region the data comes from.
+ ///
+ public readonly int Width;
+
+ ///
+ /// Gets the height of region the data comes from.
+ ///
+ public readonly int Height;
+
+ ///
+ /// Gets the data for the requested row.
+ ///
+ /// The row to get the data for.
+ /// A readonly span containing the data of the row.
+ public ReadOnlySpan this[int row] => _data.Slice((((_y + row) * _stride) + _x) * _dataPerPixel, _dataWidth);
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The width of the region the data comes from.
+ /// The height of region the data comes from.
+ /// The data to sample.
+ public SamplerInfo(int x, int y, int width, int height, int stride, int dataPerPixel, in ReadOnlySpan data)
+ {
+ this._x = x;
+ this._y = y;
+ this._data = data;
+ this._stride = stride;
+ this._dataPerPixel = dataPerPixel;
+ this.Width = width;
+ this.Height = height;
+
+ _dataWidth = width * dataPerPixel;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/ScreenCapture.NET.X11/Resources/icon.png b/ScreenCapture.NET.X11/Resources/icon.png
new file mode 100644
index 0000000..46c5033
Binary files /dev/null and b/ScreenCapture.NET.X11/Resources/icon.png differ
diff --git a/ScreenCapture.NET.X11/ScreenCapture.NET.X11.csproj b/ScreenCapture.NET.X11/ScreenCapture.NET.X11.csproj
new file mode 100644
index 0000000..b7cbb84
--- /dev/null
+++ b/ScreenCapture.NET.X11/ScreenCapture.NET.X11.csproj
@@ -0,0 +1,68 @@
+
+
+ net7.0;net6.0
+ linux-x64
+ latest
+ enable
+ true
+
+ Darth Affe
+ Wyrez
+ en-US
+ en-US
+ ScreenCapture.NET.X11
+ ScreenCapture.NET.X11
+ ScreenCapture.NET.X11
+ ScreenCapture.NET.X11
+ ScreenCapture.NET
+ Vortice based Screen-Capturing
+ Vortice based Screen-Capturing using Desktop Duplication
+ Copyright © Darth Affe 2023
+ Copyright © Darth Affe 2023
+ icon.png
+ https://github.com/DarthAffe/ScreenCapture.NET
+ LGPL-2.1-only
+ Github
+ https://github.com/DarthAffe/ScreenCapture.NET
+ True
+
+
+
+
+ 2.0.0
+ 2.0.0
+ 2.0.0
+
+ ..\bin\
+ true
+ True
+ True
+ snupkg
+
+
+
+ $(DefineConstants);TRACE;DEBUG
+ true
+ full
+ false
+
+
+
+ portable
+ true
+ $(NoWarn);CS1591;CS1572;CS1573
+ $(DefineConstants);RELEASE
+
+
+
+
+ True
+
+
+
+
+
+
+
+
+
diff --git a/ScreenCapture.NET.X11/X11.cs b/ScreenCapture.NET.X11/X11.cs
new file mode 100644
index 0000000..85b8ec3
--- /dev/null
+++ b/ScreenCapture.NET.X11/X11.cs
@@ -0,0 +1,133 @@
+using System.Runtime.InteropServices;
+
+namespace ScreenCapture.NET;
+
+#if NET7_0_OR_GREATER
+internal static partial class X11
+{
+ internal const nint DISPLAY_NAME = 0;
+
+ internal const long ALL_PLANES = -1;
+ internal const int ZPIXMAP = 2;
+
+ [LibraryImport("libX11.so.6")]
+ internal static partial nint XOpenDisplay(nint displayName);
+
+ [LibraryImport("libX11.so.6")]
+ internal static partial int XScreenCount(nint display);
+
+ [LibraryImport("libX11.so.6")]
+ internal static partial nint XScreenOfDisplay(nint display, int screeenNumber);
+
+ [LibraryImport("libX11.so.6")]
+ internal static partial int XWidthOfScreen(nint screen);
+
+ [LibraryImport("libX11.so.6")]
+ internal static partial int XHeightOfScreen(nint screen);
+
+ [LibraryImport("libX11.so.6")]
+ internal static partial nint XRootWindowOfScreen(nint screen);
+
+ [LibraryImport("libX11.so.6")]
+ internal static partial nint XGetImage(nint display, nint drawable, int x, int y, uint width, uint height, long planeMask, int format);
+
+ [LibraryImport("libX11.so.6")]
+ internal static partial nint XGetSubImage(nint display, nint drawable, int x, int y, uint width, uint height, long planeMask, int format, nint image, int destX, int dextY);
+
+ [LibraryImport("libX11.so.6")]
+ internal static partial void XDestroyImage(nint image);
+
+ [LibraryImport("libX11.so.6")]
+ internal static partial nint XDisplayString(nint display);
+
+ [LibraryImport("libX11.so.6")]
+ internal static partial void XCloseDisplay(nint display);
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct XImage
+ {
+ // ReSharper disable MemberCanBePrivate.Global
+ public int width;
+ public int height;
+ public int xoffset;
+ public int format;
+ public byte* data;
+ public int byte_order;
+ public int bitmap_unit;
+ public int bitmap_bit_order;
+ public int bitmap_pad;
+ public int depth;
+ public int bytes_per_line;
+ public int bits_per_pixel;
+ public uint red_mask;
+ public uint green_mask;
+ public uint blue_mask;
+ public nint obdata;
+ // ReSharper restore MemberCanBePrivate.Global
+ }
+}
+#else
+internal static class X11
+{
+ internal const nint DISPLAY_NAME = 0;
+
+ internal const long ALL_PLANES = -1;
+ internal const int ZPIXMAP = 2;
+
+ [DllImport("libX11.so.6")]
+ internal static extern nint XOpenDisplay(nint displayName);
+
+ [DllImport("libX11.so.6")]
+ internal static extern int XScreenCount(nint display);
+
+ [DllImport("libX11.so.6")]
+ internal static extern nint XScreenOfDisplay(nint display, int screeenNumber);
+
+ [DllImport("libX11.so.6")]
+ internal static extern int XWidthOfScreen(nint screen);
+
+ [DllImport("libX11.so.6")]
+ internal static extern int XHeightOfScreen(nint screen);
+
+ [DllImport("libX11.so.6")]
+ internal static extern nint XRootWindowOfScreen(nint screen);
+
+ [DllImport("libX11.so.6")]
+ internal static extern nint XGetImage(nint display, nint drawable, int x, int y, uint width, uint height, long planeMask, int format);
+
+ [DllImport("libX11.so.6")]
+ internal static extern nint XGetSubImage(nint display, nint drawable, int x, int y, uint width, uint height, long planeMask, int format, nint image, int destX, int dextY);
+
+ [DllImport("libX11.so.6")]
+ internal static extern void XDestroyImage(nint image);
+
+ [DllImport("libX11.so.6")]
+ internal static extern nint XDisplayString(nint display);
+
+ [DllImport("libX11.so.6")]
+ internal static extern void XCloseDisplay(nint display);
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct XImage
+ {
+ // ReSharper disable MemberCanBePrivate.Global
+ public int width;
+ public int height;
+ public int xoffset;
+ public int format;
+ public byte* data;
+ public int byte_order;
+ public int bitmap_unit;
+ public int bitmap_bit_order;
+ public int bitmap_pad;
+ public int depth;
+ public int bytes_per_line;
+ public int bits_per_pixel;
+ public uint red_mask;
+ public uint green_mask;
+ public uint blue_mask;
+ public nint obdata;
+ // ReSharper restore MemberCanBePrivate.Global
+ }
+}
+#endif
\ No newline at end of file
diff --git a/ScreenCapture.NET.X11/X11ScreenCapture.cs b/ScreenCapture.NET.X11/X11ScreenCapture.cs
new file mode 100644
index 0000000..562d9e1
--- /dev/null
+++ b/ScreenCapture.NET.X11/X11ScreenCapture.cs
@@ -0,0 +1,300 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using ScreenCapture.NET.Downscale;
+
+namespace ScreenCapture.NET;
+
+///
+/// Represents a ScreenCapture using DirectX 11 desktop duplicaton.
+/// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api
+///
+// ReSharper disable once InconsistentNaming
+public sealed class X11ScreenCapture : AbstractScreenCapture
+{
+ #region Properties & Fields
+
+ private readonly object _captureLock = new();
+
+ private nint _display;
+
+ private readonly Dictionary, ZoneTextures> _textures = new();
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to duplicate.
+ public X11ScreenCapture(Display display)
+ : base(display)
+ {
+ Restart();
+ }
+
+ #endregion
+
+ #region Methods
+
+ protected override bool PerformScreenCapture()
+ {
+ lock (_captureLock)
+ {
+ if (_display == 0)
+ {
+ Restart();
+ return false;
+ }
+
+ lock (CaptureZones)
+ lock (_textures)
+ foreach (CaptureZone captureZone in CaptureZones)
+ {
+ if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) break;
+ textures.Update();
+ }
+
+ return true;
+ }
+ }
+
+ protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer)
+ {
+ lock (_textures)
+ {
+ using IDisposable @lock = captureZone.Lock();
+ {
+ if (captureZone.DownscaleLevel == 0)
+ CopyZone(captureZone, buffer);
+ else
+ DownscaleZone(captureZone, buffer);
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void CopyZone(CaptureZone captureZone, in Span buffer)
+ {
+ if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) return;
+
+ ReadOnlySpan source = MemoryMarshal.Cast(textures.Data);
+ Span target = MemoryMarshal.Cast(buffer);
+
+ int width = captureZone.Width;
+ int height = captureZone.Height;
+ int sourceStride = textures.Image.bytes_per_line / ColorBGRA.ColorFormat.BytesPerPixel;
+
+ for (int y = 0; y < height; y++)
+ {
+ int sourceOffset = y * sourceStride;
+ int targetOffset = y * width;
+ source.Slice(sourceOffset, width).CopyTo(target.Slice(targetOffset, width));
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DownscaleZone(CaptureZone captureZone, in Span buffer)
+ {
+ if (!_textures.TryGetValue(captureZone, out ZoneTextures? textures)) return;
+
+ ReadOnlySpan source = textures.Data;
+ Span target = buffer;
+
+ int blockSize = captureZone.DownscaleLevel switch
+ {
+ 1 => 2,
+ 2 => 4,
+ 3 => 8,
+ 4 => 16,
+ 5 => 32,
+ 6 => 64,
+ 7 => 128,
+ 8 => 256,
+ _ => (int)Math.Pow(2, captureZone.DownscaleLevel),
+ };
+
+ int width = captureZone.Width;
+ int height = captureZone.Height;
+ int stride = captureZone.Stride;
+ int bpp = captureZone.ColorFormat.BytesPerPixel;
+ int sourceStride = textures.Image.bytes_per_line / ColorBGRA.ColorFormat.BytesPerPixel;
+
+ Span scaleBuffer = stackalloc byte[bpp];
+ for (int y = 0; y < height; y++)
+ for (int x = 0; x < width; x++)
+ {
+ AverageByteSampler.Sample(new SamplerInfo(x * blockSize, y * blockSize, blockSize, blockSize, sourceStride, bpp, source), scaleBuffer);
+
+ int targetOffset = (y * stride) + (x * bpp);
+
+ // DarthAffe 09.09.2023: Unroll as optimization since we know it's always 4 bpp - not ideal but it does quite a lot
+ target[targetOffset] = scaleBuffer[0];
+ target[targetOffset + 1] = scaleBuffer[1];
+ target[targetOffset + 2] = scaleBuffer[2];
+ target[targetOffset + 3] = scaleBuffer[3];
+ }
+ }
+
+ ///
+ public override CaptureZone RegisterCaptureZone(int x, int y, int width, int height, int downscaleLevel = 0)
+ {
+ CaptureZone captureZone = base.RegisterCaptureZone(x, y, width, height, downscaleLevel);
+
+ lock (_textures)
+ InitializeCaptureZone(captureZone);
+
+ return captureZone;
+ }
+
+ ///
+ public override bool UnregisterCaptureZone(CaptureZone captureZone)
+ {
+ if (!base.UnregisterCaptureZone(captureZone)) return false;
+
+ lock (_textures)
+ {
+ if (_textures.TryGetValue(captureZone, out ZoneTextures? textures))
+ {
+ textures.Dispose();
+ _textures.Remove(captureZone);
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ ///
+ public override void UpdateCaptureZone(CaptureZone captureZone, int? x = null, int? y = null, int? width = null, int? height = null, int? downscaleLevel = null)
+ {
+ base.UpdateCaptureZone(captureZone, x, y, width, height, downscaleLevel);
+
+ if ((width != null) || (height != null))
+ {
+ lock (_textures)
+ {
+ if (_textures.TryGetValue(captureZone, out ZoneTextures? textures))
+ {
+ textures.Dispose();
+ InitializeCaptureZone(captureZone);
+ }
+ }
+ }
+ }
+
+ private void InitializeCaptureZone(in CaptureZone captureZone)
+ => _textures[captureZone] = new ZoneTextures(_display, captureZone.Display.Index, captureZone.X, captureZone.Y, captureZone.UnscaledWidth, captureZone.UnscaledHeight);
+
+ ///
+ public override void Restart()
+ {
+ base.Restart();
+
+ lock (_captureLock)
+ {
+ DisposeDisplay();
+ try
+ {
+ _display = X11.XOpenDisplay(X11.DISPLAY_NAME);
+ }
+ catch
+ {
+ DisposeDisplay();
+ }
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ lock (_captureLock)
+ {
+ try
+ {
+ foreach ((CaptureZone _, ZoneTextures textures) in _textures)
+ textures.Dispose();
+
+ DisposeDisplay();
+ }
+ catch { /**/ }
+ }
+ }
+
+ private void DisposeDisplay()
+ {
+ if (_display == 0) return;
+
+ try { X11.XCloseDisplay(_display); } catch { /**/ }
+ }
+
+ #endregion
+
+ private sealed class ZoneTextures : IDisposable
+ {
+ #region Properties & Fields
+
+ private readonly int _screenNumber;
+ private readonly int _x;
+ private readonly int _y;
+ private readonly uint _width;
+ private readonly uint _height;
+ private readonly int _size;
+
+ private nint _display;
+ private nint _drawable;
+ public nint ImageHandle { get; private set; }
+ public X11.XImage Image { get; private set; }
+ public unsafe ReadOnlySpan Data => new(Image.data, _size);
+
+ #endregion
+
+ #region Constructors
+
+ public ZoneTextures(nint display, int screenNumber, int x, int y, int width, int height)
+ {
+ this._screenNumber = screenNumber;
+ this._x = x;
+ this._y = y;
+ this._width = (uint)width;
+ this._height = (uint)height;
+
+ _size = width * height * ColorBGRA.ColorFormat.BytesPerPixel;
+ Initialize(display);
+ }
+
+ #endregion
+
+ #region Methods
+
+ public void Initialize(nint display)
+ {
+ Dispose();
+
+ _display = display;
+
+ nint screen = X11.XScreenOfDisplay(_display, _screenNumber);
+ _drawable = X11.XRootWindowOfScreen(screen);
+ ImageHandle = X11.XGetImage(display, _drawable, _x, _y, _width, _height, X11.ALL_PLANES, X11.ZPIXMAP);
+ Image = Marshal.PtrToStructure(ImageHandle);
+
+ if (Image.bits_per_pixel != (ColorBGRA.ColorFormat.BytesPerPixel * 8)) throw new NotSupportedException("The X-Server is configured to a not supported pixel-format. Needs to be 32 bit per pixel BGR.");
+ }
+
+ public void Update() => X11.XGetSubImage(_display, _drawable, _x, _y, _width, _height, X11.ALL_PLANES, X11.ZPIXMAP, ImageHandle, 0, 0);
+
+ public void Dispose()
+ {
+ if (ImageHandle != 0)
+ try { X11.XDestroyImage(ImageHandle); } catch { /**/ }
+
+ Image = default;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/ScreenCapture.NET.X11/X11ScreenCaptureService.cs b/ScreenCapture.NET.X11/X11ScreenCaptureService.cs
new file mode 100644
index 0000000..c54f5d3
--- /dev/null
+++ b/ScreenCapture.NET.X11/X11ScreenCaptureService.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace ScreenCapture.NET;
+
+///
+/// Represents a using the .
+///
+public class X11ScreenCaptureService : IScreenCaptureService
+{
+ #region Properties & Fields
+
+ private readonly Dictionary _screenCaptures = new();
+
+ private bool _isDisposed;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public X11ScreenCaptureService()
+ { }
+
+ ~X11ScreenCaptureService() => Dispose();
+
+ #endregion
+
+ #region Methods
+
+ ///
+ public IEnumerable GetGraphicsCards()
+ {
+ if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
+
+ nint display = X11.XOpenDisplay(X11.DISPLAY_NAME);
+ try
+ {
+ string name = Marshal.PtrToStringAnsi(X11.XDisplayString(display)) ?? string.Empty;
+ yield return new GraphicsCard(0, name, 0, 0);
+ }
+ finally
+ {
+ X11.XCloseDisplay(display);
+ }
+ }
+
+ ///
+ public IEnumerable GetDisplays(GraphicsCard graphicsCard)
+ {
+ if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
+
+ nint display = X11.XOpenDisplay(X11.DISPLAY_NAME);
+ try
+ {
+ int screenCount = X11.XScreenCount(display);
+ for (int screenNumber = 0; screenNumber < screenCount; screenNumber++)
+ {
+ nint screen = X11.XScreenOfDisplay(display, screenNumber);
+ int screenWidth = X11.XWidthOfScreen(screen);
+ int screenHeight = X11.XHeightOfScreen(screen);
+
+ // DarthAffe 10.09.2023: Emulate DX-Displaynames for no real reason ¯\(°_o)/¯
+ yield return new Display(screenNumber, @$"\\.\DISPLAY{screenNumber + 1}", screenWidth, screenHeight, Rotation.None, graphicsCard);
+ }
+ }
+ finally
+ {
+ X11.XCloseDisplay(display);
+ }
+ }
+
+ ///
+ IScreenCapture IScreenCaptureService.GetScreenCapture(Display display) => GetScreenCapture(display);
+ public X11ScreenCapture GetScreenCapture(Display display)
+ {
+ if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
+
+ if (!_screenCaptures.TryGetValue(display, out X11ScreenCapture? screenCapture))
+ _screenCaptures.Add(display, screenCapture = new X11ScreenCapture(display));
+ return screenCapture;
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (_isDisposed) return;
+
+ foreach (X11ScreenCapture screenCapture in _screenCaptures.Values)
+ screenCapture.Dispose();
+ _screenCaptures.Clear();
+
+ GC.SuppressFinalize(this);
+
+ _isDisposed = true;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/ScreenCapture.NET.sln b/ScreenCapture.NET.sln
index e927c38..b91b878 100644
--- a/ScreenCapture.NET.sln
+++ b/ScreenCapture.NET.sln
@@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.Tests", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX9", "ScreenCapture.NET.DX9\ScreenCapture.NET.DX9.csproj", "{27EB5B17-2F83-43BA-A21F-06D93948B8BF}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.X11", "ScreenCapture.NET.X11\ScreenCapture.NET.X11.csproj", "{F81562C8-2035-4FB9-9547-C51F9D343BDF}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -35,6 +37,10 @@ Global
{27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs
index cffd4b4..f7551f1 100644
--- a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs
+++ b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs
@@ -51,15 +51,16 @@ public abstract class AbstractScreenCapture : IScreenCapture
result = false;
}
- foreach (CaptureZone captureZone in CaptureZones.Where(x => x.AutoUpdate || x.IsUpdateRequested))
- {
- try
+ lock (CaptureZones)
+ foreach (CaptureZone captureZone in CaptureZones.Where(x => x.AutoUpdate || x.IsUpdateRequested))
{
- PerformCaptureZoneUpdate(captureZone, captureZone.InternalBuffer);
- captureZone.SetUpdated();
+ try
+ {
+ PerformCaptureZoneUpdate(captureZone, captureZone.InternalBuffer);
+ captureZone.SetUpdated();
+ }
+ catch { /* */ }
}
- catch { /* */ }
- }
OnUpdated(result);
diff --git a/ScreenCapture.NET/Model/Colors/ColorABGR.cs b/ScreenCapture.NET/Model/Colors/ColorABGR.cs
new file mode 100644
index 0000000..511a80b
--- /dev/null
+++ b/ScreenCapture.NET/Model/Colors/ColorABGR.cs
@@ -0,0 +1,47 @@
+// ReSharper disable ConvertToAutoProperty
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace ScreenCapture.NET;
+
+[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct ColorABGR : IColor
+{
+ #region Properties & Fields
+
+ public static ColorFormat ColorFormat => ColorFormat.ABGR;
+
+ private readonly byte _a;
+ private readonly byte _b;
+ private readonly byte _g;
+ private readonly byte _r;
+
+ // ReSharper disable ConvertToAutoPropertyWhenPossible
+ public byte A => _a;
+ public byte B => _b;
+ public byte G => _g;
+ public byte R => _r;
+ // ReSharper restore ConvertToAutoPropertyWhenPossible
+
+ #endregion
+
+ #region Constructors
+
+ public ColorABGR(byte a, byte b, byte g, byte r)
+ {
+ this._a = a;
+ this._b = b;
+ this._g = g;
+ this._r = r;
+ }
+
+ #endregion
+
+ #region Methods
+
+ public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]";
+
+ #endregion
+}
\ No newline at end of file
diff --git a/ScreenCapture.NET/Model/Colors/ColorFormat.cs b/ScreenCapture.NET/Model/Colors/ColorFormat.cs
index eafc1e6..ff3b03a 100644
--- a/ScreenCapture.NET/Model/Colors/ColorFormat.cs
+++ b/ScreenCapture.NET/Model/Colors/ColorFormat.cs
@@ -5,9 +5,11 @@ public readonly struct ColorFormat
#region Instances
public static readonly ColorFormat BGRA = new(1, 4);
- public static readonly ColorFormat ARGB = new(2, 4);
- public static readonly ColorFormat RGB = new(3, 3);
- public static readonly ColorFormat BGR = new(4, 3);
+ public static readonly ColorFormat ABGR = new(2, 4);
+ public static readonly ColorFormat RGBA = new(3, 4);
+ public static readonly ColorFormat ARGB = new(4, 4);
+ public static readonly ColorFormat BGR = new(5, 3);
+ public static readonly ColorFormat RGB = new(6, 3);
#endregion
diff --git a/ScreenCapture.NET/Model/Colors/ColorRGBA.cs b/ScreenCapture.NET/Model/Colors/ColorRGBA.cs
new file mode 100644
index 0000000..0b01e87
--- /dev/null
+++ b/ScreenCapture.NET/Model/Colors/ColorRGBA.cs
@@ -0,0 +1,47 @@
+// ReSharper disable ConvertToAutoProperty
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace ScreenCapture.NET;
+
+[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct ColorRGBA : IColor
+{
+ #region Properties & Fields
+
+ public static ColorFormat ColorFormat => ColorFormat.RGBA;
+
+ private readonly byte _r;
+ private readonly byte _g;
+ private readonly byte _b;
+ private readonly byte _a;
+
+ // ReSharper disable ConvertToAutoPropertyWhenPossible
+ public byte R => _r;
+ public byte G => _g;
+ public byte B => _b;
+ public byte A => _a;
+ // ReSharper restore ConvertToAutoPropertyWhenPossible
+
+ #endregion
+
+ #region Constructors
+
+ public ColorRGBA(byte r, byte g, byte b, byte a)
+ {
+ this._r = r;
+ this._g = g;
+ this._b = b;
+ this._a = a;
+ }
+
+ #endregion
+
+ #region Methods
+
+ public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]";
+
+ #endregion
+}
\ No newline at end of file