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
|
-
//
|
|
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
|
-
|
|
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
|
@@ -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
|
-
//
|
|
75
|
-
CGSize recordingSize = captureRect.size.width > 0 ? captureRect.size :
|
|
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
|
-
//
|
|
157
|
+
// CRITICAL FIX: Use coordinates as-is from JS layer (already display-relative)
|
|
158
158
|
if (!CGRectIsEmpty(captureRect)) {
|
|
159
|
-
//
|
|
160
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
|
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(@"
|
|
247
|
-
NSLog(@" This
|
|
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
|
-
|
|
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
|
|
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
|
|
193
|
-
CGFloat
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|