node-mac-recorder 1.0.5 → 1.2.0

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,399 @@
1
+ #import <napi.h>
2
+ #import <AppKit/AppKit.h>
3
+ #import <Foundation/Foundation.h>
4
+ #import <CoreGraphics/CoreGraphics.h>
5
+ #import <ApplicationServices/ApplicationServices.h>
6
+ #import <Carbon/Carbon.h>
7
+ #import <Accessibility/Accessibility.h>
8
+
9
+ // Global state for cursor tracking
10
+ static bool g_isCursorTracking = false;
11
+ static NSMutableArray *g_cursorData = nil;
12
+ static CFMachPortRef g_eventTap = NULL;
13
+ static CFRunLoopSourceRef g_runLoopSource = NULL;
14
+ static NSDate *g_trackingStartTime = nil;
15
+ static NSString *g_outputPath = nil;
16
+ static NSTimer *g_cursorTimer = nil;
17
+ static int g_debugCallbackCount = 0;
18
+
19
+ // Forward declaration
20
+ void cursorTimerCallback(NSTimer *timer);
21
+
22
+ // Timer helper class
23
+ @interface CursorTimerTarget : NSObject
24
+ - (void)timerCallback:(NSTimer *)timer;
25
+ @end
26
+
27
+ @implementation CursorTimerTarget
28
+ - (void)timerCallback:(NSTimer *)timer {
29
+ cursorTimerCallback(timer);
30
+ }
31
+ @end
32
+
33
+ static CursorTimerTarget *g_timerTarget = nil;
34
+
35
+ // Global cursor state tracking
36
+ static NSString *g_lastDetectedCursorType = nil;
37
+ static int g_cursorTypeCounter = 0;
38
+
39
+ // Cursor type detection helper
40
+ NSString* getCursorType() {
41
+ @autoreleasepool {
42
+ g_cursorTypeCounter++;
43
+
44
+ // Simple simulation - cycle through cursor types for demo
45
+ // Bu gerçek uygulamada daha akıllı olacak
46
+ int typeIndex = (g_cursorTypeCounter / 6) % 4; // Her 6 call'da değiştir (daha hızlı demo)
47
+
48
+ switch (typeIndex) {
49
+ case 0:
50
+ g_lastDetectedCursorType = @"default";
51
+ return @"default";
52
+ case 1:
53
+ g_lastDetectedCursorType = @"pointer";
54
+ return @"pointer";
55
+ case 2:
56
+ g_lastDetectedCursorType = @"text";
57
+ return @"text";
58
+ case 3:
59
+ g_lastDetectedCursorType = @"grabbing";
60
+ return @"grabbing";
61
+ default:
62
+ g_lastDetectedCursorType = @"default";
63
+ return @"default";
64
+ }
65
+ }
66
+ }
67
+
68
+ // Event callback for mouse events
69
+ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
70
+ @autoreleasepool {
71
+ if (!g_isCursorTracking || !g_cursorData || !g_trackingStartTime) {
72
+ return event;
73
+ }
74
+
75
+ CGPoint location = CGEventGetLocation(event);
76
+ NSTimeInterval timestamp = [[NSDate date] timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
77
+ NSString *cursorType = getCursorType();
78
+ NSString *eventType = @"move";
79
+
80
+ // Event tipini belirle
81
+ switch (type) {
82
+ case kCGEventLeftMouseDown:
83
+ case kCGEventRightMouseDown:
84
+ case kCGEventOtherMouseDown:
85
+ eventType = @"mousedown";
86
+ break;
87
+ case kCGEventLeftMouseUp:
88
+ case kCGEventRightMouseUp:
89
+ case kCGEventOtherMouseUp:
90
+ eventType = @"mouseup";
91
+ break;
92
+ case kCGEventLeftMouseDragged:
93
+ case kCGEventRightMouseDragged:
94
+ case kCGEventOtherMouseDragged:
95
+ eventType = @"drag";
96
+ break;
97
+ case kCGEventMouseMoved:
98
+ default:
99
+ eventType = @"move";
100
+ break;
101
+ }
102
+
103
+ // Cursor data oluştur
104
+ NSDictionary *cursorInfo = @{
105
+ @"x": @((int)location.x),
106
+ @"y": @((int)location.y),
107
+ @"timestamp": @((int)timestamp),
108
+ @"cursorType": cursorType,
109
+ @"type": eventType
110
+ };
111
+
112
+ // Thread-safe olarak array'e ekle
113
+ @synchronized(g_cursorData) {
114
+ [g_cursorData addObject:cursorInfo];
115
+ }
116
+
117
+ return event;
118
+ }
119
+ }
120
+
121
+ // Timer callback for periodic cursor position updates
122
+ void cursorTimerCallback(NSTimer *timer) {
123
+ @autoreleasepool {
124
+ g_debugCallbackCount++;
125
+
126
+ if (!g_isCursorTracking || !g_cursorData || !g_trackingStartTime) {
127
+ return;
128
+ }
129
+
130
+ // Ana thread'de mouse pozisyonu al
131
+ __block NSPoint mouseLocation;
132
+ __block CGPoint location;
133
+
134
+ if ([NSThread isMainThread]) {
135
+ mouseLocation = [NSEvent mouseLocation];
136
+ } else {
137
+ dispatch_sync(dispatch_get_main_queue(), ^{
138
+ mouseLocation = [NSEvent mouseLocation];
139
+ });
140
+ }
141
+
142
+ CGDirectDisplayID mainDisplay = CGMainDisplayID();
143
+ size_t displayHeight = CGDisplayPixelsHigh(mainDisplay);
144
+ location = CGPointMake(mouseLocation.x, displayHeight - mouseLocation.y);
145
+
146
+ NSTimeInterval timestamp = [[NSDate date] timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
147
+ NSString *cursorType = getCursorType();
148
+
149
+ // Cursor data oluştur
150
+ NSDictionary *cursorInfo = @{
151
+ @"x": @((int)location.x),
152
+ @"y": @((int)location.y),
153
+ @"timestamp": @((int)timestamp),
154
+ @"cursorType": cursorType,
155
+ @"type": @"move"
156
+ };
157
+
158
+ // Thread-safe olarak array'e ekle
159
+ @synchronized(g_cursorData) {
160
+ [g_cursorData addObject:cursorInfo];
161
+ }
162
+ }
163
+ }
164
+
165
+ // Helper function to cleanup cursor tracking
166
+ void cleanupCursorTracking() {
167
+ g_isCursorTracking = false;
168
+
169
+ // Timer'ı durdur
170
+ if (g_cursorTimer) {
171
+ [g_cursorTimer invalidate];
172
+ g_cursorTimer = nil;
173
+ }
174
+
175
+ // Timer target'ı temizle
176
+ if (g_timerTarget) {
177
+ [g_timerTarget release];
178
+ g_timerTarget = nil;
179
+ }
180
+
181
+ // Event tap'i durdur
182
+ if (g_eventTap) {
183
+ CGEventTapEnable(g_eventTap, false);
184
+ CFMachPortInvalidate(g_eventTap);
185
+ CFRelease(g_eventTap);
186
+ g_eventTap = NULL;
187
+ }
188
+
189
+ // Run loop source'unu kaldır
190
+ if (g_runLoopSource) {
191
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), g_runLoopSource, kCFRunLoopCommonModes);
192
+ CFRelease(g_runLoopSource);
193
+ g_runLoopSource = NULL;
194
+ }
195
+
196
+ g_cursorData = nil;
197
+ g_trackingStartTime = nil;
198
+ g_outputPath = nil;
199
+ g_debugCallbackCount = 0;
200
+ g_lastDetectedCursorType = nil;
201
+ g_cursorTypeCounter = 0;
202
+ }
203
+
204
+ // NAPI Function: Start Cursor Tracking
205
+ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
206
+ Napi::Env env = info.Env();
207
+
208
+ if (info.Length() < 1) {
209
+ Napi::TypeError::New(env, "Output path required").ThrowAsJavaScriptException();
210
+ return env.Null();
211
+ }
212
+
213
+ if (g_isCursorTracking) {
214
+ return Napi::Boolean::New(env, false);
215
+ }
216
+
217
+ std::string outputPath = info[0].As<Napi::String>().Utf8Value();
218
+
219
+ @try {
220
+ // Initialize cursor data array
221
+ g_cursorData = [[NSMutableArray alloc] init];
222
+ g_trackingStartTime = [NSDate date];
223
+ g_outputPath = [NSString stringWithUTF8String:outputPath.c_str()];
224
+
225
+ // Create event tap for mouse events
226
+ CGEventMask eventMask = (CGEventMaskBit(kCGEventLeftMouseDown) |
227
+ CGEventMaskBit(kCGEventLeftMouseUp) |
228
+ CGEventMaskBit(kCGEventRightMouseDown) |
229
+ CGEventMaskBit(kCGEventRightMouseUp) |
230
+ CGEventMaskBit(kCGEventOtherMouseDown) |
231
+ CGEventMaskBit(kCGEventOtherMouseUp) |
232
+ CGEventMaskBit(kCGEventMouseMoved) |
233
+ CGEventMaskBit(kCGEventLeftMouseDragged) |
234
+ CGEventMaskBit(kCGEventRightMouseDragged) |
235
+ CGEventMaskBit(kCGEventOtherMouseDragged));
236
+
237
+ g_eventTap = CGEventTapCreate(kCGSessionEventTap,
238
+ kCGHeadInsertEventTap,
239
+ kCGEventTapOptionListenOnly,
240
+ eventMask,
241
+ eventCallback,
242
+ NULL);
243
+
244
+ if (g_eventTap) {
245
+ // Event tap başarılı - detaylı event tracking aktif
246
+ g_runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, g_eventTap, 0);
247
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), g_runLoopSource, kCFRunLoopCommonModes);
248
+ CGEventTapEnable(g_eventTap, true);
249
+ }
250
+ // Event tap başarısız olsa da devam et - sadece timer ile tracking yapar
251
+
252
+ // Timer helper oluştur
253
+ g_timerTarget = [[CursorTimerTarget alloc] init];
254
+
255
+ // NSTimer kullan (ana thread'de çalışır)
256
+ g_cursorTimer = [NSTimer scheduledTimerWithTimeInterval:0.016667 // ~60 FPS
257
+ target:g_timerTarget
258
+ selector:@selector(timerCallback:)
259
+ userInfo:nil
260
+ repeats:YES];
261
+
262
+ // Timer'ı farklı run loop mode'larında da çalıştır
263
+ [[NSRunLoop currentRunLoop] addTimer:g_cursorTimer forMode:NSRunLoopCommonModes];
264
+
265
+ g_isCursorTracking = true;
266
+ return Napi::Boolean::New(env, true);
267
+
268
+ } @catch (NSException *exception) {
269
+ cleanupCursorTracking();
270
+ return Napi::Boolean::New(env, false);
271
+ }
272
+ }
273
+
274
+ // NAPI Function: Stop Cursor Tracking
275
+ Napi::Value StopCursorTracking(const Napi::CallbackInfo& info) {
276
+ Napi::Env env = info.Env();
277
+
278
+ if (!g_isCursorTracking) {
279
+ return Napi::Boolean::New(env, false);
280
+ }
281
+
282
+ @try {
283
+ // JSON dosyasını kaydet
284
+ if (g_cursorData && g_outputPath) {
285
+ @synchronized(g_cursorData) {
286
+ NSError *error;
287
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:g_cursorData
288
+ options:NSJSONWritingPrettyPrinted
289
+ error:&error];
290
+ if (jsonData && !error) {
291
+ [jsonData writeToFile:g_outputPath atomically:YES];
292
+ }
293
+ }
294
+ }
295
+
296
+ cleanupCursorTracking();
297
+ return Napi::Boolean::New(env, true);
298
+
299
+ } @catch (NSException *exception) {
300
+ cleanupCursorTracking();
301
+ return Napi::Boolean::New(env, false);
302
+ }
303
+ }
304
+
305
+ // NAPI Function: Get Current Cursor Position
306
+ Napi::Value GetCursorPosition(const Napi::CallbackInfo& info) {
307
+ Napi::Env env = info.Env();
308
+
309
+ @try {
310
+ // NSEvent kullanarak mouse pozisyonu al (daha güvenli)
311
+ NSPoint mouseLocation = [NSEvent mouseLocation];
312
+
313
+ // CGDisplayPixelsHigh ve CGDisplayPixelsWide ile koordinat dönüşümü
314
+ CGDirectDisplayID mainDisplay = CGMainDisplayID();
315
+ size_t displayHeight = CGDisplayPixelsHigh(mainDisplay);
316
+
317
+ // macOS coordinate system (bottom-left origin) to screen coordinates (top-left origin)
318
+ CGPoint location = CGPointMake(mouseLocation.x, displayHeight - mouseLocation.y);
319
+
320
+ NSString *cursorType = getCursorType();
321
+
322
+ Napi::Object result = Napi::Object::New(env);
323
+ result.Set("x", Napi::Number::New(env, (int)location.x));
324
+ result.Set("y", Napi::Number::New(env, (int)location.y));
325
+ result.Set("cursorType", Napi::String::New(env, [cursorType UTF8String]));
326
+
327
+ return result;
328
+
329
+ } @catch (NSException *exception) {
330
+ return env.Null();
331
+ }
332
+ }
333
+
334
+ // NAPI Function: Get Cursor Tracking Status
335
+ Napi::Value GetCursorTrackingStatus(const Napi::CallbackInfo& info) {
336
+ Napi::Env env = info.Env();
337
+
338
+ Napi::Object result = Napi::Object::New(env);
339
+ result.Set("isTracking", Napi::Boolean::New(env, g_isCursorTracking));
340
+
341
+ NSUInteger dataCount = 0;
342
+ if (g_cursorData) {
343
+ @synchronized(g_cursorData) {
344
+ dataCount = [g_cursorData count];
345
+ }
346
+ }
347
+
348
+ result.Set("dataCount", Napi::Number::New(env, (int)dataCount));
349
+ result.Set("hasEventTap", Napi::Boolean::New(env, g_eventTap != NULL));
350
+ result.Set("hasRunLoopSource", Napi::Boolean::New(env, g_runLoopSource != NULL));
351
+ result.Set("debugCallbackCount", Napi::Number::New(env, g_debugCallbackCount));
352
+ result.Set("cursorTypeCounter", Napi::Number::New(env, g_cursorTypeCounter));
353
+
354
+ return result;
355
+ }
356
+
357
+ // NAPI Function: Save Cursor Data
358
+ Napi::Value SaveCursorData(const Napi::CallbackInfo& info) {
359
+ Napi::Env env = info.Env();
360
+
361
+ if (info.Length() < 1) {
362
+ Napi::TypeError::New(env, "Output path required").ThrowAsJavaScriptException();
363
+ return env.Null();
364
+ }
365
+
366
+ std::string outputPath = info[0].As<Napi::String>().Utf8Value();
367
+
368
+ @try {
369
+ if (g_cursorData) {
370
+ @synchronized(g_cursorData) {
371
+ NSError *error;
372
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:g_cursorData
373
+ options:NSJSONWritingPrettyPrinted
374
+ error:&error];
375
+ if (jsonData && !error) {
376
+ NSString *filePath = [NSString stringWithUTF8String:outputPath.c_str()];
377
+ BOOL success = [jsonData writeToFile:filePath atomically:YES];
378
+ return Napi::Boolean::New(env, success);
379
+ }
380
+ }
381
+ }
382
+
383
+ return Napi::Boolean::New(env, false);
384
+
385
+ } @catch (NSException *exception) {
386
+ return Napi::Boolean::New(env, false);
387
+ }
388
+ }
389
+
390
+ // Export functions
391
+ Napi::Object InitCursorTracker(Napi::Env env, Napi::Object exports) {
392
+ exports.Set("startCursorTracking", Napi::Function::New(env, StartCursorTracking));
393
+ exports.Set("stopCursorTracking", Napi::Function::New(env, StopCursorTracking));
394
+ exports.Set("getCursorPosition", Napi::Function::New(env, GetCursorPosition));
395
+ exports.Set("getCursorTrackingStatus", Napi::Function::New(env, GetCursorTrackingStatus));
396
+ exports.Set("saveCursorData", Napi::Function::New(env, SaveCursorData));
397
+
398
+ return exports;
399
+ }
@@ -7,6 +7,9 @@
7
7
  #import <ImageIO/ImageIO.h>
8
8
  #import <CoreAudio/CoreAudio.h>
9
9
 
10
+ // Cursor tracker function declarations
11
+ Napi::Object InitCursorTracker(Napi::Env env, Napi::Object exports);
12
+
10
13
  @interface MacRecorderDelegate : NSObject <AVCaptureFileOutputRecordingDelegate>
11
14
  @property (nonatomic, copy) void (^completionHandler)(NSURL *outputURL, NSError *error);
12
15
  @end
@@ -413,6 +416,195 @@ Napi::Value GetRecordingStatus(const Napi::CallbackInfo& info) {
413
416
  return Napi::Boolean::New(env, g_isRecording);
414
417
  }
415
418
 
419
+ // NAPI Function: Get Window Thumbnail
420
+ Napi::Value GetWindowThumbnail(const Napi::CallbackInfo& info) {
421
+ Napi::Env env = info.Env();
422
+
423
+ if (info.Length() < 1) {
424
+ Napi::TypeError::New(env, "Window ID is required").ThrowAsJavaScriptException();
425
+ return env.Null();
426
+ }
427
+
428
+ uint32_t windowID = info[0].As<Napi::Number>().Uint32Value();
429
+
430
+ // Optional parameters
431
+ int maxWidth = 300; // Default thumbnail width
432
+ int maxHeight = 200; // Default thumbnail height
433
+
434
+ if (info.Length() >= 2 && !info[1].IsNull()) {
435
+ maxWidth = info[1].As<Napi::Number>().Int32Value();
436
+ }
437
+ if (info.Length() >= 3 && !info[2].IsNull()) {
438
+ maxHeight = info[2].As<Napi::Number>().Int32Value();
439
+ }
440
+
441
+ @try {
442
+ // Create window image
443
+ CGImageRef windowImage = CGWindowListCreateImage(
444
+ CGRectNull,
445
+ kCGWindowListOptionIncludingWindow,
446
+ windowID,
447
+ kCGWindowImageBoundsIgnoreFraming | kCGWindowImageShouldBeOpaque
448
+ );
449
+
450
+ if (!windowImage) {
451
+ return env.Null();
452
+ }
453
+
454
+ // Get original dimensions
455
+ size_t originalWidth = CGImageGetWidth(windowImage);
456
+ size_t originalHeight = CGImageGetHeight(windowImage);
457
+
458
+ // Calculate scaled dimensions maintaining aspect ratio
459
+ double scaleX = (double)maxWidth / originalWidth;
460
+ double scaleY = (double)maxHeight / originalHeight;
461
+ double scale = std::min(scaleX, scaleY);
462
+
463
+ size_t thumbnailWidth = (size_t)(originalWidth * scale);
464
+ size_t thumbnailHeight = (size_t)(originalHeight * scale);
465
+
466
+ // Create scaled image
467
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
468
+ CGContextRef context = CGBitmapContextCreate(
469
+ NULL,
470
+ thumbnailWidth,
471
+ thumbnailHeight,
472
+ 8,
473
+ thumbnailWidth * 4,
474
+ colorSpace,
475
+ kCGImageAlphaPremultipliedLast
476
+ );
477
+
478
+ if (context) {
479
+ CGContextDrawImage(context, CGRectMake(0, 0, thumbnailWidth, thumbnailHeight), windowImage);
480
+ CGImageRef thumbnailImage = CGBitmapContextCreateImage(context);
481
+
482
+ if (thumbnailImage) {
483
+ // Convert to PNG data
484
+ NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:thumbnailImage];
485
+ NSData *pngData = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
486
+
487
+ if (pngData) {
488
+ // Convert to Base64
489
+ NSString *base64String = [pngData base64EncodedStringWithOptions:0];
490
+ std::string base64Std = [base64String UTF8String];
491
+
492
+ CGImageRelease(thumbnailImage);
493
+ CGContextRelease(context);
494
+ CGColorSpaceRelease(colorSpace);
495
+ CGImageRelease(windowImage);
496
+
497
+ return Napi::String::New(env, base64Std);
498
+ }
499
+
500
+ CGImageRelease(thumbnailImage);
501
+ }
502
+
503
+ CGContextRelease(context);
504
+ }
505
+
506
+ CGColorSpaceRelease(colorSpace);
507
+ CGImageRelease(windowImage);
508
+
509
+ return env.Null();
510
+
511
+ } @catch (NSException *exception) {
512
+ return env.Null();
513
+ }
514
+ }
515
+
516
+ // NAPI Function: Get Display Thumbnail
517
+ Napi::Value GetDisplayThumbnail(const Napi::CallbackInfo& info) {
518
+ Napi::Env env = info.Env();
519
+
520
+ if (info.Length() < 1) {
521
+ Napi::TypeError::New(env, "Display ID is required").ThrowAsJavaScriptException();
522
+ return env.Null();
523
+ }
524
+
525
+ uint32_t displayID = info[0].As<Napi::Number>().Uint32Value();
526
+
527
+ // Optional parameters
528
+ int maxWidth = 300; // Default thumbnail width
529
+ int maxHeight = 200; // Default thumbnail height
530
+
531
+ if (info.Length() >= 2 && !info[1].IsNull()) {
532
+ maxWidth = info[1].As<Napi::Number>().Int32Value();
533
+ }
534
+ if (info.Length() >= 3 && !info[2].IsNull()) {
535
+ maxHeight = info[2].As<Napi::Number>().Int32Value();
536
+ }
537
+
538
+ @try {
539
+ // Create display image
540
+ CGImageRef displayImage = CGDisplayCreateImage(displayID);
541
+
542
+ if (!displayImage) {
543
+ return env.Null();
544
+ }
545
+
546
+ // Get original dimensions
547
+ size_t originalWidth = CGImageGetWidth(displayImage);
548
+ size_t originalHeight = CGImageGetHeight(displayImage);
549
+
550
+ // Calculate scaled dimensions maintaining aspect ratio
551
+ double scaleX = (double)maxWidth / originalWidth;
552
+ double scaleY = (double)maxHeight / originalHeight;
553
+ double scale = std::min(scaleX, scaleY);
554
+
555
+ size_t thumbnailWidth = (size_t)(originalWidth * scale);
556
+ size_t thumbnailHeight = (size_t)(originalHeight * scale);
557
+
558
+ // Create scaled image
559
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
560
+ CGContextRef context = CGBitmapContextCreate(
561
+ NULL,
562
+ thumbnailWidth,
563
+ thumbnailHeight,
564
+ 8,
565
+ thumbnailWidth * 4,
566
+ colorSpace,
567
+ kCGImageAlphaPremultipliedLast
568
+ );
569
+
570
+ if (context) {
571
+ CGContextDrawImage(context, CGRectMake(0, 0, thumbnailWidth, thumbnailHeight), displayImage);
572
+ CGImageRef thumbnailImage = CGBitmapContextCreateImage(context);
573
+
574
+ if (thumbnailImage) {
575
+ // Convert to PNG data
576
+ NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:thumbnailImage];
577
+ NSData *pngData = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
578
+
579
+ if (pngData) {
580
+ // Convert to Base64
581
+ NSString *base64String = [pngData base64EncodedStringWithOptions:0];
582
+ std::string base64Std = [base64String UTF8String];
583
+
584
+ CGImageRelease(thumbnailImage);
585
+ CGContextRelease(context);
586
+ CGColorSpaceRelease(colorSpace);
587
+ CGImageRelease(displayImage);
588
+
589
+ return Napi::String::New(env, base64Std);
590
+ }
591
+
592
+ CGImageRelease(thumbnailImage);
593
+ }
594
+
595
+ CGContextRelease(context);
596
+ }
597
+
598
+ CGColorSpaceRelease(colorSpace);
599
+ CGImageRelease(displayImage);
600
+
601
+ return env.Null();
602
+
603
+ } @catch (NSException *exception) {
604
+ return env.Null();
605
+ }
606
+ }
607
+
416
608
  // NAPI Function: Check Permissions
417
609
  Napi::Value CheckPermissions(const Napi::CallbackInfo& info) {
418
610
  Napi::Env env = info.Env();
@@ -466,6 +658,13 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
466
658
  exports.Set(Napi::String::New(env, "getRecordingStatus"), Napi::Function::New(env, GetRecordingStatus));
467
659
  exports.Set(Napi::String::New(env, "checkPermissions"), Napi::Function::New(env, CheckPermissions));
468
660
 
661
+ // Thumbnail functions
662
+ exports.Set(Napi::String::New(env, "getWindowThumbnail"), Napi::Function::New(env, GetWindowThumbnail));
663
+ exports.Set(Napi::String::New(env, "getDisplayThumbnail"), Napi::Function::New(env, GetDisplayThumbnail));
664
+
665
+ // Initialize cursor tracker
666
+ InitCursorTracker(env, exports);
667
+
469
668
  return exports;
470
669
  }
471
670