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.
- package/binding.gyp +5 -12
- package/index.js +25 -104
- package/install.js +2 -19
- package/package.json +2 -6
- package/src/audio_capture.mm +40 -96
- package/src/cursor_tracker.mm +4 -3
- package/src/mac_recorder.mm +673 -753
- package/src/screen_capture.h +0 -5
- package/src/screen_capture.mm +60 -139
- package/src/window_selector.mm +67 -368
- package/window-selector.js +34 -112
- package/ELECTRON-INTEGRATION.md +0 -710
- package/WINDOW_SELECTOR_USAGE.md +0 -447
- package/backup/binding.gyp +0 -44
- package/backup/src/audio_capture.mm +0 -116
- package/backup/src/cursor_tracker.mm +0 -518
- package/backup/src/mac_recorder.mm +0 -829
- package/backup/src/screen_capture.h +0 -19
- package/backup/src/screen_capture.mm +0 -162
- package/backup/src/screen_capture_kit.h +0 -15
- package/backup/src/window_selector.mm +0 -1457
- package/electron-window-selector.js +0 -698
- package/node-mac-recorder-2.4.2.tgz +0 -0
- package/prebuilds/darwin-arm64/node.napi.node +0 -0
- package/test-api-compatibility.js +0 -92
- package/test-audio.js +0 -94
- package/test-comprehensive.js +0 -164
- package/test-electron-window-selector.js +0 -119
- package/test-overlay-fix.js +0 -72
- package/test-recording.js +0 -142
- package/test-sck-availability.js +0 -26
- package/test-sck-simple.js +0 -37
- package/test-sck.js +0 -56
- package/test-screencapture-overlay.js +0 -54
- package/test-simple-windows.js +0 -29
- package/test-sync.js +0 -52
- package/test-window-details.js +0 -34
- package/test-windows.js +0 -57
package/src/window_selector.mm
CHANGED
|
@@ -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
|
|
227
|
-
|
|
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 <
|
|
234
|
+
if (g_allScreens && screenIndex >= 0 && screenIndex < [g_allScreens count]) {
|
|
239
235
|
NSDictionary *screenInfo = [g_allScreens objectAtIndex:screenIndex];
|
|
240
|
-
g_selectedScreenInfo = [screenInfo
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
250
|
-
cleanupScreenSelector();
|
|
251
|
-
});
|
|
250
|
+
cleanupScreenSelector();
|
|
252
251
|
} else {
|
|
253
|
-
|
|
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
|
|
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
|
|
422
|
+
// Get window under cursor point
|
|
501
423
|
NSDictionary* getWindowUnderCursor(CGPoint point) {
|
|
502
424
|
@autoreleasepool {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
|
842
|
+
[screenInfo release];
|
|
972
843
|
}
|
|
973
844
|
|
|
974
|
-
g_allScreens = screenInfoArray;
|
|
975
|
-
screenInfoArray
|
|
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
|
-
|
|
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
|
-
|
|
905
|
+
int selectedScreenId = [[screenInfo objectForKey:@"id"] intValue];
|
|
1037
906
|
|
|
1038
907
|
// Create overlay for each screen except the selected one
|
|
1039
|
-
for (
|
|
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 %
|
|
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
|
-
|
|
1126
|
-
return
|
|
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()
|
|
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
|
|
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
|
-
//
|
|
1323
|
-
if (
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
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 (
|
|
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
|
-
|
|
1514
|
-
return
|
|
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
|
-
|
|
1519
|
-
return
|
|
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
|
|
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
|
|
1418
|
+
[screenInfo release];
|
|
1720
1419
|
|
|
1721
1420
|
return Napi::Boolean::New(env, success);
|
|
1722
1421
|
|