node-mac-recorder 2.16.31 β 2.16.32
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-permission-test.js +46 -0
- package/cursor-scaling-debug.js +53 -0
- package/index.js +32 -4
- package/package.json +1 -1
- package/src/cursor_tracker.mm +42 -39
|
@@ -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,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);
|
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,16 +473,34 @@ 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 (
|
|
490
|
+
if (isInBounds) {
|
|
496
491
|
// Get display scaling info
|
|
497
492
|
CGSize logicalSize = displayBounds.size;
|
|
498
493
|
CGSize physicalSize = CGSizeMake(CGDisplayPixelsWide(displayID), CGDisplayPixelsHigh(displayID));
|
|
499
494
|
|
|
495
|
+
NSLog(@"π Scaling info: logical(%.0fx%.0f) physical(%.0fx%.0f)",
|
|
496
|
+
logicalSize.width, logicalSize.height, physicalSize.width, physicalSize.height);
|
|
497
|
+
|
|
500
498
|
CGFloat scaleX = physicalSize.width / logicalSize.width;
|
|
501
499
|
CGFloat scaleY = physicalSize.height / logicalSize.height;
|
|
502
500
|
CGFloat scaleFactor = MAX(scaleX, scaleY);
|
|
503
501
|
|
|
502
|
+
NSLog(@"π Scale factors: X=%.2f, Y=%.2f, Final=%.2f", scaleX, scaleY, scaleFactor);
|
|
503
|
+
|
|
504
504
|
return @{
|
|
505
505
|
@"displayID": @(displayID),
|
|
506
506
|
@"logicalSize": [NSValue valueWithSize:NSMakeSize(logicalSize.width, logicalSize.height)],
|
|
@@ -549,21 +549,9 @@ Napi::Value GetCursorPosition(const Napi::CallbackInfo& info) {
|
|
|
549
549
|
CGFloat scaleFactor = [[scalingInfo objectForKey:@"scaleFactor"] doubleValue];
|
|
550
550
|
NSRect displayBounds = [[scalingInfo objectForKey:@"displayBounds"] rectValue];
|
|
551
551
|
|
|
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
|
-
}
|
|
552
|
+
// CGEventGetLocation returns LOGICAL coordinates (correct for JS layer)
|
|
553
|
+
// Keep logical coordinates - transformation happens in JS layer
|
|
554
|
+
logicalLocation = rawLocation;
|
|
567
555
|
}
|
|
568
556
|
|
|
569
557
|
NSString *cursorType = getCursorType();
|
|
@@ -602,12 +590,27 @@ Napi::Value GetCursorPosition(const Napi::CallbackInfo& info) {
|
|
|
602
590
|
result.Set("cursorType", Napi::String::New(env, [cursorType UTF8String]));
|
|
603
591
|
result.Set("eventType", Napi::String::New(env, [eventType UTF8String]));
|
|
604
592
|
|
|
605
|
-
// Add scaling
|
|
593
|
+
// Add scaling info for coordinate transformation
|
|
606
594
|
if (scalingInfo) {
|
|
607
595
|
CGFloat scaleFactor = [[scalingInfo objectForKey:@"scaleFactor"] doubleValue];
|
|
596
|
+
NSSize logicalSize = [[scalingInfo objectForKey:@"logicalSize"] sizeValue];
|
|
597
|
+
NSSize physicalSize = [[scalingInfo objectForKey:@"physicalSize"] sizeValue];
|
|
598
|
+
NSRect displayBounds = [[scalingInfo objectForKey:@"displayBounds"] rectValue];
|
|
599
|
+
|
|
608
600
|
result.Set("scaleFactor", Napi::Number::New(env, scaleFactor));
|
|
609
601
|
result.Set("rawX", Napi::Number::New(env, (int)rawLocation.x));
|
|
610
602
|
result.Set("rawY", Napi::Number::New(env, (int)rawLocation.y));
|
|
603
|
+
|
|
604
|
+
// Add display dimension info for JS coordinate transformation
|
|
605
|
+
Napi::Object displayInfo = Napi::Object::New(env);
|
|
606
|
+
displayInfo.Set("logicalWidth", Napi::Number::New(env, logicalSize.width));
|
|
607
|
+
displayInfo.Set("logicalHeight", Napi::Number::New(env, logicalSize.height));
|
|
608
|
+
displayInfo.Set("physicalWidth", Napi::Number::New(env, physicalSize.width));
|
|
609
|
+
displayInfo.Set("physicalHeight", Napi::Number::New(env, physicalSize.height));
|
|
610
|
+
displayInfo.Set("displayX", Napi::Number::New(env, displayBounds.origin.x));
|
|
611
|
+
displayInfo.Set("displayY", Napi::Number::New(env, displayBounds.origin.y));
|
|
612
|
+
|
|
613
|
+
result.Set("displayInfo", displayInfo);
|
|
611
614
|
}
|
|
612
615
|
|
|
613
616
|
return result;
|