node-mac-recorder 1.2.6 → 1.2.8

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.
@@ -0,0 +1 @@
1
+ []
@@ -0,0 +1 @@
1
+ []
package/index.js CHANGED
@@ -32,6 +32,8 @@ class MacRecorder extends EventEmitter {
32
32
  this.cursorCaptureStartTime = null;
33
33
  this.cursorCaptureFirstWrite = true;
34
34
  this.lastCapturedData = null;
35
+ this.cursorDisplayInfo = null;
36
+ this.recordingDisplayInfo = null;
35
37
 
36
38
  this.options = {
37
39
  includeMicrophone: false, // Default olarak mikrofon kapalı
@@ -44,6 +46,13 @@ class MacRecorder extends EventEmitter {
44
46
  displayId: null, // Hangi ekranı kaydedeceği (null = ana ekran)
45
47
  windowId: null, // Hangi pencereyi kaydedeceği (null = tam ekran)
46
48
  };
49
+
50
+ // Display cache için async initialization
51
+ this.cachedDisplays = null;
52
+ this.refreshDisplayCache();
53
+
54
+ // Native cursor warm-up (cold start delay'ini önlemek için)
55
+ this.warmUpCursor();
47
56
  }
48
57
 
49
58
  /**
@@ -189,6 +198,16 @@ class MacRecorder extends EventEmitter {
189
198
  // DisplayId'yi ayarla
190
199
  if (targetDisplayId !== null) {
191
200
  this.options.displayId = targetDisplayId;
201
+
202
+ // Recording için display bilgisini sakla (cursor capture için)
203
+ const targetDisplay = displays[targetDisplayId];
204
+ this.recordingDisplayInfo = {
205
+ displayId: targetDisplayId,
206
+ x: targetDisplay.x,
207
+ y: targetDisplay.y,
208
+ width: parseInt(targetDisplay.resolution.split("x")[0]),
209
+ height: parseInt(targetDisplay.resolution.split("x")[1]),
210
+ };
192
211
  }
193
212
 
194
213
  this.options.captureArea = {
@@ -210,6 +229,25 @@ class MacRecorder extends EventEmitter {
210
229
  }
211
230
  }
212
231
 
232
+ // DisplayId manuel ayarlanmışsa display bilgisini sakla
233
+ if (this.options.displayId !== null && !this.recordingDisplayInfo) {
234
+ try {
235
+ const displays = await this.getDisplays();
236
+ if (this.options.displayId < displays.length) {
237
+ const targetDisplay = displays[this.options.displayId];
238
+ this.recordingDisplayInfo = {
239
+ displayId: this.options.displayId,
240
+ x: targetDisplay.x,
241
+ y: targetDisplay.y,
242
+ width: parseInt(targetDisplay.resolution.split("x")[0]),
243
+ height: parseInt(targetDisplay.resolution.split("x")[1]),
244
+ };
245
+ }
246
+ } catch (error) {
247
+ console.warn("Display bilgisi alınamadı:", error.message);
248
+ }
249
+ }
250
+
213
251
  // Çıkış dizinini oluştur
214
252
  const outputDir = path.dirname(outputPath);
215
253
  if (!fs.existsSync(outputDir)) {
@@ -291,6 +329,7 @@ class MacRecorder extends EventEmitter {
291
329
  }
292
330
 
293
331
  this.isRecording = false;
332
+ this.recordingDisplayInfo = null;
294
333
 
295
334
  const result = {
296
335
  code: success ? 0 : 1,
@@ -311,6 +350,7 @@ class MacRecorder extends EventEmitter {
311
350
  resolve(result);
312
351
  } catch (error) {
313
352
  this.isRecording = false;
353
+ this.recordingDisplayInfo = null;
314
354
  if (this.recordingTimer) {
315
355
  clearInterval(this.recordingTimer);
316
356
  this.recordingTimer = null;
@@ -458,16 +498,52 @@ class MacRecorder extends EventEmitter {
458
498
 
459
499
  /**
460
500
  * Cursor capture başlatır - otomatik olarak dosyaya yazmaya başlar
501
+ * Recording başlatılmışsa otomatik olarak display-relative koordinatlar kullanır
502
+ * @param {string} filepath - Cursor data JSON dosya yolu
461
503
  */
462
- async startCursorCapture(filepath) {
463
- if (!filepath) {
464
- throw new Error("File path is required");
504
+ async startCursorCapture(intervalOrFilepath = 100) {
505
+ let filepath;
506
+ let interval = 20; // Default 50 FPS
507
+
508
+ // Parameter parsing: number = interval, string = filepath
509
+ if (typeof intervalOrFilepath === "number") {
510
+ interval = Math.max(10, intervalOrFilepath); // Min 10ms
511
+ filepath = `cursor-data-${Date.now()}.json`;
512
+ } else if (typeof intervalOrFilepath === "string") {
513
+ filepath = intervalOrFilepath;
514
+ } else {
515
+ throw new Error(
516
+ "Parameter must be interval (number) or filepath (string)"
517
+ );
465
518
  }
466
519
 
467
520
  if (this.cursorCaptureInterval) {
468
521
  throw new Error("Cursor capture is already running");
469
522
  }
470
523
 
524
+ // Recording başlatılmışsa o display'i kullan, yoksa main display kullan
525
+ if (this.recordingDisplayInfo) {
526
+ this.cursorDisplayInfo = this.recordingDisplayInfo;
527
+ } else {
528
+ // Main display bilgisini al (her zaman relative koordinatlar için)
529
+ try {
530
+ const displays = await this.getDisplays();
531
+ const mainDisplay = displays.find((d) => d.isPrimary) || displays[0];
532
+ if (mainDisplay) {
533
+ this.cursorDisplayInfo = {
534
+ displayId: 0,
535
+ x: mainDisplay.x,
536
+ y: mainDisplay.y,
537
+ width: parseInt(mainDisplay.resolution.split("x")[0]),
538
+ height: parseInt(mainDisplay.resolution.split("x")[1]),
539
+ };
540
+ }
541
+ } catch (error) {
542
+ console.warn("Main display bilgisi alınamadı:", error.message);
543
+ this.cursorDisplayInfo = null; // Fallback: global koordinatlar
544
+ }
545
+ }
546
+
471
547
  return new Promise((resolve, reject) => {
472
548
  try {
473
549
  // Dosyayı oluştur ve temizle
@@ -485,9 +561,29 @@ class MacRecorder extends EventEmitter {
485
561
  const position = nativeBinding.getCursorPosition();
486
562
  const timestamp = Date.now() - this.cursorCaptureStartTime;
487
563
 
564
+ // Global koordinatları display-relative'e çevir
565
+ let x = position.x;
566
+ let y = position.y;
567
+
568
+ if (this.cursorDisplayInfo) {
569
+ // Display offset'lerini çıkar
570
+ x = position.x - this.cursorDisplayInfo.x;
571
+ y = position.y - this.cursorDisplayInfo.y;
572
+
573
+ // Display bounds kontrolü - cursor display dışındaysa kaydetme
574
+ if (
575
+ x < 0 ||
576
+ y < 0 ||
577
+ x >= this.cursorDisplayInfo.width ||
578
+ y >= this.cursorDisplayInfo.height
579
+ ) {
580
+ return; // Bu frame'i skip et
581
+ }
582
+ }
583
+
488
584
  const cursorData = {
489
- x: position.x,
490
- y: position.y,
585
+ x: x,
586
+ y: y,
491
587
  timestamp: timestamp,
492
588
  cursorType: position.cursorType,
493
589
  type: position.eventType || "move",
@@ -511,7 +607,7 @@ class MacRecorder extends EventEmitter {
511
607
  } catch (error) {
512
608
  console.error("Cursor capture error:", error);
513
609
  }
514
- }, 20); // 50 FPS - mouse event'leri yakalamak için daha hızlı
610
+ }, interval); // Configurable FPS
515
611
 
516
612
  this.emit("cursorCaptureStarted", filepath);
517
613
  resolve(true);
@@ -546,6 +642,7 @@ class MacRecorder extends EventEmitter {
546
642
  this.lastCapturedData = null;
547
643
  this.cursorCaptureStartTime = null;
548
644
  this.cursorCaptureFirstWrite = true;
645
+ this.cursorDisplayInfo = null;
549
646
 
550
647
  this.emit("cursorCaptureStopped");
551
648
  resolve(true);
@@ -557,15 +654,112 @@ class MacRecorder extends EventEmitter {
557
654
 
558
655
  /**
559
656
  * Anlık cursor pozisyonunu ve tipini döndürür
657
+ * Display-relative koordinatlar döner (her zaman pozitif)
560
658
  */
561
659
  getCursorPosition() {
562
660
  try {
563
- return nativeBinding.getCursorPosition();
661
+ const position = nativeBinding.getCursorPosition();
662
+
663
+ // Cursor hangi display'de ise o display'e relative döndür
664
+ return this.getDisplayRelativePositionSync(position);
564
665
  } catch (error) {
565
666
  throw new Error("Failed to get cursor position: " + error.message);
566
667
  }
567
668
  }
568
669
 
670
+ /**
671
+ * Global koordinatları en uygun display'e relative çevirir (sync version)
672
+ */
673
+ getDisplayRelativePositionSync(position) {
674
+ try {
675
+ // Cache'lenmiş displays'leri kullan
676
+ if (!this.cachedDisplays) {
677
+ // İlk çağrı - global koordinat döndür ve cache başlat
678
+ this.refreshDisplayCache();
679
+ return position;
680
+ }
681
+
682
+ // Cursor hangi display içinde ise onu bul
683
+ for (const display of this.cachedDisplays) {
684
+ const x = parseInt(display.x);
685
+ const y = parseInt(display.y);
686
+ const width = parseInt(display.resolution.split("x")[0]);
687
+ const height = parseInt(display.resolution.split("x")[1]);
688
+
689
+ if (
690
+ position.x >= x &&
691
+ position.x < x + width &&
692
+ position.y >= y &&
693
+ position.y < y + height
694
+ ) {
695
+ // Bu display içinde
696
+ return {
697
+ x: position.x - x,
698
+ y: position.y - y,
699
+ cursorType: position.cursorType,
700
+ eventType: position.eventType,
701
+ displayId: display.id,
702
+ displayIndex: this.cachedDisplays.indexOf(display),
703
+ };
704
+ }
705
+ }
706
+
707
+ // Hiçbir display'de değilse main display'e relative döndür
708
+ const mainDisplay =
709
+ this.cachedDisplays.find((d) => d.isPrimary) || this.cachedDisplays[0];
710
+ if (mainDisplay) {
711
+ return {
712
+ x: position.x - parseInt(mainDisplay.x),
713
+ y: position.y - parseInt(mainDisplay.y),
714
+ cursorType: position.cursorType,
715
+ eventType: position.eventType,
716
+ displayId: mainDisplay.id,
717
+ displayIndex: this.cachedDisplays.indexOf(mainDisplay),
718
+ outsideDisplay: true,
719
+ };
720
+ }
721
+
722
+ // Fallback: global koordinat
723
+ return position;
724
+ } catch (error) {
725
+ // Hata durumunda global koordinat döndür
726
+ return position;
727
+ }
728
+ }
729
+
730
+ /**
731
+ * Display cache'ini refresh eder
732
+ */
733
+ async refreshDisplayCache() {
734
+ try {
735
+ this.cachedDisplays = await this.getDisplays();
736
+ } catch (error) {
737
+ console.warn("Display cache refresh failed:", error.message);
738
+ }
739
+ }
740
+
741
+ /**
742
+ * Native cursor modülünü warm-up yapar (cold start delay'ini önler)
743
+ */
744
+ warmUpCursor() {
745
+ // Async warm-up to prevent blocking constructor
746
+ setTimeout(() => {
747
+ try {
748
+ // Silent warm-up call
749
+ nativeBinding.getCursorPosition();
750
+ } catch (error) {
751
+ // Ignore warm-up errors
752
+ }
753
+ }, 10); // 10ms delay to not block initialization
754
+ }
755
+
756
+ /**
757
+ * getCurrentCursorPosition alias for getCursorPosition (backward compatibility)
758
+ */
759
+ getCurrentCursorPosition() {
760
+ return this.getCursorPosition();
761
+ }
762
+
569
763
  /**
570
764
  * Cursor capture durumunu döndürür
571
765
  */
@@ -574,6 +768,7 @@ class MacRecorder extends EventEmitter {
574
768
  isCapturing: !!this.cursorCaptureInterval,
575
769
  outputFile: this.cursorCaptureFile || null,
576
770
  startTime: this.cursorCaptureStartTime || null,
771
+ displayInfo: this.cursorDisplayInfo || null,
577
772
  };
578
773
  }
579
774
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -251,22 +251,13 @@ void cursorTimerCallback() {
251
251
  return;
252
252
  }
253
253
 
254
- // Ana thread'de mouse pozisyonu al
255
- __block NSPoint mouseLocation;
256
- __block CGPoint location;
257
-
258
- if ([NSThread isMainThread]) {
259
- mouseLocation = [NSEvent mouseLocation];
260
- } else {
261
- dispatch_sync(dispatch_get_main_queue(), ^{
262
- mouseLocation = [NSEvent mouseLocation];
263
- });
254
+ // CGEventGetLocation direkt global koordinat verir - çoklu ekran desteği için daha doğru
255
+ CGEventRef event = CGEventCreate(NULL);
256
+ CGPoint location = CGEventGetLocation(event);
257
+ if (event) {
258
+ CFRelease(event);
264
259
  }
265
260
 
266
- CGDirectDisplayID mainDisplay = CGMainDisplayID();
267
- size_t displayHeight = CGDisplayPixelsHigh(mainDisplay);
268
- location = CGPointMake(mouseLocation.x, displayHeight - mouseLocation.y);
269
-
270
261
  NSTimeInterval timestamp = [[NSDate date] timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
271
262
  NSString *cursorType = getCursorType();
272
263
 
@@ -443,15 +434,13 @@ Napi::Value GetCursorPosition(const Napi::CallbackInfo& info) {
443
434
  Napi::Env env = info.Env();
444
435
 
445
436
  @try {
446
- // NSEvent kullanarak mouse pozisyonu al (daha güvenli)
447
- NSPoint mouseLocation = [NSEvent mouseLocation];
448
-
449
- // CGDisplayPixelsHigh ve CGDisplayPixelsWide ile koordinat dönüşümü
450
- CGDirectDisplayID mainDisplay = CGMainDisplayID();
451
- size_t displayHeight = CGDisplayPixelsHigh(mainDisplay);
452
-
453
- // macOS coordinate system (bottom-left origin) to screen coordinates (top-left origin)
454
- CGPoint location = CGPointMake(mouseLocation.x, displayHeight - mouseLocation.y);
437
+ // NSEvent mouseLocation zaten global koordinatlarda (all displays combined)
438
+ // CGEventGetLocation kullanarak direkt global koordinat al - daha doğru
439
+ CGEventRef event = CGEventCreate(NULL);
440
+ CGPoint location = CGEventGetLocation(event);
441
+ if (event) {
442
+ CFRelease(event);
443
+ }
455
444
 
456
445
  NSString *cursorType = getCursorType();
457
446
 
package/cursor-data.json DELETED
@@ -1,359 +0,0 @@
1
- [
2
- {
3
- "x": 1947,
4
- "y": 780,
5
- "timestamp": 85,
6
- "cursorType": "pointer",
7
- "type": "move"
8
- },
9
- {
10
- "x": 1950,
11
- "y": 780,
12
- "timestamp": 1476,
13
- "cursorType": "pointer",
14
- "type": "move"
15
- },
16
- {
17
- "x": 1954,
18
- "y": 778,
19
- "timestamp": 1500,
20
- "cursorType": "pointer",
21
- "type": "move"
22
- },
23
- {
24
- "x": 1958,
25
- "y": 777,
26
- "timestamp": 1518,
27
- "cursorType": "pointer",
28
- "type": "move"
29
- },
30
- {
31
- "x": 1963,
32
- "y": 775,
33
- "timestamp": 1541,
34
- "cursorType": "pointer",
35
- "type": "move"
36
- },
37
- {
38
- "x": 1967,
39
- "y": 775,
40
- "timestamp": 1595,
41
- "cursorType": "default",
42
- "type": "move"
43
- },
44
- {
45
- "x": 1971,
46
- "y": 775,
47
- "timestamp": 1596,
48
- "cursorType": "default",
49
- "type": "move"
50
- },
51
- {
52
- "x": 1973,
53
- "y": 775,
54
- "timestamp": 1617,
55
- "cursorType": "default",
56
- "type": "move"
57
- },
58
- {
59
- "x": 1975,
60
- "y": 775,
61
- "timestamp": 1659,
62
- "cursorType": "default",
63
- "type": "move"
64
- },
65
- {
66
- "x": 1977,
67
- "y": 775,
68
- "timestamp": 1679,
69
- "cursorType": "pointer",
70
- "type": "move"
71
- },
72
- {
73
- "x": 1979,
74
- "y": 775,
75
- "timestamp": 1744,
76
- "cursorType": "pointer",
77
- "type": "move"
78
- },
79
- {
80
- "x": 1979,
81
- "y": 777,
82
- "timestamp": 1807,
83
- "cursorType": "pointer",
84
- "type": "move"
85
- },
86
- {
87
- "x": 1973,
88
- "y": 782,
89
- "timestamp": 1828,
90
- "cursorType": "default",
91
- "type": "move"
92
- },
93
- {
94
- "x": 1968,
95
- "y": 787,
96
- "timestamp": 1850,
97
- "cursorType": "default",
98
- "type": "move"
99
- },
100
- {
101
- "x": 1957,
102
- "y": 793,
103
- "timestamp": 1868,
104
- "cursorType": "pointer",
105
- "type": "move"
106
- },
107
- {
108
- "x": 1948,
109
- "y": 799,
110
- "timestamp": 1891,
111
- "cursorType": "pointer",
112
- "type": "move"
113
- },
114
- {
115
- "x": 1943,
116
- "y": 802,
117
- "timestamp": 1911,
118
- "cursorType": "default",
119
- "type": "move"
120
- },
121
- {
122
- "x": 1937,
123
- "y": 804,
124
- "timestamp": 1933,
125
- "cursorType": "default",
126
- "type": "move"
127
- },
128
- {
129
- "x": 1938,
130
- "y": 800,
131
- "timestamp": 2309,
132
- "cursorType": "default",
133
- "type": "move"
134
- },
135
- {
136
- "x": 1939,
137
- "y": 799,
138
- "timestamp": 2329,
139
- "cursorType": "pointer",
140
- "type": "move"
141
- },
142
- {
143
- "x": 1940,
144
- "y": 797,
145
- "timestamp": 2435,
146
- "cursorType": "pointer",
147
- "type": "move"
148
- },
149
- {
150
- "x": 1941,
151
- "y": 794,
152
- "timestamp": 2477,
153
- "cursorType": "pointer",
154
- "type": "move"
155
- },
156
- {
157
- "x": 1941,
158
- "y": 792,
159
- "timestamp": 2500,
160
- "cursorType": "pointer",
161
- "type": "move"
162
- },
163
- {
164
- "x": 1942,
165
- "y": 788,
166
- "timestamp": 2518,
167
- "cursorType": "pointer",
168
- "type": "move"
169
- },
170
- {
171
- "x": 1943,
172
- "y": 786,
173
- "timestamp": 2560,
174
- "cursorType": "pointer",
175
- "type": "move"
176
- },
177
- {
178
- "x": 1945,
179
- "y": 784,
180
- "timestamp": 2707,
181
- "cursorType": "pointer",
182
- "type": "move"
183
- },
184
- {
185
- "x": 1945,
186
- "y": 796,
187
- "timestamp": 3544,
188
- "cursorType": "pointer",
189
- "type": "move"
190
- },
191
- {
192
- "x": 1945,
193
- "y": 817,
194
- "timestamp": 3568,
195
- "cursorType": "default",
196
- "type": "move"
197
- },
198
- {
199
- "x": 1951,
200
- "y": 863,
201
- "timestamp": 3589,
202
- "cursorType": "default",
203
- "type": "move"
204
- },
205
- {
206
- "x": 1956,
207
- "y": 925,
208
- "timestamp": 3609,
209
- "cursorType": "default",
210
- "type": "move"
211
- },
212
- {
213
- "x": 1966,
214
- "y": 983,
215
- "timestamp": 3630,
216
- "cursorType": "default",
217
- "type": "move"
218
- },
219
- {
220
- "x": 1970,
221
- "y": 1006,
222
- "timestamp": 3650,
223
- "cursorType": "text",
224
- "type": "move"
225
- },
226
- {
227
- "x": 1978,
228
- "y": 1042,
229
- "timestamp": 3675,
230
- "cursorType": "default",
231
- "type": "move"
232
- },
233
- {
234
- "x": 1983,
235
- "y": 1061,
236
- "timestamp": 3693,
237
- "cursorType": "default",
238
- "type": "move"
239
- },
240
- {
241
- "x": 1986,
242
- "y": 1072,
243
- "timestamp": 3713,
244
- "cursorType": "text",
245
- "type": "move"
246
- },
247
- {
248
- "x": 1990,
249
- "y": 1082,
250
- "timestamp": 3734,
251
- "cursorType": "text",
252
- "type": "move"
253
- },
254
- {
255
- "x": 1991,
256
- "y": 1086,
257
- "timestamp": 3755,
258
- "cursorType": "text",
259
- "type": "move"
260
- },
261
- {
262
- "x": 1993,
263
- "y": 1091,
264
- "timestamp": 3776,
265
- "cursorType": "text",
266
- "type": "move"
267
- },
268
- {
269
- "x": 1994,
270
- "y": 1096,
271
- "timestamp": 3797,
272
- "cursorType": "text",
273
- "type": "move"
274
- },
275
- {
276
- "x": 1995,
277
- "y": 1099,
278
- "timestamp": 3818,
279
- "cursorType": "text",
280
- "type": "move"
281
- },
282
- {
283
- "x": 1997,
284
- "y": 1106,
285
- "timestamp": 3841,
286
- "cursorType": "text",
287
- "type": "move"
288
- },
289
- {
290
- "x": 1997,
291
- "y": 1109,
292
- "timestamp": 3859,
293
- "cursorType": "text",
294
- "type": "move"
295
- },
296
- {
297
- "x": 1998,
298
- "y": 1116,
299
- "timestamp": 3882,
300
- "cursorType": "text",
301
- "type": "move"
302
- },
303
- {
304
- "x": 2000,
305
- "y": 1120,
306
- "timestamp": 3902,
307
- "cursorType": "text",
308
- "type": "move"
309
- },
310
- {
311
- "x": 2000,
312
- "y": 1123,
313
- "timestamp": 3924,
314
- "cursorType": "text",
315
- "type": "move"
316
- },
317
- {
318
- "x": 2001,
319
- "y": 1126,
320
- "timestamp": 3945,
321
- "cursorType": "text",
322
- "type": "move"
323
- },
324
- {
325
- "x": 2001,
326
- "y": 1129,
327
- "timestamp": 3967,
328
- "cursorType": "text",
329
- "type": "move"
330
- },
331
- {
332
- "x": 2002,
333
- "y": 1135,
334
- "timestamp": 3986,
335
- "cursorType": "pointer",
336
- "type": "move"
337
- },
338
- {
339
- "x": 2002,
340
- "y": 1142,
341
- "timestamp": 4007,
342
- "cursorType": "pointer",
343
- "type": "move"
344
- },
345
- {
346
- "x": 2002,
347
- "y": 1146,
348
- "timestamp": 4027,
349
- "cursorType": "pointer",
350
- "type": "move"
351
- },
352
- {
353
- "x": 2002,
354
- "y": 1151,
355
- "timestamp": 4049,
356
- "cursorType": "pointer",
357
- "type": "move"
358
- }
359
- ]