node-mac-recorder 2.15.7 โ†’ 2.15.9

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.
@@ -39,7 +39,10 @@
39
39
  "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐Ÿงช Testing crash fix with null path protection'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testCrashFix() {\n try {\n const outputPath = ''./test-output/crash-fix-test.mov'';\n console.log(''๐Ÿ“น Starting crash fix test...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: true,\n includeSystemAudio: true,\n captureArea: {\n x: 100,\n y: 100,\n width: 800,\n height: 600\n }\n });\n \n if (success) {\n console.log(''โœ… Recording started - waiting 3 seconds'');\n await new Promise(resolve => setTimeout(resolve, 3000));\n \n console.log(''๐Ÿ›‘ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โœ… Crash fix test completed successfully!'');\n } else {\n console.log(''โŒ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โŒ Error:'', error.message);\n console.log(''Stack:'', error.stack);\n }\n}\n\ntestCrashFix();\n\")",
40
40
  "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐Ÿ Final stability test with all features'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function finalTest() {\n try {\n const outputPath = ''./test-output/final-stability-test.mov'';\n console.log(''๐Ÿ“น Starting final stability test...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: true,\n includeSystemAudio: true,\n captureArea: {\n x: 200,\n y: 200,\n width: 600,\n height: 400\n }\n });\n \n if (success) {\n console.log(''โœ… Recording started - waiting 4 seconds'');\n await new Promise(resolve => setTimeout(resolve, 4000));\n \n console.log(''๐Ÿ›‘ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''๐ŸŽ‰ FINAL TEST COMPLETED SUCCESSFULLY - NO CRASH!'');\n } else {\n console.log(''โŒ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โŒ Error:'', error.message);\n }\n}\n\nfinalTest();\n\")",
41
41
  "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐ŸŽต Testing system audio only recording'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testSystemAudio() {\n try {\n const outputPath = ''./test-output/system-audio-only.mov'';\n console.log(''๐Ÿ“น Starting system audio only recording...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: false,\n includeMicrophone: false, // Mikrophone DEVRE DIลžI \n includeSystemAudio: true, // Sadece sistem sesi Aร‡IK\n });\n \n if (success) {\n console.log(''โœ… System audio recording started - waiting 4 seconds'');\n await new Promise(resolve => setTimeout(resolve, 4000));\n \n console.log(''๐Ÿ›‘ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โœ… System audio test completed!'');\n } else {\n console.log(''โŒ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โŒ Error:'', error.message);\n }\n}\n\ntestSystemAudio();\n\")",
42
- "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐Ÿ Final stability test with all features'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function finalTest() {\n try {\n const outputPath = ''./test-output/final-stability-test.mov'';\n console.log(''๐Ÿ“น Starting final stability test...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: true,\n captureArea: {\n x: 200,\n y: 200,\n width: 600,\n height: 400\n }\n });\n \n if (success) {\n console.log(''โœ… Recording started - waiting 4 seconds'');\n await new Promise(resolve => setTimeout(resolve, 4000));\n \n console.log(''๐Ÿ›‘ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''๐ŸŽ‰ FINAL TEST COMPLETED SUCCESSFULLY - NO CRASH!'');\n } else {\n console.log(''โŒ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โŒ Error:'', error.message);\n }\n}\n\nfinalTest();\n\")"
42
+ "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐Ÿ Final stability test with all features'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function finalTest() {\n try {\n const outputPath = ''./test-output/final-stability-test.mov'';\n console.log(''๐Ÿ“น Starting final stability test...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: true,\n captureArea: {\n x: 200,\n y: 200,\n width: 600,\n height: 400\n }\n });\n \n if (success) {\n console.log(''โœ… Recording started - waiting 4 seconds'');\n await new Promise(resolve => setTimeout(resolve, 4000));\n \n console.log(''๐Ÿ›‘ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''๐ŸŽ‰ FINAL TEST COMPLETED SUCCESSFULLY - NO CRASH!'');\n } else {\n console.log(''โŒ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โŒ Error:'', error.message);\n }\n}\n\nfinalTest();\n\")",
43
+ "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐ŸŽต Testing both microphone and system audio'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testBothAudio() {\n try {\n const outputPath = ''./test-output/both-audio-test.mov'';\n console.log(''๐Ÿ“น Starting recording with both audio sources...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: true, // Mikrophone Aร‡IK\n includeSystemAudio: true // Sistem sesi Aร‡IK\n });\n \n if (success) {\n console.log(''โœ… Recording started - waiting 5 seconds'');\n await new Promise(resolve => setTimeout(resolve, 5000));\n \n console.log(''๐Ÿ›‘ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โœ… Both audio test completed!'');\n } else {\n console.log(''โŒ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โŒ Error:'', error.message);\n console.log(''Stack:'', error.stack);\n }\n}\n\ntestBothAudio();\n\")",
44
+ "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐ŸŽค Testing microphone only'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testMicrophoneOnly() {\n try {\n const outputPath = ''./test-output/microphone-only-test.mov'';\n console.log(''๐Ÿ“น Starting microphone only recording...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: false,\n includeMicrophone: true, // Sadece mikrofon Aร‡IK\n includeSystemAudio: false // Sistem sesi KAPALI\n });\n \n if (success) {\n console.log(''โœ… Microphone recording started - waiting 3 seconds'');\n await new Promise(resolve => setTimeout(resolve, 3000));\n \n console.log(''๐Ÿ›‘ Stopping microphone recording...'');\n await recorder.stopRecording();\n console.log(''โœ… Microphone only test completed!'');\n } else {\n console.log(''โŒ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โŒ Error:'', error.message);\n }\n}\n\ntestMicrophoneOnly();\n\")",
45
+ "Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐Ÿš€ Full stress test - All features + Both audio sources'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function fullStressTest() {\n try {\n const outputPath = ''./test-output/full-stress-test.mov'';\n console.log(''๐Ÿ“น Starting full featured recording (stress test)...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true, // Cursor kayฤฑt\n includeMicrophone: true, // Mikrofon kayฤฑt\n includeSystemAudio: true, // Sistem sesi kayฤฑt\n captureArea: { // Crop area\n x: 300,\n y: 300,\n width: 800,\n height: 500\n }\n });\n \n if (success) {\n console.log(''โœ… Full stress test started - waiting 6 seconds'');\n await new Promise(resolve => setTimeout(resolve, 6000));\n \n console.log(''๐Ÿ›‘ Stopping full stress test...'');\n await recorder.stopRecording();\n console.log(''๐ŸŽ‰ FULL STRESS TEST COMPLETED WITHOUT CRASH!'');\n } else {\n console.log(''โŒ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โŒ Error:'', error.message);\n console.log(''Stack:'', error.stack);\n }\n}\n\nfullStressTest();\n\")"
43
46
  ],
44
47
  "deny": []
45
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.15.7",
3
+ "version": "2.15.9",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -187,20 +187,24 @@ static NSString *g_outputPath = nil;
187
187
  NSLog(@"๐ŸŽฅ Pure ScreenCapture config: %ldx%ld @ 30fps, cursor=%d",
188
188
  recordingWidth, recordingHeight, shouldShowCursor);
189
189
 
190
- // AUDIO SUPPORT - Enable only for system audio in Electron
190
+ // AUDIO SUPPORT - Enable both microphone and system audio
191
191
  BOOL shouldCaptureMic = includeMicrophone ? [includeMicrophone boolValue] : NO;
192
192
  BOOL shouldCaptureSystemAudio = includeSystemAudio ? [includeSystemAudio boolValue] : NO;
193
193
 
194
- // Only enable system audio, mic causes crashes in Electron
194
+ // Enable audio if either microphone or system audio is requested
195
195
  if (@available(macOS 13.0, *)) {
196
- if (shouldCaptureSystemAudio && !shouldCaptureMic) {
196
+ if (shouldCaptureMic || shouldCaptureSystemAudio) {
197
197
  streamConfig.capturesAudio = YES;
198
198
  streamConfig.sampleRate = 44100;
199
199
  streamConfig.channelCount = 2;
200
- NSLog(@"๐ŸŽต System audio only enabled (safe for Electron)");
201
- } else if (shouldCaptureMic) {
202
- NSLog(@"๐Ÿšซ Microphone audio disabled in Electron for stability");
203
- streamConfig.capturesAudio = NO;
200
+
201
+ if (shouldCaptureMic && shouldCaptureSystemAudio) {
202
+ NSLog(@"๐ŸŽต Both microphone and system audio enabled");
203
+ } else if (shouldCaptureMic) {
204
+ NSLog(@"๐ŸŽค Microphone audio enabled");
205
+ } else {
206
+ NSLog(@"๐Ÿ”Š System audio enabled");
207
+ }
204
208
  } else {
205
209
  streamConfig.capturesAudio = NO;
206
210
  NSLog(@"๐Ÿ”‡ Audio disabled");
@@ -236,8 +240,12 @@ static NSString *g_outputPath = nil;
236
240
  // Create recording output with correct initializer
237
241
  g_recordingOutput = [[SCRecordingOutput alloc] initWithConfiguration:recordingConfig
238
242
  delegate:nil];
239
- if (shouldCaptureSystemAudio && !shouldCaptureMic) {
240
- NSLog(@"๐Ÿ”ง Created SCRecordingOutput with system audio only");
243
+ if (shouldCaptureMic && shouldCaptureSystemAudio) {
244
+ NSLog(@"๐Ÿ”ง Created SCRecordingOutput with microphone and system audio");
245
+ } else if (shouldCaptureMic) {
246
+ NSLog(@"๐Ÿ”ง Created SCRecordingOutput with microphone audio");
247
+ } else if (shouldCaptureSystemAudio) {
248
+ NSLog(@"๐Ÿ”ง Created SCRecordingOutput with system audio");
241
249
  } else {
242
250
  NSLog(@"๐Ÿ”ง Created SCRecordingOutput (audio disabled)");
243
251
  }
@@ -74,6 +74,7 @@ void updateScreenOverlays();
74
74
  @property (nonatomic) BOOL isActiveWindow;
75
75
  @property (nonatomic) BOOL isToggled;
76
76
  @property (nonatomic) NSRect highlightFrame;
77
+ @property (nonatomic, strong) NSValue *globalOriginOffset; // Store global coordinate offset
77
78
  - (void)setHighlightFrame:(NSRect)frame;
78
79
  @end
79
80
 
@@ -863,10 +864,21 @@ void updateOverlay() {
863
864
  }
864
865
  }
865
866
 
866
- // No need to resize window since it's full-screen, just update the highlight area
867
+ // Convert global window coordinates to local view coordinates
868
+ // Account for multi-display offset from global origin
869
+ WindowSelectorOverlayView *overlayView = (WindowSelectorOverlayView *)g_overlayView;
870
+ NSPoint globalOffset = NSZeroPoint;
871
+ if (overlayView.globalOriginOffset) {
872
+ globalOffset = [overlayView.globalOriginOffset pointValue];
873
+ }
874
+
875
+ // Convert global window position to local view position
876
+ CGFloat localX = x - globalOffset.x;
877
+ CGFloat localY = ([g_overlayView frame].size.height - (y - globalOffset.y)) - height;
878
+
867
879
  // Update overlay view window info for highlighting
868
- [(WindowSelectorOverlayView *)g_overlayView setWindowInfo:targetWindow];
869
- [(WindowSelectorOverlayView *)g_overlayView setHighlightFrame:NSMakeRect(x, [g_overlayView frame].size.height - y - height, width, height)];
880
+ [overlayView setWindowInfo:targetWindow];
881
+ [overlayView setHighlightFrame:NSMakeRect(localX, localY, width, height)];
870
882
 
871
883
  // Only reset toggle state when switching to different window (not for position updates)
872
884
  if (!g_hasToggledWindow) {
@@ -967,19 +979,19 @@ void updateOverlay() {
967
979
  // Position buttons - Start Record in center of selected window
968
980
  if (g_selectButton) {
969
981
  NSSize buttonSize = [g_selectButton frame].size;
970
- // Use window center for positioning
971
- CGFloat windowCenterX = x + (width / 2);
972
- CGFloat windowCenterY = y + (height / 2);
982
+ // Use local window center for positioning
983
+ CGFloat localWindowCenterX = localX + (width / 2);
984
+ CGFloat localWindowCenterY = localY + (height / 2);
973
985
  NSPoint buttonCenter = NSMakePoint(
974
- windowCenterX - (buttonSize.width / 2),
975
- windowCenterY - (buttonSize.height / 2) // Perfect center of window
986
+ localWindowCenterX - (buttonSize.width / 2),
987
+ localWindowCenterY - (buttonSize.height / 2) // Perfect center of window
976
988
  );
977
989
  [g_selectButton setFrameOrigin:buttonCenter];
978
990
 
979
991
  // Position app icon above window center
980
992
  NSPoint iconCenter = NSMakePoint(
981
- windowCenterX - (96 / 2), // Center horizontally on window
982
- windowCenterY + 120 // 120px above window center
993
+ localWindowCenterX - (96 / 2), // Center horizontally on window
994
+ localWindowCenterY + 120 // 120px above window center
983
995
  );
984
996
  [appIconView setFrameOrigin:iconCenter];
985
997
  NSLog(@"๐ŸŽฏ Positioning app icon at: (%.0f, %.0f) for window size: (%.0f, %.0f)",
@@ -1001,8 +1013,8 @@ void updateOverlay() {
1001
1013
 
1002
1014
  // Position info label between icon and button relative to window center
1003
1015
  NSPoint labelCenter = NSMakePoint(
1004
- windowCenterX - ([infoLabel frame].size.width / 2), // Center horizontally on window
1005
- windowCenterY + 50 // 50px above window center, below icon
1016
+ localWindowCenterX - ([infoLabel frame].size.width / 2), // Center horizontally on window
1017
+ localWindowCenterY + 50 // 50px above window center, below icon
1006
1018
  );
1007
1019
  [infoLabel setFrameOrigin:labelCenter];
1008
1020
 
@@ -1019,8 +1031,8 @@ void updateOverlay() {
1019
1031
  if (cancelButton) {
1020
1032
  NSSize cancelButtonSize = [cancelButton frame].size;
1021
1033
  NSPoint cancelButtonCenter = NSMakePoint(
1022
- windowCenterX - (cancelButtonSize.width / 2), // Center horizontally on window
1023
- windowCenterY - 80 // 80px below window center
1034
+ localWindowCenterX - (cancelButtonSize.width / 2), // Center horizontally on window
1035
+ localWindowCenterY - 80 // 80px below window center
1024
1036
  );
1025
1037
  [cancelButton setFrameOrigin:cancelButtonCenter];
1026
1038
  }
@@ -1436,8 +1448,9 @@ bool startScreenSelection() {
1436
1448
  [overlayWindow setOpaque:NO];
1437
1449
  [overlayWindow setBackgroundColor:[NSColor clearColor]];
1438
1450
 
1439
- // Create overlay view
1440
- ScreenSelectorOverlayView *overlayView = [[ScreenSelectorOverlayView alloc] initWithFrame:screenFrame];
1451
+ // Create overlay view - use local frame (0,0) not screen frame
1452
+ NSRect localFrame = NSMakeRect(0, 0, screenFrame.size.width, screenFrame.size.height);
1453
+ ScreenSelectorOverlayView *overlayView = [[ScreenSelectorOverlayView alloc] initWithFrame:localFrame];
1441
1454
  [overlayView setScreenInfo:screenInfo];
1442
1455
  [overlayWindow setContentView:overlayView];
1443
1456
 
@@ -1521,8 +1534,8 @@ bool startScreenSelection() {
1521
1534
  [screenCancelButton setFocusRingType:NSFocusRingTypeNone];
1522
1535
  [screenCancelButton setShowsBorderOnlyWhileMouseInside:NO];
1523
1536
 
1524
- // Create info label for screen
1525
- NSTextField *screenInfoLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, screenFrame.size.width - 40, 60)];
1537
+ // Create info label for screen - use local coordinates
1538
+ NSTextField *screenInfoLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, localFrame.size.width - 40, 60)];
1526
1539
  [screenInfoLabel setEditable:NO];
1527
1540
  [screenInfoLabel setSelectable:NO];
1528
1541
  [screenInfoLabel setBezeled:NO];
@@ -1548,17 +1561,17 @@ bool startScreenSelection() {
1548
1561
  NSString *resolution = [screenInfo objectForKey:@"resolution"] ?: @"Unknown Resolution";
1549
1562
  [screenInfoLabel setStringValue:[NSString stringWithFormat:@"%@\n%@", screenName, resolution]];
1550
1563
 
1551
- // Position buttons - Start Record in perfect center
1564
+ // Position buttons - Start Record in perfect center using local coordinates
1552
1565
  NSPoint buttonCenter = NSMakePoint(
1553
- (screenFrame.size.width - [selectButton frame].size.width) / 2,
1554
- (screenFrame.size.height - [selectButton frame].size.height) / 2 // Perfect center
1566
+ (localFrame.size.width - [selectButton frame].size.width) / 2,
1567
+ (localFrame.size.height - [selectButton frame].size.height) / 2 // Perfect center
1555
1568
  );
1556
1569
  [selectButton setFrameOrigin:buttonCenter];
1557
1570
 
1558
- // Position screen icon above center
1571
+ // Position screen icon above center using local coordinates
1559
1572
  NSPoint iconCenter = NSMakePoint(
1560
- (screenFrame.size.width - 96) / 2, // Center horizontally (icon is 96px wide)
1561
- (screenFrame.size.height / 2) + 120 // 120px above center
1573
+ (localFrame.size.width - 96) / 2, // Center horizontally (icon is 96px wide)
1574
+ (localFrame.size.height / 2) + 120 // 120px above center
1562
1575
  );
1563
1576
  [screenIconView setFrameOrigin:iconCenter];
1564
1577
 
@@ -1572,16 +1585,16 @@ bool startScreenSelection() {
1572
1585
  screenFloatAnimationX.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
1573
1586
  [screenIconView.layer addAnimation:screenFloatAnimationX forKey:@"floatAnimationX"];
1574
1587
 
1575
- // Position info label between icon and button
1588
+ // Position info label between icon and button using local coordinates
1576
1589
  NSPoint labelCenter = NSMakePoint(
1577
- (screenFrame.size.width - [screenInfoLabel frame].size.width) / 2, // Center horizontally
1578
- (screenFrame.size.height / 2) + 50 // 50px above center, below icon
1590
+ (localFrame.size.width - [screenInfoLabel frame].size.width) / 2, // Center horizontally
1591
+ (localFrame.size.height / 2) + 50 // 50px above center, below icon
1579
1592
  );
1580
1593
  [screenInfoLabel setFrameOrigin:labelCenter];
1581
1594
 
1582
1595
  NSPoint cancelButtonCenter = NSMakePoint(
1583
- (screenFrame.size.width - [screenCancelButton frame].size.width) / 2,
1584
- (screenFrame.size.height / 2) - 80 // 80px below center
1596
+ (localFrame.size.width - [screenCancelButton frame].size.width) / 2,
1597
+ (localFrame.size.height / 2) - 80 // 80px below center
1585
1598
  );
1586
1599
  [screenCancelButton setFrameOrigin:cancelButtonCenter];
1587
1600
 
@@ -1801,9 +1814,21 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1801
1814
  return env.Null();
1802
1815
  }
1803
1816
 
1804
- // Create full-screen overlay window to prevent window dragging
1805
- NSScreen *mainScreen = [NSScreen mainScreen];
1806
- NSRect fullScreenFrame = [mainScreen frame];
1817
+ // Create multi-display overlay window to prevent window dragging
1818
+ NSArray *allScreens = [NSScreen screens];
1819
+ NSRect combinedFrame = NSZeroRect;
1820
+
1821
+ // Calculate combined frame that covers all displays
1822
+ for (NSScreen *screen in allScreens) {
1823
+ NSRect screenFrame = [screen frame];
1824
+ if (NSEqualRects(combinedFrame, NSZeroRect)) {
1825
+ combinedFrame = screenFrame;
1826
+ } else {
1827
+ combinedFrame = NSUnionRect(combinedFrame, screenFrame);
1828
+ }
1829
+ }
1830
+
1831
+ NSRect fullScreenFrame = combinedFrame;
1807
1832
  g_overlayWindow = [[NoFocusWindow alloc] initWithContentRect:fullScreenFrame
1808
1833
  styleMask:NSWindowStyleMaskBorderless
1809
1834
  backing:NSBackingStoreBuffered
@@ -1832,8 +1857,13 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1832
1857
  [g_overlayWindow setOpaque:NO];
1833
1858
  [g_overlayWindow setBackgroundColor:[NSColor clearColor]];
1834
1859
 
1835
- // Create overlay view covering full screen
1836
- g_overlayView = [[WindowSelectorOverlayView alloc] initWithFrame:fullScreenFrame];
1860
+ // Create overlay view covering all displays
1861
+ // Convert global coordinates to local view coordinates (offset by origin)
1862
+ NSRect localViewFrame = NSMakeRect(0, 0, fullScreenFrame.size.width, fullScreenFrame.size.height);
1863
+ g_overlayView = [[WindowSelectorOverlayView alloc] initWithFrame:localViewFrame];
1864
+
1865
+ // Store the global origin offset for coordinate conversion
1866
+ [(WindowSelectorOverlayView *)g_overlayView setValue:[NSValue valueWithPoint:fullScreenFrame.origin] forKey:@"globalOriginOffset"];
1837
1867
  [g_overlayWindow setContentView:g_overlayView];
1838
1868
 
1839
1869
  // Note: NSWindow doesn't have setWantsLayer method, only NSView does
@@ -0,0 +1,120 @@
1
+ const MacRecorder = require('./index');
2
+ const WindowSelector = MacRecorder.WindowSelector;
3
+
4
+ console.log('๐Ÿงช Testing multi-display fixes...');
5
+
6
+ async function testDisplaySelection() {
7
+ console.log('\n๐Ÿ–ฅ๏ธ Testing display selection:');
8
+
9
+ const recorder = new MacRecorder();
10
+ const displays = await recorder.getDisplays();
11
+
12
+ console.log(`Found ${displays.length} displays:`);
13
+ displays.forEach((display, index) => {
14
+ console.log(` ${index + 1}. ${display.name} (ID: ${display.id}) - ${display.resolution} at (${display.x}, ${display.y}) ${display.isPrimary ? '[PRIMARY]' : ''}`);
15
+ });
16
+
17
+ // Test recording on each display
18
+ for (const display of displays) {
19
+ console.log(`\n๐Ÿ“น Testing recording on ${display.name}:`);
20
+ try {
21
+ const outputPath = `./test-output/display-${display.id}-test.mov`;
22
+
23
+ await recorder.startRecording(outputPath, {
24
+ displayId: display.id,
25
+ captureCursor: false,
26
+ includeMicrophone: false,
27
+ includeSystemAudio: false
28
+ });
29
+
30
+ console.log(`โœ… Recording started on ${display.name}`);
31
+
32
+ // Record for 2 seconds
33
+ await new Promise(resolve => setTimeout(resolve, 2000));
34
+
35
+ await recorder.stopRecording();
36
+ console.log(`โœ… Recording stopped on ${display.name}`);
37
+
38
+ } catch (error) {
39
+ console.log(`โŒ Recording failed on ${display.name}: ${error.message}`);
40
+ }
41
+
42
+ // Small delay between tests
43
+ await new Promise(resolve => setTimeout(resolve, 1000));
44
+ }
45
+ }
46
+
47
+ async function testScreenSelector() {
48
+ console.log('\n๐ŸŽฏ Testing screen selector multi-display...');
49
+
50
+ const selector = new WindowSelector();
51
+
52
+ try {
53
+ console.log('๐Ÿ” Starting screen selection... (This should show overlays on ALL displays)');
54
+ await selector.startScreenSelection();
55
+
56
+ console.log('โฑ๏ธ Screen selection UI is running...');
57
+ console.log(' - Check if overlays appear on ALL displays');
58
+ console.log(' - Check if buttons and text are positioned correctly');
59
+ console.log(' - Check if app icons are visible and animated');
60
+
61
+ // Let it run for 10 seconds for manual inspection
62
+ await new Promise(resolve => setTimeout(resolve, 10000));
63
+
64
+ console.log('๐Ÿ›‘ Stopping screen selection...');
65
+ await selector.stopScreenSelection();
66
+
67
+ } catch (error) {
68
+ console.log(`โŒ Screen selector test failed: ${error.message}`);
69
+ }
70
+ }
71
+
72
+ async function testWindowSelector() {
73
+ console.log('\n๐ŸชŸ Testing window selector multi-display...');
74
+
75
+ const selector = new WindowSelector();
76
+
77
+ try {
78
+ console.log('๐Ÿ” Starting window selection... (This should work across ALL displays)');
79
+ await selector.startSelection();
80
+
81
+ console.log('โฑ๏ธ Window selection UI is running...');
82
+ console.log(' - Move cursor to windows on different displays');
83
+ console.log(' - Check if highlighting works properly');
84
+ console.log(' - Check if buttons appear correctly over highlighted windows');
85
+
86
+ // Let it run for 15 seconds for manual inspection
87
+ await new Promise(resolve => setTimeout(resolve, 15000));
88
+
89
+ console.log('๐Ÿ›‘ Stopping window selection...');
90
+ await selector.stopSelection();
91
+
92
+ } catch (error) {
93
+ console.log(`โŒ Window selector test failed: ${error.message}`);
94
+ }
95
+ }
96
+
97
+ async function runTests() {
98
+ try {
99
+ // Check permissions first
100
+ const recorder = new MacRecorder();
101
+ const permissions = await recorder.checkPermissions();
102
+
103
+ if (!permissions.screenRecording) {
104
+ console.log('โŒ Screen recording permission required. Enable in System Preferences > Security & Privacy > Privacy > Screen Recording');
105
+ return;
106
+ }
107
+
108
+ await testDisplaySelection();
109
+ await testScreenSelector();
110
+ await testWindowSelector();
111
+
112
+ console.log('\n๐ŸŽ‰ Multi-display tests completed!');
113
+
114
+ } catch (error) {
115
+ console.log(`โŒ Test suite failed: ${error.message}`);
116
+ console.log(error.stack);
117
+ }
118
+ }
119
+
120
+ runTests();