node-mac-recorder 1.2.0 → 1.2.1

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,93 @@
1
+ [
2
+ {
3
+ "x": 1119,
4
+ "y": 1090,
5
+ "timestamp": 106,
6
+ "cursorType": "default",
7
+ "type": "move"
8
+ },
9
+ {
10
+ "x": 1119,
11
+ "y": 1080,
12
+ "timestamp": 407,
13
+ "cursorType": "default",
14
+ "type": "move"
15
+ },
16
+ {
17
+ "x": 1119,
18
+ "y": 1066,
19
+ "timestamp": 423,
20
+ "cursorType": "default",
21
+ "type": "move"
22
+ },
23
+ {
24
+ "x": 1119,
25
+ "y": 1042,
26
+ "timestamp": 446,
27
+ "cursorType": "default",
28
+ "type": "move"
29
+ },
30
+ {
31
+ "x": 1119,
32
+ "y": 968,
33
+ "timestamp": 466,
34
+ "cursorType": "default",
35
+ "type": "move"
36
+ },
37
+ {
38
+ "x": 1129,
39
+ "y": 906,
40
+ "timestamp": 487,
41
+ "cursorType": "default",
42
+ "type": "move"
43
+ },
44
+ {
45
+ "x": 1153,
46
+ "y": 820,
47
+ "timestamp": 507,
48
+ "cursorType": "default",
49
+ "type": "move"
50
+ },
51
+ {
52
+ "x": 1168,
53
+ "y": 782,
54
+ "timestamp": 528,
55
+ "cursorType": "default",
56
+ "type": "move"
57
+ },
58
+ {
59
+ "x": 1176,
60
+ "y": 764,
61
+ "timestamp": 549,
62
+ "cursorType": "default",
63
+ "type": "move"
64
+ },
65
+ {
66
+ "x": 1176,
67
+ "y": 764,
68
+ "timestamp": 2578,
69
+ "cursorType": "default",
70
+ "type": "mousedown"
71
+ },
72
+ {
73
+ "x": 1176,
74
+ "y": 764,
75
+ "timestamp": 2599,
76
+ "cursorType": "default",
77
+ "type": "move"
78
+ },
79
+ {
80
+ "x": 1176,
81
+ "y": 764,
82
+ "timestamp": 2724,
83
+ "cursorType": "default",
84
+ "type": "mouseup"
85
+ },
86
+ {
87
+ "x": 1176,
88
+ "y": 764,
89
+ "timestamp": 2744,
90
+ "cursorType": "default",
91
+ "type": "move"
92
+ }
93
+ ]
package/cursor-test.js CHANGED
@@ -2,175 +2,49 @@ const MacRecorder = require("./index");
2
2
  const path = require("path");
3
3
  const fs = require("fs");
4
4
 
5
- async function testCursorTracking() {
6
- console.log("🎯 Cursor Tracking Test Başlatılıyor...\n");
5
+ async function testCursorCapture() {
6
+ console.log("🎯 Cursor Capture Demo\n");
7
7
 
8
8
  const recorder = new MacRecorder();
9
+ const outputPath = path.join(__dirname, "cursor-data.json");
9
10
 
10
11
  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);
12
+ // Başlat
13
+ await recorder.startCursorCapture(outputPath);
14
+ console.log(" Kayıt başladı...");
15
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();
16
+ // 5 saniye bekle
17
+ console.log("📱 5 saniye hareket ettirin, tıklayın...");
32
18
 
33
19
  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
- }
20
+ process.stdout.write(`⏳ ${i}... `);
21
+ await new Promise((resolve) => setTimeout(resolve, 1000));
59
22
  }
23
+ console.log("\n");
60
24
 
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!");
25
+ // Durdur
26
+ await recorder.stopCursorCapture();
27
+ console.log("✅ Kayıt tamamlandı!");
73
28
 
74
- // Final durum kontrolü
75
- const finalStatus = recorder.getCursorTrackingStatus();
76
- console.log("📊 Final durumu:", finalStatus);
77
-
78
- // Dosya kontrolü
29
+ // Sonuç
79
30
  if (fs.existsSync(outputPath)) {
80
31
  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
- });
32
+ console.log(`📄 ${data.length} event kaydedildi -> ${outputPath}`);
95
33
 
96
- if (data.length > 3) {
97
- console.log(`... ve ${data.length - 3} adet daha`);
98
- }
34
+ // Basit istatistik
35
+ const clicks = data.filter((d) => d.type === "mousedown").length;
36
+ if (clicks > 0) {
37
+ console.log(`🖱️ ${clicks} click algılandı`);
99
38
  }
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
39
  }
121
40
  } catch (error) {
122
41
  console.error("❌ Hata:", error.message);
123
42
  }
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
43
  }
171
44
 
45
+ // Direkt çalıştır
172
46
  if (require.main === module) {
173
- main().catch(console.error);
47
+ testCursorCapture().catch(console.error);
174
48
  }
175
49
 
176
- module.exports = { testCursorTracking, testCursorPositionOnly };
50
+ module.exports = { testCursorCapture };
package/index.js CHANGED
@@ -25,6 +25,14 @@ class MacRecorder extends EventEmitter {
25
25
  this.outputPath = null;
26
26
  this.recordingTimer = null;
27
27
  this.recordingStartTime = null;
28
+
29
+ // Cursor capture variables
30
+ this.cursorCaptureInterval = null;
31
+ this.cursorCaptureFile = null;
32
+ this.cursorCaptureStartTime = null;
33
+ this.cursorCaptureFirstWrite = true;
34
+ this.lastCapturedData = null;
35
+
28
36
  this.options = {
29
37
  includeMicrophone: false, // Default olarak mikrofon kapalı
30
38
  includeSystemAudio: true, // Default olarak sistem sesi açık
@@ -414,26 +422,96 @@ class MacRecorder extends EventEmitter {
414
422
  }
415
423
 
416
424
  /**
417
- * Cursor tracking başlatır
425
+ * Event'in kaydedilip kaydedilmeyeceğini belirler
418
426
  */
419
- async startCursorTracking(outputPath) {
420
- if (!outputPath) {
421
- throw new Error("Output path is required");
427
+ shouldCaptureEvent(currentData) {
428
+ if (!this.lastCapturedData) {
429
+ return true; // İlk event
430
+ }
431
+
432
+ const last = this.lastCapturedData;
433
+
434
+ // Event type değişmişse
435
+ if (currentData.type !== last.type) {
436
+ return true;
437
+ }
438
+
439
+ // Pozisyon değişmişse (minimum 2 pixel tolerans)
440
+ if (
441
+ Math.abs(currentData.x - last.x) >= 2 ||
442
+ Math.abs(currentData.y - last.y) >= 2
443
+ ) {
444
+ return true;
445
+ }
446
+
447
+ // Cursor type değişmişse
448
+ if (currentData.cursorType !== last.cursorType) {
449
+ return true;
450
+ }
451
+
452
+ // Hiçbir değişiklik yoksa kaydetme
453
+ return false;
454
+ }
455
+
456
+ /**
457
+ * Cursor capture başlatır - otomatik olarak dosyaya yazmaya başlar
458
+ */
459
+ async startCursorCapture(filepath) {
460
+ if (!filepath) {
461
+ throw new Error("File path is required");
462
+ }
463
+
464
+ if (this.cursorCaptureInterval) {
465
+ throw new Error("Cursor capture is already running");
422
466
  }
423
467
 
424
468
  return new Promise((resolve, reject) => {
425
469
  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
- }
470
+ // Dosyayı oluştur ve temizle
471
+ const fs = require("fs");
472
+ fs.writeFileSync(filepath, "[");
473
+
474
+ this.cursorCaptureFile = filepath;
475
+ this.cursorCaptureStartTime = Date.now();
476
+ this.cursorCaptureFirstWrite = true;
477
+ this.lastCapturedData = null;
478
+
479
+ // JavaScript interval ile polling yap (daha sık - mouse event'leri yakalamak için)
480
+ this.cursorCaptureInterval = setInterval(() => {
481
+ try {
482
+ const position = nativeBinding.getCursorPosition();
483
+ const timestamp = Date.now() - this.cursorCaptureStartTime;
484
+
485
+ const cursorData = {
486
+ x: position.x,
487
+ y: position.y,
488
+ timestamp: timestamp,
489
+ cursorType: position.cursorType,
490
+ type: position.eventType || "move",
491
+ };
492
+
493
+ // Sadece eventType değiştiğinde veya pozisyon değiştiğinde kaydet
494
+ if (this.shouldCaptureEvent(cursorData)) {
495
+ // Dosyaya ekle
496
+ const jsonString = JSON.stringify(cursorData);
497
+
498
+ if (this.cursorCaptureFirstWrite) {
499
+ fs.appendFileSync(filepath, jsonString);
500
+ this.cursorCaptureFirstWrite = false;
501
+ } else {
502
+ fs.appendFileSync(filepath, "," + jsonString);
503
+ }
504
+
505
+ // Son pozisyonu sakla
506
+ this.lastCapturedData = { ...cursorData };
507
+ }
508
+ } catch (error) {
509
+ console.error("Cursor capture error:", error);
510
+ }
511
+ }, 20); // 50 FPS - mouse event'leri yakalamak için daha hızlı
512
+
513
+ this.emit("cursorCaptureStarted", filepath);
514
+ resolve(true);
437
515
  } catch (error) {
438
516
  reject(error);
439
517
  }
@@ -441,18 +519,33 @@ class MacRecorder extends EventEmitter {
441
519
  }
442
520
 
443
521
  /**
444
- * Cursor tracking durdurur ve JSON dosyasını kaydeder
522
+ * Cursor capture durdurur - dosya yazma işlemini sonlandırır
445
523
  */
446
- async stopCursorTracking() {
524
+ async stopCursorCapture() {
447
525
  return new Promise((resolve, reject) => {
448
526
  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"));
527
+ if (!this.cursorCaptureInterval) {
528
+ return resolve(false);
529
+ }
530
+
531
+ // Interval'ı durdur
532
+ clearInterval(this.cursorCaptureInterval);
533
+ this.cursorCaptureInterval = null;
534
+
535
+ // Dosyayı kapat
536
+ if (this.cursorCaptureFile) {
537
+ const fs = require("fs");
538
+ fs.appendFileSync(this.cursorCaptureFile, "]");
539
+ this.cursorCaptureFile = null;
455
540
  }
541
+
542
+ // Değişkenleri temizle
543
+ this.lastCapturedData = null;
544
+ this.cursorCaptureStartTime = null;
545
+ this.cursorCaptureFirstWrite = true;
546
+
547
+ this.emit("cursorCaptureStopped");
548
+ resolve(true);
456
549
  } catch (error) {
457
550
  reject(error);
458
551
  }
@@ -471,36 +564,14 @@ class MacRecorder extends EventEmitter {
471
564
  }
472
565
 
473
566
  /**
474
- * Cursor tracking durumunu döndürür
567
+ * Cursor capture durumunu döndürür
475
568
  */
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
- });
569
+ getCursorCaptureStatus() {
570
+ return {
571
+ isCapturing: !!this.cursorCaptureInterval,
572
+ outputFile: this.cursorCaptureFile || null,
573
+ startTime: this.cursorCaptureStartTime || null,
574
+ };
504
575
  }
505
576
 
506
577
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -8,16 +8,18 @@
8
8
 
9
9
  // Global state for cursor tracking
10
10
  static bool g_isCursorTracking = false;
11
- static NSMutableArray *g_cursorData = nil;
12
11
  static CFMachPortRef g_eventTap = NULL;
13
12
  static CFRunLoopSourceRef g_runLoopSource = NULL;
14
13
  static NSDate *g_trackingStartTime = nil;
15
14
  static NSString *g_outputPath = nil;
16
15
  static NSTimer *g_cursorTimer = nil;
17
16
  static int g_debugCallbackCount = 0;
17
+ static NSFileHandle *g_fileHandle = nil;
18
+ static bool g_isFirstWrite = true;
18
19
 
19
20
  // Forward declaration
20
- void cursorTimerCallback(NSTimer *timer);
21
+ void cursorTimerCallback();
22
+ void writeToFile(NSDictionary *cursorData);
21
23
 
22
24
  // Timer helper class
23
25
  @interface CursorTimerTarget : NSObject
@@ -26,7 +28,7 @@ void cursorTimerCallback(NSTimer *timer);
26
28
 
27
29
  @implementation CursorTimerTarget
28
30
  - (void)timerCallback:(NSTimer *)timer {
29
- cursorTimerCallback(timer);
31
+ cursorTimerCallback();
30
32
  }
31
33
  @end
32
34
 
@@ -36,31 +38,87 @@ static CursorTimerTarget *g_timerTarget = nil;
36
38
  static NSString *g_lastDetectedCursorType = nil;
37
39
  static int g_cursorTypeCounter = 0;
38
40
 
39
- // Cursor type detection helper
41
+ // Mouse button state tracking
42
+ static bool g_leftMouseDown = false;
43
+ static bool g_rightMouseDown = false;
44
+ static NSString *g_lastEventType = @"move";
45
+
46
+ // Cursor type detection helper - gerçek cursor type'ı al
40
47
  NSString* getCursorType() {
41
48
  @autoreleasepool {
42
49
  g_cursorTypeCounter++;
43
50
 
44
- // Simple simulation - cycle through cursor types for demo
45
- // Bu gerçek uygulamada daha akıllı olacak
46
- int typeIndex = (g_cursorTypeCounter / 6) % 4; // Her 6 call'da değiştir (daha hızlı demo)
47
-
48
- switch (typeIndex) {
49
- case 0:
51
+ @try {
52
+ // NSCursor.currentCursor kullanarak gerçek cursor type'ı al
53
+ NSCursor *currentCursor = [NSCursor currentCursor];
54
+
55
+ if (currentCursor == [NSCursor arrowCursor]) {
50
56
  g_lastDetectedCursorType = @"default";
51
57
  return @"default";
52
- case 1:
58
+ } else if (currentCursor == [NSCursor pointingHandCursor]) {
53
59
  g_lastDetectedCursorType = @"pointer";
54
60
  return @"pointer";
55
- case 2:
61
+ } else if (currentCursor == [NSCursor IBeamCursor]) {
56
62
  g_lastDetectedCursorType = @"text";
57
63
  return @"text";
58
- case 3:
64
+ } else if (currentCursor == [NSCursor openHandCursor]) {
65
+ g_lastDetectedCursorType = @"grab";
66
+ return @"grab";
67
+ } else if (currentCursor == [NSCursor closedHandCursor]) {
59
68
  g_lastDetectedCursorType = @"grabbing";
60
69
  return @"grabbing";
61
- default:
70
+ } else if (currentCursor == [NSCursor resizeLeftRightCursor]) {
71
+ g_lastDetectedCursorType = @"ew-resize";
72
+ return @"ew-resize";
73
+ } else if (currentCursor == [NSCursor resizeUpDownCursor]) {
74
+ g_lastDetectedCursorType = @"ns-resize";
75
+ return @"ns-resize";
76
+ } else if (currentCursor == [NSCursor crosshairCursor]) {
77
+ g_lastDetectedCursorType = @"crosshair";
78
+ return @"crosshair";
79
+ } else {
80
+ // Bilinmeyen cursor - default olarak dön
62
81
  g_lastDetectedCursorType = @"default";
63
82
  return @"default";
83
+ }
84
+ } @catch (NSException *exception) {
85
+ // Hata durumunda default dön
86
+ g_lastDetectedCursorType = @"default";
87
+ return @"default";
88
+ }
89
+ }
90
+ }
91
+
92
+ // Dosyaya yazma helper fonksiyonu
93
+ void writeToFile(NSDictionary *cursorData) {
94
+ @autoreleasepool {
95
+ if (!g_fileHandle || !cursorData) {
96
+ return;
97
+ }
98
+
99
+ @try {
100
+ NSError *error;
101
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:cursorData
102
+ options:0
103
+ error:&error];
104
+ if (jsonData && !error) {
105
+ NSString *jsonString = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] autorelease];
106
+
107
+ if (g_isFirstWrite) {
108
+ // İlk yazma - array başlat
109
+ [g_fileHandle writeData:[@"[" dataUsingEncoding:NSUTF8StringEncoding]];
110
+ [g_fileHandle writeData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]];
111
+ g_isFirstWrite = false;
112
+ } else {
113
+ // Sonraki yazmalar - virgül + json
114
+ [g_fileHandle writeData:[@"," dataUsingEncoding:NSUTF8StringEncoding]];
115
+ [g_fileHandle writeData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]];
116
+ }
117
+
118
+ [g_fileHandle synchronizeFile];
119
+ }
120
+ } @catch (NSException *exception) {
121
+ // Hata durumunda sessizce devam et
64
122
  }
65
123
  }
66
124
  }
@@ -68,7 +126,9 @@ NSString* getCursorType() {
68
126
  // Event callback for mouse events
69
127
  CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
70
128
  @autoreleasepool {
71
- if (!g_isCursorTracking || !g_cursorData || !g_trackingStartTime) {
129
+ g_debugCallbackCount++; // Callback çağrıldığını say
130
+
131
+ if (!g_isCursorTracking || !g_trackingStartTime || !g_fileHandle) {
72
132
  return event;
73
133
  }
74
134
 
@@ -109,21 +169,19 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
109
169
  @"type": eventType
110
170
  };
111
171
 
112
- // Thread-safe olarak array'e ekle
113
- @synchronized(g_cursorData) {
114
- [g_cursorData addObject:cursorInfo];
115
- }
172
+ // Direkt dosyaya yaz
173
+ writeToFile(cursorInfo);
116
174
 
117
175
  return event;
118
176
  }
119
177
  }
120
178
 
121
179
  // Timer callback for periodic cursor position updates
122
- void cursorTimerCallback(NSTimer *timer) {
180
+ void cursorTimerCallback() {
123
181
  @autoreleasepool {
124
- g_debugCallbackCount++;
182
+ g_debugCallbackCount++; // Timer callback çağrıldığını say
125
183
 
126
- if (!g_isCursorTracking || !g_cursorData || !g_trackingStartTime) {
184
+ if (!g_isCursorTracking || !g_trackingStartTime || !g_fileHandle) {
127
185
  return;
128
186
  }
129
187
 
@@ -155,10 +213,8 @@ void cursorTimerCallback(NSTimer *timer) {
155
213
  @"type": @"move"
156
214
  };
157
215
 
158
- // Thread-safe olarak array'e ekle
159
- @synchronized(g_cursorData) {
160
- [g_cursorData addObject:cursorInfo];
161
- }
216
+ // Direkt dosyaya yaz
217
+ writeToFile(cursorInfo);
162
218
  }
163
219
  }
164
220
 
@@ -166,39 +222,53 @@ void cursorTimerCallback(NSTimer *timer) {
166
222
  void cleanupCursorTracking() {
167
223
  g_isCursorTracking = false;
168
224
 
169
- // Timer durdur
225
+ // Timer temizle
170
226
  if (g_cursorTimer) {
171
227
  [g_cursorTimer invalidate];
172
228
  g_cursorTimer = nil;
173
229
  }
174
230
 
175
- // Timer target'ı temizle
176
231
  if (g_timerTarget) {
177
- [g_timerTarget release];
232
+ [g_timerTarget autorelease];
178
233
  g_timerTarget = nil;
179
234
  }
180
235
 
181
- // Event tap'i durdur
236
+ // Dosyayı önce kapat (en önemli işlem)
237
+ if (g_fileHandle) {
238
+ @try {
239
+ if (g_isFirstWrite) {
240
+ // Hiç veri yazılmamışsa boş array
241
+ [g_fileHandle writeData:[@"[]" dataUsingEncoding:NSUTF8StringEncoding]];
242
+ } else {
243
+ // JSON array'i kapat
244
+ [g_fileHandle writeData:[@"]" dataUsingEncoding:NSUTF8StringEncoding]];
245
+ }
246
+ [g_fileHandle synchronizeFile];
247
+ [g_fileHandle closeFile];
248
+ } @catch (NSException *exception) {
249
+ // Dosya işlemi hata verirse sessizce devam et
250
+ }
251
+ g_fileHandle = nil;
252
+ }
253
+
254
+ // Event tap'i durdur (non-blocking)
182
255
  if (g_eventTap) {
183
256
  CGEventTapEnable(g_eventTap, false);
184
- CFMachPortInvalidate(g_eventTap);
185
- CFRelease(g_eventTap);
186
- g_eventTap = NULL;
257
+ g_eventTap = NULL; // CFRelease işlemini yapmıyoruz - system handle etsin
187
258
  }
188
259
 
189
- // Run loop source'unu kaldır
260
+ // Run loop source'unu kaldır (non-blocking)
190
261
  if (g_runLoopSource) {
191
- CFRunLoopRemoveSource(CFRunLoopGetCurrent(), g_runLoopSource, kCFRunLoopCommonModes);
192
- CFRelease(g_runLoopSource);
193
- g_runLoopSource = NULL;
262
+ g_runLoopSource = NULL; // CFRelease işlemini yapmıyoruz
194
263
  }
195
264
 
196
- g_cursorData = nil;
265
+ // Global değişkenleri sıfırla
197
266
  g_trackingStartTime = nil;
198
267
  g_outputPath = nil;
199
268
  g_debugCallbackCount = 0;
200
269
  g_lastDetectedCursorType = nil;
201
270
  g_cursorTypeCounter = 0;
271
+ g_isFirstWrite = true;
202
272
  }
203
273
 
204
274
  // NAPI Function: Start Cursor Tracking
@@ -217,10 +287,25 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
217
287
  std::string outputPath = info[0].As<Napi::String>().Utf8Value();
218
288
 
219
289
  @try {
220
- // Initialize cursor data array
221
- g_cursorData = [[NSMutableArray alloc] init];
222
- g_trackingStartTime = [NSDate date];
290
+ // Dosyayı oluştur ve
223
291
  g_outputPath = [NSString stringWithUTF8String:outputPath.c_str()];
292
+ g_fileHandle = [[NSFileHandle fileHandleForWritingAtPath:g_outputPath] retain];
293
+
294
+ if (!g_fileHandle) {
295
+ // Dosya yoksa oluştur
296
+ [[NSFileManager defaultManager] createFileAtPath:g_outputPath contents:nil attributes:nil];
297
+ g_fileHandle = [[NSFileHandle fileHandleForWritingAtPath:g_outputPath] retain];
298
+ }
299
+
300
+ if (!g_fileHandle) {
301
+ return Napi::Boolean::New(env, false);
302
+ }
303
+
304
+ // Dosyayı temizle (baştan başla)
305
+ [g_fileHandle truncateFileAtOffset:0];
306
+ g_isFirstWrite = true;
307
+
308
+ g_trackingStartTime = [NSDate date];
224
309
 
225
310
  // Create event tap for mouse events
226
311
  CGEventMask eventMask = (CGEventMaskBit(kCGEventLeftMouseDown) |
@@ -244,23 +329,21 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
244
329
  if (g_eventTap) {
245
330
  // Event tap başarılı - detaylı event tracking aktif
246
331
  g_runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, g_eventTap, 0);
247
- CFRunLoopAddSource(CFRunLoopGetCurrent(), g_runLoopSource, kCFRunLoopCommonModes);
332
+ CFRunLoopAddSource(CFRunLoopGetMain(), g_runLoopSource, kCFRunLoopCommonModes);
248
333
  CGEventTapEnable(g_eventTap, true);
249
334
  }
250
- // Event tap başarısız olsa da devam et - sadece timer ile tracking yapar
251
335
 
252
- // Timer helper oluştur
336
+ // NSTimer kullan (main thread'de çalışır)
253
337
  g_timerTarget = [[CursorTimerTarget alloc] init];
254
338
 
255
- // NSTimer kullan (ana thread'de çalışır)
256
- g_cursorTimer = [NSTimer scheduledTimerWithTimeInterval:0.016667 // ~60 FPS
257
- target:g_timerTarget
258
- selector:@selector(timerCallback:)
259
- userInfo:nil
260
- repeats:YES];
339
+ g_cursorTimer = [NSTimer timerWithTimeInterval:0.05 // 50ms (20 FPS)
340
+ target:g_timerTarget
341
+ selector:@selector(timerCallback:)
342
+ userInfo:nil
343
+ repeats:YES];
261
344
 
262
- // Timer'ı farklı run loop mode'larında da çalıştır
263
- [[NSRunLoop currentRunLoop] addTimer:g_cursorTimer forMode:NSRunLoopCommonModes];
345
+ // Main run loop'a ekle
346
+ [[NSRunLoop mainRunLoop] addTimer:g_cursorTimer forMode:NSRunLoopCommonModes];
264
347
 
265
348
  g_isCursorTracking = true;
266
349
  return Napi::Boolean::New(env, true);
@@ -280,19 +363,6 @@ Napi::Value StopCursorTracking(const Napi::CallbackInfo& info) {
280
363
  }
281
364
 
282
365
  @try {
283
- // JSON dosyasını kaydet
284
- if (g_cursorData && g_outputPath) {
285
- @synchronized(g_cursorData) {
286
- NSError *error;
287
- NSData *jsonData = [NSJSONSerialization dataWithJSONObject:g_cursorData
288
- options:NSJSONWritingPrettyPrinted
289
- error:&error];
290
- if (jsonData && !error) {
291
- [jsonData writeToFile:g_outputPath atomically:YES];
292
- }
293
- }
294
- }
295
-
296
366
  cleanupCursorTracking();
297
367
  return Napi::Boolean::New(env, true);
298
368
 
@@ -319,10 +389,39 @@ Napi::Value GetCursorPosition(const Napi::CallbackInfo& info) {
319
389
 
320
390
  NSString *cursorType = getCursorType();
321
391
 
392
+ // Mouse button state'ini kontrol et
393
+ bool currentLeftMouseDown = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonLeft);
394
+ bool currentRightMouseDown = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonRight);
395
+
396
+ NSString *eventType = @"move";
397
+
398
+ // Mouse button state değişikliklerini tespit et
399
+ if (currentLeftMouseDown && !g_leftMouseDown) {
400
+ eventType = @"mousedown";
401
+ g_lastEventType = @"mousedown";
402
+ } else if (!currentLeftMouseDown && g_leftMouseDown) {
403
+ eventType = @"mouseup";
404
+ g_lastEventType = @"mouseup";
405
+ } else if (currentRightMouseDown && !g_rightMouseDown) {
406
+ eventType = @"rightmousedown";
407
+ g_lastEventType = @"rightmousedown";
408
+ } else if (!currentRightMouseDown && g_rightMouseDown) {
409
+ eventType = @"rightmouseup";
410
+ g_lastEventType = @"rightmouseup";
411
+ } else {
412
+ eventType = @"move";
413
+ g_lastEventType = @"move";
414
+ }
415
+
416
+ // State'i güncelle
417
+ g_leftMouseDown = currentLeftMouseDown;
418
+ g_rightMouseDown = currentRightMouseDown;
419
+
322
420
  Napi::Object result = Napi::Object::New(env);
323
421
  result.Set("x", Napi::Number::New(env, (int)location.x));
324
422
  result.Set("y", Napi::Number::New(env, (int)location.y));
325
423
  result.Set("cursorType", Napi::String::New(env, [cursorType UTF8String]));
424
+ result.Set("eventType", Napi::String::New(env, [eventType UTF8String]));
326
425
 
327
426
  return result;
328
427
 
@@ -337,63 +436,22 @@ Napi::Value GetCursorTrackingStatus(const Napi::CallbackInfo& info) {
337
436
 
338
437
  Napi::Object result = Napi::Object::New(env);
339
438
  result.Set("isTracking", Napi::Boolean::New(env, g_isCursorTracking));
340
-
341
- NSUInteger dataCount = 0;
342
- if (g_cursorData) {
343
- @synchronized(g_cursorData) {
344
- dataCount = [g_cursorData count];
345
- }
346
- }
347
-
348
- result.Set("dataCount", Napi::Number::New(env, (int)dataCount));
349
439
  result.Set("hasEventTap", Napi::Boolean::New(env, g_eventTap != NULL));
350
440
  result.Set("hasRunLoopSource", Napi::Boolean::New(env, g_runLoopSource != NULL));
441
+ result.Set("hasFileHandle", Napi::Boolean::New(env, g_fileHandle != NULL));
442
+ result.Set("hasTimer", Napi::Boolean::New(env, g_cursorTimer != NULL));
351
443
  result.Set("debugCallbackCount", Napi::Number::New(env, g_debugCallbackCount));
352
444
  result.Set("cursorTypeCounter", Napi::Number::New(env, g_cursorTypeCounter));
353
445
 
354
446
  return result;
355
447
  }
356
448
 
357
- // NAPI Function: Save Cursor Data
358
- Napi::Value SaveCursorData(const Napi::CallbackInfo& info) {
359
- Napi::Env env = info.Env();
360
-
361
- if (info.Length() < 1) {
362
- Napi::TypeError::New(env, "Output path required").ThrowAsJavaScriptException();
363
- return env.Null();
364
- }
365
-
366
- std::string outputPath = info[0].As<Napi::String>().Utf8Value();
367
-
368
- @try {
369
- if (g_cursorData) {
370
- @synchronized(g_cursorData) {
371
- NSError *error;
372
- NSData *jsonData = [NSJSONSerialization dataWithJSONObject:g_cursorData
373
- options:NSJSONWritingPrettyPrinted
374
- error:&error];
375
- if (jsonData && !error) {
376
- NSString *filePath = [NSString stringWithUTF8String:outputPath.c_str()];
377
- BOOL success = [jsonData writeToFile:filePath atomically:YES];
378
- return Napi::Boolean::New(env, success);
379
- }
380
- }
381
- }
382
-
383
- return Napi::Boolean::New(env, false);
384
-
385
- } @catch (NSException *exception) {
386
- return Napi::Boolean::New(env, false);
387
- }
388
- }
389
-
390
449
  // Export functions
391
450
  Napi::Object InitCursorTracker(Napi::Env env, Napi::Object exports) {
392
451
  exports.Set("startCursorTracking", Napi::Function::New(env, StartCursorTracking));
393
452
  exports.Set("stopCursorTracking", Napi::Function::New(env, StopCursorTracking));
394
453
  exports.Set("getCursorPosition", Napi::Function::New(env, GetCursorPosition));
395
454
  exports.Set("getCursorTrackingStatus", Napi::Function::New(env, GetCursorTrackingStatus));
396
- exports.Set("saveCursorData", Napi::Function::New(env, SaveCursorData));
397
455
 
398
456
  return exports;
399
457
  }
@@ -1,352 +0,0 @@
1
- [
2
- {
3
- "x": 1195,
4
- "y": 590,
5
- "timestamp": 36,
6
- "cursorType": "default",
7
- "type": "move"
8
- },
9
- {
10
- "x": 1195,
11
- "y": 590,
12
- "timestamp": 136,
13
- "cursorType": "default",
14
- "type": "move"
15
- },
16
- {
17
- "x": 1195,
18
- "y": 590,
19
- "timestamp": 237,
20
- "cursorType": "default",
21
- "type": "move"
22
- },
23
- {
24
- "x": 1195,
25
- "y": 590,
26
- "timestamp": 340,
27
- "cursorType": "default",
28
- "type": "move"
29
- },
30
- {
31
- "x": 1210,
32
- "y": 590,
33
- "timestamp": 441,
34
- "cursorType": "default",
35
- "type": "move"
36
- },
37
- {
38
- "x": 1228,
39
- "y": 591,
40
- "timestamp": 542,
41
- "cursorType": "pointer",
42
- "type": "move"
43
- },
44
- {
45
- "x": 1233,
46
- "y": 593,
47
- "timestamp": 644,
48
- "cursorType": "pointer",
49
- "type": "move"
50
- },
51
- {
52
- "x": 1234,
53
- "y": 593,
54
- "timestamp": 746,
55
- "cursorType": "pointer",
56
- "type": "move"
57
- },
58
- {
59
- "x": 1234,
60
- "y": 613,
61
- "timestamp": 849,
62
- "cursorType": "pointer",
63
- "type": "move"
64
- },
65
- {
66
- "x": 1231,
67
- "y": 696,
68
- "timestamp": 951,
69
- "cursorType": "pointer",
70
- "type": "move"
71
- },
72
- {
73
- "x": 1231,
74
- "y": 758,
75
- "timestamp": 1053,
76
- "cursorType": "pointer",
77
- "type": "move"
78
- },
79
- {
80
- "x": 1232,
81
- "y": 771,
82
- "timestamp": 1156,
83
- "cursorType": "text",
84
- "type": "move"
85
- },
86
- {
87
- "x": 1232,
88
- "y": 771,
89
- "timestamp": 1257,
90
- "cursorType": "text",
91
- "type": "move"
92
- },
93
- {
94
- "x": 1232,
95
- "y": 771,
96
- "timestamp": 1359,
97
- "cursorType": "text",
98
- "type": "move"
99
- },
100
- {
101
- "x": 1235,
102
- "y": 782,
103
- "timestamp": 1460,
104
- "cursorType": "text",
105
- "type": "move"
106
- },
107
- {
108
- "x": 1251,
109
- "y": 806,
110
- "timestamp": 1562,
111
- "cursorType": "text",
112
- "type": "move"
113
- },
114
- {
115
- "x": 1266,
116
- "y": 827,
117
- "timestamp": 1664,
118
- "cursorType": "text",
119
- "type": "move"
120
- },
121
- {
122
- "x": 1268,
123
- "y": 838,
124
- "timestamp": 1766,
125
- "cursorType": "grabbing",
126
- "type": "move"
127
- },
128
- {
129
- "x": 1265,
130
- "y": 853,
131
- "timestamp": 1867,
132
- "cursorType": "grabbing",
133
- "type": "move"
134
- },
135
- {
136
- "x": 1252,
137
- "y": 877,
138
- "timestamp": 1969,
139
- "cursorType": "grabbing",
140
- "type": "move"
141
- },
142
- {
143
- "x": 1240,
144
- "y": 904,
145
- "timestamp": 2070,
146
- "cursorType": "grabbing",
147
- "type": "move"
148
- },
149
- {
150
- "x": 1232,
151
- "y": 920,
152
- "timestamp": 2173,
153
- "cursorType": "grabbing",
154
- "type": "move"
155
- },
156
- {
157
- "x": 1229,
158
- "y": 929,
159
- "timestamp": 2274,
160
- "cursorType": "grabbing",
161
- "type": "move"
162
- },
163
- {
164
- "x": 1228,
165
- "y": 931,
166
- "timestamp": 2375,
167
- "cursorType": "default",
168
- "type": "move"
169
- },
170
- {
171
- "x": 1225,
172
- "y": 936,
173
- "timestamp": 2476,
174
- "cursorType": "default",
175
- "type": "move"
176
- },
177
- {
178
- "x": 1222,
179
- "y": 941,
180
- "timestamp": 2577,
181
- "cursorType": "default",
182
- "type": "move"
183
- },
184
- {
185
- "x": 1219,
186
- "y": 945,
187
- "timestamp": 2679,
188
- "cursorType": "default",
189
- "type": "move"
190
- },
191
- {
192
- "x": 1217,
193
- "y": 947,
194
- "timestamp": 2781,
195
- "cursorType": "default",
196
- "type": "move"
197
- },
198
- {
199
- "x": 1214,
200
- "y": 951,
201
- "timestamp": 2882,
202
- "cursorType": "default",
203
- "type": "move"
204
- },
205
- {
206
- "x": 1214,
207
- "y": 951,
208
- "timestamp": 2983,
209
- "cursorType": "pointer",
210
- "type": "move"
211
- },
212
- {
213
- "x": 1219,
214
- "y": 937,
215
- "timestamp": 3084,
216
- "cursorType": "pointer",
217
- "type": "move"
218
- },
219
- {
220
- "x": 1224,
221
- "y": 928,
222
- "timestamp": 3185,
223
- "cursorType": "pointer",
224
- "type": "move"
225
- },
226
- {
227
- "x": 1227,
228
- "y": 923,
229
- "timestamp": 3286,
230
- "cursorType": "pointer",
231
- "type": "move"
232
- },
233
- {
234
- "x": 1229,
235
- "y": 919,
236
- "timestamp": 3390,
237
- "cursorType": "pointer",
238
- "type": "move"
239
- },
240
- {
241
- "x": 1233,
242
- "y": 910,
243
- "timestamp": 3491,
244
- "cursorType": "pointer",
245
- "type": "move"
246
- },
247
- {
248
- "x": 1234,
249
- "y": 907,
250
- "timestamp": 3592,
251
- "cursorType": "text",
252
- "type": "move"
253
- },
254
- {
255
- "x": 1236,
256
- "y": 904,
257
- "timestamp": 3694,
258
- "cursorType": "text",
259
- "type": "move"
260
- },
261
- {
262
- "x": 1236,
263
- "y": 904,
264
- "timestamp": 3795,
265
- "cursorType": "text",
266
- "type": "move"
267
- },
268
- {
269
- "x": 1236,
270
- "y": 904,
271
- "timestamp": 3898,
272
- "cursorType": "text",
273
- "type": "move"
274
- },
275
- {
276
- "x": 1236,
277
- "y": 904,
278
- "timestamp": 3999,
279
- "cursorType": "text",
280
- "type": "move"
281
- },
282
- {
283
- "x": 1236,
284
- "y": 904,
285
- "timestamp": 4101,
286
- "cursorType": "text",
287
- "type": "move"
288
- },
289
- {
290
- "x": 1236,
291
- "y": 904,
292
- "timestamp": 4203,
293
- "cursorType": "grabbing",
294
- "type": "move"
295
- },
296
- {
297
- "x": 1236,
298
- "y": 903,
299
- "timestamp": 4304,
300
- "cursorType": "grabbing",
301
- "type": "move"
302
- },
303
- {
304
- "x": 1229,
305
- "y": 964,
306
- "timestamp": 4406,
307
- "cursorType": "grabbing",
308
- "type": "move"
309
- },
310
- {
311
- "x": 1220,
312
- "y": 991,
313
- "timestamp": 4507,
314
- "cursorType": "grabbing",
315
- "type": "move"
316
- },
317
- {
318
- "x": 1217,
319
- "y": 1004,
320
- "timestamp": 4609,
321
- "cursorType": "grabbing",
322
- "type": "move"
323
- },
324
- {
325
- "x": 1217,
326
- "y": 1005,
327
- "timestamp": 4709,
328
- "cursorType": "grabbing",
329
- "type": "move"
330
- },
331
- {
332
- "x": 1215,
333
- "y": 1014,
334
- "timestamp": 4810,
335
- "cursorType": "default",
336
- "type": "move"
337
- },
338
- {
339
- "x": 1214,
340
- "y": 1025,
341
- "timestamp": 4912,
342
- "cursorType": "default",
343
- "type": "move"
344
- },
345
- {
346
- "x": 1214,
347
- "y": 1025,
348
- "timestamp": 5015,
349
- "cursorType": "default",
350
- "type": "move"
351
- }
352
- ]