node-mac-recorder 2.16.31 → 2.16.33

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.
@@ -0,0 +1,60 @@
1
+ const MacRecorder = require('./index.js');
2
+
3
+ console.log('šŸ” Debugging cursor coordinate scaling issue...\n');
4
+
5
+ const recorder = new MacRecorder();
6
+
7
+ async function testCursorScaling() {
8
+ console.log('Getting display info...');
9
+ const displays = await recorder.getDisplays();
10
+
11
+ displays.forEach((display, index) => {
12
+ console.log(`Display ${index}:`);
13
+ console.log(` Resolution: ${display.resolution}`);
14
+ console.log(` Position: (${display.x}, ${display.y})`);
15
+ console.log(` Primary: ${display.isPrimary}`);
16
+ console.log(` ID: ${display.id}`);
17
+ });
18
+
19
+ console.log('\nšŸŽÆ Please move your mouse to different corners and press Enter...');
20
+ console.log('This will help us understand the coordinate mapping issue.\n');
21
+
22
+ const readline = require('readline');
23
+ const rl = readline.createInterface({
24
+ input: process.stdin,
25
+ output: process.stdout
26
+ });
27
+
28
+ let testCount = 0;
29
+ const testPositions = [
30
+ 'Top-Left corner of the display',
31
+ 'Top-Right corner of the display',
32
+ 'Bottom-Left corner of the display',
33
+ 'Bottom-Right corner of the display',
34
+ 'Center of the display'
35
+ ];
36
+
37
+ function nextTest() {
38
+ if (testCount >= testPositions.length) {
39
+ console.log('\nāœ… Test completed!');
40
+ rl.close();
41
+ process.exit(0);
42
+ return;
43
+ }
44
+
45
+ console.log(`\nšŸ“ Test ${testCount + 1}: Move mouse to ${testPositions[testCount]} and press Enter:`);
46
+ rl.question('', () => {
47
+ const position = recorder.getCursorPosition();
48
+ console.log(` Raw cursor position: (${position.rawX || 'N/A'}, ${position.rawY || 'N/A'})`);
49
+ console.log(` Logical cursor position: (${position.x}, ${position.y})`);
50
+ console.log(` Scale factor: ${position.scaleFactor || 'N/A'}x`);
51
+
52
+ testCount++;
53
+ nextTest();
54
+ });
55
+ }
56
+
57
+ nextTest();
58
+ }
59
+
60
+ testCursorScaling().catch(console.error);
@@ -0,0 +1,63 @@
1
+ const MacRecorder = require('./index.js');
2
+
3
+ async function testMacBookCursor() {
4
+ console.log('šŸ–„ļø Testing cursor on MacBook internal display...\n');
5
+
6
+ const recorder = new MacRecorder();
7
+
8
+ // Get displays
9
+ const displays = await recorder.getDisplays();
10
+ console.log('šŸ“ŗ Available displays:');
11
+ displays.forEach((display, index) => {
12
+ console.log(` Display ${index}: ${display.resolution} at (${display.x}, ${display.y}) - Primary: ${display.isPrimary}`);
13
+ });
14
+
15
+ console.log('\nšŸŽÆ Move your mouse to the PRIMARY MacBook display and press any key...');
16
+ console.log('(Make sure cursor is on the built-in MacBook screen, not external monitor)\n');
17
+
18
+ // Wait for keypress
19
+ await new Promise((resolve) => {
20
+ process.stdin.setRawMode(true);
21
+ process.stdin.resume();
22
+ process.stdin.once('data', () => {
23
+ process.stdin.setRawMode(false);
24
+ resolve();
25
+ });
26
+ });
27
+
28
+ console.log('šŸ” Testing cursor position on MacBook display:');
29
+ for (let i = 0; i < 3; i++) {
30
+ const position = recorder.getCursorPosition();
31
+ console.log(`\n Test ${i+1}:`);
32
+ console.log(` Cursor: (${position.x}, ${position.y})`);
33
+ console.log(` Cursor type: ${position.cursorType}`);
34
+
35
+ // Check which display cursor is on
36
+ const primaryDisplay = displays.find(d => d.isPrimary);
37
+ if (primaryDisplay) {
38
+ const isOnPrimary = position.x >= primaryDisplay.x &&
39
+ position.x < primaryDisplay.x + parseInt(primaryDisplay.resolution.split('x')[0]) &&
40
+ position.y >= primaryDisplay.y &&
41
+ position.y < primaryDisplay.y + parseInt(primaryDisplay.resolution.split('x')[1]);
42
+
43
+ console.log(` On primary display: ${isOnPrimary ? 'āœ… YES' : 'āŒ NO'}`);
44
+ }
45
+
46
+ if (position.scaleFactor) {
47
+ console.log(` Scale factor: ${position.scaleFactor}x`);
48
+ if (position.displayInfo) {
49
+ console.log(` Display logical: ${position.displayInfo.logicalWidth}x${position.displayInfo.logicalHeight}`);
50
+ console.log(` Display physical: ${position.displayInfo.physicalWidth}x${position.displayInfo.physicalHeight}`);
51
+ console.log(` Raw cursor: (${position.rawX}, ${position.rawY})`);
52
+ }
53
+ } else {
54
+ console.log(` āš ļø No scaling info detected`);
55
+ }
56
+
57
+ await new Promise(resolve => setTimeout(resolve, 1000));
58
+ }
59
+
60
+ process.exit(0);
61
+ }
62
+
63
+ testMacBookCursor().catch(console.error);
@@ -0,0 +1,46 @@
1
+ const MacRecorder = require('./index.js');
2
+
3
+ async function testCursorPermissions() {
4
+ console.log('šŸ”’ Testing cursor tracking permissions...\n');
5
+
6
+ const recorder = new MacRecorder();
7
+
8
+ // Check permissions first
9
+ console.log('1. Checking permissions:');
10
+ const permissions = await recorder.checkPermissions();
11
+ console.log(' Screen Recording:', permissions.screenRecording ? 'āœ…' : 'āŒ');
12
+ console.log(' Accessibility:', permissions.accessibility ? 'āœ…' : 'āŒ');
13
+ console.log(' Microphone:', permissions.microphone ? 'āœ…' : 'āŒ');
14
+
15
+ if (permissions.error) {
16
+ console.log(' Error:', permissions.error);
17
+ }
18
+
19
+ console.log('\n2. Testing direct cursor position (no capture):');
20
+ for (let i = 0; i < 5; i++) {
21
+ try {
22
+ const position = recorder.getCursorPosition();
23
+ console.log(` Position ${i+1}: (${position.x}, ${position.y}) - ${position.cursorType}`);
24
+
25
+ if (position.scaleFactor) {
26
+ console.log(` Scale: ${position.scaleFactor}x, Display: (${position.displayInfo?.displayX}, ${position.displayInfo?.displayY})`);
27
+ console.log(` Logical: ${position.displayInfo?.logicalWidth}x${position.displayInfo?.logicalHeight}`);
28
+ console.log(` Physical: ${position.displayInfo?.physicalWidth}x${position.displayInfo?.physicalHeight}`);
29
+ }
30
+
31
+ await new Promise(resolve => setTimeout(resolve, 500));
32
+ } catch (error) {
33
+ console.error(` Error getting position ${i+1}:`, error.message);
34
+ }
35
+ }
36
+
37
+ console.log('\n3. Testing cursor capture status:');
38
+ const status = recorder.getCursorCaptureStatus();
39
+ console.log(' Is Capturing:', status.isCapturing);
40
+ console.log(' Output File:', status.outputFile);
41
+ console.log(' Display Info:', status.displayInfo);
42
+
43
+ process.exit(0);
44
+ }
45
+
46
+ testCursorPermissions().catch(console.error);
@@ -0,0 +1,53 @@
1
+ const MacRecorder = require('./index.js');
2
+
3
+ async function debugCursorScaling() {
4
+ console.log('šŸ” Debugging cursor scaling detection...\n');
5
+
6
+ const recorder = new MacRecorder();
7
+
8
+ // Get all displays
9
+ const displays = await recorder.getDisplays();
10
+ console.log('šŸ“ŗ Available displays:');
11
+ displays.forEach((display, index) => {
12
+ console.log(` Display ${index}: ID=${display.id}, ${display.resolution} at (${display.x}, ${display.y}), Primary: ${display.isPrimary}`);
13
+ });
14
+
15
+ console.log('\nšŸŽÆ Current cursor position analysis:');
16
+ const position = recorder.getCursorPosition();
17
+
18
+ console.log(` Logical position: (${position.x}, ${position.y})`);
19
+ console.log(` Cursor type: ${position.cursorType}`);
20
+ console.log(` Event type: ${position.eventType}`);
21
+
22
+ if (position.scaleFactor) {
23
+ console.log(` Scale factor detected: ${position.scaleFactor}x`);
24
+ if (position.displayInfo) {
25
+ const info = position.displayInfo;
26
+ console.log(` Display bounds: (${info.displayX}, ${info.displayY})`);
27
+ console.log(` Logical size: ${info.logicalWidth}x${info.logicalHeight}`);
28
+ console.log(` Physical size: ${info.physicalWidth}x${info.physicalHeight}`);
29
+ console.log(` Raw position: (${position.rawX}, ${position.rawY})`);
30
+ }
31
+ } else {
32
+ console.log(` āŒ No scale factor detected`);
33
+ console.log(` This could mean:`);
34
+ console.log(` - getDisplayScalingInfo() didn't find the correct display`);
35
+ console.log(` - Display detection logic needs debugging`);
36
+ }
37
+
38
+ // Find which display the cursor is on
39
+ console.log('\nšŸ” Manual display detection:');
40
+ displays.forEach((display, index) => {
41
+ const inX = position.x >= display.x && position.x < display.x + parseInt(display.resolution.split('x')[0]);
42
+ const inY = position.y >= display.y && position.y < display.y + parseInt(display.resolution.split('x')[1]);
43
+ const isInside = inX && inY;
44
+
45
+ console.log(` Display ${index} (${display.resolution}): ${isInside ? 'āœ… CURSOR IS HERE' : 'āŒ'}`);
46
+ console.log(` Bounds: X(${display.x} - ${display.x + parseInt(display.resolution.split('x')[0])}), Y(${display.y} - ${display.y + parseInt(display.resolution.split('x')[1])})`);
47
+ console.log(` Cursor: X(${position.x}), Y(${position.y})`);
48
+ });
49
+
50
+ process.exit(0);
51
+ }
52
+
53
+ debugCursorScaling().catch(console.error);
@@ -0,0 +1,26 @@
1
+ const MacRecorder = require('./index.js');
2
+
3
+ console.log('šŸŽÆ Simple cursor test - Move your mouse to MacBook screen...\n');
4
+
5
+ const recorder = new MacRecorder();
6
+
7
+ // Test 10 cursor positions
8
+ for (let i = 0; i < 10; i++) {
9
+ setTimeout(() => {
10
+ const position = recorder.getCursorPosition();
11
+ console.log(`${i+1}. Cursor: (${position.x}, ${position.y}), Scale: ${position.scaleFactor || 'none'}`);
12
+
13
+ if (position.displayInfo && position.scaleFactor > 1.1) {
14
+ console.log(` šŸŽ‰ SCALING DETECTED! ${position.scaleFactor}x`);
15
+ console.log(` Logical: ${position.displayInfo.logicalWidth}x${position.displayInfo.logicalHeight}`);
16
+ console.log(` Physical: ${position.displayInfo.physicalWidth}x${position.displayInfo.physicalHeight}`);
17
+ console.log(` Raw cursor: (${position.rawX}, ${position.rawY})`);
18
+ process.exit(0);
19
+ }
20
+
21
+ if (i === 9) {
22
+ console.log('\nāš ļø No scaling detected. Try moving mouse to MacBook internal display.');
23
+ process.exit(0);
24
+ }
25
+ }, i * 500);
26
+ }
package/index.js CHANGED
@@ -258,6 +258,9 @@ class MacRecorder extends EventEmitter {
258
258
  y: targetDisplay.y,
259
259
  width: parseInt(targetDisplay.resolution.split("x")[0]),
260
260
  height: parseInt(targetDisplay.resolution.split("x")[1]),
261
+ // Add scaling information for cursor coordinate transformation
262
+ logicalWidth: parseInt(targetDisplay.resolution.split("x")[0]),
263
+ logicalHeight: parseInt(targetDisplay.resolution.split("x")[1]),
261
264
  };
262
265
  }
263
266
 
@@ -292,6 +295,9 @@ class MacRecorder extends EventEmitter {
292
295
  y: targetDisplay.y,
293
296
  width: parseInt(targetDisplay.resolution.split("x")[0]),
294
297
  height: parseInt(targetDisplay.resolution.split("x")[1]),
298
+ // Add scaling information for cursor coordinate transformation
299
+ logicalWidth: parseInt(targetDisplay.resolution.split("x")[0]),
300
+ logicalHeight: parseInt(targetDisplay.resolution.split("x")[1]),
295
301
  };
296
302
  }
297
303
  } catch (error) {
@@ -696,10 +702,32 @@ class MacRecorder extends EventEmitter {
696
702
  let coordinateSystem = "global";
697
703
 
698
704
  if (this.cursorDisplayInfo) {
699
- // Offset'leri Ƨıkar (display veya window)
700
- // Y koordinat dönüşümü başlangıçta yapıldı
701
- x = position.x - this.cursorDisplayInfo.x;
702
- y = position.y - this.cursorDisplayInfo.y;
705
+ // CRITICAL FIX: Handle DPR scaling for cursor coordinates
706
+ // Get scaling information from native cursor position
707
+ const scaleFactor = position.scaleFactor || 1;
708
+ const displayInfo = position.displayInfo;
709
+
710
+ // Convert logical cursor position to physical (if recording uses physical coordinates)
711
+ let physicalX = position.x;
712
+ let physicalY = position.y;
713
+
714
+ if (scaleFactor > 1.1 && displayInfo) {
715
+ // Convert to display-relative logical, then to physical
716
+ const displayRelativeX = position.x - displayInfo.displayX;
717
+ const displayRelativeY = position.y - displayInfo.displayY;
718
+
719
+ // Scale to physical coordinates
720
+ const physicalRelativeX = displayRelativeX * scaleFactor;
721
+ const physicalRelativeY = displayRelativeY * scaleFactor;
722
+
723
+ // Convert back to global physical
724
+ physicalX = displayInfo.displayX + physicalRelativeX;
725
+ physicalY = displayInfo.displayY + physicalRelativeY;
726
+ }
727
+
728
+ // Offset'leri Ƨıkar (display veya window) - use physical coordinates
729
+ x = physicalX - this.cursorDisplayInfo.x;
730
+ y = physicalY - this.cursorDisplayInfo.y;
703
731
 
704
732
  if (this.cursorDisplayInfo.windowRelative) {
705
733
  // Window-relative koordinatlar
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.16.31",
3
+ "version": "2.16.33",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -209,17 +209,8 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
209
209
  CGFloat scaleFactor = [[scalingInfo objectForKey:@"scaleFactor"] doubleValue];
210
210
  NSRect displayBounds = [[scalingInfo objectForKey:@"displayBounds"] rectValue];
211
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
- }
212
+ // Keep logical coordinates - no scaling needed here
213
+ location = rawLocation;
223
214
  }
224
215
  NSDate *currentDate = [NSDate date];
225
216
  NSTimeInterval timestamp = [currentDate timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
@@ -291,17 +282,8 @@ void cursorTimerCallback() {
291
282
  CGFloat scaleFactor = [[scalingInfo objectForKey:@"scaleFactor"] doubleValue];
292
283
  NSRect displayBounds = [[scalingInfo objectForKey:@"displayBounds"] rectValue];
293
284
 
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
- }
285
+ // Keep logical coordinates - no scaling needed here
286
+ location = rawLocation;
305
287
  }
306
288
 
307
289
  NSDate *currentDate = [NSDate date];
@@ -491,37 +473,125 @@ NSDictionary* getDisplayScalingInfo(CGPoint globalPoint) {
491
473
  CGDirectDisplayID displayID = displayIDs[i];
492
474
  CGRect displayBounds = CGDisplayBounds(displayID);
493
475
 
476
+ NSLog(@"šŸ” Display %u: bounds(%.0f,%.0f %.0fx%.0f), cursor(%.0f,%.0f)",
477
+ displayID, displayBounds.origin.x, displayBounds.origin.y,
478
+ displayBounds.size.width, displayBounds.size.height,
479
+ globalPoint.x, globalPoint.y);
480
+
481
+ // CRITICAL FIX: Manual bounds check for better coordinate system compatibility
482
+ BOOL isInBounds = (globalPoint.x >= displayBounds.origin.x &&
483
+ globalPoint.x < displayBounds.origin.x + displayBounds.size.width &&
484
+ globalPoint.y >= displayBounds.origin.y &&
485
+ globalPoint.y < displayBounds.origin.y + displayBounds.size.height);
486
+
487
+ NSLog(@"šŸ” Manual bounds check: %s", isInBounds ? "INSIDE" : "OUTSIDE");
488
+
494
489
  // Check if point is within this display
495
- if (CGRectContainsPoint(displayBounds, globalPoint)) {
496
- // Get display scaling info
490
+ if (isInBounds) {
491
+ // CRITICAL FIX: Get REAL physical dimensions using multiple detection methods
492
+ // Method 1: CGDisplayCreateImage (may be scaled on some systems)
493
+ CGImageRef testImage = CGDisplayCreateImage(displayID);
494
+ CGSize imageSize = CGSizeMake(CGImageGetWidth(testImage), CGImageGetHeight(testImage));
495
+ CGImageRelease(testImage);
496
+
497
+ // Method 2: Native display mode detection for true physical resolution
498
+ CGSize actualPhysicalSize = imageSize;
499
+ CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(displayID, NULL);
500
+ if (displayModes) {
501
+ CFIndex modeCount = CFArrayGetCount(displayModes);
502
+ CGSize maxResolution = CGSizeMake(0, 0);
503
+
504
+ // Find the highest resolution mode (native resolution)
505
+ for (CFIndex i = 0; i < modeCount; i++) {
506
+ CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i);
507
+ CGSize modeSize = CGSizeMake(CGDisplayModeGetWidth(mode), CGDisplayModeGetHeight(mode));
508
+
509
+ if (modeSize.width > maxResolution.width ||
510
+ (modeSize.width == maxResolution.width && modeSize.height > maxResolution.height)) {
511
+ maxResolution = modeSize;
512
+ }
513
+ }
514
+
515
+ // Use the max resolution if it's significantly higher than image size
516
+ if (maxResolution.width > imageSize.width * 1.5 || maxResolution.height > imageSize.height * 1.5) {
517
+ actualPhysicalSize = maxResolution;
518
+ NSLog(@"šŸ” Using display mode detection: %.0fx%.0f (was %.0fx%.0f)",
519
+ maxResolution.width, maxResolution.height, imageSize.width, imageSize.height);
520
+ } else {
521
+ actualPhysicalSize = imageSize;
522
+ NSLog(@"šŸ” Using image size detection: %.0fx%.0f", imageSize.width, imageSize.height);
523
+ }
524
+
525
+ CFRelease(displayModes);
526
+ } else {
527
+ actualPhysicalSize = imageSize;
528
+ }
529
+
497
530
  CGSize logicalSize = displayBounds.size;
498
- CGSize physicalSize = CGSizeMake(CGDisplayPixelsWide(displayID), CGDisplayPixelsHigh(displayID));
531
+ CGSize reportedPhysicalSize = CGSizeMake(CGDisplayPixelsWide(displayID), CGDisplayPixelsHigh(displayID));
499
532
 
500
- CGFloat scaleX = physicalSize.width / logicalSize.width;
501
- CGFloat scaleY = physicalSize.height / logicalSize.height;
533
+ NSLog(@"šŸ” REAL scaling info:");
534
+ NSLog(@" Logical: %.0fx%.0f", logicalSize.width, logicalSize.height);
535
+ NSLog(@" Reported physical: %.0fx%.0f", reportedPhysicalSize.width, reportedPhysicalSize.height);
536
+ NSLog(@" ACTUAL physical: %.0fx%.0f", actualPhysicalSize.width, actualPhysicalSize.height);
537
+
538
+ CGFloat scaleX = actualPhysicalSize.width / logicalSize.width;
539
+ CGFloat scaleY = actualPhysicalSize.height / logicalSize.height;
502
540
  CGFloat scaleFactor = MAX(scaleX, scaleY);
503
541
 
542
+ NSLog(@"šŸ” REAL scale factors: X=%.2f, Y=%.2f, Final=%.2f", scaleX, scaleY, scaleFactor);
543
+
504
544
  return @{
505
545
  @"displayID": @(displayID),
506
546
  @"logicalSize": [NSValue valueWithSize:NSMakeSize(logicalSize.width, logicalSize.height)],
507
- @"physicalSize": [NSValue valueWithSize:NSMakeSize(physicalSize.width, physicalSize.height)],
547
+ @"physicalSize": [NSValue valueWithSize:NSMakeSize(actualPhysicalSize.width, actualPhysicalSize.height)],
508
548
  @"scaleFactor": @(scaleFactor),
509
549
  @"displayBounds": [NSValue valueWithRect:NSMakeRect(displayBounds.origin.x, displayBounds.origin.y, displayBounds.size.width, displayBounds.size.height)]
510
550
  };
511
551
  }
512
552
  }
513
553
 
514
- // Fallback to main display
554
+ // Fallback to main display with REAL physical dimensions
515
555
  CGDirectDisplayID mainDisplay = CGMainDisplayID();
516
556
  CGRect displayBounds = CGDisplayBounds(mainDisplay);
557
+
558
+ // Get REAL physical dimensions using multiple detection methods
559
+ CGImageRef testImage = CGDisplayCreateImage(mainDisplay);
560
+ CGSize imageSize = CGSizeMake(CGImageGetWidth(testImage), CGImageGetHeight(testImage));
561
+ CGImageRelease(testImage);
562
+
563
+ // Try display mode detection for true native resolution
564
+ CGSize actualPhysicalSize = imageSize;
565
+ CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(mainDisplay, NULL);
566
+ if (displayModes) {
567
+ CFIndex modeCount = CFArrayGetCount(displayModes);
568
+ CGSize maxResolution = CGSizeMake(0, 0);
569
+
570
+ for (CFIndex i = 0; i < modeCount; i++) {
571
+ CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i);
572
+ CGSize modeSize = CGSizeMake(CGDisplayModeGetWidth(mode), CGDisplayModeGetHeight(mode));
573
+
574
+ if (modeSize.width > maxResolution.width ||
575
+ (modeSize.width == maxResolution.width && modeSize.height > maxResolution.height)) {
576
+ maxResolution = modeSize;
577
+ }
578
+ }
579
+
580
+ if (maxResolution.width > imageSize.width * 1.5 || maxResolution.height > imageSize.height * 1.5) {
581
+ actualPhysicalSize = maxResolution;
582
+ }
583
+
584
+ CFRelease(displayModes);
585
+ }
586
+
517
587
  CGSize logicalSize = displayBounds.size;
518
- CGSize physicalSize = CGSizeMake(CGDisplayPixelsWide(mainDisplay), CGDisplayPixelsHigh(mainDisplay));
588
+ CGFloat scaleFactor = MAX(actualPhysicalSize.width / logicalSize.width, actualPhysicalSize.height / logicalSize.height);
519
589
 
520
590
  return @{
521
591
  @"displayID": @(mainDisplay),
522
592
  @"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)),
593
+ @"physicalSize": [NSValue valueWithSize:NSMakeSize(actualPhysicalSize.width, actualPhysicalSize.height)],
594
+ @"scaleFactor": @(scaleFactor),
525
595
  @"displayBounds": [NSValue valueWithRect:NSMakeRect(displayBounds.origin.x, displayBounds.origin.y, displayBounds.size.width, displayBounds.size.height)]
526
596
  };
527
597
  } @catch (NSException *exception) {
@@ -549,21 +619,9 @@ Napi::Value GetCursorPosition(const Napi::CallbackInfo& info) {
549
619
  CGFloat scaleFactor = [[scalingInfo objectForKey:@"scaleFactor"] doubleValue];
550
620
  NSRect displayBounds = [[scalingInfo objectForKey:@"displayBounds"] rectValue];
551
621
 
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
- }
622
+ // CGEventGetLocation returns LOGICAL coordinates (correct for JS layer)
623
+ // Keep logical coordinates - transformation happens in JS layer
624
+ logicalLocation = rawLocation;
567
625
  }
568
626
 
569
627
  NSString *cursorType = getCursorType();
@@ -602,12 +660,27 @@ Napi::Value GetCursorPosition(const Napi::CallbackInfo& info) {
602
660
  result.Set("cursorType", Napi::String::New(env, [cursorType UTF8String]));
603
661
  result.Set("eventType", Napi::String::New(env, [eventType UTF8String]));
604
662
 
605
- // Add scaling debug info
663
+ // Add scaling info for coordinate transformation
606
664
  if (scalingInfo) {
607
665
  CGFloat scaleFactor = [[scalingInfo objectForKey:@"scaleFactor"] doubleValue];
666
+ NSSize logicalSize = [[scalingInfo objectForKey:@"logicalSize"] sizeValue];
667
+ NSSize physicalSize = [[scalingInfo objectForKey:@"physicalSize"] sizeValue];
668
+ NSRect displayBounds = [[scalingInfo objectForKey:@"displayBounds"] rectValue];
669
+
608
670
  result.Set("scaleFactor", Napi::Number::New(env, scaleFactor));
609
671
  result.Set("rawX", Napi::Number::New(env, (int)rawLocation.x));
610
672
  result.Set("rawY", Napi::Number::New(env, (int)rawLocation.y));
673
+
674
+ // Add display dimension info for JS coordinate transformation
675
+ Napi::Object displayInfo = Napi::Object::New(env);
676
+ displayInfo.Set("logicalWidth", Napi::Number::New(env, logicalSize.width));
677
+ displayInfo.Set("logicalHeight", Napi::Number::New(env, logicalSize.height));
678
+ displayInfo.Set("physicalWidth", Napi::Number::New(env, physicalSize.width));
679
+ displayInfo.Set("physicalHeight", Napi::Number::New(env, physicalSize.height));
680
+ displayInfo.Set("displayX", Napi::Number::New(env, displayBounds.origin.x));
681
+ displayInfo.Set("displayY", Napi::Number::New(env, displayBounds.origin.y));
682
+
683
+ result.Set("displayInfo", displayInfo);
611
684
  }
612
685
 
613
686
  return result;