node-mac-recorder 2.22.24 → 2.22.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.
@@ -1,585 +0,0 @@
1
- "use strict";
2
-
3
- const fs = require("fs");
4
- const { resolveCursorDisplayInfo } = require("./displayInfo");
5
-
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;
19
-
20
- function shouldCaptureCursorSample(lastCapturedData, currentData) {
21
- if (!lastCapturedData) {
22
- return true;
23
- }
24
- const last = lastCapturedData;
25
- if (currentData.type !== last.type) {
26
- return true;
27
- }
28
- if (
29
- Math.abs(currentData.x - last.x) >= 2 ||
30
- Math.abs(currentData.y - last.y) >= 2
31
- ) {
32
- return true;
33
- }
34
- if (currentData.cursorType !== last.cursorType) {
35
- return true;
36
- }
37
- return false;
38
- }
39
-
40
- function appendCursorJsonLine(recorder, filepath, obj) {
41
- const jsonString = JSON.stringify(obj);
42
- if (recorder.cursorCaptureFirstWrite) {
43
- fs.appendFileSync(filepath, jsonString);
44
- recorder.cursorCaptureFirstWrite = false;
45
- } else {
46
- fs.appendFileSync(filepath, "," + jsonString);
47
- }
48
- }
49
-
50
- function packDisplayInfoForExport(di) {
51
- if (!di) return {};
52
- return {
53
- displayId: di.displayId,
54
- displayX: Number.isFinite(Number(di.displayX))
55
- ? Number(di.displayX)
56
- : Number(di.x) || 0,
57
- displayY: Number.isFinite(Number(di.displayY))
58
- ? Number(di.displayY)
59
- : Number(di.y) || 0,
60
- width: di.displayWidth ?? di.width,
61
- height: di.displayHeight ?? di.height,
62
- };
63
- }
64
-
65
- function transformGlobalToVideo(globalX, globalY, d) {
66
- if (!d || !d.videoRelative) {
67
- return {
68
- x: globalX,
69
- y: globalY,
70
- coordinateSystem: "global",
71
- outsideVideo: false,
72
- };
73
- }
74
- const displayRelativeX = globalX - d.displayX;
75
- const displayRelativeY = globalY - d.displayY;
76
- const x = displayRelativeX - d.videoOffsetX;
77
- const y = displayRelativeY - d.videoOffsetY;
78
- const outsideVideo =
79
- x < 0 ||
80
- y < 0 ||
81
- x >= d.videoWidth ||
82
- y >= d.videoHeight;
83
- return {
84
- x,
85
- y,
86
- coordinateSystem: outsideVideo
87
- ? "video-relative-outside"
88
- : "video-relative",
89
- outsideVideo,
90
- };
91
- }
92
-
93
- function transformInputFrameGlobal(ifr, d) {
94
- if (!ifr || typeof ifr !== "object") {
95
- return {};
96
- }
97
- const ox = Number(ifr.x);
98
- const oy = Number(ifr.y);
99
- const tw = transformGlobalToVideo(ox, oy, d);
100
- return {
101
- x: tw.x,
102
- y: tw.y,
103
- width: Number(ifr.width) || 0,
104
- height: Number(ifr.height) || 0,
105
- };
106
- }
107
-
108
- /** @returns {boolean} Dosyaya textInput satırı yazıldı mı */
109
- function tryAppendSyntheticTextInputRow(recorder, filepath, cursorData, timestamp) {
110
- if (!IS_ELECTRON) {
111
- return false;
112
- }
113
- const ct = cursorData.cursorType || "";
114
- if (ct !== "text" && ct !== "vertical-text") {
115
- return false;
116
- }
117
- if (timestamp < ELECTRON_SYNTH_GRACE_MS) {
118
- return false;
119
- }
120
- const wall = Date.now();
121
- if (
122
- wall - (recorder._electronSynthWallMs || 0) <
123
- ELECTRON_SYNTH_THROTTLE_MS
124
- ) {
125
- return false;
126
- }
127
- const vx = cursorData.x;
128
- const vy = cursorData.y;
129
- if (!Number.isFinite(vx) || !Number.isFinite(vy)) {
130
- return false;
131
- }
132
-
133
- const tiRow = {
134
- x: vx,
135
- y: vy,
136
- timestamp,
137
- unixTimeMs: wall,
138
- cursorType: "text",
139
- type: "textInput",
140
- caretX: vx,
141
- caretY: vy,
142
- inputFrame: {
143
- x: vx - 1,
144
- y: vy - 9,
145
- width: 2,
146
- height: 18,
147
- },
148
- coordinateSystem: cursorData.coordinateSystem,
149
- recordingType: cursorData.recordingType,
150
- videoInfo: cursorData.videoInfo || {},
151
- displayInfo: cursorData.displayInfo || {},
152
- };
153
-
154
- if (cursorData.location) {
155
- tiRow.location = cursorData.location;
156
- }
157
- if (cursorData.windowRelative) {
158
- tiRow.windowRelative = cursorData.windowRelative;
159
- }
160
-
161
- if (
162
- recorder.cursorCaptureFirstWrite &&
163
- recorder.cursorCaptureSessionTimestamp
164
- ) {
165
- tiRow._syncMetadata = {
166
- videoStartTime: recorder.cursorCaptureSessionTimestamp,
167
- cursorStartTime: recorder.cursorCaptureStartTime,
168
- offsetMs:
169
- recorder.cursorCaptureStartTime -
170
- recorder.cursorCaptureSessionTimestamp,
171
- };
172
- }
173
-
174
- const le = recorder._lastTextInputEmitted;
175
- if (
176
- le &&
177
- Math.abs(le.caretX - tiRow.caretX) < 0.75 &&
178
- Math.abs(le.caretY - tiRow.caretY) < 0.75 &&
179
- timestamp - le.timestamp < 220
180
- ) {
181
- return false;
182
- }
183
- recorder._lastTextInputEmitted = {
184
- caretX: tiRow.caretX,
185
- caretY: tiRow.caretY,
186
- timestamp,
187
- };
188
- recorder._electronSynthWallMs = wall;
189
-
190
- appendCursorJsonLine(recorder, filepath, tiRow);
191
- return true;
192
- }
193
-
194
- function tryAppendTextInput(
195
- recorder,
196
- nativeBinding,
197
- filepath,
198
- position,
199
- timestamp,
200
- ) {
201
- if (IS_ELECTRON) {
202
- return;
203
- }
204
- if (typeof nativeBinding.getTextInputSnapshot !== "function") {
205
- return;
206
- }
207
- if (timestamp < NATIVE_TEXT_INPUT_GRACE_MS) {
208
- return;
209
- }
210
- const ct = position.cursorType || "";
211
- if (ct !== "text" && ct !== "vertical-text") {
212
- return;
213
- }
214
- const wall = Date.now();
215
- if (
216
- wall - (recorder._tiSampleWallMs || 0) <
217
- NATIVE_TEXT_INPUT_SAMPLE_MS
218
- ) {
219
- return;
220
- }
221
- recorder._tiSampleWallMs = wall;
222
-
223
- let snap = null;
224
- try {
225
- snap = nativeBinding.getTextInputSnapshot();
226
- } catch {
227
- return;
228
- }
229
- if (
230
- !snap ||
231
- !Number.isFinite(snap.caretX) ||
232
- !Number.isFinite(snap.caretY)
233
- ) {
234
- return;
235
- }
236
-
237
- const d = recorder.cursorDisplayInfo;
238
- const caretT = transformGlobalToVideo(snap.caretX, snap.caretY, d);
239
- const mouseGX = position.x;
240
- const mouseGY = position.y;
241
- const mouseT = transformGlobalToVideo(mouseGX, mouseGY, d);
242
-
243
- const inputFrameVid = transformInputFrameGlobal(snap.inputFrame, d);
244
-
245
- const tiRow = {
246
- x: mouseT.x,
247
- y: mouseT.y,
248
- timestamp,
249
- unixTimeMs: wall,
250
- cursorType: "text",
251
- type: "textInput",
252
- caretX: caretT.x,
253
- caretY: caretT.y,
254
- inputFrame: inputFrameVid,
255
- coordinateSystem: caretT.coordinateSystem,
256
- recordingType: d?.recordingType || "display",
257
- videoInfo: d
258
- ? {
259
- width: d.videoWidth,
260
- height: d.videoHeight,
261
- offsetX: d.videoOffsetX,
262
- offsetY: d.videoOffsetY,
263
- }
264
- : {},
265
- displayInfo: packDisplayInfoForExport(d),
266
- };
267
-
268
- if (
269
- recorder.cursorCaptureFirstWrite &&
270
- recorder.cursorCaptureSessionTimestamp
271
- ) {
272
- tiRow._syncMetadata = {
273
- videoStartTime: recorder.cursorCaptureSessionTimestamp,
274
- cursorStartTime: recorder.cursorCaptureStartTime,
275
- offsetMs:
276
- recorder.cursorCaptureStartTime -
277
- recorder.cursorCaptureSessionTimestamp,
278
- };
279
- }
280
-
281
- const le = recorder._lastTextInputEmitted;
282
- if (
283
- le &&
284
- Math.abs(le.caretX - tiRow.caretX) < 0.75 &&
285
- Math.abs(le.caretY - tiRow.caretY) < 0.75 &&
286
- timestamp - le.timestamp < 220
287
- ) {
288
- return;
289
- }
290
- recorder._lastTextInputEmitted = {
291
- caretX: tiRow.caretX,
292
- caretY: tiRow.caretY,
293
- timestamp,
294
- };
295
-
296
- const jsonString = JSON.stringify(tiRow);
297
- if (recorder.cursorCaptureFirstWrite) {
298
- fs.appendFileSync(filepath, jsonString);
299
- recorder.cursorCaptureFirstWrite = false;
300
- } else {
301
- fs.appendFileSync(filepath, "," + jsonString);
302
- }
303
- }
304
-
305
- function queueDeferredTextInputSample(
306
- recorder,
307
- nativeBinding,
308
- filepath,
309
- position,
310
- timestamp,
311
- ) {
312
- if (IS_ELECTRON) {
313
- return;
314
- }
315
- if (typeof nativeBinding.getTextInputSnapshot !== "function") {
316
- return;
317
- }
318
- if (timestamp < NATIVE_TEXT_INPUT_GRACE_MS) {
319
- return;
320
- }
321
- const ct = position.cursorType || "";
322
- if (ct !== "text" && ct !== "vertical-text") {
323
- return;
324
- }
325
- if (recorder._tiDeferredPending) {
326
- return;
327
- }
328
- recorder._tiDeferredPending = true;
329
- const pos = {
330
- x: position.x,
331
- y: position.y,
332
- cursorType: position.cursorType,
333
- eventType: position.eventType,
334
- };
335
- const ts = timestamp;
336
- setImmediate(() => {
337
- recorder._tiDeferredPending = false;
338
- if (!recorder.cursorCaptureFile || recorder.cursorCaptureFile !== filepath) {
339
- return;
340
- }
341
- try {
342
- tryAppendTextInput(recorder, nativeBinding, filepath, pos, ts);
343
- } catch {
344
- /* ignore */
345
- }
346
- });
347
- }
348
-
349
- async function startCursorCapture(recorder, nativeBinding, intervalOrFilepath, options = {}) {
350
- let filepath;
351
- let interval = DEFAULT_CURSOR_INTERVAL_MS;
352
-
353
- if (typeof intervalOrFilepath === "number") {
354
- interval = Math.max(10, intervalOrFilepath);
355
- filepath = `cursor-data-${Date.now()}.json`;
356
- } else if (typeof intervalOrFilepath === "string") {
357
- filepath = intervalOrFilepath;
358
- } else {
359
- throw new Error("Parameter must be interval (number) or filepath (string)");
360
- }
361
-
362
- if (recorder.cursorCaptureInterval) {
363
- throw new Error("Cursor capture is already running");
364
- }
365
-
366
- const syncStartTime = options.startTimestamp || Date.now();
367
-
368
- if (options.multiWindowBounds && options.multiWindowBounds.length > 0) {
369
- try {
370
- const allWindows = await recorder.getWindows();
371
- for (const windowInfo of options.multiWindowBounds) {
372
- const windowData = allWindows.find(
373
- (w) => w.id === windowInfo.windowId,
374
- );
375
- if (windowData) {
376
- windowInfo.bounds = {
377
- x: windowData.x || 0,
378
- y: windowData.y || 0,
379
- width: windowData.width || 0,
380
- height: windowData.height || 0,
381
- };
382
- }
383
- }
384
- } catch (error) {
385
- console.warn(
386
- "Failed to fetch window bounds for multi-window cursor tracking:",
387
- error.message,
388
- );
389
- }
390
- }
391
-
392
- await resolveCursorDisplayInfo(recorder, options);
393
-
394
- return new Promise((resolve, reject) => {
395
- try {
396
- fs.writeFileSync(filepath, "[");
397
-
398
- recorder.cursorCaptureFile = filepath;
399
- recorder.cursorCaptureStartTime = syncStartTime;
400
- recorder.cursorCaptureFirstWrite = true;
401
- recorder.lastCapturedData = null;
402
- recorder.cursorCaptureSessionTimestamp = recorder.sessionTimestamp;
403
- recorder._tiSampleWallMs = 0;
404
- recorder._electronSynthWallMs = 0;
405
- recorder._lastTextInputEmitted = null;
406
- recorder._tiDeferredPending = false;
407
-
408
- recorder.cursorCaptureInterval = setInterval(() => {
409
- try {
410
- const position = nativeBinding.getCursorPosition();
411
- const timestamp =
412
- Date.now() - recorder.cursorCaptureStartTime;
413
-
414
- let x = position.x;
415
- let y = position.y;
416
- let coordinateSystem = "global";
417
-
418
- const di = recorder.cursorDisplayInfo;
419
- if (di && di.videoRelative) {
420
- const displayRelativeX = position.x - di.displayX;
421
- const displayRelativeY = position.y - di.displayY;
422
- x = displayRelativeX - di.videoOffsetX;
423
- y = displayRelativeY - di.videoOffsetY;
424
- coordinateSystem = "video-relative";
425
- const outsideVideo =
426
- x < 0 ||
427
- y < 0 ||
428
- x >= di.videoWidth ||
429
- y >= di.videoHeight;
430
- if (outsideVideo) {
431
- coordinateSystem = "video-relative-outside";
432
- }
433
- }
434
-
435
- const cursorData = {
436
- x,
437
- y,
438
- timestamp,
439
- unixTimeMs: Date.now(),
440
- cursorType: position.cursorType,
441
- type: position.eventType || "move",
442
- coordinateSystem,
443
- recordingType: di?.recordingType || "display",
444
- videoInfo: di
445
- ? {
446
- width: di.videoWidth,
447
- height: di.videoHeight,
448
- offsetX: di.videoOffsetX,
449
- offsetY: di.videoOffsetY,
450
- }
451
- : {},
452
- displayInfo: packDisplayInfoForExport(di),
453
- };
454
-
455
- if (di?.multiWindowBounds && di.multiWindowBounds.length > 0) {
456
- const location = { hover: null, click: null };
457
- let windowRelativeCoords = null;
458
- for (const windowInfo of di.multiWindowBounds) {
459
- if (windowInfo.bounds) {
460
- const { x: wx, y: wy, width: ww, height: wh } =
461
- windowInfo.bounds;
462
- if (
463
- position.x >= wx &&
464
- position.x <= wx + ww &&
465
- position.y >= wy &&
466
- position.y <= wy + wh
467
- ) {
468
- location.hover = windowInfo.windowId;
469
- windowRelativeCoords = {
470
- windowId: windowInfo.windowId,
471
- x: position.x - wx,
472
- y: position.y - wy,
473
- windowWidth: ww,
474
- windowHeight: wh,
475
- };
476
- const eventType = position.eventType || "";
477
- if (
478
- eventType === "mousedown" ||
479
- eventType === "mouseup" ||
480
- eventType === "drag" ||
481
- eventType === "rightmousedown" ||
482
- eventType === "rightmouseup" ||
483
- eventType === "rightdrag"
484
- ) {
485
- location.click = windowInfo.windowId;
486
- }
487
- break;
488
- }
489
- }
490
- }
491
- cursorData.location = location;
492
- if (windowRelativeCoords) {
493
- cursorData.windowRelative = windowRelativeCoords;
494
- }
495
- }
496
-
497
- if (
498
- recorder.cursorCaptureFirstWrite &&
499
- recorder.cursorCaptureSessionTimestamp
500
- ) {
501
- cursorData._syncMetadata = {
502
- videoStartTime: recorder.cursorCaptureSessionTimestamp,
503
- cursorStartTime: recorder.cursorCaptureStartTime,
504
- offsetMs:
505
- recorder.cursorCaptureStartTime -
506
- recorder.cursorCaptureSessionTimestamp,
507
- };
508
- }
509
-
510
- let wroteMoveSample = false;
511
- if (shouldCaptureCursorSample(recorder.lastCapturedData, cursorData)) {
512
- appendCursorJsonLine(recorder, filepath, cursorData);
513
- recorder.lastCapturedData = { ...cursorData };
514
- wroteMoveSample = true;
515
- }
516
-
517
- if (IS_ELECTRON) {
518
- const textInputWritten = tryAppendSyntheticTextInputRow(
519
- recorder,
520
- filepath,
521
- cursorData,
522
- timestamp,
523
- );
524
- if (textInputWritten && !wroteMoveSample) {
525
- appendCursorJsonLine(recorder, filepath, cursorData);
526
- recorder.lastCapturedData = { ...cursorData };
527
- }
528
- } else {
529
- queueDeferredTextInputSample(
530
- recorder,
531
- nativeBinding,
532
- filepath,
533
- position,
534
- timestamp,
535
- );
536
- }
537
- } catch (error) {
538
- console.error("Cursor capture error:", error);
539
- }
540
- }, interval);
541
-
542
- recorder.emit("cursorCaptureStarted", filepath);
543
- resolve(true);
544
- } catch (error) {
545
- reject(error);
546
- }
547
- });
548
- }
549
-
550
- async function stopCursorCapture(recorder) {
551
- return new Promise((resolve, reject) => {
552
- try {
553
- if (!recorder.cursorCaptureInterval) {
554
- return resolve(false);
555
- }
556
- clearInterval(recorder.cursorCaptureInterval);
557
- recorder.cursorCaptureInterval = null;
558
-
559
- if (recorder.cursorCaptureFile) {
560
- fs.appendFileSync(recorder.cursorCaptureFile, "]");
561
- recorder.cursorCaptureFile = null;
562
- }
563
-
564
- recorder.lastCapturedData = null;
565
- recorder.cursorCaptureStartTime = null;
566
- recorder.cursorCaptureFirstWrite = true;
567
- recorder.cursorDisplayInfo = null;
568
- recorder._tiSampleWallMs = 0;
569
- recorder._electronSynthWallMs = 0;
570
- recorder._lastTextInputEmitted = null;
571
- recorder._tiDeferredPending = false;
572
-
573
- recorder.emit("cursorCaptureStopped");
574
- resolve(true);
575
- } catch (error) {
576
- reject(error);
577
- }
578
- });
579
- }
580
-
581
- module.exports = {
582
- startCursorCapture,
583
- stopCursorCapture,
584
- shouldCaptureCursorSample,
585
- };
@@ -1,3 +0,0 @@
1
- #import <Foundation/Foundation.h>
2
-
3
- NSDictionary *_Nullable MRTextInputSnapshotDictionary(void);