node-mac-recorder 2.16.15 โ†’ 2.16.17

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐Ÿงช Testing area recording with scaling...'');\nconst MacRecorder = require(''./index.js'');\nconst recorder = new MacRecorder();\n\nconst options = {\n captureArea: {\n x: 200,\n y: 200, \n width: 800,\n height: 600\n }\n};\n\nrecorder.startRecording(''/tmp/area-scaling-test.mov'', options)\n .then(success => {\n console.log(''Area recording:'', success ? ''โœ… SUCCESS'' : ''โŒ FAILED'');\n if (success) {\n setTimeout(() => {\n recorder.stopRecording().then(() => {\n console.log(''โœ… Area scaling test complete'');\n const fs = require(''fs'');\n if (fs.existsSync(''/tmp/area-scaling-test.mov'')) {\n const size = Math.round(fs.statSync(''/tmp/area-scaling-test.mov'').size/1024);\n console.log(''๐Ÿ“น File size:'', size + ''KB'');\n }\n });\n }, 2000);\n }\n })\n .catch(console.error);\n\")"
4
+ "Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐Ÿงช Testing per-screen window selector overlay system...'');\nconst WindowSelector = require(''./window-selector.js'');\nconst selector = new WindowSelector();\n\n// Test permission check first\nselector.checkPermissions().then(perms => {\n console.log(''Permissions:'', perms);\n \n // Start window selection to test per-screen overlays\n return selector.startSelection();\n}).then(() => {\n console.log(''โœ… Window selection started - testing per-screen overlays'');\n console.log(''โžก๏ธ Move cursor over windows on different displays to test overlay positioning'');\n \n // Auto-stop after 10 seconds for testing\n setTimeout(() => {\n selector.stopSelection().then(() => {\n console.log(''โœ… Window selection stopped - test complete'');\n });\n }, 10000);\n}).catch(console.error);\n\")"
5
5
  ],
6
6
  "deny": [],
7
7
  "ask": []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.16.15",
3
+ "version": "2.16.17",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -65,8 +65,17 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
65
65
  CGFloat scaleY = physicalSize.height / logicalSize.height;
66
66
  CGFloat scaleFactor = MAX(scaleX, scaleY); // Use max to handle non-uniform scaling
67
67
 
68
- // For AVFoundation, use logical size (what CGDisplayCreateImage actually captures)
69
- CGSize recordingSize = captureRect.size.width > 0 ? captureRect.size : logicalSize;
68
+ // CRITICAL FIX: Use actual captured image dimensions, not logical size
69
+ // CGDisplayCreateImage may return higher resolution on Retina displays
70
+ CGImageRef testImage = CGDisplayCreateImage(displayID);
71
+ CGSize actualImageSize = CGSizeMake(CGImageGetWidth(testImage), CGImageGetHeight(testImage));
72
+ CGImageRelease(testImage);
73
+
74
+ // For AVFoundation, use actual image size that CGDisplayCreateImage returns
75
+ CGSize recordingSize = captureRect.size.width > 0 ? captureRect.size : actualImageSize;
76
+
77
+ NSLog(@"๐ŸŽฏ CRITICAL: Logical %.0fx%.0f โ†’ Actual image %.0fx%.0f",
78
+ logicalSize.width, logicalSize.height, actualImageSize.width, actualImageSize.height);
70
79
 
71
80
  NSLog(@"๐Ÿ–ฅ๏ธ Display bounds (logical): %.0fx%.0f", logicalSize.width, logicalSize.height);
72
81
  NSLog(@"๐Ÿ–ฅ๏ธ Display pixels (physical): %.0fx%.0f", physicalSize.width, physicalSize.height);
@@ -147,13 +156,30 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
147
156
 
148
157
  // Apply scaling to capture rect if provided (for macOS 14/13 compatibility)
149
158
  if (!CGRectIsEmpty(captureRect)) {
150
- // Note: captureRect comes in logical coordinates, keep as-is for CGDisplayCreateImage
151
- g_avCaptureRect = captureRect;
152
- NSLog(@"๐Ÿ”ฒ Capture area (logical): %.0f,%.0f %.0fx%.0f",
153
- captureRect.origin.x, captureRect.origin.y, captureRect.size.width, captureRect.size.height);
159
+ // Scale capture rect to match actual image dimensions if needed
160
+ CGFloat imageScaleX = actualImageSize.width / logicalSize.width;
161
+ CGFloat imageScaleY = actualImageSize.height / logicalSize.height;
162
+
163
+ if (imageScaleX > 1.1 || imageScaleY > 1.1) {
164
+ // Scale up capture rect for high-DPI displays
165
+ CGRect scaledRect = CGRectMake(
166
+ captureRect.origin.x * imageScaleX,
167
+ captureRect.origin.y * imageScaleY,
168
+ captureRect.size.width * imageScaleX,
169
+ captureRect.size.height * imageScaleY
170
+ );
171
+ g_avCaptureRect = scaledRect;
172
+ NSLog(@"๐Ÿ”ฒ Capture area scaled: %.0f,%.0f %.0fx%.0f (scale %.1fx%.1fx)",
173
+ scaledRect.origin.x, scaledRect.origin.y, scaledRect.size.width, scaledRect.size.height,
174
+ imageScaleX, imageScaleY);
175
+ } else {
176
+ g_avCaptureRect = captureRect;
177
+ NSLog(@"๐Ÿ”ฒ Capture area (no scaling): %.0f,%.0f %.0fx%.0f",
178
+ captureRect.origin.x, captureRect.origin.y, captureRect.size.width, captureRect.size.height);
179
+ }
154
180
  } else {
155
181
  g_avCaptureRect = CGRectZero; // Full screen
156
- NSLog(@"๐Ÿ–ฅ๏ธ Full screen capture (logical bounds)");
182
+ NSLog(@"๐Ÿ–ฅ๏ธ Full screen capture (actual image dimensions)");
157
183
  }
158
184
 
159
185
  g_avFrameNumber = 0;
@@ -214,8 +240,11 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
214
240
  size_t imageWidth = CGImageGetWidth(screenImage);
215
241
  size_t imageHeight = CGImageGetHeight(screenImage);
216
242
 
243
+ NSLog(@"๐Ÿ” Debug: Buffer %zux%zu, Image %zux%zu", bufferWidth, bufferHeight, imageWidth, imageHeight);
244
+
217
245
  if (bufferWidth != imageWidth || bufferHeight != imageHeight) {
218
- NSLog(@"โš ๏ธ Size mismatch! Buffer %zux%zu vs Image %zux%zu", bufferWidth, bufferHeight, imageWidth, imageHeight);
246
+ NSLog(@"โš ๏ธ CRITICAL SIZE MISMATCH! Buffer %zux%zu vs Image %zux%zu", bufferWidth, bufferHeight, imageWidth, imageHeight);
247
+ NSLog(@" This indicates Retina scaling issue on macOS 14/13");
219
248
  }
220
249
 
221
250
  CVPixelBufferLockBaseAddress(pixelBuffer, 0);
@@ -16,6 +16,11 @@ static NSTimer *g_trackingTimer = nil;
16
16
  static NSDictionary *g_selectedWindowInfo = nil;
17
17
  static NSMutableArray *g_allWindows = nil;
18
18
 
19
+ // Per-screen overlay system
20
+ static NSMutableArray *g_perScreenOverlays = nil;
21
+ static NSMutableArray *g_perScreenOverlayViews = nil;
22
+ static NSInteger g_activeScreenIndex = -1;
23
+
19
24
  // Functions to hide/show main overlay window during recording
20
25
  void hideAllOverlayWindows() {
21
26
  if (g_overlayWindow && [g_overlayWindow isVisible]) {
@@ -866,44 +871,58 @@ void updateOverlay() {
866
871
  }
867
872
  }
868
873
 
869
- // Convert global window coordinates to local view coordinates
870
- // Account for multi-display offset from global origin
871
- WindowSelectorOverlayView *overlayView = (WindowSelectorOverlayView *)g_overlayView;
872
- NSPoint globalOffset = NSZeroPoint;
873
- if (overlayView.globalOriginOffset) {
874
- globalOffset = [overlayView.globalOriginOffset pointValue];
875
- }
876
-
877
- // Determine if this is a primary display window
874
+ // NEW PER-SCREEN SYSTEM: Find which screen this window belongs to
878
875
  NSArray *allScreens = [NSScreen screens];
879
- NSScreen *primaryScreen = [allScreens objectAtIndex:0]; // Primary screen
880
- NSRect primaryFrame = [primaryScreen frame];
881
- BOOL isPrimaryDisplayWindow = (x >= primaryFrame.origin.x &&
882
- x <= primaryFrame.origin.x + primaryFrame.size.width &&
883
- y >= primaryFrame.origin.y &&
884
- y <= primaryFrame.origin.y + primaryFrame.size.height);
885
-
886
- CGFloat localX, localY;
887
- if (isPrimaryDisplayWindow) {
888
- // Primary display windows: Calculate dynamic offset from combined frame
889
- NSRect combinedFrame = [g_overlayWindow frame];
890
-
891
- // Calculate primary screen offset within combined frame
892
- CGFloat primaryOffsetX = primaryFrame.origin.x - combinedFrame.origin.x;
893
- CGFloat primaryOffsetY = primaryFrame.origin.y - combinedFrame.origin.y;
876
+ NSInteger targetScreenIndex = -1;
877
+ NSScreen *targetScreen = nil;
878
+
879
+ // Find screen containing this window
880
+ for (NSInteger i = 0; i < [allScreens count]; i++) {
881
+ NSScreen *screen = [allScreens objectAtIndex:i];
882
+ NSRect screenFrame = [screen frame];
894
883
 
895
- localX = x + primaryOffsetX;
896
- localY = ([g_overlayView frame].size.height - (y + primaryOffsetY)) - height;
884
+ // Check if window center is within this screen bounds
885
+ CGFloat windowCenterX = x + width/2;
886
+ CGFloat windowCenterY = y + height/2;
897
887
 
898
- } else {
899
- // Secondary display windows: Apply standard coordinate transformation
900
- localX = x - globalOffset.x;
901
- localY = ([g_overlayView frame].size.height - (y - globalOffset.y)) - height;
888
+ if (windowCenterX >= screenFrame.origin.x &&
889
+ windowCenterX <= screenFrame.origin.x + screenFrame.size.width &&
890
+ windowCenterY >= screenFrame.origin.y &&
891
+ windowCenterY <= screenFrame.origin.y + screenFrame.size.height) {
892
+ targetScreenIndex = i;
893
+ targetScreen = screen;
894
+ break;
895
+ }
896
+ }
897
+
898
+ if (targetScreenIndex == -1) {
899
+ NSLog(@"โš ๏ธ Window not found on any screen: (%.0f,%.0f)", (CGFloat)x, (CGFloat)y);
900
+ return;
902
901
  }
903
902
 
904
- // Update overlay view window info for highlighting
905
- [overlayView setWindowInfo:targetWindow];
906
- [overlayView setHighlightFrame:NSMakeRect(localX, localY, width, height)];
903
+ // Get the overlay for target screen
904
+ NSWindow *targetOverlay = [g_perScreenOverlays objectAtIndex:targetScreenIndex];
905
+ WindowSelectorOverlayView *targetOverlayView = [g_perScreenOverlayViews objectAtIndex:targetScreenIndex];
906
+ NSRect targetScreenFrame = [targetScreen frame];
907
+
908
+ // Calculate LOCAL coordinates within target screen (much simpler!)
909
+ CGFloat localX = x - targetScreenFrame.origin.x;
910
+ CGFloat localY = (targetScreenFrame.size.height - (y - targetScreenFrame.origin.y)) - height;
911
+
912
+ NSLog(@"๐ŸŽฏ Window on Screen %ld: Global(%.0f,%.0f) โ†’ Local(%.0f,%.0f)",
913
+ targetScreenIndex, (CGFloat)x, (CGFloat)y, localX, localY);
914
+
915
+ // Update active screen if changed
916
+ if (g_activeScreenIndex != targetScreenIndex) {
917
+ NSLog(@"๐Ÿ”„ Switching to Screen %ld overlay", targetScreenIndex);
918
+ g_activeScreenIndex = targetScreenIndex;
919
+ g_overlayWindow = targetOverlay;
920
+ g_overlayView = targetOverlayView;
921
+ }
922
+
923
+ // Update TARGET overlay view window info for highlighting
924
+ [targetOverlayView setWindowInfo:targetWindow];
925
+ [targetOverlayView setHighlightFrame:NSMakeRect(localX, localY, width, height)];
907
926
 
908
927
  // Only reset toggle state when switching to different window (not for position updates)
909
928
  if (!g_hasToggledWindow) {
@@ -1123,7 +1142,27 @@ void cleanupWindowSelector() {
1123
1142
  g_windowKeyEventMonitor = nil;
1124
1143
  }
1125
1144
 
1126
- // Close overlay window
1145
+ // NEW PER-SCREEN CLEANUP: Close all per-screen overlays
1146
+ if (g_perScreenOverlays) {
1147
+ NSLog(@"๐Ÿงน Cleaning up %lu per-screen overlays", [g_perScreenOverlays count]);
1148
+ for (NSWindow *overlay in g_perScreenOverlays) {
1149
+ if (overlay) {
1150
+ [overlay close];
1151
+ }
1152
+ }
1153
+ [g_perScreenOverlays release];
1154
+ g_perScreenOverlays = nil;
1155
+ }
1156
+
1157
+ if (g_perScreenOverlayViews) {
1158
+ [g_perScreenOverlayViews release];
1159
+ g_perScreenOverlayViews = nil;
1160
+ }
1161
+
1162
+ // Reset per-screen state
1163
+ g_activeScreenIndex = -1;
1164
+
1165
+ // Close legacy overlay window (fallback cleanup)
1127
1166
  if (g_overlayWindow) {
1128
1167
  [g_overlayWindow close];
1129
1168
  g_overlayWindow = nil;
@@ -1836,32 +1875,55 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1836
1875
  }
1837
1876
 
1838
1877
  // Get all windows
1839
- g_allWindows = [getAllSelectableWindows() retain];
1878
+ g_allWindows = [[NSMutableArray alloc] initWithArray:getAllSelectableWindows()];
1840
1879
 
1841
1880
  if (!g_allWindows || [g_allWindows count] == 0) {
1842
1881
  Napi::Error::New(env, "No selectable windows found").ThrowAsJavaScriptException();
1843
1882
  return env.Null();
1844
1883
  }
1845
1884
 
1846
- // Create multi-display overlay window to prevent window dragging
1885
+ // Create PER-SCREEN overlay windows (much simpler than combined overlay)
1847
1886
  NSArray *allScreens = [NSScreen screens];
1848
- NSRect combinedFrame = NSZeroRect;
1887
+ g_perScreenOverlays = [[NSMutableArray alloc] init];
1888
+ g_perScreenOverlayViews = [[NSMutableArray alloc] init];
1889
+
1890
+ NSLog(@"๐Ÿ–ฅ๏ธ Creating per-screen overlays for %lu displays", [allScreens count]);
1849
1891
 
1850
- // Calculate combined frame that covers all displays
1851
- for (NSScreen *screen in allScreens) {
1892
+ // Create overlay for each screen
1893
+ for (NSInteger i = 0; i < [allScreens count]; i++) {
1894
+ NSScreen *screen = [allScreens objectAtIndex:i];
1852
1895
  NSRect screenFrame = [screen frame];
1853
- if (NSEqualRects(combinedFrame, NSZeroRect)) {
1854
- combinedFrame = screenFrame;
1855
- } else {
1856
- combinedFrame = NSUnionRect(combinedFrame, screenFrame);
1857
- }
1896
+
1897
+ // Create per-screen overlay window
1898
+ NSWindow *screenOverlay = [[NoFocusWindow alloc] initWithContentRect:screenFrame
1899
+ styleMask:NSWindowStyleMaskBorderless
1900
+ backing:NSBackingStoreBuffered
1901
+ defer:NO
1902
+ screen:screen];
1903
+
1904
+ // Create per-screen overlay view
1905
+ WindowSelectorOverlayView *overlayView = [[WindowSelectorOverlayView alloc] initWithFrame:NSMakeRect(0, 0, screenFrame.size.width, screenFrame.size.height)];
1906
+ [screenOverlay setContentView:overlayView];
1907
+
1908
+ // Configure overlay window
1909
+ [screenOverlay setStyleMask:NSWindowStyleMaskBorderless];
1910
+ [screenOverlay setBackgroundColor:[NSColor clearColor]];
1911
+ [screenOverlay setOpaque:NO];
1912
+ [screenOverlay setIgnoresMouseEvents:NO];
1913
+ [screenOverlay setAcceptsMouseMovedEvents:YES];
1914
+ [screenOverlay setLevel:NSScreenSaverWindowLevel];
1915
+ [screenOverlay setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorStationary];
1916
+
1917
+ NSLog(@" Screen %ld: (%.0f,%.0f) %.0fx%.0f โ†’ Overlay created", i, screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
1918
+
1919
+ [g_perScreenOverlays addObject:screenOverlay];
1920
+ [g_perScreenOverlayViews addObject:overlayView];
1858
1921
  }
1859
1922
 
1860
- NSRect fullScreenFrame = combinedFrame;
1861
- g_overlayWindow = [[NoFocusWindow alloc] initWithContentRect:fullScreenFrame
1862
- styleMask:NSWindowStyleMaskBorderless
1863
- backing:NSBackingStoreBuffered
1864
- defer:NO];
1923
+ // For compatibility, keep first screen as main overlay
1924
+ g_overlayWindow = [g_perScreenOverlays objectAtIndex:0];
1925
+ g_overlayView = [g_perScreenOverlayViews objectAtIndex:0];
1926
+ g_activeScreenIndex = 0;
1865
1927
 
1866
1928
  // Force completely borderless appearance
1867
1929
  [g_overlayWindow setStyleMask:NSWindowStyleMaskBorderless];
@@ -1886,17 +1948,7 @@ Napi::Value StartWindowSelection(const Napi::CallbackInfo& info) {
1886
1948
  [g_overlayWindow setOpaque:NO];
1887
1949
  [g_overlayWindow setBackgroundColor:[NSColor clearColor]];
1888
1950
 
1889
- // Create overlay view covering all displays
1890
- // Convert global coordinates to local view coordinates (offset by origin)
1891
- NSRect localViewFrame = NSMakeRect(0, 0, fullScreenFrame.size.width, fullScreenFrame.size.height);
1892
- g_overlayView = [[WindowSelectorOverlayView alloc] initWithFrame:localViewFrame];
1893
-
1894
- // Store the global origin offset for coordinate conversion
1895
- WindowSelectorOverlayView *overlayView = (WindowSelectorOverlayView *)g_overlayView;
1896
- overlayView.globalOriginOffset = [NSValue valueWithPoint:fullScreenFrame.origin];
1897
-
1898
- NSLog(@"๐Ÿ”ง SET GlobalOriginOffset: (%.0f, %.0f)", fullScreenFrame.origin.x, fullScreenFrame.origin.y);
1899
- [g_overlayWindow setContentView:g_overlayView];
1951
+ NSLog(@"โœ… Per-screen overlay system initialized with %lu screens", [allScreens count]);
1900
1952
 
1901
1953
  // Note: NSWindow doesn't have setWantsLayer method, only NSView does
1902
1954