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.
- package/.claude/settings.local.json +1 -3
- package/cursor-dpr-test.js +73 -0
- package/cursor-dpr-test.json +1 -0
- package/package.json +1 -1
- package/src/cursor_tracker.mm +134 -8
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"Bash(
|
|
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
package/src/cursor_tracker.mm
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
279
|
+
// Get cursor position with DPR scaling correction
|
|
258
280
|
CGEventRef event = CGEventCreate(NULL);
|
|
259
|
-
CGPoint
|
|
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
|
-
//
|
|
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
|
|
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)
|
|
483
|
-
result.Set("y", Napi::Number::New(env, (int)
|
|
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) {
|