diff --git a/bindings/csharp/src/CNativeAPI/generated/CaptureSource.cs b/bindings/csharp/src/CNativeAPI/generated/CaptureSource.cs
new file mode 100644
index 00000000..519ac7fa
--- /dev/null
+++ b/bindings/csharp/src/CNativeAPI/generated/CaptureSource.cs
@@ -0,0 +1,28 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace CNativeAPI;
+
+public static partial class Interop
+{
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr native_capture_source_get_name(ulong self);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int native_capture_source_get_type(ulong self);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern ulong native_capture_source_from_display(ulong display);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern ulong native_capture_source_from_window(ulong window);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void native_capture_source_free(ulong handle);
+}
+
diff --git a/bindings/csharp/src/CNativeAPI/generated/ScreenCapturer.cs b/bindings/csharp/src/CNativeAPI/generated/ScreenCapturer.cs
new file mode 100644
index 00000000..c8abdfe6
--- /dev/null
+++ b/bindings/csharp/src/CNativeAPI/generated/ScreenCapturer.cs
@@ -0,0 +1,59 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace CNativeAPI;
+
+[StructLayout(LayoutKind.Sequential)]
+public struct native_capture_source_selection_options_t
+{
+ public byte allow_displays;
+ public byte allow_windows;
+ public ulong parent_window;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct native_screen_capture_options_t
+{
+ public byte include_cursor;
+ public byte crop_enabled;
+ public native_rectangle_t crop;
+}
+
+[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+public delegate void ScreenCapturerCaptureCallbackNativeCallback([MarshalAs(UnmanagedType.I1)] bool arg0, ulong arg1, ulong delivery, IntPtr userData);
+
+[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+public delegate void ScreenCapturerSelectSourceCallbackNativeCallback([MarshalAs(UnmanagedType.I1)] bool arg0, ulong arg1, ulong delivery, IntPtr userData);
+
+public static partial class Interop
+{
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool native_screen_capturer_is_direct_capture_supported(int type);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool native_screen_capturer_is_source_selection_supported();
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool native_screen_capturer_is_supported();
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void native_capture_source_selection_options_free(ref native_capture_source_selection_options_t value);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void native_screen_capture_options_free(ref native_screen_capture_options_t value);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void native_screen_capturer_capture(ulong source, native_screen_capture_options_t options, ScreenCapturerCaptureCallbackNativeCallback callback, IntPtr callback_user_data, ReleaseUserDataNativeCallback callback_release_user_data);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void native_screen_capturer_select_source(native_capture_source_selection_options_t options, ScreenCapturerSelectSourceCallbackNativeCallback callback, IntPtr callback_user_data, ReleaseUserDataNativeCallback callback_release_user_data);
+}
+
diff --git a/bindings/csharp/src/CNativeAPI/generated/ScreenRecorder.cs b/bindings/csharp/src/CNativeAPI/generated/ScreenRecorder.cs
new file mode 100644
index 00000000..bed4454a
--- /dev/null
+++ b/bindings/csharp/src/CNativeAPI/generated/ScreenRecorder.cs
@@ -0,0 +1,96 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace CNativeAPI;
+
+[StructLayout(LayoutKind.Sequential)]
+public struct native_screen_recording_options_t
+{
+ public native_screen_capture_options_t capture;
+ public double frame_rate;
+ public CULong video_bitrate;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct native_screen_recording_event_t
+{
+ public int type;
+ public DataUnion data;
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct DataUnion
+ {
+ [FieldOffset(0)] public CompletedData completed;
+ [FieldOffset(0)] public FailedData failed;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct CompletedData
+ {
+ public IntPtr file_path;
+ public double duration;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct FailedData
+ {
+ public IntPtr message;
+ }
+}
+
+[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+public delegate void ScreenRecordingEventNativeCallback(IntPtr evt, IntPtr userData);
+
+public static partial class Interop
+{
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool native_screen_recorder_cancel(ulong self);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool native_screen_recorder_is_active(ulong self);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool native_screen_recorder_is_supported();
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool native_screen_recorder_remove_listener(ulong self, ulong listenerId);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool native_screen_recorder_start(ulong self, ulong source, [MarshalAs(UnmanagedType.LPUTF8Str)] string? filePath, native_screen_recording_options_t options);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static extern bool native_screen_recorder_stop(ulong self);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr native_screen_recorder_get_file_path(ulong self);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double native_screen_recorder_get_duration(ulong self);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int native_screen_recorder_get_state(ulong self);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern ulong native_screen_recorder_add_listener(ulong self, ScreenRecordingEventNativeCallback callback, IntPtr userData, ReleaseUserDataNativeCallback releaseUserData);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern ulong native_screen_recorder_create();
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void native_screen_recorder_free(ulong handle);
+
+ [DllImport(Libraries.NativeApi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void native_screen_recording_options_free(ref native_screen_recording_options_t value);
+}
+
diff --git a/bindings/csharp/src/NativeAPI/CaptureSource.cs b/bindings/csharp/src/NativeAPI/CaptureSource.cs
new file mode 100644
index 00000000..aeafdbe8
--- /dev/null
+++ b/bindings/csharp/src/NativeAPI/CaptureSource.cs
@@ -0,0 +1,80 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using CNativeAPI;
+
+namespace NativeAPI;
+
+public enum CaptureSourceType
+{
+ None = 0,
+ Display = 1,
+ Window = 2,
+ Unknown = 3,
+}
+
+/// Owned handle to a native CaptureSource.
+public sealed partial class CaptureSource : IDisposable
+{
+ public ulong NativeHandle { get; private set; }
+ private readonly bool _ownsHandle;
+
+ public CaptureSource(ulong nativeHandle, bool ownsHandle = true)
+ {
+ NativeHandle = nativeHandle;
+ _ownsHandle = ownsHandle;
+ }
+
+ ~CaptureSource() => ReleaseHandle();
+
+ public void Dispose()
+ {
+ ReleaseHandle();
+ GC.SuppressFinalize(this);
+ }
+
+ private void ReleaseHandle()
+ {
+ if (_ownsHandle && NativeHandle != 0)
+ {
+ Interop.native_capture_source_free(NativeHandle);
+ NativeHandle = 0;
+ }
+ }
+
+ public static CaptureSource? FromDisplay(Display? display)
+ {
+ var rawResult = Interop.native_capture_source_from_display(display?.NativeHandle ?? 0);
+ return rawResult == 0 ? null : new CaptureSource(rawResult);
+ }
+
+ public static CaptureSource? FromWindow(Window? window)
+ {
+ var rawResult = Interop.native_capture_source_from_window(window?.NativeHandle ?? 0);
+ return rawResult == 0 ? null : new CaptureSource(rawResult);
+ }
+
+ public CaptureSourceType Type
+ {
+ get
+ {
+ var rawResult = Interop.native_capture_source_get_type(NativeHandle);
+ return (CaptureSourceType)rawResult;
+ }
+ }
+
+ public string? Name
+ {
+ get
+ {
+ var rawResult = Interop.native_capture_source_get_name(NativeHandle);
+ return Interop.ConsumeString(rawResult);
+ }
+ }
+
+}
+
diff --git a/bindings/csharp/src/NativeAPI/ScreenCapturer.cs b/bindings/csharp/src/NativeAPI/ScreenCapturer.cs
new file mode 100644
index 00000000..f51ff888
--- /dev/null
+++ b/bindings/csharp/src/NativeAPI/ScreenCapturer.cs
@@ -0,0 +1,113 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using CNativeAPI;
+
+namespace NativeAPI;
+
+public struct CaptureSourceSelectionOptions
+{
+ public bool AllowDisplays;
+ public bool AllowWindows;
+ public Window? ParentWindow;
+
+ public CaptureSourceSelectionOptions(bool allowDisplays, bool allowWindows, Window? parentWindow)
+ {
+ AllowDisplays = allowDisplays;
+ AllowWindows = allowWindows;
+ ParentWindow = parentWindow;
+ }
+
+ internal static CaptureSourceSelectionOptions FromRaw(in native_capture_source_selection_options_t raw)
+ {
+ return new CaptureSourceSelectionOptions(raw.allow_displays != 0, raw.allow_windows != 0, raw.parent_window == 0 ? null : new Window(Interop.native_handle_retain(raw.parent_window)));
+ }
+
+ internal native_capture_source_selection_options_t ToRaw()
+ {
+ var raw = new native_capture_source_selection_options_t();
+ raw.allow_displays = (byte)(AllowDisplays ? 1 : 0);
+ raw.allow_windows = (byte)(AllowWindows ? 1 : 0);
+ raw.parent_window = ParentWindow?.NativeHandle ?? 0;
+ return raw;
+ }
+
+ internal static void ReleaseRaw(ref native_capture_source_selection_options_t raw)
+ {
+ }
+}
+
+public struct ScreenCaptureOptions
+{
+ public bool IncludeCursor;
+ public bool CropEnabled;
+ public Rectangle Crop;
+
+ public ScreenCaptureOptions(bool includeCursor, bool cropEnabled, Rectangle crop)
+ {
+ IncludeCursor = includeCursor;
+ CropEnabled = cropEnabled;
+ Crop = crop;
+ }
+
+ internal static ScreenCaptureOptions FromRaw(in native_screen_capture_options_t raw)
+ {
+ return new ScreenCaptureOptions(raw.include_cursor != 0, raw.crop_enabled != 0, Rectangle.FromRaw(in raw.crop));
+ }
+
+ internal native_screen_capture_options_t ToRaw()
+ {
+ var raw = new native_screen_capture_options_t();
+ raw.include_cursor = (byte)(IncludeCursor ? 1 : 0);
+ raw.crop_enabled = (byte)(CropEnabled ? 1 : 0);
+ raw.crop = Crop.ToRaw();
+ return raw;
+ }
+
+ internal static void ReleaseRaw(ref native_screen_capture_options_t raw)
+ {
+ }
+}
+
+public static partial class ScreenCapturer
+{
+ public static bool IsSupported()
+ {
+ var rawResult = Interop.native_screen_capturer_is_supported();
+ return rawResult;
+ }
+
+ public static bool IsSourceSelectionSupported()
+ {
+ var rawResult = Interop.native_screen_capturer_is_source_selection_supported();
+ return rawResult;
+ }
+
+ public static bool IsDirectCaptureSupported(CaptureSourceType type)
+ {
+ var rawResult = Interop.native_screen_capturer_is_direct_capture_supported((int)type);
+ return rawResult;
+ }
+
+ public static void SelectSource(CaptureSourceSelectionOptions options, Action callback)
+ {
+ var rawOptions = options.ToRaw();
+ ScreenCapturerSelectSourceCallbackNativeCallback nativeCallback = (arg0, arg1, delivery, userData) => { try { if (!Interop.native_event_delivery_is_active(delivery)) return; callback(arg0, arg1 == 0 ? null : new CaptureSource(Interop.native_handle_retain(arg1))); } catch (Exception) { } finally { Interop.native_event_delivery_complete(delivery, true); } };
+ Interop.native_screen_capturer_select_source(rawOptions, nativeCallback, CallbackKeeper.Hold(nativeCallback), CallbackKeeper.Release);
+ CaptureSourceSelectionOptions.ReleaseRaw(ref rawOptions);
+ }
+
+ public static void Capture(CaptureSource? source, ScreenCaptureOptions options, Action callback)
+ {
+ var rawOptions = options.ToRaw();
+ ScreenCapturerCaptureCallbackNativeCallback nativeCallback = (arg0, arg1, delivery, userData) => { try { if (!Interop.native_event_delivery_is_active(delivery)) return; callback(arg0, arg1 == 0 ? null : new Image(Interop.native_handle_retain(arg1))); } catch (Exception) { } finally { Interop.native_event_delivery_complete(delivery, true); } };
+ Interop.native_screen_capturer_capture(source?.NativeHandle ?? 0, rawOptions, nativeCallback, CallbackKeeper.Hold(nativeCallback), CallbackKeeper.Release);
+ ScreenCaptureOptions.ReleaseRaw(ref rawOptions);
+ }
+
+}
+
diff --git a/bindings/csharp/src/NativeAPI/ScreenRecorder.cs b/bindings/csharp/src/NativeAPI/ScreenRecorder.cs
new file mode 100644
index 00000000..8b8ee5c4
--- /dev/null
+++ b/bindings/csharp/src/NativeAPI/ScreenRecorder.cs
@@ -0,0 +1,205 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using CNativeAPI;
+
+namespace NativeAPI;
+
+public enum ScreenRecordingState
+{
+ Idle = 0,
+ Starting = 1,
+ Recording = 2,
+ Stopping = 3,
+ Completed = 4,
+ Cancelled = 5,
+ Failed = 6,
+}
+
+public struct ScreenRecordingOptions
+{
+ public ScreenCaptureOptions Capture;
+ public double FrameRate;
+ public ulong VideoBitrate;
+
+ public ScreenRecordingOptions(ScreenCaptureOptions capture, double frameRate, ulong videoBitrate)
+ {
+ Capture = capture;
+ FrameRate = frameRate;
+ VideoBitrate = videoBitrate;
+ }
+
+ internal static ScreenRecordingOptions FromRaw(in native_screen_recording_options_t raw)
+ {
+ return new ScreenRecordingOptions(ScreenCaptureOptions.FromRaw(in raw.capture), raw.frame_rate, (ulong)raw.video_bitrate.Value);
+ }
+
+ internal native_screen_recording_options_t ToRaw()
+ {
+ var raw = new native_screen_recording_options_t();
+ raw.capture = Capture.ToRaw();
+ raw.frame_rate = FrameRate;
+ raw.video_bitrate = new CULong(checked((nuint)VideoBitrate));
+ return raw;
+ }
+
+ internal static void ReleaseRaw(ref native_screen_recording_options_t raw)
+ {
+ }
+}
+
+/// One ScreenRecordingEvent, in its concrete form.
+public abstract record ScreenRecordingEvent
+{
+ private ScreenRecordingEvent() { }
+
+ public sealed record Started : ScreenRecordingEvent;
+ public sealed record Completed(string? FilePath, double Duration) : ScreenRecordingEvent;
+ public sealed record Cancelled : ScreenRecordingEvent;
+ public sealed record Failed(string? Message) : ScreenRecordingEvent;
+
+ internal static ScreenRecordingEvent? FromRaw(in native_screen_recording_event_t raw)
+ {
+ switch (raw.type)
+ {
+ case 0: return new Started();
+ case 1: return new Completed(Marshal.PtrToStringUTF8(raw.data.completed.file_path), raw.data.completed.duration);
+ case 2: return new Cancelled();
+ case 3: return new Failed(Marshal.PtrToStringUTF8(raw.data.failed.message));
+ default: return null;
+ }
+ }
+}
+
+/// Owned handle to a native ScreenRecorder.
+public sealed partial class ScreenRecorder : IDisposable
+{
+ public ulong NativeHandle { get; private set; }
+ private readonly bool _ownsHandle;
+
+ public ScreenRecorder(ulong nativeHandle, bool ownsHandle = true)
+ {
+ NativeHandle = nativeHandle;
+ _ownsHandle = ownsHandle;
+ }
+
+ ~ScreenRecorder() => ReleaseHandle();
+
+ public void Dispose()
+ {
+ ReleaseHandle();
+ GC.SuppressFinalize(this);
+ }
+
+ private void ReleaseHandle()
+ {
+ if (_ownsHandle && NativeHandle != 0)
+ {
+ Interop.native_screen_recorder_free(NativeHandle);
+ NativeHandle = 0;
+ }
+ }
+
+ /// Creates a new ScreenRecorder; returns null if the native side failed.
+ public static ScreenRecorder? Create()
+ {
+ var handle = Interop.native_screen_recorder_create();
+ return handle == 0 ? null : new ScreenRecorder(handle);
+ }
+
+ public static bool IsSupported()
+ {
+ var rawResult = Interop.native_screen_recorder_is_supported();
+ return rawResult;
+ }
+
+ public bool Start(CaptureSource? source, string filePath, ScreenRecordingOptions options)
+ {
+ var rawOptions = options.ToRaw();
+ var rawResult = Interop.native_screen_recorder_start(NativeHandle, source?.NativeHandle ?? 0, filePath, rawOptions);
+ ScreenRecordingOptions.ReleaseRaw(ref rawOptions);
+ return rawResult;
+ }
+
+ public bool Stop()
+ {
+ var rawResult = Interop.native_screen_recorder_stop(NativeHandle);
+ return rawResult;
+ }
+
+ public bool Cancel()
+ {
+ var rawResult = Interop.native_screen_recorder_cancel(NativeHandle);
+ return rawResult;
+ }
+
+ public ScreenRecordingState State
+ {
+ get
+ {
+ var rawResult = Interop.native_screen_recorder_get_state(NativeHandle);
+ return (ScreenRecordingState)rawResult;
+ }
+ }
+
+ public bool IsActive
+ {
+ get
+ {
+ var rawResult = Interop.native_screen_recorder_is_active(NativeHandle);
+ return rawResult;
+ }
+ }
+
+ public double Duration
+ {
+ get
+ {
+ var rawResult = Interop.native_screen_recorder_get_duration(NativeHandle);
+ return rawResult;
+ }
+ }
+
+ public string? FilePath
+ {
+ get
+ {
+ var rawResult = Interop.native_screen_recorder_get_file_path(NativeHandle);
+ return Interop.ConsumeString(rawResult);
+ }
+ }
+
+ /// Registers for every ScreenRecordingEvent this ScreenRecorder emits.
+ ///
+ /// The delegate is kept alive until the listener is removed or its emitter
+ /// destroyed; the core releases it then.
+ ///
+ public ulong AddListener(Action callback)
+ {
+ ScreenRecordingEventNativeCallback native = (evt, userData) =>
+ {
+ if (evt == IntPtr.Zero)
+ {
+ return;
+ }
+ var value = ScreenRecordingEvent.FromRaw(Marshal.PtrToStructure(evt));
+ if (value is not null)
+ {
+ callback(value);
+ }
+ };
+ return Interop.native_screen_recorder_add_listener(NativeHandle, native, CallbackKeeper.Hold(native), CallbackKeeper.Release);
+ }
+
+ /// Unregisters a listener. Returns false if unknown.
+ public bool RemoveListener(ulong listenerId)
+ {
+ return Interop.native_screen_recorder_remove_listener(NativeHandle, listenerId);
+ }
+
+}
+
diff --git a/bindings/dart/cnativeapi/ffigen.yaml b/bindings/dart/cnativeapi/ffigen.yaml
index 7990fe65..c83035b7 100644
--- a/bindings/dart/cnativeapi/ffigen.yaml
+++ b/bindings/dart/cnativeapi/ffigen.yaml
@@ -13,6 +13,7 @@ headers:
- "../../../core/src/capi/accessibility_manager_c.h"
- "../../../core/src/capi/app_info_c.h"
- "../../../core/src/capi/application_c.h"
+ - "../../../core/src/capi/capture_source_c.h"
- "../../../core/src/capi/clipboard_c.h"
- "../../../core/src/capi/color_c.h"
- "../../../core/src/capi/common_c.h"
@@ -35,6 +36,8 @@ headers:
- "../../../core/src/capi/placement_c.h"
- "../../../core/src/capi/positioning_strategy_c.h"
- "../../../core/src/capi/preferences_c.h"
+ - "../../../core/src/capi/screen_capturer_c.h"
+ - "../../../core/src/capi/screen_recorder_c.h"
- "../../../core/src/capi/secure_storage_c.h"
- "../../../core/src/capi/shortcut_c.h"
- "../../../core/src/capi/shortcut_manager_c.h"
@@ -53,6 +56,7 @@ headers:
- "../../../core/src/capi/accessibility_manager_c.h"
- "../../../core/src/capi/app_info_c.h"
- "../../../core/src/capi/application_c.h"
+ - "../../../core/src/capi/capture_source_c.h"
- "../../../core/src/capi/clipboard_c.h"
- "../../../core/src/capi/color_c.h"
- "../../../core/src/capi/common_c.h"
@@ -75,6 +79,8 @@ headers:
- "../../../core/src/capi/placement_c.h"
- "../../../core/src/capi/positioning_strategy_c.h"
- "../../../core/src/capi/preferences_c.h"
+ - "../../../core/src/capi/screen_capturer_c.h"
+ - "../../../core/src/capi/screen_recorder_c.h"
- "../../../core/src/capi/secure_storage_c.h"
- "../../../core/src/capi/shortcut_c.h"
- "../../../core/src/capi/shortcut_manager_c.h"
diff --git a/bindings/dart/cnativeapi/hook/build.dart b/bindings/dart/cnativeapi/hook/build.dart
index fa43a17c..412889d4 100644
--- a/bindings/dart/cnativeapi/hook/build.dart
+++ b/bindings/dart/cnativeapi/hook/build.dart
@@ -59,6 +59,7 @@ void main(List args) async {
switch (targetOS) {
case OS.macOS:
flags.addAll(_objectiveCpp);
+ flags.addAll(['-weak_framework', 'ScreenCaptureKit']);
frameworks = [
'Cocoa',
'QuartzCore',
@@ -66,6 +67,10 @@ void main(List args) async {
'CoreGraphics',
'ApplicationServices',
'ServiceManagement',
+ 'AVFoundation',
+ 'CoreMedia',
+ 'CoreVideo',
+ 'VideoToolbox',
];
case OS.iOS:
flags.addAll(_objectiveCpp);
@@ -99,6 +104,14 @@ void main(List args) async {
'advapi32',
'version',
'gdi32',
+ 'd3d11',
+ 'dxgi',
+ 'windowsapp',
+ 'runtimeobject',
+ 'mfplat',
+ 'mfreadwrite',
+ 'mfuuid',
+ 'windowscodecs',
]);
}
diff --git a/bindings/dart/cnativeapi/lib/src/bindings_generated.dart b/bindings/dart/cnativeapi/lib/src/bindings_generated.dart
index e84afe46..6a1e097a 100644
--- a/bindings/dart/cnativeapi/lib/src/bindings_generated.dart
+++ b/bindings/dart/cnativeapi/lib/src/bindings_generated.dart
@@ -174,6 +174,46 @@ external ffi.Pointer native_button_get_text(int button);
@ffi.Native)>()
external void native_button_set_text(int button, ffi.Pointer text);
+/// Releases the caller's reference. Safe to call with an invalid or
+/// already-released handle.
+@ffi.Native()
+external void native_capture_source_free(int capture_source);
+
+/// Caller owns the returned handle; release it with native_capture_source_free().
+@ffi.Native()
+external int native_capture_source_from_display(int display);
+
+/// Caller owns the returned handle; release it with native_capture_source_free().
+@ffi.Native()
+external int native_capture_source_from_window(int window);
+
+/// Caller owns the returned string; free it with free_c_str().
+@ffi.Native Function(native_capture_source_t)>()
+external ffi.Pointer native_capture_source_get_name(
+ int capture_source,
+);
+
+@ffi.Native(
+ symbol: 'native_capture_source_get_type',
+)
+external int _native_capture_source_get_type(int capture_source);
+
+native_capture_source_type_t native_capture_source_get_type(
+ Dartnative_capture_source_t capture_source,
+) {
+ return native_capture_source_type_t.fromValue(
+ _native_capture_source_get_type(capture_source),
+ );
+}
+
+/// Frees everything the struct owns.
+@ffi.Native<
+ ffi.Void Function(ffi.Pointer)
+>()
+external void native_capture_source_selection_options_free(
+ ffi.Pointer value,
+);
+
/// Registers @p callback for every ClipboardEvent this Clipboard emits.
/// @return the listener id, or NATIVE_INVALID_LISTENER_ID on failure.
@ffi.Native<
@@ -309,6 +349,10 @@ external bool native_clipboard_write_image(int image);
@ffi.Native)>()
external bool native_clipboard_write_text(ffi.Pointer text);
+/// Frees everything the struct owns.
+@ffi.Native)>()
+external void native_color_free(ffi.Pointer value);
+
@ffi.Native)>()
external native_color_t native_color_from_hex(ffi.Pointer hex);
@@ -589,7 +633,7 @@ external void native_drag_source_set_text(
ffi.Pointer text,
);
-@ffi.Native()
+@ffi.Native()
external bool native_drag_source_start_dragging(int drag_source, int window);
/// Registers @p callback for every DropTargetEvent this DropTarget emits.
@@ -628,7 +672,7 @@ external int native_drop_target_add_listener_async(
);
/// Creates a DropTarget instance; release it with native_drop_target_free().
-@ffi.Native()
+@ffi.Native()
external int native_drop_target_create(int window);
/// Releases the caller's reference. Safe to call with an invalid or
@@ -683,6 +727,10 @@ void native_drop_target_set_drop_operation(
@ffi.Native()
external native_edge_insets_t native_edge_insets_all(double value);
+/// Frees everything the struct owns.
+@ffi.Native)>()
+external void native_edge_insets_free(ffi.Pointer value);
+
@ffi.Native()
external native_edge_insets_t native_edge_insets_symmetric(
double vertical,
@@ -812,7 +860,7 @@ void native_file_dialog_set_modality(
return _native_file_dialog_set_modality(file_dialog, modality.value);
}
-@ffi.Native()
+@ffi.Native()
external bool native_file_dialog_set_parent_window(int file_dialog, int window);
@ffi.Native)>()
@@ -1582,7 +1630,7 @@ void native_message_dialog_set_modality(
return _native_message_dialog_set_modality(message_dialog, modality.value);
}
-@ffi.Native()
+@ffi.Native()
external bool native_message_dialog_set_parent_window(
int message_dialog,
int window,
@@ -1666,6 +1714,10 @@ external bool native_notification_manager_show(
@ffi.Native()
external void native_notification_manager_shutdown();
+/// Frees everything the struct owns.
+@ffi.Native)>()
+external void native_point_free(ffi.Pointer value);
+
/// Caller owns the returned handle; release it with native_positioning_strategy_free().
@ffi.Native()
external int native_positioning_strategy_absolute(native_point_t point);
@@ -1799,6 +1851,171 @@ external bool native_preferences_set(
ffi.Pointer value,
);
+/// Frees everything the struct owns.
+@ffi.Native)>()
+external void native_rectangle_free(ffi.Pointer value);
+
+/// Frees everything the struct owns.
+@ffi.Native)>()
+external void native_screen_capture_options_free(
+ ffi.Pointer value,
+);
+
+@ffi.Native<
+ ffi.Void Function(
+ native_capture_source_t$1,
+ native_screen_capture_options_t,
+ native_screen_capturer_capture_callback_t,
+ ffi.Pointer,
+ native_release_user_data_t,
+ )
+>()
+external void native_screen_capturer_capture(
+ int source,
+ native_screen_capture_options_t options,
+ native_screen_capturer_capture_callback_t callback,
+ ffi.Pointer callback_user_data,
+ native_release_user_data_t callback_release_user_data,
+);
+
+@ffi.Native(
+ symbol: 'native_screen_capturer_is_direct_capture_supported',
+)
+external bool _native_screen_capturer_is_direct_capture_supported(int type);
+
+bool native_screen_capturer_is_direct_capture_supported(
+ native_capture_source_type_t type,
+) {
+ return _native_screen_capturer_is_direct_capture_supported(type.value);
+}
+
+@ffi.Native()
+external bool native_screen_capturer_is_source_selection_supported();
+
+@ffi.Native()
+external bool native_screen_capturer_is_supported();
+
+@ffi.Native<
+ ffi.Void Function(
+ native_capture_source_selection_options_t,
+ native_screen_capturer_select_source_callback_t,
+ ffi.Pointer,
+ native_release_user_data_t,
+ )
+>()
+external void native_screen_capturer_select_source(
+ native_capture_source_selection_options_t options,
+ native_screen_capturer_select_source_callback_t callback,
+ ffi.Pointer callback_user_data,
+ native_release_user_data_t callback_release_user_data,
+);
+
+/// Registers @p callback for every ScreenRecordingEvent this ScreenRecorder emits.
+/// @return the listener id, or NATIVE_INVALID_LISTENER_ID on failure.
+@ffi.Native<
+ native_listener_id_t Function(
+ native_screen_recorder_t,
+ native_screen_recording_event_callback_t,
+ ffi.Pointer,
+ native_release_user_data_t,
+ )
+>()
+external int native_screen_recorder_add_listener(
+ int screen_recorder,
+ native_screen_recording_event_callback_t callback,
+ ffi.Pointer user_data,
+ native_release_user_data_t release_user_data,
+);
+
+/// Registers an asynchronous callback. Its event, borrowed handles and user_data
+/// remain valid until event_delivery_complete is called, including after removal.
+/// Every delivered payload must be acknowledged. Check is_active before invoking a queued callback.
+@ffi.Native<
+ native_listener_id_t Function(
+ native_screen_recorder_t,
+ native_screen_recording_event_callback_t_async,
+ ffi.Pointer,
+ native_release_user_data_t,
+ )
+>()
+external int native_screen_recorder_add_listener_async(
+ int screen_recorder,
+ native_screen_recording_event_callback_t_async callback,
+ ffi.Pointer user_data,
+ native_release_user_data_t release_user_data,
+);
+
+@ffi.Native()
+external bool native_screen_recorder_cancel(int screen_recorder);
+
+/// Creates a ScreenRecorder instance; release it with native_screen_recorder_free().
+@ffi.Native()
+external int native_screen_recorder_create();
+
+/// Releases the caller's reference. Safe to call with an invalid or
+/// already-released handle.
+@ffi.Native()
+external void native_screen_recorder_free(int screen_recorder);
+
+@ffi.Native()
+external double native_screen_recorder_get_duration(int screen_recorder);
+
+/// Caller owns the returned string; free it with free_c_str().
+@ffi.Native Function(native_screen_recorder_t)>()
+external ffi.Pointer native_screen_recorder_get_file_path(
+ int screen_recorder,
+);
+
+@ffi.Native(
+ symbol: 'native_screen_recorder_get_state',
+)
+external int _native_screen_recorder_get_state(int screen_recorder);
+
+native_screen_recording_state_t native_screen_recorder_get_state(
+ Dartnative_screen_recorder_t screen_recorder,
+) {
+ return native_screen_recording_state_t.fromValue(
+ _native_screen_recorder_get_state(screen_recorder),
+ );
+}
+
+@ffi.Native()
+external bool native_screen_recorder_is_active(int screen_recorder);
+
+@ffi.Native()
+external bool native_screen_recorder_is_supported();
+
+/// Unregisters a listener. Returns false if unknown.
+@ffi.Native()
+external bool native_screen_recorder_remove_listener(
+ int screen_recorder,
+ int listener_id,
+);
+
+@ffi.Native<
+ ffi.Bool Function(
+ native_screen_recorder_t,
+ native_capture_source_t$2,
+ ffi.Pointer,
+ native_screen_recording_options_t,
+ )
+>()
+external bool native_screen_recorder_start(
+ int screen_recorder,
+ int source,
+ ffi.Pointer file_path,
+ native_screen_recording_options_t options,
+);
+
+@ffi.Native()
+external bool native_screen_recorder_stop(int screen_recorder);
+
+/// Frees everything the struct owns.
+@ffi.Native)>()
+external void native_screen_recording_options_free(
+ ffi.Pointer value,
+);
+
@ffi.Native()
external bool native_secure_storage_clear(int secure_storage);
@@ -2099,6 +2316,10 @@ external void native_shortcut_set_description(
@ffi.Native()
external void native_shortcut_set_enabled(int shortcut, bool enabled);
+/// Frees everything the struct owns.
+@ffi.Native)>()
+external void native_size_free(ffi.Pointer value);
+
/// Free a string list allocated by to_c_string_list
/// @param list The list to free (can be nullptr)
@ffi.Native)>()
@@ -2286,7 +2507,7 @@ native_context_menu_trigger_t native_tray_icon_get_context_menu_trigger(
}
/// Caller owns the returned handle; release it with native_image_free().
-@ffi.Native()
+@ffi.Native()
external int native_tray_icon_get_icon(int tray_icon);
@ffi.Native(
@@ -2372,7 +2593,7 @@ void native_tray_icon_set_context_menu_trigger(
return _native_tray_icon_set_context_menu_trigger(tray_icon, trigger.value);
}
-@ffi.Native()
+@ffi.Native()
external void native_tray_icon_set_icon(int tray_icon, int image);
@ffi.Native(
@@ -2821,7 +3042,7 @@ external bool native_window_drag_session_remove_listener(
@ffi.Native<
ffi.Bool Function(
native_window_drag_session_t,
- native_window_t$5,
+ native_window_t$7,
native_point_t,
)
>()
@@ -3114,18 +3335,18 @@ external bool native_window_manager_call_original_hide(int id);
external bool native_window_manager_call_original_show(int id);
/// Caller owns the returned handle; release it with native_window_free().
-@ffi.Native()
+@ffi.Native()
external int native_window_manager_get(int id);
@ffi.Native()
external native_window_list_t native_window_manager_get_all();
/// Caller owns the returned handle; release it with native_window_free().
-@ffi.Native()
+@ffi.Native()
external int native_window_manager_get_current();
/// Caller owns the returned handle; release it with native_window_free().
-@ffi.Native()
+@ffi.Native()
external int native_window_manager_get_window_at_point(
native_point_t point,
int excluded_window_id,
@@ -3478,6 +3699,8 @@ external void native_window_unmaximize(int window);
const int NATIVE_INVALID_BUTTON = 0;
+const int NATIVE_INVALID_CAPTURE_SOURCE = 0;
+
const int NATIVE_INVALID_DISPLAY = 0;
const int NATIVE_INVALID_DRAG_SOURCE = 0;
@@ -3512,6 +3735,8 @@ const int NATIVE_INVALID_POSITIONING_STRATEGY = 0;
const int NATIVE_INVALID_PREFERENCES = 0;
+const int NATIVE_INVALID_SCREEN_RECORDER = 0;
+
const int NATIVE_INVALID_SECURE_STORAGE = 0;
const int NATIVE_INVALID_SHORTCUT = 0;
@@ -3560,32 +3785,36 @@ final class UnnamedStruct$10 extends ffi.Struct {
}
final class UnnamedStruct$11 extends ffi.Struct {
- external ffi.Pointer error_message;
+ external ffi.Pointer file_path;
+
+ @ffi.Double()
+ external double duration;
static ffi.Pointer $allocate(
ffi.Allocator $allocator, {
- required ffi.Pointer error_message,
- }) => $allocator()..ref.error_message = error_message;
+ required ffi.Pointer file_path,
+ required double duration,
+ }) => $allocator()
+ ..ref.file_path = file_path
+ ..ref.duration = duration;
}
final class UnnamedStruct$12 extends ffi.Struct {
- @native_tray_icon_id_t()
- external int tray_icon_id;
+ external ffi.Pointer message;
static ffi.Pointer $allocate(
ffi.Allocator $allocator, {
- required int tray_icon_id,
- }) => $allocator()..ref.tray_icon_id = tray_icon_id;
+ required ffi.Pointer message,
+ }) => $allocator()..ref.message = message;
}
final class UnnamedStruct$13 extends ffi.Struct {
- @native_tray_icon_id_t()
- external int tray_icon_id;
+ external ffi.Pointer error_message;
static ffi.Pointer $allocate(
ffi.Allocator $allocator, {
- required int tray_icon_id,
- }) => $allocator()..ref.tray_icon_id = tray_icon_id;
+ required ffi.Pointer error_message,
+ }) => $allocator()..ref.error_message = error_message;
}
final class UnnamedStruct$14 extends ffi.Struct {
@@ -3599,44 +3828,40 @@ final class UnnamedStruct$14 extends ffi.Struct {
}
final class UnnamedStruct$15 extends ffi.Struct {
- external ffi.Pointer text;
+ @native_tray_icon_id_t()
+ external int tray_icon_id;
static ffi.Pointer $allocate(
ffi.Allocator $allocator, {
- required ffi.Pointer text,
- }) => $allocator()..ref.text = text;
+ required int tray_icon_id,
+ }) => $allocator()..ref.tray_icon_id = tray_icon_id;
}
final class UnnamedStruct$16 extends ffi.Struct {
- external native_point_t new_position;
+ @native_tray_icon_id_t()
+ external int tray_icon_id;
+
+ static ffi.Pointer $allocate(
+ ffi.Allocator $allocator, {
+ required int tray_icon_id,
+ }) => $allocator()..ref.tray_icon_id = tray_icon_id;
}
final class UnnamedStruct$17 extends ffi.Struct {
- external native_size_t new_size;
+ external ffi.Pointer text;
+
+ static ffi.Pointer $allocate(
+ ffi.Allocator $allocator, {
+ required ffi.Pointer text,
+ }) => $allocator()..ref.text = text;
}
final class UnnamedStruct$18 extends ffi.Struct {
- @native_event_request_t()
- external int request;
-
- static ffi.Pointer $allocate(
- ffi.Allocator $allocator, {
- required int request,
- }) => $allocator()..ref.request = request;
+ external native_point_t new_position;
}
final class UnnamedStruct$19 extends ffi.Struct {
- @ffi.UnsignedInt()
- external int propertyAsInt;
-
- native_window_property_t get property =>
- native_window_property_t.fromValue(propertyAsInt);
- set property(native_window_property_t value) => propertyAsInt = value.value;
-
- static ffi.Pointer $allocate(
- ffi.Allocator $allocator, {
- required native_window_property_t property,
- }) => $allocator()..ref.property = property;
+ external native_size_t new_size;
}
final class UnnamedStruct$2 extends ffi.Struct {
@@ -3654,6 +3879,30 @@ final class UnnamedStruct$2 extends ffi.Struct {
}
final class UnnamedStruct$20 extends ffi.Struct {
+ @native_event_request_t()
+ external int request;
+
+ static ffi.Pointer $allocate(
+ ffi.Allocator $allocator, {
+ required int request,
+ }) => $allocator()..ref.request = request;
+}
+
+final class UnnamedStruct$21 extends ffi.Struct {
+ @ffi.UnsignedInt()
+ external int propertyAsInt;
+
+ native_window_property_t get property =>
+ native_window_property_t.fromValue(propertyAsInt);
+ set property(native_window_property_t value) => propertyAsInt = value.value;
+
+ static ffi.Pointer $allocate(
+ ffi.Allocator $allocator, {
+ required native_window_property_t property,
+ }) => $allocator()..ref.property = property;
+}
+
+final class UnnamedStruct$22 extends ffi.Struct {
@ffi.UnsignedInt()
external int occlusion_stateAsInt;
@@ -3662,10 +3911,10 @@ final class UnnamedStruct$20 extends ffi.Struct {
set occlusion_state(native_window_occlusion_state_t value) =>
occlusion_stateAsInt = value.value;
- static ffi.Pointer $allocate(
+ static ffi.Pointer $allocate(
ffi.Allocator $allocator, {
required native_window_occlusion_state_t occlusion_state,
- }) => $allocator()..ref.occlusion_state = occlusion_state;
+ }) => $allocator()..ref.occlusion_state = occlusion_state;
}
final class UnnamedStruct$3 extends ffi.Struct {
@@ -3744,6 +3993,18 @@ final class UnnamedUnion$1 extends ffi.Union {
external UnnamedStruct$2 ended;
}
+final class UnnamedUnion$10 extends ffi.Union {
+ external UnnamedStruct$18 moved;
+
+ external UnnamedStruct$19 resized;
+
+ external UnnamedStruct$20 close_requested;
+
+ external UnnamedStruct$21 property_changed;
+
+ external UnnamedStruct$22 occlusion_changed;
+}
+
final class UnnamedUnion$2 extends ffi.Union {
external UnnamedStruct$3 dropped;
}
@@ -3769,31 +4030,25 @@ final class UnnamedUnion$5 extends ffi.Union {
}
final class UnnamedUnion$6 extends ffi.Union {
- external UnnamedStruct$11 registration_failed;
+ external UnnamedStruct$11 completed;
+
+ external UnnamedStruct$12 failed;
}
final class UnnamedUnion$7 extends ffi.Union {
- external UnnamedStruct$12 clicked;
-
- external UnnamedStruct$13 right_clicked;
-
- external UnnamedStruct$14 double_clicked;
+ external UnnamedStruct$13 registration_failed;
}
final class UnnamedUnion$8 extends ffi.Union {
- external UnnamedStruct$15 text_field_changed;
-}
-
-final class UnnamedUnion$9 extends ffi.Union {
- external UnnamedStruct$16 moved;
+ external UnnamedStruct$14 clicked;
- external UnnamedStruct$17 resized;
+ external UnnamedStruct$15 right_clicked;
- external UnnamedStruct$18 close_requested;
-
- external UnnamedStruct$19 property_changed;
+ external UnnamedStruct$16 double_clicked;
+}
- external UnnamedStruct$20 occlusion_changed;
+final class UnnamedUnion$9 extends ffi.Union {
+ external UnnamedStruct$17 text_field_changed;
}
typedef native_application_event_callback_t =
@@ -3890,6 +4145,60 @@ enum native_brightness_t {
typedef native_button_t = ffi.Uint64;
typedef Dartnative_button_t = int;
+final class native_capture_source_selection_options_t extends ffi.Struct {
+ @ffi.Bool()
+ external bool allow_displays;
+
+ @ffi.Bool()
+ external bool allow_windows;
+
+ @native_window_t$2()
+ external int parent_window;
+
+ static ffi.Pointer $allocate(
+ ffi.Allocator $allocator, {
+ required bool allow_displays,
+ required bool allow_windows,
+ required int parent_window,
+ }) => $allocator()
+ ..ref.allow_displays = allow_displays
+ ..ref.allow_windows = allow_windows
+ ..ref.parent_window = parent_window;
+}
+
+/// Opaque CaptureSource handle.
+///
+/// A generational index into the library's handle table, NOT a pointer:
+/// never dereference it, and compare it against NATIVE_INVALID_CAPTURE_SOURCE rather than NULL.
+/// Releasing a handle invalidates it; later calls fail safely instead of
+/// touching freed memory.
+typedef native_capture_source_t = ffi.Uint64;
+typedef Dartnative_capture_source_t = int;
+typedef native_capture_source_t$1 = ffi.Uint64;
+typedef Dartnative_capture_source_t$1 = int;
+typedef native_capture_source_t$2 = ffi.Uint64;
+typedef Dartnative_capture_source_t$2 = int;
+
+enum native_capture_source_type_t {
+ NATIVE_CAPTURE_SOURCE_TYPE_NONE(0),
+ NATIVE_CAPTURE_SOURCE_TYPE_DISPLAY(1),
+ NATIVE_CAPTURE_SOURCE_TYPE_WINDOW(2),
+ NATIVE_CAPTURE_SOURCE_TYPE_UNKNOWN(3);
+
+ final int value;
+ const native_capture_source_type_t(this.value);
+
+ static native_capture_source_type_t fromValue(int value) => switch (value) {
+ 0 => NATIVE_CAPTURE_SOURCE_TYPE_NONE,
+ 1 => NATIVE_CAPTURE_SOURCE_TYPE_DISPLAY,
+ 2 => NATIVE_CAPTURE_SOURCE_TYPE_WINDOW,
+ 3 => NATIVE_CAPTURE_SOURCE_TYPE_UNKNOWN,
+ _ => throw ArgumentError(
+ 'Unknown value for native_capture_source_type_t: $value',
+ ),
+ };
+}
+
final class native_clipboard_data_t extends ffi.Struct {
external ffi.Pointer text;
@@ -4228,6 +4537,8 @@ typedef native_display_t = ffi.Uint64;
typedef Dartnative_display_t = int;
typedef native_display_t$1 = ffi.Uint64;
typedef Dartnative_display_t$1 = int;
+typedef native_display_t$2 = ffi.Uint64;
+typedef Dartnative_display_t$2 = int;
enum native_drag_operation_t {
NATIVE_DRAG_OPERATION_NONE(0),
@@ -4517,6 +4828,8 @@ typedef native_image_t$4 = ffi.Uint64;
typedef Dartnative_image_t$4 = int;
typedef native_image_t$5 = ffi.Uint64;
typedef Dartnative_image_t$5 = int;
+typedef native_image_t$6 = ffi.Uint64;
+typedef Dartnative_image_t$6 = int;
/// Opaque ImageView handle.
///
@@ -5076,6 +5389,168 @@ enum native_resize_edge_t {
};
}
+final class native_screen_capture_options_t extends ffi.Struct {
+ @ffi.Bool()
+ external bool include_cursor;
+
+ @ffi.Bool()
+ external bool crop_enabled;
+
+ external native_rectangle_t crop;
+}
+
+/// Arguments are borrowed until event_delivery_complete(delivery, ...), which is required exactly once.
+typedef native_screen_capturer_capture_callback_t =
+ ffi.Pointer<
+ ffi.NativeFunction
+ >;
+typedef native_screen_capturer_capture_callback_tFunction = ffi.Void Function(
+ ffi.Bool arg0,
+ native_image_t$3 arg1,
+ native_event_delivery_t delivery,
+ ffi.Pointer user_data,
+);
+typedef Dartnative_screen_capturer_capture_callback_tFunction = void Function(
+ bool arg0,
+ Dartnative_image_t$3 arg1,
+ Dartnative_event_delivery_t delivery,
+ ffi.Pointer user_data,
+);
+
+/// Arguments are borrowed until event_delivery_complete(delivery, ...), which is required exactly once.
+typedef native_screen_capturer_select_source_callback_t =
+ ffi.Pointer<
+ ffi.NativeFunction<
+ native_screen_capturer_select_source_callback_tFunction
+ >
+ >;
+typedef native_screen_capturer_select_source_callback_tFunction =
+ ffi.Void Function(
+ ffi.Bool arg0,
+ native_capture_source_t$1 arg1,
+ native_event_delivery_t delivery,
+ ffi.Pointer user_data,
+ );
+typedef Dartnative_screen_capturer_select_source_callback_tFunction =
+ void Function(
+ bool arg0,
+ Dartnative_capture_source_t$1 arg1,
+ Dartnative_event_delivery_t delivery,
+ ffi.Pointer user_data,
+ );
+
+/// Opaque ScreenRecorder handle.
+///
+/// A generational index into the library's handle table, NOT a pointer:
+/// never dereference it, and compare it against NATIVE_INVALID_SCREEN_RECORDER rather than NULL.
+/// Releasing a handle invalidates it; later calls fail safely instead of
+/// touching freed memory.
+typedef native_screen_recorder_t = ffi.Uint64;
+typedef Dartnative_screen_recorder_t = int;
+typedef native_screen_recording_event_callback_t =
+ ffi.Pointer<
+ ffi.NativeFunction
+ >;
+typedef native_screen_recording_event_callback_tFunction = ffi.Void Function(
+ ffi.Pointer event,
+ ffi.Pointer user_data,
+);
+typedef Dartnative_screen_recording_event_callback_tFunction = void Function(
+ ffi.Pointer event,
+ ffi.Pointer user_data,
+);
+typedef native_screen_recording_event_callback_t_async =
+ ffi.Pointer<
+ ffi.NativeFunction
+ >;
+typedef native_screen_recording_event_callback_t_asyncFunction =
+ ffi.Void Function(
+ ffi.Pointer event,
+ native_event_delivery_t delivery,
+ ffi.Pointer user_data,
+ );
+typedef Dartnative_screen_recording_event_callback_t_asyncFunction =
+ void Function(
+ ffi.Pointer event,
+ Dartnative_event_delivery_t delivery,
+ ffi.Pointer user_data,
+ );
+
+/// One ScreenRecordingEvent, tagged by its concrete type.
+///
+/// Synchronous callbacks borrow this payload until they return. Async callbacks
+/// borrow it until event_delivery_complete. Copy anything needed after that.
+final class native_screen_recording_event_t extends ffi.Struct {
+ @ffi.UnsignedInt()
+ external int typeAsInt;
+
+ native_screen_recording_event_type_t get type =>
+ native_screen_recording_event_type_t.fromValue(typeAsInt);
+ set type(native_screen_recording_event_type_t value) =>
+ typeAsInt = value.value;
+
+ external UnnamedUnion$6 data;
+}
+
+/// Which concrete ScreenRecordingEvent arrived.
+enum native_screen_recording_event_type_t {
+ NATIVE_SCREEN_RECORDING_EVENT_TYPE_STARTED(0),
+ NATIVE_SCREEN_RECORDING_EVENT_TYPE_COMPLETED(1),
+ NATIVE_SCREEN_RECORDING_EVENT_TYPE_CANCELLED(2),
+ NATIVE_SCREEN_RECORDING_EVENT_TYPE_FAILED(3);
+
+ final int value;
+ const native_screen_recording_event_type_t(this.value);
+
+ static native_screen_recording_event_type_t fromValue(int value) =>
+ switch (value) {
+ 0 => NATIVE_SCREEN_RECORDING_EVENT_TYPE_STARTED,
+ 1 => NATIVE_SCREEN_RECORDING_EVENT_TYPE_COMPLETED,
+ 2 => NATIVE_SCREEN_RECORDING_EVENT_TYPE_CANCELLED,
+ 3 => NATIVE_SCREEN_RECORDING_EVENT_TYPE_FAILED,
+ _ => throw ArgumentError(
+ 'Unknown value for native_screen_recording_event_type_t: $value',
+ ),
+ };
+}
+
+final class native_screen_recording_options_t extends ffi.Struct {
+ external native_screen_capture_options_t capture;
+
+ @ffi.Double()
+ external double frame_rate;
+
+ @ffi.UnsignedLong()
+ external int video_bitrate;
+}
+
+enum native_screen_recording_state_t {
+ NATIVE_SCREEN_RECORDING_STATE_IDLE(0),
+ NATIVE_SCREEN_RECORDING_STATE_STARTING(1),
+ NATIVE_SCREEN_RECORDING_STATE_RECORDING(2),
+ NATIVE_SCREEN_RECORDING_STATE_STOPPING(3),
+ NATIVE_SCREEN_RECORDING_STATE_COMPLETED(4),
+ NATIVE_SCREEN_RECORDING_STATE_CANCELLED(5),
+ NATIVE_SCREEN_RECORDING_STATE_FAILED(6);
+
+ final int value;
+ const native_screen_recording_state_t(this.value);
+
+ static native_screen_recording_state_t fromValue(int value) =>
+ switch (value) {
+ 0 => NATIVE_SCREEN_RECORDING_STATE_IDLE,
+ 1 => NATIVE_SCREEN_RECORDING_STATE_STARTING,
+ 2 => NATIVE_SCREEN_RECORDING_STATE_RECORDING,
+ 3 => NATIVE_SCREEN_RECORDING_STATE_STOPPING,
+ 4 => NATIVE_SCREEN_RECORDING_STATE_COMPLETED,
+ 5 => NATIVE_SCREEN_RECORDING_STATE_CANCELLED,
+ 6 => NATIVE_SCREEN_RECORDING_STATE_FAILED,
+ _ => throw ArgumentError(
+ 'Unknown value for native_screen_recording_state_t: $value',
+ ),
+ };
+}
+
/// Opaque SecureStorage handle.
///
/// A generational index into the library's handle table, NOT a pointer:
@@ -5136,7 +5611,7 @@ final class native_shortcut_event_t extends ffi.Struct {
external ffi.Pointer accelerator;
- external UnnamedUnion$6 data;
+ external UnnamedUnion$7 data;
}
/// Which concrete ShortcutEvent arrived.
@@ -5416,7 +5891,7 @@ final class native_tray_icon_event_t extends ffi.Struct {
native_tray_icon_event_type_t.fromValue(typeAsInt);
set type(native_tray_icon_event_type_t value) => typeAsInt = value.value;
- external UnnamedUnion$7 data;
+ external UnnamedUnion$8 data;
}
/// Which concrete TrayIconEvent arrived.
@@ -5605,7 +6080,7 @@ final class native_view_event_t extends ffi.Struct {
@native_view_id_t()
external int view_id;
- external UnnamedUnion$8 data;
+ external UnnamedUnion$9 data;
}
/// Which concrete ViewEvent arrived.
@@ -5840,7 +6315,7 @@ final class native_window_event_t extends ffi.Struct {
@native_window_id_t()
external int window_id;
- external UnnamedUnion$9 data;
+ external UnnamedUnion$10 data;
}
/// Which concrete WindowEvent arrived.
@@ -6009,6 +6484,8 @@ typedef native_window_t = ffi.Uint64;
typedef Dartnative_window_t = int;
typedef native_window_t$1 = ffi.Uint64;
typedef Dartnative_window_t$1 = int;
+typedef native_window_t$10 = ffi.Uint64;
+typedef Dartnative_window_t$10 = int;
typedef native_window_t$2 = ffi.Uint64;
typedef Dartnative_window_t$2 = int;
typedef native_window_t$3 = ffi.Uint64;
@@ -6023,3 +6500,5 @@ typedef native_window_t$7 = ffi.Uint64;
typedef Dartnative_window_t$7 = int;
typedef native_window_t$8 = ffi.Uint64;
typedef Dartnative_window_t$8 = int;
+typedef native_window_t$9 = ffi.Uint64;
+typedef Dartnative_window_t$9 = int;
diff --git a/bindings/dart/nativeapi/CHANGELOG.md b/bindings/dart/nativeapi/CHANGELOG.md
index 78c6e2d1..1aae895b 100644
--- a/bindings/dart/nativeapi/CHANGELOG.md
+++ b/bindings/dart/nativeapi/CHANGELOG.md
@@ -1,5 +1,7 @@
## Unreleased
+- Add `ScreenCapturer`, `CaptureSource` and `ScreenRecorder` for screenshots and silent H.264 MP4 recordings on macOS 14+ and Windows 10 version 2004+. Include system source selection, physical-pixel crops, cursor options and recording lifecycle events, plus a Flutter desktop example. Other platforms report unsupported.
+
- Windows: window observation now runs on the thread that owns the windows, not the thread that first called the API (#18). `WindowManager` installs its WinEvent hooks and window subclassing there, and deferred main-thread work is delivered there, so a Flutter app keeping the default thread policy (UI isolate on its own thread) still gets focus, lifetime, occlusion and geometry events. Out-of-context WinEvents and window subclassing only work on the window-owning thread.
- Windows: an elevated application's tray icon now receives clicks (#110). The shell posts the icon's callback message from a lower integrity level, so User Interface Privilege Isolation dropped it at an elevated window; the icon's host window now allows that message. An elevated application also lets the theme-change broadcasts through now, so template icons follow a theme change.
diff --git a/bindings/dart/nativeapi/lib/src/capture_source.dart b/bindings/dart/nativeapi/lib/src/capture_source.dart
new file mode 100644
index 00000000..c096c6ec
--- /dev/null
+++ b/bindings/dart/nativeapi/lib/src/capture_source.dart
@@ -0,0 +1,92 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+
+// ignore_for_file: unused_import, unnecessary_import
+
+import 'dart:ffi' as ffi;
+
+import 'package:cnativeapi/cnativeapi.dart' as c;
+import 'package:ffi/ffi.dart' as pkg_ffi;
+
+import 'display.dart';
+import 'window.dart';
+
+enum CaptureSourceType {
+ none(0),
+ display(1),
+ window(2),
+ unknown(3);
+
+ const CaptureSourceType(this.value);
+ final int value;
+
+ static CaptureSourceType fromValue(int value) => switch (value) {
+ 0 => CaptureSourceType.none,
+ 1 => CaptureSourceType.display,
+ 2 => CaptureSourceType.window,
+ 3 => CaptureSourceType.unknown,
+ _ => CaptureSourceType.none,
+ };
+
+ c.native_capture_source_type_t get raw =>
+ c.native_capture_source_type_t.fromValue(value);
+}
+
+class CaptureSource implements ffi.Finalizable {
+ /// Adopts a handle returned by the C API and releases it when this
+ /// object becomes unreachable.
+ CaptureSource.fromHandle(this.nativeHandle) {
+ _finalizer.attach(
+ this,
+ ffi.Pointer.fromAddress(nativeHandle),
+ detach: this,
+ );
+ }
+
+ /// Wraps a handle owned elsewhere; releasing it stays the owner's job.
+ CaptureSource.borrowed(this.nativeHandle);
+
+ /// The underlying handle-table entry.
+ final int nativeHandle;
+
+ static final _finalizer = ffi.NativeFinalizer(
+ ffi.Native.addressOf<
+ ffi.NativeFunction)>
+ >(c.native_handle_finalize),
+ );
+
+ /// Releases the handle now instead of at collection.
+ void dispose() {
+ _finalizer.detach(this);
+ c.native_capture_source_free(nativeHandle);
+ }
+
+ static CaptureSource? fromDisplay(Display? display) {
+ final handle = c.native_capture_source_from_display(
+ display?.nativeHandle ?? 0,
+ );
+ if (handle == 0) return null;
+ return CaptureSource.fromHandle(handle);
+ }
+
+ static CaptureSource? fromWindow(Window? window) {
+ final handle = c.native_capture_source_from_window(
+ window?.nativeHandle ?? 0,
+ );
+ if (handle == 0) return null;
+ return CaptureSource.fromHandle(handle);
+ }
+
+ CaptureSourceType get type {
+ final raw = c.native_capture_source_get_type(nativeHandle);
+ return CaptureSourceType.fromValue(raw.value);
+ }
+
+ String? get name {
+ final resultPointer = c.native_capture_source_get_name(nativeHandle);
+ if (resultPointer == ffi.nullptr) return null;
+ final result = resultPointer.cast().toDartString();
+ c.free_c_str(resultPointer);
+ return result;
+ }
+}
diff --git a/bindings/dart/nativeapi/lib/src/generated.dart b/bindings/dart/nativeapi/lib/src/generated.dart
index ccccad72..c56c2291 100644
--- a/bindings/dart/nativeapi/lib/src/generated.dart
+++ b/bindings/dart/nativeapi/lib/src/generated.dart
@@ -4,6 +4,7 @@
export 'accessibility_manager.dart';
export 'app_info.dart';
export 'application.dart';
+export 'capture_source.dart';
export 'clipboard.dart';
export 'device_info.dart';
export 'dialog.dart';
@@ -25,6 +26,8 @@ export 'notification_manager.dart';
export 'placement.dart';
export 'positioning_strategy.dart';
export 'preferences.dart';
+export 'screen_capturer.dart';
+export 'screen_recorder.dart';
export 'secure_storage.dart';
export 'shortcut.dart';
export 'shortcut_manager.dart';
diff --git a/bindings/dart/nativeapi/lib/src/screen_capturer.dart b/bindings/dart/nativeapi/lib/src/screen_capturer.dart
new file mode 100644
index 00000000..8f36e919
--- /dev/null
+++ b/bindings/dart/nativeapi/lib/src/screen_capturer.dart
@@ -0,0 +1,220 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+
+// ignore_for_file: unused_import, unnecessary_import
+
+import 'dart:ffi' as ffi;
+
+import 'package:cnativeapi/cnativeapi.dart' as c;
+import 'package:ffi/ffi.dart' as pkg_ffi;
+
+import 'capture_source.dart';
+import 'foundation/geometry.dart';
+import 'image.dart';
+import 'window.dart';
+
+import 'callbacks.dart';
+
+class CaptureSourceSelectionOptions {
+ const CaptureSourceSelectionOptions({
+ required this.allowDisplays,
+ required this.allowWindows,
+ this.parentWindow,
+ });
+
+ final bool allowDisplays;
+ final bool allowWindows;
+ final Window? parentWindow;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ (other is CaptureSourceSelectionOptions &&
+ other.allowDisplays == allowDisplays &&
+ other.allowWindows == allowWindows &&
+ other.parentWindow == parentWindow);
+
+ @override
+ int get hashCode => Object.hash(allowDisplays, allowWindows, parentWindow);
+
+ @override
+ String toString() =>
+ 'CaptureSourceSelectionOptions(allowDisplays: $allowDisplays, allowWindows: $allowWindows, parentWindow: $parentWindow)';
+
+ factory CaptureSourceSelectionOptions.fromNative(
+ c.native_capture_source_selection_options_t raw,
+ ) => CaptureSourceSelectionOptions(
+ allowDisplays: raw.allow_displays,
+ allowWindows: raw.allow_windows,
+ parentWindow: raw.parent_window == 0
+ ? null
+ : Window.fromHandle(c.native_handle_retain(raw.parent_window)),
+ );
+
+ /// Allocates the C form; free it with [freeNative].
+ ffi.Pointer allocNative() {
+ final pointer = pkg_ffi
+ .calloc();
+ pointer.ref.allow_displays = allowDisplays;
+ pointer.ref.allow_windows = allowWindows;
+ pointer.ref.parent_window = parentWindow?.nativeHandle ?? 0;
+ return pointer;
+ }
+
+ static void freeNative(
+ ffi.Pointer pointer,
+ ) {
+ pkg_ffi.calloc.free(pointer);
+ }
+}
+
+class ScreenCaptureOptions {
+ const ScreenCaptureOptions({
+ required this.includeCursor,
+ required this.cropEnabled,
+ required this.crop,
+ });
+
+ final bool includeCursor;
+ final bool cropEnabled;
+ final Rectangle crop;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ (other is ScreenCaptureOptions &&
+ other.includeCursor == includeCursor &&
+ other.cropEnabled == cropEnabled &&
+ other.crop == crop);
+
+ @override
+ int get hashCode => Object.hash(includeCursor, cropEnabled, crop);
+
+ @override
+ String toString() =>
+ 'ScreenCaptureOptions(includeCursor: $includeCursor, cropEnabled: $cropEnabled, crop: $crop)';
+
+ factory ScreenCaptureOptions.fromNative(
+ c.native_screen_capture_options_t raw,
+ ) => ScreenCaptureOptions(
+ includeCursor: raw.include_cursor,
+ cropEnabled: raw.crop_enabled,
+ crop: Rectangle.fromNative(raw.crop),
+ );
+
+ /// Allocates the C form; free it with [freeNative].
+ ffi.Pointer allocNative() {
+ final pointer = pkg_ffi.calloc();
+ pointer.ref.include_cursor = includeCursor;
+ pointer.ref.crop_enabled = cropEnabled;
+ final cropPointer = crop.allocNative();
+ pointer.ref.crop = cropPointer.ref;
+ pkg_ffi.calloc.free(cropPointer);
+ return pointer;
+ }
+
+ static void freeNative(
+ ffi.Pointer pointer,
+ ) {
+ pkg_ffi.calloc.free(pointer);
+ }
+}
+
+class ScreenCapturer {
+ ScreenCapturer._();
+ static bool isSupported() {
+ return c.native_screen_capturer_is_supported();
+ }
+
+ static bool isSourceSelectionSupported() {
+ return c.native_screen_capturer_is_source_selection_supported();
+ }
+
+ static bool isDirectCaptureSupported(CaptureSourceType type) {
+ return c.native_screen_capturer_is_direct_capture_supported(type.raw);
+ }
+
+ static void selectSource(
+ CaptureSourceSelectionOptions options,
+ void Function(bool, CaptureSource?) callback,
+ ) {
+ final optionsPointer = options.allocNative();
+ final callbackCallable =
+ ffi.NativeCallable<
+ ffi.Void Function(
+ ffi.Bool,
+ ffi.Uint64,
+ ffi.Uint64,
+ ffi.Pointer,
+ )
+ >.listener((
+ bool arg0,
+ int arg1,
+ int delivery,
+ ffi.Pointer _,
+ ) {
+ try {
+ if (c.native_event_delivery_is_active(delivery)) {
+ callback(
+ arg0,
+ arg1 == 0
+ ? null
+ : CaptureSource.fromHandle(c.native_handle_retain(arg1)),
+ );
+ }
+ } finally {
+ c.native_event_delivery_complete(delivery, true);
+ }
+ });
+ c.native_screen_capturer_select_source(
+ optionsPointer.ref,
+ callbackCallable.nativeFunction,
+ NativeCallbacks.userData(callbackCallable),
+ NativeCallbacks.release,
+ );
+ CaptureSourceSelectionOptions.freeNative(optionsPointer);
+ }
+
+ static void capture(
+ CaptureSource? source,
+ ScreenCaptureOptions options,
+ void Function(bool, Image?) callback,
+ ) {
+ final optionsPointer = options.allocNative();
+ final callbackCallable =
+ ffi.NativeCallable<
+ ffi.Void Function(
+ ffi.Bool,
+ ffi.Uint64,
+ ffi.Uint64,
+ ffi.Pointer,
+ )
+ >.listener((
+ bool arg0,
+ int arg1,
+ int delivery,
+ ffi.Pointer _,
+ ) {
+ try {
+ if (c.native_event_delivery_is_active(delivery)) {
+ callback(
+ arg0,
+ arg1 == 0
+ ? null
+ : Image.fromHandle(c.native_handle_retain(arg1)),
+ );
+ }
+ } finally {
+ c.native_event_delivery_complete(delivery, true);
+ }
+ });
+ c.native_screen_capturer_capture(
+ source?.nativeHandle ?? 0,
+ optionsPointer.ref,
+ callbackCallable.nativeFunction,
+ NativeCallbacks.userData(callbackCallable),
+ NativeCallbacks.release,
+ );
+ ScreenCaptureOptions.freeNative(optionsPointer);
+ }
+}
diff --git a/bindings/dart/nativeapi/lib/src/screen_recorder.dart b/bindings/dart/nativeapi/lib/src/screen_recorder.dart
new file mode 100644
index 00000000..1fdefefd
--- /dev/null
+++ b/bindings/dart/nativeapi/lib/src/screen_recorder.dart
@@ -0,0 +1,300 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+
+// ignore_for_file: unused_import, unnecessary_import
+
+import 'dart:async';
+import 'dart:ffi' as ffi;
+
+import 'package:cnativeapi/cnativeapi.dart' as c;
+import 'package:ffi/ffi.dart' as pkg_ffi;
+
+import 'capture_source.dart';
+import 'screen_capturer.dart';
+
+import 'support.dart';
+
+import 'callbacks.dart';
+
+enum ScreenRecordingState {
+ idle(0),
+ starting(1),
+ recording(2),
+ stopping(3),
+ completed(4),
+ cancelled(5),
+ failed(6);
+
+ const ScreenRecordingState(this.value);
+ final int value;
+
+ static ScreenRecordingState fromValue(int value) => switch (value) {
+ 0 => ScreenRecordingState.idle,
+ 1 => ScreenRecordingState.starting,
+ 2 => ScreenRecordingState.recording,
+ 3 => ScreenRecordingState.stopping,
+ 4 => ScreenRecordingState.completed,
+ 5 => ScreenRecordingState.cancelled,
+ 6 => ScreenRecordingState.failed,
+ _ => ScreenRecordingState.idle,
+ };
+
+ c.native_screen_recording_state_t get raw =>
+ c.native_screen_recording_state_t.fromValue(value);
+}
+
+class ScreenRecordingOptions {
+ const ScreenRecordingOptions({
+ required this.capture,
+ required this.frameRate,
+ required this.videoBitrate,
+ });
+
+ final ScreenCaptureOptions capture;
+ final double frameRate;
+ final int videoBitrate;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ (other is ScreenRecordingOptions &&
+ other.capture == capture &&
+ other.frameRate == frameRate &&
+ other.videoBitrate == videoBitrate);
+
+ @override
+ int get hashCode => Object.hash(capture, frameRate, videoBitrate);
+
+ @override
+ String toString() =>
+ 'ScreenRecordingOptions(capture: $capture, frameRate: $frameRate, videoBitrate: $videoBitrate)';
+
+ factory ScreenRecordingOptions.fromNative(
+ c.native_screen_recording_options_t raw,
+ ) => ScreenRecordingOptions(
+ capture: ScreenCaptureOptions.fromNative(raw.capture),
+ frameRate: raw.frame_rate,
+ videoBitrate: raw.video_bitrate,
+ );
+
+ /// Allocates the C form; free it with [freeNative].
+ ffi.Pointer allocNative() {
+ final pointer = pkg_ffi.calloc();
+ final capturePointer = capture.allocNative();
+ pointer.ref.capture = capturePointer.ref;
+ pkg_ffi.calloc.free(capturePointer);
+ pointer.ref.frame_rate = frameRate;
+ pointer.ref.video_bitrate = videoBitrate;
+ return pointer;
+ }
+
+ static void freeNative(
+ ffi.Pointer pointer,
+ ) {
+ pkg_ffi.calloc.free(pointer);
+ }
+}
+
+/// One `ScreenRecordingEvent`, in its concrete form.
+sealed class ScreenRecordingEvent {
+ const ScreenRecordingEvent();
+
+ /// Reads the event out of its C form. Returns null for a variant this
+ /// binding does not know about.
+ static ScreenRecordingEvent? fromNative(
+ c.native_screen_recording_event_t raw,
+ ) {
+ if (raw.typeAsInt ==
+ c
+ .native_screen_recording_event_type_t
+ .NATIVE_SCREEN_RECORDING_EVENT_TYPE_STARTED
+ .value) {
+ return ScreenRecordingStartedEvent();
+ }
+ if (raw.typeAsInt ==
+ c
+ .native_screen_recording_event_type_t
+ .NATIVE_SCREEN_RECORDING_EVENT_TYPE_COMPLETED
+ .value) {
+ return ScreenRecordingCompletedEvent(
+ filePath: raw.data.completed.file_path == ffi.nullptr
+ ? null
+ : raw.data.completed.file_path.cast().toDartString(),
+ duration: raw.data.completed.duration,
+ );
+ }
+ if (raw.typeAsInt ==
+ c
+ .native_screen_recording_event_type_t
+ .NATIVE_SCREEN_RECORDING_EVENT_TYPE_CANCELLED
+ .value) {
+ return ScreenRecordingCancelledEvent();
+ }
+ if (raw.typeAsInt ==
+ c
+ .native_screen_recording_event_type_t
+ .NATIVE_SCREEN_RECORDING_EVENT_TYPE_FAILED
+ .value) {
+ return ScreenRecordingFailedEvent(
+ message: raw.data.failed.message == ffi.nullptr
+ ? null
+ : raw.data.failed.message.cast().toDartString(),
+ );
+ }
+ return null;
+ }
+}
+
+final class ScreenRecordingStartedEvent extends ScreenRecordingEvent {
+ const ScreenRecordingStartedEvent();
+}
+
+final class ScreenRecordingCompletedEvent extends ScreenRecordingEvent {
+ const ScreenRecordingCompletedEvent({
+ required this.filePath,
+ required this.duration,
+ });
+
+ final String? filePath;
+ final double duration;
+}
+
+final class ScreenRecordingCancelledEvent extends ScreenRecordingEvent {
+ const ScreenRecordingCancelledEvent();
+}
+
+final class ScreenRecordingFailedEvent extends ScreenRecordingEvent {
+ const ScreenRecordingFailedEvent({required this.message});
+
+ final String? message;
+}
+
+class ScreenRecorder implements ffi.Finalizable {
+ /// Adopts a handle returned by the C API and releases it when this
+ /// object becomes unreachable.
+ ScreenRecorder.fromHandle(this.nativeHandle) {
+ _finalizer.attach(
+ this,
+ ffi.Pointer.fromAddress(nativeHandle),
+ detach: this,
+ );
+ }
+
+ /// Wraps a handle owned elsewhere; releasing it stays the owner's job.
+ ScreenRecorder.borrowed(this.nativeHandle);
+
+ /// The underlying handle-table entry.
+ final int nativeHandle;
+
+ static final _finalizer = ffi.NativeFinalizer(
+ ffi.Native.addressOf<
+ ffi.NativeFunction)>
+ >(c.native_handle_finalize),
+ );
+
+ /// Releases the handle now instead of at collection.
+ void dispose() {
+ _finalizer.detach(this);
+ c.native_screen_recorder_free(nativeHandle);
+ }
+
+ /// Creates a new `ScreenRecorder`; returns null if the native side failed.
+ static ScreenRecorder? create() {
+ final handle = c.native_screen_recorder_create();
+ if (handle == 0) return null;
+ return ScreenRecorder.fromHandle(handle);
+ }
+
+ static bool isSupported() {
+ return c.native_screen_recorder_is_supported();
+ }
+
+ bool start(
+ CaptureSource? source,
+ String filePath,
+ ScreenRecordingOptions options,
+ ) {
+ final filePathNative = filePath.toNativeUtf8().cast();
+ final optionsPointer = options.allocNative();
+ final result = c.native_screen_recorder_start(
+ nativeHandle,
+ source?.nativeHandle ?? 0,
+ filePathNative,
+ optionsPointer.ref,
+ );
+ pkg_ffi.calloc.free(filePathNative);
+ ScreenRecordingOptions.freeNative(optionsPointer);
+ return result;
+ }
+
+ bool stop() {
+ return c.native_screen_recorder_stop(nativeHandle);
+ }
+
+ bool cancel() {
+ return c.native_screen_recorder_cancel(nativeHandle);
+ }
+
+ ScreenRecordingState get state {
+ final raw = c.native_screen_recorder_get_state(nativeHandle);
+ return ScreenRecordingState.fromValue(raw.value);
+ }
+
+ bool get isActive {
+ return c.native_screen_recorder_is_active(nativeHandle);
+ }
+
+ double get duration {
+ return c.native_screen_recorder_get_duration(nativeHandle);
+ }
+
+ String? get filePath {
+ final resultPointer = c.native_screen_recorder_get_file_path(nativeHandle);
+ if (resultPointer == ffi.nullptr) return null;
+ final result = resultPointer.cast().toDartString();
+ c.free_c_str(resultPointer);
+ return result;
+ }
+
+ /// Registers [callback] for every `ScreenRecordingEvent` this `ScreenRecorder` emits.
+ ///
+ /// Delivered on the registering isolate, including events from native UI threads.
+ /// The callback may return a Future; borrowed handles stay valid until it completes.
+ /// Events queued before removal are skipped if their callback has not started.
+ ListenerId addListener(
+ FutureOr Function(ScreenRecordingEvent) callback,
+ ) {
+ final callable =
+ ffi.NativeCallable<
+ ffi.Void Function(
+ ffi.Pointer,
+ ffi.Uint64,
+ ffi.Pointer,
+ )
+ >.listener((
+ ffi.Pointer event,
+ int delivery,
+ ffi.Pointer _,
+ ) {
+ unawaited(
+ NativeCallbacks.deliverEvent(
+ delivery,
+ () => event == ffi.nullptr
+ ? null
+ : ScreenRecordingEvent.fromNative(event.ref),
+ callback,
+ ),
+ );
+ });
+ return c.native_screen_recorder_add_listener_async(
+ nativeHandle,
+ callable.nativeFunction,
+ NativeCallbacks.userData(callable),
+ NativeCallbacks.release,
+ );
+ }
+
+ /// Unregisters a listener. Returns false if unknown.
+ bool removeListener(ListenerId listenerId) =>
+ c.native_screen_recorder_remove_listener(nativeHandle, listenerId);
+}
diff --git a/bindings/dart/nativeapi/lib/src/web/capture_source.dart b/bindings/dart/nativeapi/lib/src/web/capture_source.dart
new file mode 100644
index 00000000..8312dc1f
--- /dev/null
+++ b/bindings/dart/nativeapi/lib/src/web/capture_source.dart
@@ -0,0 +1,43 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+
+// ignore_for_file: unused_import, unnecessary_import
+
+import 'display.dart';
+import 'window.dart';
+import 'unsupported.dart';
+
+enum CaptureSourceType {
+ none(0),
+ display(1),
+ window(2),
+ unknown(3);
+
+ const CaptureSourceType(this.value);
+ final int value;
+
+ static CaptureSourceType fromValue(int value) => switch (value) {
+ 0 => CaptureSourceType.none,
+ 1 => CaptureSourceType.display,
+ 2 => CaptureSourceType.window,
+ 3 => CaptureSourceType.unknown,
+ _ => CaptureSourceType.none,
+ };
+}
+
+class CaptureSource {
+ CaptureSource.fromHandle(this.nativeHandle);
+ CaptureSource.borrowed(this.nativeHandle);
+
+ final int nativeHandle;
+
+ void dispose() => unsupported();
+
+ static CaptureSource? fromDisplay(Display? display) => unsupported();
+
+ static CaptureSource? fromWindow(Window? window) => unsupported();
+
+ CaptureSourceType get type => unsupported();
+
+ String? get name => unsupported();
+}
diff --git a/bindings/dart/nativeapi/lib/src/web/generated.dart b/bindings/dart/nativeapi/lib/src/web/generated.dart
index ccccad72..c56c2291 100644
--- a/bindings/dart/nativeapi/lib/src/web/generated.dart
+++ b/bindings/dart/nativeapi/lib/src/web/generated.dart
@@ -4,6 +4,7 @@
export 'accessibility_manager.dart';
export 'app_info.dart';
export 'application.dart';
+export 'capture_source.dart';
export 'clipboard.dart';
export 'device_info.dart';
export 'dialog.dart';
@@ -25,6 +26,8 @@ export 'notification_manager.dart';
export 'placement.dart';
export 'positioning_strategy.dart';
export 'preferences.dart';
+export 'screen_capturer.dart';
+export 'screen_recorder.dart';
export 'secure_storage.dart';
export 'shortcut.dart';
export 'shortcut_manager.dart';
diff --git a/bindings/dart/nativeapi/lib/src/web/screen_capturer.dart b/bindings/dart/nativeapi/lib/src/web/screen_capturer.dart
new file mode 100644
index 00000000..98be96ab
--- /dev/null
+++ b/bindings/dart/nativeapi/lib/src/web/screen_capturer.dart
@@ -0,0 +1,87 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+
+// ignore_for_file: unused_import, unnecessary_import
+
+import 'capture_source.dart';
+import 'foundation/geometry.dart';
+import 'image.dart';
+import 'window.dart';
+import 'unsupported.dart';
+
+class CaptureSourceSelectionOptions {
+ const CaptureSourceSelectionOptions({
+ required this.allowDisplays,
+ required this.allowWindows,
+ this.parentWindow,
+ });
+
+ final bool allowDisplays;
+ final bool allowWindows;
+ final Window? parentWindow;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ (other is CaptureSourceSelectionOptions &&
+ other.allowDisplays == allowDisplays &&
+ other.allowWindows == allowWindows &&
+ other.parentWindow == parentWindow);
+
+ @override
+ int get hashCode => Object.hash(allowDisplays, allowWindows, parentWindow);
+
+ @override
+ String toString() =>
+ 'CaptureSourceSelectionOptions(allowDisplays: $allowDisplays, allowWindows: $allowWindows, parentWindow: $parentWindow)';
+}
+
+class ScreenCaptureOptions {
+ const ScreenCaptureOptions({
+ required this.includeCursor,
+ required this.cropEnabled,
+ required this.crop,
+ });
+
+ final bool includeCursor;
+ final bool cropEnabled;
+ final Rectangle crop;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ (other is ScreenCaptureOptions &&
+ other.includeCursor == includeCursor &&
+ other.cropEnabled == cropEnabled &&
+ other.crop == crop);
+
+ @override
+ int get hashCode => Object.hash(includeCursor, cropEnabled, crop);
+
+ @override
+ String toString() =>
+ 'ScreenCaptureOptions(includeCursor: $includeCursor, cropEnabled: $cropEnabled, crop: $crop)';
+}
+
+class ScreenCapturer {
+ const ScreenCapturer._();
+
+ static const ScreenCapturer instance = ScreenCapturer._();
+
+ static bool isSupported() => unsupported();
+
+ static bool isSourceSelectionSupported() => unsupported();
+
+ static bool isDirectCaptureSupported(CaptureSourceType type) => unsupported();
+
+ static void selectSource(
+ CaptureSourceSelectionOptions options,
+ void Function(bool, CaptureSource?) callback,
+ ) => unsupported();
+
+ static void capture(
+ CaptureSource? source,
+ ScreenCaptureOptions options,
+ void Function(bool, Image?) callback,
+ ) => unsupported();
+}
diff --git a/bindings/dart/nativeapi/lib/src/web/screen_recorder.dart b/bindings/dart/nativeapi/lib/src/web/screen_recorder.dart
new file mode 100644
index 00000000..f56fafa8
--- /dev/null
+++ b/bindings/dart/nativeapi/lib/src/web/screen_recorder.dart
@@ -0,0 +1,128 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+
+// ignore_for_file: unused_import, unnecessary_import
+
+import 'dart:async';
+
+import 'capture_source.dart';
+import 'screen_capturer.dart';
+import 'support.dart';
+import 'unsupported.dart';
+
+enum ScreenRecordingState {
+ idle(0),
+ starting(1),
+ recording(2),
+ stopping(3),
+ completed(4),
+ cancelled(5),
+ failed(6);
+
+ const ScreenRecordingState(this.value);
+ final int value;
+
+ static ScreenRecordingState fromValue(int value) => switch (value) {
+ 0 => ScreenRecordingState.idle,
+ 1 => ScreenRecordingState.starting,
+ 2 => ScreenRecordingState.recording,
+ 3 => ScreenRecordingState.stopping,
+ 4 => ScreenRecordingState.completed,
+ 5 => ScreenRecordingState.cancelled,
+ 6 => ScreenRecordingState.failed,
+ _ => ScreenRecordingState.idle,
+ };
+}
+
+class ScreenRecordingOptions {
+ const ScreenRecordingOptions({
+ required this.capture,
+ required this.frameRate,
+ required this.videoBitrate,
+ });
+
+ final ScreenCaptureOptions capture;
+ final double frameRate;
+ final int videoBitrate;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ (other is ScreenRecordingOptions &&
+ other.capture == capture &&
+ other.frameRate == frameRate &&
+ other.videoBitrate == videoBitrate);
+
+ @override
+ int get hashCode => Object.hash(capture, frameRate, videoBitrate);
+
+ @override
+ String toString() =>
+ 'ScreenRecordingOptions(capture: $capture, frameRate: $frameRate, videoBitrate: $videoBitrate)';
+}
+
+/// One `ScreenRecordingEvent`, in its concrete form.
+sealed class ScreenRecordingEvent {
+ const ScreenRecordingEvent();
+}
+
+final class ScreenRecordingStartedEvent extends ScreenRecordingEvent {
+ const ScreenRecordingStartedEvent();
+}
+
+final class ScreenRecordingCompletedEvent extends ScreenRecordingEvent {
+ const ScreenRecordingCompletedEvent({
+ required this.filePath,
+ required this.duration,
+ });
+
+ final String? filePath;
+ final double duration;
+}
+
+final class ScreenRecordingCancelledEvent extends ScreenRecordingEvent {
+ const ScreenRecordingCancelledEvent();
+}
+
+final class ScreenRecordingFailedEvent extends ScreenRecordingEvent {
+ const ScreenRecordingFailedEvent({required this.message});
+
+ final String? message;
+}
+
+class ScreenRecorder {
+ ScreenRecorder.fromHandle(this.nativeHandle);
+ ScreenRecorder.borrowed(this.nativeHandle);
+
+ final int nativeHandle;
+
+ void dispose() => unsupported();
+
+ static ScreenRecorder? create() => unsupported();
+
+ static bool isSupported() => unsupported();
+
+ bool start(
+ CaptureSource? source,
+ String filePath,
+ ScreenRecordingOptions options,
+ ) => unsupported();
+
+ bool stop() => unsupported();
+
+ bool cancel() => unsupported();
+
+ ScreenRecordingState get state => unsupported();
+
+ bool get isActive => unsupported();
+
+ double get duration => unsupported();
+
+ String? get filePath => unsupported();
+
+ ListenerId addListener(
+ FutureOr Function(ScreenRecordingEvent) callback,
+ ) => unsupported();
+
+ bool removeListener(ListenerId listenerId) => unsupported();
+}
diff --git a/bindings/go/bridge.gen.h b/bindings/go/bridge.gen.h
index adfd1906..8ff47bd3 100644
--- a/bindings/go/bridge.gen.h
+++ b/bindings/go/bridge.gen.h
@@ -25,6 +25,12 @@ static inline char* go_native_notification_event_t_activated_Argument(const nati
extern void goEvent_native_notification_event_t(uintptr_t handle, native_notification_event_t* event);
static inline void go_native_notification_event_callback_t(const native_notification_event_t* event, void* data) { goEvent_native_notification_event_t(*(uintptr_t*)data, (native_notification_event_t*)event); }
static inline native_notification_event_callback_t go_get_native_notification_event_callback_t(void) { return go_native_notification_event_callback_t; }
+static inline char* go_native_screen_recording_event_t_completed_FilePath(const native_screen_recording_event_t* event) { return event->data.completed.file_path; }
+static inline double go_native_screen_recording_event_t_completed_Duration(const native_screen_recording_event_t* event) { return event->data.completed.duration; }
+static inline char* go_native_screen_recording_event_t_failed_Message(const native_screen_recording_event_t* event) { return event->data.failed.message; }
+extern void goEvent_native_screen_recording_event_t(uintptr_t handle, native_screen_recording_event_t* event);
+static inline void go_native_screen_recording_event_callback_t(const native_screen_recording_event_t* event, void* data) { goEvent_native_screen_recording_event_t(*(uintptr_t*)data, (native_screen_recording_event_t*)event); }
+static inline native_screen_recording_event_callback_t go_get_native_screen_recording_event_callback_t(void) { return go_native_screen_recording_event_callback_t; }
static inline char* go_native_view_event_t_text_field_changed_Text(const native_view_event_t* event) { return event->data.text_field_changed.text; }
extern void goEvent_native_view_event_t(uintptr_t handle, native_view_event_t* event);
static inline void go_native_view_event_callback_t(const native_view_event_t* event, void* data) { goEvent_native_view_event_t(*(uintptr_t*)data, (native_view_event_t*)event); }
@@ -87,6 +93,12 @@ static inline native_clipboard_read_image_callback_t go_get_native_clipboard_rea
extern void goCall_native_clipboard_read_text_callback_t(uintptr_t handle, bool arg0, void* arg1, native_event_delivery_t delivery);
static inline void go_native_clipboard_read_text_callback_t(bool arg0, const char* arg1, native_event_delivery_t delivery, void* data) { goCall_native_clipboard_read_text_callback_t(*(uintptr_t*)data, arg0, (void*)arg1, delivery); }
static inline native_clipboard_read_text_callback_t go_get_native_clipboard_read_text_callback_t(void) { return go_native_clipboard_read_text_callback_t; }
+extern void goCall_native_screen_capturer_capture_callback_t(uintptr_t handle, bool arg0, native_image_t arg1, native_event_delivery_t delivery);
+static inline void go_native_screen_capturer_capture_callback_t(bool arg0, native_image_t arg1, native_event_delivery_t delivery, void* data) { goCall_native_screen_capturer_capture_callback_t(*(uintptr_t*)data, arg0, arg1, delivery); }
+static inline native_screen_capturer_capture_callback_t go_get_native_screen_capturer_capture_callback_t(void) { return go_native_screen_capturer_capture_callback_t; }
+extern void goCall_native_screen_capturer_select_source_callback_t(uintptr_t handle, bool arg0, native_capture_source_t arg1, native_event_delivery_t delivery);
+static inline void go_native_screen_capturer_select_source_callback_t(bool arg0, native_capture_source_t arg1, native_event_delivery_t delivery, void* data) { goCall_native_screen_capturer_select_source_callback_t(*(uintptr_t*)data, arg0, arg1, delivery); }
+static inline native_screen_capturer_select_source_callback_t go_get_native_screen_capturer_select_source_callback_t(void) { return go_native_screen_capturer_select_source_callback_t; }
extern void goCall_native_shortcut_create_with_id_and_accelerator_and_callback_t(uintptr_t handle);
static inline void go_native_shortcut_create_with_id_and_accelerator_and_callback_t(void* data) { goCall_native_shortcut_create_with_id_and_accelerator_and_callback_t(*(uintptr_t*)data); }
static inline native_shortcut_create_with_id_and_accelerator_and_callback_t go_get_native_shortcut_create_with_id_and_accelerator_and_callback_t(void) { return go_native_shortcut_create_with_id_and_accelerator_and_callback_t; }
diff --git a/bindings/go/nativeapi.gen.go b/bindings/go/nativeapi.gen.go
index 560dec8a..ed81c334 100644
--- a/bindings/go/nativeapi.gen.go
+++ b/bindings/go/nativeapi.gen.go
@@ -2091,6 +2091,388 @@ func (i *Image) NativeObject() unsafe.Pointer {
return C.native_image_get_native_object(C.native_image_t(i.nativeHandleValue()))
}
+// CaptureSourceType represents native CaptureSourceType values.
+type CaptureSourceType int32
+
+const (
+ CaptureSourceTypeNone CaptureSourceType = 0
+ CaptureSourceTypeDisplay CaptureSourceType = 1
+ CaptureSourceTypeWindow CaptureSourceType = 2
+ CaptureSourceTypeUnknown CaptureSourceType = 3
+)
+
+// CaptureSource owns a native handle. Call Release on the UI thread when done.
+// Copies share ownership state; borrowed event handles must not outlive their callback.
+type CaptureSource struct{ *nativeHandle }
+
+func wrapCaptureSource(value uint64, borrowed bool) *CaptureSource {
+ if value == 0 {
+ return nil
+ }
+ return wrapCaptureSourceState(&nativeHandle{value: value, borrowed: borrowed})
+}
+func wrapCaptureSourceState(state *nativeHandle) *CaptureSource {
+ return &CaptureSource{state}
+}
+func (cs *CaptureSource) nativeHandleValue() uint64 {
+ if cs == nil || cs.nativeHandle == nil {
+ return 0
+ }
+ return cs.nativeHandle.nativeHandleValue()
+}
+
+// Release drops this owned reference. Repeated calls and nil receivers are safe.
+func (cs *CaptureSource) Release() {
+ if cs.nativeHandleValue() == 0 || cs.borrowed {
+ return
+ }
+ C.native_capture_source_free(C.native_capture_source_t(cs.value))
+ cs.value = 0
+}
+
+// CaptureSourceFromDisplay invokes the native FromDisplay operation.
+// Failure wraps ErrOperationFailed; the native ABI provides no detailed cause.
+func CaptureSourceFromDisplay(display *Display) (*CaptureSource, error) {
+ result := C.native_capture_source_from_display(C.native_display_t(display.nativeHandleValue()))
+ if result == 0 {
+ return nil, nativeError("CaptureSourceFromDisplay")
+ }
+ return wrapCaptureSource(uint64(result), false), nil
+}
+
+// CaptureSourceFromWindow invokes the native FromWindow operation.
+// Failure wraps ErrOperationFailed; the native ABI provides no detailed cause.
+func CaptureSourceFromWindow(window *Window) (*CaptureSource, error) {
+ result := C.native_capture_source_from_window(C.native_window_t(window.nativeHandleValue()))
+ if result == 0 {
+ return nil, nativeError("CaptureSourceFromWindow")
+ }
+ return wrapCaptureSource(uint64(result), false), nil
+}
+
+// Type invokes the native GetType operation.
+func (cs *CaptureSource) Type() CaptureSourceType {
+ result := C.native_capture_source_get_type(C.native_capture_source_t(cs.nativeHandleValue()))
+ return CaptureSourceType(result)
+}
+
+// Name invokes the native GetName operation.
+func (cs *CaptureSource) Name() string {
+ result := C.native_capture_source_get_name(C.native_capture_source_t(cs.nativeHandleValue()))
+ defer C.free_c_str(result)
+ return C.GoString(result)
+}
+
+// CaptureSourceSelectionOptions holds the native CaptureSourceSelectionOptions value.
+type CaptureSourceSelectionOptions struct {
+ AllowDisplays bool
+ AllowWindows bool
+ ParentWindow *Window
+}
+
+func fromCCaptureSourceSelectionOptions(raw C.native_capture_source_selection_options_t) CaptureSourceSelectionOptions {
+ return CaptureSourceSelectionOptions{
+ AllowDisplays: bool(raw.allow_displays),
+ AllowWindows: bool(raw.allow_windows),
+ ParentWindow: wrapWindow(uint64(C.native_handle_retain(raw.parent_window)), false),
+ }
+}
+func toCCaptureSourceSelectionOptions(value CaptureSourceSelectionOptions) (C.native_capture_source_selection_options_t, func()) {
+ var raw C.native_capture_source_selection_options_t
+ var cleanup []func()
+ raw.allow_displays = C.bool(value.AllowDisplays)
+ raw.allow_windows = C.bool(value.AllowWindows)
+ raw.parent_window = C.native_window_t(value.ParentWindow.nativeHandleValue())
+ return raw, func() {
+ for i := len(cleanup) - 1; i >= 0; i-- {
+ cleanup[i]()
+ }
+ }
+}
+
+// ScreenCaptureOptions holds the native ScreenCaptureOptions value.
+type ScreenCaptureOptions struct {
+ IncludeCursor bool
+ CropEnabled bool
+ Crop Rectangle
+}
+
+func fromCScreenCaptureOptions(raw C.native_screen_capture_options_t) ScreenCaptureOptions {
+ return ScreenCaptureOptions{
+ IncludeCursor: bool(raw.include_cursor),
+ CropEnabled: bool(raw.crop_enabled),
+ Crop: fromCRectangle(raw.crop),
+ }
+}
+func toCScreenCaptureOptions(value ScreenCaptureOptions) (C.native_screen_capture_options_t, func()) {
+ var raw C.native_screen_capture_options_t
+ var cleanup []func()
+ raw.include_cursor = C.bool(value.IncludeCursor)
+ raw.crop_enabled = C.bool(value.CropEnabled)
+ cField2, cField2Cleanup := toCRectangle(value.Crop)
+ cleanup = append(cleanup, func() { cField2Cleanup() })
+ raw.crop = cField2
+ return raw, func() {
+ for i := len(cleanup) - 1; i >= 0; i-- {
+ cleanup[i]()
+ }
+ }
+}
+
+// ScreenCapturerIsSupported invokes the native IsSupported operation.
+func ScreenCapturerIsSupported() bool {
+ result := C.native_screen_capturer_is_supported()
+ return bool(result)
+}
+
+// ScreenCapturerIsSourceSelectionSupported invokes the native IsSourceSelectionSupported operation.
+func ScreenCapturerIsSourceSelectionSupported() bool {
+ result := C.native_screen_capturer_is_source_selection_supported()
+ return bool(result)
+}
+
+// ScreenCapturerIsDirectCaptureSupported invokes the native IsDirectCaptureSupported operation.
+func ScreenCapturerIsDirectCaptureSupported(kind CaptureSourceType) bool {
+ result := C.native_screen_capturer_is_direct_capture_supported(C.native_capture_source_type_t(kind))
+ return bool(result)
+}
+
+// ScreenCapturerSelectSource invokes the native SelectSource operation.
+func ScreenCapturerSelectSource(options CaptureSourceSelectionOptions, callback func(bool, *CaptureSource)) {
+ cArg0, cArg0Cleanup := toCCaptureSourceSelectionOptions(options)
+ defer cArg0Cleanup()
+ var cArg1Fn C.native_screen_capturer_select_source_callback_t
+ var cArg1Data unsafe.Pointer
+ var cArg1Release C.native_release_user_data_t
+ if callback != nil {
+ cArg1Data = newCallback(callback)
+ cArg1Fn = C.native_screen_capturer_select_source_callback_t(C.go_get_native_screen_capturer_select_source_callback_t())
+ cArg1Release = C.native_release_user_data_t(C.go_get_release_callback())
+ }
+ C.native_screen_capturer_select_source(cArg0, cArg1Fn, cArg1Data, cArg1Release)
+}
+
+// ScreenCapturerCapture invokes the native Capture operation.
+func ScreenCapturerCapture(source *CaptureSource, options ScreenCaptureOptions, callback func(bool, *Image)) {
+ cArg1, cArg1Cleanup := toCScreenCaptureOptions(options)
+ defer cArg1Cleanup()
+ var cArg2Fn C.native_screen_capturer_capture_callback_t
+ var cArg2Data unsafe.Pointer
+ var cArg2Release C.native_release_user_data_t
+ if callback != nil {
+ cArg2Data = newCallback(callback)
+ cArg2Fn = C.native_screen_capturer_capture_callback_t(C.go_get_native_screen_capturer_capture_callback_t())
+ cArg2Release = C.native_release_user_data_t(C.go_get_release_callback())
+ }
+ C.native_screen_capturer_capture(C.native_capture_source_t(source.nativeHandleValue()), cArg1, cArg2Fn, cArg2Data, cArg2Release)
+}
+
+// ScreenRecordingState represents native ScreenRecordingState values.
+type ScreenRecordingState int32
+
+const (
+ ScreenRecordingStateIdle ScreenRecordingState = 0
+ ScreenRecordingStateStarting ScreenRecordingState = 1
+ ScreenRecordingStateRecording ScreenRecordingState = 2
+ ScreenRecordingStateStopping ScreenRecordingState = 3
+ ScreenRecordingStateCompleted ScreenRecordingState = 4
+ ScreenRecordingStateCancelled ScreenRecordingState = 5
+ ScreenRecordingStateFailed ScreenRecordingState = 6
+)
+
+// ScreenRecordingOptions holds the native ScreenRecordingOptions value.
+type ScreenRecordingOptions struct {
+ Capture ScreenCaptureOptions
+ FrameRate float64
+ VideoBitrate uint64
+}
+
+func fromCScreenRecordingOptions(raw C.native_screen_recording_options_t) ScreenRecordingOptions {
+ return ScreenRecordingOptions{
+ Capture: fromCScreenCaptureOptions(raw.capture),
+ FrameRate: float64(raw.frame_rate),
+ VideoBitrate: uint64(raw.video_bitrate),
+ }
+}
+func toCScreenRecordingOptions(value ScreenRecordingOptions) (C.native_screen_recording_options_t, func()) {
+ var raw C.native_screen_recording_options_t
+ var cleanup []func()
+ cField0, cField0Cleanup := toCScreenCaptureOptions(value.Capture)
+ cleanup = append(cleanup, func() { cField0Cleanup() })
+ raw.capture = cField0
+ raw.frame_rate = C.double(value.FrameRate)
+ raw.video_bitrate = C.ulong(value.VideoBitrate)
+ return raw, func() {
+ for i := len(cleanup) - 1; i >= 0; i-- {
+ cleanup[i]()
+ }
+ }
+}
+
+// ScreenRecordingEventType identifies the active event variant.
+type ScreenRecordingEventType int32
+
+const (
+ ScreenRecordingEventStarted ScreenRecordingEventType = 0
+ ScreenRecordingEventCompleted ScreenRecordingEventType = 1
+ ScreenRecordingEventCancelled ScreenRecordingEventType = 2
+ ScreenRecordingEventFailed ScreenRecordingEventType = 3
+)
+
+// ScreenRecordingEvent copies an event payload. Handle fields are borrowed until the callback returns.
+// Only fields belonging to Type are populated.
+type ScreenRecordingEvent struct {
+ Type ScreenRecordingEventType
+ FilePath string
+ Duration float64
+ Message string
+}
+
+func fromCScreenRecordingEvent(raw *C.native_screen_recording_event_t) ScreenRecordingEvent {
+ value := ScreenRecordingEvent{Type: ScreenRecordingEventType(raw._type)}
+ switch value.Type {
+ case ScreenRecordingEventStarted:
+ case ScreenRecordingEventCompleted:
+ value.FilePath = C.GoString(C.go_native_screen_recording_event_t_completed_FilePath(raw))
+ value.Duration = float64(C.go_native_screen_recording_event_t_completed_Duration(raw))
+ case ScreenRecordingEventCancelled:
+ case ScreenRecordingEventFailed:
+ value.Message = C.GoString(C.go_native_screen_recording_event_t_failed_Message(raw))
+ }
+ return value
+}
+
+//export goEvent_native_screen_recording_event_t
+func goEvent_native_screen_recording_event_t(handle C.uintptr_t, event *C.native_screen_recording_event_t) {
+ cgo.Handle(handle).Value().(func(ScreenRecordingEvent))(fromCScreenRecordingEvent(event))
+}
+
+// ScreenRecorder owns a native handle. Call Release on the UI thread when done.
+// Copies share ownership state; borrowed event handles must not outlive their callback.
+type ScreenRecorder struct{ *nativeHandle }
+
+func wrapScreenRecorder(value uint64, borrowed bool) *ScreenRecorder {
+ if value == 0 {
+ return nil
+ }
+ return wrapScreenRecorderState(&nativeHandle{value: value, borrowed: borrowed})
+}
+func wrapScreenRecorderState(state *nativeHandle) *ScreenRecorder {
+ return &ScreenRecorder{state}
+}
+func (sr *ScreenRecorder) nativeHandleValue() uint64 {
+ if sr == nil || sr.nativeHandle == nil {
+ return 0
+ }
+ return sr.nativeHandle.nativeHandleValue()
+}
+
+// Release drops this owned reference. Repeated calls and nil receivers are safe.
+func (sr *ScreenRecorder) Release() {
+ if sr.nativeHandleValue() == 0 || sr.borrowed {
+ return
+ }
+ C.native_screen_recorder_free(C.native_screen_recorder_t(sr.value))
+ sr.value = 0
+}
+
+// NewScreenRecorder creates an owned ScreenRecorder.
+// Failure wraps ErrOperationFailed; the native ABI provides no detailed cause.
+func NewScreenRecorder() (*ScreenRecorder, error) {
+ result := C.native_screen_recorder_create()
+ if result == 0 {
+ return nil, nativeError("NewScreenRecorder")
+ }
+ return wrapScreenRecorder(uint64(result), false), nil
+}
+
+// ScreenRecorderIsSupported invokes the native IsSupported operation.
+func ScreenRecorderIsSupported() bool {
+ result := C.native_screen_recorder_is_supported()
+ return bool(result)
+}
+
+// Start invokes the native Start operation.
+// Failure wraps ErrOperationFailed; the native ABI provides no detailed cause.
+func (sr *ScreenRecorder) Start(source *CaptureSource, filePath string, options ScreenRecordingOptions) error {
+ cArg1 := C.CString(filePath)
+ defer C.free(unsafe.Pointer(cArg1))
+ cArg2, cArg2Cleanup := toCScreenRecordingOptions(options)
+ defer cArg2Cleanup()
+ result := C.native_screen_recorder_start(C.native_screen_recorder_t(sr.nativeHandleValue()), C.native_capture_source_t(source.nativeHandleValue()), cArg1, cArg2)
+ if !bool(result) {
+ return nativeError("ScreenRecorder.Start")
+ }
+ return nil
+}
+
+// Stop invokes the native Stop operation.
+// Failure wraps ErrOperationFailed; the native ABI provides no detailed cause.
+func (sr *ScreenRecorder) Stop() error {
+ result := C.native_screen_recorder_stop(C.native_screen_recorder_t(sr.nativeHandleValue()))
+ if !bool(result) {
+ return nativeError("ScreenRecorder.Stop")
+ }
+ return nil
+}
+
+// Cancel invokes the native Cancel operation.
+// Failure wraps ErrOperationFailed; the native ABI provides no detailed cause.
+func (sr *ScreenRecorder) Cancel() error {
+ result := C.native_screen_recorder_cancel(C.native_screen_recorder_t(sr.nativeHandleValue()))
+ if !bool(result) {
+ return nativeError("ScreenRecorder.Cancel")
+ }
+ return nil
+}
+
+// State invokes the native GetState operation.
+func (sr *ScreenRecorder) State() ScreenRecordingState {
+ result := C.native_screen_recorder_get_state(C.native_screen_recorder_t(sr.nativeHandleValue()))
+ return ScreenRecordingState(result)
+}
+
+// IsActive invokes the native IsActive operation.
+func (sr *ScreenRecorder) IsActive() bool {
+ result := C.native_screen_recorder_is_active(C.native_screen_recorder_t(sr.nativeHandleValue()))
+ return bool(result)
+}
+
+// Duration invokes the native GetDuration operation.
+func (sr *ScreenRecorder) Duration() float64 {
+ result := C.native_screen_recorder_get_duration(C.native_screen_recorder_t(sr.nativeHandleValue()))
+ return float64(result)
+}
+
+// FilePath invokes the native GetFilePath operation.
+func (sr *ScreenRecorder) FilePath() string {
+ result := C.native_screen_recorder_get_file_path(C.native_screen_recorder_t(sr.nativeHandleValue()))
+ defer C.free_c_str(result)
+ return C.GoString(result)
+}
+
+// AddListener invokes callback synchronously on the native UI thread.
+// Remove the listener before releasing its owner. A nil callback is invalid.
+func (sr *ScreenRecorder) AddListener(callback func(ScreenRecordingEvent)) (ListenerID, error) {
+ if callback == nil {
+ return 0, invalidArgument("ScreenRecorder.AddListener")
+ }
+ data := newCallback(callback)
+ id := C.native_screen_recorder_add_listener(C.native_screen_recorder_t(sr.nativeHandleValue()), C.native_screen_recording_event_callback_t(C.go_get_native_screen_recording_event_callback_t()), data, C.native_release_user_data_t(C.go_get_release_callback()))
+ if id == 0 {
+ return 0, nativeError("ScreenRecorder.AddListener")
+ }
+ return ListenerID(id), nil
+}
+
+// RemoveListener unregisters id; an unknown id returns an error.
+func (sr *ScreenRecorder) RemoveListener(id ListenerID) error {
+ if !bool(C.native_screen_recorder_remove_listener(C.native_screen_recorder_t(sr.nativeHandleValue()), C.native_listener_id_t(id))) {
+ return nativeError("ScreenRecorder.RemoveListener")
+ }
+ return nil
+}
+
// WindowShape owns a native handle. Call Release on the UI thread when done.
// Copies share ownership state; borrowed event handles must not outlive their callback.
type WindowShape struct{ *nativeHandle }
@@ -6195,6 +6577,24 @@ func goCall_native_clipboard_read_text_callback_t(handle C.uintptr_t, arg0 C.boo
cgo.Handle(handle).Value().(func(bool, *string))(bool(arg0), optionalStringFromC((*C.char)(arg1)))
}
+//export goCall_native_screen_capturer_capture_callback_t
+func goCall_native_screen_capturer_capture_callback_t(handle C.uintptr_t, arg0 C.bool, arg1 C.native_image_t, delivery C.native_event_delivery_t) {
+ defer C.native_event_delivery_complete(delivery, true)
+ if !bool(C.native_event_delivery_is_active(delivery)) {
+ return
+ }
+ cgo.Handle(handle).Value().(func(bool, *Image))(bool(arg0), wrapImage(uint64(C.native_handle_retain(arg1)), false))
+}
+
+//export goCall_native_screen_capturer_select_source_callback_t
+func goCall_native_screen_capturer_select_source_callback_t(handle C.uintptr_t, arg0 C.bool, arg1 C.native_capture_source_t, delivery C.native_event_delivery_t) {
+ defer C.native_event_delivery_complete(delivery, true)
+ if !bool(C.native_event_delivery_is_active(delivery)) {
+ return
+ }
+ cgo.Handle(handle).Value().(func(bool, *CaptureSource))(bool(arg0), wrapCaptureSource(uint64(C.native_handle_retain(arg1)), false))
+}
+
//export goCall_native_shortcut_create_with_id_and_accelerator_and_callback_t
func goCall_native_shortcut_create_with_id_and_accelerator_and_callback_t(handle C.uintptr_t) {
cgo.Handle(handle).Value().(func())()
diff --git a/bindings/go/screen_capture_test.go b/bindings/go/screen_capture_test.go
new file mode 100644
index 00000000..c1f3f091
--- /dev/null
+++ b/bindings/go/screen_capture_test.go
@@ -0,0 +1,26 @@
+package nativeapi
+
+import "testing"
+
+// No desktop or permission: exercise the real cgo nested-value conversion.
+func TestRecordingRejectionKeepsIdle(t *testing.T) {
+ recorder, err := NewScreenRecorder()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer recorder.Release()
+ options := ScreenRecordingOptions{
+ Capture: ScreenCaptureOptions{IncludeCursor: false, CropEnabled: true,
+ Crop: Rectangle{X: 1, Y: 2, Width: 32, Height: 48}},
+ FrameRate: 30, VideoBitrate: 1000000,
+ }
+ if recorder.Start(nil, "relative.mp4", options) == nil {
+ t.Fatal("invalid request accepted")
+ }
+ if recorder.State() != ScreenRecordingStateIdle || recorder.IsActive() || recorder.Duration() != 0 {
+ t.Fatal("rejected request changed recorder state")
+ }
+ if recorder.Stop() == nil || recorder.Cancel() == nil {
+ t.Fatal("idle recorder accepted a termination action")
+ }
+}
diff --git a/bindings/js/lib/capture_source.ts b/bindings/js/lib/capture_source.ts
new file mode 100644
index 00000000..7539b25d
--- /dev/null
+++ b/bindings/js/lib/capture_source.ts
@@ -0,0 +1,38 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+
+import { native, NativeObject, wrapHandle } from "./runtime.ts";
+import { Display } from "./display.ts";
+import { Window } from "./window.ts";
+
+export const CaptureSourceType = {
+ None: 0,
+ Display: 1,
+ Window: 2,
+ Unknown: 3,
+} as const;
+export type CaptureSourceType = (typeof CaptureSourceType)[keyof typeof CaptureSourceType];
+
+/** A native CaptureSource, held through an owned handle. */
+export class CaptureSource extends NativeObject {
+ /** Wraps a raw handle; an owned one is released on `dispose()` or collection. */
+ constructor(handle: bigint, owned = true) {
+ super(handle, owned ? native.native_capture_source_free : undefined);
+ }
+
+ static fromDisplay(display: Display | null): CaptureSource | null {
+ return wrapHandle(CaptureSource, native.native_capture_source_from_display(display?.nativeHandle ?? 0n));
+ }
+
+ static fromWindow(window: Window | null): CaptureSource | null {
+ return wrapHandle(CaptureSource, native.native_capture_source_from_window(window?.nativeHandle ?? 0n));
+ }
+
+ get type(): CaptureSourceType {
+ return native.native_capture_source_get_type(this.nativeHandle);
+ }
+
+ get name(): string {
+ return native.native_capture_source_get_name(this.nativeHandle);
+ }
+}
diff --git a/bindings/js/lib/modules.ts b/bindings/js/lib/modules.ts
index c7669b4c..53d9dea3 100644
--- a/bindings/js/lib/modules.ts
+++ b/bindings/js/lib/modules.ts
@@ -21,6 +21,9 @@ export * from "./message_dialog.ts";
export * from "./file_dialog.ts";
export * from "./notification_manager.ts";
export * from "./image.ts";
+export * from "./capture_source.ts";
+export * from "./screen_capturer.ts";
+export * from "./screen_recorder.ts";
export * from "./window_shape.ts";
export * from "./window_shadow.ts";
export * from "./view.ts";
diff --git a/bindings/js/lib/screen_capturer.ts b/bindings/js/lib/screen_capturer.ts
new file mode 100644
index 00000000..f16d3900
--- /dev/null
+++ b/bindings/js/lib/screen_capturer.ts
@@ -0,0 +1,44 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+
+import { native, deliverEvent } from "./runtime.ts";
+import { CaptureSource, CaptureSourceType } from "./capture_source.ts";
+import { type Rectangle } from "./geometry.ts";
+import { Image } from "./image.ts";
+import { Window } from "./window.ts";
+
+export interface CaptureSourceSelectionOptions {
+ allowDisplays: boolean;
+ allowWindows: boolean;
+ parentWindow?: Window | null;
+}
+
+export interface ScreenCaptureOptions {
+ includeCursor: boolean;
+ cropEnabled: boolean;
+ crop: Rectangle;
+}
+
+export class ScreenCapturer {
+ private constructor() {}
+
+ static isSupported(): boolean {
+ return native.native_screen_capturer_is_supported();
+ }
+
+ static isSourceSelectionSupported(): boolean {
+ return native.native_screen_capturer_is_source_selection_supported();
+ }
+
+ static isDirectCaptureSupported(type: CaptureSourceType): boolean {
+ return native.native_screen_capturer_is_direct_capture_supported(type);
+ }
+
+ static selectSource(options: CaptureSourceSelectionOptions, callback: (arg0: boolean, arg1: CaptureSource | null) => void): void {
+ native.native_screen_capturer_select_source(({ ...options, parentWindow: options.parentWindow?.nativeHandle ?? 0n }), (arg0: any, arg1: any, delivery: bigint) => deliverEvent(delivery, () => [arg0, (arg1 ? new CaptureSource(native.retainHandle(arg1)) : null)] as const, (values) => callback(...values)));
+ }
+
+ static capture(source: CaptureSource | null, options: ScreenCaptureOptions, callback: (arg0: boolean, arg1: Image | null) => void): void {
+ native.native_screen_capturer_capture(source?.nativeHandle ?? 0n, ({ ...options, crop: options.crop }), (arg0: any, arg1: any, delivery: bigint) => deliverEvent(delivery, () => [arg0, (arg1 ? new Image(native.retainHandle(arg1)) : null)] as const, (values) => callback(...values)));
+ }
+}
diff --git a/bindings/js/lib/screen_recorder.ts b/bindings/js/lib/screen_recorder.ts
new file mode 100644
index 00000000..f517e1f5
--- /dev/null
+++ b/bindings/js/lib/screen_recorder.ts
@@ -0,0 +1,87 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+
+import { native, NativeObject, deliverEvent } from "./runtime.ts";
+import { CaptureSource } from "./capture_source.ts";
+import { type ScreenCaptureOptions } from "./screen_capturer.ts";
+
+export const ScreenRecordingState = {
+ Idle: 0,
+ Starting: 1,
+ Recording: 2,
+ Stopping: 3,
+ Completed: 4,
+ Cancelled: 5,
+ Failed: 6,
+} as const;
+export type ScreenRecordingState = (typeof ScreenRecordingState)[keyof typeof ScreenRecordingState];
+
+export interface ScreenRecordingOptions {
+ capture: ScreenCaptureOptions;
+ frameRate: number;
+ videoBitrate: number;
+}
+
+export type ScreenRecordingEvent =
+ | { type: "started" }
+ | { type: "completed"; filePath: string | null; duration: number }
+ | { type: "cancelled" }
+ | { type: "failed"; message: string | null };
+
+/** A native ScreenRecorder, held through an owned handle. */
+export class ScreenRecorder extends NativeObject {
+ /** Wraps a raw handle; an owned one is released on `dispose()` or collection. */
+ constructor(handle: bigint, owned = true) {
+ super(handle, owned ? native.native_screen_recorder_free : undefined);
+ }
+
+ static create(): ScreenRecorder | null {
+ const handle: bigint = native.native_screen_recorder_create();
+ return handle ? new ScreenRecorder(handle) : null;
+ }
+
+ static isSupported(): boolean {
+ return native.native_screen_recorder_is_supported();
+ }
+
+ start(source: CaptureSource | null, filePath: string, options: ScreenRecordingOptions): boolean {
+ return native.native_screen_recorder_start(this.nativeHandle, source?.nativeHandle ?? 0n, filePath, ({ ...options, capture: ({ ...options.capture, crop: options.capture.crop }) }));
+ }
+
+ stop(): boolean {
+ return native.native_screen_recorder_stop(this.nativeHandle);
+ }
+
+ cancel(): boolean {
+ return native.native_screen_recorder_cancel(this.nativeHandle);
+ }
+
+ get state(): ScreenRecordingState {
+ return native.native_screen_recorder_get_state(this.nativeHandle);
+ }
+
+ get isActive(): boolean {
+ return native.native_screen_recorder_is_active(this.nativeHandle);
+ }
+
+ get duration(): number {
+ return native.native_screen_recorder_get_duration(this.nativeHandle);
+ }
+
+ get filePath(): string {
+ return native.native_screen_recorder_get_file_path(this.nativeHandle);
+ }
+
+ /** Receives events on the JS thread. Borrowed objects stay valid until the returned Promise settles. */
+ addListener(listener: (event: ScreenRecordingEvent) => void | Promise): number {
+ return native.native_screen_recorder_add_listener(this.nativeHandle, (event: Record, delivery: bigint) =>
+ deliverEvent(delivery, () => {
+ return event as unknown as ScreenRecordingEvent;
+ }, listener));
+ }
+
+ /** Unregisters a listener; returns false if the id is unknown. */
+ removeListener(listenerId: number): boolean {
+ return native.native_screen_recorder_remove_listener(this.nativeHandle, listenerId);
+ }
+}
diff --git a/bindings/js/src/generated/capture_source.cc b/bindings/js/src/generated/capture_source.cc
new file mode 100644
index 00000000..77c4cb85
--- /dev/null
+++ b/bindings/js/src/generated/capture_source.cc
@@ -0,0 +1,96 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+
+#include "types.h"
+
+namespace nativeapi_js {
+namespace {
+
+napi_value Js_native_capture_source_free(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ uint64_t self = 0;
+ if (!GetHandle(env, args[0], &self)) {
+ return nullptr;
+ }
+ OnMainThread([&] { return native_capture_source_free(self); });
+ return Undefined(env);
+}
+
+napi_value Js_native_capture_source_from_display(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ native_display_t p0 = {};
+ if (!GetHandle(env, args[0], &p0)) {
+ return nullptr;
+ }
+ auto result = OnMainThread([&] { return native_capture_source_from_display(p0); });
+ return Value::BigInt(result).ToJs(env);
+}
+
+napi_value Js_native_capture_source_from_window(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ native_window_t p0 = {};
+ if (!GetHandle(env, args[0], &p0)) {
+ return nullptr;
+ }
+ auto result = OnMainThread([&] { return native_capture_source_from_window(p0); });
+ return Value::BigInt(result).ToJs(env);
+}
+
+napi_value Js_native_capture_source_get_type(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ uint64_t self = 0;
+ if (!GetHandle(env, args[0], &self)) {
+ return nullptr;
+ }
+ auto result = OnMainThread([&] { return native_capture_source_get_type(self); });
+ return Value::Number(static_cast(result)).ToJs(env);
+}
+
+napi_value Js_native_capture_source_get_name(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ uint64_t self = 0;
+ if (!GetHandle(env, args[0], &self)) {
+ return nullptr;
+ }
+ auto result = OnMainThread([&] { return native_capture_source_get_name(self); });
+ Value value = Value::String(result ? result : "");
+ free_c_str(result);
+ return value.ToJs(env);
+}
+
+} // namespace
+
+void RegisterCaptureSource(napi_env env, napi_value exports) {
+ Export(env, exports, "native_capture_source_free", Js_native_capture_source_free);
+ Export(env, exports, "native_capture_source_from_display", Js_native_capture_source_from_display);
+ Export(env, exports, "native_capture_source_from_window", Js_native_capture_source_from_window);
+ Export(env, exports, "native_capture_source_get_type", Js_native_capture_source_get_type);
+ Export(env, exports, "native_capture_source_get_name", Js_native_capture_source_get_name);
+}
+
+} // namespace nativeapi_js
diff --git a/bindings/js/src/generated/modules.cc b/bindings/js/src/generated/modules.cc
index 3d4ba3b7..34c56a8a 100644
--- a/bindings/js/src/generated/modules.cc
+++ b/bindings/js/src/generated/modules.cc
@@ -25,6 +25,9 @@ void RegisterMessageDialog(napi_env env, napi_value exports);
void RegisterFileDialog(napi_env env, napi_value exports);
void RegisterNotificationManager(napi_env env, napi_value exports);
void RegisterImage(napi_env env, napi_value exports);
+void RegisterCaptureSource(napi_env env, napi_value exports);
+void RegisterScreenCapturer(napi_env env, napi_value exports);
+void RegisterScreenRecorder(napi_env env, napi_value exports);
void RegisterWindowShape(napi_env env, napi_value exports);
void RegisterWindowShadow(napi_env env, napi_value exports);
void RegisterView(napi_env env, napi_value exports);
@@ -63,6 +66,9 @@ void RegisterGenerated(napi_env env, napi_value exports) {
RegisterFileDialog(env, exports);
RegisterNotificationManager(env, exports);
RegisterImage(env, exports);
+ RegisterCaptureSource(env, exports);
+ RegisterScreenCapturer(env, exports);
+ RegisterScreenRecorder(env, exports);
RegisterWindowShape(env, exports);
RegisterWindowShadow(env, exports);
RegisterView(env, exports);
diff --git a/bindings/js/src/generated/screen_capturer.cc b/bindings/js/src/generated/screen_capturer.cc
new file mode 100644
index 00000000..30c23d09
--- /dev/null
+++ b/bindings/js/src/generated/screen_capturer.cc
@@ -0,0 +1,98 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+
+#include "types.h"
+
+namespace nativeapi_js {
+namespace {
+
+napi_value Js_native_screen_capturer_is_supported(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ auto result = OnMainThread([&] { return native_screen_capturer_is_supported(); });
+ return Value::Bool(result).ToJs(env);
+}
+
+napi_value Js_native_screen_capturer_is_source_selection_supported(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ auto result = OnMainThread([&] { return native_screen_capturer_is_source_selection_supported(); });
+ return Value::Bool(result).ToJs(env);
+}
+
+napi_value Js_native_screen_capturer_is_direct_capture_supported(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ native_capture_source_type_t p0 = {};
+ if (!GetNumber(env, args[0], &p0)) {
+ return nullptr;
+ }
+ auto result = OnMainThread([&] { return native_screen_capturer_is_direct_capture_supported(p0); });
+ return Value::Bool(result).ToJs(env);
+}
+
+napi_value Js_native_screen_capturer_select_source(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ native_capture_source_selection_options_t p0 = {};
+ if (!FromJs(env, args[0], &p0, arena)) {
+ return nullptr;
+ }
+ Callback* p1 = nullptr;
+ if (!GetCallback(env, args[1], /*optional=*/false, &p1)) {
+ return nullptr;
+ }
+ OnMainThread([&] { return native_screen_capturer_select_source(p0, +[](bool arg0, native_capture_source_t arg1, native_event_delivery_t delivery, void* user_data) { Callback::DispatchEvent(user_data, {Value::Bool(arg0), Value::BigInt(arg1)}, delivery); }, p1, &Callback::ReleaseUserData); });
+ return Undefined(env);
+}
+
+napi_value Js_native_screen_capturer_capture(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ native_capture_source_t p0 = {};
+ if (!GetHandle(env, args[0], &p0)) {
+ return nullptr;
+ }
+ native_screen_capture_options_t p1 = {};
+ if (!FromJs(env, args[1], &p1, arena)) {
+ return nullptr;
+ }
+ Callback* p2 = nullptr;
+ if (!GetCallback(env, args[2], /*optional=*/false, &p2)) {
+ return nullptr;
+ }
+ OnMainThread([&] { return native_screen_capturer_capture(p0, p1, +[](bool arg0, native_image_t arg1, native_event_delivery_t delivery, void* user_data) { Callback::DispatchEvent(user_data, {Value::Bool(arg0), Value::BigInt(arg1)}, delivery); }, p2, &Callback::ReleaseUserData); });
+ return Undefined(env);
+}
+
+} // namespace
+
+void RegisterScreenCapturer(napi_env env, napi_value exports) {
+ Export(env, exports, "native_screen_capturer_is_supported", Js_native_screen_capturer_is_supported);
+ Export(env, exports, "native_screen_capturer_is_source_selection_supported", Js_native_screen_capturer_is_source_selection_supported);
+ Export(env, exports, "native_screen_capturer_is_direct_capture_supported", Js_native_screen_capturer_is_direct_capture_supported);
+ Export(env, exports, "native_screen_capturer_select_source", Js_native_screen_capturer_select_source);
+ Export(env, exports, "native_screen_capturer_capture", Js_native_screen_capturer_capture);
+}
+
+} // namespace nativeapi_js
diff --git a/bindings/js/src/generated/screen_recorder.cc b/bindings/js/src/generated/screen_recorder.cc
new file mode 100644
index 00000000..beb0626c
--- /dev/null
+++ b/bindings/js/src/generated/screen_recorder.cc
@@ -0,0 +1,221 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+
+#include "types.h"
+
+namespace nativeapi_js {
+namespace {
+
+napi_value Js_native_screen_recorder_create(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ auto result = OnMainThread([&] { return native_screen_recorder_create(); });
+ return Value::BigInt(result).ToJs(env);
+}
+
+napi_value Js_native_screen_recorder_free(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ uint64_t self = 0;
+ if (!GetHandle(env, args[0], &self)) {
+ return nullptr;
+ }
+ OnMainThread([&] { return native_screen_recorder_free(self); });
+ return Undefined(env);
+}
+
+napi_value Js_native_screen_recorder_is_supported(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ auto result = OnMainThread([&] { return native_screen_recorder_is_supported(); });
+ return Value::Bool(result).ToJs(env);
+}
+
+napi_value Js_native_screen_recorder_start(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ uint64_t self = 0;
+ if (!GetHandle(env, args[0], &self)) {
+ return nullptr;
+ }
+ native_capture_source_t p0 = {};
+ if (!GetHandle(env, args[1], &p0)) {
+ return nullptr;
+ }
+ const char* p1 = {};
+ if (!GetString(env, args[2], arena, &p1)) {
+ return nullptr;
+ }
+ native_screen_recording_options_t p2 = {};
+ if (!FromJs(env, args[3], &p2, arena)) {
+ return nullptr;
+ }
+ auto result = OnMainThread([&] { return native_screen_recorder_start(self, p0, p1, p2); });
+ return Value::Bool(result).ToJs(env);
+}
+
+napi_value Js_native_screen_recorder_stop(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ uint64_t self = 0;
+ if (!GetHandle(env, args[0], &self)) {
+ return nullptr;
+ }
+ auto result = OnMainThread([&] { return native_screen_recorder_stop(self); });
+ return Value::Bool(result).ToJs(env);
+}
+
+napi_value Js_native_screen_recorder_cancel(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ uint64_t self = 0;
+ if (!GetHandle(env, args[0], &self)) {
+ return nullptr;
+ }
+ auto result = OnMainThread([&] { return native_screen_recorder_cancel(self); });
+ return Value::Bool(result).ToJs(env);
+}
+
+napi_value Js_native_screen_recorder_get_state(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ uint64_t self = 0;
+ if (!GetHandle(env, args[0], &self)) {
+ return nullptr;
+ }
+ auto result = OnMainThread([&] { return native_screen_recorder_get_state(self); });
+ return Value::Number(static_cast(result)).ToJs(env);
+}
+
+napi_value Js_native_screen_recorder_is_active(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ uint64_t self = 0;
+ if (!GetHandle(env, args[0], &self)) {
+ return nullptr;
+ }
+ auto result = OnMainThread([&] { return native_screen_recorder_is_active(self); });
+ return Value::Bool(result).ToJs(env);
+}
+
+napi_value Js_native_screen_recorder_get_duration(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ uint64_t self = 0;
+ if (!GetHandle(env, args[0], &self)) {
+ return nullptr;
+ }
+ auto result = OnMainThread([&] { return native_screen_recorder_get_duration(self); });
+ return Value::Number(static_cast(result)).ToJs(env);
+}
+
+napi_value Js_native_screen_recorder_get_file_path(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ Arena arena;
+ (void)arena;
+ uint64_t self = 0;
+ if (!GetHandle(env, args[0], &self)) {
+ return nullptr;
+ }
+ auto result = OnMainThread([&] { return native_screen_recorder_get_file_path(self); });
+ Value value = Value::String(result ? result : "");
+ free_c_str(result);
+ return value.ToJs(env);
+}
+
+napi_value Js_native_screen_recorder_add_listener(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ uint64_t self = 0;
+ if (!GetHandle(env, args[0], &self)) {
+ return nullptr;
+ }
+ Callback* callback = nullptr;
+ if (!GetCallback(env, args[1], /*optional=*/false, &callback)) {
+ return nullptr;
+ }
+ native_listener_id_t id = OnMainThread([&] { return native_screen_recorder_add_listener_async(self, +[](const native_screen_recording_event_t* event, native_event_delivery_t delivery, void* user_data) {
+ if (event != nullptr) {
+ Callback::DispatchEvent(user_data, {ToValue(*event)}, delivery);
+ } else { native_event_delivery_complete(delivery, false); }
+ }, callback, &Callback::ReleaseUserData); });
+ if (id) Callback::AttachRegistration(callback, [self, id] { (void)native_screen_recorder_remove_listener(self, id); });
+ return Value::Number(static_cast(id)).ToJs(env);
+}
+
+napi_value Js_native_screen_recorder_remove_listener(napi_env env, napi_callback_info info) {
+ Args args(env, info);
+ if (!args.ok()) {
+ return nullptr;
+ }
+ uint64_t self = 0;
+ if (!GetHandle(env, args[0], &self)) {
+ return nullptr;
+ }
+ native_listener_id_t id = 0;
+ if (!GetNumber(env, args[1], &id)) {
+ return nullptr;
+ }
+ bool removed = OnMainThread([&] { return native_screen_recorder_remove_listener(self, id); });
+ return Value::Bool(removed).ToJs(env);
+}
+
+} // namespace
+
+void RegisterScreenRecorder(napi_env env, napi_value exports) {
+ Export(env, exports, "native_screen_recorder_create", Js_native_screen_recorder_create);
+ Export(env, exports, "native_screen_recorder_free", Js_native_screen_recorder_free);
+ Export(env, exports, "native_screen_recorder_is_supported", Js_native_screen_recorder_is_supported);
+ Export(env, exports, "native_screen_recorder_start", Js_native_screen_recorder_start);
+ Export(env, exports, "native_screen_recorder_stop", Js_native_screen_recorder_stop);
+ Export(env, exports, "native_screen_recorder_cancel", Js_native_screen_recorder_cancel);
+ Export(env, exports, "native_screen_recorder_get_state", Js_native_screen_recorder_get_state);
+ Export(env, exports, "native_screen_recorder_is_active", Js_native_screen_recorder_is_active);
+ Export(env, exports, "native_screen_recorder_get_duration", Js_native_screen_recorder_get_duration);
+ Export(env, exports, "native_screen_recorder_get_file_path", Js_native_screen_recorder_get_file_path);
+ Export(env, exports, "native_screen_recorder_add_listener", Js_native_screen_recorder_add_listener);
+ Export(env, exports, "native_screen_recorder_remove_listener", Js_native_screen_recorder_remove_listener);
+}
+
+} // namespace nativeapi_js
diff --git a/bindings/js/src/generated/types.h b/bindings/js/src/generated/types.h
index 7c10a62a..9c93e8ca 100644
--- a/bindings/js/src/generated/types.h
+++ b/bindings/js/src/generated/types.h
@@ -25,6 +25,9 @@
#include "capi/file_dialog_c.h"
#include "capi/notification_manager_c.h"
#include "capi/image_c.h"
+#include "capi/capture_source_c.h"
+#include "capi/screen_capturer_c.h"
+#include "capi/screen_recorder_c.h"
#include "capi/window_shape_c.h"
#include "capi/window_shadow_c.h"
#include "capi/view_c.h"
@@ -64,6 +67,13 @@ inline Value ToValue(const native_display_event_t& event);
inline Value ToValue(const native_url_open_result_t& value);
inline bool FromJs(napi_env env, napi_value value, native_url_open_result_t* out, Arena& arena);
inline Value ToValue(const native_notification_event_t& event);
+inline Value ToValue(const native_capture_source_selection_options_t& value);
+inline bool FromJs(napi_env env, napi_value value, native_capture_source_selection_options_t* out, Arena& arena);
+inline Value ToValue(const native_screen_capture_options_t& value);
+inline bool FromJs(napi_env env, napi_value value, native_screen_capture_options_t* out, Arena& arena);
+inline Value ToValue(const native_screen_recording_options_t& value);
+inline bool FromJs(napi_env env, napi_value value, native_screen_recording_options_t* out, Arena& arena);
+inline Value ToValue(const native_screen_recording_event_t& event);
inline Value ToValue(const native_view_event_t& event);
inline Value ToValue(const native_window_event_t& event);
inline Value ToValue(const native_window_drag_event_t& event);
@@ -469,6 +479,150 @@ inline Value ToValue(const native_notification_event_t& event) {
return result;
}
+inline Value ToValue(const native_capture_source_selection_options_t& value) {
+ Value result = Value::Object();
+ result.Set("allowDisplays", Value::Bool(value.allow_displays));
+ result.Set("allowWindows", Value::Bool(value.allow_windows));
+ result.Set("parentWindow", Value::BigInt(value.parent_window));
+ return result;
+}
+
+inline bool FromJs(napi_env env, napi_value value, native_capture_source_selection_options_t* out, Arena& arena) {
+ if (!ExpectObject(env, value, "CaptureSourceSelectionOptions")) {
+ return false;
+ }
+ napi_value field = nullptr;
+ if (!GetField(env, value, "allowDisplays", &field)) {
+ return false;
+ }
+ if (field != nullptr) {
+ if (!GetBool(env, field, &out->allow_displays)) {
+ return false;
+ }
+ }
+ if (!GetField(env, value, "allowWindows", &field)) {
+ return false;
+ }
+ if (field != nullptr) {
+ if (!GetBool(env, field, &out->allow_windows)) {
+ return false;
+ }
+ }
+ if (!GetField(env, value, "parentWindow", &field)) {
+ return false;
+ }
+ if (field != nullptr) {
+ if (!GetHandle(env, field, &out->parent_window)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+inline Value ToValue(const native_screen_capture_options_t& value) {
+ Value result = Value::Object();
+ result.Set("includeCursor", Value::Bool(value.include_cursor));
+ result.Set("cropEnabled", Value::Bool(value.crop_enabled));
+ result.Set("crop", ToValue(value.crop));
+ return result;
+}
+
+inline bool FromJs(napi_env env, napi_value value, native_screen_capture_options_t* out, Arena& arena) {
+ if (!ExpectObject(env, value, "ScreenCaptureOptions")) {
+ return false;
+ }
+ napi_value field = nullptr;
+ if (!GetField(env, value, "includeCursor", &field)) {
+ return false;
+ }
+ if (field != nullptr) {
+ if (!GetBool(env, field, &out->include_cursor)) {
+ return false;
+ }
+ }
+ if (!GetField(env, value, "cropEnabled", &field)) {
+ return false;
+ }
+ if (field != nullptr) {
+ if (!GetBool(env, field, &out->crop_enabled)) {
+ return false;
+ }
+ }
+ if (!GetField(env, value, "crop", &field)) {
+ return false;
+ }
+ if (field != nullptr) {
+ if (!FromJs(env, field, &out->crop, arena)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+inline Value ToValue(const native_screen_recording_options_t& value) {
+ Value result = Value::Object();
+ result.Set("capture", ToValue(value.capture));
+ result.Set("frameRate", Value::Number(static_cast(value.frame_rate)));
+ result.Set("videoBitrate", Value::Number(static_cast(value.video_bitrate)));
+ return result;
+}
+
+inline bool FromJs(napi_env env, napi_value value, native_screen_recording_options_t* out, Arena& arena) {
+ if (!ExpectObject(env, value, "ScreenRecordingOptions")) {
+ return false;
+ }
+ napi_value field = nullptr;
+ if (!GetField(env, value, "capture", &field)) {
+ return false;
+ }
+ if (field != nullptr) {
+ if (!FromJs(env, field, &out->capture, arena)) {
+ return false;
+ }
+ }
+ if (!GetField(env, value, "frameRate", &field)) {
+ return false;
+ }
+ if (field != nullptr) {
+ if (!GetNumber(env, field, &out->frame_rate)) {
+ return false;
+ }
+ }
+ if (!GetField(env, value, "videoBitrate", &field)) {
+ return false;
+ }
+ if (field != nullptr) {
+ if (!GetNumber(env, field, &out->video_bitrate)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+inline Value ToValue(const native_screen_recording_event_t& event) {
+ Value result = Value::Object();
+ switch (event.type) {
+ case NATIVE_SCREEN_RECORDING_EVENT_TYPE_STARTED:
+ result.Set("type", Value::String("started"));
+ break;
+ case NATIVE_SCREEN_RECORDING_EVENT_TYPE_COMPLETED:
+ result.Set("type", Value::String("completed"));
+ result.Set("filePath", Value::String(event.data.completed.file_path));
+ result.Set("duration", Value::Number(static_cast(event.data.completed.duration)));
+ break;
+ case NATIVE_SCREEN_RECORDING_EVENT_TYPE_CANCELLED:
+ result.Set("type", Value::String("cancelled"));
+ break;
+ case NATIVE_SCREEN_RECORDING_EVENT_TYPE_FAILED:
+ result.Set("type", Value::String("failed"));
+ result.Set("message", Value::String(event.data.failed.message));
+ break;
+ default:
+ return Value::Null();
+ }
+ return result;
+}
+
inline Value ToValue(const native_view_event_t& event) {
Value result = Value::Object();
switch (event.type) {
diff --git a/bindings/js/test/screen_capture.test.ts b/bindings/js/test/screen_capture.test.ts
new file mode 100644
index 00000000..8b5ccf79
--- /dev/null
+++ b/bindings/js/test/screen_capture.test.ts
@@ -0,0 +1,28 @@
+// No UI or permission request: real addon conversions and rejected lifecycle.
+import assert from "node:assert/strict";
+import { test } from "node:test";
+import { CaptureSourceType, ScreenCapturer, ScreenRecorder, ScreenRecordingState } from "../lib/index.ts";
+
+test("static capture entry rejects invalid direct types", () => {
+ assert.equal(ScreenCapturer.isDirectCaptureSupported(CaptureSourceType.None), false);
+ assert.equal(ScreenCapturer.isDirectCaptureSupported(CaptureSourceType.Unknown), false);
+});
+
+test("nested recording options cross the ABI without creating an output", () => {
+ const recorder = ScreenRecorder.create()!;
+ try {
+ assert.equal(recorder.state, ScreenRecordingState.Idle);
+ assert.equal(recorder.start(null, "relative.mp4", {
+ capture: { includeCursor: false, cropEnabled: true, crop: { x: 1, y: 2, width: 32, height: 48 } },
+ frameRate: 30,
+ videoBitrate: 1_000_000,
+ }), false);
+ assert.equal(recorder.state, ScreenRecordingState.Idle);
+ assert.equal(recorder.isActive, false);
+ assert.equal(recorder.duration, 0);
+ assert.equal(recorder.stop(), false);
+ assert.equal(recorder.cancel(), false);
+ } finally {
+ recorder.dispose();
+ }
+});
diff --git a/bindings/python/nativeapi/__init__.py b/bindings/python/nativeapi/__init__.py
index 2d26d5a6..2e19a7bc 100644
--- a/bindings/python/nativeapi/__init__.py
+++ b/bindings/python/nativeapi/__init__.py
@@ -24,6 +24,10 @@
ApplicationStartedEvent,
Brightness,
)
+from .capture_source import (
+ CaptureSource,
+ CaptureSourceType,
+)
from .clipboard import (
Clipboard,
ClipboardChangedEvent,
@@ -131,6 +135,21 @@
from .preferences import (
Preferences,
)
+from .screen_capturer import (
+ CaptureSourceSelectionOptions,
+ ScreenCaptureOptions,
+ ScreenCapturer,
+)
+from .screen_recorder import (
+ ScreenRecorder,
+ ScreenRecordingCancelledEvent,
+ ScreenRecordingCompletedEvent,
+ ScreenRecordingEvent,
+ ScreenRecordingFailedEvent,
+ ScreenRecordingOptions,
+ ScreenRecordingStartedEvent,
+ ScreenRecordingState,
+)
from .secure_storage import (
SecureStorage,
)
@@ -240,6 +259,9 @@
"Brightness",
"Button",
"ButtonClickedEvent",
+ "CaptureSource",
+ "CaptureSourceSelectionOptions",
+ "CaptureSourceType",
"Clipboard",
"ClipboardChangedEvent",
"ClipboardData",
@@ -310,6 +332,16 @@
"Preferences",
"Rectangle",
"ResizeEdge",
+ "ScreenCaptureOptions",
+ "ScreenCapturer",
+ "ScreenRecorder",
+ "ScreenRecordingCancelledEvent",
+ "ScreenRecordingCompletedEvent",
+ "ScreenRecordingEvent",
+ "ScreenRecordingFailedEvent",
+ "ScreenRecordingOptions",
+ "ScreenRecordingStartedEvent",
+ "ScreenRecordingState",
"SecureStorage",
"Shortcut",
"ShortcutActivatedEvent",
diff --git a/bindings/python/nativeapi/_capi.py b/bindings/python/nativeapi/_capi.py
index 4f808fc7..46bd29b6 100644
--- a/bindings/python/nativeapi/_capi.py
+++ b/bindings/python/nativeapi/_capi.py
@@ -121,6 +121,34 @@ class native_notification_event_t(Structure):
pass
+class native_capture_source_selection_options_t(Structure):
+ pass
+
+
+class native_screen_capture_options_t(Structure):
+ pass
+
+
+class native_screen_recording_options_t(Structure):
+ pass
+
+
+class native_screen_recording_event_completed_t(Structure):
+ pass
+
+
+class native_screen_recording_event_failed_t(Structure):
+ pass
+
+
+class native_screen_recording_event_data_t(Union):
+ pass
+
+
+class native_screen_recording_event_t(Structure):
+ pass
+
+
class native_view_event_text_field_changed_t(Structure):
pass
@@ -300,6 +328,13 @@ class native_window_list_t(Structure):
POINTER(native_application_event_t),
c_void_p,
)
+native_callback_bool_capture_source_payload_t = CFUNCTYPE(
+ None,
+ c_bool,
+ c_uint64,
+ c_uint64,
+ c_void_p,
+)
native_callback_bool_clipboard_data_payload_t = CFUNCTYPE(
None,
c_bool,
@@ -360,6 +395,11 @@ class native_window_list_t(Structure):
c_void_p,
)
native_release_user_data_t = CFUNCTYPE(None, c_void_p)
+native_screen_recording_event_callback_t = CFUNCTYPE(
+ None,
+ POINTER(native_screen_recording_event_t),
+ c_void_p,
+)
native_shortcut_event_callback_t = CFUNCTYPE(
None,
POINTER(native_shortcut_event_t),
@@ -455,6 +495,36 @@ class native_window_list_t(Structure):
("type", c_int),
("data", native_notification_event_data_t),
]
+native_capture_source_selection_options_t._fields_ = [
+ ("allow_displays", c_bool),
+ ("allow_windows", c_bool),
+ ("parent_window", c_uint64),
+]
+native_screen_capture_options_t._fields_ = [
+ ("include_cursor", c_bool),
+ ("crop_enabled", c_bool),
+ ("crop", native_rectangle_t),
+]
+native_screen_recording_options_t._fields_ = [
+ ("capture", native_screen_capture_options_t),
+ ("frame_rate", c_double),
+ ("video_bitrate", c_ulong),
+]
+native_screen_recording_event_completed_t._fields_ = [
+ ("file_path", c_char_p),
+ ("duration", c_double),
+]
+native_screen_recording_event_failed_t._fields_ = [
+ ("message", c_char_p),
+]
+native_screen_recording_event_data_t._fields_ = [
+ ("completed", native_screen_recording_event_completed_t),
+ ("failed", native_screen_recording_event_failed_t),
+]
+native_screen_recording_event_t._fields_ = [
+ ("type", c_int),
+ ("data", native_screen_recording_event_data_t),
+]
native_view_event_text_field_changed_t._fields_ = [
("text", c_char_p),
]
@@ -1639,6 +1709,183 @@ class native_window_list_t(Structure):
],
)
+# capture_source.h
+
+native_capture_source_free = function("native_capture_source_free", None, [c_uint64])
+native_capture_source_from_display = function(
+ "native_capture_source_from_display",
+ c_uint64,
+ [
+ c_uint64,
+ ],
+)
+native_capture_source_from_window = function(
+ "native_capture_source_from_window",
+ c_uint64,
+ [
+ c_uint64,
+ ],
+)
+native_capture_source_get_type = function(
+ "native_capture_source_get_type",
+ c_int,
+ [
+ c_uint64,
+ ],
+)
+native_capture_source_get_name = function(
+ "native_capture_source_get_name",
+ c_void_p,
+ [
+ c_uint64,
+ ],
+)
+
+# screen_capturer.h
+
+native_capture_source_selection_options_free = function(
+ "native_capture_source_selection_options_free",
+ None,
+ [
+ POINTER(native_capture_source_selection_options_t),
+ ],
+)
+native_screen_capture_options_free = function(
+ "native_screen_capture_options_free",
+ None,
+ [
+ POINTER(native_screen_capture_options_t),
+ ],
+)
+native_screen_capturer_is_supported = function(
+ "native_screen_capturer_is_supported",
+ c_bool,
+ [
+ ],
+)
+native_screen_capturer_is_source_selection_supported = function(
+ "native_screen_capturer_is_source_selection_supported",
+ c_bool,
+ [
+ ],
+)
+native_screen_capturer_is_direct_capture_supported = function(
+ "native_screen_capturer_is_direct_capture_supported",
+ c_bool,
+ [
+ c_int,
+ ],
+)
+native_screen_capturer_select_source = function(
+ "native_screen_capturer_select_source",
+ None,
+ [
+ native_capture_source_selection_options_t,
+ native_callback_bool_capture_source_payload_t,
+ c_void_p,
+ native_release_user_data_t,
+ ],
+)
+native_screen_capturer_capture = function(
+ "native_screen_capturer_capture",
+ None,
+ [
+ c_uint64,
+ native_screen_capture_options_t,
+ native_callback_bool_image_payload_t,
+ c_void_p,
+ native_release_user_data_t,
+ ],
+)
+
+# screen_recorder.h
+
+native_screen_recording_options_free = function(
+ "native_screen_recording_options_free",
+ None,
+ [
+ POINTER(native_screen_recording_options_t),
+ ],
+)
+native_screen_recorder_free = function("native_screen_recorder_free", None, [c_uint64])
+native_screen_recorder_create = function("native_screen_recorder_create", c_uint64, [])
+native_screen_recorder_is_supported = function(
+ "native_screen_recorder_is_supported",
+ c_bool,
+ [
+ ],
+)
+native_screen_recorder_start = function(
+ "native_screen_recorder_start",
+ c_bool,
+ [
+ c_uint64,
+ c_uint64,
+ c_char_p,
+ native_screen_recording_options_t,
+ ],
+)
+native_screen_recorder_stop = function(
+ "native_screen_recorder_stop",
+ c_bool,
+ [
+ c_uint64,
+ ],
+)
+native_screen_recorder_cancel = function(
+ "native_screen_recorder_cancel",
+ c_bool,
+ [
+ c_uint64,
+ ],
+)
+native_screen_recorder_get_state = function(
+ "native_screen_recorder_get_state",
+ c_int,
+ [
+ c_uint64,
+ ],
+)
+native_screen_recorder_is_active = function(
+ "native_screen_recorder_is_active",
+ c_bool,
+ [
+ c_uint64,
+ ],
+)
+native_screen_recorder_get_duration = function(
+ "native_screen_recorder_get_duration",
+ c_double,
+ [
+ c_uint64,
+ ],
+)
+native_screen_recorder_get_file_path = function(
+ "native_screen_recorder_get_file_path",
+ c_void_p,
+ [
+ c_uint64,
+ ],
+)
+native_screen_recorder_add_listener = function(
+ "native_screen_recorder_add_listener",
+ c_uint64,
+ [
+ c_uint64,
+ native_screen_recording_event_callback_t,
+ c_void_p,
+ native_release_user_data_t,
+ ],
+)
+native_screen_recorder_remove_listener = function(
+ "native_screen_recorder_remove_listener",
+ c_bool,
+ [
+ c_uint64,
+ c_uint64,
+ ],
+)
+
# window_shape.h
native_window_shape_free = function("native_window_shape_free", None, [c_uint64])
diff --git a/bindings/python/nativeapi/capture_source.py b/bindings/python/nativeapi/capture_source.py
new file mode 100644
index 00000000..a6cae08e
--- /dev/null
+++ b/bindings/python/nativeapi/capture_source.py
@@ -0,0 +1,50 @@
+# AUTO-GENERATED. DO NOT EDIT.
+# Any manual changes WILL BE LOST when this file is regenerated.
+"""Generated from capture_source.h."""
+
+from __future__ import annotations
+
+import enum
+
+from . import _capi as _C
+from . import _runtime as _rt
+from . import display as _display
+from . import window as _window
+
+
+class CaptureSourceType(enum.IntEnum):
+ NONE = 0
+ DISPLAY = 1
+ WINDOW = 2
+ UNKNOWN = 3
+
+
+class CaptureSource(_rt.NativeObject):
+ """Owned reference to a native CaptureSource.
+
+ `dispose()` (or `with`) releases it; otherwise it is released when the
+ wrapper is garbage collected.
+ """
+
+ __slots__ = ()
+ _free = staticmethod(_C.native_capture_source_free)
+
+ @staticmethod
+ def from_display(display: _display.Display | None) -> CaptureSource | None:
+ raw = _C.native_capture_source_from_display(_rt.handle_of(display))
+ return CaptureSource._owned(raw)
+
+ @staticmethod
+ def from_window(window: _window.Window | None) -> CaptureSource | None:
+ raw = _C.native_capture_source_from_window(_rt.handle_of(window))
+ return CaptureSource._owned(raw)
+
+ @property
+ def type(self) -> CaptureSourceType:
+ raw = _C.native_capture_source_get_type(self._handle)
+ return _rt.to_enum(CaptureSourceType, raw)
+
+ @property
+ def name(self) -> str:
+ raw = _C.native_capture_source_get_name(self._handle)
+ return _rt.take_str(raw)
diff --git a/bindings/python/nativeapi/screen_capturer.py b/bindings/python/nativeapi/screen_capturer.py
new file mode 100644
index 00000000..ec04c18f
--- /dev/null
+++ b/bindings/python/nativeapi/screen_capturer.py
@@ -0,0 +1,128 @@
+# AUTO-GENERATED. DO NOT EDIT.
+# Any manual changes WILL BE LOST when this file is regenerated.
+"""Generated from screen_capturer.h."""
+
+from __future__ import annotations
+
+from collections.abc import Callable
+from dataclasses import dataclass, field
+
+from . import _capi as _C
+from . import _runtime as _rt
+from . import capture_source as _capture_source
+from . import geometry as _geometry
+from . import image as _image
+from . import window as _window
+
+
+@dataclass
+class CaptureSourceSelectionOptions:
+ allow_displays: bool = False
+ allow_windows: bool = False
+ parent_window: _window.Window | None = None
+
+ @classmethod
+ def _from_c(
+ cls,
+ raw: _C.native_capture_source_selection_options_t,
+ ) -> CaptureSourceSelectionOptions:
+ return cls(
+ bool(raw.allow_displays),
+ bool(raw.allow_windows),
+ _window.Window._owned(_rt.retain_handle(raw.parent_window)),
+ )
+
+ def _to_c(self) -> _C.native_capture_source_selection_options_t:
+ raw = _C.native_capture_source_selection_options_t()
+ raw.allow_displays = self.allow_displays
+ raw.allow_windows = self.allow_windows
+ raw.parent_window = _rt.handle_of(self.parent_window)
+ return raw
+
+
+@dataclass
+class ScreenCaptureOptions:
+ include_cursor: bool = False
+ crop_enabled: bool = False
+ crop: _geometry.Rectangle = field(default_factory=lambda: _geometry.Rectangle())
+
+ @classmethod
+ def _from_c(cls, raw: _C.native_screen_capture_options_t) -> ScreenCaptureOptions:
+ return cls(
+ bool(raw.include_cursor),
+ bool(raw.crop_enabled),
+ _geometry.Rectangle._from_c(raw.crop),
+ )
+
+ def _to_c(self) -> _C.native_screen_capture_options_t:
+ raw = _C.native_screen_capture_options_t()
+ raw.include_cursor = self.include_cursor
+ raw.crop_enabled = self.crop_enabled
+ raw.crop = self.crop._to_c()
+ return raw
+
+
+class ScreenCapturer:
+ def __init__(self) -> None:
+ raise TypeError("ScreenCapturer contains only static methods")
+
+ @staticmethod
+ def is_supported() -> bool:
+ raw = _C.native_screen_capturer_is_supported()
+ return raw
+
+ @staticmethod
+ def is_source_selection_supported() -> bool:
+ raw = _C.native_screen_capturer_is_source_selection_supported()
+ return raw
+
+ @staticmethod
+ def is_direct_capture_supported(type: _capture_source.CaptureSourceType) -> bool:
+ raw = _C.native_screen_capturer_is_direct_capture_supported(int(type))
+ return raw
+
+ @staticmethod
+ def select_source(
+ options: CaptureSourceSelectionOptions,
+ callback: Callable[[bool, _capture_source.CaptureSource | None], None],
+ ) -> None:
+ native_callback = _rt.make_callback(
+ _C.native_callback_bool_capture_source_payload_t,
+ lambda a0, a1, delivery, _user_data: _rt.deliver_callback(
+ delivery,
+ lambda: callback(
+ bool(a0),
+ _capture_source.CaptureSource._owned(_rt.retain_handle(a1))
+ ),
+ ),
+ )
+ _C.native_screen_capturer_select_source(
+ options._to_c(),
+ native_callback,
+ _rt.user_data(native_callback),
+ _rt.release_user_data,
+ )
+
+ @staticmethod
+ def capture(
+ source: _capture_source.CaptureSource | None,
+ options: ScreenCaptureOptions,
+ callback: Callable[[bool, _image.Image | None], None],
+ ) -> None:
+ native_callback = _rt.make_callback(
+ _C.native_callback_bool_image_payload_t,
+ lambda a0, a1, delivery, _user_data: _rt.deliver_callback(
+ delivery,
+ lambda: callback(
+ bool(a0),
+ _image.Image._owned(_rt.retain_handle(a1))
+ ),
+ ),
+ )
+ _C.native_screen_capturer_capture(
+ _rt.handle_of(source),
+ options._to_c(),
+ native_callback,
+ _rt.user_data(native_callback),
+ _rt.release_user_data,
+ )
diff --git a/bindings/python/nativeapi/screen_recorder.py b/bindings/python/nativeapi/screen_recorder.py
new file mode 100644
index 00000000..d2634ce8
--- /dev/null
+++ b/bindings/python/nativeapi/screen_recorder.py
@@ -0,0 +1,184 @@
+# AUTO-GENERATED. DO NOT EDIT.
+# Any manual changes WILL BE LOST when this file is regenerated.
+"""Generated from screen_recorder.h."""
+
+from __future__ import annotations
+
+import enum
+from collections.abc import Callable
+from dataclasses import dataclass, field
+
+from . import _capi as _C
+from . import _runtime as _rt
+from . import capture_source as _capture_source
+from . import screen_capturer as _screen_capturer
+
+
+class ScreenRecordingState(enum.IntEnum):
+ IDLE = 0
+ STARTING = 1
+ RECORDING = 2
+ STOPPING = 3
+ COMPLETED = 4
+ CANCELLED = 5
+ FAILED = 6
+
+
+@dataclass
+class ScreenRecordingOptions:
+ capture: _screen_capturer.ScreenCaptureOptions = (
+ field(default_factory=lambda: _screen_capturer.ScreenCaptureOptions())
+ )
+ frame_rate: float = 0.0
+ video_bitrate: int = 0
+
+ @classmethod
+ def _from_c(
+ cls,
+ raw: _C.native_screen_recording_options_t,
+ ) -> ScreenRecordingOptions:
+ return cls(
+ _screen_capturer.ScreenCaptureOptions._from_c(raw.capture),
+ raw.frame_rate,
+ raw.video_bitrate,
+ )
+
+ def _to_c(self) -> _C.native_screen_recording_options_t:
+ raw = _C.native_screen_recording_options_t()
+ raw.capture = self.capture._to_c()
+ raw.frame_rate = self.frame_rate
+ raw.video_bitrate = self.video_bitrate
+ return raw
+
+
+@dataclass(frozen=True)
+class ScreenRecordingEvent:
+ """Base of every ScreenRecordingEvent; listeners receive one of its subclasses."""
+
+ @staticmethod
+ def _from_c(raw: _C.native_screen_recording_event_t) -> ScreenRecordingEvent | None:
+ if raw.type == 0:
+ return ScreenRecordingStartedEvent()
+ if raw.type == 1:
+ return ScreenRecordingCompletedEvent(
+ _rt.decode(raw.data.completed.file_path),
+ raw.data.completed.duration,
+ )
+ if raw.type == 2:
+ return ScreenRecordingCancelledEvent()
+ if raw.type == 3:
+ return ScreenRecordingFailedEvent(_rt.decode(raw.data.failed.message))
+ return None
+
+
+@dataclass(frozen=True)
+class ScreenRecordingStartedEvent(ScreenRecordingEvent):
+ pass
+
+
+@dataclass(frozen=True)
+class ScreenRecordingCompletedEvent(ScreenRecordingEvent):
+ file_path: str
+ duration: float
+
+
+@dataclass(frozen=True)
+class ScreenRecordingCancelledEvent(ScreenRecordingEvent):
+ pass
+
+
+@dataclass(frozen=True)
+class ScreenRecordingFailedEvent(ScreenRecordingEvent):
+ message: str
+
+
+class ScreenRecorder(_rt.NativeObject):
+ """Owned reference to a native ScreenRecorder.
+
+ `dispose()` (or `with`) releases it; otherwise it is released when the
+ wrapper is garbage collected.
+ """
+
+ __slots__ = ()
+ _free = staticmethod(_C.native_screen_recorder_free)
+
+ def __init__(self) -> None:
+ handle = _C.native_screen_recorder_create()
+ if not handle:
+ raise _rt.NativeApiError("failed to create a ScreenRecorder")
+ self._adopt(handle)
+
+ @staticmethod
+ def is_supported() -> bool:
+ raw = _C.native_screen_recorder_is_supported()
+ return raw
+
+ def start(
+ self,
+ source: _capture_source.CaptureSource | None,
+ file_path: str,
+ options: ScreenRecordingOptions,
+ ) -> bool:
+ raw = _C.native_screen_recorder_start(
+ self._handle,
+ _rt.handle_of(source),
+ _rt.encode(file_path),
+ options._to_c(),
+ )
+ return raw
+
+ def stop(self) -> bool:
+ raw = _C.native_screen_recorder_stop(self._handle)
+ return raw
+
+ def cancel(self) -> bool:
+ raw = _C.native_screen_recorder_cancel(self._handle)
+ return raw
+
+ @property
+ def state(self) -> ScreenRecordingState:
+ raw = _C.native_screen_recorder_get_state(self._handle)
+ return _rt.to_enum(ScreenRecordingState, raw)
+
+ @property
+ def is_active(self) -> bool:
+ raw = _C.native_screen_recorder_is_active(self._handle)
+ return raw
+
+ @property
+ def duration(self) -> float:
+ raw = _C.native_screen_recorder_get_duration(self._handle)
+ return raw
+
+ @property
+ def file_path(self) -> str:
+ raw = _C.native_screen_recorder_get_file_path(self._handle)
+ return _rt.take_str(raw)
+
+ def add_listener(self, callback: Callable[[ScreenRecordingEvent], None]) -> int:
+ """Calls `callback` with every ScreenRecordingEvent this ScreenRecorder emits.
+
+ Callbacks run synchronously; coroutine callbacks are not accepted.
+ Use an owned EventDecision for asynchronous request confirmation.
+ Returns the listener id for `remove_listener()`.
+ """
+
+ def trampoline(raw, _user_data):
+ event = ScreenRecordingEvent._from_c(raw.contents)
+ if event is not None:
+ _rt.deliver_event(callback, event)
+
+ return _rt.add_listener(
+ _C.native_screen_recorder_add_listener,
+ _C.native_screen_recording_event_callback_t,
+ trampoline,
+ self._handle,
+ )
+
+ def remove_listener(self, listener_id: int) -> bool:
+ """Unregisters a listener. Returns False if unknown."""
+ return _rt.remove_listener(
+ _C.native_screen_recorder_remove_listener,
+ listener_id,
+ self._handle,
+ )
diff --git a/bindings/python/tests/test_screen_capture.py b/bindings/python/tests/test_screen_capture.py
new file mode 100644
index 00000000..6b13ca9b
--- /dev/null
+++ b/bindings/python/tests/test_screen_capture.py
@@ -0,0 +1,35 @@
+"""No capture/UI: validate nested options and recorder state across the real ABI."""
+
+import pytest
+
+from nativeapi import (
+ CaptureSourceType,
+ Rectangle,
+ ScreenCaptureOptions,
+ ScreenCapturer,
+ ScreenRecorder,
+ ScreenRecordingOptions,
+ ScreenRecordingState,
+)
+
+
+def test_static_entry_and_invalid_direct_types():
+ with pytest.raises(TypeError):
+ ScreenCapturer()
+ assert not ScreenCapturer.is_direct_capture_supported(CaptureSourceType.NONE)
+ assert not ScreenCapturer.is_direct_capture_supported(CaptureSourceType.UNKNOWN)
+
+
+def test_nested_options_rejection_keeps_idle_recorder():
+ capture = ScreenCaptureOptions(False, True, Rectangle(1, 2, 32, 48))
+ options = ScreenRecordingOptions(capture, 30.0, 1_000_000)
+ # Exercises the actual nested ctypes value conversion independently of a
+ # desktop/permission; a relative destination is rejected before any capture.
+ with ScreenRecorder() as recorder:
+ assert recorder.state == ScreenRecordingState.IDLE
+ assert not recorder.start(None, "relative.mp4", options)
+ assert recorder.state == ScreenRecordingState.IDLE
+ assert recorder.duration == 0
+ assert not recorder.is_active
+ assert not recorder.stop()
+ assert not recorder.cancel()
diff --git a/bindings/rust/cnativeapi/build.rs b/bindings/rust/cnativeapi/build.rs
index b9ac4bd0..65a8b841 100644
--- a/bindings/rust/cnativeapi/build.rs
+++ b/bindings/rust/cnativeapi/build.rs
@@ -34,6 +34,39 @@ fn main() {
match target_os.as_str() {
"macos" => {
cmake_config.define("CMAKE_SYSTEM_NAME", "Darwin");
+ // Match Rust's deployment target so availability-annotated native
+ // APIs remain weak imports on older macOS versions.
+ println!("cargo:rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET");
+ let deployment_target = env::var("MACOSX_DEPLOYMENT_TARGET")
+ .ok()
+ .or_else(|| {
+ let output = std::process::Command::new(env::var("RUSTC").ok()?)
+ .args([
+ "--print",
+ "deployment-target",
+ "--target",
+ &env::var("TARGET").ok()?,
+ ])
+ .output()
+ .ok()?;
+ if !output.status.success() {
+ return None;
+ }
+ String::from_utf8(output.stdout)
+ .ok()?
+ .trim()
+ .split_once('=')
+ .map(|(_, value)| value.to_owned())
+ })
+ .unwrap_or_else(|| {
+ if _target_arch == "aarch64" {
+ "11.0"
+ } else {
+ "10.12"
+ }
+ .into()
+ });
+ cmake_config.define("CMAKE_OSX_DEPLOYMENT_TARGET", deployment_target);
// Mirrors src/CMakeLists.txt. Cocoa alone leaves the global
// shortcut and launch-at-login implementations unresolved: hotkeys
// go through Carbon's RegisterEventHotKey, and login items through
@@ -42,6 +75,11 @@ fn main() {
println!("cargo:rustc-link-lib=framework=QuartzCore");
println!("cargo:rustc-link-lib=framework=Carbon");
println!("cargo:rustc-link-lib=framework=ServiceManagement");
+ // SCK is autolinked by the capture object, so an application not
+ // using capture does not acquire an unconditional SCK dependency.
+ for framework in ["AVFoundation", "CoreMedia", "CoreVideo", "VideoToolbox"] {
+ println!("cargo:rustc-link-lib=framework={framework}");
+ }
}
"ios" => {
cmake_config.define("CMAKE_SYSTEM_NAME", "iOS");
@@ -63,7 +101,22 @@ fn main() {
println!("cargo:rustc-link-lib=dwmapi");
println!("cargo:rustc-link-lib=gdiplus");
println!("cargo:rustc-link-lib=crypt32");
- for library in ["comctl32", "advapi32", "version", "ole32", "uuid", "gdi32"] {
+ for library in [
+ "comctl32",
+ "advapi32",
+ "version",
+ "ole32",
+ "uuid",
+ "gdi32",
+ "d3d11",
+ "dxgi",
+ "windowsapp",
+ "runtimeobject",
+ "mfplat",
+ "mfreadwrite",
+ "mfuuid",
+ "windowscodecs",
+ ] {
println!("cargo:rustc-link-lib={library}");
}
}
diff --git a/bindings/rust/cnativeapi/src/bindings.rs b/bindings/rust/cnativeapi/src/bindings.rs
index 03624860..1d84ff4f 100644
--- a/bindings/rust/cnativeapi/src/bindings.rs
+++ b/bindings/rust/cnativeapi/src/bindings.rs
@@ -128,12 +128,28 @@ pub struct native_edge_insets_t {
pub bottom: f64,
pub left: f64,
}
+unsafe extern "C" {
+ #[doc = " Frees everything the struct owns."]
+ pub fn native_point_free(value: *mut native_point_t);
+}
+unsafe extern "C" {
+ #[doc = " Frees everything the struct owns."]
+ pub fn native_size_free(value: *mut native_size_t);
+}
+unsafe extern "C" {
+ #[doc = " Frees everything the struct owns."]
+ pub fn native_rectangle_free(value: *mut native_rectangle_t);
+}
unsafe extern "C" {
pub fn native_edge_insets_all(value: f64) -> native_edge_insets_t;
}
unsafe extern "C" {
pub fn native_edge_insets_symmetric(vertical: f64, horizontal: f64) -> native_edge_insets_t;
}
+unsafe extern "C" {
+ #[doc = " Frees everything the struct owns."]
+ pub fn native_edge_insets_free(value: *mut native_edge_insets_t);
+}
unsafe extern "C" {
#[doc = " Caller owns the returned handle; release it with native_image_free()."]
pub fn native_image_from_file(file_path: *const ::std::os::raw::c_char) -> native_image_t;
@@ -332,6 +348,10 @@ unsafe extern "C" {
unsafe extern "C" {
pub fn native_color_to_argb(color: native_color_t) -> ::std::os::raw::c_uint;
}
+unsafe extern "C" {
+ #[doc = " Frees everything the struct owns."]
+ pub fn native_color_free(value: *mut native_color_t);
+}
pub type native_view_id_t = ::std::os::raw::c_uint;
pub const NATIVE_VIEW_LAYOUT_ABSOLUTE: native_view_layout_t = 0;
pub const NATIVE_VIEW_LAYOUT_ROW: native_view_layout_t = 1;
@@ -1986,6 +2006,150 @@ unsafe extern "C" {
#[doc = " Unregisters a listener. Returns false if unknown."]
pub fn native_application_remove_listener(listener_id: native_listener_id_t) -> bool;
}
+#[doc = " Opaque Display handle.\n\n A generational index into the library's handle table, NOT a pointer:\n never dereference it, and compare it against NATIVE_INVALID_DISPLAY rather than NULL.\n Releasing a handle invalidates it; later calls fail safely instead of\n touching freed memory."]
+pub type native_display_t = u64;
+pub type native_display_id_t = ::std::os::raw::c_uint;
+pub const NATIVE_DISPLAY_ORIENTATION_PORTRAIT: native_display_orientation_t = 0;
+pub const NATIVE_DISPLAY_ORIENTATION_LANDSCAPE: native_display_orientation_t = 90;
+pub const NATIVE_DISPLAY_ORIENTATION_PORTRAIT_FLIPPED: native_display_orientation_t = 180;
+pub const NATIVE_DISPLAY_ORIENTATION_LANDSCAPE_FLIPPED: native_display_orientation_t = 270;
+pub type native_display_orientation_t = ::std::os::raw::c_uint;
+#[doc = " Owning list of Display handles."]
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct native_display_list_t {
+ pub displays: *mut native_display_t,
+ pub count: ::std::os::raw::c_long,
+}
+impl Default for native_display_list_t {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub const NATIVE_DISPLAY_EVENT_TYPE_ADDED: native_display_event_type_t = 0;
+pub const NATIVE_DISPLAY_EVENT_TYPE_REMOVED: native_display_event_type_t = 1;
+pub const NATIVE_DISPLAY_EVENT_TYPE_CHANGED: native_display_event_type_t = 2;
+#[doc = " Which concrete DisplayEvent arrived."]
+pub type native_display_event_type_t = ::std::os::raw::c_uint;
+#[doc = " One DisplayEvent, tagged by its concrete type.\n\n Synchronous callbacks borrow this payload until they return. Async callbacks\n borrow it until event_delivery_complete. Copy anything needed after that."]
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct native_display_event_t {
+ pub type_: native_display_event_type_t,
+ pub display: native_display_t,
+}
+impl Default for native_display_event_t {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type native_display_event_callback_t = ::std::option::Option<
+ unsafe extern "C" fn(
+ event: *const native_display_event_t,
+ user_data: *mut ::std::os::raw::c_void,
+ ),
+>;
+pub type native_display_event_callback_t_async = ::std::option::Option<
+ unsafe extern "C" fn(
+ event: *const native_display_event_t,
+ delivery: native_event_delivery_t,
+ user_data: *mut ::std::os::raw::c_void,
+ ),
+>;
+unsafe extern "C" {
+ #[doc = " Creates a Display instance; release it with native_display_free()."]
+ pub fn native_display_create(display: *mut ::std::os::raw::c_void) -> native_display_t;
+}
+unsafe extern "C" {
+ pub fn native_display_get_id(display: native_display_t) -> native_display_id_t;
+}
+unsafe extern "C" {
+ #[doc = " Caller owns the returned string; free it with free_c_str()."]
+ pub fn native_display_get_name(display: native_display_t) -> *mut ::std::os::raw::c_char;
+}
+unsafe extern "C" {
+ pub fn native_display_get_position(display: native_display_t) -> native_point_t;
+}
+unsafe extern "C" {
+ pub fn native_display_get_size(display: native_display_t) -> native_size_t;
+}
+unsafe extern "C" {
+ pub fn native_display_get_work_area(display: native_display_t) -> native_rectangle_t;
+}
+unsafe extern "C" {
+ pub fn native_display_get_scale_factor(display: native_display_t) -> f64;
+}
+unsafe extern "C" {
+ pub fn native_display_is_primary(display: native_display_t) -> bool;
+}
+unsafe extern "C" {
+ pub fn native_display_get_orientation(
+ display: native_display_t,
+ ) -> native_display_orientation_t;
+}
+unsafe extern "C" {
+ pub fn native_display_get_refresh_rate(display: native_display_t) -> ::std::os::raw::c_int;
+}
+unsafe extern "C" {
+ pub fn native_display_get_bit_depth(display: native_display_t) -> ::std::os::raw::c_int;
+}
+unsafe extern "C" {
+ #[doc = " Platform-specific native object (NSScreen*, HMONITOR, ...)."]
+ pub fn native_display_get_native_object(
+ display: native_display_t,
+ ) -> *mut ::std::os::raw::c_void;
+}
+unsafe extern "C" {
+ #[doc = " Releases the caller's reference. Safe to call with an invalid or\n already-released handle."]
+ pub fn native_display_free(display: native_display_t);
+}
+unsafe extern "C" {
+ #[doc = " Frees the array and releases every handle it contains."]
+ pub fn native_display_list_free(list: *mut native_display_list_t);
+}
+unsafe extern "C" {
+ #[doc = " Frees only the array; the caller takes over the handles."]
+ pub fn native_display_list_release(list: *mut native_display_list_t);
+}
+pub const NATIVE_CAPTURE_SOURCE_TYPE_NONE: native_capture_source_type_t = 0;
+pub const NATIVE_CAPTURE_SOURCE_TYPE_DISPLAY: native_capture_source_type_t = 1;
+pub const NATIVE_CAPTURE_SOURCE_TYPE_WINDOW: native_capture_source_type_t = 2;
+pub const NATIVE_CAPTURE_SOURCE_TYPE_UNKNOWN: native_capture_source_type_t = 3;
+pub type native_capture_source_type_t = ::std::os::raw::c_uint;
+#[doc = " Opaque CaptureSource handle.\n\n A generational index into the library's handle table, NOT a pointer:\n never dereference it, and compare it against NATIVE_INVALID_CAPTURE_SOURCE rather than NULL.\n Releasing a handle invalidates it; later calls fail safely instead of\n touching freed memory."]
+pub type native_capture_source_t = u64;
+unsafe extern "C" {
+ #[doc = " Caller owns the returned handle; release it with native_capture_source_free()."]
+ pub fn native_capture_source_from_display(display: native_display_t)
+ -> native_capture_source_t;
+}
+unsafe extern "C" {
+ #[doc = " Caller owns the returned handle; release it with native_capture_source_free()."]
+ pub fn native_capture_source_from_window(window: native_window_t) -> native_capture_source_t;
+}
+unsafe extern "C" {
+ pub fn native_capture_source_get_type(
+ capture_source: native_capture_source_t,
+ ) -> native_capture_source_type_t;
+}
+unsafe extern "C" {
+ #[doc = " Caller owns the returned string; free it with free_c_str()."]
+ pub fn native_capture_source_get_name(
+ capture_source: native_capture_source_t,
+ ) -> *mut ::std::os::raw::c_char;
+}
+unsafe extern "C" {
+ #[doc = " Releases the caller's reference. Safe to call with an invalid or\n already-released handle."]
+ pub fn native_capture_source_free(capture_source: native_capture_source_t);
+}
#[doc = " An owning list of strings.\n\n Free with native_string_list_free(); it releases every item and the array."]
#[repr(C)]
#[derive(Debug, Copy, Clone)]
@@ -2242,119 +2406,6 @@ pub const NATIVE_DIALOG_MODALITY_NONE: native_dialog_modality_t = 0;
pub const NATIVE_DIALOG_MODALITY_APPLICATION: native_dialog_modality_t = 1;
pub const NATIVE_DIALOG_MODALITY_WINDOW: native_dialog_modality_t = 2;
pub type native_dialog_modality_t = ::std::os::raw::c_uint;
-pub type native_display_id_t = ::std::os::raw::c_uint;
-pub const NATIVE_DISPLAY_ORIENTATION_PORTRAIT: native_display_orientation_t = 0;
-pub const NATIVE_DISPLAY_ORIENTATION_LANDSCAPE: native_display_orientation_t = 90;
-pub const NATIVE_DISPLAY_ORIENTATION_PORTRAIT_FLIPPED: native_display_orientation_t = 180;
-pub const NATIVE_DISPLAY_ORIENTATION_LANDSCAPE_FLIPPED: native_display_orientation_t = 270;
-pub type native_display_orientation_t = ::std::os::raw::c_uint;
-#[doc = " Opaque Display handle.\n\n A generational index into the library's handle table, NOT a pointer:\n never dereference it, and compare it against NATIVE_INVALID_DISPLAY rather than NULL.\n Releasing a handle invalidates it; later calls fail safely instead of\n touching freed memory."]
-pub type native_display_t = u64;
-#[doc = " Owning list of Display handles."]
-#[repr(C)]
-#[derive(Debug, Copy, Clone)]
-pub struct native_display_list_t {
- pub displays: *mut native_display_t,
- pub count: ::std::os::raw::c_long,
-}
-impl Default for native_display_list_t {
- fn default() -> Self {
- let mut s = ::std::mem::MaybeUninit::::uninit();
- unsafe {
- ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
- s.assume_init()
- }
- }
-}
-pub const NATIVE_DISPLAY_EVENT_TYPE_ADDED: native_display_event_type_t = 0;
-pub const NATIVE_DISPLAY_EVENT_TYPE_REMOVED: native_display_event_type_t = 1;
-pub const NATIVE_DISPLAY_EVENT_TYPE_CHANGED: native_display_event_type_t = 2;
-#[doc = " Which concrete DisplayEvent arrived."]
-pub type native_display_event_type_t = ::std::os::raw::c_uint;
-#[doc = " One DisplayEvent, tagged by its concrete type.\n\n Synchronous callbacks borrow this payload until they return. Async callbacks\n borrow it until event_delivery_complete. Copy anything needed after that."]
-#[repr(C)]
-#[derive(Debug, Copy, Clone)]
-pub struct native_display_event_t {
- pub type_: native_display_event_type_t,
- pub display: native_display_t,
-}
-impl Default for native_display_event_t {
- fn default() -> Self {
- let mut s = ::std::mem::MaybeUninit::::uninit();
- unsafe {
- ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
- s.assume_init()
- }
- }
-}
-pub type native_display_event_callback_t = ::std::option::Option<
- unsafe extern "C" fn(
- event: *const native_display_event_t,
- user_data: *mut ::std::os::raw::c_void,
- ),
->;
-pub type native_display_event_callback_t_async = ::std::option::Option<
- unsafe extern "C" fn(
- event: *const native_display_event_t,
- delivery: native_event_delivery_t,
- user_data: *mut ::std::os::raw::c_void,
- ),
->;
-unsafe extern "C" {
- #[doc = " Creates a Display instance; release it with native_display_free()."]
- pub fn native_display_create(display: *mut ::std::os::raw::c_void) -> native_display_t;
-}
-unsafe extern "C" {
- pub fn native_display_get_id(display: native_display_t) -> native_display_id_t;
-}
-unsafe extern "C" {
- #[doc = " Caller owns the returned string; free it with free_c_str()."]
- pub fn native_display_get_name(display: native_display_t) -> *mut ::std::os::raw::c_char;
-}
-unsafe extern "C" {
- pub fn native_display_get_position(display: native_display_t) -> native_point_t;
-}
-unsafe extern "C" {
- pub fn native_display_get_size(display: native_display_t) -> native_size_t;
-}
-unsafe extern "C" {
- pub fn native_display_get_work_area(display: native_display_t) -> native_rectangle_t;
-}
-unsafe extern "C" {
- pub fn native_display_get_scale_factor(display: native_display_t) -> f64;
-}
-unsafe extern "C" {
- pub fn native_display_is_primary(display: native_display_t) -> bool;
-}
-unsafe extern "C" {
- pub fn native_display_get_orientation(
- display: native_display_t,
- ) -> native_display_orientation_t;
-}
-unsafe extern "C" {
- pub fn native_display_get_refresh_rate(display: native_display_t) -> ::std::os::raw::c_int;
-}
-unsafe extern "C" {
- pub fn native_display_get_bit_depth(display: native_display_t) -> ::std::os::raw::c_int;
-}
-unsafe extern "C" {
- #[doc = " Platform-specific native object (NSScreen*, HMONITOR, ...)."]
- pub fn native_display_get_native_object(
- display: native_display_t,
- ) -> *mut ::std::os::raw::c_void;
-}
-unsafe extern "C" {
- #[doc = " Releases the caller's reference. Safe to call with an invalid or\n already-released handle."]
- pub fn native_display_free(display: native_display_t);
-}
-unsafe extern "C" {
- #[doc = " Frees the array and releases every handle it contains."]
- pub fn native_display_list_free(list: *mut native_display_list_t);
-}
-unsafe extern "C" {
- #[doc = " Frees only the array; the caller takes over the handles."]
- pub fn native_display_list_release(list: *mut native_display_list_t);
-}
unsafe extern "C" {
pub fn native_display_manager_get_all() -> native_display_list_t;
}
@@ -3151,6 +3202,243 @@ unsafe extern "C" {
#[doc = " Releases the caller's reference. Safe to call with an invalid or\n already-released handle."]
pub fn native_preferences_free(preferences: native_preferences_t);
}
+#[doc = " Arguments are borrowed until event_delivery_complete(delivery, ...), which is required exactly once."]
+pub type native_screen_capturer_select_source_callback_t = ::std::option::Option<
+ unsafe extern "C" fn(
+ arg0: bool,
+ arg1: native_capture_source_t,
+ delivery: native_event_delivery_t,
+ user_data: *mut ::std::os::raw::c_void,
+ ),
+>;
+#[doc = " Arguments are borrowed until event_delivery_complete(delivery, ...), which is required exactly once."]
+pub type native_screen_capturer_capture_callback_t = ::std::option::Option<
+ unsafe extern "C" fn(
+ arg0: bool,
+ arg1: native_image_t,
+ delivery: native_event_delivery_t,
+ user_data: *mut ::std::os::raw::c_void,
+ ),
+>;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct native_capture_source_selection_options_t {
+ pub allow_displays: bool,
+ pub allow_windows: bool,
+ pub parent_window: native_window_t,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct native_screen_capture_options_t {
+ pub include_cursor: bool,
+ pub crop_enabled: bool,
+ pub crop: native_rectangle_t,
+}
+unsafe extern "C" {
+ #[doc = " Frees everything the struct owns."]
+ pub fn native_capture_source_selection_options_free(
+ value: *mut native_capture_source_selection_options_t,
+ );
+}
+unsafe extern "C" {
+ #[doc = " Frees everything the struct owns."]
+ pub fn native_screen_capture_options_free(value: *mut native_screen_capture_options_t);
+}
+unsafe extern "C" {
+ pub fn native_screen_capturer_is_supported() -> bool;
+}
+unsafe extern "C" {
+ pub fn native_screen_capturer_is_source_selection_supported() -> bool;
+}
+unsafe extern "C" {
+ pub fn native_screen_capturer_is_direct_capture_supported(
+ type_: native_capture_source_type_t,
+ ) -> bool;
+}
+unsafe extern "C" {
+ pub fn native_screen_capturer_select_source(
+ options: native_capture_source_selection_options_t,
+ callback: native_screen_capturer_select_source_callback_t,
+ callback_user_data: *mut ::std::os::raw::c_void,
+ callback_release_user_data: native_release_user_data_t,
+ );
+}
+unsafe extern "C" {
+ pub fn native_screen_capturer_capture(
+ source: native_capture_source_t,
+ options: native_screen_capture_options_t,
+ callback: native_screen_capturer_capture_callback_t,
+ callback_user_data: *mut ::std::os::raw::c_void,
+ callback_release_user_data: native_release_user_data_t,
+ );
+}
+pub const NATIVE_SCREEN_RECORDING_STATE_IDLE: native_screen_recording_state_t = 0;
+pub const NATIVE_SCREEN_RECORDING_STATE_STARTING: native_screen_recording_state_t = 1;
+pub const NATIVE_SCREEN_RECORDING_STATE_RECORDING: native_screen_recording_state_t = 2;
+pub const NATIVE_SCREEN_RECORDING_STATE_STOPPING: native_screen_recording_state_t = 3;
+pub const NATIVE_SCREEN_RECORDING_STATE_COMPLETED: native_screen_recording_state_t = 4;
+pub const NATIVE_SCREEN_RECORDING_STATE_CANCELLED: native_screen_recording_state_t = 5;
+pub const NATIVE_SCREEN_RECORDING_STATE_FAILED: native_screen_recording_state_t = 6;
+pub type native_screen_recording_state_t = ::std::os::raw::c_uint;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct native_screen_recording_options_t {
+ pub capture: native_screen_capture_options_t,
+ pub frame_rate: f64,
+ pub video_bitrate: ::std::os::raw::c_ulong,
+}
+#[doc = " Opaque ScreenRecorder handle.\n\n A generational index into the library's handle table, NOT a pointer:\n never dereference it, and compare it against NATIVE_INVALID_SCREEN_RECORDER rather than NULL.\n Releasing a handle invalidates it; later calls fail safely instead of\n touching freed memory."]
+pub type native_screen_recorder_t = u64;
+pub const NATIVE_SCREEN_RECORDING_EVENT_TYPE_STARTED: native_screen_recording_event_type_t = 0;
+pub const NATIVE_SCREEN_RECORDING_EVENT_TYPE_COMPLETED: native_screen_recording_event_type_t = 1;
+pub const NATIVE_SCREEN_RECORDING_EVENT_TYPE_CANCELLED: native_screen_recording_event_type_t = 2;
+pub const NATIVE_SCREEN_RECORDING_EVENT_TYPE_FAILED: native_screen_recording_event_type_t = 3;
+#[doc = " Which concrete ScreenRecordingEvent arrived."]
+pub type native_screen_recording_event_type_t = ::std::os::raw::c_uint;
+#[doc = " One ScreenRecordingEvent, tagged by its concrete type.\n\n Synchronous callbacks borrow this payload until they return. Async callbacks\n borrow it until event_delivery_complete. Copy anything needed after that."]
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct native_screen_recording_event_t {
+ pub type_: native_screen_recording_event_type_t,
+ pub data: native_screen_recording_event_t__bindgen_ty_1,
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union native_screen_recording_event_t__bindgen_ty_1 {
+ pub completed: native_screen_recording_event_t__bindgen_ty_1__bindgen_ty_1,
+ pub failed: native_screen_recording_event_t__bindgen_ty_1__bindgen_ty_2,
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct native_screen_recording_event_t__bindgen_ty_1__bindgen_ty_1 {
+ pub file_path: *mut ::std::os::raw::c_char,
+ pub duration: f64,
+}
+impl Default for native_screen_recording_event_t__bindgen_ty_1__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct native_screen_recording_event_t__bindgen_ty_1__bindgen_ty_2 {
+ pub message: *mut ::std::os::raw::c_char,
+}
+impl Default for native_screen_recording_event_t__bindgen_ty_1__bindgen_ty_2 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+impl Default for native_screen_recording_event_t__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+impl Default for native_screen_recording_event_t {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type native_screen_recording_event_callback_t = ::std::option::Option<
+ unsafe extern "C" fn(
+ event: *const native_screen_recording_event_t,
+ user_data: *mut ::std::os::raw::c_void,
+ ),
+>;
+pub type native_screen_recording_event_callback_t_async = ::std::option::Option<
+ unsafe extern "C" fn(
+ event: *const native_screen_recording_event_t,
+ delivery: native_event_delivery_t,
+ user_data: *mut ::std::os::raw::c_void,
+ ),
+>;
+unsafe extern "C" {
+ #[doc = " Frees everything the struct owns."]
+ pub fn native_screen_recording_options_free(value: *mut native_screen_recording_options_t);
+}
+unsafe extern "C" {
+ #[doc = " Creates a ScreenRecorder instance; release it with native_screen_recorder_free()."]
+ pub fn native_screen_recorder_create() -> native_screen_recorder_t;
+}
+unsafe extern "C" {
+ pub fn native_screen_recorder_is_supported() -> bool;
+}
+unsafe extern "C" {
+ pub fn native_screen_recorder_start(
+ screen_recorder: native_screen_recorder_t,
+ source: native_capture_source_t,
+ file_path: *const ::std::os::raw::c_char,
+ options: native_screen_recording_options_t,
+ ) -> bool;
+}
+unsafe extern "C" {
+ pub fn native_screen_recorder_stop(screen_recorder: native_screen_recorder_t) -> bool;
+}
+unsafe extern "C" {
+ pub fn native_screen_recorder_cancel(screen_recorder: native_screen_recorder_t) -> bool;
+}
+unsafe extern "C" {
+ pub fn native_screen_recorder_get_state(
+ screen_recorder: native_screen_recorder_t,
+ ) -> native_screen_recording_state_t;
+}
+unsafe extern "C" {
+ pub fn native_screen_recorder_is_active(screen_recorder: native_screen_recorder_t) -> bool;
+}
+unsafe extern "C" {
+ pub fn native_screen_recorder_get_duration(screen_recorder: native_screen_recorder_t) -> f64;
+}
+unsafe extern "C" {
+ #[doc = " Caller owns the returned string; free it with free_c_str()."]
+ pub fn native_screen_recorder_get_file_path(
+ screen_recorder: native_screen_recorder_t,
+ ) -> *mut ::std::os::raw::c_char;
+}
+unsafe extern "C" {
+ #[doc = " Releases the caller's reference. Safe to call with an invalid or\n already-released handle."]
+ pub fn native_screen_recorder_free(screen_recorder: native_screen_recorder_t);
+}
+unsafe extern "C" {
+ #[doc = " Registers @p callback for every ScreenRecordingEvent this ScreenRecorder emits.\n @return the listener id, or NATIVE_INVALID_LISTENER_ID on failure."]
+ pub fn native_screen_recorder_add_listener(
+ screen_recorder: native_screen_recorder_t,
+ callback: native_screen_recording_event_callback_t,
+ user_data: *mut ::std::os::raw::c_void,
+ release_user_data: native_release_user_data_t,
+ ) -> native_listener_id_t;
+}
+unsafe extern "C" {
+ #[doc = " Registers an asynchronous callback. Its event, borrowed handles and user_data\n remain valid until event_delivery_complete is called, including after removal.\n Every delivered payload must be acknowledged. Check is_active before invoking a queued callback."]
+ pub fn native_screen_recorder_add_listener_async(
+ screen_recorder: native_screen_recorder_t,
+ callback: native_screen_recording_event_callback_t_async,
+ user_data: *mut ::std::os::raw::c_void,
+ release_user_data: native_release_user_data_t,
+ ) -> native_listener_id_t;
+}
+unsafe extern "C" {
+ #[doc = " Unregisters a listener. Returns false if unknown."]
+ pub fn native_screen_recorder_remove_listener(
+ screen_recorder: native_screen_recorder_t,
+ listener_id: native_listener_id_t,
+ ) -> bool;
+}
#[doc = " Opaque SecureStorage handle.\n\n A generational index into the library's handle table, NOT a pointer:\n never dereference it, and compare it against NATIVE_INVALID_SECURE_STORAGE rather than NULL.\n Releasing a handle invalidates it; later calls fail safely instead of\n touching freed memory."]
pub type native_secure_storage_t = u64;
unsafe extern "C" {
diff --git a/bindings/rust/nativeapi/src/capture_source.rs b/bindings/rust/nativeapi/src/capture_source.rs
new file mode 100644
index 00000000..e2cd4a8e
--- /dev/null
+++ b/bindings/rust/nativeapi/src/capture_source.rs
@@ -0,0 +1,122 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+#![allow(dead_code)]
+#![allow(unused_imports)]
+
+use cnativeapi;
+use std::ffi::{CStr, CString};
+
+use crate::display::Display;
+use crate::window::Window;
+
+#[repr(i32)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum CaptureSourceType {
+ None = 0,
+ Display = 1,
+ Window = 2,
+ Unknown = 3,
+}
+
+impl CaptureSourceType {
+ pub(crate) fn from_raw(raw: cnativeapi::native_capture_source_type_t) -> Self {
+ match raw {
+ cnativeapi::NATIVE_CAPTURE_SOURCE_TYPE_NONE => Self::None,
+ cnativeapi::NATIVE_CAPTURE_SOURCE_TYPE_DISPLAY => Self::Display,
+ cnativeapi::NATIVE_CAPTURE_SOURCE_TYPE_WINDOW => Self::Window,
+ cnativeapi::NATIVE_CAPTURE_SOURCE_TYPE_UNKNOWN => Self::Unknown,
+ _ => Self::None,
+ }
+ }
+
+ pub(crate) fn to_raw(self) -> cnativeapi::native_capture_source_type_t {
+ self as cnativeapi::native_capture_source_type_t
+ }
+}
+
+/// Owned handle to a native `CaptureSource`.
+#[derive(Debug)]
+#[repr(transparent)]
+pub struct CaptureSource {
+ handle: cnativeapi::native_capture_source_t,
+}
+
+impl CaptureSource {
+ /// Adopts a handle returned by the C API.
+ ///
+ /// # Safety
+ /// `handle` must be a live handle that is not owned elsewhere.
+ pub unsafe fn from_raw(handle: cnativeapi::native_capture_source_t) -> Option {
+ if handle == 0 {
+ None
+ } else {
+ Some(Self { handle })
+ }
+ }
+
+ /// Borrows the underlying C handle.
+ pub fn as_raw(&self) -> cnativeapi::native_capture_source_t {
+ self.handle
+ }
+
+ pub fn from_display(display: Option<&Display>) -> Option {
+ unsafe {
+ CaptureSource::from_raw(cnativeapi::native_capture_source_from_display(display.map_or(0, |value| value.as_raw())))
+ }
+ }
+
+ pub fn from_window(window: Option<&Window>) -> Option {
+ unsafe {
+ CaptureSource::from_raw(cnativeapi::native_capture_source_from_window(window.map_or(0, |value| value.as_raw())))
+ }
+ }
+
+ pub fn r#type(&self) -> CaptureSourceType {
+ unsafe {
+ CaptureSourceType::from_raw(cnativeapi::native_capture_source_get_type(self.handle))
+ }
+ }
+
+ pub fn name(&self) -> Option {
+ unsafe {
+ let ptr = cnativeapi::native_capture_source_get_name(self.handle);
+ if ptr.is_null() {
+ return None;
+ }
+ let value = CStr::from_ptr(ptr).to_string_lossy().into_owned();
+ cnativeapi::free_c_str(ptr);
+ Some(value)
+ }
+ }
+
+}
+
+impl Drop for CaptureSource {
+ fn drop(&mut self) {
+ if self.handle != 0 {
+ unsafe { cnativeapi::native_capture_source_free(self.handle) };
+ }
+ }
+}
+
+/// A `CaptureSource` owned elsewhere; using it does not release it.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct CaptureSourceRef(cnativeapi::native_capture_source_t);
+
+impl CaptureSourceRef {
+ pub(crate) fn from_raw(handle: cnativeapi::native_capture_source_t) -> Self {
+ Self(handle)
+ }
+
+ pub fn as_raw(&self) -> cnativeapi::native_capture_source_t {
+ self.0
+ }
+
+ /// Runs `f` against the borrowed handle. The `CaptureSource` it receives
+ /// must not outlive the call.
+ pub fn with(&self, f: impl FnOnce(&CaptureSource) -> R) -> R {
+ let borrowed = std::mem::ManuallyDrop::new(CaptureSource { handle: self.0 });
+ f(&borrowed)
+ }
+}
+
diff --git a/bindings/rust/nativeapi/src/modules.rs b/bindings/rust/nativeapi/src/modules.rs
index 53fd70a6..92ded2de 100644
--- a/bindings/rust/nativeapi/src/modules.rs
+++ b/bindings/rust/nativeapi/src/modules.rs
@@ -6,6 +6,7 @@
pub mod accessibility_manager;
pub mod app_info;
pub mod application;
+pub mod capture_source;
pub mod clipboard;
pub mod color;
pub mod device_info;
@@ -27,6 +28,8 @@ pub mod notification_manager;
pub mod placement;
pub mod positioning_strategy;
pub mod preferences;
+pub mod screen_capturer;
+pub mod screen_recorder;
pub mod secure_storage;
pub mod shortcut;
pub mod shortcut_manager;
@@ -45,6 +48,7 @@ pub mod window_shape;
pub use accessibility_manager::{AccessibilityManager};
pub use app_info::{AppInfo};
pub use application::{Brightness, ApplicationEvent, Application};
+pub use capture_source::{CaptureSourceType, CaptureSource, CaptureSourceRef};
pub use clipboard::{ClipboardData, ClipboardEvent, Clipboard};
pub use color::{Color};
pub use device_info::{DeviceInfo};
@@ -66,6 +70,8 @@ pub use notification_manager::{NotificationEvent, NotificationManager};
pub use placement::{Placement};
pub use positioning_strategy::{PositioningStrategyType, PositioningStrategy, PositioningStrategyRef};
pub use preferences::{Preferences, PreferencesRef};
+pub use screen_capturer::{CaptureSourceSelectionOptions, ScreenCaptureOptions, ScreenCapturer};
+pub use screen_recorder::{ScreenRecordingState, ScreenRecordingOptions, ScreenRecordingEvent, ScreenRecorder, ScreenRecorderRef};
pub use secure_storage::{SecureStorage, SecureStorageRef};
pub use shortcut::{ShortcutId, ShortcutScope, ShortcutOptions, ShortcutEvent, Shortcut, ShortcutRef};
pub use shortcut_manager::{ShortcutManager};
diff --git a/bindings/rust/nativeapi/src/screen_capturer.rs b/bindings/rust/nativeapi/src/screen_capturer.rs
new file mode 100644
index 00000000..38e53c2b
--- /dev/null
+++ b/bindings/rust/nativeapi/src/screen_capturer.rs
@@ -0,0 +1,153 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+#![allow(dead_code)]
+#![allow(unused_imports)]
+
+use cnativeapi;
+use std::ffi::{CStr, CString};
+
+use crate::capture_source::{CaptureSource, CaptureSourceType};
+use crate::geometry::{RawOfRectangle, Rectangle};
+use crate::image::Image;
+use crate::window::Window;
+
+#[derive(Debug)]
+pub struct CaptureSourceSelectionOptions {
+ pub allow_displays: bool,
+ pub allow_windows: bool,
+ pub parent_window: Option,
+}
+
+impl CaptureSourceSelectionOptions {
+ pub(crate) unsafe fn from_raw(raw: &cnativeapi::native_capture_source_selection_options_t) -> Self {
+ Self {
+ allow_displays: raw.allow_displays,
+ allow_windows: raw.allow_windows,
+ parent_window: Window::from_raw(cnativeapi::native_handle_retain(raw.parent_window)),
+ }
+ }
+
+ pub(crate) fn to_raw(&self) -> RawOfCaptureSourceSelectionOptions {
+ let mut raw = cnativeapi::native_capture_source_selection_options_t::default();
+ raw.allow_displays = self.allow_displays;
+ raw.allow_windows = self.allow_windows;
+ raw.parent_window = self.parent_window.as_ref().map_or(0, |v| v.as_raw());
+ RawOfCaptureSourceSelectionOptions { raw, _owned: () }
+ }
+}
+
+/// `CaptureSourceSelectionOptions` in its C form, keeping any borrowed buffers alive.
+pub(crate) struct RawOfCaptureSourceSelectionOptions {
+ pub(crate) raw: cnativeapi::native_capture_source_selection_options_t,
+ _owned: (),
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct ScreenCaptureOptions {
+ pub include_cursor: bool,
+ pub crop_enabled: bool,
+ pub crop: Rectangle,
+}
+
+impl ScreenCaptureOptions {
+ pub(crate) unsafe fn from_raw(raw: &cnativeapi::native_screen_capture_options_t) -> Self {
+ Self {
+ include_cursor: raw.include_cursor,
+ crop_enabled: raw.crop_enabled,
+ crop: Rectangle::from_raw(&raw.crop),
+ }
+ }
+
+ pub(crate) fn to_raw(&self) -> RawOfScreenCaptureOptions {
+ let mut raw = cnativeapi::native_screen_capture_options_t::default();
+ raw.include_cursor = self.include_cursor;
+ raw.crop_enabled = self.crop_enabled;
+ let crop_nested = self.crop.to_raw();
+ raw.crop = crop_nested.raw;
+ RawOfScreenCaptureOptions { raw, _owned: (crop_nested, ()) }
+ }
+}
+
+/// `ScreenCaptureOptions` in its C form, keeping any borrowed buffers alive.
+pub(crate) struct RawOfScreenCaptureOptions {
+ pub(crate) raw: cnativeapi::native_screen_capture_options_t,
+ _owned: (RawOfRectangle, ()),
+}
+
+pub struct ScreenCapturer;
+
+impl ScreenCapturer {
+ pub fn is_supported() -> bool {
+ unsafe {
+ cnativeapi::native_screen_capturer_is_supported()
+ }
+ }
+
+ pub fn is_source_selection_supported() -> bool {
+ unsafe {
+ cnativeapi::native_screen_capturer_is_source_selection_supported()
+ }
+ }
+
+ pub fn is_direct_capture_supported(r#type: CaptureSourceType) -> bool {
+ unsafe {
+ cnativeapi::native_screen_capturer_is_direct_capture_supported(r#type.to_raw())
+ }
+ }
+
+ pub fn select_source(options: &CaptureSourceSelectionOptions, callback: impl Fn(bool, Option) + 'static) {
+ let options_raw = options.to_raw();
+ unsafe extern "C" fn trampoline(arg0: bool, arg1: u64, delivery: cnativeapi::native_event_delivery_t, user_data: *mut std::ffi::c_void) {
+ struct Delivery(cnativeapi::native_event_delivery_t);
+ impl Drop for Delivery { fn drop(&mut self) { unsafe { cnativeapi::native_event_delivery_complete(self.0, true); } } }
+ let _delivery = Delivery(delivery);
+ if !cnativeapi::native_event_delivery_is_active(delivery) { return; }
+ if user_data.is_null() {
+ return;
+ }
+ let callback = &*(user_data as *const std::sync::Arc)>);
+ let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(arg0, CaptureSource::from_raw(cnativeapi::native_handle_retain(arg1)))));
+ }
+ fn into_user_data(callback: std::sync::Arc)>) -> *mut std::ffi::c_void {
+ Box::into_raw(Box::new(callback)) as *mut std::ffi::c_void
+ }
+ unsafe extern "C" fn release(user_data: *mut std::ffi::c_void) {
+ if !user_data.is_null() {
+ drop(Box::from_raw(user_data as *mut std::sync::Arc)>));
+ }
+ }
+ let callback_user_data = into_user_data(std::sync::Arc::new(callback));
+ unsafe {
+ cnativeapi::native_screen_capturer_select_source(options_raw.raw, Some(trampoline), callback_user_data, Some(release));
+ }
+ }
+
+ pub fn capture(source: Option<&CaptureSource>, options: &ScreenCaptureOptions, callback: impl Fn(bool, Option) + 'static) {
+ let options_raw = options.to_raw();
+ unsafe extern "C" fn trampoline(arg0: bool, arg1: u64, delivery: cnativeapi::native_event_delivery_t, user_data: *mut std::ffi::c_void) {
+ struct Delivery(cnativeapi::native_event_delivery_t);
+ impl Drop for Delivery { fn drop(&mut self) { unsafe { cnativeapi::native_event_delivery_complete(self.0, true); } } }
+ let _delivery = Delivery(delivery);
+ if !cnativeapi::native_event_delivery_is_active(delivery) { return; }
+ if user_data.is_null() {
+ return;
+ }
+ let callback = &*(user_data as *const std::sync::Arc)>);
+ let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(arg0, Image::from_raw(cnativeapi::native_handle_retain(arg1)))));
+ }
+ fn into_user_data(callback: std::sync::Arc)>) -> *mut std::ffi::c_void {
+ Box::into_raw(Box::new(callback)) as *mut std::ffi::c_void
+ }
+ unsafe extern "C" fn release(user_data: *mut std::ffi::c_void) {
+ if !user_data.is_null() {
+ drop(Box::from_raw(user_data as *mut std::sync::Arc)>));
+ }
+ }
+ let callback_user_data = into_user_data(std::sync::Arc::new(callback));
+ unsafe {
+ cnativeapi::native_screen_capturer_capture(source.map_or(0, |value| value.as_raw()), options_raw.raw, Some(trampoline), callback_user_data, Some(release));
+ }
+ }
+
+}
+
diff --git a/bindings/rust/nativeapi/src/screen_recorder.rs b/bindings/rust/nativeapi/src/screen_recorder.rs
new file mode 100644
index 00000000..060eb5d4
--- /dev/null
+++ b/bindings/rust/nativeapi/src/screen_recorder.rs
@@ -0,0 +1,244 @@
+// AUTO-GENERATED. DO NOT EDIT.
+// Any manual changes WILL BE LOST when this file is regenerated.
+#![allow(dead_code)]
+#![allow(unused_imports)]
+
+use cnativeapi;
+use std::ffi::{CStr, CString};
+
+use crate::capture_source::CaptureSource;
+use crate::screen_capturer::{RawOfScreenCaptureOptions, ScreenCaptureOptions};
+
+/// Identifies one registered event listener.
+pub type ListenerId = cnativeapi::native_listener_id_t;
+
+#[repr(i32)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum ScreenRecordingState {
+ Idle = 0,
+ Starting = 1,
+ Recording = 2,
+ Stopping = 3,
+ Completed = 4,
+ Cancelled = 5,
+ Failed = 6,
+}
+
+impl ScreenRecordingState {
+ pub(crate) fn from_raw(raw: cnativeapi::native_screen_recording_state_t) -> Self {
+ match raw {
+ cnativeapi::NATIVE_SCREEN_RECORDING_STATE_IDLE => Self::Idle,
+ cnativeapi::NATIVE_SCREEN_RECORDING_STATE_STARTING => Self::Starting,
+ cnativeapi::NATIVE_SCREEN_RECORDING_STATE_RECORDING => Self::Recording,
+ cnativeapi::NATIVE_SCREEN_RECORDING_STATE_STOPPING => Self::Stopping,
+ cnativeapi::NATIVE_SCREEN_RECORDING_STATE_COMPLETED => Self::Completed,
+ cnativeapi::NATIVE_SCREEN_RECORDING_STATE_CANCELLED => Self::Cancelled,
+ cnativeapi::NATIVE_SCREEN_RECORDING_STATE_FAILED => Self::Failed,
+ _ => Self::Idle,
+ }
+ }
+
+ pub(crate) fn to_raw(self) -> cnativeapi::native_screen_recording_state_t {
+ self as cnativeapi::native_screen_recording_state_t
+ }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct ScreenRecordingOptions {
+ pub capture: ScreenCaptureOptions,
+ pub frame_rate: f64,
+ pub video_bitrate: std::os::raw::c_ulong,
+}
+
+impl ScreenRecordingOptions {
+ pub(crate) unsafe fn from_raw(raw: &cnativeapi::native_screen_recording_options_t) -> Self {
+ Self {
+ capture: ScreenCaptureOptions::from_raw(&raw.capture),
+ frame_rate: raw.frame_rate,
+ video_bitrate: raw.video_bitrate,
+ }
+ }
+
+ pub(crate) fn to_raw(&self) -> RawOfScreenRecordingOptions {
+ let mut raw = cnativeapi::native_screen_recording_options_t::default();
+ let capture_nested = self.capture.to_raw();
+ raw.capture = capture_nested.raw;
+ raw.frame_rate = self.frame_rate;
+ raw.video_bitrate = self.video_bitrate;
+ RawOfScreenRecordingOptions { raw, _owned: (capture_nested, ()) }
+ }
+}
+
+/// `ScreenRecordingOptions` in its C form, keeping any borrowed buffers alive.
+pub(crate) struct RawOfScreenRecordingOptions {
+ pub(crate) raw: cnativeapi::native_screen_recording_options_t,
+ _owned: (RawOfScreenCaptureOptions, ()),
+}
+
+/// One `ScreenRecordingEvent`, in its concrete form.
+#[derive(Debug, Clone, PartialEq)]
+pub enum ScreenRecordingEvent {
+ Started,
+ Completed { file_path: Option, duration: f64 },
+ Cancelled,
+ Failed { message: Option },
+}
+
+impl ScreenRecordingEvent {
+ pub(crate) unsafe fn from_raw(raw: &cnativeapi::native_screen_recording_event_t) -> Option {
+ Some(match raw.type_ {
+ cnativeapi::NATIVE_SCREEN_RECORDING_EVENT_TYPE_STARTED => Self::Started,
+ cnativeapi::NATIVE_SCREEN_RECORDING_EVENT_TYPE_COMPLETED => Self::Completed { file_path: if raw.data.completed.file_path.is_null() { None } else { Some(CStr::from_ptr(raw.data.completed.file_path).to_string_lossy().into_owned()) }, duration: raw.data.completed.duration },
+ cnativeapi::NATIVE_SCREEN_RECORDING_EVENT_TYPE_CANCELLED => Self::Cancelled,
+ cnativeapi::NATIVE_SCREEN_RECORDING_EVENT_TYPE_FAILED => Self::Failed { message: if raw.data.failed.message.is_null() { None } else { Some(CStr::from_ptr(raw.data.failed.message).to_string_lossy().into_owned()) } },
+ _ => return None,
+ })
+ }
+}
+
+/// Owned handle to a native `ScreenRecorder`.
+#[derive(Debug)]
+#[repr(transparent)]
+pub struct ScreenRecorder {
+ handle: cnativeapi::native_screen_recorder_t,
+}
+
+impl ScreenRecorder {
+ /// Adopts a handle returned by the C API.
+ ///
+ /// # Safety
+ /// `handle` must be a live handle that is not owned elsewhere.
+ pub unsafe fn from_raw(handle: cnativeapi::native_screen_recorder_t) -> Option {
+ if handle == 0 {
+ None
+ } else {
+ Some(Self { handle })
+ }
+ }
+
+ /// Borrows the underlying C handle.
+ pub fn as_raw(&self) -> cnativeapi::native_screen_recorder_t {
+ self.handle
+ }
+
+ /// Creates a new `ScreenRecorder`; returns `None` if the native side failed.
+ pub fn new() -> Option {
+ unsafe {
+ Self::from_raw(cnativeapi::native_screen_recorder_create())
+ }
+ }
+
+ pub fn is_supported() -> bool {
+ unsafe {
+ cnativeapi::native_screen_recorder_is_supported()
+ }
+ }
+
+ pub fn start(&self, source: Option<&CaptureSource>, file_path: &str, options: &ScreenRecordingOptions) -> bool {
+ let file_path_native = CString::new(file_path).expect("string argument contains interior nul byte");
+ let options_raw = options.to_raw();
+ unsafe {
+ cnativeapi::native_screen_recorder_start(self.handle, source.map_or(0, |value| value.as_raw()), file_path_native.as_ptr(), options_raw.raw)
+ }
+ }
+
+ pub fn stop(&self) -> bool {
+ unsafe {
+ cnativeapi::native_screen_recorder_stop(self.handle)
+ }
+ }
+
+ pub fn cancel(&self) -> bool {
+ unsafe {
+ cnativeapi::native_screen_recorder_cancel(self.handle)
+ }
+ }
+
+ pub fn state(&self) -> ScreenRecordingState {
+ unsafe {
+ ScreenRecordingState::from_raw(cnativeapi::native_screen_recorder_get_state(self.handle))
+ }
+ }
+
+ pub fn is_active(&self) -> bool {
+ unsafe {
+ cnativeapi::native_screen_recorder_is_active(self.handle)
+ }
+ }
+
+ pub fn duration(&self) -> f64 {
+ unsafe {
+ cnativeapi::native_screen_recorder_get_duration(self.handle)
+ }
+ }
+
+ pub fn file_path(&self) -> Option {
+ unsafe {
+ let ptr = cnativeapi::native_screen_recorder_get_file_path(self.handle);
+ if ptr.is_null() {
+ return None;
+ }
+ let value = CStr::from_ptr(ptr).to_string_lossy().into_owned();
+ cnativeapi::free_c_str(ptr);
+ Some(value)
+ }
+ }
+
+ /// Registers `callback` for every `ScreenRecordingEvent` this `ScreenRecorder` emits.
+ ///
+ /// The closure is dropped on the main thread once the listener is removed
+ /// or its emitter destroyed.
+ pub fn add_listener(&self, callback: impl Fn(&ScreenRecordingEvent) + 'static) -> ListenerId {
+ unsafe extern "C" fn trampoline(event: *const cnativeapi::native_screen_recording_event_t, user_data: *mut std::ffi::c_void) {
+ if event.is_null() || user_data.is_null() {
+ return;
+ }
+ let callback = &*(user_data as *const Box);
+ if let Some(event) = ScreenRecordingEvent::from_raw(&*event) {
+ callback(&event);
+ }
+ }
+ let boxed: Box> = Box::new(Box::new(callback));
+ let user_data = Box::into_raw(boxed) as *mut std::ffi::c_void;
+ unsafe extern "C" fn release(user_data: *mut std::ffi::c_void) {
+ drop(Box::from_raw(user_data as *mut Box));
+ }
+ unsafe { cnativeapi::native_screen_recorder_add_listener(self.handle, Some(trampoline), user_data, Some(release)) }
+ }
+
+ /// Unregisters a listener. Returns false if unknown.
+ pub fn remove_listener(&self, listener_id: ListenerId) -> bool {
+ unsafe { cnativeapi::native_screen_recorder_remove_listener(self.handle, listener_id) }
+ }
+
+}
+
+impl Drop for ScreenRecorder {
+ fn drop(&mut self) {
+ if self.handle != 0 {
+ unsafe { cnativeapi::native_screen_recorder_free(self.handle) };
+ }
+ }
+}
+
+/// A `ScreenRecorder` owned elsewhere; using it does not release it.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ScreenRecorderRef(cnativeapi::native_screen_recorder_t);
+
+impl ScreenRecorderRef {
+ pub(crate) fn from_raw(handle: cnativeapi::native_screen_recorder_t) -> Self {
+ Self(handle)
+ }
+
+ pub fn as_raw(&self) -> cnativeapi::native_screen_recorder_t {
+ self.0
+ }
+
+ /// Runs `f` against the borrowed handle. The `ScreenRecorder` it receives
+ /// must not outlive the call.
+ pub fn with(&self, f: impl FnOnce(&ScreenRecorder) -> R) -> R {
+ let borrowed = std::mem::ManuallyDrop::new(ScreenRecorder { handle: self.0 });
+ f(&borrowed)
+ }
+}
+
diff --git a/bindings/rust/nativeapi/tests/screen_capture.rs b/bindings/rust/nativeapi/tests/screen_capture.rs
new file mode 100644
index 00000000..7f4914e6
--- /dev/null
+++ b/bindings/rust/nativeapi/tests/screen_capture.rs
@@ -0,0 +1,37 @@
+use nativeapi::{
+ CaptureSourceType, Rectangle, ScreenCaptureOptions, ScreenCapturer, ScreenRecorder,
+ ScreenRecordingOptions, ScreenRecordingState,
+};
+
+// No desktop access: links the real capture backend and exercises nested FFI
+// values, source validation and lifecycle rejection without requesting capture.
+#[test]
+fn invalid_request_keeps_recorder_idle() {
+ assert!(!ScreenCapturer::is_direct_capture_supported(
+ CaptureSourceType::None
+ ));
+ assert!(!ScreenCapturer::is_direct_capture_supported(
+ CaptureSourceType::Unknown
+ ));
+ let options = ScreenRecordingOptions {
+ capture: ScreenCaptureOptions {
+ include_cursor: false,
+ crop_enabled: true,
+ crop: Rectangle {
+ x: 1.0,
+ y: 2.0,
+ width: 32.0,
+ height: 48.0,
+ },
+ },
+ frame_rate: 30.0,
+ video_bitrate: 1_000_000,
+ };
+ let recorder = ScreenRecorder::new().expect("recorder handle");
+ assert!(!recorder.start(None, "relative.mp4", &options));
+ assert_eq!(recorder.state(), ScreenRecordingState::Idle);
+ assert_eq!(recorder.duration(), 0.0);
+ assert!(!recorder.is_active());
+ assert!(!recorder.stop());
+ assert!(!recorder.cancel());
+}
diff --git a/core b/core
index 5086bdb7..ecdf94e9 160000
--- a/core
+++ b/core
@@ -1 +1 @@
-Subproject commit 5086bdb7238f48822665b676c6284edece7db305
+Subproject commit ecdf94e9dbd8ddc8f74f43ae7e31154c96da2795
diff --git a/examples/flutter_screen_capture_example/.gitignore b/examples/flutter_screen_capture_example/.gitignore
new file mode 100644
index 00000000..79f7ecab
--- /dev/null
+++ b/examples/flutter_screen_capture_example/.gitignore
@@ -0,0 +1,48 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins-dependencies
+.pub-cache/
+.pub/
+/build/
+/coverage/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
+
+# Widget Preview related
+.widget_preview/
diff --git a/examples/flutter_screen_capture_example/.metadata b/examples/flutter_screen_capture_example/.metadata
new file mode 100644
index 00000000..12f6820b
--- /dev/null
+++ b/examples/flutter_screen_capture_example/.metadata
@@ -0,0 +1,33 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "5fc346839b5d0eef006ed8404392afb4dfae428d"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 5fc346839b5d0eef006ed8404392afb4dfae428d
+ base_revision: 5fc346839b5d0eef006ed8404392afb4dfae428d
+ - platform: macos
+ create_revision: 5fc346839b5d0eef006ed8404392afb4dfae428d
+ base_revision: 5fc346839b5d0eef006ed8404392afb4dfae428d
+ - platform: windows
+ create_revision: 5fc346839b5d0eef006ed8404392afb4dfae428d
+ base_revision: 5fc346839b5d0eef006ed8404392afb4dfae428d
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/examples/flutter_screen_capture_example/README.md b/examples/flutter_screen_capture_example/README.md
new file mode 100644
index 00000000..2c6b5602
--- /dev/null
+++ b/examples/flutter_screen_capture_example/README.md
@@ -0,0 +1,52 @@
+# Screen capture example
+
+Flutter desktop example for `ScreenCapturer`, `CaptureSource`, and `ScreenRecorder`.
+Requires macOS 14+ or Windows 10 version 2004+ with an available H.264 encoder.
+Other platforms currently expose unsupported stubs.
+
+## Run
+
+From the workspace root:
+
+```sh
+flutter pub get
+cd examples/flutter_screen_capture_example
+flutter run -d macos
+# On Windows:
+flutter run -d windows
+```
+
+## Try it
+
+1. Select **This window**, a display, or **System picker…**. On Windows the
+ system picker lets the user choose either a window or a display.
+2. Optionally include the cursor or crop a rectangle. Crop coordinates are
+ physical pixels relative to the selected source, not desktop coordinates.
+3. **Take screenshot** shows a preview; **Save PNG** writes a separate file.
+4. **Start recording**, then **Stop & save** to finalize a silent H.264 MP4.
+ **Discard** cancels and removes the temporary recording.
+5. Choose an output directory to keep captures outside the default temporary
+ directory. Each output receives a unique filename; existing files are not
+ overwritten.
+
+Allow the operating system's screen-capture request when prompted. Direct
+capture on macOS may require Screen & System Audio Recording permission;
+use the system picker to select a source explicitly. Closing the app during an
+operation is blocked until it finishes or the recording is discarded.
+
+The controller keeps native source/image/recorder handles alive until completion
+and explicitly disposes them. Capture options are supplied explicitly because
+binding defaults do not yet consistently match the C++ defaults. The screenshot
+preview decodes the data URI returned by `Image.toBase64()`.
+
+## Validation and remaining work
+
+The macOS example has been built and run: actual screenshots (including a
+2560 × 1440 frame) and short MP4 recordings succeeded. The Windows core capture
+backend has desktop smoke coverage; the Flutter example has not yet been run
+on Windows. Longer recordings, permission denial, source disappearance and
+runtime shutdown still need end-to-end coverage.
+
+The current **Open folder**, **Open** and **Play** actions use `UrlOpener`, whose
+current implementation accepts HTTP/HTTPS only. Local files are saved correctly,
+but these actions report an error; open the displayed path manually for now.
diff --git a/examples/flutter_screen_capture_example/analysis_options.yaml b/examples/flutter_screen_capture_example/analysis_options.yaml
new file mode 100644
index 00000000..362ff73a
--- /dev/null
+++ b/examples/flutter_screen_capture_example/analysis_options.yaml
@@ -0,0 +1,34 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+analyzer:
+ exclude:
+ - build/**
+ - windows/**
+ - macos/**
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/examples/flutter_screen_capture_example/lib/capture_controller.dart b/examples/flutter_screen_capture_example/lib/capture_controller.dart
new file mode 100644
index 00000000..f5fe922a
--- /dev/null
+++ b/examples/flutter_screen_capture_example/lib/capture_controller.dart
@@ -0,0 +1,406 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:file_selector/file_selector.dart';
+import 'package:flutter/foundation.dart';
+import 'package:nativeapi/nativeapi.dart' as na;
+
+class CaptureArtifact {
+ const CaptureArtifact(this.path, {this.duration, this.width, this.height});
+ final String path;
+ final double? duration;
+ final int? width;
+ final int? height;
+ bool get isVideo => duration != null;
+ String get name => path.split(Platform.pathSeparator).last;
+}
+
+/// Owns native handles explicitly. UI changes never alter an in-flight request.
+class CaptureController extends ChangeNotifier {
+ final artifacts = [];
+ final log = [];
+ List displays = [];
+ na.Window? _window;
+ na.CaptureSource? _source;
+ na.Image? _image;
+ na.ScreenRecorder? _recorder;
+ int? _recordingListener, _closeListener, _quitListener;
+ Timer? _timer;
+ final _elapsed = Stopwatch();
+ bool _disposed = false;
+ bool ready = false;
+ bool busy = false;
+ bool captureSupported = false;
+ bool recordingSupported = false;
+ bool pickerSupported = false;
+ String sourceLabel = 'No source selected';
+ String outputDirectory = '';
+ String status = 'Choose a display, this window, or the system picker.';
+ String? error;
+ Uint8List? preview;
+ int imageWidth = 0, imageHeight = 0;
+ na.ScreenRecordingState recordingState = na.ScreenRecordingState.idle;
+
+ bool get hasSource => _source != null;
+ bool get hasImage => _image != null;
+ bool get active => switch (recordingState) {
+ na.ScreenRecordingState.starting ||
+ na.ScreenRecordingState.recording ||
+ na.ScreenRecordingState.stopping => true,
+ _ => false,
+ };
+ bool get locked => busy || active || !ready;
+ double get elapsed => _elapsed.elapsedMilliseconds / 1000;
+
+ void _changed() {
+ if (!_disposed) notifyListeners();
+ }
+
+ void _message(String message, {bool failure = false}) {
+ status = message;
+ error = failure ? message : null;
+ log.insert(0, message);
+ if (log.length > 30) log.removeLast();
+ debugPrint('[capture] $message');
+ _changed();
+ }
+
+ Future initialize() async {
+ try {
+ final directory = await Directory.systemTemp.createTemp(
+ 'nativeapi-capture-',
+ );
+ if (_disposed) return;
+ outputDirectory = directory.path;
+ _window = na.WindowManager.instance.getCurrent();
+ _window?.contentSize = const na.Size(width: 1120, height: 800);
+ captureSupported = na.ScreenCapturer.isSupported();
+ recordingSupported = na.ScreenRecorder.isSupported();
+ pickerSupported = na.ScreenCapturer.isSourceSelectionSupported();
+ displays = na.DisplayManager.instance.getAll();
+ _closeListener = _window?.addListener((event) {
+ if (event is na.WindowCloseRequestedEvent && (busy || active)) {
+ event.request.cancel();
+ _message(
+ 'Finish the current action, or stop / discard the recording before closing.',
+ );
+ }
+ });
+ _quitListener = na.Application.instance.addListener((event) {
+ if (event is na.ApplicationQuitRequestedEvent && (busy || active)) {
+ event.request.cancel();
+ _message('Stop and save, or discard the recording before quitting.');
+ }
+ });
+ ready = true;
+ _changed();
+ } catch (e) {
+ _message('Initialization failed: $e', failure: true);
+ }
+ }
+
+ void _replaceSource(na.CaptureSource? next) {
+ if (next == null) {
+ _message(
+ 'This source is unavailable. Select it again or try the system picker.',
+ failure: true,
+ );
+ return;
+ }
+ _source?.dispose();
+ _source = next;
+ final name = next.name;
+ sourceLabel = name == null || name.isEmpty
+ ? 'Selected ${next.type.name} source'
+ : name;
+ _message('Source selected: $sourceLabel');
+ }
+
+ void selectDisplay(na.Display display) {
+ if (locked) return;
+ _replaceSource(na.CaptureSource.fromDisplay(display));
+ }
+
+ void selectThisWindow() {
+ if (locked) return;
+ _replaceSource(na.CaptureSource.fromWindow(_window));
+ }
+
+ void releaseSource() {
+ if (locked) return;
+ _source?.dispose();
+ _source = null;
+ sourceLabel = 'No source selected';
+ _message('Source released. Any retained sharing session can now close.');
+ }
+
+ Future selectSource() async {
+ if (locked || !pickerSupported) return;
+ busy = true;
+ _message('Choose one screen or window in the system picker.');
+ try {
+ final result = Completer<(bool, na.CaptureSource?)>();
+ na.ScreenCapturer.selectSource(
+ na.CaptureSourceSelectionOptions(
+ allowDisplays: true,
+ allowWindows: true,
+ parentWindow: _window,
+ ),
+ (success, source) => result.complete((success, source)),
+ );
+ final (success, source) = await result.future;
+ if (_disposed) {
+ source?.dispose();
+ return;
+ }
+ if (!success) {
+ source?.dispose();
+ _message(
+ 'Source selection failed. Check system permission and try again.',
+ failure: true,
+ );
+ } else if (source == null) {
+ _message('Selection cancelled. The previous source is unchanged.');
+ } else {
+ _replaceSource(source);
+ }
+ } catch (e) {
+ _message('Source selection failed: $e', failure: true);
+ } finally {
+ busy = false;
+ _changed();
+ }
+ }
+
+ Future capture(na.ScreenCaptureOptions options) async {
+ if (locked || _source == null) return;
+ busy = true;
+ _message('Capturing a new frame…');
+ try {
+ final result = Completer<(bool, na.Image?)>();
+ na.ScreenCapturer.capture(
+ _source,
+ options,
+ (success, image) => result.complete((success, image)),
+ );
+ final (success, image) = await result.future;
+ if (_disposed) {
+ image?.dispose();
+ return;
+ }
+ if (!success || image == null) {
+ image?.dispose();
+ throw StateError(
+ 'No frame received. Check screen-recording permission, source availability and crop bounds.',
+ );
+ }
+ try {
+ final encoded = image.toBase64();
+ if (encoded == null) {
+ throw StateError('Cannot convert the captured image for preview.');
+ }
+ // Image.ToBase64 returns a data URI, including its MIME type.
+ final bytes = encoded.startsWith('data:')
+ ? UriData.parse(encoded).contentAsBytes()
+ : base64Decode(encoded);
+ imageWidth = image.size.width.toInt();
+ imageHeight = image.size.height.toInt();
+ _image?.dispose();
+ _image = image;
+ preview = bytes;
+ } catch (_) {
+ image.dispose();
+ rethrow;
+ }
+ _message('Screenshot ready · $imageWidth × $imageHeight px');
+ } catch (e) {
+ _message('Screenshot failed: $e', failure: true);
+ } finally {
+ busy = false;
+ _changed();
+ }
+ }
+
+ Future _newPath(String extension) async {
+ if (outputDirectory.isEmpty || !await Directory(outputDirectory).exists()) {
+ throw StateError('Choose an existing output folder.');
+ }
+ final path =
+ '$outputDirectory${Platform.pathSeparator}capture-${DateTime.now().microsecondsSinceEpoch}.$extension';
+ if (await FileSystemEntity.type(path) != FileSystemEntityType.notFound) {
+ throw StateError(
+ 'The output already exists. Try again with a new filename.',
+ );
+ }
+ return path;
+ }
+
+ Future saveScreenshot() async {
+ if (locked || _image == null) return;
+ busy = true;
+ _changed();
+ try {
+ final path = await _newPath('png');
+ if (_disposed) return;
+ if (!_image!.saveToFile(path)) {
+ throw StateError('PNG write failed. Check folder permissions.');
+ }
+ artifacts.insert(
+ 0,
+ CaptureArtifact(path, width: imageWidth, height: imageHeight),
+ );
+ _message('PNG saved: $path');
+ } catch (e) {
+ _message('Save failed: $e', failure: true);
+ } finally {
+ busy = false;
+ _changed();
+ }
+ }
+
+ Future chooseOutputDirectory() async {
+ if (locked) return;
+ busy = true;
+ _changed();
+ try {
+ final path = await getDirectoryPath(confirmButtonText: 'Use this folder');
+ if (_disposed) return;
+ if (path != null) {
+ outputDirectory = path;
+ _message('Output folder: $path');
+ }
+ } catch (e) {
+ _message('Cannot select a folder: $e', failure: true);
+ } finally {
+ busy = false;
+ _changed();
+ }
+ }
+
+ Future start(na.ScreenRecordingOptions options) async {
+ if (locked || _source == null || !recordingSupported) return;
+ busy = true;
+ _changed();
+ try {
+ final path = await _newPath('mp4');
+ if (_disposed) return;
+ final recorder = na.ScreenRecorder.create();
+ if (recorder == null) throw StateError('Cannot create a recorder.');
+ _recorder = recorder;
+ _recordingListener = recorder.addListener((event) {
+ if (_disposed || !identical(_recorder, recorder)) return;
+ if (event is na.ScreenRecordingStartedEvent) {
+ // Queued Started must not turn an already-cancelled session back on.
+ recordingState = recorder.state;
+ if (recordingState == na.ScreenRecordingState.recording) {
+ _elapsed.start();
+ _timer = Timer.periodic(
+ const Duration(milliseconds: 200),
+ (_) => _changed(),
+ );
+ _message('Recording · MP4 / H.264 · no audio');
+ }
+ } else if (event is na.ScreenRecordingCompletedEvent) {
+ recordingState = na.ScreenRecordingState.completed;
+ final saved = event.filePath;
+ if (saved != null) {
+ artifacts.insert(
+ 0,
+ CaptureArtifact(saved, duration: event.duration),
+ );
+ }
+ _message(
+ 'Recording saved · ${event.duration.toStringAsFixed(1)} s · $saved',
+ );
+ _releaseRecorder();
+ } else if (event is na.ScreenRecordingCancelledEvent) {
+ recordingState = na.ScreenRecordingState.cancelled;
+ _message('Recording discarded. No video file was saved.');
+ _releaseRecorder();
+ } else if (event is na.ScreenRecordingFailedEvent) {
+ recordingState = na.ScreenRecordingState.failed;
+ _message(
+ 'Recording failed: ${event.message ?? 'Unknown native failure'}',
+ failure: true,
+ );
+ _releaseRecorder();
+ }
+ _changed();
+ });
+ if (_recordingListener == 0) {
+ throw StateError('Cannot subscribe to recording events.');
+ }
+ _elapsed.reset();
+ recordingState = na.ScreenRecordingState.starting;
+ if (!recorder.start(_source, path, options)) {
+ recordingState = na.ScreenRecordingState.idle;
+ throw StateError(
+ 'Start rejected. Check the source, options and output path.',
+ );
+ }
+ _message('Starting… waiting for permission and the first encoded frame.');
+ } catch (e) {
+ _releaseRecorder();
+ recordingState = na.ScreenRecordingState.failed;
+ _message('Cannot start recording: $e', failure: true);
+ } finally {
+ busy = false;
+ _changed();
+ }
+ }
+
+ void stop() {
+ if (_recorder?.stop() != true) return;
+ recordingState = na.ScreenRecordingState.stopping;
+ _elapsed.stop();
+ _message('Saving… waiting for the MP4 to be finalized.');
+ }
+
+ void cancel() {
+ if (_recorder?.cancel() != true) return;
+ recordingState = na.ScreenRecordingState.stopping;
+ _elapsed.stop();
+ _message('Discarding… waiting for temporary output cleanup.');
+ }
+
+ void openPath(String path, {bool directory = false}) {
+ final uri = directory ? Uri.directory(path) : Uri.file(path);
+ final result = na.UrlOpener.instance.open(uri.toString());
+ if (!result.success) {
+ _message('Cannot open $path: ${result.errorMessage}', failure: true);
+ }
+ }
+
+ void reportInvalidOptions(String message) => _message(message, failure: true);
+
+ void _releaseRecorder() {
+ _timer?.cancel();
+ _timer = null;
+ _elapsed.stop();
+ if (_recordingListener != null) {
+ _recorder?.removeListener(_recordingListener!);
+ }
+ _recordingListener = null;
+ _recorder?.dispose();
+ _recorder = null;
+ }
+
+ @override
+ void dispose() {
+ _disposed = true;
+ // Ordinary close is vetoed while active; forced teardown discards output.
+ _releaseRecorder();
+ if (_closeListener != null) _window?.removeListener(_closeListener!);
+ if (_quitListener != null) {
+ na.Application.instance.removeListener(_quitListener!);
+ }
+ _image?.dispose();
+ _source?.dispose();
+ for (final display in displays) {
+ display.dispose();
+ }
+ _window?.dispose();
+ super.dispose();
+ }
+}
diff --git a/examples/flutter_screen_capture_example/lib/main.dart b/examples/flutter_screen_capture_example/lib/main.dart
new file mode 100644
index 00000000..75561694
--- /dev/null
+++ b/examples/flutter_screen_capture_example/lib/main.dart
@@ -0,0 +1,589 @@
+import 'dart:async';
+
+import 'package:dazzui_host/dazzui_host.dart';
+import 'package:nativeapi/nativeapi.dart' as na;
+
+import 'capture_controller.dart';
+
+void main() => runApp(const CaptureApp());
+
+class CaptureApp extends StatelessWidget {
+ const CaptureApp({super.key, this.controller});
+ final CaptureController? controller;
+
+ @override
+ Widget build(BuildContext context) => Host(
+ title: 'Screen Capture',
+ home: CapturePage(controller: controller),
+ );
+}
+
+class CapturePage extends StatefulWidget {
+ const CapturePage({super.key, this.controller});
+ final CaptureController? controller;
+ @override
+ State createState() => _CapturePageState();
+}
+
+class _CapturePageState extends State {
+ late final CaptureController capture;
+ final cropFields = [
+ TextEditingController(text: '0'),
+ TextEditingController(text: '0'),
+ TextEditingController(text: '640'),
+ TextEditingController(text: '480'),
+ ];
+ bool includeCursor = false, cropEnabled = false;
+ double frameRate = 30;
+ int bitrate = 0;
+
+ @override
+ void initState() {
+ super.initState();
+ capture = widget.controller ?? CaptureController();
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ if (mounted) unawaited(capture.initialize());
+ });
+ }
+
+ @override
+ void dispose() {
+ for (final field in cropFields) {
+ field.dispose();
+ }
+ capture.dispose();
+ super.dispose();
+ }
+
+ na.ScreenCaptureOptions? options() {
+ final values = cropFields
+ .map((field) => int.tryParse(field.text.trim()))
+ .toList();
+ if (cropEnabled &&
+ (values.any((value) => value == null) ||
+ values[0]! < 0 ||
+ values[1]! < 0 ||
+ values[2]! <= 0 ||
+ values[3]! <= 0)) {
+ capture.reportInvalidOptions(
+ 'Crop needs whole pixel values: X/Y ≥ 0 and Width/Height > 0.',
+ );
+ return null;
+ }
+ return na.ScreenCaptureOptions(
+ includeCursor: includeCursor,
+ cropEnabled: cropEnabled,
+ crop: na.Rectangle(
+ x: (values[0] ?? 0).toDouble(),
+ y: (values[1] ?? 0).toDouble(),
+ width: (values[2] ?? 0).toDouble(),
+ height: (values[3] ?? 0).toDouble(),
+ ),
+ );
+ }
+
+ void takeScreenshot() {
+ final value = options();
+ if (value != null) unawaited(capture.capture(value));
+ }
+
+ void startRecording() {
+ final value = options();
+ if (value != null) {
+ unawaited(
+ capture.start(
+ na.ScreenRecordingOptions(
+ capture: value,
+ frameRate: frameRate,
+ videoBitrate: bitrate,
+ ),
+ ),
+ );
+ }
+ }
+
+ Widget action(
+ String label,
+ VoidCallback? callback, {
+ bool primary = false,
+ bool danger = false,
+ }) => Button(
+ variant: primary ? ButtonVariant.filled : ButtonVariant.normal,
+ tint: danger
+ ? ButtonTint.danger
+ : (primary ? ButtonTint.primary : ButtonTint.neutral),
+ onPressed: callback,
+ child: Text(label),
+ );
+
+ Widget section(String title, List children) => Padding(
+ padding: const EdgeInsets.only(bottom: 20),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ Text(title, style: context.vars.titleMedium),
+ const SizedBox(height: 10),
+ ...children,
+ ],
+ ),
+ );
+
+ @override
+ Widget build(BuildContext context) => ListenableBuilder(
+ listenable: capture,
+ builder: (context, _) {
+ final vars = context.vars;
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
+ child: Row(
+ children: [
+ Icon(
+ FluentIcons.record_24_regular,
+ color: vars.colorPrimary[600],
+ size: 28,
+ ),
+ const SizedBox(width: 12),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text('Screen Capture', style: vars.titleLarge),
+ Text(
+ 'Screenshots & silent recordings',
+ style: vars.muted,
+ ),
+ ],
+ ),
+ ),
+ Badge(
+ variant: BadgeVariant.tinted,
+ tint: capture.active ? BadgeTint.danger : BadgeTint.neutral,
+ child: Text(
+ capture.active
+ ? '${capture.recordingState.name} · ${capture.elapsed.toStringAsFixed(1)} s'
+ : capture.recordingState.name,
+ ),
+ ),
+ ],
+ ),
+ ),
+ const Divider(),
+ Expanded(
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ if (constraints.maxWidth < 880) {
+ return ListView(
+ padding: const EdgeInsets.all(20),
+ children: [
+ controls(),
+ SizedBox(height: 450, child: preview()),
+ SizedBox(height: 220, child: results()),
+ ],
+ );
+ }
+ return Row(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ SizedBox(
+ width: 350,
+ child: SingleChildScrollView(
+ padding: const EdgeInsets.all(24),
+ child: controls(),
+ ),
+ ),
+ const VerticalDivider(),
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.all(24),
+ child: Column(
+ children: [
+ Expanded(child: preview()),
+ const SizedBox(height: 16),
+ SizedBox(height: 188, child: results()),
+ ],
+ ),
+ ),
+ ),
+ ],
+ );
+ },
+ ),
+ ),
+ const Divider(),
+ Container(
+ padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
+ color: capture.error == null
+ ? vars.colorSurfaceSunken
+ : vars.colorDanger[50],
+ child: Text(
+ capture.status,
+ maxLines: 3,
+ overflow: TextOverflow.ellipsis,
+ style: capture.error == null
+ ? vars.bodyMedium
+ : vars.bodyMedium.copyWith(color: vars.colorDanger[700]),
+ ),
+ ),
+ ],
+ );
+ },
+ );
+
+ Widget controls() {
+ final enabled = !capture.locked && capture.captureSupported;
+ final canCapture = enabled && capture.hasSource;
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ if (capture.ready && !capture.captureSupported)
+ const Padding(
+ padding: EdgeInsets.only(bottom: 16),
+ child: Text(
+ 'Capture requires macOS 14+ or Windows 10 2004+ with a supported desktop session.',
+ ),
+ ),
+ section('1 Choose a source', [
+ action(
+ 'System picker…',
+ enabled && capture.pickerSupported ? capture.selectSource : null,
+ primary: true,
+ ),
+ const SizedBox(height: 8),
+ Wrap(
+ spacing: 6,
+ runSpacing: 6,
+ children: [
+ action('This window', enabled ? capture.selectThisWindow : null),
+ for (var i = 0; i < capture.displays.length; i++)
+ action(
+ 'Display ${i + 1}${capture.displays[i].isPrimary ? ' · primary' : ''}',
+ enabled
+ ? () => capture.selectDisplay(capture.displays[i])
+ : null,
+ ),
+ ],
+ ),
+ const SizedBox(height: 10),
+ Text(
+ capture.sourceLabel,
+ style: context.vars.bodyMedium,
+ maxLines: 2,
+ overflow: TextOverflow.ellipsis,
+ ),
+ if (capture.hasSource)
+ Align(
+ alignment: Alignment.centerLeft,
+ child: action(
+ 'Release source',
+ !capture.locked ? capture.releaseSource : null,
+ ),
+ ),
+ ]),
+ section('2 Capture options', [
+ Wrap(
+ spacing: 6,
+ runSpacing: 6,
+ children: [
+ OptionChip(
+ label: 'Include cursor',
+ selected: includeCursor,
+ onTap: !capture.locked
+ ? () => setState(() => includeCursor = !includeCursor)
+ : null,
+ ),
+ OptionChip(
+ label: 'Crop region',
+ selected: cropEnabled,
+ onTap: !capture.locked
+ ? () => setState(() => cropEnabled = !cropEnabled)
+ : null,
+ ),
+ ],
+ ),
+ if (cropEnabled) ...[
+ const SizedBox(height: 10),
+ Row(
+ children: [
+ for (var i = 0; i < 4; i++)
+ Expanded(
+ child: Padding(
+ padding: EdgeInsets.only(right: i == 3 ? 0 : 6),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ ['X', 'Y', 'Width', 'Height'][i],
+ style: context.vars.muted,
+ ),
+ const SizedBox(height: 4),
+ TextField(
+ controller: cropFields[i],
+ enabled: !capture.locked,
+ size: WidgetSize.small,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 6),
+ Text(
+ 'Source pixels, not desktop coordinates. The region must fit inside the captured image.',
+ style: context.vars.muted,
+ ),
+ ],
+ const SizedBox(height: 12),
+ Wrap(
+ spacing: 6,
+ runSpacing: 6,
+ children: [
+ for (final fps in [15.0, 30.0, 60.0])
+ OptionChip(
+ label: '${fps.toInt()} fps',
+ selected: frameRate == fps,
+ onTap: !capture.locked
+ ? () => setState(() => frameRate = fps)
+ : null,
+ ),
+ ],
+ ),
+ const SizedBox(height: 6),
+ Wrap(
+ spacing: 6,
+ runSpacing: 6,
+ children: [
+ for (final mbps in [0, 4, 8, 16])
+ OptionChip(
+ label: mbps == 0 ? 'Auto bitrate' : '$mbps Mbps',
+ selected: bitrate == mbps * 1000000,
+ onTap: !capture.locked
+ ? () => setState(() => bitrate = mbps * 1000000)
+ : null,
+ ),
+ ],
+ ),
+ ]),
+ section('3 Capture & record', [
+ Wrap(
+ spacing: 8,
+ runSpacing: 8,
+ children: [
+ action(
+ 'Take screenshot',
+ canCapture ? takeScreenshot : null,
+ primary: true,
+ ),
+ action(
+ 'Save PNG',
+ !capture.locked && capture.hasImage
+ ? capture.saveScreenshot
+ : null,
+ ),
+ ],
+ ),
+ const SizedBox(height: 10),
+ if (!capture.active)
+ action(
+ 'Start recording',
+ canCapture && capture.recordingSupported ? startRecording : null,
+ primary: true,
+ )
+ else
+ Wrap(
+ spacing: 8,
+ runSpacing: 8,
+ children: [
+ action(
+ 'Stop & save',
+ capture.recordingState == na.ScreenRecordingState.recording
+ ? capture.stop
+ : null,
+ primary: true,
+ ),
+ action(
+ 'Discard',
+ capture.recordingState == na.ScreenRecordingState.starting ||
+ capture.recordingState ==
+ na.ScreenRecordingState.recording
+ ? capture.cancel
+ : null,
+ danger: true,
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Text(
+ 'MP4 · H.264 · SDR · no audio. Wait for “saved” before opening a recording.',
+ style: context.vars.muted,
+ ),
+ ]),
+ section('Output folder', [
+ Text(
+ capture.outputDirectory.isEmpty
+ ? 'Preparing…'
+ : capture.outputDirectory,
+ style: context.vars.mono,
+ maxLines: 3,
+ overflow: TextOverflow.ellipsis,
+ ),
+ const SizedBox(height: 8),
+ Wrap(
+ spacing: 8,
+ runSpacing: 8,
+ children: [
+ action(
+ 'Choose folder…',
+ !capture.locked ? capture.chooseOutputDirectory : null,
+ ),
+ action(
+ 'Open folder',
+ capture.outputDirectory.isNotEmpty
+ ? () => capture.openPath(
+ capture.outputDirectory,
+ directory: true,
+ )
+ : null,
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Text(
+ 'Starts in a temporary folder. Choose another folder to keep your captures.',
+ style: context.vars.muted,
+ ),
+ ]),
+ Text(
+ 'On macOS, allow Screen & System Audio Recording in System Settings if direct capture is denied. Reopen the app if macOS asks you to.',
+ style: context.vars.muted,
+ ),
+ ],
+ );
+ }
+
+ Widget preview() => Card(
+ variant: CardVariant.sunken,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ Row(
+ children: [
+ Text('Screenshot preview', style: context.vars.titleMedium),
+ const Spacer(),
+ if (capture.hasImage)
+ Text(
+ '${capture.imageWidth} × ${capture.imageHeight} px',
+ style: context.vars.mono,
+ ),
+ ],
+ ),
+ const SizedBox(height: 14),
+ Expanded(
+ child: ClipRect(
+ child: capture.preview == null
+ ? Center(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(
+ FluentIcons.image_24_regular,
+ size: 48,
+ color: context.vars.colorContentMuted,
+ ),
+ const SizedBox(height: 14),
+ const Text('Your next screenshot appears here'),
+ const SizedBox(height: 6),
+ Text(
+ 'Choose “This window” for a quick first capture.',
+ style: context.vars.muted,
+ ),
+ ],
+ ),
+ )
+ : InteractiveViewer(
+ minScale: 0.5,
+ maxScale: 5,
+ child: Center(
+ child: Image.memory(
+ capture.preview!,
+ fit: BoxFit.contain,
+ gaplessPlayback: true,
+ errorBuilder: (_, _, _) => const Center(
+ child: Text(
+ 'Preview could not be decoded. You can still save the PNG.',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(height: 10),
+ Text(
+ capture.preview == null ? 'PNG screenshots · physical pixels' : 'Scroll to zoom · drag to pan · this is a still image, not a live feed',
+ style: context.vars.muted,
+ ),
+ ],
+ ),
+ );
+
+ Widget results() => Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ Text(
+ 'Saved captures (${capture.artifacts.length})',
+ style: context.vars.titleMedium,
+ ),
+ const SizedBox(height: 8),
+ if (capture.artifacts.isEmpty)
+ Text(
+ 'Saved PNGs and completed MP4s appear here.',
+ style: context.vars.muted,
+ )
+ else
+ Expanded(
+ child: ListView.separated(
+ itemCount: capture.artifacts.length,
+ separatorBuilder: (_, _) => const Divider(),
+ itemBuilder: (context, index) {
+ final artifact = capture.artifacts[index];
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 9),
+ child: Row(
+ children: [
+ Icon(
+ artifact.isVideo
+ ? FluentIcons.video_24_regular
+ : FluentIcons.image_24_regular,
+ ),
+ const SizedBox(width: 10),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(artifact.name, overflow: TextOverflow.ellipsis),
+ Text(
+ artifact.isVideo
+ ? 'MP4 · ${artifact.duration!.toStringAsFixed(1)} s'
+ : 'PNG · ${artifact.width} × ${artifact.height} px',
+ style: context.vars.muted,
+ ),
+ ],
+ ),
+ ),
+ action(
+ artifact.isVideo ? 'Play' : 'Open',
+ () => capture.openPath(artifact.path),
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ ),
+ ],
+ );
+}
diff --git a/examples/flutter_screen_capture_example/macos/.gitignore b/examples/flutter_screen_capture_example/macos/.gitignore
new file mode 100644
index 00000000..746adbb6
--- /dev/null
+++ b/examples/flutter_screen_capture_example/macos/.gitignore
@@ -0,0 +1,7 @@
+# Flutter-related
+**/Flutter/ephemeral/
+**/Pods/
+
+# Xcode-related
+**/dgph
+**/xcuserdata/
diff --git a/examples/flutter_screen_capture_example/macos/Flutter/Flutter-Debug.xcconfig b/examples/flutter_screen_capture_example/macos/Flutter/Flutter-Debug.xcconfig
new file mode 100644
index 00000000..c2efd0b6
--- /dev/null
+++ b/examples/flutter_screen_capture_example/macos/Flutter/Flutter-Debug.xcconfig
@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/examples/flutter_screen_capture_example/macos/Flutter/Flutter-Release.xcconfig b/examples/flutter_screen_capture_example/macos/Flutter/Flutter-Release.xcconfig
new file mode 100644
index 00000000..c2efd0b6
--- /dev/null
+++ b/examples/flutter_screen_capture_example/macos/Flutter/Flutter-Release.xcconfig
@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/examples/flutter_screen_capture_example/macos/Flutter/GeneratedPluginRegistrant.swift b/examples/flutter_screen_capture_example/macos/Flutter/GeneratedPluginRegistrant.swift
new file mode 100644
index 00000000..14b5f7c7
--- /dev/null
+++ b/examples/flutter_screen_capture_example/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -0,0 +1,12 @@
+//
+// Generated file. Do not edit.
+//
+
+import FlutterMacOS
+import Foundation
+
+import file_selector_macos
+
+func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+ FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
+}
diff --git a/examples/flutter_screen_capture_example/macos/Runner.xcodeproj/project.pbxproj b/examples/flutter_screen_capture_example/macos/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..c1e9bf0b
--- /dev/null
+++ b/examples/flutter_screen_capture_example/macos/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,729 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 54;
+ objects = {
+
+/* Begin PBXAggregateTarget section */
+ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
+ isa = PBXAggregateTarget;
+ buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
+ buildPhases = (
+ 33CC111E2044C6BF0003C045 /* ShellScript */,
+ );
+ dependencies = (
+ );
+ name = "Flutter Assemble";
+ productName = FLX;
+ };
+/* End PBXAggregateTarget section */
+
+/* Begin PBXBuildFile section */
+ 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
+ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
+ 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
+ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
+ 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
+ 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 33CC10EC2044A3C60003C045;
+ remoteInfo = Runner;
+ };
+ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 33CC111A2044C6BA0003C045;
+ remoteInfo = FLX;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 33CC110E2044A8840003C045 /* Bundle Framework */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ name = "Bundle Framework";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
+ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; };
+ 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; };
+ 33CC10ED2044A3C60003C045 /* screen_capture_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "screen_capture_example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; };
+ 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
+ 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; };
+ 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; };
+ 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; };
+ 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; };
+ 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; };
+ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; };
+ 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; };
+ 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; };
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 331C80D2294CF70F00263BE5 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 33CC10EA2044A3C60003C045 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 331C80D6294CF71000263BE5 /* RunnerTests */ = {
+ isa = PBXGroup;
+ children = (
+ 331C80D7294CF71000263BE5 /* RunnerTests.swift */,
+ );
+ path = RunnerTests;
+ sourceTree = "";
+ };
+ 33BA886A226E78AF003329D5 /* Configs */ = {
+ isa = PBXGroup;
+ children = (
+ 33E5194F232828860026EE4D /* AppInfo.xcconfig */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
+ );
+ path = Configs;
+ sourceTree = "";
+ };
+ 33CC10E42044A3C60003C045 = {
+ isa = PBXGroup;
+ children = (
+ 33FAB671232836740065AC1E /* Runner */,
+ 33CEB47122A05771004F2AC0 /* Flutter */,
+ 331C80D6294CF71000263BE5 /* RunnerTests */,
+ 33CC10EE2044A3C60003C045 /* Products */,
+ D73912EC22F37F3D000D13A0 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 33CC10EE2044A3C60003C045 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 33CC10ED2044A3C60003C045 /* screen_capture_example.app */,
+ 331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 33CC11242044D66E0003C045 /* Resources */ = {
+ isa = PBXGroup;
+ children = (
+ 33CC10F22044A3C60003C045 /* Assets.xcassets */,
+ 33CC10F42044A3C60003C045 /* MainMenu.xib */,
+ 33CC10F72044A3C60003C045 /* Info.plist */,
+ );
+ name = Resources;
+ path = ..;
+ sourceTree = "";
+ };
+ 33CEB47122A05771004F2AC0 /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
+ 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
+ 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
+ 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
+ 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
+ );
+ path = Flutter;
+ sourceTree = "