diff --git a/.gitignore b/.gitignore
index dfcfd56..bdbf9be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -348,3 +348,6 @@ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
+# macOS
+.DS_Store
+**/.DS_Store
diff --git a/ScreenCapture.NET.SCK/SCKScreenCapture.cs b/ScreenCapture.NET.SCK/SCKScreenCapture.cs
new file mode 100644
index 0000000..aaf13f3
--- /dev/null
+++ b/ScreenCapture.NET.SCK/SCKScreenCapture.cs
@@ -0,0 +1,209 @@
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using HPPH;
+using ObjCRuntime;
+using ScreenCapture.NET.SCK;
+using ScreenCaptureKit;
+
+namespace ScreenCapture.NET;
+///
+/// Follow the architecture of this library created by author
+/// This class create a session require for screen content (in mac os)
+/// Capture screen for whatever interval (you have to implement a loop in calling class)
+/// Then store at a buffer with the size calculated by color space and screen resolution
+/// Everytime user register a new capture zone, a buffer corresponding for that zone is created
+///
+///
+
+public sealed class SCKScreenCapture : AbstractScreenCapture
+{
+ private SCDisplay _selectedDisplay;
+ private byte[]? _buffer;
+ private int _stride;
+ public double ScalingFactor => _scalingFactor;
+ private double _scalingFactor = 0.25d;
+ private SCContentFilter _filter;
+ private SCStreamConfiguration _streamConfig;
+ private SCStream _stream;
+ private ScreenCaptureDelegate _delegate;
+ public readonly object _captureLock = new();
+ public int BufferReceived => _delegate.BufferReceived;
+ private bool _isInitialized;
+ private int _blackFrameCounter;
+ private readonly object _lock = new();
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to capture.
+ /// The scale factor value to start new video stream at.
+ internal SCKScreenCapture(Display display, double scalingFactor)
+ : base(display)
+ {
+ _scalingFactor = scalingFactor;
+ Restart();
+ OnStreamComplete += StreamComplete;
+ }
+
+ private void StreamComplete(NSError error)
+ {
+ if (error != null)
+ {
+ _isInitialized = false;
+ _stream?.Dispose();
+ Thread.Sleep(1000);
+ }
+ else
+ {
+ _isInitialized = true;
+ }
+
+ }
+
+ public NativeHandle Handle { get; set; }
+
+ #endregion
+
+ ///
+ /// Perform a clean restart
+ ///
+ public override void Restart()
+ {
+ base.Restart();
+ lock (_captureLock)
+ {
+ try
+ {
+ SCShareableContent.GetShareableContent((SCShareableContent content, NSError error) =>
+ {
+ if (error != null)
+ {
+ //this is when user press denied, so we stop requesting
+ _isInitialized = true;
+ return;
+ }
+
+ //check if the display exist, feel silly enough because it actually happens when mac
+ // just wakeup from sleep
+ //the display is not available yet but the code is already running in background
+ if (content.Displays.Length <= Display.Index)
+ {
+ // Log.Error("Requested display not found" + Display.Index);
+ return;
+ }
+ _selectedDisplay = content.Displays[Display.Index];
+ _stride = (int)(Display.Width * _scalingFactor * ColorBGRA.ColorFormat.BytesPerPixel);
+ _buffer = new byte[(int)(Display.Height * _scalingFactor * _stride)];
+ var apps = content.Applications;
+ //config new sreen capture session
+ _filter = new SCContentFilter(_selectedDisplay, [], SCContentFilterOption.Exclude);
+ _streamConfig = new SCStreamConfiguration
+ {
+ Width = (nuint)(Display.Width * _scalingFactor),
+ Height = (nuint)(Display.Height * _scalingFactor),
+ MinimumFrameInterval = new CoreMedia.CMTime(1, 30), // 60 FPS
+ QueueDepth = 5,
+ PixelFormat = CoreVideo.CVPixelFormatType.CV32BGRA,
+ ScalesToFit = false,
+ SourceRect = new CGRect(0, 0, Display.Width, Display.Height),
+ ShowsCursor = false,
+ CaptureResolution = SCCaptureResolutionType.Best,
+ CapturesAudio = false,
+ StreamName = "SCKScreenCapture.NET"
+
+ };
+ //update registerd zones
+ _delegate = new ScreenCaptureDelegate(_buffer);
+ _delegate.StreamStopped += OnStreamStopped;
+ _stream = new SCStream(_filter, _streamConfig, _delegate);
+ var streamError = new NSError();
+ _stream.AddStreamOutput(_delegate, SCStreamOutputType.Screen, null, out streamError);
+ _stream.StartCapture(OnStreamComplete);
+
+ });
+
+ }
+ catch (Exception ex)
+ {
+
+ }
+ finally
+ {
+
+ }
+
+ }
+
+
+ }
+ private void OnStreamStopped()
+ {
+ _stream?.Dispose();
+ Thread.Sleep(1000);
+ _isInitialized = false;
+ }
+ private Action OnStreamComplete;
+ private bool ReadyToRestart()
+ {
+ lock (_lock)
+ {
+ return !_isInitialized;
+ }
+ }
+ ///
+ protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, Span buffer)
+ {
+ if (ReadyToRestart())
+ {
+ _blackFrameCounter++;
+ if (_blackFrameCounter > 600)
+ {
+ //10 secs has passed, try to restart
+ Restart();
+ _blackFrameCounter = 0;
+ }
+ }
+
+ if (_buffer == null) return;
+ using IDisposable @lock = captureZone.Lock();
+ {
+ if (captureZone.DownscaleLevel == 0)
+ CopyZone(captureZone, buffer);
+ else
+ DownscaleZone(captureZone, buffer);
+ }
+ }
+
+ protected override bool PerformScreenCapture()
+ {
+ bool result = true;
+ //this will be handled in PerformCaptureZoneUpdate();
+ return result;
+ }
+
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void CopyZone(CaptureZone captureZone, Span buffer)
+ {
+ RefImage.Wrap(_buffer, (int)(Display.Width * _scalingFactor), (int)(Display.Height * _scalingFactor), _stride)[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(_buffer, (int)(Display.Width * _scalingFactor), (int)(Display.Height * _scalingFactor), _stride)[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();
+ }
+
+}
diff --git a/ScreenCapture.NET.SCK/SCKScreenCaptureService.cs b/ScreenCapture.NET.SCK/SCKScreenCaptureService.cs
new file mode 100644
index 0000000..6d489c1
--- /dev/null
+++ b/ScreenCapture.NET.SCK/SCKScreenCaptureService.cs
@@ -0,0 +1,81 @@
+using System;
+
+namespace ScreenCapture.NET;
+
+public class SCKScreenCaptureService : IScreenCaptureService
+{
+ #region Properties & Fields
+
+
+ private bool _isDisposed;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SCKScreenCaptureService()
+ {
+
+ }
+
+ private readonly Dictionary _screenCaptures = new();
+ ~SCKScreenCaptureService() => Dispose();
+
+ #endregion
+
+ #region Methods
+
+ ///
+ public IEnumerable GetGraphicsCards()
+ {
+ if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
+
+ Dictionary graphicsCards = new();
+ // mac os doesn't need this
+ return graphicsCards.Values;
+ }
+
+ ///
+ public IEnumerable GetDisplays(GraphicsCard graphicsCard)
+
+ {
+ if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
+
+ for (int i = 0; i < NSScreen.Screens.Length; i++)
+ {
+ yield return new Display(i, NSScreen.Screens[i].LocalizedName, (int)NSScreen.Screens[i].Frame.Width, (int)NSScreen.Screens[i].Frame.Height, Rotation.None, graphicsCard);
+ }
+ }
+
+ ///
+ IScreenCapture IScreenCaptureService.GetScreenCapture(Display display) => GetScreenCapture(display);
+ public SCKScreenCapture GetScreenCapture(Display display)
+ {
+ if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
+
+ if (!_screenCaptures.TryGetValue(display, out SCKScreenCapture? screenCapture))
+ _screenCaptures.Add(display, screenCapture = new SCKScreenCapture(display, 0.25f));
+ return screenCapture;
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (_isDisposed) return;
+
+ foreach (SCKScreenCapture screenCapture in _screenCaptures.Values)
+ screenCapture.Dispose();
+ _screenCaptures.Clear();
+
+ //dispose sck
+
+ GC.SuppressFinalize(this);
+
+ _isDisposed = true;
+ }
+
+ #endregion
+}
diff --git a/ScreenCapture.NET.SCK/ScreenCapture.NET.SCK.csproj b/ScreenCapture.NET.SCK/ScreenCapture.NET.SCK.csproj
new file mode 100644
index 0000000..a115f79
--- /dev/null
+++ b/ScreenCapture.NET.SCK/ScreenCapture.NET.SCK.csproj
@@ -0,0 +1,63 @@
+
+
+
+ net8.0-macos
+ enable
+ enable
+ true
+
+ Kaitoukid93
+ Ambino
+ en-US
+ en-US
+ ScreenCapture.NET.SCK
+ ScreenCapture.NET.SCK
+ ScreenCapture.NET.SCK
+ ScreenCapture.NET.SCK
+ ScreenCapture.NET
+ Screen Capture Kit based Screen-Capturing
+ Screen Capture Kit based Screen-Capturing
+ Copyright © Le Hoang Anh 2025
+ Copyright © Le Hoang Anh 2025
+
+ https://github.com/DarthAffe/ScreenCapture.NET
+ LGPL-2.1-only
+ Github
+ https://github.com/DarthAffe/ScreenCapture.NET
+ True
+
+
+
+
+ 3.0.0
+ 3.0.0
+ 3.0.0
+
+ ..\bin\
+ true
+ True
+ True
+ snupkg
+
+
+
+ $(DefineConstants);TRACE;DEBUG
+ true
+ full
+ false
+
+
+
+ portable
+ true
+ $(NoWarn);CS1591;CS1572;CS1573
+ $(DefineConstants);RELEASE
+
+
+
+
+
+
+
+
+
diff --git a/ScreenCapture.NET.SCK/ScreenCaptureDelegate.cs b/ScreenCapture.NET.SCK/ScreenCaptureDelegate.cs
new file mode 100644
index 0000000..b8610b1
--- /dev/null
+++ b/ScreenCapture.NET.SCK/ScreenCaptureDelegate.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Runtime.InteropServices;
+using AudioToolbox;
+using CoreMedia;
+using CoreVideo;
+using ObjCRuntime;
+using ScreenCaptureKit;
+using Serilog;
+
+namespace ScreenCapture.NET.SCK;
+
+public class ScreenCaptureDelegate : NSObject, ISCStreamOutput, INativeObject, IDisposable, ISCStreamDelegate
+{
+ public ScreenCaptureDelegate(byte[] buffer)
+ {
+ _buffer = buffer;
+ }
+ public event Action StreamStopped;
+ public int BufferReceived { get; set; }
+ private AudioBuffer[] _audioBuffer;
+ private byte[] _buffer;
+ [Export("init")]
+ public ScreenCaptureDelegate()
+ {
+
+ }
+ [Foundation.Export("stream:didOutputSampleBuffer:ofType:")]
+ public unsafe void DidOutputSampleBuffer(SCStream stream, CMSampleBuffer sampleBuffer, SCStreamOutputType type)
+ {
+
+ try
+ {
+ if (type == SCStreamOutputType.Screen)
+ {
+ // Process video frame
+ using (sampleBuffer)
+ {
+ var imageBuffer = sampleBuffer.GetImageBuffer() as CVPixelBuffer;
+ if (imageBuffer != null)
+ {
+ using (imageBuffer)
+ {
+ imageBuffer.Lock(lockFlags: CVPixelBufferLock.ReadOnly);
+ IntPtr baseAddress = imageBuffer.BaseAddress;
+ int bytesPerRow = (int)imageBuffer.BytesPerRow;
+ int width = (int)imageBuffer.Width;
+ int height = (int)imageBuffer.Height;
+
+ Marshal.Copy(baseAddress, _buffer, 0, _buffer.Length);
+ imageBuffer.Unlock(CVPixelBufferLock.ReadOnly);
+ }
+ }
+ }
+
+ }
+ else if (type == SCStreamOutputType.Audio)
+ {
+ // Process audio buffer
+ var formatDescription = sampleBuffer.GetAudioFormatDescription();
+ var numSamples = (int)sampleBuffer.NumSamples;
+ _audioBuffer = new AudioBuffer[numSamples];
+ AudioBuffers outputBuffer = new AudioBuffers(numSamples); // Replace '1' with the appropriate number of buffers required
+ var error = sampleBuffer.CopyPCMDataIntoAudioBufferList(0, (int)sampleBuffer.NumSamples, outputBuffer);
+
+ if (error != CMSampleBufferError.None)
+ {
+ Log.Error(error.ToString());
+ }
+ else
+ {
+ ProcessAudioBuffer(outputBuffer);
+ outputBuffer.Dispose();
+ sampleBuffer.Dispose();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ }
+
+ }
+
+ private unsafe void ProcessAudioBuffer(AudioBuffers audioBufferList)
+ {
+ for (int i = 0; i < audioBufferList.Count; i++)
+ {
+
+ var audioBuffer = audioBufferList[i];
+ _audioBuffer[i] = audioBuffer;
+
+ // IntPtr audioData = audioBufferList[i].Data;
+ // int audioDataByteSize = audioBufferList[i].DataByteSize;
+
+ // // Convert audio data to a float array for processing
+ // float[] audioSamples = new float[audioDataByteSize / sizeof(float)];
+ // Marshal.Copy(audioData, audioSamples, 0, audioSamples.Length);
+
+ // // Perform further processing on the audioSamples array (e.g., FFT, visualization, etc.)
+ // Console.WriteLine($"Processed {audioSamples.Length} audio samples.");
+ }
+
+ }
+ [Export("stream:didStopWithError:")]
+ void DidStop(SCStream stream, NSError error)
+ {
+ if (error != null)
+ {
+ Log.Error("Error while capturing screen: " + error.ToString());
+ StreamStopped?.Invoke();
+ }
+ }
+}
diff --git a/ScreenCapture.NET.sln b/ScreenCapture.NET.sln
index b91b878..6901959 100644
--- a/ScreenCapture.NET.sln
+++ b/ScreenCapture.NET.sln
@@ -15,32 +15,90 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX9", "Sc
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.X11", "ScreenCapture.NET.X11\ScreenCapture.NET.X11.csproj", "{F81562C8-2035-4FB9-9547-C51F9D343BDF}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenCapture.NET.SCK", "ScreenCapture.NET.SCK\ScreenCapture.NET.SCK.csproj", "{102F2DAB-C4AC-40E3-A4ED-66851D98917F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
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}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {90596344-E012-4534-A933-3BD1B55469DC}.Debug|x64.Build.0 = Debug|Any CPU
+ {90596344-E012-4534-A933-3BD1B55469DC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {90596344-E012-4534-A933-3BD1B55469DC}.Debug|x86.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
+ {90596344-E012-4534-A933-3BD1B55469DC}.Release|x64.ActiveCfg = Release|Any CPU
+ {90596344-E012-4534-A933-3BD1B55469DC}.Release|x64.Build.0 = Release|Any CPU
+ {90596344-E012-4534-A933-3BD1B55469DC}.Release|x86.ActiveCfg = Release|Any CPU
+ {90596344-E012-4534-A933-3BD1B55469DC}.Release|x86.Build.0 = Release|Any CPU
{58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Debug|x64.Build.0 = Debug|Any CPU
+ {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Debug|x86.Build.0 = Debug|Any CPU
{58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Release|x64.ActiveCfg = Release|Any CPU
+ {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Release|x64.Build.0 = Release|Any CPU
+ {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Release|x86.ActiveCfg = Release|Any CPU
+ {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Release|x86.Build.0 = Release|Any CPU
{AA1829BB-EFA7-4BB8-8041-76374659A42B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA1829BB-EFA7-4BB8-8041-76374659A42B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Debug|x64.Build.0 = Debug|Any CPU
+ {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Debug|x86.Build.0 = Debug|Any CPU
{AA1829BB-EFA7-4BB8-8041-76374659A42B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA1829BB-EFA7-4BB8-8041-76374659A42B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Release|x64.ActiveCfg = Release|Any CPU
+ {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Release|x64.Build.0 = Release|Any CPU
+ {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Release|x86.ActiveCfg = Release|Any CPU
+ {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Release|x86.Build.0 = Release|Any CPU
{27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Debug|x64.Build.0 = Debug|Any CPU
+ {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Debug|x86.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
+ {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Release|x64.ActiveCfg = Release|Any CPU
+ {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Release|x64.Build.0 = Release|Any CPU
+ {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Release|x86.ActiveCfg = Release|Any CPU
+ {27EB5B17-2F83-43BA-A21F-06D93948B8BF}.Release|x86.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}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Debug|x64.Build.0 = Debug|Any CPU
+ {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Debug|x86.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
+ {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Release|x64.ActiveCfg = Release|Any CPU
+ {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Release|x64.Build.0 = Release|Any CPU
+ {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Release|x86.ActiveCfg = Release|Any CPU
+ {F81562C8-2035-4FB9-9547-C51F9D343BDF}.Release|x86.Build.0 = Release|Any CPU
+ {102F2DAB-C4AC-40E3-A4ED-66851D98917F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {102F2DAB-C4AC-40E3-A4ED-66851D98917F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {102F2DAB-C4AC-40E3-A4ED-66851D98917F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {102F2DAB-C4AC-40E3-A4ED-66851D98917F}.Debug|x64.Build.0 = Debug|Any CPU
+ {102F2DAB-C4AC-40E3-A4ED-66851D98917F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {102F2DAB-C4AC-40E3-A4ED-66851D98917F}.Debug|x86.Build.0 = Debug|Any CPU
+ {102F2DAB-C4AC-40E3-A4ED-66851D98917F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {102F2DAB-C4AC-40E3-A4ED-66851D98917F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {102F2DAB-C4AC-40E3-A4ED-66851D98917F}.Release|x64.ActiveCfg = Release|Any CPU
+ {102F2DAB-C4AC-40E3-A4ED-66851D98917F}.Release|x64.Build.0 = Release|Any CPU
+ {102F2DAB-C4AC-40E3-A4ED-66851D98917F}.Release|x86.ActiveCfg = Release|Any CPU
+ {102F2DAB-C4AC-40E3-A4ED-66851D98917F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Tests/.DS_Store b/Tests/.DS_Store
new file mode 100644
index 0000000..d290c16
Binary files /dev/null and b/Tests/.DS_Store differ
diff --git a/Tests/ScreenCapture.NET.Tests/.DS_Store b/Tests/ScreenCapture.NET.Tests/.DS_Store
new file mode 100644
index 0000000..acbe96b
Binary files /dev/null and b/Tests/ScreenCapture.NET.Tests/.DS_Store differ
diff --git a/docs/MacOs.md b/docs/MacOs.md
new file mode 100644
index 0000000..17af44d
--- /dev/null
+++ b/docs/MacOs.md
@@ -0,0 +1,29 @@
+# macOS Screen Capture (ScreenCaptureKit)
+
+This module provides **macOS screen capturing support** for `ScreenCapture.NET` using Apple's **ScreenCaptureKit** framework.
+It is intended to be used as a platform-specific backend similar to the existing **DX11** and **GDI** implementations.
+
+ScreenCaptureKit offers **high-performance GPU-based screen capture** on macOS and is available starting from **macOS 12.3+**.
+
+---
+
+## Requirements
+
+- macOS **12.3 or newer**
+- .NET **6.0+** (or the same version used by the main solution)
+- Apple **ScreenCaptureKit** framework available on the system
+
+---
+
+## Integration
+
+The recommended approach is to **select the capture backend at compile time** depending on the target platform.
+
+Example:
+
+```csharp
+#if MACOS
+_screenCaptureService = new SCKScreenCaptureService();
+#else
+_screenCaptureService = new DX11ScreenCaptureService();
+#endif