node-mac-recorder 2.16.27 → 2.16.29

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.
@@ -2,7 +2,8 @@
2
2
  "permissions": {
3
3
  "allow": [
4
4
  "Bash(FORCE_AVFOUNDATION=1 node test-sequential.js)",
5
- "Bash(grep:*)"
5
+ "Bash(grep:*)",
6
+ "Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''🧪 Testing macOS 14/13 coordinate scaling fix...'');\nconst MacRecorder = require(''./index.js'');\nconst recorder = new MacRecorder();\n\nrecorder.startRecording(''/tmp/coordinate-fix-test.mov'')\n .then(success => {\n console.log(''Start result:'', success ? ''✅ SUCCESS'' : ''❌ FAILED'');\n if (success) {\n setTimeout(() => {\n recorder.stopRecording().then(() => {\n console.log(''✅ Recording stopped'');\n const fs = require(''fs'');\n if (fs.existsSync(''/tmp/coordinate-fix-test.mov'')) {\n const size = Math.round(fs.statSync(''/tmp/coordinate-fix-test.mov'').size/1024);\n console.log(''📹 File size:'', size + ''KB'');\n if (size > 100) {\n console.log(''🎉 macOS 14/13 coordinate fix test SUCCESS!'');\n } else {\n console.log(''⚠️ File too small, may have issues'');\n }\n } else {\n console.log(''❌ No output file created'');\n }\n });\n }, 3000); // 3 seconds recording\n }\n })\n .catch(console.error);\n\")"
6
7
  ],
7
8
  "deny": [],
8
9
  "ask": []
package/index.js CHANGED
@@ -212,14 +212,12 @@ class MacRecorder extends EventEmitter {
212
212
  targetWindow.y < display.y + displayHeight
213
213
  ) {
214
214
  targetDisplayId = display.id; // Use actual display ID, not array index
215
- // Koordinatları display'e göre normalize et
215
+ // CRITICAL FIX: Convert global coordinates to display-relative coordinates
216
+ // AVFoundation expects simple display-relative top-left coordinates (no flipping)
216
217
  adjustedX = targetWindow.x - display.x;
218
+ adjustedY = targetWindow.y - display.y;
217
219
 
218
- // Y coordinate conversion: CGWindow (top-left) to AVFoundation (bottom-left)
219
- // Overlay'deki dönüşümle aynı mantık: screenHeight - windowY - windowHeight
220
- const displayHeight = parseInt(display.resolution.split("x")[1]);
221
- const convertedY = displayHeight - targetWindow.y - targetWindow.height;
222
- adjustedY = Math.max(0, convertedY - display.y);
220
+ console.log(`🔧 macOS 14/13 coordinate fix: Global (${targetWindow.x},${targetWindow.y}) -> Display-relative (${adjustedX},${adjustedY})`);
223
221
  break;
224
222
  }
225
223
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.16.27",
3
+ "version": "2.16.29",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -71,8 +71,8 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
71
71
  CGSize actualImageSize = CGSizeMake(CGImageGetWidth(testImage), CGImageGetHeight(testImage));
72
72
  CGImageRelease(testImage);
73
73
 
74
- // For AVFoundation, use actual image size that CGDisplayCreateImage returns
75
- CGSize recordingSize = captureRect.size.width > 0 ? captureRect.size : actualImageSize;
74
+ // CRITICAL FIX: Use logical size for consistency, actual image size only for validation
75
+ CGSize recordingSize = captureRect.size.width > 0 ? captureRect.size : logicalSize;
76
76
 
77
77
  NSLog(@"🎯 CRITICAL: Logical %.0fx%.0f → Actual image %.0fx%.0f",
78
78
  logicalSize.width, logicalSize.height, actualImageSize.width, actualImageSize.height);
@@ -154,32 +154,25 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
154
154
  // Store recording parameters with scaling correction
155
155
  g_avDisplayID = displayID;
156
156
 
157
- // Apply scaling to capture rect if provided (for macOS 14/13 compatibility)
157
+ // CRITICAL FIX: Use coordinates as-is from JS layer (already display-relative)
158
158
  if (!CGRectIsEmpty(captureRect)) {
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;
159
+ // Use coordinates directly - JS layer already converted global to display-relative
160
+ g_avCaptureRect = captureRect;
162
161
 
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);
162
+ NSLog(@"🔲 macOS 14/13 FIXED: Display-relative capture area: %.0f,%.0f %.0fx%.0f",
163
+ captureRect.origin.x, captureRect.origin.y, captureRect.size.width, captureRect.size.height);
164
+
165
+ // Validate coordinates are within logical display bounds
166
+ if (captureRect.origin.x >= 0 && captureRect.origin.y >= 0 &&
167
+ captureRect.origin.x + captureRect.size.width <= logicalSize.width &&
168
+ captureRect.origin.y + captureRect.size.height <= logicalSize.height) {
169
+ NSLog(@"✅ Coordinates validated within logical display bounds %.0fx%.0f", logicalSize.width, logicalSize.height);
175
170
  } 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);
171
+ NSLog(@"⚠️ Coordinates may be outside logical display bounds - clipping may occur");
179
172
  }
180
173
  } else {
181
174
  g_avCaptureRect = CGRectZero; // Full screen
182
- NSLog(@"🖥️ Full screen capture (actual image dimensions)");
175
+ NSLog(@"🖥️ Full screen capture using logical dimensions %.0fx%.0f", logicalSize.width, logicalSize.height);
183
176
  }
184
177
 
185
178
  g_avFrameNumber = 0;
@@ -243,8 +236,8 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
243
236
  NSLog(@"🔍 Debug: Buffer %zux%zu, Image %zux%zu", bufferWidth, bufferHeight, imageWidth, imageHeight);
244
237
 
245
238
  if (bufferWidth != imageWidth || bufferHeight != 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");
239
+ NSLog(@"🔧 EXPECTED SIZE DIFFERENCE: Buffer %zux%zu (logical) vs Image %zux%zu (physical)", bufferWidth, bufferHeight, imageWidth, imageHeight);
240
+ NSLog(@" This is normal on Retina displays - scaling handled correctly now");
248
241
  }
249
242
 
250
243
  CVPixelBufferLockBaseAddress(pixelBuffer, 0);
@@ -282,7 +275,9 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
282
275
  8, bytesPerRow, colorSpace, bitmapInfo);
283
276
 
284
277
  if (context) {
285
- CGContextDrawImage(context, CGRectMake(0, 0, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer)), screenImage);
278
+ // CRITICAL FIX: Draw image at correct size - scale physical image to logical buffer size
279
+ CGSize bufferSize = CGSizeMake(CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer));
280
+ CGContextDrawImage(context, CGRectMake(0, 0, bufferSize.width, bufferSize.height), screenImage);
286
281
  CGContextRelease(context);
287
282
 
288
283
  // Write frame only if input is ready
@@ -114,8 +114,8 @@ Napi::Value StartRecordingElectronSafe(const Napi::CallbackInfo& info) {
114
114
  double width = areaObj.Get("width").As<Napi::Number>().DoubleValue();
115
115
  double height = areaObj.Get("height").As<Napi::Number>().DoubleValue();
116
116
 
117
- // Validate bounds
118
- if (width > 0 && height > 0 && x >= 0 && y >= 0) {
117
+ // Validate bounds - allow negative coordinates for external displays
118
+ if (width > 0 && height > 0) {
119
119
  mutableOptions[@"captureArea"] = @{
120
120
  @"x": @(x),
121
121
  @"y": @(y),
@@ -103,6 +103,7 @@ static NSString *g_outputPath = nil;
103
103
  SCContentFilter *filter = nil;
104
104
  NSInteger recordingWidth = 0;
105
105
  NSInteger recordingHeight = 0;
106
+ SCDisplay *targetDisplay = nil; // Move to shared scope
106
107
 
107
108
  // WINDOW RECORDING
108
109
  if (windowId && [windowId integerValue] != 0) {
@@ -130,7 +131,6 @@ static NSString *g_outputPath = nil;
130
131
  }
131
132
  // DISPLAY RECORDING
132
133
  else {
133
- SCDisplay *targetDisplay = nil;
134
134
 
135
135
  if (displayId && [displayId integerValue] != 0) {
136
136
  // Find specific display
@@ -187,17 +187,40 @@ static NSString *g_outputPath = nil;
187
187
  streamConfig.pixelFormat = kCVPixelFormatType_32BGRA;
188
188
  streamConfig.scalesToFit = NO;
189
189
 
190
- // Apply crop area using sourceRect
190
+ // Apply crop area using sourceRect - CONVERT GLOBAL TO DISPLAY-RELATIVE COORDINATES
191
191
  if (captureRect && captureRect[@"x"] && captureRect[@"y"] && captureRect[@"width"] && captureRect[@"height"]) {
192
- CGFloat cropX = [captureRect[@"x"] doubleValue];
193
- CGFloat cropY = [captureRect[@"y"] doubleValue];
192
+ CGFloat globalX = [captureRect[@"x"] doubleValue];
193
+ CGFloat globalY = [captureRect[@"y"] doubleValue];
194
194
  CGFloat cropWidth = [captureRect[@"width"] doubleValue];
195
195
  CGFloat cropHeight = [captureRect[@"height"] doubleValue];
196
196
 
197
- if (cropWidth > 0 && cropHeight > 0) {
198
- CGRect sourceRect = CGRectMake(cropX, cropY, cropWidth, cropHeight);
199
- streamConfig.sourceRect = sourceRect;
200
- NSLog(@"✂️ Crop sourceRect applied: (%.0f,%.0f) %.0fx%.0f", cropX, cropY, cropWidth, cropHeight);
197
+ if (cropWidth > 0 && cropHeight > 0 && targetDisplay) {
198
+ // Convert global coordinates to display-relative coordinates
199
+ CGRect displayBounds = targetDisplay.frame;
200
+ CGFloat displayRelativeX = globalX - displayBounds.origin.x;
201
+ CGFloat displayRelativeY = globalY - displayBounds.origin.y;
202
+
203
+ NSLog(@"🌐 Global coords: (%.0f,%.0f) on Display ID=%u", globalX, globalY, targetDisplay.displayID);
204
+ NSLog(@"🖥️ Display bounds: (%.0f,%.0f,%.0fx%.0f)",
205
+ displayBounds.origin.x, displayBounds.origin.y,
206
+ displayBounds.size.width, displayBounds.size.height);
207
+ NSLog(@"📍 Display-relative: (%.0f,%.0f) -> SourceRect", displayRelativeX, displayRelativeY);
208
+
209
+ // Validate coordinates are within display bounds
210
+ if (displayRelativeX >= 0 && displayRelativeY >= 0 &&
211
+ displayRelativeX + cropWidth <= displayBounds.size.width &&
212
+ displayRelativeY + cropHeight <= displayBounds.size.height) {
213
+
214
+ CGRect sourceRect = CGRectMake(displayRelativeX, displayRelativeY, cropWidth, cropHeight);
215
+ streamConfig.sourceRect = sourceRect;
216
+ NSLog(@"✂️ Crop sourceRect applied: (%.0f,%.0f) %.0fx%.0f (display-relative)",
217
+ displayRelativeX, displayRelativeY, cropWidth, cropHeight);
218
+ } else {
219
+ NSLog(@"❌ Crop coordinates out of display bounds - skipping crop");
220
+ NSLog(@" Relative: (%.0f,%.0f) size:(%.0fx%.0f) vs display:(%.0fx%.0f)",
221
+ displayRelativeX, displayRelativeY, cropWidth, cropHeight,
222
+ displayBounds.size.width, displayBounds.size.height);
223
+ }
201
224
  }
202
225
  }
203
226