node-mac-recorder 1.1.0 → 1.2.0

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/README.md CHANGED
@@ -11,6 +11,7 @@ A powerful native macOS screen recording Node.js package with advanced window se
11
11
  - 🎯 **Area Selection** - Record custom screen regions
12
12
  - 🖱️ **Multi-Display Support** - Automatic display detection and selection
13
13
  - 🎨 **Cursor Control** - Toggle cursor visibility in recordings
14
+ - 🖱️ **Cursor Tracking** - Track mouse position, cursor types, and click events
14
15
 
15
16
  🎵 **Granular Audio Controls**
16
17
 
@@ -231,6 +232,64 @@ const thumbnail = await recorder.getDisplayThumbnail(0, {
231
232
  // Perfect for display selection UI
232
233
  ```
233
234
 
235
+ ### Cursor Tracking Methods
236
+
237
+ #### `startCursorTracking(outputPath)`
238
+
239
+ Starts tracking cursor movements and saves data to JSON file.
240
+
241
+ ```javascript
242
+ await recorder.startCursorTracking("./cursor-data.json");
243
+ // Cursor tracking started - will record position, cursor type, and events
244
+ ```
245
+
246
+ #### `stopCursorTracking()`
247
+
248
+ Stops cursor tracking and saves collected data.
249
+
250
+ ```javascript
251
+ await recorder.stopCursorTracking();
252
+ // Data saved to specified JSON file
253
+ ```
254
+
255
+ #### `getCursorPosition()`
256
+
257
+ Gets current cursor position and type.
258
+
259
+ ```javascript
260
+ const position = recorder.getCursorPosition();
261
+ console.log(position);
262
+ // {
263
+ // x: 1234,
264
+ // y: 567,
265
+ // cursorType: "default" // "default", "pointer", "grabbing", "text"
266
+ // }
267
+ ```
268
+
269
+ #### `getCursorTrackingStatus()`
270
+
271
+ Returns cursor tracking status and data count.
272
+
273
+ ```javascript
274
+ const status = recorder.getCursorTrackingStatus();
275
+ console.log(status);
276
+ // {
277
+ // isTracking: true,
278
+ // dataCount: 1250,
279
+ // hasEventTap: true,
280
+ // hasRunLoopSource: true
281
+ // }
282
+ ```
283
+
284
+ #### `saveCursorData(outputPath)`
285
+
286
+ Manually saves current cursor data to file.
287
+
288
+ ```javascript
289
+ await recorder.saveCursorData("./cursor-backup.json");
290
+ // Data saved to file
291
+ ```
292
+
234
293
  ## Usage Examples
235
294
 
236
295
  ### Window-Specific Recording
@@ -404,6 +463,99 @@ async function createDisplaySelector() {
404
463
  }
405
464
  ```
406
465
 
466
+ ### Cursor Tracking Usage
467
+
468
+ ```javascript
469
+ const recorder = new MacRecorder();
470
+
471
+ async function trackUserInteraction() {
472
+ // Start cursor tracking
473
+ await recorder.startCursorTracking("./user-interactions.json");
474
+ console.log("Cursor tracking started...");
475
+
476
+ // Monitor real-time cursor position
477
+ const monitorInterval = setInterval(() => {
478
+ const position = recorder.getCursorPosition();
479
+ console.log(
480
+ `Cursor: ${position.x}, ${position.y} (${position.cursorType})`
481
+ );
482
+
483
+ const status = recorder.getCursorTrackingStatus();
484
+ console.log(`Tracking status: ${status.dataCount} positions recorded`);
485
+ }, 100); // Check every 100ms
486
+
487
+ // Track for 10 seconds
488
+ setTimeout(async () => {
489
+ clearInterval(monitorInterval);
490
+
491
+ // Stop tracking and save data
492
+ await recorder.stopCursorTracking();
493
+ console.log("Cursor tracking completed!");
494
+
495
+ // Load and analyze the data
496
+ const fs = require("fs");
497
+ const data = JSON.parse(
498
+ fs.readFileSync("./user-interactions.json", "utf8")
499
+ );
500
+
501
+ console.log(`Total interactions recorded: ${data.length}`);
502
+
503
+ // Analyze cursor types
504
+ const cursorTypes = {};
505
+ data.forEach((item) => {
506
+ cursorTypes[item.cursorType] = (cursorTypes[item.cursorType] || 0) + 1;
507
+ });
508
+
509
+ console.log("Cursor types distribution:", cursorTypes);
510
+
511
+ // Analyze event types
512
+ const eventTypes = {};
513
+ data.forEach((item) => {
514
+ eventTypes[item.type] = (eventTypes[item.type] || 0) + 1;
515
+ });
516
+
517
+ console.log("Event types distribution:", eventTypes);
518
+ }, 10000);
519
+ }
520
+
521
+ trackUserInteraction();
522
+ ```
523
+
524
+ ### Combined Screen Recording + Cursor Tracking
525
+
526
+ ```javascript
527
+ const recorder = new MacRecorder();
528
+
529
+ async function recordWithCursorTracking() {
530
+ // Start both screen recording and cursor tracking
531
+ await Promise.all([
532
+ recorder.startRecording("./screen-recording.mov", {
533
+ captureCursor: false, // Don't show cursor in video
534
+ includeSystemAudio: true,
535
+ quality: "high",
536
+ }),
537
+ recorder.startCursorTracking("./cursor-data.json"),
538
+ ]);
539
+
540
+ console.log("Recording screen and tracking cursor...");
541
+
542
+ // Record for 30 seconds
543
+ setTimeout(async () => {
544
+ await Promise.all([
545
+ recorder.stopRecording(),
546
+ recorder.stopCursorTracking(),
547
+ ]);
548
+
549
+ console.log("Screen recording and cursor tracking completed!");
550
+ console.log("Files created:");
551
+ console.log("- screen-recording.mov");
552
+ console.log("- cursor-data.json");
553
+ }, 30000);
554
+ }
555
+
556
+ recordWithCursorTracking();
557
+ ```
558
+
407
559
  ## Integration Examples
408
560
 
409
561
  ### Electron Integration
@@ -577,6 +729,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
577
729
 
578
730
  ### Latest Updates
579
731
 
732
+ - ✅ **Cursor Tracking**: Track mouse position, cursor types, and click events with JSON export
580
733
  - ✅ **Window Recording**: Automatic coordinate conversion for multi-display setups
581
734
  - ✅ **Audio Controls**: Separate microphone and system audio controls
582
735
  - ✅ **Display Selection**: Multi-monitor support with automatic detection
package/binding.gyp CHANGED
@@ -5,7 +5,8 @@
5
5
  "sources": [
6
6
  "src/mac_recorder.mm",
7
7
  "src/screen_capture.mm",
8
- "src/audio_capture.mm"
8
+ "src/audio_capture.mm",
9
+ "src/cursor_tracker.mm"
9
10
  ],
10
11
  "include_dirs": [
11
12
  "<!@(node -p \"require('node-addon-api').include\")"
@@ -30,7 +31,10 @@
30
31
  "-framework CoreVideo",
31
32
  "-framework Foundation",
32
33
  "-framework AppKit",
33
- "-framework ScreenCaptureKit"
34
+ "-framework ScreenCaptureKit",
35
+ "-framework ApplicationServices",
36
+ "-framework Carbon",
37
+ "-framework Accessibility"
34
38
  ]
35
39
  },
36
40
  "defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ]
package/cursor-test.js ADDED
@@ -0,0 +1,176 @@
1
+ const MacRecorder = require("./index");
2
+ const path = require("path");
3
+ const fs = require("fs");
4
+
5
+ async function testCursorTracking() {
6
+ console.log("🎯 Cursor Tracking Test Başlatılıyor...\n");
7
+
8
+ const recorder = new MacRecorder();
9
+
10
+ try {
11
+ // Cursor tracking başlat
12
+ const outputPath = path.join(__dirname, "cursor-data.json");
13
+ console.log("📍 Cursor tracking başlatılıyor...");
14
+ console.log("📁 Output dosyası:", outputPath);
15
+
16
+ await recorder.startCursorTracking(outputPath);
17
+ console.log("✅ Cursor tracking başlatıldı!");
18
+
19
+ // Durum kontrolü
20
+ const status = recorder.getCursorTrackingStatus();
21
+ console.log("📊 Tracking durumu:", status);
22
+
23
+ // 5 saniye bekle ve pozisyon örnekleri al
24
+ console.log(
25
+ "\n🎬 5 saniye boyunca cursor hareketlerinizi takip ediyoruz..."
26
+ );
27
+ console.log("💡 Fare hareket ettirin, tıklayın ve sürükleyin!");
28
+
29
+ // Manuel data collection - JavaScript tarafında polling
30
+ const manualData = [];
31
+ const startTime = Date.now();
32
+
33
+ for (let i = 5; i > 0; i--) {
34
+ console.log(`⏳ ${i} saniye kaldı...`);
35
+
36
+ // 100ms'de bir pozisyon al (10 FPS)
37
+ for (let j = 0; j < 10; j++) {
38
+ const position = recorder.getCursorPosition();
39
+ const timestamp = Date.now() - startTime;
40
+
41
+ manualData.push({
42
+ x: position.x,
43
+ y: position.y,
44
+ timestamp: timestamp,
45
+ cursorType: position.cursorType,
46
+ type: "move",
47
+ });
48
+
49
+ await new Promise((resolve) => setTimeout(resolve, 100));
50
+ }
51
+
52
+ // Son pozisyonu göster
53
+ if (manualData.length > 0) {
54
+ const lastPos = manualData[manualData.length - 1];
55
+ console.log(
56
+ `📌 Pozisyon: x=${lastPos.x}, y=${lastPos.y}, tip=${lastPos.cursorType}`
57
+ );
58
+ }
59
+ }
60
+
61
+ // Manuel veriyi dosyaya kaydet
62
+ console.log(
63
+ `\n📝 Manuel data collection: ${manualData.length} pozisyon toplandı`
64
+ );
65
+ const manualPath = path.join(__dirname, "manual-cursor-data.json");
66
+ fs.writeFileSync(manualPath, JSON.stringify(manualData, null, 2));
67
+ console.log(`📄 Manuel veriler kaydedildi: ${manualPath}`);
68
+
69
+ // Tracking durdur
70
+ console.log("\n🛑 Cursor tracking durduruluyor...");
71
+ await recorder.stopCursorTracking();
72
+ console.log("✅ Cursor tracking durduruldu!");
73
+
74
+ // Final durum kontrolü
75
+ const finalStatus = recorder.getCursorTrackingStatus();
76
+ console.log("📊 Final durumu:", finalStatus);
77
+
78
+ // Dosya kontrolü
79
+ if (fs.existsSync(outputPath)) {
80
+ const data = JSON.parse(fs.readFileSync(outputPath, "utf8"));
81
+ console.log(
82
+ `\n📄 JSON dosyası oluşturuldu: ${data.length} adet cursor verisi kaydedildi`
83
+ );
84
+
85
+ // İlk birkaç veriyi göster
86
+ if (data.length > 0) {
87
+ console.log("\n📝 İlk 3 cursor verisi:");
88
+ data.slice(0, 3).forEach((item, index) => {
89
+ console.log(
90
+ `${index + 1}. x:${item.x}, y:${item.y}, timestamp:${
91
+ item.timestamp
92
+ }, cursorType:${item.cursorType}, type:${item.type}`
93
+ );
94
+ });
95
+
96
+ if (data.length > 3) {
97
+ console.log(`... ve ${data.length - 3} adet daha`);
98
+ }
99
+ }
100
+
101
+ // Cursor tipleri istatistiği
102
+ const cursorTypes = {};
103
+ const eventTypes = {};
104
+ data.forEach((item) => {
105
+ cursorTypes[item.cursorType] = (cursorTypes[item.cursorType] || 0) + 1;
106
+ eventTypes[item.type] = (eventTypes[item.type] || 0) + 1;
107
+ });
108
+
109
+ console.log("\n📈 Cursor Tipleri İstatistiği:");
110
+ Object.keys(cursorTypes).forEach((type) => {
111
+ console.log(` ${type}: ${cursorTypes[type]} adet`);
112
+ });
113
+
114
+ console.log("\n🎭 Event Tipleri İstatistiği:");
115
+ Object.keys(eventTypes).forEach((type) => {
116
+ console.log(` ${type}: ${eventTypes[type]} adet`);
117
+ });
118
+ } else {
119
+ console.log("❌ JSON dosyası oluşturulamadı!");
120
+ }
121
+ } catch (error) {
122
+ console.error("❌ Hata:", error.message);
123
+ }
124
+
125
+ console.log("\n🎉 Test tamamlandı!");
126
+ }
127
+
128
+ // Ek fonksiyonlar test et
129
+ async function testCursorPositionOnly() {
130
+ console.log("\n🎯 Anlık Cursor Pozisyon Testi...");
131
+
132
+ const recorder = new MacRecorder();
133
+
134
+ try {
135
+ for (let i = 0; i < 5; i++) {
136
+ const position = recorder.getCursorPosition();
137
+ console.log(
138
+ `📌 Pozisyon ${i + 1}: x=${position.x}, y=${position.y}, tip=${
139
+ position.cursorType
140
+ }`
141
+ );
142
+ await new Promise((resolve) => setTimeout(resolve, 500));
143
+ }
144
+ } catch (error) {
145
+ console.error("❌ Pozisyon alma hatası:", error.message);
146
+ }
147
+ }
148
+
149
+ // Test menüsü
150
+ async function main() {
151
+ console.log("🚀 MacRecorder Cursor Tracking Test Menüsü\n");
152
+
153
+ const args = process.argv.slice(2);
154
+
155
+ if (args.includes("--position")) {
156
+ await testCursorPositionOnly();
157
+ } else if (args.includes("--full")) {
158
+ await testCursorTracking();
159
+ } else {
160
+ console.log("Kullanım:");
161
+ console.log(
162
+ " node cursor-test.js --full # Tam cursor tracking testi (5 saniye)"
163
+ );
164
+ console.log(
165
+ " node cursor-test.js --position # Sadece anlık pozisyon testi"
166
+ );
167
+ console.log("\nÖrnek:");
168
+ console.log(" node cursor-test.js --full");
169
+ }
170
+ }
171
+
172
+ if (require.main === module) {
173
+ main().catch(console.error);
174
+ }
175
+
176
+ module.exports = { testCursorTracking, testCursorPositionOnly };
package/index.js CHANGED
@@ -413,6 +413,96 @@ class MacRecorder extends EventEmitter {
413
413
  });
414
414
  }
415
415
 
416
+ /**
417
+ * Cursor tracking başlatır
418
+ */
419
+ async startCursorTracking(outputPath) {
420
+ if (!outputPath) {
421
+ throw new Error("Output path is required");
422
+ }
423
+
424
+ return new Promise((resolve, reject) => {
425
+ try {
426
+ const success = nativeBinding.startCursorTracking(outputPath);
427
+ if (success) {
428
+ this.emit("cursorTrackingStarted", outputPath);
429
+ resolve(true);
430
+ } else {
431
+ reject(
432
+ new Error(
433
+ "Failed to start cursor tracking. Check permissions and try again."
434
+ )
435
+ );
436
+ }
437
+ } catch (error) {
438
+ reject(error);
439
+ }
440
+ });
441
+ }
442
+
443
+ /**
444
+ * Cursor tracking durdurur ve JSON dosyasını kaydeder
445
+ */
446
+ async stopCursorTracking() {
447
+ return new Promise((resolve, reject) => {
448
+ try {
449
+ const success = nativeBinding.stopCursorTracking();
450
+ if (success) {
451
+ this.emit("cursorTrackingStopped");
452
+ resolve(true);
453
+ } else {
454
+ reject(new Error("Failed to stop cursor tracking"));
455
+ }
456
+ } catch (error) {
457
+ reject(error);
458
+ }
459
+ });
460
+ }
461
+
462
+ /**
463
+ * Anlık cursor pozisyonunu ve tipini döndürür
464
+ */
465
+ getCursorPosition() {
466
+ try {
467
+ return nativeBinding.getCursorPosition();
468
+ } catch (error) {
469
+ throw new Error("Failed to get cursor position: " + error.message);
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Cursor tracking durumunu döndürür
475
+ */
476
+ getCursorTrackingStatus() {
477
+ try {
478
+ return nativeBinding.getCursorTrackingStatus();
479
+ } catch (error) {
480
+ throw new Error("Failed to get cursor tracking status: " + error.message);
481
+ }
482
+ }
483
+
484
+ /**
485
+ * Cursor verilerini JSON dosyasına kaydeder
486
+ */
487
+ async saveCursorData(outputPath) {
488
+ if (!outputPath) {
489
+ throw new Error("Output path is required");
490
+ }
491
+
492
+ return new Promise((resolve, reject) => {
493
+ try {
494
+ const success = nativeBinding.saveCursorData(outputPath);
495
+ if (success) {
496
+ resolve(true);
497
+ } else {
498
+ reject(new Error("Failed to save cursor data"));
499
+ }
500
+ } catch (error) {
501
+ reject(error);
502
+ }
503
+ });
504
+ }
505
+
416
506
  /**
417
507
  * Native modül bilgilerini döndürür
418
508
  */