node-mac-recorder 2.17.14 โ 2.17.16
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/index.js +20 -3
- package/package.json +1 -1
- package/src/cursor_tracker.mm +59 -41
- package/src/screen_capture_kit.mm +34 -1
- package/test-cursor-fix.js +105 -0
- package/test-output/cursor-test-window-1758264693010.json +1 -0
package/index.js
CHANGED
|
@@ -830,9 +830,26 @@ class MacRecorder extends EventEmitter {
|
|
|
830
830
|
|
|
831
831
|
if (this.cursorDisplayInfo) {
|
|
832
832
|
if (this.cursorDisplayInfo.windowRelative) {
|
|
833
|
-
// Window recording: Transform
|
|
834
|
-
|
|
835
|
-
|
|
833
|
+
// Window recording: Transform cursor coordinates relative to window's display first
|
|
834
|
+
let adjustedX = position.x;
|
|
835
|
+
let adjustedY = position.y;
|
|
836
|
+
|
|
837
|
+
// If we have target display info, first convert global coords to display-relative
|
|
838
|
+
if (this.cursorDisplayInfo.targetDisplay) {
|
|
839
|
+
adjustedX = position.x - this.cursorDisplayInfo.targetDisplay.x;
|
|
840
|
+
adjustedY = position.y - this.cursorDisplayInfo.targetDisplay.y;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Then convert display-relative coords to window-relative
|
|
844
|
+
if (this.cursorDisplayInfo.windowInfo && this.cursorDisplayInfo.windowInfo.displayOffsetX !== undefined) {
|
|
845
|
+
x = adjustedX - this.cursorDisplayInfo.windowInfo.displayOffsetX;
|
|
846
|
+
y = adjustedY - this.cursorDisplayInfo.windowInfo.displayOffsetY;
|
|
847
|
+
} else {
|
|
848
|
+
// Fallback: direct window offset from global coordinates
|
|
849
|
+
x = position.x - this.cursorDisplayInfo.x;
|
|
850
|
+
y = position.y - this.cursorDisplayInfo.y;
|
|
851
|
+
}
|
|
852
|
+
|
|
836
853
|
coordinateSystem = "window-relative";
|
|
837
854
|
|
|
838
855
|
// Window bounds check - skip if cursor is outside window
|
package/package.json
CHANGED
package/src/cursor_tracker.mm
CHANGED
|
@@ -43,9 +43,9 @@ static NSTimeInterval g_lastCursorCheckTime = 0;
|
|
|
43
43
|
static int g_sameCursorDetectionCount = 0;
|
|
44
44
|
static NSString *g_pendingCursorType = nil;
|
|
45
45
|
|
|
46
|
-
// Cursor stability constants -
|
|
47
|
-
static const NSTimeInterval CURSOR_STABILITY_THRESHOLD = 0.
|
|
48
|
-
static const int CURSOR_CONFIRMATION_COUNT = 1; //
|
|
46
|
+
// Cursor stability constants - minimal stability for real-time response
|
|
47
|
+
static const NSTimeInterval CURSOR_STABILITY_THRESHOLD = 0.02; // 20ms (very fast)
|
|
48
|
+
static const int CURSOR_CONFIRMATION_COUNT = 1; // Immediate response
|
|
49
49
|
|
|
50
50
|
// Mouse button state tracking
|
|
51
51
|
static bool g_leftMouseDown = false;
|
|
@@ -261,9 +261,18 @@ NSString* getCursorType() {
|
|
|
261
261
|
else if ([elementRole isEqualToString:@"AXProgressIndicator"]) {
|
|
262
262
|
contextualCursorType = @"progress";
|
|
263
263
|
}
|
|
264
|
-
//
|
|
264
|
+
// DEFAULT AREAS - explicitly set default for known non-interactive elements
|
|
265
|
+
else if ([elementRole isEqualToString:@"AXApplication"] ||
|
|
266
|
+
[elementRole isEqualToString:@"AXWindow"] ||
|
|
267
|
+
[elementRole isEqualToString:@"AXScrollArea"] ||
|
|
268
|
+
[elementRole isEqualToString:@"AXScrollBar"] ||
|
|
269
|
+
[elementRole isEqualToString:@"AXStaticText"] ||
|
|
270
|
+
[elementRole isEqualToString:@"AXGroup"]) {
|
|
271
|
+
// These are typically non-interactive - should be default cursor
|
|
272
|
+
contextualCursorType = @"default";
|
|
273
|
+
}
|
|
274
|
+
// OTHER ELEMENTS - let NSCursor handle or default
|
|
265
275
|
else {
|
|
266
|
-
// Don't override NSCursor for unknown elements
|
|
267
276
|
contextualCursorType = nil;
|
|
268
277
|
}
|
|
269
278
|
}
|
|
@@ -271,59 +280,68 @@ NSString* getCursorType() {
|
|
|
271
280
|
}
|
|
272
281
|
if (systemWide) CFRelease(systemWide);
|
|
273
282
|
|
|
274
|
-
// Layer 3:
|
|
283
|
+
// Layer 3: Contextual-first approach (NSCursor is unreliable)
|
|
275
284
|
NSString *detectedCursorType = @"default";
|
|
276
285
|
|
|
277
|
-
// Priority logic
|
|
278
|
-
// 1. If contextual gives
|
|
279
|
-
if (contextualCursorType != nil
|
|
280
|
-
([contextualCursorType hasSuffix:@"resize"] ||
|
|
281
|
-
[contextualCursorType isEqualToString:@"col-resize"] ||
|
|
282
|
-
[contextualCursorType isEqualToString:@"ns-resize"])) {
|
|
286
|
+
// Priority logic - contextual detection first:
|
|
287
|
+
// 1. If contextual gives ANY specific cursor, use it
|
|
288
|
+
if (contextualCursorType != nil) {
|
|
283
289
|
detectedCursorType = contextualCursorType;
|
|
284
290
|
}
|
|
285
|
-
// 2.
|
|
286
|
-
else if (nsCursorType != nil
|
|
291
|
+
// 2. Fallback to NSCursor only for very specific cases
|
|
292
|
+
else if (nsCursorType != nil &&
|
|
293
|
+
([nsCursorType isEqualToString:@"text"] ||
|
|
294
|
+
[nsCursorType isEqualToString:@"pointer"])) {
|
|
295
|
+
// Only trust NSCursor for text and pointer (most reliable)
|
|
287
296
|
detectedCursorType = nsCursorType;
|
|
288
297
|
}
|
|
289
|
-
// 3.
|
|
290
|
-
else if (contextualCursorType != nil &&
|
|
291
|
-
![contextualCursorType isEqualToString:@"default"]) {
|
|
292
|
-
detectedCursorType = contextualCursorType;
|
|
293
|
-
}
|
|
294
|
-
// 4. If both are nil or default, use true default
|
|
298
|
+
// 3. Everything else defaults to default
|
|
295
299
|
else {
|
|
296
300
|
detectedCursorType = @"default";
|
|
297
301
|
}
|
|
298
302
|
|
|
299
|
-
// Layer 4:
|
|
303
|
+
// Layer 4: Minimal stability - mostly real-time with quick default fallback
|
|
304
|
+
NSString *finalCursorType = detectedCursorType;
|
|
300
305
|
|
|
301
|
-
//
|
|
306
|
+
// Quick validation and immediate response for most cases
|
|
302
307
|
if (currentTime - g_lastCursorCheckTime > CURSOR_STABILITY_THRESHOLD) {
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
308
|
+
// Update immediately for most cursor types
|
|
309
|
+
if ([detectedCursorType isEqualToString:@"default"] ||
|
|
310
|
+
[detectedCursorType isEqualToString:@"text"] ||
|
|
311
|
+
[detectedCursorType isEqualToString:@"pointer"]) {
|
|
312
|
+
// High-confidence cursor types - update immediately
|
|
313
|
+
g_stableCursorType = detectedCursorType;
|
|
314
|
+
finalCursorType = detectedCursorType;
|
|
315
|
+
}
|
|
316
|
+
// Resize cursors need tiny bit of stability to avoid jitter
|
|
317
|
+
else if ([detectedCursorType hasSuffix:@"resize"]) {
|
|
318
|
+
if ([detectedCursorType isEqualToString:g_pendingCursorType]) {
|
|
319
|
+
g_sameCursorDetectionCount++;
|
|
320
|
+
if (g_sameCursorDetectionCount >= CURSOR_CONFIRMATION_COUNT) {
|
|
321
|
+
g_stableCursorType = detectedCursorType;
|
|
322
|
+
finalCursorType = detectedCursorType;
|
|
323
|
+
} else {
|
|
324
|
+
finalCursorType = g_stableCursorType; // Keep previous
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
g_pendingCursorType = detectedCursorType;
|
|
328
|
+
g_sameCursorDetectionCount = 1;
|
|
329
|
+
finalCursorType = g_stableCursorType; // Keep previous
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// Everything else - immediate update
|
|
333
|
+
else {
|
|
314
334
|
g_stableCursorType = detectedCursorType;
|
|
315
|
-
|
|
335
|
+
finalCursorType = detectedCursorType;
|
|
316
336
|
}
|
|
337
|
+
|
|
338
|
+
g_lastCursorCheckTime = currentTime;
|
|
317
339
|
} else {
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
g_sameCursorDetectionCount = 1;
|
|
340
|
+
// Too soon - use stable cursor
|
|
341
|
+
finalCursorType = g_stableCursorType;
|
|
321
342
|
}
|
|
322
343
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
// Final validation
|
|
326
|
-
NSString *finalCursorType = g_stableCursorType;
|
|
344
|
+
// Final fallback validation
|
|
327
345
|
if (!finalCursorType || [finalCursorType length] == 0) {
|
|
328
346
|
finalCursorType = @"default";
|
|
329
347
|
}
|
|
@@ -119,8 +119,41 @@ static NSString *g_outputPath = nil;
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
if (targetWindow && targetApp) {
|
|
122
|
-
NSLog(@"๐ช Recording window: %@ (%ux%u)",
|
|
122
|
+
NSLog(@"๐ช Recording window: %@ (%ux%u)",
|
|
123
123
|
targetWindow.title, (unsigned)targetWindow.frame.size.width, (unsigned)targetWindow.frame.size.height);
|
|
124
|
+
|
|
125
|
+
// Find which display contains this window for proper cursor coordinate transformation
|
|
126
|
+
CGRect windowFrame = targetWindow.frame;
|
|
127
|
+
NSLog(@"๐ Window frame: (%.0f,%.0f,%.0fx%.0f)",
|
|
128
|
+
windowFrame.origin.x, windowFrame.origin.y,
|
|
129
|
+
windowFrame.size.width, windowFrame.size.height);
|
|
130
|
+
|
|
131
|
+
// Find the display that contains the window's center point
|
|
132
|
+
CGPoint windowCenter = CGPointMake(
|
|
133
|
+
windowFrame.origin.x + windowFrame.size.width / 2,
|
|
134
|
+
windowFrame.origin.y + windowFrame.size.height / 2
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
for (SCDisplay *display in content.displays) {
|
|
138
|
+
CGRect displayFrame = display.frame;
|
|
139
|
+
NSLog(@"๐ Checking if window center (%.0f,%.0f) is in display ID=%u frame (%.0f,%.0f,%.0fx%.0f)",
|
|
140
|
+
windowCenter.x, windowCenter.y, display.displayID,
|
|
141
|
+
displayFrame.origin.x, displayFrame.origin.y,
|
|
142
|
+
displayFrame.size.width, displayFrame.size.height);
|
|
143
|
+
|
|
144
|
+
if (CGRectContainsPoint(displayFrame, windowCenter)) {
|
|
145
|
+
targetDisplay = display;
|
|
146
|
+
NSLog(@"โ
Window is on display ID=%u", display.displayID);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!targetDisplay) {
|
|
152
|
+
// Fallback: use main display if no display contains the window center
|
|
153
|
+
targetDisplay = content.displays.firstObject;
|
|
154
|
+
NSLog(@"โ ๏ธ Window not contained in any display, using main display ID=%u as fallback", targetDisplay.displayID);
|
|
155
|
+
}
|
|
156
|
+
|
|
124
157
|
filter = [[SCContentFilter alloc] initWithDesktopIndependentWindow:targetWindow];
|
|
125
158
|
recordingWidth = (NSInteger)targetWindow.frame.size.width;
|
|
126
159
|
recordingHeight = (NSInteger)targetWindow.frame.size.height;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const MacRecorder = require('./index.js');
|
|
2
|
+
|
|
3
|
+
async function testCursorCoordinates() {
|
|
4
|
+
console.log('๐งช Testing cursor coordinate fixes for window recording...');
|
|
5
|
+
|
|
6
|
+
const recorder = new MacRecorder();
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
// Get available windows and displays
|
|
10
|
+
const windows = await recorder.getWindows();
|
|
11
|
+
const displays = await recorder.getDisplays();
|
|
12
|
+
|
|
13
|
+
console.log('\n๐ฑ Available displays:');
|
|
14
|
+
displays.forEach((display, i) => {
|
|
15
|
+
console.log(` ${i + 1}. Display ${display.id}: ${display.resolution} at (${display.x}, ${display.y}) ${display.isPrimary ? '(Primary)' : ''}`);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
console.log('\n๐ช Available windows:');
|
|
19
|
+
windows.slice(0, 5).forEach((window, i) => {
|
|
20
|
+
console.log(` ${i + 1}. ${window.title} (${window.app}) - ${window.width}x${window.height} at (${window.x}, ${window.y})`);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (windows.length === 0) {
|
|
24
|
+
console.log('โ No windows available for testing');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Pick the first available window for testing
|
|
29
|
+
const testWindow = windows[0];
|
|
30
|
+
console.log(`\n๐ฏ Testing with window: ${testWindow.title}`);
|
|
31
|
+
console.log(` Window position: (${testWindow.x}, ${testWindow.y})`);
|
|
32
|
+
console.log(` Window size: ${testWindow.width}x${testWindow.height}`);
|
|
33
|
+
|
|
34
|
+
// Check current cursor position before recording
|
|
35
|
+
const cursorPos = recorder.getCursorPosition();
|
|
36
|
+
console.log(`\n๐ฑ๏ธ Current cursor position (global): (${cursorPos.x}, ${cursorPos.y})`);
|
|
37
|
+
|
|
38
|
+
// Setup recording options for window recording
|
|
39
|
+
recorder.options.windowId = testWindow.id;
|
|
40
|
+
recorder.options.captureCursor = true;
|
|
41
|
+
|
|
42
|
+
console.log('\n๐ง Starting cursor capture test for window recording...');
|
|
43
|
+
|
|
44
|
+
// Start cursor capture with window-relative coordinates
|
|
45
|
+
const outputFile = `./test-output/cursor-test-window-${Date.now()}.json`;
|
|
46
|
+
|
|
47
|
+
// Start cursor tracking first to see if coordinate transformation works
|
|
48
|
+
await recorder.startCursorCapture(outputFile, {
|
|
49
|
+
windowRelative: true,
|
|
50
|
+
windowInfo: {
|
|
51
|
+
x: testWindow.x,
|
|
52
|
+
y: testWindow.y,
|
|
53
|
+
width: testWindow.width,
|
|
54
|
+
height: testWindow.height,
|
|
55
|
+
displayId: null // Let it auto-detect
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
console.log('โ
Cursor capture started successfully!');
|
|
60
|
+
console.log('๐ฑ๏ธ Move your mouse around the window for 5 seconds...');
|
|
61
|
+
console.log(` Window bounds: (0, 0) to (${testWindow.width}, ${testWindow.height})`);
|
|
62
|
+
|
|
63
|
+
// Wait for 5 seconds to capture cursor movements
|
|
64
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
65
|
+
|
|
66
|
+
console.log('\n๐ Stopping cursor capture...');
|
|
67
|
+
await recorder.stopCursorCapture();
|
|
68
|
+
|
|
69
|
+
console.log(`โ
Test completed! Check cursor data in: ${outputFile}`);
|
|
70
|
+
|
|
71
|
+
// Read and analyze some cursor data
|
|
72
|
+
const fs = require('fs');
|
|
73
|
+
if (fs.existsSync(outputFile)) {
|
|
74
|
+
const cursorData = JSON.parse(fs.readFileSync(outputFile, 'utf8'));
|
|
75
|
+
console.log(`\n๐ Captured ${cursorData.length} cursor events`);
|
|
76
|
+
|
|
77
|
+
if (cursorData.length > 0) {
|
|
78
|
+
const first = cursorData[0];
|
|
79
|
+
const last = cursorData[cursorData.length - 1];
|
|
80
|
+
|
|
81
|
+
console.log(` First event: (${first.x}, ${first.y}) - ${first.coordinateSystem}`);
|
|
82
|
+
console.log(` Last event: (${last.x}, ${last.y}) - ${last.coordinateSystem}`);
|
|
83
|
+
|
|
84
|
+
// Check if coordinates are within window bounds
|
|
85
|
+
const inBounds = cursorData.filter(event =>
|
|
86
|
+
event.x >= 0 && event.x < testWindow.width &&
|
|
87
|
+
event.y >= 0 && event.y < testWindow.height
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
console.log(` Events within window bounds: ${inBounds.length}/${cursorData.length} (${Math.round(inBounds.length/cursorData.length*100)}%)`);
|
|
91
|
+
|
|
92
|
+
if (inBounds.length > 0) {
|
|
93
|
+
console.log('โ
Cursor coordinates appear to be correctly transformed to window-relative!');
|
|
94
|
+
} else {
|
|
95
|
+
console.log('โ No cursor coordinates are within window bounds - transformation may be incorrect');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('โ Test failed:', error.message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
testCursorCoordinates().catch(console.error);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"x":758,"y":1089,"timestamp":37,"unixTimeMs":1758264693047,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":758,"y":1089,"timestamp":80,"unixTimeMs":1758264693090,"cursorType":"default","type":"mousedown","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":758,"y":1089,"timestamp":99,"unixTimeMs":1758264693109,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":758,"y":1089,"timestamp":180,"unixTimeMs":1758264693190,"cursorType":"default","type":"mouseup","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":758,"y":1089,"timestamp":201,"unixTimeMs":1758264693211,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":774,"y":1071,"timestamp":1610,"unixTimeMs":1758264694620,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":818,"y":1030,"timestamp":1631,"unixTimeMs":1758264694641,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":893,"y":972,"timestamp":1654,"unixTimeMs":1758264694664,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1015,"y":886,"timestamp":1677,"unixTimeMs":1758264694687,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1087,"y":843,"timestamp":1696,"unixTimeMs":1758264694706,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1233,"y":793,"timestamp":1714,"unixTimeMs":1758264694724,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1301,"y":776,"timestamp":1733,"unixTimeMs":1758264694743,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1360,"y":765,"timestamp":1755,"unixTimeMs":1758264694765,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1366,"y":764,"timestamp":1774,"unixTimeMs":1758264694784,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1363,"y":751,"timestamp":1814,"unixTimeMs":1758264694824,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1343,"y":709,"timestamp":1838,"unixTimeMs":1758264694848,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1335,"y":691,"timestamp":1855,"unixTimeMs":1758264694865,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1271,"y":583,"timestamp":1878,"unixTimeMs":1758264694888,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1250,"y":558,"timestamp":1898,"unixTimeMs":1758264694908,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1242,"y":552,"timestamp":1917,"unixTimeMs":1758264694927,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1238,"y":552,"timestamp":1937,"unixTimeMs":1758264694947,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1234,"y":554,"timestamp":1959,"unixTimeMs":1758264694969,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1233,"y":556,"timestamp":2207,"unixTimeMs":1758264695217,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1232,"y":559,"timestamp":2231,"unixTimeMs":1758264695241,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1232,"y":561,"timestamp":2252,"unixTimeMs":1758264695262,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1231,"y":565,"timestamp":2272,"unixTimeMs":1758264695282,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1231,"y":567,"timestamp":2290,"unixTimeMs":1758264695300,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1231,"y":595,"timestamp":2311,"unixTimeMs":1758264695321,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1236,"y":629,"timestamp":2330,"unixTimeMs":1758264695341,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1242,"y":647,"timestamp":2352,"unixTimeMs":1758264695362,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1251,"y":670,"timestamp":2369,"unixTimeMs":1758264695379,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1259,"y":702,"timestamp":2391,"unixTimeMs":1758264695401,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1265,"y":746,"timestamp":2409,"unixTimeMs":1758264695419,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1276,"y":763,"timestamp":2430,"unixTimeMs":1758264695440,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1272,"y":760,"timestamp":2985,"unixTimeMs":1758264695995,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1270,"y":758,"timestamp":3005,"unixTimeMs":1758264696015,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1269,"y":756,"timestamp":3047,"unixTimeMs":1758264696057,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1268,"y":754,"timestamp":3089,"unixTimeMs":1758264696099,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1268,"y":752,"timestamp":3132,"unixTimeMs":1758264696142,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1267,"y":750,"timestamp":3173,"unixTimeMs":1758264696183,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1266,"y":747,"timestamp":3215,"unixTimeMs":1758264696225,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1265,"y":745,"timestamp":3238,"unixTimeMs":1758264696248,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1263,"y":742,"timestamp":3278,"unixTimeMs":1758264696288,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1264,"y":740,"timestamp":3482,"unixTimeMs":1758264696492,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1266,"y":739,"timestamp":3522,"unixTimeMs":1758264696532,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1268,"y":738,"timestamp":3541,"unixTimeMs":1758264696551,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1277,"y":731,"timestamp":3563,"unixTimeMs":1758264696573,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1279,"y":730,"timestamp":3583,"unixTimeMs":1758264696593,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1281,"y":729,"timestamp":3688,"unixTimeMs":1758264696698,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1283,"y":727,"timestamp":3708,"unixTimeMs":1758264696718,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1286,"y":718,"timestamp":3873,"unixTimeMs":1758264696883,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1287,"y":713,"timestamp":3893,"unixTimeMs":1758264696903,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1289,"y":713,"timestamp":4204,"unixTimeMs":1758264697214,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}},{"x":1291,"y":713,"timestamp":4330,"unixTimeMs":1758264697340,"cursorType":"default","type":"move","coordinateSystem":"window-relative","windowInfo":{"width":3440,"height":1440,"displayId":null}}]
|