node-mac-recorder 2.22.16 → 2.22.18
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/lib/cursorCapture/polling.js +182 -10
- package/package.json +1 -1
|
@@ -3,7 +3,19 @@
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const { resolveCursorDisplayInfo } = require("./displayInfo");
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const IS_ELECTRON = !!(
|
|
7
|
+
process &&
|
|
8
|
+
process.versions &&
|
|
9
|
+
process.versions.electron
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const NATIVE_TEXT_INPUT_SAMPLE_MS = 120;
|
|
13
|
+
const NATIVE_TEXT_INPUT_GRACE_MS = 600;
|
|
14
|
+
|
|
15
|
+
const ELECTRON_SYNTH_GRACE_MS = 400;
|
|
16
|
+
const ELECTRON_SYNTH_THROTTLE_MS = 220;
|
|
17
|
+
|
|
18
|
+
const DEFAULT_CURSOR_INTERVAL_MS = IS_ELECTRON ? 50 : 20;
|
|
7
19
|
|
|
8
20
|
function shouldCaptureCursorSample(lastCapturedData, currentData) {
|
|
9
21
|
if (!lastCapturedData) {
|
|
@@ -68,6 +80,96 @@ function transformInputFrameGlobal(ifr, d) {
|
|
|
68
80
|
};
|
|
69
81
|
}
|
|
70
82
|
|
|
83
|
+
function tryAppendSyntheticTextInputRow(recorder, filepath, cursorData, timestamp) {
|
|
84
|
+
if (!IS_ELECTRON) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const ct = cursorData.cursorType || "";
|
|
88
|
+
if (ct !== "text" && ct !== "vertical-text") {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (timestamp < ELECTRON_SYNTH_GRACE_MS) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const wall = Date.now();
|
|
95
|
+
if (
|
|
96
|
+
wall - (recorder._electronSynthWallMs || 0) <
|
|
97
|
+
ELECTRON_SYNTH_THROTTLE_MS
|
|
98
|
+
) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const vx = cursorData.x;
|
|
102
|
+
const vy = cursorData.y;
|
|
103
|
+
if (!Number.isFinite(vx) || !Number.isFinite(vy)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const tiRow = {
|
|
108
|
+
x: vx,
|
|
109
|
+
y: vy,
|
|
110
|
+
timestamp,
|
|
111
|
+
unixTimeMs: wall,
|
|
112
|
+
cursorType: "text",
|
|
113
|
+
type: "textInput",
|
|
114
|
+
caretX: vx,
|
|
115
|
+
caretY: vy,
|
|
116
|
+
inputFrame: {
|
|
117
|
+
x: vx - 1,
|
|
118
|
+
y: vy - 9,
|
|
119
|
+
width: 2,
|
|
120
|
+
height: 18,
|
|
121
|
+
},
|
|
122
|
+
coordinateSystem: cursorData.coordinateSystem,
|
|
123
|
+
recordingType: cursorData.recordingType,
|
|
124
|
+
videoInfo: cursorData.videoInfo || {},
|
|
125
|
+
displayInfo: cursorData.displayInfo || {},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
if (cursorData.location) {
|
|
129
|
+
tiRow.location = cursorData.location;
|
|
130
|
+
}
|
|
131
|
+
if (cursorData.windowRelative) {
|
|
132
|
+
tiRow.windowRelative = cursorData.windowRelative;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
recorder.cursorCaptureFirstWrite &&
|
|
137
|
+
recorder.cursorCaptureSessionTimestamp
|
|
138
|
+
) {
|
|
139
|
+
tiRow._syncMetadata = {
|
|
140
|
+
videoStartTime: recorder.cursorCaptureSessionTimestamp,
|
|
141
|
+
cursorStartTime: recorder.cursorCaptureStartTime,
|
|
142
|
+
offsetMs:
|
|
143
|
+
recorder.cursorCaptureStartTime -
|
|
144
|
+
recorder.cursorCaptureSessionTimestamp,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const le = recorder._lastTextInputEmitted;
|
|
149
|
+
if (
|
|
150
|
+
le &&
|
|
151
|
+
Math.abs(le.caretX - tiRow.caretX) < 0.75 &&
|
|
152
|
+
Math.abs(le.caretY - tiRow.caretY) < 0.75 &&
|
|
153
|
+
timestamp - le.timestamp < 220
|
|
154
|
+
) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
recorder._lastTextInputEmitted = {
|
|
158
|
+
caretX: tiRow.caretX,
|
|
159
|
+
caretY: tiRow.caretY,
|
|
160
|
+
timestamp,
|
|
161
|
+
};
|
|
162
|
+
recorder._electronSynthWallMs = wall;
|
|
163
|
+
|
|
164
|
+
const jsonString = JSON.stringify(tiRow);
|
|
165
|
+
if (recorder.cursorCaptureFirstWrite) {
|
|
166
|
+
fs.appendFileSync(filepath, jsonString);
|
|
167
|
+
recorder.cursorCaptureFirstWrite = false;
|
|
168
|
+
} else {
|
|
169
|
+
fs.appendFileSync(filepath, "," + jsonString);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
71
173
|
function tryAppendTextInput(
|
|
72
174
|
recorder,
|
|
73
175
|
nativeBinding,
|
|
@@ -75,11 +177,24 @@ function tryAppendTextInput(
|
|
|
75
177
|
position,
|
|
76
178
|
timestamp,
|
|
77
179
|
) {
|
|
180
|
+
if (IS_ELECTRON) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
78
183
|
if (typeof nativeBinding.getTextInputSnapshot !== "function") {
|
|
79
184
|
return;
|
|
80
185
|
}
|
|
186
|
+
if (timestamp < NATIVE_TEXT_INPUT_GRACE_MS) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const ct = position.cursorType || "";
|
|
190
|
+
if (ct !== "text" && ct !== "vertical-text") {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
81
193
|
const wall = Date.now();
|
|
82
|
-
if (
|
|
194
|
+
if (
|
|
195
|
+
wall - (recorder._tiSampleWallMs || 0) <
|
|
196
|
+
NATIVE_TEXT_INPUT_SAMPLE_MS
|
|
197
|
+
) {
|
|
83
198
|
return;
|
|
84
199
|
}
|
|
85
200
|
recorder._tiSampleWallMs = wall;
|
|
@@ -172,9 +287,53 @@ function tryAppendTextInput(
|
|
|
172
287
|
}
|
|
173
288
|
}
|
|
174
289
|
|
|
290
|
+
function queueDeferredTextInputSample(
|
|
291
|
+
recorder,
|
|
292
|
+
nativeBinding,
|
|
293
|
+
filepath,
|
|
294
|
+
position,
|
|
295
|
+
timestamp,
|
|
296
|
+
) {
|
|
297
|
+
if (IS_ELECTRON) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (typeof nativeBinding.getTextInputSnapshot !== "function") {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (timestamp < NATIVE_TEXT_INPUT_GRACE_MS) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const ct = position.cursorType || "";
|
|
307
|
+
if (ct !== "text" && ct !== "vertical-text") {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (recorder._tiDeferredPending) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
recorder._tiDeferredPending = true;
|
|
314
|
+
const pos = {
|
|
315
|
+
x: position.x,
|
|
316
|
+
y: position.y,
|
|
317
|
+
cursorType: position.cursorType,
|
|
318
|
+
eventType: position.eventType,
|
|
319
|
+
};
|
|
320
|
+
const ts = timestamp;
|
|
321
|
+
setImmediate(() => {
|
|
322
|
+
recorder._tiDeferredPending = false;
|
|
323
|
+
if (!recorder.cursorCaptureFile || recorder.cursorCaptureFile !== filepath) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
tryAppendTextInput(recorder, nativeBinding, filepath, pos, ts);
|
|
328
|
+
} catch {
|
|
329
|
+
/* ignore */
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
175
334
|
async function startCursorCapture(recorder, nativeBinding, intervalOrFilepath, options = {}) {
|
|
176
335
|
let filepath;
|
|
177
|
-
let interval =
|
|
336
|
+
let interval = DEFAULT_CURSOR_INTERVAL_MS;
|
|
178
337
|
|
|
179
338
|
if (typeof intervalOrFilepath === "number") {
|
|
180
339
|
interval = Math.max(10, intervalOrFilepath);
|
|
@@ -227,7 +386,9 @@ async function startCursorCapture(recorder, nativeBinding, intervalOrFilepath, o
|
|
|
227
386
|
recorder.lastCapturedData = null;
|
|
228
387
|
recorder.cursorCaptureSessionTimestamp = recorder.sessionTimestamp;
|
|
229
388
|
recorder._tiSampleWallMs = 0;
|
|
389
|
+
recorder._electronSynthWallMs = 0;
|
|
230
390
|
recorder._lastTextInputEmitted = null;
|
|
391
|
+
recorder._tiDeferredPending = false;
|
|
231
392
|
|
|
232
393
|
recorder.cursorCaptureInterval = setInterval(() => {
|
|
233
394
|
try {
|
|
@@ -348,13 +509,22 @@ async function startCursorCapture(recorder, nativeBinding, intervalOrFilepath, o
|
|
|
348
509
|
recorder.lastCapturedData = { ...cursorData };
|
|
349
510
|
}
|
|
350
511
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
512
|
+
if (IS_ELECTRON) {
|
|
513
|
+
tryAppendSyntheticTextInputRow(
|
|
514
|
+
recorder,
|
|
515
|
+
filepath,
|
|
516
|
+
cursorData,
|
|
517
|
+
timestamp,
|
|
518
|
+
);
|
|
519
|
+
} else {
|
|
520
|
+
queueDeferredTextInputSample(
|
|
521
|
+
recorder,
|
|
522
|
+
nativeBinding,
|
|
523
|
+
filepath,
|
|
524
|
+
position,
|
|
525
|
+
timestamp,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
358
528
|
} catch (error) {
|
|
359
529
|
console.error("Cursor capture error:", error);
|
|
360
530
|
}
|
|
@@ -387,7 +557,9 @@ async function stopCursorCapture(recorder) {
|
|
|
387
557
|
recorder.cursorCaptureFirstWrite = true;
|
|
388
558
|
recorder.cursorDisplayInfo = null;
|
|
389
559
|
recorder._tiSampleWallMs = 0;
|
|
560
|
+
recorder._electronSynthWallMs = 0;
|
|
390
561
|
recorder._lastTextInputEmitted = null;
|
|
562
|
+
recorder._tiDeferredPending = false;
|
|
391
563
|
|
|
392
564
|
recorder.emit("cursorCaptureStopped");
|
|
393
565
|
resolve(true);
|