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.
- package/cursor-debug-test.js +60 -0
- package/cursor-macbook-test.js +63 -0
- package/cursor-permission-test.js +46 -0
- package/cursor-scaling-debug.js +53 -0
- package/cursor-simple-test.js +26 -0
- package/index.js +32 -4
- package/package.json +1 -1
- package/src/cursor_tracker.mm +121 -48
|
@@ -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
|
-
//
|
|
700
|
-
//
|
|
701
|
-
|
|
702
|
-
|
|
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
package/src/cursor_tracker.mm
CHANGED
|
@@ -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
|
-
|
|
213
|
-
|
|
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
|
-
|
|
295
|
-
|
|
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 (
|
|
496
|
-
// Get
|
|
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
|
|
531
|
+
CGSize reportedPhysicalSize = CGSizeMake(CGDisplayPixelsWide(displayID), CGDisplayPixelsHigh(displayID));
|
|
499
532
|
|
|
500
|
-
|
|
501
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
524
|
-
@"scaleFactor": @(
|
|
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
|
-
//
|
|
553
|
-
//
|
|
554
|
-
|
|
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
|
|
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;
|