using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using HPPH; namespace ScreenCapture.NET; /// /// Represents a ScreenCapture using libX11. /// https://x.org/releases/current/doc/libX11/libX11/libX11.html#XGetImage /// // ReSharper disable once InconsistentNaming public sealed class X11ScreenCapture : AbstractScreenCapture { #region Properties & Fields private readonly object _captureLock = new(); private nint _display; private nint _drawable; private nint _imageHandle; private X11.XImage _image; private int _size; private unsafe ReadOnlySpan Data => new(_image.data, _size); #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// The to duplicate. internal X11ScreenCapture(Display display) : base(display) { Restart(); } #endregion #region Methods /// protected override bool PerformScreenCapture() { lock (_captureLock) { if ((_display == 0) || (_imageHandle == 0)) { Restart(); return false; } X11.XGetSubImage(_display, _drawable, 0, 0, (uint)Display.Width, (uint)Display.Height, X11.ALL_PLANES, X11.ZPIXMAP, _imageHandle, 0, 0); return true; } } /// protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, Span buffer) { using IDisposable @lock = captureZone.Lock(); { if (captureZone.DownscaleLevel == 0) CopyZone(captureZone, buffer); else DownscaleZone(captureZone, buffer); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CopyZone(CaptureZone captureZone, Span buffer) { RefImage.Wrap(Data, Display.Width, Display.Height, _image.bytes_per_line)[captureZone.X, captureZone.Y, captureZone.Width, captureZone.Height] .CopyTo(MemoryMarshal.Cast(buffer)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DownscaleZone(CaptureZone captureZone, Span buffer) { RefImage source = RefImage.Wrap(Data, Display.Width, Display.Height, _image.bytes_per_line)[captureZone.X, captureZone.Y, captureZone.UnscaledWidth, captureZone.UnscaledHeight]; Span target = MemoryMarshal.Cast(buffer); int blockSize = 1 << captureZone.DownscaleLevel; int width = captureZone.Width; int height = captureZone.Height; for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) target[(y * width) + x] = source[x * blockSize, y * blockSize, blockSize, blockSize].Average(); } /// public override void Restart() { base.Restart(); lock (_captureLock) { DisposeDisplay(); try { _display = X11.XOpenDisplay(X11.DISPLAY_NAME); nint screen = X11.XScreenOfDisplay(_display, Display.Index); _drawable = X11.XRootWindowOfScreen(screen); _imageHandle = X11.XGetImage(_display, _drawable, 0, 0, (uint)Display.Width, (uint)Display.Height, X11.ALL_PLANES, X11.ZPIXMAP); _image = Marshal.PtrToStructure(_imageHandle); _size = _image.bytes_per_line * _image.height; 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."); } catch { DisposeDisplay(); } } } /// protected override void Dispose(bool disposing) { base.Dispose(disposing); lock (_captureLock) { try { DisposeDisplay(); } catch { /**/ } } } private void DisposeDisplay() { if (_imageHandle != 0) try { X11.XDestroyImage(_imageHandle); } catch { /**/ } _image = default; if (_display != 0) try { X11.XCloseDisplay(_display); } catch { /**/ } } #endregion }