node-mac-recorder 2.4.11 → 2.4.13

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];
@@ -583,19 +467,36 @@ void updateOverlay() {
583
467
  int width = [[windowUnderCursor objectForKey:@"width"] intValue];
584
468
  int height = [[windowUnderCursor objectForKey:@"height"] intValue];
585
469
 
586
- // Convert coordinates from CGWindow (top-left) to NSWindow (bottom-left)
587
- NSScreen *mainScreen = [NSScreen mainScreen];
588
- CGFloat screenHeight = [mainScreen frame].size.height;
589
- CGFloat adjustedY = screenHeight - y - height;
470
+ // Find which screen contains the window center
471
+ NSArray *screens = [NSScreen screens];
472
+ NSScreen *windowScreen = nil;
473
+ CGFloat windowCenterX = x + width / 2;
474
+ CGFloat windowCenterY = y + height / 2;
590
475
 
591
- // Clamp overlay to screen bounds to avoid partial off-screen issues
592
- NSRect screenFrame = [mainScreen frame];
593
- CGFloat clampedX = MAX(screenFrame.origin.x, MIN(x, screenFrame.origin.x + screenFrame.size.width - width));
594
- CGFloat clampedY = MAX(screenFrame.origin.y, MIN(adjustedY, screenFrame.origin.y + screenFrame.size.height - height));
595
- CGFloat clampedWidth = MIN(width, screenFrame.size.width - (clampedX - screenFrame.origin.x));
596
- CGFloat clampedHeight = MIN(height, screenFrame.size.height - (clampedY - screenFrame.origin.y));
476
+ for (NSScreen *screen in screens) {
477
+ NSRect screenFrame = [screen frame];
478
+ // Convert screen frame to CGWindow coordinates
479
+ CGFloat screenTop = screenFrame.origin.y + screenFrame.size.height;
480
+ CGFloat screenBottom = screenFrame.origin.y;
481
+ CGFloat screenLeft = screenFrame.origin.x;
482
+ CGFloat screenRight = screenFrame.origin.x + screenFrame.size.width;
483
+
484
+ if (windowCenterX >= screenLeft && windowCenterX <= screenRight &&
485
+ windowCenterY >= screenBottom && windowCenterY <= screenTop) {
486
+ windowScreen = screen;
487
+ break;
488
+ }
489
+ }
597
490
 
598
- NSRect overlayFrame = NSMakeRect(clampedX, clampedY, clampedWidth, clampedHeight);
491
+ // Use main screen if no specific screen found
492
+ if (!windowScreen) windowScreen = [NSScreen mainScreen];
493
+
494
+ // Convert coordinates from CGWindow (top-left) to NSWindow (bottom-left) for the specific screen
495
+ CGFloat screenHeight = [windowScreen frame].size.height;
496
+ CGFloat adjustedY = screenHeight - y - height;
497
+
498
+ // Use actual window coordinates without clamping to preserve overlay accuracy
499
+ NSRect overlayFrame = NSMakeRect(x, adjustedY, width, height);
599
500
 
600
501
  NSString *windowTitle = [windowUnderCursor objectForKey:@"title"] ?: @"Untitled";
601
502
  NSString *appName = [windowUnderCursor objectForKey:@"appName"] ?: @"Unknown";
@@ -617,6 +518,8 @@ void updateOverlay() {
617
518
  }
618
519
  }
619
520
  }
521
+
522
+ // Ensure overlay is on the correct screen
620
523
  [g_overlayWindow setFrame:overlayFrame display:YES];
621
524
 
622
525
  // Update overlay view window info
@@ -666,6 +569,7 @@ void updateOverlay() {
666
569
  NSLog(@"🚪 WINDOW LEFT: %@ - \"%@\"", leftAppName, leftWindowTitle);
667
570
 
668
571
  [g_overlayWindow orderOut:nil];
572
+ [g_currentWindowUnderCursor release];
669
573
  g_currentWindowUnderCursor = nil;
670
574
  }
671
575
  }
@@ -673,12 +577,6 @@ void updateOverlay() {
673
577
 
674
578
  // Cleanup function
675
579
  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
580
  g_isWindowSelecting = false;
683
581
 
684
582
  // Stop tracking timer
@@ -703,25 +601,24 @@ void cleanupWindowSelector() {
703
601
 
704
602
  // Clean up delegate
705
603
  if (g_delegate) {
604
+ [g_delegate release];
706
605
  g_delegate = nil;
707
606
  }
708
607
 
709
608
  // Clean up data
710
609
  if (g_allWindows) {
610
+ [g_allWindows release];
711
611
  g_allWindows = nil;
712
612
  }
713
613
 
714
614
  if (g_currentWindowUnderCursor) {
615
+ [g_currentWindowUnderCursor release];
715
616
  g_currentWindowUnderCursor = nil;
716
617
  }
717
618
  }
718
619
 
719
620
  // Recording preview functions
720
621
  void cleanupRecordingPreview() {
721
- if (![NSThread isMainThread]) {
722
- dispatch_async(dispatch_get_main_queue(), ^{ cleanupRecordingPreview(); });
723
- return;
724
- }
725
622
  if (g_recordingPreviewWindow) {
726
623
  [g_recordingPreviewWindow close];
727
624
  g_recordingPreviewWindow = nil;
@@ -729,6 +626,7 @@ void cleanupRecordingPreview() {
729
626
  }
730
627
 
731
628
  if (g_recordingWindowInfo) {
629
+ [g_recordingWindowInfo release];
732
630
  g_recordingWindowInfo = nil;
733
631
  }
734
632
  }
@@ -741,7 +639,7 @@ bool showRecordingPreview(NSDictionary *windowInfo) {
741
639
  if (!windowInfo) return false;
742
640
 
743
641
  // Store window info
744
- g_recordingWindowInfo = windowInfo;
642
+ g_recordingWindowInfo = [windowInfo retain];
745
643
 
746
644
  // Get main screen bounds for full screen overlay
747
645
  NSScreen *mainScreen = [NSScreen mainScreen];
@@ -801,10 +699,6 @@ bool hideRecordingPreview() {
801
699
 
802
700
  // Screen selection functions
803
701
  void cleanupScreenSelector() {
804
- if (![NSThread isMainThread]) {
805
- dispatch_async(dispatch_get_main_queue(), ^{ cleanupScreenSelector(); });
806
- return;
807
- }
808
702
  g_isScreenSelecting = false;
809
703
 
810
704
  // Remove key event monitor
@@ -818,11 +712,13 @@ void cleanupScreenSelector() {
818
712
  for (NSWindow *overlayWindow in g_screenOverlayWindows) {
819
713
  [overlayWindow close];
820
714
  }
715
+ [g_screenOverlayWindows release];
821
716
  g_screenOverlayWindows = nil;
822
717
  }
823
718
 
824
719
  // Clean up screen data
825
720
  if (g_allScreens) {
721
+ [g_allScreens release];
826
722
  g_allScreens = nil;
827
723
  }
828
724
  }
@@ -830,13 +726,7 @@ void cleanupScreenSelector() {
830
726
  bool startScreenSelection() {
831
727
  @try {
832
728
  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
-
729
+
840
730
  // Get all available screens
841
731
  NSArray *screens = [NSScreen screens];
842
732
  if (!screens || [screens count] == 0) return false;
@@ -845,7 +735,7 @@ bool startScreenSelection() {
845
735
  NSMutableArray *screenInfoArray = [[NSMutableArray alloc] init];
846
736
  g_screenOverlayWindows = [[NSMutableArray alloc] init];
847
737
 
848
- for (NSUInteger i = 0; i < [screens count]; i++) {
738
+ for (NSInteger i = 0; i < [screens count]; i++) {
849
739
  NSScreen *screen = [screens objectAtIndex:i];
850
740
  NSRect screenFrame = [screen frame];
851
741
 
@@ -886,16 +776,15 @@ bool startScreenSelection() {
886
776
  NSButton *selectButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 180, 60)];
887
777
  [selectButton setTitle:@"Start Record"];
888
778
  [selectButton setButtonType:NSButtonTypeMomentaryPushIn];
889
- [selectButton setBezelStyle:NSBezelStyleRounded];
779
+ [selectButton setBezelStyle:NSBezelStyleRegularSquare];
890
780
  [selectButton setFont:[NSFont systemFontOfSize:16 weight:NSFontWeightSemibold]];
891
781
  [selectButton setTag:i]; // Set screen index as tag
892
782
 
893
783
  // Blue background with white text
894
784
  [selectButton setWantsLayer:YES];
895
- [selectButton.layer setBackgroundColor:[[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] CGColor]];
896
- [selectButton.layer setCornerRadius:8.0];
897
- [selectButton.layer setBorderColor:[[NSColor colorWithRed:0.0 green:0.3 blue:0.7 alpha:1.0] CGColor]];
898
- [selectButton.layer setBorderWidth:2.0];
785
+ [selectButton.layer setBackgroundColor:[[NSColor colorWithRed:0.2 green:0.6 blue:1.0 alpha:0.95] CGColor]];
786
+ [selectButton.layer setCornerRadius:12.0];
787
+ [selectButton.layer setBorderWidth:0.0];
899
788
 
900
789
  // White text color
901
790
  NSMutableAttributedString *titleString = [[NSMutableAttributedString alloc]
@@ -922,15 +811,14 @@ bool startScreenSelection() {
922
811
  NSButton *screenCancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 120, 40)];
923
812
  [screenCancelButton setTitle:@"Cancel"];
924
813
  [screenCancelButton setButtonType:NSButtonTypeMomentaryPushIn];
925
- [screenCancelButton setBezelStyle:NSBezelStyleRounded];
814
+ [screenCancelButton setBezelStyle:NSBezelStyleRegularSquare];
926
815
  [screenCancelButton setFont:[NSFont systemFontOfSize:14 weight:NSFontWeightMedium]];
927
816
 
928
817
  // Gray cancel button styling
929
818
  [screenCancelButton setWantsLayer:YES];
930
- [screenCancelButton.layer setBackgroundColor:[[NSColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:0.8] CGColor]];
931
- [screenCancelButton.layer setCornerRadius:6.0];
932
- [screenCancelButton.layer setBorderColor:[[NSColor colorWithRed:0.4 green:0.4 blue:0.4 alpha:1.0] CGColor]];
933
- [screenCancelButton.layer setBorderWidth:1.0];
819
+ [screenCancelButton.layer setBackgroundColor:[[NSColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:0.9] CGColor]];
820
+ [screenCancelButton.layer setCornerRadius:10.0];
821
+ [screenCancelButton.layer setBorderWidth:0.0];
934
822
 
935
823
  // White text for cancel button
936
824
  NSMutableAttributedString *screenCancelTitleString = [[NSMutableAttributedString alloc]
@@ -968,11 +856,11 @@ bool startScreenSelection() {
968
856
  [overlayWindow makeKeyAndOrderFront:nil];
969
857
 
970
858
  [g_screenOverlayWindows addObject:overlayWindow];
971
- screenInfo = nil;
859
+ [screenInfo release];
972
860
  }
973
861
 
974
- g_allScreens = screenInfoArray;
975
- screenInfoArray = nil;
862
+ g_allScreens = [screenInfoArray retain];
863
+ [screenInfoArray release];
976
864
  g_isScreenSelecting = true;
977
865
 
978
866
  // Add ESC key event monitor to cancel selection
@@ -998,12 +886,9 @@ bool startScreenSelection() {
998
886
  bool stopScreenSelection() {
999
887
  @try {
1000
888
  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
- }
889
+
1006
890
  cleanupScreenSelector();
891
+ NSLog(@"🖥️ SCREEN SELECTION: Stopped");
1007
892
  return true;
1008
893
 
1009
894
  } @catch (NSException *exception) {
@@ -1015,10 +900,11 @@ bool stopScreenSelection() {
1015
900
  NSDictionary* getSelectedScreenInfo() {
1016
901
  if (!g_selectedScreenInfo) return nil;
1017
902
 
1018
- NSDictionary *result = g_selectedScreenInfo;
903
+ NSDictionary *result = [g_selectedScreenInfo retain];
904
+ [g_selectedScreenInfo release];
1019
905
  g_selectedScreenInfo = nil;
1020
906
 
1021
- return result;
907
+ return [result autorelease];
1022
908
  }
1023
909
 
1024
910
  bool showScreenRecordingPreview(NSDictionary *screenInfo) {
@@ -1033,10 +919,10 @@ bool showScreenRecordingPreview(NSDictionary *screenInfo) {
1033
919
  NSArray *screens = [NSScreen screens];
1034
920
  if (!screens || [screens count] == 0) return false;
1035
921
 
1036
- NSUInteger selectedScreenId = (NSUInteger)[[screenInfo objectForKey:@"id"] intValue];
922
+ int selectedScreenId = [[screenInfo objectForKey:@"id"] intValue];
1037
923
 
1038
924
  // Create overlay for each screen except the selected one
1039
- for (NSUInteger i = 0; i < [screens count]; i++) {
925
+ for (NSInteger i = 0; i < [screens count]; i++) {
1040
926
  if (i == selectedScreenId) continue; // Skip selected screen
1041
927
 
1042
928
  NSScreen *screen = [screens objectAtIndex:i];
@@ -1067,7 +953,7 @@ bool showScreenRecordingPreview(NSDictionary *screenInfo) {
1067
953
  }
1068
954
  }
1069
955
 
1070
- NSLog(@"🎬 SCREEN RECORDING PREVIEW: Showing overlay for Screen %lu", (unsigned long)selectedScreenId);
956
+ NSLog(@"🎬 SCREEN RECORDING PREVIEW: Showing overlay for Screen %d", selectedScreenId);
1071
957
 
1072
958
  return true;
1073
959
 
@@ -1085,50 +971,14 @@ bool hideScreenRecordingPreview() {
1085
971
  Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1086
972
  Napi::Env env = info.Env();
1087
973
 
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
974
  if (g_isWindowSelecting) {
1125
- NSLog(@"⚠️ Window selection already in progress");
1126
- return Napi::Boolean::New(env, false);
975
+ Napi::TypeError::New(env, "Window selection already in progress").ThrowAsJavaScriptException();
976
+ return env.Null();
1127
977
  }
1128
978
 
1129
979
  @try {
1130
980
  // Get all windows
1131
- g_allWindows = [getAllSelectableWindows() mutableCopy];
981
+ g_allWindows = [getAllSelectableWindows() retain];
1132
982
 
1133
983
  if (!g_allWindows || [g_allWindows count] == 0) {
1134
984
  Napi::Error::New(env, "No selectable windows found").ThrowAsJavaScriptException();
@@ -1159,15 +1009,14 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1159
1009
  g_selectButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 160, 60)];
1160
1010
  [g_selectButton setTitle:@"Start Record"];
1161
1011
  [g_selectButton setButtonType:NSButtonTypeMomentaryPushIn];
1162
- [g_selectButton setBezelStyle:NSBezelStyleRounded];
1012
+ [g_selectButton setBezelStyle:NSBezelStyleRegularSquare];
1163
1013
  [g_selectButton setFont:[NSFont systemFontOfSize:16 weight:NSFontWeightSemibold]];
1164
1014
 
1165
1015
  // Blue background with white text
1166
1016
  [g_selectButton setWantsLayer:YES];
1167
- [g_selectButton.layer setBackgroundColor:[[NSColor colorWithRed:0.0 green:0.4 blue:0.8 alpha:0.9] CGColor]];
1168
- [g_selectButton.layer setCornerRadius:8.0];
1169
- [g_selectButton.layer setBorderColor:[[NSColor colorWithRed:0.0 green:0.3 blue:0.7 alpha:1.0] CGColor]];
1170
- [g_selectButton.layer setBorderWidth:2.0];
1017
+ [g_selectButton.layer setBackgroundColor:[[NSColor colorWithRed:0.2 green:0.6 blue:1.0 alpha:0.95] CGColor]];
1018
+ [g_selectButton.layer setCornerRadius:12.0];
1019
+ [g_selectButton.layer setBorderWidth:0.0];
1171
1020
 
1172
1021
  // White text color
1173
1022
  NSMutableAttributedString *titleString = [[NSMutableAttributedString alloc]
@@ -1195,15 +1044,14 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1195
1044
  NSButton *cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 120, 40)];
1196
1045
  [cancelButton setTitle:@"Cancel"];
1197
1046
  [cancelButton setButtonType:NSButtonTypeMomentaryPushIn];
1198
- [cancelButton setBezelStyle:NSBezelStyleRounded];
1047
+ [cancelButton setBezelStyle:NSBezelStyleRegularSquare];
1199
1048
  [cancelButton setFont:[NSFont systemFontOfSize:14 weight:NSFontWeightMedium]];
1200
1049
 
1201
1050
  // Gray cancel button styling
1202
1051
  [cancelButton setWantsLayer:YES];
1203
- [cancelButton.layer setBackgroundColor:[[NSColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:0.8] CGColor]];
1204
- [cancelButton.layer setCornerRadius:6.0];
1205
- [cancelButton.layer setBorderColor:[[NSColor colorWithRed:0.4 green:0.4 blue:0.4 alpha:1.0] CGColor]];
1206
- [cancelButton.layer setBorderWidth:1.0];
1052
+ [cancelButton.layer setBackgroundColor:[[NSColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:0.9] CGColor]];
1053
+ [cancelButton.layer setCornerRadius:10.0];
1054
+ [cancelButton.layer setBorderWidth:0.0];
1207
1055
 
1208
1056
  // White text for cancel button
1209
1057
  NSMutableAttributedString *cancelTitleString = [[NSMutableAttributedString alloc]
@@ -1308,54 +1156,26 @@ Napi::Value GetSelectedWindowInfo(const Napi::CallbackInfo& info) {
1308
1156
  NSLog(@" 📊 Details: ID=%@, Pos=(%d,%d), Size=%dx%d",
1309
1157
  [g_selectedWindowInfo objectForKey:@"id"], x, y, width, height);
1310
1158
 
1311
- // Get all screens and find which screen contains this window
1159
+ // Get all screens
1312
1160
  NSArray *screens = [NSScreen screens];
1313
1161
  NSScreen *windowScreen = nil;
1314
1162
  NSScreen *mainScreen = [NSScreen mainScreen];
1315
1163
 
1316
- // Calculate window center point for better screen detection
1317
- CGPoint windowCenter = CGPointMake(x + width/2, y + height/2);
1318
-
1319
1164
  for (NSScreen *screen in screens) {
1320
1165
  NSRect screenFrame = [screen frame];
1321
1166
 
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) {
1167
+ // Convert window coordinates to screen-relative
1168
+ if (x >= screenFrame.origin.x &&
1169
+ x < screenFrame.origin.x + screenFrame.size.width &&
1170
+ y >= screenFrame.origin.y &&
1171
+ y < screenFrame.origin.y + screenFrame.size.height) {
1327
1172
  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
1173
  break;
1332
1174
  }
1333
1175
  }
1334
1176
 
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
1177
  if (!windowScreen) {
1357
1178
  windowScreen = mainScreen;
1358
- NSLog(@" 🖥️ Window defaulted to main screen");
1359
1179
  }
1360
1180
 
1361
1181
  // Add screen information
@@ -1368,6 +1188,7 @@ Napi::Value GetSelectedWindowInfo(const Napi::CallbackInfo& info) {
1368
1188
  result.Set("screenHeight", Napi::Number::New(env, (int)screenFrame.size.height));
1369
1189
 
1370
1190
  // Clear selected window info after reading
1191
+ [g_selectedWindowInfo release];
1371
1192
  g_selectedWindowInfo = nil;
1372
1193
 
1373
1194
  return result;
@@ -1423,74 +1244,13 @@ Napi::Value GetWindowSelectionStatus(const Napi::CallbackInfo& info) {
1423
1244
  updateOverlay();
1424
1245
  }
1425
1246
 
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
1247
  Napi::Object result = Napi::Object::New(env);
1431
1248
  result.Set("isSelecting", Napi::Boolean::New(env, g_isWindowSelecting));
1432
1249
  result.Set("hasSelectedWindow", Napi::Boolean::New(env, g_selectedWindowInfo != nil));
1433
1250
  result.Set("windowCount", Napi::Number::New(env, g_allWindows ? [g_allWindows count] : 0));
1434
1251
  result.Set("hasOverlay", Napi::Boolean::New(env, g_overlayWindow != nil));
1435
1252
 
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
1253
+ if (g_currentWindowUnderCursor) {
1494
1254
  Napi::Object currentWindow = Napi::Object::New(env);
1495
1255
  currentWindow.Set("id", Napi::Number::New(env, [[g_currentWindowUnderCursor objectForKey:@"id"] intValue]));
1496
1256
  currentWindow.Set("title", Napi::String::New(env, [[g_currentWindowUnderCursor objectForKey:@"title"] UTF8String]));
@@ -1510,13 +1270,13 @@ Napi::Value ShowRecordingPreview(const Napi::CallbackInfo& info) {
1510
1270
  Napi::Env env = info.Env();
1511
1271
 
1512
1272
  if (info.Length() < 1) {
1513
- NSLog(@"⚠️ Window info object required");
1514
- return Napi::Boolean::New(env, false);
1273
+ Napi::TypeError::New(env, "Window info object required").ThrowAsJavaScriptException();
1274
+ return env.Null();
1515
1275
  }
1516
1276
 
1517
1277
  if (!info[0].IsObject()) {
1518
- NSLog(@"⚠️ Window info must be an object");
1519
- return Napi::Boolean::New(env, false);
1278
+ Napi::TypeError::New(env, "Window info must be an object").ThrowAsJavaScriptException();
1279
+ return env.Null();
1520
1280
  }
1521
1281
 
1522
1282
  @try {
@@ -1548,7 +1308,7 @@ Napi::Value ShowRecordingPreview(const Napi::CallbackInfo& info) {
1548
1308
  }
1549
1309
 
1550
1310
  bool success = showRecordingPreview(windowInfo);
1551
- windowInfo = nil;
1311
+ [windowInfo release];
1552
1312
 
1553
1313
  return Napi::Boolean::New(env, success);
1554
1314
 
@@ -1574,57 +1334,11 @@ Napi::Value HideRecordingPreview(const Napi::CallbackInfo& info) {
1574
1334
  Napi::Value StartScreenSelection(const Napi::CallbackInfo& info) {
1575
1335
  Napi::Env env = info.Env();
1576
1336
 
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
1337
  @try {
1623
1338
  bool success = startScreenSelection();
1624
1339
  return Napi::Boolean::New(env, success);
1625
1340
 
1626
1341
  } @catch (NSException *exception) {
1627
- NSLog(@"❌ Screen selection error: %@", exception);
1628
1342
  return Napi::Boolean::New(env, false);
1629
1343
  }
1630
1344
  }
@@ -1716,7 +1430,7 @@ Napi::Value ShowScreenRecordingPreview(const Napi::CallbackInfo& info) {
1716
1430
  }
1717
1431
 
1718
1432
  bool success = showScreenRecordingPreview(screenInfo);
1719
- screenInfo = nil;
1433
+ [screenInfo release];
1720
1434
 
1721
1435
  return Napi::Boolean::New(env, success);
1722
1436