node-mac-recorder 2.16.30 โ†’ 2.16.31

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,9 +1,7 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(FORCE_AVFOUNDATION=1 node test-sequential.js)",
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\")"
4
+ "Bash(node:*)"
7
5
  ],
8
6
  "deny": [],
9
7
  "ask": []
@@ -0,0 +1,73 @@
1
+ const MacRecorder = require('./index.js');
2
+
3
+ console.log('๐Ÿงช Testing cursor DPR scaling fixes...\n');
4
+
5
+ const recorder = new MacRecorder();
6
+
7
+ // Test basic cursor position
8
+ console.log('1. Testing basic cursor position:');
9
+ try {
10
+ const position = recorder.getCursorPosition();
11
+ console.log(` Position: (${position.x}, ${position.y})`);
12
+
13
+ // If we have scaling debug info, show it
14
+ if (position.scaleFactor) {
15
+ console.log(` Scale factor: ${position.scaleFactor}x`);
16
+ console.log(` Raw position: (${position.rawX}, ${position.rawY})`);
17
+ console.log(` Logical position: (${position.x}, ${position.y})`);
18
+ }
19
+ } catch (error) {
20
+ console.error(' Error:', error.message);
21
+ }
22
+
23
+ console.log('\n2. Testing display information:');
24
+ recorder.getDisplays().then(displays => {
25
+ displays.forEach((display, index) => {
26
+ console.log(` Display ${index}: ${display.resolution} at (${display.x}, ${display.y})`);
27
+ console.log(` Primary: ${display.isPrimary}, ID: ${display.id}`);
28
+ });
29
+
30
+ console.log('\n3. Testing cursor capture with DPR fix:');
31
+ const outputFile = 'cursor-dpr-test.json';
32
+
33
+ recorder.startCursorCapture(outputFile, 50).then(() => {
34
+ console.log(` โœ… Cursor capture started, saving to ${outputFile}`);
35
+ console.log(' Move your mouse around for 5 seconds...');
36
+
37
+ setTimeout(() => {
38
+ recorder.stopCursorCapture().then(() => {
39
+ console.log(' โœ… Cursor capture stopped');
40
+
41
+ // Read and analyze the captured data
42
+ const fs = require('fs');
43
+ try {
44
+ const data = JSON.parse(fs.readFileSync(outputFile, 'utf8'));
45
+ if (data.length > 0) {
46
+ const first = data[0];
47
+ const last = data[data.length - 1];
48
+
49
+ console.log(` ๐Ÿ“Š Captured ${data.length} cursor events`);
50
+ console.log(` ๐Ÿ“ First: (${first.x}, ${first.y}) ${first.coordinateSystem || 'unknown'}`);
51
+ console.log(` ๐Ÿ“ Last: (${last.x}, ${last.y}) ${last.coordinateSystem || 'unknown'}`);
52
+
53
+ // Check coordinate system
54
+ const hasCoordinateSystem = data.some(d => d.coordinateSystem);
55
+ console.log(` ๐ŸŽฏ Coordinate system info: ${hasCoordinateSystem ? 'Present' : 'Missing'}`);
56
+ } else {
57
+ console.log(' โš ๏ธ No cursor events captured');
58
+ }
59
+ } catch (readError) {
60
+ console.error(' โŒ Error reading capture file:', readError.message);
61
+ }
62
+
63
+ process.exit(0);
64
+ });
65
+ }, 5000);
66
+ }).catch(error => {
67
+ console.error(' โŒ Cursor capture failed:', error.message);
68
+ process.exit(1);
69
+ });
70
+ }).catch(error => {
71
+ console.error(' Error getting displays:', error.message);
72
+ process.exit(1);
73
+ });
@@ -0,0 +1 @@
1
+ []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.16.30",
3
+ "version": "2.16.31",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -20,6 +20,7 @@ static bool g_isFirstWrite = true;
20
20
  // Forward declaration
21
21
  void cursorTimerCallback();
22
22
  void writeToFile(NSDictionary *cursorData);
23
+ NSDictionary* getDisplayScalingInfo(CGPoint globalPoint);
23
24
 
24
25
  // Timer helper class
25
26
  @interface CursorTimerTarget : NSObject
@@ -198,7 +199,28 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
198
199
  return event;
199
200
  }
200
201
 
201
- CGPoint location = CGEventGetLocation(event);
202
+ CGPoint rawLocation = CGEventGetLocation(event);
203
+
204
+ // Apply DPR scaling correction for Retina displays
205
+ NSDictionary *scalingInfo = getDisplayScalingInfo(rawLocation);
206
+ CGPoint location = rawLocation;
207
+
208
+ if (scalingInfo) {
209
+ CGFloat scaleFactor = [[scalingInfo objectForKey:@"scaleFactor"] doubleValue];
210
+ NSRect displayBounds = [[scalingInfo objectForKey:@"displayBounds"] rectValue];
211
+
212
+ if (scaleFactor > 1.1) {
213
+ // Convert to logical coordinates
214
+ CGFloat displayRelativeX = rawLocation.x - displayBounds.origin.x;
215
+ CGFloat displayRelativeY = rawLocation.y - displayBounds.origin.y;
216
+
217
+ CGFloat logicalRelativeX = displayRelativeX / scaleFactor;
218
+ CGFloat logicalRelativeY = displayRelativeY / scaleFactor;
219
+
220
+ location.x = displayBounds.origin.x + logicalRelativeX;
221
+ location.y = displayBounds.origin.y + logicalRelativeY;
222
+ }
223
+ }
202
224
  NSDate *currentDate = [NSDate date];
203
225
  NSTimeInterval timestamp = [currentDate timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
204
226
  NSTimeInterval unixTimeMs = [currentDate timeIntervalSince1970] * 1000; // unix timestamp in milliseconds
@@ -254,13 +276,34 @@ void cursorTimerCallback() {
254
276
  return;
255
277
  }
256
278
 
257
- // CGEventGetLocation direkt global koordinat verir - รงoklu ekran desteฤŸi iรงin daha doฤŸru
279
+ // Get cursor position with DPR scaling correction
258
280
  CGEventRef event = CGEventCreate(NULL);
259
- CGPoint location = CGEventGetLocation(event);
281
+ CGPoint rawLocation = CGEventGetLocation(event);
260
282
  if (event) {
261
283
  CFRelease(event);
262
284
  }
263
285
 
286
+ // Apply DPR scaling correction for Retina displays
287
+ NSDictionary *scalingInfo = getDisplayScalingInfo(rawLocation);
288
+ CGPoint location = rawLocation;
289
+
290
+ if (scalingInfo) {
291
+ CGFloat scaleFactor = [[scalingInfo objectForKey:@"scaleFactor"] doubleValue];
292
+ NSRect displayBounds = [[scalingInfo objectForKey:@"displayBounds"] rectValue];
293
+
294
+ if (scaleFactor > 1.1) {
295
+ // Convert to logical coordinates
296
+ CGFloat displayRelativeX = rawLocation.x - displayBounds.origin.x;
297
+ CGFloat displayRelativeY = rawLocation.y - displayBounds.origin.y;
298
+
299
+ CGFloat logicalRelativeX = displayRelativeX / scaleFactor;
300
+ CGFloat logicalRelativeY = displayRelativeY / scaleFactor;
301
+
302
+ location.x = displayBounds.origin.x + logicalRelativeX;
303
+ location.y = displayBounds.origin.y + logicalRelativeY;
304
+ }
305
+ }
306
+
264
307
  NSDate *currentDate = [NSDate date];
265
308
  NSTimeInterval timestamp = [currentDate timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
266
309
  NSTimeInterval unixTimeMs = [currentDate timeIntervalSince1970] * 1000; // unix timestamp in milliseconds
@@ -435,19 +478,94 @@ Napi::Value StopCursorTracking(const Napi::CallbackInfo& info) {
435
478
  }
436
479
  }
437
480
 
481
+ // Helper function to get display scaling info for cursor coordinates
482
+ NSDictionary* getDisplayScalingInfo(CGPoint globalPoint) {
483
+ @try {
484
+ // Get all displays
485
+ uint32_t displayCount;
486
+ CGDirectDisplayID displayIDs[32];
487
+ CGGetActiveDisplayList(32, displayIDs, &displayCount);
488
+
489
+ // Find which display contains this point
490
+ for (uint32_t i = 0; i < displayCount; i++) {
491
+ CGDirectDisplayID displayID = displayIDs[i];
492
+ CGRect displayBounds = CGDisplayBounds(displayID);
493
+
494
+ // Check if point is within this display
495
+ if (CGRectContainsPoint(displayBounds, globalPoint)) {
496
+ // Get display scaling info
497
+ CGSize logicalSize = displayBounds.size;
498
+ CGSize physicalSize = CGSizeMake(CGDisplayPixelsWide(displayID), CGDisplayPixelsHigh(displayID));
499
+
500
+ CGFloat scaleX = physicalSize.width / logicalSize.width;
501
+ CGFloat scaleY = physicalSize.height / logicalSize.height;
502
+ CGFloat scaleFactor = MAX(scaleX, scaleY);
503
+
504
+ return @{
505
+ @"displayID": @(displayID),
506
+ @"logicalSize": [NSValue valueWithSize:NSMakeSize(logicalSize.width, logicalSize.height)],
507
+ @"physicalSize": [NSValue valueWithSize:NSMakeSize(physicalSize.width, physicalSize.height)],
508
+ @"scaleFactor": @(scaleFactor),
509
+ @"displayBounds": [NSValue valueWithRect:NSMakeRect(displayBounds.origin.x, displayBounds.origin.y, displayBounds.size.width, displayBounds.size.height)]
510
+ };
511
+ }
512
+ }
513
+
514
+ // Fallback to main display
515
+ CGDirectDisplayID mainDisplay = CGMainDisplayID();
516
+ CGRect displayBounds = CGDisplayBounds(mainDisplay);
517
+ CGSize logicalSize = displayBounds.size;
518
+ CGSize physicalSize = CGSizeMake(CGDisplayPixelsWide(mainDisplay), CGDisplayPixelsHigh(mainDisplay));
519
+
520
+ return @{
521
+ @"displayID": @(mainDisplay),
522
+ @"logicalSize": [NSValue valueWithSize:NSMakeSize(logicalSize.width, logicalSize.height)],
523
+ @"physicalSize": [NSValue valueWithSize:NSMakeSize(physicalSize.width, physicalSize.height)],
524
+ @"scaleFactor": @(MAX(physicalSize.width / logicalSize.width, physicalSize.height / logicalSize.height)),
525
+ @"displayBounds": [NSValue valueWithRect:NSMakeRect(displayBounds.origin.x, displayBounds.origin.y, displayBounds.size.width, displayBounds.size.height)]
526
+ };
527
+ } @catch (NSException *exception) {
528
+ return nil;
529
+ }
530
+ }
531
+
438
532
  // NAPI Function: Get Current Cursor Position
439
533
  Napi::Value GetCursorPosition(const Napi::CallbackInfo& info) {
440
534
  Napi::Env env = info.Env();
441
535
 
442
536
  @try {
443
- // NSEvent mouseLocation zaten global koordinatlarda (all displays combined)
444
- // CGEventGetLocation kullanarak direkt global koordinat al - daha doฤŸru
537
+ // Get raw cursor position (may be scaled on Retina displays)
445
538
  CGEventRef event = CGEventCreate(NULL);
446
- CGPoint location = CGEventGetLocation(event);
539
+ CGPoint rawLocation = CGEventGetLocation(event);
447
540
  if (event) {
448
541
  CFRelease(event);
449
542
  }
450
543
 
544
+ // Get display scaling information
545
+ NSDictionary *scalingInfo = getDisplayScalingInfo(rawLocation);
546
+ CGPoint logicalLocation = rawLocation;
547
+
548
+ if (scalingInfo) {
549
+ CGFloat scaleFactor = [[scalingInfo objectForKey:@"scaleFactor"] doubleValue];
550
+ NSRect displayBounds = [[scalingInfo objectForKey:@"displayBounds"] rectValue];
551
+
552
+ // CRITICAL FIX: Convert physical coordinates to logical coordinates
553
+ // CGEventGetLocation returns physical coordinates on Retina displays
554
+ if (scaleFactor > 1.1) {
555
+ // Convert to display-relative, then scale down to logical, then back to global logical
556
+ CGFloat displayRelativeX = rawLocation.x - displayBounds.origin.x;
557
+ CGFloat displayRelativeY = rawLocation.y - displayBounds.origin.y;
558
+
559
+ // Scale down to logical coordinates
560
+ CGFloat logicalRelativeX = displayRelativeX / scaleFactor;
561
+ CGFloat logicalRelativeY = displayRelativeY / scaleFactor;
562
+
563
+ // Convert back to global logical coordinates
564
+ logicalLocation.x = displayBounds.origin.x + logicalRelativeX;
565
+ logicalLocation.y = displayBounds.origin.y + logicalRelativeY;
566
+ }
567
+ }
568
+
451
569
  NSString *cursorType = getCursorType();
452
570
 
453
571
  // Mouse button state'ini kontrol et
@@ -479,11 +597,19 @@ Napi::Value GetCursorPosition(const Napi::CallbackInfo& info) {
479
597
  g_rightMouseDown = currentRightMouseDown;
480
598
 
481
599
  Napi::Object result = Napi::Object::New(env);
482
- result.Set("x", Napi::Number::New(env, (int)location.x));
483
- result.Set("y", Napi::Number::New(env, (int)location.y));
600
+ result.Set("x", Napi::Number::New(env, (int)logicalLocation.x));
601
+ result.Set("y", Napi::Number::New(env, (int)logicalLocation.y));
484
602
  result.Set("cursorType", Napi::String::New(env, [cursorType UTF8String]));
485
603
  result.Set("eventType", Napi::String::New(env, [eventType UTF8String]));
486
604
 
605
+ // Add scaling debug info
606
+ if (scalingInfo) {
607
+ CGFloat scaleFactor = [[scalingInfo objectForKey:@"scaleFactor"] doubleValue];
608
+ result.Set("scaleFactor", Napi::Number::New(env, scaleFactor));
609
+ result.Set("rawX", Napi::Number::New(env, (int)rawLocation.x));
610
+ result.Set("rawY", Napi::Number::New(env, (int)rawLocation.y));
611
+ }
612
+
487
613
  return result;
488
614
 
489
615
  } @catch (NSException *exception) {