node-mac-recorder 2.15.0 → 2.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,137 @@
1
+ #import <napi.h>
2
+ #import <AVFoundation/AVFoundation.h>
3
+ #import <CoreAudio/CoreAudio.h>
4
+
5
+ // Thread-safe audio device management for Electron
6
+ static dispatch_queue_t g_audioQueue = nil;
7
+
8
+ static void initializeAudioQueue() {
9
+ static dispatch_once_t onceToken;
10
+ dispatch_once(&onceToken, ^{
11
+ g_audioQueue = dispatch_queue_create("com.macrecorder.audio.electron", DISPATCH_QUEUE_SERIAL);
12
+ });
13
+ }
14
+
15
+ // NAPI Function: Get Audio Devices (Electron-safe)
16
+ Napi::Value GetAudioDevicesElectronSafe(const Napi::CallbackInfo& info) {
17
+ Napi::Env env = info.Env();
18
+
19
+ @try {
20
+ initializeAudioQueue();
21
+
22
+ __block NSArray *devices = nil;
23
+
24
+ dispatch_sync(g_audioQueue, ^{
25
+ @try {
26
+ NSMutableArray *audioDevices = [NSMutableArray array];
27
+
28
+ // Get all audio devices
29
+ AudioObjectPropertyAddress propertyAddress = {
30
+ kAudioHardwarePropertyDevices,
31
+ kAudioObjectPropertyScopeGlobal,
32
+ kAudioObjectPropertyElementMaster
33
+ };
34
+
35
+ UInt32 dataSize = 0;
36
+ OSStatus status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
37
+ &propertyAddress,
38
+ 0,
39
+ NULL,
40
+ &dataSize);
41
+
42
+ if (status == noErr && dataSize > 0) {
43
+ UInt32 deviceCount = dataSize / sizeof(AudioDeviceID);
44
+ AudioDeviceID *audioDeviceIDs = (AudioDeviceID*)malloc(dataSize);
45
+
46
+ status = AudioObjectGetPropertyData(kAudioObjectSystemObject,
47
+ &propertyAddress,
48
+ 0,
49
+ NULL,
50
+ &dataSize,
51
+ audioDeviceIDs);
52
+
53
+ if (status == noErr) {
54
+ for (UInt32 i = 0; i < deviceCount; i++) {
55
+ AudioDeviceID deviceID = audioDeviceIDs[i];
56
+
57
+ // Get device name
58
+ CFStringRef deviceName = NULL;
59
+ UInt32 nameSize = sizeof(CFStringRef);
60
+ AudioObjectPropertyAddress nameAddress = {
61
+ kAudioDevicePropertyDeviceNameCFString,
62
+ kAudioObjectPropertyScopeGlobal,
63
+ kAudioObjectPropertyElementMaster
64
+ };
65
+
66
+ status = AudioObjectGetPropertyData(deviceID,
67
+ &nameAddress,
68
+ 0,
69
+ NULL,
70
+ &nameSize,
71
+ &deviceName);
72
+
73
+ if (status == noErr && deviceName) {
74
+ NSString *name = (__bridge NSString*)deviceName;
75
+
76
+ NSDictionary *deviceInfo = @{
77
+ @"id": @(deviceID),
78
+ @"name": name,
79
+ @"type": @"Audio Device"
80
+ };
81
+
82
+ [audioDevices addObject:deviceInfo];
83
+ CFRelease(deviceName);
84
+ }
85
+ }
86
+ }
87
+
88
+ free(audioDeviceIDs);
89
+ }
90
+
91
+ devices = [audioDevices copy];
92
+
93
+ } @catch (NSException *e) {
94
+ NSLog(@"❌ Exception getting audio devices: %@", e.reason);
95
+ devices = @[];
96
+ }
97
+ });
98
+
99
+ Napi::Array result = Napi::Array::New(env, devices ? devices.count : 0);
100
+
101
+ if (devices) {
102
+ for (NSUInteger i = 0; i < devices.count; i++) {
103
+ NSDictionary *device = devices[i];
104
+ Napi::Object deviceObj = Napi::Object::New(env);
105
+
106
+ deviceObj.Set("id", Napi::Number::New(env, [device[@"id"] unsignedIntValue]));
107
+ deviceObj.Set("name", Napi::String::New(env, [device[@"name"] UTF8String]));
108
+ deviceObj.Set("type", Napi::String::New(env, [device[@"type"] UTF8String]));
109
+
110
+ result.Set(i, deviceObj);
111
+ }
112
+ }
113
+
114
+ return result;
115
+
116
+ } @catch (NSException *e) {
117
+ NSLog(@"❌ Fatal exception in GetAudioDevicesElectronSafe: %@", e.reason);
118
+ Napi::Error::New(env, "Failed to get audio devices").ThrowAsJavaScriptException();
119
+ return env.Null();
120
+ }
121
+ }
122
+
123
+ // Initialize audio capture module
124
+ Napi::Object InitAudioCaptureElectron(Napi::Env env, Napi::Object exports) {
125
+ @try {
126
+ initializeAudioQueue();
127
+
128
+ exports.Set("getAudioDevices", Napi::Function::New(env, GetAudioDevicesElectronSafe));
129
+
130
+ NSLog(@"✅ Electron-safe audio capture initialized");
131
+ return exports;
132
+
133
+ } @catch (NSException *e) {
134
+ NSLog(@"❌ Exception initializing audio capture: %@", e.reason);
135
+ return exports;
136
+ }
137
+ }
@@ -0,0 +1,90 @@
1
+ #import <napi.h>
2
+ #import <CoreGraphics/CoreGraphics.h>
3
+ #import <AppKit/AppKit.h>
4
+
5
+ // Thread-safe cursor tracking for Electron
6
+ static dispatch_queue_t g_cursorQueue = nil;
7
+
8
+ static void initializeCursorQueue() {
9
+ static dispatch_once_t onceToken;
10
+ dispatch_once(&onceToken, ^{
11
+ g_cursorQueue = dispatch_queue_create("com.macrecorder.cursor.electron", DISPATCH_QUEUE_SERIAL);
12
+ });
13
+ }
14
+
15
+ // NAPI Function: Get Cursor Position (Electron-safe)
16
+ Napi::Value GetCursorPositionElectronSafe(const Napi::CallbackInfo& info) {
17
+ Napi::Env env = info.Env();
18
+
19
+ @try {
20
+ initializeCursorQueue();
21
+
22
+ __block CGPoint mouseLocation = CGPointZero;
23
+ __block NSString *cursorType = @"arrow";
24
+
25
+ dispatch_sync(g_cursorQueue, ^{
26
+ @try {
27
+ // Get mouse location
28
+ mouseLocation = [NSEvent mouseLocation];
29
+
30
+ // Convert to screen coordinates (flip Y)
31
+ NSArray *screens = [NSScreen screens];
32
+ if (screens.count > 0) {
33
+ NSScreen *mainScreen = screens[0];
34
+ CGFloat screenHeight = mainScreen.frame.size.height;
35
+ mouseLocation.y = screenHeight - mouseLocation.y;
36
+ }
37
+
38
+ // Get cursor type safely
39
+ NSCursor *currentCursor = [NSCursor currentCursor];
40
+ if (currentCursor) {
41
+ if (currentCursor == [NSCursor arrowCursor]) {
42
+ cursorType = @"arrow";
43
+ } else if (currentCursor == [NSCursor IBeamCursor]) {
44
+ cursorType = @"ibeam";
45
+ } else if (currentCursor == [NSCursor pointingHandCursor]) {
46
+ cursorType = @"hand";
47
+ } else if (currentCursor == [NSCursor resizeLeftRightCursor]) {
48
+ cursorType = @"resize-horizontal";
49
+ } else if (currentCursor == [NSCursor resizeUpDownCursor]) {
50
+ cursorType = @"resize-vertical";
51
+ } else {
52
+ cursorType = @"default";
53
+ }
54
+ }
55
+
56
+ } @catch (NSException *e) {
57
+ NSLog(@"❌ Exception getting cursor position: %@", e.reason);
58
+ }
59
+ });
60
+
61
+ Napi::Object result = Napi::Object::New(env);
62
+ result.Set("x", Napi::Number::New(env, mouseLocation.x));
63
+ result.Set("y", Napi::Number::New(env, mouseLocation.y));
64
+ result.Set("cursorType", Napi::String::New(env, [cursorType UTF8String]));
65
+ result.Set("eventType", Napi::String::New(env, "move"));
66
+
67
+ return result;
68
+
69
+ } @catch (NSException *e) {
70
+ NSLog(@"❌ Fatal exception in GetCursorPositionElectronSafe: %@", e.reason);
71
+ Napi::Error::New(env, "Failed to get cursor position").ThrowAsJavaScriptException();
72
+ return env.Null();
73
+ }
74
+ }
75
+
76
+ // Initialize cursor tracker module
77
+ Napi::Object InitCursorTrackerElectron(Napi::Env env, Napi::Object exports) {
78
+ @try {
79
+ initializeCursorQueue();
80
+
81
+ exports.Set("getCursorPosition", Napi::Function::New(env, GetCursorPositionElectronSafe));
82
+
83
+ NSLog(@"✅ Electron-safe cursor tracker initialized");
84
+ return exports;
85
+
86
+ } @catch (NSException *e) {
87
+ NSLog(@"❌ Exception initializing cursor tracker: %@", e.reason);
88
+ return exports;
89
+ }
90
+ }
@@ -0,0 +1,337 @@
1
+ #import <napi.h>
2
+ #import <AVFoundation/AVFoundation.h>
3
+ #import <CoreMedia/CoreMedia.h>
4
+ #import <AppKit/AppKit.h>
5
+ #import <Foundation/Foundation.h>
6
+ #import <CoreGraphics/CoreGraphics.h>
7
+ #import <ImageIO/ImageIO.h>
8
+ #import <CoreAudio/CoreAudio.h>
9
+
10
+ // Electron-safe screen capture headers
11
+ #import "screen_capture_electron.h"
12
+
13
+ // Forward declarations for other modules
14
+ Napi::Object InitCursorTrackerElectron(Napi::Env env, Napi::Object exports);
15
+ Napi::Object InitWindowSelectorElectron(Napi::Env env, Napi::Object exports);
16
+
17
+ // Thread-safe recording state with proper synchronization
18
+ @interface ElectronSafeRecordingState : NSObject
19
+ @property (atomic) BOOL isRecording;
20
+ @property (atomic, strong) NSString *outputPath;
21
+ @property (atomic, strong) NSDate *startTime;
22
+ + (instancetype)sharedState;
23
+ - (void)resetState;
24
+ @end
25
+
26
+ @implementation ElectronSafeRecordingState
27
+ + (instancetype)sharedState {
28
+ static ElectronSafeRecordingState *shared = nil;
29
+ static dispatch_once_t onceToken;
30
+ dispatch_once(&onceToken, ^{
31
+ shared = [[ElectronSafeRecordingState alloc] init];
32
+ });
33
+ return shared;
34
+ }
35
+
36
+ - (instancetype)init {
37
+ if (self = [super init]) {
38
+ [self resetState];
39
+ }
40
+ return self;
41
+ }
42
+
43
+ - (void)resetState {
44
+ self.isRecording = NO;
45
+ self.outputPath = nil;
46
+ self.startTime = nil;
47
+ }
48
+ @end
49
+
50
+ // Electron-safe cleanup function
51
+ void electronSafeCleanup() {
52
+ dispatch_async(dispatch_get_main_queue(), ^{
53
+ @try {
54
+ [[ElectronSafeRecordingState sharedState] resetState];
55
+
56
+ // Stop any active recording safely
57
+ if (@available(macOS 12.3, *)) {
58
+ [ElectronSafeScreenCapture stopRecordingSafely];
59
+ }
60
+ } @catch (NSException *e) {
61
+ NSLog(@"⚠️ Safe cleanup exception: %@", e.reason);
62
+ }
63
+ });
64
+ }
65
+
66
+ // NAPI Function: Electron-safe Start Recording
67
+ Napi::Value StartRecordingElectronSafe(const Napi::CallbackInfo& info) {
68
+ Napi::Env env = info.Env();
69
+
70
+ @try {
71
+ if (info.Length() < 1) {
72
+ Napi::TypeError::New(env, "Output path required").ThrowAsJavaScriptException();
73
+ return env.Null();
74
+ }
75
+
76
+ ElectronSafeRecordingState *state = [ElectronSafeRecordingState sharedState];
77
+
78
+ if (state.isRecording) {
79
+ NSLog(@"⚠️ Recording already in progress");
80
+ return Napi::Boolean::New(env, false);
81
+ }
82
+
83
+ std::string outputPath = info[0].As<Napi::String>().Utf8Value();
84
+
85
+ // Parse options safely
86
+ NSDictionary *options = @{};
87
+ if (info.Length() > 1 && info[1].IsObject()) {
88
+ Napi::Object optionsObj = info[1].As<Napi::Object>();
89
+ NSMutableDictionary *mutableOptions = [NSMutableDictionary dictionary];
90
+
91
+ // Extract options with proper type checking
92
+ if (optionsObj.Has("captureCursor")) {
93
+ mutableOptions[@"captureCursor"] = @(optionsObj.Get("captureCursor").As<Napi::Boolean>().Value());
94
+ }
95
+ if (optionsObj.Has("includeMicrophone")) {
96
+ mutableOptions[@"includeMicrophone"] = @(optionsObj.Get("includeMicrophone").As<Napi::Boolean>().Value());
97
+ }
98
+ if (optionsObj.Has("includeSystemAudio")) {
99
+ mutableOptions[@"includeSystemAudio"] = @(optionsObj.Get("includeSystemAudio").As<Napi::Boolean>().Value());
100
+ }
101
+ if (optionsObj.Has("displayId")) {
102
+ mutableOptions[@"displayId"] = @(optionsObj.Get("displayId").As<Napi::Number>().Uint32Value());
103
+ }
104
+ if (optionsObj.Has("windowId")) {
105
+ mutableOptions[@"windowId"] = @(optionsObj.Get("windowId").As<Napi::Number>().Uint32Value());
106
+ }
107
+
108
+ // Capture area with bounds checking
109
+ if (optionsObj.Has("captureArea") && optionsObj.Get("captureArea").IsObject()) {
110
+ Napi::Object areaObj = optionsObj.Get("captureArea").As<Napi::Object>();
111
+ if (areaObj.Has("x") && areaObj.Has("y") && areaObj.Has("width") && areaObj.Has("height")) {
112
+ double x = areaObj.Get("x").As<Napi::Number>().DoubleValue();
113
+ double y = areaObj.Get("y").As<Napi::Number>().DoubleValue();
114
+ double width = areaObj.Get("width").As<Napi::Number>().DoubleValue();
115
+ double height = areaObj.Get("height").As<Napi::Number>().DoubleValue();
116
+
117
+ // Validate bounds
118
+ if (width > 0 && height > 0 && x >= 0 && y >= 0) {
119
+ mutableOptions[@"captureArea"] = @{
120
+ @"x": @(x),
121
+ @"y": @(y),
122
+ @"width": @(width),
123
+ @"height": @(height)
124
+ };
125
+ }
126
+ }
127
+ }
128
+
129
+ options = [mutableOptions copy];
130
+ }
131
+
132
+ // Start recording on main queue for Electron safety
133
+ __block BOOL success = NO;
134
+ dispatch_sync(dispatch_get_main_queue(), ^{
135
+ @try {
136
+ NSString *nsOutputPath = [NSString stringWithUTF8String:outputPath.c_str()];
137
+
138
+ if (@available(macOS 12.3, *)) {
139
+ success = [ElectronSafeScreenCapture startRecordingWithPath:nsOutputPath options:options];
140
+ } else {
141
+ NSLog(@"❌ ScreenCaptureKit not available on this macOS version");
142
+ success = NO;
143
+ }
144
+
145
+ if (success) {
146
+ state.isRecording = YES;
147
+ state.outputPath = nsOutputPath;
148
+ state.startTime = [NSDate date];
149
+ NSLog(@"✅ Electron-safe recording started: %@", nsOutputPath);
150
+ }
151
+ } @catch (NSException *e) {
152
+ NSLog(@"❌ Recording start exception: %@", e.reason);
153
+ success = NO;
154
+ }
155
+ });
156
+
157
+ return Napi::Boolean::New(env, success);
158
+
159
+ } @catch (NSException *e) {
160
+ NSLog(@"❌ Fatal exception in StartRecordingElectronSafe: %@", e.reason);
161
+ electronSafeCleanup();
162
+ Napi::Error::New(env, "Native recording failed").ThrowAsJavaScriptException();
163
+ return env.Null();
164
+ }
165
+ }
166
+
167
+ // NAPI Function: Electron-safe Stop Recording
168
+ Napi::Value StopRecordingElectronSafe(const Napi::CallbackInfo& info) {
169
+ Napi::Env env = info.Env();
170
+
171
+ @try {
172
+ ElectronSafeRecordingState *state = [ElectronSafeRecordingState sharedState];
173
+
174
+ if (!state.isRecording) {
175
+ NSLog(@"⚠️ No recording in progress");
176
+ return Napi::Boolean::New(env, false);
177
+ }
178
+
179
+ __block BOOL success = NO;
180
+ dispatch_sync(dispatch_get_main_queue(), ^{
181
+ @try {
182
+ if (@available(macOS 12.3, *)) {
183
+ success = [ElectronSafeScreenCapture stopRecordingSafely];
184
+ }
185
+
186
+ // Always reset state
187
+ [state resetState];
188
+
189
+ NSLog(@"✅ Electron-safe recording stopped");
190
+ } @catch (NSException *e) {
191
+ NSLog(@"❌ Recording stop exception: %@", e.reason);
192
+ [state resetState]; // Reset state even on error
193
+ success = NO;
194
+ }
195
+ });
196
+
197
+ return Napi::Boolean::New(env, success);
198
+
199
+ } @catch (NSException *e) {
200
+ NSLog(@"❌ Fatal exception in StopRecordingElectronSafe: %@", e.reason);
201
+ electronSafeCleanup();
202
+ Napi::Error::New(env, "Native stop recording failed").ThrowAsJavaScriptException();
203
+ return env.Null();
204
+ }
205
+ }
206
+
207
+ // NAPI Function: Get Recording Status (Electron-safe)
208
+ Napi::Value GetRecordingStatusElectronSafe(const Napi::CallbackInfo& info) {
209
+ Napi::Env env = info.Env();
210
+
211
+ @try {
212
+ ElectronSafeRecordingState *state = [ElectronSafeRecordingState sharedState];
213
+
214
+ Napi::Object status = Napi::Object::New(env);
215
+ status.Set("isRecording", Napi::Boolean::New(env, state.isRecording));
216
+
217
+ if (state.outputPath) {
218
+ status.Set("outputPath", Napi::String::New(env, [state.outputPath UTF8String]));
219
+ }
220
+
221
+ if (state.startTime) {
222
+ NSTimeInterval elapsed = [[NSDate date] timeIntervalSinceDate:state.startTime];
223
+ status.Set("elapsedTime", Napi::Number::New(env, elapsed));
224
+ }
225
+
226
+ return status;
227
+
228
+ } @catch (NSException *e) {
229
+ NSLog(@"❌ Fatal exception in GetRecordingStatusElectronSafe: %@", e.reason);
230
+ Napi::Error::New(env, "Failed to get status").ThrowAsJavaScriptException();
231
+ return env.Null();
232
+ }
233
+ }
234
+
235
+ // NAPI Function: Get Displays (Electron-safe)
236
+ Napi::Value GetDisplaysElectronSafe(const Napi::CallbackInfo& info) {
237
+ Napi::Env env = info.Env();
238
+
239
+ @try {
240
+ __block NSArray *displays = nil;
241
+
242
+ dispatch_sync(dispatch_get_main_queue(), ^{
243
+ @try {
244
+ displays = [ElectronSafeScreenCapture getAvailableDisplays];
245
+ } @catch (NSException *e) {
246
+ NSLog(@"❌ Exception getting displays: %@", e.reason);
247
+ }
248
+ });
249
+
250
+ if (!displays) {
251
+ return Napi::Array::New(env, 0);
252
+ }
253
+
254
+ Napi::Array result = Napi::Array::New(env, displays.count);
255
+ for (NSUInteger i = 0; i < displays.count; i++) {
256
+ NSDictionary *display = displays[i];
257
+ Napi::Object displayObj = Napi::Object::New(env);
258
+
259
+ displayObj.Set("id", Napi::Number::New(env, [display[@"id"] unsignedIntValue]));
260
+ displayObj.Set("name", Napi::String::New(env, [display[@"name"] UTF8String]));
261
+ displayObj.Set("width", Napi::Number::New(env, [display[@"width"] intValue]));
262
+ displayObj.Set("height", Napi::Number::New(env, [display[@"height"] intValue]));
263
+ displayObj.Set("x", Napi::Number::New(env, [display[@"x"] intValue]));
264
+ displayObj.Set("y", Napi::Number::New(env, [display[@"y"] intValue]));
265
+ displayObj.Set("isPrimary", Napi::Boolean::New(env, [display[@"isPrimary"] boolValue]));
266
+
267
+ result.Set(i, displayObj);
268
+ }
269
+
270
+ return result;
271
+
272
+ } @catch (NSException *e) {
273
+ NSLog(@"❌ Fatal exception in GetDisplaysElectronSafe: %@", e.reason);
274
+ Napi::Error::New(env, "Failed to get displays").ThrowAsJavaScriptException();
275
+ return env.Null();
276
+ }
277
+ }
278
+
279
+ // NAPI Function: Check Permissions (Electron-safe)
280
+ Napi::Value CheckPermissionsElectronSafe(const Napi::CallbackInfo& info) {
281
+ Napi::Env env = info.Env();
282
+
283
+ @try {
284
+ __block BOOL hasPermission = NO;
285
+
286
+ dispatch_sync(dispatch_get_main_queue(), ^{
287
+ @try {
288
+ hasPermission = [ElectronSafeScreenCapture checkPermissions];
289
+ } @catch (NSException *e) {
290
+ NSLog(@"❌ Exception checking permissions: %@", e.reason);
291
+ }
292
+ });
293
+
294
+ return Napi::Boolean::New(env, hasPermission);
295
+
296
+ } @catch (NSException *e) {
297
+ NSLog(@"❌ Fatal exception in CheckPermissionsElectronSafe: %@", e.reason);
298
+ return Napi::Boolean::New(env, false);
299
+ }
300
+ }
301
+
302
+ // Module initialization for Electron
303
+ Napi::Object InitElectronSafe(Napi::Env env, Napi::Object exports) {
304
+ @try {
305
+ NSLog(@"🔌 Initializing Electron-safe mac-recorder module");
306
+
307
+ // Export Electron-safe functions
308
+ exports.Set("startRecording", Napi::Function::New(env, StartRecordingElectronSafe));
309
+ exports.Set("stopRecording", Napi::Function::New(env, StopRecordingElectronSafe));
310
+ exports.Set("getRecordingStatus", Napi::Function::New(env, GetRecordingStatusElectronSafe));
311
+ exports.Set("getDisplays", Napi::Function::New(env, GetDisplaysElectronSafe));
312
+ exports.Set("checkPermissions", Napi::Function::New(env, CheckPermissionsElectronSafe));
313
+
314
+ // Initialize sub-modules safely
315
+ @try {
316
+ InitCursorTrackerElectron(env, exports);
317
+ } @catch (NSException *e) {
318
+ NSLog(@"⚠️ Cursor tracker initialization failed: %@", e.reason);
319
+ }
320
+
321
+ @try {
322
+ InitWindowSelectorElectron(env, exports);
323
+ } @catch (NSException *e) {
324
+ NSLog(@"⚠️ Window selector initialization failed: %@", e.reason);
325
+ }
326
+
327
+ NSLog(@"✅ Electron-safe module initialized successfully");
328
+ return exports;
329
+
330
+ } @catch (NSException *e) {
331
+ NSLog(@"❌ Fatal exception during module initialization: %@", e.reason);
332
+ return exports;
333
+ }
334
+ }
335
+
336
+ // Register the module
337
+ NODE_API_MODULE(mac_recorder_electron, InitElectronSafe)
@@ -0,0 +1,30 @@
1
+ #ifndef SCREEN_CAPTURE_ELECTRON_H
2
+ #define SCREEN_CAPTURE_ELECTRON_H
3
+
4
+ #import <Foundation/Foundation.h>
5
+ #import <CoreGraphics/CoreGraphics.h>
6
+ #import <ScreenCaptureKit/ScreenCaptureKit.h>
7
+
8
+ @interface ElectronSafeScreenCapture : NSObject
9
+
10
+ // Core recording functions
11
+ + (BOOL)startRecordingWithPath:(NSString *)outputPath options:(NSDictionary *)options;
12
+ + (BOOL)stopRecordingSafely;
13
+ + (BOOL)isRecording;
14
+
15
+ // Information functions
16
+ + (NSArray *)getAvailableDisplays;
17
+ + (NSArray *)getAvailableWindows;
18
+ + (BOOL)checkPermissions;
19
+
20
+ // Thumbnail functions
21
+ + (NSString *)getDisplayThumbnailBase64:(CGDirectDisplayID)displayID
22
+ maxWidth:(NSInteger)maxWidth
23
+ maxHeight:(NSInteger)maxHeight;
24
+ + (NSString *)getWindowThumbnailBase64:(uint32_t)windowID
25
+ maxWidth:(NSInteger)maxWidth
26
+ maxHeight:(NSInteger)maxHeight;
27
+
28
+ @end
29
+
30
+ #endif // SCREEN_CAPTURE_ELECTRON_H