node-mac-recorder 2.4.11 → 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
 
@@ -497,56 +419,22 @@ NSArray* getAllSelectableWindowsLegacy() {
497
419
  }
498
420
  }
499
421
 
500
- // Get window under cursor point using real-time mouse position
422
+ // Get window under cursor point
501
423
  NSDictionary* getWindowUnderCursor(CGPoint point) {
502
424
  @autoreleasepool {
503
- // Get window ID directly under cursor using macOS API
504
- CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
505
-
506
- if (windowList) {
507
- NSArray *windowArray = (__bridge NSArray *)windowList;
425
+ if (!g_allWindows) return nil;
426
+
427
+ // Find window that contains the cursor point
428
+ for (NSDictionary *window in g_allWindows) {
429
+ int x = [[window objectForKey:@"x"] intValue];
430
+ int y = [[window objectForKey:@"y"] intValue];
431
+ int width = [[window objectForKey:@"width"] intValue];
432
+ int height = [[window objectForKey:@"height"] intValue];
508
433
 
509
- // Find the topmost window at cursor position
510
- for (NSDictionary *windowInfo in windowArray) {
511
- NSDictionary *bounds = [windowInfo objectForKey:(NSString *)kCGWindowBounds];
512
- NSNumber *layer = [windowInfo objectForKey:(NSString *)kCGWindowLayer];
513
- NSString *owner = [windowInfo objectForKey:(NSString *)kCGWindowOwnerName];
514
- NSNumber *winID = [windowInfo objectForKey:(NSString *)kCGWindowNumber];
515
-
516
- if (!bounds || !winID || !owner) continue;
517
- if ([layer intValue] != 0) continue; // Only normal windows
518
- if ([owner isEqualToString:@"WindowServer"] || [owner isEqualToString:@"Dock"]) continue;
519
-
520
- int x = [[bounds objectForKey:@"X"] intValue];
521
- int y = [[bounds objectForKey:@"Y"] intValue];
522
- int width = [[bounds objectForKey:@"Width"] intValue];
523
- int height = [[bounds objectForKey:@"Height"] intValue];
524
-
525
- // Skip too small windows
526
- if (width < 50 || height < 50) continue;
527
-
528
- // Check if cursor is within window bounds
529
- if (point.x >= x && point.x <= x + width &&
530
- point.y >= y && point.y <= y + height) {
531
-
532
- NSString *windowName = [windowInfo objectForKey:(NSString *)kCGWindowName];
533
-
534
- // Create window info in our format
535
- NSDictionary *window = @{
536
- @"id": winID,
537
- @"title": windowName ?: @"Untitled",
538
- @"appName": owner,
539
- @"x": @(x),
540
- @"y": @(y),
541
- @"width": @(width),
542
- @"height": @(height)
543
- };
544
-
545
- CFRelease(windowList);
546
- return window;
547
- }
434
+ if (point.x >= x && point.x <= x + width &&
435
+ point.y >= y && point.y <= y + height) {
436
+ return window;
548
437
  }
549
- CFRelease(windowList);
550
438
  }
551
439
 
552
440
  return nil;
@@ -555,11 +443,6 @@ NSDictionary* getWindowUnderCursor(CGPoint point) {
555
443
 
556
444
  // Update overlay to highlight window under cursor
557
445
  void updateOverlay() {
558
- // Ensure AppKit usage on main thread
559
- if (![NSThread isMainThread]) {
560
- dispatch_async(dispatch_get_main_queue(), ^{ updateOverlay(); });
561
- return;
562
- }
563
446
  @autoreleasepool {
564
447
  if (!g_isWindowSelecting || !g_overlayWindow) return;
565
448
 
@@ -575,7 +458,8 @@ void updateOverlay() {
575
458
 
576
459
  if (windowUnderCursor && ![windowUnderCursor isEqualToDictionary:g_currentWindowUnderCursor]) {
577
460
  // Update current window
578
- g_currentWindowUnderCursor = windowUnderCursor;
461
+ [g_currentWindowUnderCursor release];
462
+ g_currentWindowUnderCursor = [windowUnderCursor retain];
579
463
 
580
464
  // Update overlay position and size
581
465
  int x = [[windowUnderCursor objectForKey:@"x"] intValue];
@@ -666,6 +550,7 @@ void updateOverlay() {
666
550
  NSLog(@"🚪 WINDOW LEFT: %@ - \"%@\"", leftAppName, leftWindowTitle);
667
551
 
668
552
  [g_overlayWindow orderOut:nil];
553
+ [g_currentWindowUnderCursor release];
669
554
  g_currentWindowUnderCursor = nil;
670
555
  }
671
556
  }
@@ -673,12 +558,6 @@ void updateOverlay() {
673
558
 
674
559
  // Cleanup function
675
560
  void cleanupWindowSelector() {
676
- if (![NSThread isMainThread]) {
677
- dispatch_async(dispatch_get_main_queue(), ^{ cleanupWindowSelector(); });
678
- return;
679
- }
680
-
681
- NSLog(@"🧹 Cleaning up window selector resources");
682
561
  g_isWindowSelecting = false;
683
562
 
684
563
  // Stop tracking timer
@@ -703,25 +582,24 @@ void cleanupWindowSelector() {
703
582
 
704
583
  // Clean up delegate
705
584
  if (g_delegate) {
585
+ [g_delegate release];
706
586
  g_delegate = nil;
707
587
  }
708
588
 
709
589
  // Clean up data
710
590
  if (g_allWindows) {
591
+ [g_allWindows release];
711
592
  g_allWindows = nil;
712
593
  }
713
594
 
714
595
  if (g_currentWindowUnderCursor) {
596
+ [g_currentWindowUnderCursor release];
715
597
  g_currentWindowUnderCursor = nil;
716
598
  }
717
599
  }
718
600
 
719
601
  // Recording preview functions
720
602
  void cleanupRecordingPreview() {
721
- if (![NSThread isMainThread]) {
722
- dispatch_async(dispatch_get_main_queue(), ^{ cleanupRecordingPreview(); });
723
- return;
724
- }
725
603
  if (g_recordingPreviewWindow) {
726
604
  [g_recordingPreviewWindow close];
727
605
  g_recordingPreviewWindow = nil;
@@ -729,6 +607,7 @@ void cleanupRecordingPreview() {
729
607
  }
730
608
 
731
609
  if (g_recordingWindowInfo) {
610
+ [g_recordingWindowInfo release];
732
611
  g_recordingWindowInfo = nil;
733
612
  }
734
613
  }
@@ -741,7 +620,7 @@ bool showRecordingPreview(NSDictionary *windowInfo) {
741
620
  if (!windowInfo) return false;
742
621
 
743
622
  // Store window info
744
- g_recordingWindowInfo = windowInfo;
623
+ g_recordingWindowInfo = [windowInfo retain];
745
624
 
746
625
  // Get main screen bounds for full screen overlay
747
626
  NSScreen *mainScreen = [NSScreen mainScreen];
@@ -801,10 +680,6 @@ bool hideRecordingPreview() {
801
680
 
802
681
  // Screen selection functions
803
682
  void cleanupScreenSelector() {
804
- if (![NSThread isMainThread]) {
805
- dispatch_async(dispatch_get_main_queue(), ^{ cleanupScreenSelector(); });
806
- return;
807
- }
808
683
  g_isScreenSelecting = false;
809
684
 
810
685
  // Remove key event monitor
@@ -818,11 +693,13 @@ void cleanupScreenSelector() {
818
693
  for (NSWindow *overlayWindow in g_screenOverlayWindows) {
819
694
  [overlayWindow close];
820
695
  }
696
+ [g_screenOverlayWindows release];
821
697
  g_screenOverlayWindows = nil;
822
698
  }
823
699
 
824
700
  // Clean up screen data
825
701
  if (g_allScreens) {
702
+ [g_allScreens release];
826
703
  g_allScreens = nil;
827
704
  }
828
705
  }
@@ -830,13 +707,7 @@ void cleanupScreenSelector() {
830
707
  bool startScreenSelection() {
831
708
  @try {
832
709
  if (g_isScreenSelecting) return false;
833
- // Force to main thread
834
- if (![NSThread isMainThread]) {
835
- __block BOOL ok = NO;
836
- dispatch_sync(dispatch_get_main_queue(), ^{ ok = startScreenSelection(); });
837
- return ok;
838
- }
839
-
710
+
840
711
  // Get all available screens
841
712
  NSArray *screens = [NSScreen screens];
842
713
  if (!screens || [screens count] == 0) return false;
@@ -845,7 +716,7 @@ bool startScreenSelection() {
845
716
  NSMutableArray *screenInfoArray = [[NSMutableArray alloc] init];
846
717
  g_screenOverlayWindows = [[NSMutableArray alloc] init];
847
718
 
848
- for (NSUInteger i = 0; i < [screens count]; i++) {
719
+ for (NSInteger i = 0; i < [screens count]; i++) {
849
720
  NSScreen *screen = [screens objectAtIndex:i];
850
721
  NSRect screenFrame = [screen frame];
851
722
 
@@ -968,11 +839,11 @@ bool startScreenSelection() {
968
839
  [overlayWindow makeKeyAndOrderFront:nil];
969
840
 
970
841
  [g_screenOverlayWindows addObject:overlayWindow];
971
- screenInfo = nil;
842
+ [screenInfo release];
972
843
  }
973
844
 
974
- g_allScreens = screenInfoArray;
975
- screenInfoArray = nil;
845
+ g_allScreens = [screenInfoArray retain];
846
+ [screenInfoArray release];
976
847
  g_isScreenSelecting = true;
977
848
 
978
849
  // Add ESC key event monitor to cancel selection
@@ -998,12 +869,9 @@ bool startScreenSelection() {
998
869
  bool stopScreenSelection() {
999
870
  @try {
1000
871
  if (!g_isScreenSelecting) return false;
1001
- if (![NSThread isMainThread]) {
1002
- __block BOOL ok = NO;
1003
- dispatch_sync(dispatch_get_main_queue(), ^{ ok = stopScreenSelection(); });
1004
- return ok;
1005
- }
872
+
1006
873
  cleanupScreenSelector();
874
+ NSLog(@"🖥️ SCREEN SELECTION: Stopped");
1007
875
  return true;
1008
876
 
1009
877
  } @catch (NSException *exception) {
@@ -1015,10 +883,11 @@ bool stopScreenSelection() {
1015
883
  NSDictionary* getSelectedScreenInfo() {
1016
884
  if (!g_selectedScreenInfo) return nil;
1017
885
 
1018
- NSDictionary *result = g_selectedScreenInfo;
886
+ NSDictionary *result = [g_selectedScreenInfo retain];
887
+ [g_selectedScreenInfo release];
1019
888
  g_selectedScreenInfo = nil;
1020
889
 
1021
- return result;
890
+ return [result autorelease];
1022
891
  }
1023
892
 
1024
893
  bool showScreenRecordingPreview(NSDictionary *screenInfo) {
@@ -1033,10 +902,10 @@ bool showScreenRecordingPreview(NSDictionary *screenInfo) {
1033
902
  NSArray *screens = [NSScreen screens];
1034
903
  if (!screens || [screens count] == 0) return false;
1035
904
 
1036
- NSUInteger selectedScreenId = (NSUInteger)[[screenInfo objectForKey:@"id"] intValue];
905
+ int selectedScreenId = [[screenInfo objectForKey:@"id"] intValue];
1037
906
 
1038
907
  // Create overlay for each screen except the selected one
1039
- for (NSUInteger i = 0; i < [screens count]; i++) {
908
+ for (NSInteger i = 0; i < [screens count]; i++) {
1040
909
  if (i == selectedScreenId) continue; // Skip selected screen
1041
910
 
1042
911
  NSScreen *screen = [screens objectAtIndex:i];
@@ -1067,7 +936,7 @@ bool showScreenRecordingPreview(NSDictionary *screenInfo) {
1067
936
  }
1068
937
  }
1069
938
 
1070
- NSLog(@"🎬 SCREEN RECORDING PREVIEW: Showing overlay for Screen %lu", (unsigned long)selectedScreenId);
939
+ NSLog(@"🎬 SCREEN RECORDING PREVIEW: Showing overlay for Screen %d", selectedScreenId);
1071
940
 
1072
941
  return true;
1073
942
 
@@ -1085,50 +954,14 @@ bool hideScreenRecordingPreview() {
1085
954
  Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1086
955
  Napi::Env env = info.Env();
1087
956
 
1088
- // Electron safety check - prevent NSWindow crashes
1089
- const char* electronVersion = getenv("ELECTRON_VERSION");
1090
- const char* electronRunAs = getenv("ELECTRON_RUN_AS_NODE");
1091
-
1092
- NSLog(@"🔍 Debug: electronVersion='%s', electronRunAs='%s'",
1093
- electronVersion ? electronVersion : "null",
1094
- electronRunAs ? electronRunAs : "null");
1095
-
1096
- if (electronVersion || electronRunAs) {
1097
- NSLog(@"🔍 Detected Electron environment - using safe mode");
1098
-
1099
- // In Electron, return window list without creating native NSWindow overlays
1100
- // The Electron app can handle UI selection itself
1101
- @try {
1102
- NSArray *windows = getAllSelectableWindows();
1103
-
1104
- if (!windows || [windows count] == 0) {
1105
- NSLog(@"❌ No selectable windows found");
1106
- return Napi::Boolean::New(env, false);
1107
- }
1108
-
1109
- // Store windows for later retrieval via getWindowSelectionStatus
1110
- g_allWindows = [getAllSelectableWindows() mutableCopy];
1111
- g_isWindowSelecting = true;
1112
-
1113
- // Return true to indicate windows are available
1114
- // Electron app should call getWindowSelectionStatus to get the list
1115
- NSLog(@"✅ Electron-safe mode: %lu windows available for selection", (unsigned long)[windows count]);
1116
- return Napi::Boolean::New(env, true);
1117
-
1118
- } @catch (NSException *exception) {
1119
- NSLog(@"❌ Exception in Electron-safe window selection: %@", [exception reason]);
1120
- return Napi::Boolean::New(env, false);
1121
- }
1122
- }
1123
-
1124
957
  if (g_isWindowSelecting) {
1125
- NSLog(@"⚠️ Window selection already in progress");
1126
- return Napi::Boolean::New(env, false);
958
+ Napi::TypeError::New(env, "Window selection already in progress").ThrowAsJavaScriptException();
959
+ return env.Null();
1127
960
  }
1128
961
 
1129
962
  @try {
1130
963
  // Get all windows
1131
- g_allWindows = [getAllSelectableWindows() mutableCopy];
964
+ g_allWindows = [getAllSelectableWindows() retain];
1132
965
 
1133
966
  if (!g_allWindows || [g_allWindows count] == 0) {
1134
967
  Napi::Error::New(env, "No selectable windows found").ThrowAsJavaScriptException();
@@ -1308,54 +1141,26 @@ Napi::Value GetSelectedWindowInfo(const Napi::CallbackInfo& info) {
1308
1141
  NSLog(@" 📊 Details: ID=%@, Pos=(%d,%d), Size=%dx%d",
1309
1142
  [g_selectedWindowInfo objectForKey:@"id"], x, y, width, height);
1310
1143
 
1311
- // Get all screens and find which screen contains this window
1144
+ // Get all screens
1312
1145
  NSArray *screens = [NSScreen screens];
1313
1146
  NSScreen *windowScreen = nil;
1314
1147
  NSScreen *mainScreen = [NSScreen mainScreen];
1315
1148
 
1316
- // Calculate window center point for better screen detection
1317
- CGPoint windowCenter = CGPointMake(x + width/2, y + height/2);
1318
-
1319
1149
  for (NSScreen *screen in screens) {
1320
1150
  NSRect screenFrame = [screen frame];
1321
1151
 
1322
- // Check if window center is within screen bounds
1323
- if (windowCenter.x >= screenFrame.origin.x &&
1324
- windowCenter.x < screenFrame.origin.x + screenFrame.size.width &&
1325
- windowCenter.y >= screenFrame.origin.y &&
1326
- windowCenter.y < screenFrame.origin.y + screenFrame.size.height) {
1152
+ // Convert window coordinates to screen-relative
1153
+ if (x >= screenFrame.origin.x &&
1154
+ x < screenFrame.origin.x + screenFrame.size.width &&
1155
+ y >= screenFrame.origin.y &&
1156
+ y < screenFrame.origin.y + screenFrame.size.height) {
1327
1157
  windowScreen = screen;
1328
- NSLog(@" 🖥️ Window found on screen: (%.0f,%.0f) %.0fx%.0f",
1329
- screenFrame.origin.x, screenFrame.origin.y,
1330
- screenFrame.size.width, screenFrame.size.height);
1331
1158
  break;
1332
1159
  }
1333
1160
  }
1334
1161
 
1335
- // If no exact match, find screen with maximum overlap
1336
- if (!windowScreen) {
1337
- CGFloat maxOverlapArea = 0;
1338
- NSRect windowRect = NSMakeRect(x, y, width, height);
1339
-
1340
- for (NSScreen *screen in screens) {
1341
- NSRect screenFrame = [screen frame];
1342
- NSRect intersection = NSIntersectionRect(windowRect, screenFrame);
1343
- CGFloat overlapArea = intersection.size.width * intersection.size.height;
1344
-
1345
- if (overlapArea > maxOverlapArea) {
1346
- maxOverlapArea = overlapArea;
1347
- windowScreen = screen;
1348
- }
1349
- }
1350
-
1351
- if (windowScreen) {
1352
- NSLog(@" 🖥️ Window assigned to screen with max overlap: %.0f pixels²", maxOverlapArea);
1353
- }
1354
- }
1355
-
1356
1162
  if (!windowScreen) {
1357
1163
  windowScreen = mainScreen;
1358
- NSLog(@" 🖥️ Window defaulted to main screen");
1359
1164
  }
1360
1165
 
1361
1166
  // Add screen information
@@ -1368,6 +1173,7 @@ Napi::Value GetSelectedWindowInfo(const Napi::CallbackInfo& info) {
1368
1173
  result.Set("screenHeight", Napi::Number::New(env, (int)screenFrame.size.height));
1369
1174
 
1370
1175
  // Clear selected window info after reading
1176
+ [g_selectedWindowInfo release];
1371
1177
  g_selectedWindowInfo = nil;
1372
1178
 
1373
1179
  return result;
@@ -1423,74 +1229,13 @@ Napi::Value GetWindowSelectionStatus(const Napi::CallbackInfo& info) {
1423
1229
  updateOverlay();
1424
1230
  }
1425
1231
 
1426
- // For Electron mode, also get real-time window under cursor
1427
- const char* electronVersion = getenv("ELECTRON_VERSION");
1428
- const char* electronRunAs = getenv("ELECTRON_RUN_AS_NODE");
1429
-
1430
1232
  Napi::Object result = Napi::Object::New(env);
1431
1233
  result.Set("isSelecting", Napi::Boolean::New(env, g_isWindowSelecting));
1432
1234
  result.Set("hasSelectedWindow", Napi::Boolean::New(env, g_selectedWindowInfo != nil));
1433
1235
  result.Set("windowCount", Napi::Number::New(env, g_allWindows ? [g_allWindows count] : 0));
1434
1236
  result.Set("hasOverlay", Napi::Boolean::New(env, g_overlayWindow != nil));
1435
1237
 
1436
- if (electronVersion || electronRunAs) {
1437
- // In Electron mode, get real-time window under cursor
1438
- @try {
1439
- NSPoint mouseLocation = [NSEvent mouseLocation];
1440
- NSScreen *mainScreen = [NSScreen mainScreen];
1441
- CGFloat screenHeight = [mainScreen frame].size.height;
1442
- CGPoint globalPoint = CGPointMake(mouseLocation.x, screenHeight - mouseLocation.y);
1443
-
1444
- NSDictionary *windowUnderCursor = getWindowUnderCursor(globalPoint);
1445
-
1446
- if (windowUnderCursor) {
1447
- Napi::Object currentWindow = Napi::Object::New(env);
1448
- currentWindow.Set("id", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"id"] intValue]));
1449
- currentWindow.Set("title", Napi::String::New(env, [[windowUnderCursor objectForKey:@"title"] UTF8String]));
1450
- currentWindow.Set("appName", Napi::String::New(env, [[windowUnderCursor objectForKey:@"appName"] UTF8String]));
1451
- currentWindow.Set("x", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"x"] intValue]));
1452
- currentWindow.Set("y", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"y"] intValue]));
1453
- currentWindow.Set("width", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"width"] intValue]));
1454
- currentWindow.Set("height", Napi::Number::New(env, [[windowUnderCursor objectForKey:@"height"] intValue]));
1455
-
1456
- // Add screen detection for Electron
1457
- int x = [[windowUnderCursor objectForKey:@"x"] intValue];
1458
- int y = [[windowUnderCursor objectForKey:@"y"] intValue];
1459
- int width = [[windowUnderCursor objectForKey:@"width"] intValue];
1460
- int height = [[windowUnderCursor objectForKey:@"height"] intValue];
1461
-
1462
- NSArray *screens = [NSScreen screens];
1463
- NSScreen *windowScreen = nil;
1464
- CGPoint windowCenter = CGPointMake(x + width/2, y + height/2);
1465
-
1466
- for (NSScreen *screen in screens) {
1467
- NSRect screenFrame = [screen frame];
1468
- if (windowCenter.x >= screenFrame.origin.x &&
1469
- windowCenter.x < screenFrame.origin.x + screenFrame.size.width &&
1470
- windowCenter.y >= screenFrame.origin.y &&
1471
- windowCenter.y < screenFrame.origin.y + screenFrame.size.height) {
1472
- windowScreen = screen;
1473
- break;
1474
- }
1475
- }
1476
-
1477
- if (windowScreen) {
1478
- NSRect screenFrame = [windowScreen frame];
1479
- currentWindow.Set("screenId", Napi::Number::New(env, [[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] ?
1480
- [[[windowScreen deviceDescription] objectForKey:@"NSScreenNumber"] intValue] : 0));
1481
- currentWindow.Set("screenX", Napi::Number::New(env, (int)screenFrame.origin.x));
1482
- currentWindow.Set("screenY", Napi::Number::New(env, (int)screenFrame.origin.y));
1483
- currentWindow.Set("screenWidth", Napi::Number::New(env, (int)screenFrame.size.width));
1484
- currentWindow.Set("screenHeight", Napi::Number::New(env, (int)screenFrame.size.height));
1485
- }
1486
-
1487
- result.Set("currentWindow", currentWindow);
1488
- }
1489
- } @catch (NSException *exception) {
1490
- // Ignore mouse tracking errors in Electron mode
1491
- }
1492
- } else if (g_currentWindowUnderCursor) {
1493
- // Native mode
1238
+ if (g_currentWindowUnderCursor) {
1494
1239
  Napi::Object currentWindow = Napi::Object::New(env);
1495
1240
  currentWindow.Set("id", Napi::Number::New(env, [[g_currentWindowUnderCursor objectForKey:@"id"] intValue]));
1496
1241
  currentWindow.Set("title", Napi::String::New(env, [[g_currentWindowUnderCursor objectForKey:@"title"] UTF8String]));
@@ -1510,13 +1255,13 @@ Napi::Value ShowRecordingPreview(const Napi::CallbackInfo& info) {
1510
1255
  Napi::Env env = info.Env();
1511
1256
 
1512
1257
  if (info.Length() < 1) {
1513
- NSLog(@"⚠️ Window info object required");
1514
- return Napi::Boolean::New(env, false);
1258
+ Napi::TypeError::New(env, "Window info object required").ThrowAsJavaScriptException();
1259
+ return env.Null();
1515
1260
  }
1516
1261
 
1517
1262
  if (!info[0].IsObject()) {
1518
- NSLog(@"⚠️ Window info must be an object");
1519
- return Napi::Boolean::New(env, false);
1263
+ Napi::TypeError::New(env, "Window info must be an object").ThrowAsJavaScriptException();
1264
+ return env.Null();
1520
1265
  }
1521
1266
 
1522
1267
  @try {
@@ -1548,7 +1293,7 @@ Napi::Value ShowRecordingPreview(const Napi::CallbackInfo& info) {
1548
1293
  }
1549
1294
 
1550
1295
  bool success = showRecordingPreview(windowInfo);
1551
- windowInfo = nil;
1296
+ [windowInfo release];
1552
1297
 
1553
1298
  return Napi::Boolean::New(env, success);
1554
1299
 
@@ -1574,57 +1319,11 @@ Napi::Value HideRecordingPreview(const Napi::CallbackInfo& info) {
1574
1319
  Napi::Value StartScreenSelection(const Napi::CallbackInfo& info) {
1575
1320
  Napi::Env env = info.Env();
1576
1321
 
1577
- // Electron safety check - prevent NSWindow crashes
1578
- const char* electronVersion = getenv("ELECTRON_VERSION");
1579
- const char* electronRunAs = getenv("ELECTRON_RUN_AS_NODE");
1580
-
1581
- NSLog(@"🔍 Screen Debug: electronVersion='%s', electronRunAs='%s'",
1582
- electronVersion ? electronVersion : "null",
1583
- electronRunAs ? electronRunAs : "null");
1584
-
1585
- if (electronVersion || electronRunAs) {
1586
- NSLog(@"🔍 Detected Electron environment - using safe screen selection");
1587
-
1588
- // In Electron, return screen list without creating native NSWindow overlays
1589
- @try {
1590
- NSArray *screens = [NSScreen screens];
1591
-
1592
- if (!screens || [screens count] == 0) {
1593
- NSLog(@"❌ No screens available");
1594
- return Napi::Boolean::New(env, false);
1595
- }
1596
-
1597
- // Store screens and select first one automatically for Electron
1598
- g_allScreens = screens;
1599
- g_isScreenSelecting = true;
1600
-
1601
- NSScreen *mainScreen = [screens firstObject];
1602
- g_selectedScreenInfo = @{
1603
- @"id": @((int)[screens indexOfObject:mainScreen]),
1604
- @"width": @((int)mainScreen.frame.size.width),
1605
- @"height": @((int)mainScreen.frame.size.height),
1606
- @"x": @((int)mainScreen.frame.origin.x),
1607
- @"y": @((int)mainScreen.frame.origin.y)
1608
- };
1609
-
1610
- // Mark as complete so getSelectedScreenInfo returns the selection
1611
- g_isScreenSelecting = false;
1612
-
1613
- NSLog(@"✅ Electron-safe screen selection: %lu screens available", (unsigned long)[screens count]);
1614
- return Napi::Boolean::New(env, true);
1615
-
1616
- } @catch (NSException *exception) {
1617
- NSLog(@"❌ Exception in Electron-safe screen selection: %@", [exception reason]);
1618
- return Napi::Boolean::New(env, false);
1619
- }
1620
- }
1621
-
1622
1322
  @try {
1623
1323
  bool success = startScreenSelection();
1624
1324
  return Napi::Boolean::New(env, success);
1625
1325
 
1626
1326
  } @catch (NSException *exception) {
1627
- NSLog(@"❌ Screen selection error: %@", exception);
1628
1327
  return Napi::Boolean::New(env, false);
1629
1328
  }
1630
1329
  }
@@ -1716,7 +1415,7 @@ Napi::Value ShowScreenRecordingPreview(const Napi::CallbackInfo& info) {
1716
1415
  }
1717
1416
 
1718
1417
  bool success = showScreenRecordingPreview(screenInfo);
1719
- screenInfo = nil;
1418
+ [screenInfo release];
1720
1419
 
1721
1420
  return Napi::Boolean::New(env, success);
1722
1421