node-mac-recorder 1.2.6 → 1.2.7

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,10 @@ 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();
47
53
  }
48
54
 
49
55
  /**
@@ -189,6 +195,16 @@ class MacRecorder extends EventEmitter {
189
195
  // DisplayId'yi ayarla
190
196
  if (targetDisplayId !== null) {
191
197
  this.options.displayId = targetDisplayId;
198
+
199
+ // Recording için display bilgisini sakla (cursor capture için)
200
+ const targetDisplay = displays[targetDisplayId];
201
+ this.recordingDisplayInfo = {
202
+ displayId: targetDisplayId,
203
+ x: targetDisplay.x,
204
+ y: targetDisplay.y,
205
+ width: parseInt(targetDisplay.resolution.split("x")[0]),
206
+ height: parseInt(targetDisplay.resolution.split("x")[1]),
207
+ };
192
208
  }
193
209
 
194
210
  this.options.captureArea = {
@@ -210,6 +226,25 @@ class MacRecorder extends EventEmitter {
210
226
  }
211
227
  }
212
228
 
229
+ // DisplayId manuel ayarlanmışsa display bilgisini sakla
230
+ if (this.options.displayId !== null && !this.recordingDisplayInfo) {
231
+ try {
232
+ const displays = await this.getDisplays();
233
+ if (this.options.displayId < displays.length) {
234
+ const targetDisplay = displays[this.options.displayId];
235
+ this.recordingDisplayInfo = {
236
+ displayId: this.options.displayId,
237
+ x: targetDisplay.x,
238
+ y: targetDisplay.y,
239
+ width: parseInt(targetDisplay.resolution.split("x")[0]),
240
+ height: parseInt(targetDisplay.resolution.split("x")[1]),
241
+ };
242
+ }
243
+ } catch (error) {
244
+ console.warn("Display bilgisi alınamadı:", error.message);
245
+ }
246
+ }
247
+
213
248
  // Çıkış dizinini oluştur
214
249
  const outputDir = path.dirname(outputPath);
215
250
  if (!fs.existsSync(outputDir)) {
@@ -291,6 +326,7 @@ class MacRecorder extends EventEmitter {
291
326
  }
292
327
 
293
328
  this.isRecording = false;
329
+ this.recordingDisplayInfo = null;
294
330
 
295
331
  const result = {
296
332
  code: success ? 0 : 1,
@@ -311,6 +347,7 @@ class MacRecorder extends EventEmitter {
311
347
  resolve(result);
312
348
  } catch (error) {
313
349
  this.isRecording = false;
350
+ this.recordingDisplayInfo = null;
314
351
  if (this.recordingTimer) {
315
352
  clearInterval(this.recordingTimer);
316
353
  this.recordingTimer = null;
@@ -458,16 +495,52 @@ class MacRecorder extends EventEmitter {
458
495
 
459
496
  /**
460
497
  * Cursor capture başlatır - otomatik olarak dosyaya yazmaya başlar
498
+ * Recording başlatılmışsa otomatik olarak display-relative koordinatlar kullanır
499
+ * @param {string} filepath - Cursor data JSON dosya yolu
461
500
  */
462
- async startCursorCapture(filepath) {
463
- if (!filepath) {
464
- throw new Error("File path is required");
501
+ async startCursorCapture(intervalOrFilepath = 100) {
502
+ let filepath;
503
+ let interval = 20; // Default 50 FPS
504
+
505
+ // Parameter parsing: number = interval, string = filepath
506
+ if (typeof intervalOrFilepath === "number") {
507
+ interval = Math.max(10, intervalOrFilepath); // Min 10ms
508
+ filepath = `cursor-data-${Date.now()}.json`;
509
+ } else if (typeof intervalOrFilepath === "string") {
510
+ filepath = intervalOrFilepath;
511
+ } else {
512
+ throw new Error(
513
+ "Parameter must be interval (number) or filepath (string)"
514
+ );
465
515
  }
466
516
 
467
517
  if (this.cursorCaptureInterval) {
468
518
  throw new Error("Cursor capture is already running");
469
519
  }
470
520
 
521
+ // Recording başlatılmışsa o display'i kullan, yoksa main display kullan
522
+ if (this.recordingDisplayInfo) {
523
+ this.cursorDisplayInfo = this.recordingDisplayInfo;
524
+ } else {
525
+ // Main display bilgisini al (her zaman relative koordinatlar için)
526
+ try {
527
+ const displays = await this.getDisplays();
528
+ const mainDisplay = displays.find((d) => d.isPrimary) || displays[0];
529
+ if (mainDisplay) {
530
+ this.cursorDisplayInfo = {
531
+ displayId: 0,
532
+ x: mainDisplay.x,
533
+ y: mainDisplay.y,
534
+ width: parseInt(mainDisplay.resolution.split("x")[0]),
535
+ height: parseInt(mainDisplay.resolution.split("x")[1]),
536
+ };
537
+ }
538
+ } catch (error) {
539
+ console.warn("Main display bilgisi alınamadı:", error.message);
540
+ this.cursorDisplayInfo = null; // Fallback: global koordinatlar
541
+ }
542
+ }
543
+
471
544
  return new Promise((resolve, reject) => {
472
545
  try {
473
546
  // Dosyayı oluştur ve temizle
@@ -485,9 +558,29 @@ class MacRecorder extends EventEmitter {
485
558
  const position = nativeBinding.getCursorPosition();
486
559
  const timestamp = Date.now() - this.cursorCaptureStartTime;
487
560
 
561
+ // Global koordinatları display-relative'e çevir
562
+ let x = position.x;
563
+ let y = position.y;
564
+
565
+ if (this.cursorDisplayInfo) {
566
+ // Display offset'lerini çıkar
567
+ x = position.x - this.cursorDisplayInfo.x;
568
+ y = position.y - this.cursorDisplayInfo.y;
569
+
570
+ // Display bounds kontrolü - cursor display dışındaysa kaydetme
571
+ if (
572
+ x < 0 ||
573
+ y < 0 ||
574
+ x >= this.cursorDisplayInfo.width ||
575
+ y >= this.cursorDisplayInfo.height
576
+ ) {
577
+ return; // Bu frame'i skip et
578
+ }
579
+ }
580
+
488
581
  const cursorData = {
489
- x: position.x,
490
- y: position.y,
582
+ x: x,
583
+ y: y,
491
584
  timestamp: timestamp,
492
585
  cursorType: position.cursorType,
493
586
  type: position.eventType || "move",
@@ -511,7 +604,7 @@ class MacRecorder extends EventEmitter {
511
604
  } catch (error) {
512
605
  console.error("Cursor capture error:", error);
513
606
  }
514
- }, 20); // 50 FPS - mouse event'leri yakalamak için daha hızlı
607
+ }, interval); // Configurable FPS
515
608
 
516
609
  this.emit("cursorCaptureStarted", filepath);
517
610
  resolve(true);
@@ -546,6 +639,7 @@ class MacRecorder extends EventEmitter {
546
639
  this.lastCapturedData = null;
547
640
  this.cursorCaptureStartTime = null;
548
641
  this.cursorCaptureFirstWrite = true;
642
+ this.cursorDisplayInfo = null;
549
643
 
550
644
  this.emit("cursorCaptureStopped");
551
645
  resolve(true);
@@ -557,15 +651,97 @@ class MacRecorder extends EventEmitter {
557
651
 
558
652
  /**
559
653
  * Anlık cursor pozisyonunu ve tipini döndürür
654
+ * Display-relative koordinatlar döner (her zaman pozitif)
560
655
  */
561
656
  getCursorPosition() {
562
657
  try {
563
- return nativeBinding.getCursorPosition();
658
+ const position = nativeBinding.getCursorPosition();
659
+
660
+ // Cursor hangi display'de ise o display'e relative döndür
661
+ return this.getDisplayRelativePositionSync(position);
564
662
  } catch (error) {
565
663
  throw new Error("Failed to get cursor position: " + error.message);
566
664
  }
567
665
  }
568
666
 
667
+ /**
668
+ * Global koordinatları en uygun display'e relative çevirir (sync version)
669
+ */
670
+ getDisplayRelativePositionSync(position) {
671
+ try {
672
+ // Cache'lenmiş displays'leri kullan
673
+ if (!this.cachedDisplays) {
674
+ // İlk çağrı - global koordinat döndür ve cache başlat
675
+ this.refreshDisplayCache();
676
+ return position;
677
+ }
678
+
679
+ // Cursor hangi display içinde ise onu bul
680
+ for (const display of this.cachedDisplays) {
681
+ const x = parseInt(display.x);
682
+ const y = parseInt(display.y);
683
+ const width = parseInt(display.resolution.split("x")[0]);
684
+ const height = parseInt(display.resolution.split("x")[1]);
685
+
686
+ if (
687
+ position.x >= x &&
688
+ position.x < x + width &&
689
+ position.y >= y &&
690
+ position.y < y + height
691
+ ) {
692
+ // Bu display içinde
693
+ return {
694
+ x: position.x - x,
695
+ y: position.y - y,
696
+ cursorType: position.cursorType,
697
+ eventType: position.eventType,
698
+ displayId: display.id,
699
+ displayIndex: this.cachedDisplays.indexOf(display),
700
+ };
701
+ }
702
+ }
703
+
704
+ // Hiçbir display'de değilse main display'e relative döndür
705
+ const mainDisplay =
706
+ this.cachedDisplays.find((d) => d.isPrimary) || this.cachedDisplays[0];
707
+ if (mainDisplay) {
708
+ return {
709
+ x: position.x - parseInt(mainDisplay.x),
710
+ y: position.y - parseInt(mainDisplay.y),
711
+ cursorType: position.cursorType,
712
+ eventType: position.eventType,
713
+ displayId: mainDisplay.id,
714
+ displayIndex: this.cachedDisplays.indexOf(mainDisplay),
715
+ outsideDisplay: true,
716
+ };
717
+ }
718
+
719
+ // Fallback: global koordinat
720
+ return position;
721
+ } catch (error) {
722
+ // Hata durumunda global koordinat döndür
723
+ return position;
724
+ }
725
+ }
726
+
727
+ /**
728
+ * Display cache'ini refresh eder
729
+ */
730
+ async refreshDisplayCache() {
731
+ try {
732
+ this.cachedDisplays = await this.getDisplays();
733
+ } catch (error) {
734
+ console.warn("Display cache refresh failed:", error.message);
735
+ }
736
+ }
737
+
738
+ /**
739
+ * getCurrentCursorPosition alias for getCursorPosition (backward compatibility)
740
+ */
741
+ getCurrentCursorPosition() {
742
+ return this.getCursorPosition();
743
+ }
744
+
569
745
  /**
570
746
  * Cursor capture durumunu döndürür
571
747
  */
@@ -574,6 +750,7 @@ class MacRecorder extends EventEmitter {
574
750
  isCapturing: !!this.cursorCaptureInterval,
575
751
  outputFile: this.cursorCaptureFile || null,
576
752
  startTime: this.cursorCaptureStartTime || null,
753
+ displayInfo: this.cursorDisplayInfo || null,
577
754
  };
578
755
  }
579
756
 
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.7",
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
- ]