node-mac-recorder 2.17.21 → 2.18.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.
- package/cursor-data-1751364226346.json +1 -0
- package/cursor-data-1751364314136.json +1 -0
- package/cursor-data.json +1 -0
- package/index.js +152 -64
- package/package.json +1 -1
- package/publish.sh +31 -18
- package/src/cursor_tracker.mm +218 -456
- package/src/screen_capture_kit.mm +1 -34
- package/auto-front-demo.js +0 -71
- package/simple-api-example.js +0 -182
- package/usage-examples.js +0 -202
- package/working-example.js +0 -94
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
package/cursor-data.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"x":1151,"y":726,"timestamp":20,"cursorType":"text","type":"move"}
|
package/index.js
CHANGED
|
@@ -283,16 +283,25 @@ class MacRecorder extends EventEmitter {
|
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
//
|
|
287
|
-
if (
|
|
286
|
+
// Ensure recordingDisplayInfo is always set for cursor tracking
|
|
287
|
+
if (!this.recordingDisplayInfo) {
|
|
288
288
|
try {
|
|
289
289
|
const displays = await this.getDisplays();
|
|
290
|
-
|
|
290
|
+
let targetDisplay;
|
|
291
|
+
|
|
292
|
+
if (this.options.displayId !== null) {
|
|
293
|
+
// Manual displayId specified
|
|
294
|
+
targetDisplay = displays.find(d => d.id === this.options.displayId);
|
|
295
|
+
} else {
|
|
296
|
+
// Default to main display
|
|
297
|
+
targetDisplay = displays.find(d => d.isPrimary) || displays[0];
|
|
298
|
+
}
|
|
299
|
+
|
|
291
300
|
if (targetDisplay) {
|
|
292
301
|
this.recordingDisplayInfo = {
|
|
293
|
-
displayId:
|
|
294
|
-
x: targetDisplay.x,
|
|
295
|
-
y: targetDisplay.y,
|
|
302
|
+
displayId: targetDisplay.id,
|
|
303
|
+
x: targetDisplay.x || 0,
|
|
304
|
+
y: targetDisplay.y || 0,
|
|
296
305
|
width: parseInt(targetDisplay.resolution.split("x")[0]),
|
|
297
306
|
height: parseInt(targetDisplay.resolution.split("x")[1]),
|
|
298
307
|
// Add scaling information for cursor coordinate transformation
|
|
@@ -356,12 +365,19 @@ class MacRecorder extends EventEmitter {
|
|
|
356
365
|
this.isRecording = true;
|
|
357
366
|
this.recordingStartTime = Date.now();
|
|
358
367
|
|
|
359
|
-
// Start cursor tracking
|
|
360
|
-
|
|
368
|
+
// Start unified cursor tracking for all recording types
|
|
369
|
+
// Use the same standard cursor tracking logic that works best (display-relative)
|
|
370
|
+
const standardCursorOptions = {
|
|
371
|
+
displayRelative: true,
|
|
372
|
+
displayInfo: this.recordingDisplayInfo,
|
|
373
|
+
recordingType: this.options.windowId ? 'window' :
|
|
374
|
+
this.options.captureArea ? 'area' : 'display',
|
|
375
|
+
captureArea: this.options.captureArea,
|
|
376
|
+
windowId: this.options.windowId
|
|
377
|
+
};
|
|
361
378
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
console.warn('Cursor tracking failed to start:', cursorError.message);
|
|
379
|
+
this.startCursorCapture(cursorFilePath, standardCursorOptions).catch(cursorError => {
|
|
380
|
+
console.warn('Unified cursor tracking failed:', cursorError.message);
|
|
365
381
|
});
|
|
366
382
|
|
|
367
383
|
// Timer başlat (progress tracking için)
|
|
@@ -456,11 +472,9 @@ class MacRecorder extends EventEmitter {
|
|
|
456
472
|
|
|
457
473
|
// Stop cursor tracking automatically
|
|
458
474
|
if (this.cursorCaptureInterval) {
|
|
459
|
-
|
|
460
|
-
this.stopCursorCapture();
|
|
461
|
-
} catch (cursorError) {
|
|
475
|
+
this.stopCursorCapture().catch(cursorError => {
|
|
462
476
|
console.warn('Cursor tracking failed to stop:', cursorError.message);
|
|
463
|
-
}
|
|
477
|
+
});
|
|
464
478
|
}
|
|
465
479
|
|
|
466
480
|
// Timer durdur
|
|
@@ -638,18 +652,20 @@ class MacRecorder extends EventEmitter {
|
|
|
638
652
|
}
|
|
639
653
|
|
|
640
654
|
/**
|
|
641
|
-
*
|
|
642
|
-
* Recording başlatılmışsa otomatik olarak display-relative koordinatlar kullanır
|
|
655
|
+
* Unified cursor capture for all recording types - uses standardized display-relative coordinates
|
|
643
656
|
* @param {string|number} intervalOrFilepath - Cursor data JSON dosya yolu veya interval
|
|
644
657
|
* @param {Object} options - Cursor capture seçenekleri
|
|
645
|
-
* @param {
|
|
646
|
-
* @param {
|
|
658
|
+
* @param {boolean} options.displayRelative - Use display-relative coordinates (recommended)
|
|
659
|
+
* @param {Object} options.displayInfo - Display information for coordinate transformation
|
|
660
|
+
* @param {string} options.recordingType - Type of recording: 'display', 'window', 'area'
|
|
647
661
|
*/
|
|
648
662
|
async startCursorCapture(intervalOrFilepath = 100, options = {}) {
|
|
649
663
|
let filepath;
|
|
664
|
+
let interval = 20; // Default 50 FPS
|
|
650
665
|
|
|
651
666
|
// Parameter parsing: number = interval, string = filepath
|
|
652
667
|
if (typeof intervalOrFilepath === "number") {
|
|
668
|
+
interval = Math.max(10, intervalOrFilepath); // Min 10ms
|
|
653
669
|
filepath = `cursor-data-${Date.now()}.json`;
|
|
654
670
|
} else if (typeof intervalOrFilepath === "string") {
|
|
655
671
|
filepath = intervalOrFilepath;
|
|
@@ -663,28 +679,127 @@ class MacRecorder extends EventEmitter {
|
|
|
663
679
|
throw new Error("Cursor capture is already running");
|
|
664
680
|
}
|
|
665
681
|
|
|
682
|
+
// Use standardized display-relative coordinate system for all recording types
|
|
683
|
+
if (options.displayRelative && options.displayInfo) {
|
|
684
|
+
// Standardized display-relative coordinates for all recording types
|
|
685
|
+
this.cursorDisplayInfo = {
|
|
686
|
+
displayId: options.displayInfo.displayId || options.displayInfo.id,
|
|
687
|
+
x: options.displayInfo.x || 0,
|
|
688
|
+
y: options.displayInfo.y || 0,
|
|
689
|
+
width: options.displayInfo.width || options.displayInfo.logicalWidth,
|
|
690
|
+
height: options.displayInfo.height || options.displayInfo.logicalHeight,
|
|
691
|
+
displayRelative: true,
|
|
692
|
+
recordingType: options.recordingType || 'display',
|
|
693
|
+
// Store additional context for debugging
|
|
694
|
+
captureArea: options.captureArea,
|
|
695
|
+
windowId: options.windowId
|
|
696
|
+
};
|
|
697
|
+
} else if (this.recordingDisplayInfo) {
|
|
698
|
+
// Fallback: Use recording display info if available
|
|
699
|
+
this.cursorDisplayInfo = {
|
|
700
|
+
...this.recordingDisplayInfo,
|
|
701
|
+
displayRelative: true,
|
|
702
|
+
recordingType: options.recordingType || 'display'
|
|
703
|
+
};
|
|
704
|
+
} else {
|
|
705
|
+
// Final fallback: Main display global coordinates
|
|
706
|
+
try {
|
|
707
|
+
const displays = await this.getDisplays();
|
|
708
|
+
const mainDisplay = displays.find((d) => d.isPrimary) || displays[0];
|
|
709
|
+
if (mainDisplay) {
|
|
710
|
+
this.cursorDisplayInfo = {
|
|
711
|
+
displayId: mainDisplay.id,
|
|
712
|
+
x: mainDisplay.x,
|
|
713
|
+
y: mainDisplay.y,
|
|
714
|
+
width: parseInt(mainDisplay.resolution.split("x")[0]),
|
|
715
|
+
height: parseInt(mainDisplay.resolution.split("x")[1]),
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
} catch (error) {
|
|
719
|
+
console.warn("Main display bilgisi alınamadı:", error.message);
|
|
720
|
+
this.cursorDisplayInfo = null; // Fallback: global koordinatlar
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
666
724
|
return new Promise((resolve, reject) => {
|
|
667
725
|
try {
|
|
668
|
-
//
|
|
669
|
-
const
|
|
726
|
+
// Dosyayı oluştur ve temizle
|
|
727
|
+
const fs = require("fs");
|
|
728
|
+
fs.writeFileSync(filepath, "[");
|
|
670
729
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
730
|
+
this.cursorCaptureFile = filepath;
|
|
731
|
+
this.cursorCaptureStartTime = Date.now();
|
|
732
|
+
this.cursorCaptureFirstWrite = true;
|
|
733
|
+
this.lastCapturedData = null;
|
|
675
734
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
735
|
+
// JavaScript interval ile polling yap (daha sık - mouse event'leri yakalamak için)
|
|
736
|
+
this.cursorCaptureInterval = setInterval(() => {
|
|
737
|
+
try {
|
|
738
|
+
const position = nativeBinding.getCursorPosition();
|
|
739
|
+
const timestamp = Date.now() - this.cursorCaptureStartTime;
|
|
740
|
+
|
|
741
|
+
// Standardized coordinate transformation for all recording types
|
|
742
|
+
let x = position.x;
|
|
743
|
+
let y = position.y;
|
|
744
|
+
let coordinateSystem = "global";
|
|
745
|
+
|
|
746
|
+
// Apply display-relative transformation for all recording types
|
|
747
|
+
if (this.cursorDisplayInfo && this.cursorDisplayInfo.displayRelative) {
|
|
748
|
+
// Transform global → display-relative coordinates
|
|
749
|
+
x = position.x - this.cursorDisplayInfo.x;
|
|
750
|
+
y = position.y - this.cursorDisplayInfo.y;
|
|
751
|
+
coordinateSystem = "display-relative";
|
|
752
|
+
|
|
753
|
+
// Optional bounds check for display (don't skip, just note if outside)
|
|
754
|
+
const outsideDisplay = x < 0 || y < 0 ||
|
|
755
|
+
x >= this.cursorDisplayInfo.width ||
|
|
756
|
+
y >= this.cursorDisplayInfo.height;
|
|
757
|
+
|
|
758
|
+
// For debugging - add metadata if cursor is outside recording area
|
|
759
|
+
if (outsideDisplay) {
|
|
760
|
+
coordinateSystem = "display-relative-outside";
|
|
761
|
+
}
|
|
762
|
+
}
|
|
680
763
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
764
|
+
const cursorData = {
|
|
765
|
+
x: x,
|
|
766
|
+
y: y,
|
|
767
|
+
timestamp: timestamp,
|
|
768
|
+
unixTimeMs: Date.now(),
|
|
769
|
+
cursorType: position.cursorType,
|
|
770
|
+
type: position.eventType || "move",
|
|
771
|
+
coordinateSystem: coordinateSystem,
|
|
772
|
+
// Standardized metadata for all recording types
|
|
773
|
+
recordingType: this.cursorDisplayInfo?.recordingType || "display",
|
|
774
|
+
displayInfo: this.cursorDisplayInfo ? {
|
|
775
|
+
displayId: this.cursorDisplayInfo.displayId,
|
|
776
|
+
width: this.cursorDisplayInfo.width,
|
|
777
|
+
height: this.cursorDisplayInfo.height
|
|
778
|
+
} : null
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
// Sadece eventType değiştiğinde veya pozisyon değiştiğinde kaydet
|
|
782
|
+
if (this.shouldCaptureEvent(cursorData)) {
|
|
783
|
+
// Dosyaya ekle
|
|
784
|
+
const jsonString = JSON.stringify(cursorData);
|
|
785
|
+
|
|
786
|
+
if (this.cursorCaptureFirstWrite) {
|
|
787
|
+
fs.appendFileSync(filepath, jsonString);
|
|
788
|
+
this.cursorCaptureFirstWrite = false;
|
|
789
|
+
} else {
|
|
790
|
+
fs.appendFileSync(filepath, "," + jsonString);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Son pozisyonu sakla
|
|
794
|
+
this.lastCapturedData = { ...cursorData };
|
|
795
|
+
}
|
|
796
|
+
} catch (error) {
|
|
797
|
+
console.error("Cursor capture error:", error);
|
|
798
|
+
}
|
|
799
|
+
}, interval); // Configurable FPS
|
|
800
|
+
|
|
801
|
+
this.emit("cursorCaptureStarted", filepath);
|
|
802
|
+
resolve(true);
|
|
688
803
|
} catch (error) {
|
|
689
804
|
reject(error);
|
|
690
805
|
}
|
|
@@ -694,34 +809,7 @@ class MacRecorder extends EventEmitter {
|
|
|
694
809
|
/**
|
|
695
810
|
* Cursor capture durdurur - dosya yazma işlemini sonlandırır
|
|
696
811
|
*/
|
|
697
|
-
stopCursorCapture() {
|
|
698
|
-
if (!this.cursorCaptureInterval) {
|
|
699
|
-
return false;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
try {
|
|
703
|
-
// Native cursor tracking'i durdur
|
|
704
|
-
const success = nativeBinding.stopCursorTracking();
|
|
705
|
-
|
|
706
|
-
this.cursorCaptureInterval = null;
|
|
707
|
-
this.cursorCaptureFile = null;
|
|
708
|
-
this.cursorCaptureStartTime = null;
|
|
709
|
-
|
|
710
|
-
this.emit("cursorCaptureStopped", {
|
|
711
|
-
timestamp: Date.now()
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
return success;
|
|
715
|
-
} catch (error) {
|
|
716
|
-
console.warn("Error stopping cursor tracking:", error.message);
|
|
717
|
-
return false;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
/**
|
|
722
|
-
* LEGACY: Eski JS-based cursor capture durdurmak için geriye uyumluluk
|
|
723
|
-
*/
|
|
724
|
-
async _legacyStopCursorCapture() {
|
|
812
|
+
async stopCursorCapture() {
|
|
725
813
|
return new Promise((resolve, reject) => {
|
|
726
814
|
try {
|
|
727
815
|
if (!this.cursorCaptureInterval) {
|
package/package.json
CHANGED
package/publish.sh
CHANGED
|
@@ -3,6 +3,37 @@
|
|
|
3
3
|
# Publish script for node-mac-recorder
|
|
4
4
|
# Usage: ./publish.sh <patch|minor|major> "commit message"
|
|
5
5
|
|
|
6
|
+
# Clean up development files before publishing
|
|
7
|
+
echo "🧹 Cleaning up development files..."
|
|
8
|
+
|
|
9
|
+
# Remove video files
|
|
10
|
+
echo " • Removing .mov and .mp4 files..."
|
|
11
|
+
find . -name "*.mov" -type f -delete 2>/dev/null
|
|
12
|
+
find . -name "*.mp4" -type f -delete 2>/dev/null
|
|
13
|
+
|
|
14
|
+
# Remove files containing specific keywords
|
|
15
|
+
echo " • Removing files containing test, debug, example, demo, sample..."
|
|
16
|
+
find . -type f -not -path "./node_modules/*" \( \
|
|
17
|
+
-name "*test*" -o \
|
|
18
|
+
-name "*debug*" -o \
|
|
19
|
+
-name "*example*" -o \
|
|
20
|
+
-name "*demo*" -o \
|
|
21
|
+
-name "*sample*" \
|
|
22
|
+
\) -delete 2>/dev/null
|
|
23
|
+
|
|
24
|
+
# Remove folders containing specific keywords
|
|
25
|
+
echo " • Removing folders containing test, debug, example, demo, sample..."
|
|
26
|
+
find . -type d -not -path "./node_modules/*" \( \
|
|
27
|
+
-name "*test*" -o \
|
|
28
|
+
-name "*debug*" -o \
|
|
29
|
+
-name "*example*" -o \
|
|
30
|
+
-name "*demo*" -o \
|
|
31
|
+
-name "*sample*" \
|
|
32
|
+
\) -exec rm -rf {} + 2>/dev/null
|
|
33
|
+
|
|
34
|
+
echo "✅ Cleanup completed"
|
|
35
|
+
echo ""
|
|
36
|
+
|
|
6
37
|
# Check if correct number of arguments provided
|
|
7
38
|
if [ $# -ne 2 ]; then
|
|
8
39
|
echo "❌ Usage: $0 <patch|minor|major> \"commit message\""
|
|
@@ -26,24 +57,6 @@ if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
|
|
26
57
|
exit 1
|
|
27
58
|
fi
|
|
28
59
|
|
|
29
|
-
# Clean up test files and video files before publishing
|
|
30
|
-
echo "🧹 Cleaning up test files and video files..."
|
|
31
|
-
|
|
32
|
-
# Remove video files (MP4, MOV, AVI, etc.)
|
|
33
|
-
find . -maxdepth 1 -type f \( -name "*.mp4" -o -name "*.mov" -o -name "*.avi" -o -name "*.mkv" -o -name "*.webm" \) -delete
|
|
34
|
-
|
|
35
|
-
# Remove test output directories
|
|
36
|
-
rm -rf test-output debug-output
|
|
37
|
-
|
|
38
|
-
# Remove test files and temporary files
|
|
39
|
-
find . -maxdepth 1 -type f \( -name "*test*.json" -o -name "*debug*.json" -o -name "*cursor*.json" -o -name "temp-*" -o -name "*-temp.*" -o -name "*.tmp" \) -delete
|
|
40
|
-
|
|
41
|
-
# Remove test JavaScript files that shouldn't be published
|
|
42
|
-
find . -maxdepth 1 -type f -name "*test*.js" ! -name "test.js" -delete
|
|
43
|
-
find . -maxdepth 1 -type f -name "*debug*.js" -delete
|
|
44
|
-
|
|
45
|
-
echo "✅ Cleanup completed"
|
|
46
|
-
|
|
47
60
|
# Check if there are uncommitted changes
|
|
48
61
|
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
49
62
|
echo "📁 Adding all changes to git..."
|