node-mac-recorder 2.4.10 → 2.4.12

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.
@@ -5,7 +5,6 @@
5
5
  #import <ApplicationServices/ApplicationServices.h>
6
6
  #import <Carbon/Carbon.h>
7
7
  #import <Accessibility/Accessibility.h>
8
- #import <ScreenCaptureKit/ScreenCaptureKit.h>
9
8
 
10
9
  // Global state for window selection
11
10
  static bool g_isWindowSelecting = false;
@@ -36,7 +35,6 @@ void cleanupWindowSelector();
36
35
  void updateOverlay();
37
36
  NSDictionary* getWindowUnderCursor(CGPoint point);
38
37
  NSArray* getAllSelectableWindows();
39
- NSArray* getAllSelectableWindowsLegacy();
40
38
  bool bringWindowToFront(int windowId);
41
39
  void cleanupRecordingPreview();
42
40
  bool showRecordingPreview(NSDictionary *windowInfo);
@@ -223,10 +221,8 @@ bool hideScreenRecordingPreview();
223
221
  @implementation WindowSelectorDelegate
224
222
  - (void)selectButtonClicked:(id)sender {
225
223
  if (g_currentWindowUnderCursor) {
226
- g_selectedWindowInfo = [g_currentWindowUnderCursor copy];
227
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
228
- cleanupWindowSelector();
229
- });
224
+ g_selectedWindowInfo = [g_currentWindowUnderCursor retain];
225
+ cleanupWindowSelector();
230
226
  }
231
227
  }
232
228
 
@@ -235,24 +231,25 @@ bool hideScreenRecordingPreview();
235
231
  NSInteger screenIndex = [button tag];
236
232
 
237
233
  // Get screen info from global array using button tag
238
- if (g_allScreens && screenIndex >= 0 && screenIndex < (NSInteger)[g_allScreens count]) {
234
+ if (g_allScreens && screenIndex >= 0 && screenIndex < [g_allScreens count]) {
239
235
  NSDictionary *screenInfo = [g_allScreens objectAtIndex:screenIndex];
240
- g_selectedScreenInfo = [screenInfo copy];
241
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
242
- cleanupScreenSelector();
243
- });
236
+ g_selectedScreenInfo = [screenInfo retain];
237
+
238
+ NSLog(@"🖥️ SCREEN BUTTON CLICKED: %@ (%@)",
239
+ [screenInfo objectForKey:@"name"],
240
+ [screenInfo objectForKey:@"resolution"]);
241
+
242
+ cleanupScreenSelector();
244
243
  }
245
244
  }
246
245
 
247
246
  - (void)cancelButtonClicked:(id)sender {
247
+ NSLog(@"🚫 CANCEL BUTTON CLICKED: Selection cancelled");
248
+ // Clean up without selecting anything
248
249
  if (g_isScreenSelecting) {
249
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
250
- cleanupScreenSelector();
251
- });
250
+ cleanupScreenSelector();
252
251
  } else {
253
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
254
- cleanupWindowSelector();
255
- });
252
+ cleanupWindowSelector();
256
253
  }
257
254
  }
258
255
 
@@ -369,83 +366,8 @@ bool bringWindowToFront(int windowId) {
369
366
  }
370
367
  }
371
368
 
372
- // Get all selectable windows using ScreenCaptureKit
369
+ // Get all selectable windows
373
370
  NSArray* getAllSelectableWindows() {
374
- @autoreleasepool {
375
- NSMutableArray *windows = [NSMutableArray array];
376
-
377
- // Check if ScreenCaptureKit is available (requires macOS 12.3+)
378
- if (@available(macOS 12.3, *)) {
379
- // Use dispatch_semaphore for synchronous SCShareableContent retrieval
380
- dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
381
- __block SCShareableContent *content = nil;
382
- __block NSError *error = nil;
383
-
384
- [SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent *shareableContent, NSError *contentError) {
385
- content = shareableContent;
386
- error = contentError;
387
- dispatch_semaphore_signal(semaphore);
388
- }];
389
-
390
- // Wait for completion (timeout after 5 seconds)
391
- dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
392
- if (dispatch_semaphore_wait(semaphore, timeout) == 0 && content) {
393
- NSLog(@"✅ ScreenCaptureKit: Found %lu windows", (unsigned long)content.windows.count);
394
-
395
- for (SCWindow *scWindow in content.windows) {
396
- // Skip windows without proper frame or title
397
- if (scWindow.frame.size.width < 50 || scWindow.frame.size.height < 50) continue;
398
- if (!scWindow.owningApplication) continue;
399
-
400
- // Skip system applications
401
- NSString *bundleId = scWindow.owningApplication.bundleIdentifier;
402
- NSString *appName = scWindow.owningApplication.applicationName;
403
- if ([bundleId hasPrefix:@"com.apple.dock"] ||
404
- [bundleId hasPrefix:@"com.apple.WindowServer"] ||
405
- [bundleId hasPrefix:@"com.apple.controlcenter"] ||
406
- [bundleId hasPrefix:@"com.apple.notificationcenterui"]) {
407
- continue;
408
- }
409
-
410
- // Convert SCWindow coordinates to match CGWindow coordinate system
411
- CGRect frame = scWindow.frame;
412
- int x = (int)frame.origin.x;
413
- int y = (int)frame.origin.y;
414
- int width = (int)frame.size.width;
415
- int height = (int)frame.size.height;
416
-
417
- NSString *windowTitle = scWindow.title ?: @"Untitled";
418
- if ([windowTitle length] == 0) windowTitle = @"Untitled";
419
-
420
- NSDictionary *window = @{
421
- @"id": @(scWindow.windowID),
422
- @"title": windowTitle,
423
- @"appName": appName ?: @"Unknown App",
424
- @"x": @(x),
425
- @"y": @(y),
426
- @"width": @(width),
427
- @"height": @(height),
428
- @"bundleId": bundleId ?: @""
429
- };
430
-
431
- [windows addObject:window];
432
- }
433
- } else {
434
- NSLog(@"❌ ScreenCaptureKit: Failed to get shareable content or timeout occurred");
435
- // Fall back to CGWindowList as backup
436
- return getAllSelectableWindowsLegacy();
437
- }
438
- } else {
439
- NSLog(@"⚠️ ScreenCaptureKit not available, falling back to CGWindowList");
440
- return getAllSelectableWindowsLegacy();
441
- }
442
-
443
- return [windows copy];
444
- }
445
- }
446
-
447
- // Legacy window enumeration using CGWindowList (fallback for older macOS)
448
- NSArray* getAllSelectableWindowsLegacy() {
449
371
  @autoreleasepool {
450
372
  NSMutableArray *windows = [NSMutableArray array];
451
373
 
@@ -521,11 +443,6 @@ NSDictionary* getWindowUnderCursor(CGPoint point) {
521
443
 
522
444
  // Update overlay to highlight window under cursor
523
445
  void updateOverlay() {
524
- // Ensure AppKit usage on main thread
525
- if (![NSThread isMainThread]) {
526
- dispatch_async(dispatch_get_main_queue(), ^{ updateOverlay(); });
527
- return;
528
- }
529
446
  @autoreleasepool {
530
447
  if (!g_isWindowSelecting || !g_overlayWindow) return;
531
448
 
@@ -541,7 +458,8 @@ void updateOverlay() {
541
458
 
542
459
  if (windowUnderCursor && ![windowUnderCursor isEqualToDictionary:g_currentWindowUnderCursor]) {
543
460
  // Update current window
544
- g_currentWindowUnderCursor = windowUnderCursor;
461
+ [g_currentWindowUnderCursor release];
462
+ g_currentWindowUnderCursor = [windowUnderCursor retain];
545
463
 
546
464
  // Update overlay position and size
547
465
  int x = [[windowUnderCursor objectForKey:@"x"] intValue];
@@ -632,6 +550,7 @@ void updateOverlay() {
632
550
  NSLog(@"🚪 WINDOW LEFT: %@ - \"%@\"", leftAppName, leftWindowTitle);
633
551
 
634
552
  [g_overlayWindow orderOut:nil];
553
+ [g_currentWindowUnderCursor release];
635
554
  g_currentWindowUnderCursor = nil;
636
555
  }
637
556
  }
@@ -639,12 +558,6 @@ void updateOverlay() {
639
558
 
640
559
  // Cleanup function
641
560
  void cleanupWindowSelector() {
642
- if (![NSThread isMainThread]) {
643
- dispatch_async(dispatch_get_main_queue(), ^{ cleanupWindowSelector(); });
644
- return;
645
- }
646
-
647
- NSLog(@"🧹 Cleaning up window selector resources");
648
561
  g_isWindowSelecting = false;
649
562
 
650
563
  // Stop tracking timer
@@ -669,25 +582,24 @@ void cleanupWindowSelector() {
669
582
 
670
583
  // Clean up delegate
671
584
  if (g_delegate) {
585
+ [g_delegate release];
672
586
  g_delegate = nil;
673
587
  }
674
588
 
675
589
  // Clean up data
676
590
  if (g_allWindows) {
591
+ [g_allWindows release];
677
592
  g_allWindows = nil;
678
593
  }
679
594
 
680
595
  if (g_currentWindowUnderCursor) {
596
+ [g_currentWindowUnderCursor release];
681
597
  g_currentWindowUnderCursor = nil;
682
598
  }
683
599
  }
684
600
 
685
601
  // Recording preview functions
686
602
  void cleanupRecordingPreview() {
687
- if (![NSThread isMainThread]) {
688
- dispatch_async(dispatch_get_main_queue(), ^{ cleanupRecordingPreview(); });
689
- return;
690
- }
691
603
  if (g_recordingPreviewWindow) {
692
604
  [g_recordingPreviewWindow close];
693
605
  g_recordingPreviewWindow = nil;
@@ -695,6 +607,7 @@ void cleanupRecordingPreview() {
695
607
  }
696
608
 
697
609
  if (g_recordingWindowInfo) {
610
+ [g_recordingWindowInfo release];
698
611
  g_recordingWindowInfo = nil;
699
612
  }
700
613
  }
@@ -707,7 +620,7 @@ bool showRecordingPreview(NSDictionary *windowInfo) {
707
620
  if (!windowInfo) return false;
708
621
 
709
622
  // Store window info
710
- g_recordingWindowInfo = windowInfo;
623
+ g_recordingWindowInfo = [windowInfo retain];
711
624
 
712
625
  // Get main screen bounds for full screen overlay
713
626
  NSScreen *mainScreen = [NSScreen mainScreen];
@@ -767,10 +680,6 @@ bool hideRecordingPreview() {
767
680
 
768
681
  // Screen selection functions
769
682
  void cleanupScreenSelector() {
770
- if (![NSThread isMainThread]) {
771
- dispatch_async(dispatch_get_main_queue(), ^{ cleanupScreenSelector(); });
772
- return;
773
- }
774
683
  g_isScreenSelecting = false;
775
684
 
776
685
  // Remove key event monitor
@@ -784,11 +693,13 @@ void cleanupScreenSelector() {
784
693
  for (NSWindow *overlayWindow in g_screenOverlayWindows) {
785
694
  [overlayWindow close];
786
695
  }
696
+ [g_screenOverlayWindows release];
787
697
  g_screenOverlayWindows = nil;
788
698
  }
789
699
 
790
700
  // Clean up screen data
791
701
  if (g_allScreens) {
702
+ [g_allScreens release];
792
703
  g_allScreens = nil;
793
704
  }
794
705
  }
@@ -796,13 +707,7 @@ void cleanupScreenSelector() {
796
707
  bool startScreenSelection() {
797
708
  @try {
798
709
  if (g_isScreenSelecting) return false;
799
- // Force to main thread
800
- if (![NSThread isMainThread]) {
801
- __block BOOL ok = NO;
802
- dispatch_sync(dispatch_get_main_queue(), ^{ ok = startScreenSelection(); });
803
- return ok;
804
- }
805
-
710
+
806
711
  // Get all available screens
807
712
  NSArray *screens = [NSScreen screens];
808
713
  if (!screens || [screens count] == 0) return false;
@@ -811,7 +716,7 @@ bool startScreenSelection() {
811
716
  NSMutableArray *screenInfoArray = [[NSMutableArray alloc] init];
812
717
  g_screenOverlayWindows = [[NSMutableArray alloc] init];
813
718
 
814
- for (NSUInteger i = 0; i < [screens count]; i++) {
719
+ for (NSInteger i = 0; i < [screens count]; i++) {
815
720
  NSScreen *screen = [screens objectAtIndex:i];
816
721
  NSRect screenFrame = [screen frame];
817
722
 
@@ -934,11 +839,11 @@ bool startScreenSelection() {
934
839
  [overlayWindow makeKeyAndOrderFront:nil];
935
840
 
936
841
  [g_screenOverlayWindows addObject:overlayWindow];
937
- screenInfo = nil;
842
+ [screenInfo release];
938
843
  }
939
844
 
940
- g_allScreens = screenInfoArray;
941
- screenInfoArray = nil;
845
+ g_allScreens = [screenInfoArray retain];
846
+ [screenInfoArray release];
942
847
  g_isScreenSelecting = true;
943
848
 
944
849
  // Add ESC key event monitor to cancel selection
@@ -964,12 +869,9 @@ bool startScreenSelection() {
964
869
  bool stopScreenSelection() {
965
870
  @try {
966
871
  if (!g_isScreenSelecting) return false;
967
- if (![NSThread isMainThread]) {
968
- __block BOOL ok = NO;
969
- dispatch_sync(dispatch_get_main_queue(), ^{ ok = stopScreenSelection(); });
970
- return ok;
971
- }
872
+
972
873
  cleanupScreenSelector();
874
+ NSLog(@"🖥️ SCREEN SELECTION: Stopped");
973
875
  return true;
974
876
 
975
877
  } @catch (NSException *exception) {
@@ -981,10 +883,11 @@ bool stopScreenSelection() {
981
883
  NSDictionary* getSelectedScreenInfo() {
982
884
  if (!g_selectedScreenInfo) return nil;
983
885
 
984
- NSDictionary *result = g_selectedScreenInfo;
886
+ NSDictionary *result = [g_selectedScreenInfo retain];
887
+ [g_selectedScreenInfo release];
985
888
  g_selectedScreenInfo = nil;
986
889
 
987
- return result;
890
+ return [result autorelease];
988
891
  }
989
892
 
990
893
  bool showScreenRecordingPreview(NSDictionary *screenInfo) {
@@ -999,10 +902,10 @@ bool showScreenRecordingPreview(NSDictionary *screenInfo) {
999
902
  NSArray *screens = [NSScreen screens];
1000
903
  if (!screens || [screens count] == 0) return false;
1001
904
 
1002
- NSUInteger selectedScreenId = (NSUInteger)[[screenInfo objectForKey:@"id"] intValue];
905
+ int selectedScreenId = [[screenInfo objectForKey:@"id"] intValue];
1003
906
 
1004
907
  // Create overlay for each screen except the selected one
1005
- for (NSUInteger i = 0; i < [screens count]; i++) {
908
+ for (NSInteger i = 0; i < [screens count]; i++) {
1006
909
  if (i == selectedScreenId) continue; // Skip selected screen
1007
910
 
1008
911
  NSScreen *screen = [screens objectAtIndex:i];
@@ -1033,7 +936,7 @@ bool showScreenRecordingPreview(NSDictionary *screenInfo) {
1033
936
  }
1034
937
  }
1035
938
 
1036
- NSLog(@"🎬 SCREEN RECORDING PREVIEW: Showing overlay for Screen %lu", (unsigned long)selectedScreenId);
939
+ NSLog(@"🎬 SCREEN RECORDING PREVIEW: Showing overlay for Screen %d", selectedScreenId);
1037
940
 
1038
941
  return true;
1039
942
 
@@ -1051,50 +954,14 @@ bool hideScreenRecordingPreview() {
1051
954
  Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1052
955
  Napi::Env env = info.Env();
1053
956
 
1054
- // Electron safety check - prevent NSWindow crashes
1055
- const char* electronVersion = getenv("ELECTRON_VERSION");
1056
- const char* electronRunAs = getenv("ELECTRON_RUN_AS_NODE");
1057
-
1058
- NSLog(@"🔍 Debug: electronVersion='%s', electronRunAs='%s'",
1059
- electronVersion ? electronVersion : "null",
1060
- electronRunAs ? electronRunAs : "null");
1061
-
1062
- if (electronVersion || electronRunAs) {
1063
- NSLog(@"🔍 Detected Electron environment - using safe mode");
1064
-
1065
- // In Electron, return window list without creating native NSWindow overlays
1066
- // The Electron app can handle UI selection itself
1067
- @try {
1068
- NSArray *windows = getAllSelectableWindows();
1069
-
1070
- if (!windows || [windows count] == 0) {
1071
- NSLog(@"❌ No selectable windows found");
1072
- return Napi::Boolean::New(env, false);
1073
- }
1074
-
1075
- // Store windows for later retrieval via getWindowSelectionStatus
1076
- g_allWindows = [getAllSelectableWindows() mutableCopy];
1077
- g_isWindowSelecting = true;
1078
-
1079
- // Return true to indicate windows are available
1080
- // Electron app should call getWindowSelectionStatus to get the list
1081
- NSLog(@"✅ Electron-safe mode: %lu windows available for selection", (unsigned long)[windows count]);
1082
- return Napi::Boolean::New(env, true);
1083
-
1084
- } @catch (NSException *exception) {
1085
- NSLog(@"❌ Exception in Electron-safe window selection: %@", [exception reason]);
1086
- return Napi::Boolean::New(env, false);
1087
- }
1088
- }
1089
-
1090
957
  if (g_isWindowSelecting) {
1091
- NSLog(@"⚠️ Window selection already in progress");
1092
- return Napi::Boolean::New(env, false);
958
+ Napi::TypeError::New(env, "Window selection already in progress").ThrowAsJavaScriptException();
959
+ return env.Null();
1093
960
  }
1094
961
 
1095
962
  @try {
1096
963
  // Get all windows
1097
- g_allWindows = [getAllSelectableWindows() mutableCopy];
964
+ g_allWindows = [getAllSelectableWindows() retain];
1098
965
 
1099
966
  if (!g_allWindows || [g_allWindows count] == 0) {
1100
967
  Napi::Error::New(env, "No selectable windows found").ThrowAsJavaScriptException();
@@ -1306,6 +1173,7 @@ Napi::Value GetSelectedWindowInfo(const Napi::CallbackInfo& info) {
1306
1173
  result.Set("screenHeight", Napi::Number::New(env, (int)screenFrame.size.height));
1307
1174
 
1308
1175
  // Clear selected window info after reading
1176
+ [g_selectedWindowInfo release];
1309
1177
  g_selectedWindowInfo = nil;
1310
1178
 
1311
1179
  return result;
@@ -1387,13 +1255,13 @@ Napi::Value ShowRecordingPreview(const Napi::CallbackInfo& info) {
1387
1255
  Napi::Env env = info.Env();
1388
1256
 
1389
1257
  if (info.Length() < 1) {
1390
- NSLog(@"⚠️ Window info object required");
1391
- return Napi::Boolean::New(env, false);
1258
+ Napi::TypeError::New(env, "Window info object required").ThrowAsJavaScriptException();
1259
+ return env.Null();
1392
1260
  }
1393
1261
 
1394
1262
  if (!info[0].IsObject()) {
1395
- NSLog(@"⚠️ Window info must be an object");
1396
- return Napi::Boolean::New(env, false);
1263
+ Napi::TypeError::New(env, "Window info must be an object").ThrowAsJavaScriptException();
1264
+ return env.Null();
1397
1265
  }
1398
1266
 
1399
1267
  @try {
@@ -1425,7 +1293,7 @@ Napi::Value ShowRecordingPreview(const Napi::CallbackInfo& info) {
1425
1293
  }
1426
1294
 
1427
1295
  bool success = showRecordingPreview(windowInfo);
1428
- windowInfo = nil;
1296
+ [windowInfo release];
1429
1297
 
1430
1298
  return Napi::Boolean::New(env, success);
1431
1299
 
@@ -1451,57 +1319,11 @@ Napi::Value HideRecordingPreview(const Napi::CallbackInfo& info) {
1451
1319
  Napi::Value StartScreenSelection(const Napi::CallbackInfo& info) {
1452
1320
  Napi::Env env = info.Env();
1453
1321
 
1454
- // Electron safety check - prevent NSWindow crashes
1455
- const char* electronVersion = getenv("ELECTRON_VERSION");
1456
- const char* electronRunAs = getenv("ELECTRON_RUN_AS_NODE");
1457
-
1458
- NSLog(@"🔍 Screen Debug: electronVersion='%s', electronRunAs='%s'",
1459
- electronVersion ? electronVersion : "null",
1460
- electronRunAs ? electronRunAs : "null");
1461
-
1462
- if (electronVersion || electronRunAs) {
1463
- NSLog(@"🔍 Detected Electron environment - using safe screen selection");
1464
-
1465
- // In Electron, return screen list without creating native NSWindow overlays
1466
- @try {
1467
- NSArray *screens = [NSScreen screens];
1468
-
1469
- if (!screens || [screens count] == 0) {
1470
- NSLog(@"❌ No screens available");
1471
- return Napi::Boolean::New(env, false);
1472
- }
1473
-
1474
- // Store screens and select first one automatically for Electron
1475
- g_allScreens = screens;
1476
- g_isScreenSelecting = true;
1477
-
1478
- NSScreen *mainScreen = [screens firstObject];
1479
- g_selectedScreenInfo = @{
1480
- @"id": @((int)[screens indexOfObject:mainScreen]),
1481
- @"width": @((int)mainScreen.frame.size.width),
1482
- @"height": @((int)mainScreen.frame.size.height),
1483
- @"x": @((int)mainScreen.frame.origin.x),
1484
- @"y": @((int)mainScreen.frame.origin.y)
1485
- };
1486
-
1487
- // Mark as complete so getSelectedScreenInfo returns the selection
1488
- g_isScreenSelecting = false;
1489
-
1490
- NSLog(@"✅ Electron-safe screen selection: %lu screens available", (unsigned long)[screens count]);
1491
- return Napi::Boolean::New(env, true);
1492
-
1493
- } @catch (NSException *exception) {
1494
- NSLog(@"❌ Exception in Electron-safe screen selection: %@", [exception reason]);
1495
- return Napi::Boolean::New(env, false);
1496
- }
1497
- }
1498
-
1499
1322
  @try {
1500
1323
  bool success = startScreenSelection();
1501
1324
  return Napi::Boolean::New(env, success);
1502
1325
 
1503
1326
  } @catch (NSException *exception) {
1504
- NSLog(@"❌ Screen selection error: %@", exception);
1505
1327
  return Napi::Boolean::New(env, false);
1506
1328
  }
1507
1329
  }
@@ -1593,7 +1415,7 @@ Napi::Value ShowScreenRecordingPreview(const Napi::CallbackInfo& info) {
1593
1415
  }
1594
1416
 
1595
1417
  bool success = showScreenRecordingPreview(screenInfo);
1596
- screenInfo = nil;
1418
+ [screenInfo release];
1597
1419
 
1598
1420
  return Napi::Boolean::New(env, success);
1599
1421