node-mac-recorder 2.17.21 → 2.18.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/cursor-data-1751364226346.json +1 -0
- package/cursor-data-1751364314136.json +1 -0
- package/cursor-data.json +1 -0
- package/index.js +244 -51
- 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
|
@@ -359,10 +359,53 @@ class MacRecorder extends EventEmitter {
|
|
|
359
359
|
// Start cursor tracking automatically with recording
|
|
360
360
|
let cursorOptions = {};
|
|
361
361
|
|
|
362
|
-
//
|
|
363
|
-
this.
|
|
364
|
-
|
|
365
|
-
|
|
362
|
+
// For window recording, use simplified window-relative coordinates
|
|
363
|
+
if (this.options.windowId) {
|
|
364
|
+
// Use cached window info from the earlier window detection
|
|
365
|
+
this.getWindows().then(windows => {
|
|
366
|
+
const targetWindow = windows.find(w => w.id === this.options.windowId);
|
|
367
|
+
if (targetWindow) {
|
|
368
|
+
// Start cursor capture with simplified window-relative tracking
|
|
369
|
+
this.startCursorCapture(cursorFilePath, {
|
|
370
|
+
windowRelative: true,
|
|
371
|
+
windowInfo: {
|
|
372
|
+
// Use original global window coordinates for reference
|
|
373
|
+
x: targetWindow.x,
|
|
374
|
+
y: targetWindow.y,
|
|
375
|
+
width: targetWindow.width,
|
|
376
|
+
height: targetWindow.height,
|
|
377
|
+
displayId: this.options.displayId,
|
|
378
|
+
// Persist capture area so we can rebuild global offsets reliably
|
|
379
|
+
captureArea: this.options.captureArea,
|
|
380
|
+
// Keep a snapshot of the window details for debugging/analytics
|
|
381
|
+
originalWindow: targetWindow,
|
|
382
|
+
// Store display info for multi-display coordinate fixes
|
|
383
|
+
targetDisplay: this.recordingDisplayInfo
|
|
384
|
+
}
|
|
385
|
+
}).catch(cursorError => {
|
|
386
|
+
console.warn('Window cursor tracking failed:', cursorError.message);
|
|
387
|
+
// Fallback to display recording
|
|
388
|
+
this.startCursorCapture(cursorFilePath).catch(fallbackError => {
|
|
389
|
+
console.warn('Fallback cursor tracking failed:', fallbackError.message);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}).catch(error => {
|
|
394
|
+
console.warn('Could not get window info for cursor tracking:', error.message);
|
|
395
|
+
// Fallback to display cursor tracking
|
|
396
|
+
this.startCursorCapture(cursorFilePath).catch(cursorError => {
|
|
397
|
+
console.warn('Cursor tracking failed to start:', cursorError.message);
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
} else {
|
|
401
|
+
// For display recording, use display-relative cursor tracking
|
|
402
|
+
this.startCursorCapture(cursorFilePath, {
|
|
403
|
+
displayRelative: true,
|
|
404
|
+
displayInfo: this.recordingDisplayInfo
|
|
405
|
+
}).catch(cursorError => {
|
|
406
|
+
console.warn('Display cursor tracking failed:', cursorError.message);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
366
409
|
|
|
367
410
|
// Timer başlat (progress tracking için)
|
|
368
411
|
this.recordingTimer = setInterval(() => {
|
|
@@ -456,11 +499,9 @@ class MacRecorder extends EventEmitter {
|
|
|
456
499
|
|
|
457
500
|
// Stop cursor tracking automatically
|
|
458
501
|
if (this.cursorCaptureInterval) {
|
|
459
|
-
|
|
460
|
-
this.stopCursorCapture();
|
|
461
|
-
} catch (cursorError) {
|
|
502
|
+
this.stopCursorCapture().catch(cursorError => {
|
|
462
503
|
console.warn('Cursor tracking failed to stop:', cursorError.message);
|
|
463
|
-
}
|
|
504
|
+
});
|
|
464
505
|
}
|
|
465
506
|
|
|
466
507
|
// Timer durdur
|
|
@@ -647,9 +688,11 @@ class MacRecorder extends EventEmitter {
|
|
|
647
688
|
*/
|
|
648
689
|
async startCursorCapture(intervalOrFilepath = 100, options = {}) {
|
|
649
690
|
let filepath;
|
|
691
|
+
let interval = 20; // Default 50 FPS
|
|
650
692
|
|
|
651
693
|
// Parameter parsing: number = interval, string = filepath
|
|
652
694
|
if (typeof intervalOrFilepath === "number") {
|
|
695
|
+
interval = Math.max(10, intervalOrFilepath); // Min 10ms
|
|
653
696
|
filepath = `cursor-data-${Date.now()}.json`;
|
|
654
697
|
} else if (typeof intervalOrFilepath === "string") {
|
|
655
698
|
filepath = intervalOrFilepath;
|
|
@@ -663,65 +706,215 @@ class MacRecorder extends EventEmitter {
|
|
|
663
706
|
throw new Error("Cursor capture is already running");
|
|
664
707
|
}
|
|
665
708
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
709
|
+
// Koordinat sistemi belirle: window-relative, display-relative veya global
|
|
710
|
+
if (options.windowRelative && options.windowInfo) {
|
|
711
|
+
const windowInfo = options.windowInfo;
|
|
712
|
+
const targetDisplay = windowInfo.targetDisplay || this.recordingDisplayInfo || null;
|
|
713
|
+
const captureArea = windowInfo.captureArea || null;
|
|
714
|
+
const hasNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
670
715
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
this.cursorCaptureStartTime = Date.now();
|
|
674
|
-
this.cursorCaptureInterval = true; // Mark as active
|
|
716
|
+
let globalX = hasNumber(windowInfo.x) ? windowInfo.x : null;
|
|
717
|
+
let globalY = hasNumber(windowInfo.y) ? windowInfo.y : null;
|
|
675
718
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
719
|
+
if (captureArea && targetDisplay) {
|
|
720
|
+
if (!hasNumber(globalX) && hasNumber(captureArea.x) && hasNumber(targetDisplay.x)) {
|
|
721
|
+
globalX = targetDisplay.x + captureArea.x;
|
|
722
|
+
}
|
|
723
|
+
if (!hasNumber(globalY) && hasNumber(captureArea.y) && hasNumber(targetDisplay.y)) {
|
|
724
|
+
globalY = targetDisplay.y + captureArea.y;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
680
727
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
});
|
|
728
|
+
if (!hasNumber(globalX)) {
|
|
729
|
+
if (captureArea && hasNumber(captureArea.x) && targetDisplay && hasNumber(targetDisplay.x)) {
|
|
730
|
+
globalX = targetDisplay.x + captureArea.x;
|
|
685
731
|
} else {
|
|
686
|
-
|
|
732
|
+
globalX = hasNumber(captureArea?.x) ? captureArea.x : 0;
|
|
687
733
|
}
|
|
688
|
-
} catch (error) {
|
|
689
|
-
reject(error);
|
|
690
734
|
}
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
735
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
736
|
+
if (!hasNumber(globalY)) {
|
|
737
|
+
if (captureArea && hasNumber(captureArea.y) && targetDisplay && hasNumber(targetDisplay.y)) {
|
|
738
|
+
globalY = targetDisplay.y + captureArea.y;
|
|
739
|
+
} else {
|
|
740
|
+
globalY = hasNumber(captureArea?.y) ? captureArea.y : 0;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const displayOffsetX = captureArea && hasNumber(captureArea.x)
|
|
745
|
+
? captureArea.x
|
|
746
|
+
: (targetDisplay && hasNumber(globalX) && hasNumber(targetDisplay.x)
|
|
747
|
+
? globalX - targetDisplay.x
|
|
748
|
+
: globalX);
|
|
749
|
+
const displayOffsetY = captureArea && hasNumber(captureArea.y)
|
|
750
|
+
? captureArea.y
|
|
751
|
+
: (targetDisplay && hasNumber(globalY) && hasNumber(targetDisplay.y)
|
|
752
|
+
? globalY - targetDisplay.y
|
|
753
|
+
: globalY);
|
|
754
|
+
|
|
755
|
+
this.cursorDisplayInfo = {
|
|
756
|
+
displayId:
|
|
757
|
+
windowInfo.displayId ??
|
|
758
|
+
targetDisplay?.displayId ??
|
|
759
|
+
targetDisplay?.id ??
|
|
760
|
+
null,
|
|
761
|
+
x: globalX,
|
|
762
|
+
y: globalY,
|
|
763
|
+
width: windowInfo.width,
|
|
764
|
+
height: windowInfo.height,
|
|
765
|
+
windowRelative: true,
|
|
766
|
+
windowInfo: {
|
|
767
|
+
...windowInfo,
|
|
768
|
+
globalX,
|
|
769
|
+
globalY,
|
|
770
|
+
displayOffsetX,
|
|
771
|
+
displayOffsetY
|
|
772
|
+
},
|
|
773
|
+
targetDisplay,
|
|
774
|
+
captureArea
|
|
775
|
+
};
|
|
776
|
+
} else if (options.displayRelative && options.displayInfo) {
|
|
777
|
+
// Display recording: Use display-relative coordinates
|
|
778
|
+
this.cursorDisplayInfo = {
|
|
779
|
+
displayId: options.displayInfo.displayId,
|
|
780
|
+
x: options.displayInfo.x,
|
|
781
|
+
y: options.displayInfo.y,
|
|
782
|
+
width: options.displayInfo.width,
|
|
783
|
+
height: options.displayInfo.height,
|
|
784
|
+
displayRelative: true
|
|
785
|
+
};
|
|
786
|
+
} else if (this.recordingDisplayInfo) {
|
|
787
|
+
// Fallback: Use recording display info if available
|
|
788
|
+
this.cursorDisplayInfo = this.recordingDisplayInfo;
|
|
789
|
+
} else {
|
|
790
|
+
// Final fallback: Main display global coordinates
|
|
791
|
+
try {
|
|
792
|
+
const displays = await this.getDisplays();
|
|
793
|
+
const mainDisplay = displays.find((d) => d.isPrimary) || displays[0];
|
|
794
|
+
if (mainDisplay) {
|
|
795
|
+
this.cursorDisplayInfo = {
|
|
796
|
+
displayId: mainDisplay.id,
|
|
797
|
+
x: mainDisplay.x,
|
|
798
|
+
y: mainDisplay.y,
|
|
799
|
+
width: parseInt(mainDisplay.resolution.split("x")[0]),
|
|
800
|
+
height: parseInt(mainDisplay.resolution.split("x")[1]),
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
} catch (error) {
|
|
804
|
+
console.warn("Main display bilgisi alınamadı:", error.message);
|
|
805
|
+
this.cursorDisplayInfo = null; // Fallback: global koordinatlar
|
|
806
|
+
}
|
|
700
807
|
}
|
|
701
808
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
809
|
+
return new Promise((resolve, reject) => {
|
|
810
|
+
try {
|
|
811
|
+
// Dosyayı oluştur ve temizle
|
|
812
|
+
const fs = require("fs");
|
|
813
|
+
fs.writeFileSync(filepath, "[");
|
|
705
814
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
815
|
+
this.cursorCaptureFile = filepath;
|
|
816
|
+
this.cursorCaptureStartTime = Date.now();
|
|
817
|
+
this.cursorCaptureFirstWrite = true;
|
|
818
|
+
this.lastCapturedData = null;
|
|
709
819
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
820
|
+
// JavaScript interval ile polling yap (daha sık - mouse event'leri yakalamak için)
|
|
821
|
+
this.cursorCaptureInterval = setInterval(() => {
|
|
822
|
+
try {
|
|
823
|
+
const position = nativeBinding.getCursorPosition();
|
|
824
|
+
const timestamp = Date.now() - this.cursorCaptureStartTime;
|
|
825
|
+
|
|
826
|
+
// Transform coordinates based on recording type
|
|
827
|
+
let x = position.x;
|
|
828
|
+
let y = position.y;
|
|
829
|
+
let coordinateSystem = "global";
|
|
830
|
+
|
|
831
|
+
if (this.cursorDisplayInfo) {
|
|
832
|
+
if (this.cursorDisplayInfo.windowRelative) {
|
|
833
|
+
// Window recording: Transform global → window-relative coordinates
|
|
834
|
+
x = position.x - this.cursorDisplayInfo.x;
|
|
835
|
+
y = position.y - this.cursorDisplayInfo.y;
|
|
836
|
+
coordinateSystem = "window-relative";
|
|
837
|
+
|
|
838
|
+
// Window bounds check - skip if cursor is outside window
|
|
839
|
+
if (x < 0 || y < 0 || x >= this.cursorDisplayInfo.width || y >= this.cursorDisplayInfo.height) {
|
|
840
|
+
return; // Skip frame - cursor outside window
|
|
841
|
+
}
|
|
842
|
+
} else if (this.cursorDisplayInfo.displayRelative) {
|
|
843
|
+
// Display recording: Transform global → display-relative coordinates
|
|
844
|
+
x = position.x - this.cursorDisplayInfo.x;
|
|
845
|
+
y = position.y - this.cursorDisplayInfo.y;
|
|
846
|
+
coordinateSystem = "display-relative";
|
|
847
|
+
|
|
848
|
+
// Display bounds check - skip if cursor is outside display
|
|
849
|
+
if (x < 0 || y < 0 || x >= this.cursorDisplayInfo.width || y >= this.cursorDisplayInfo.height) {
|
|
850
|
+
return; // Skip frame - cursor outside display
|
|
851
|
+
}
|
|
852
|
+
} else {
|
|
853
|
+
// Legacy fallback: Use global coordinates with basic offset
|
|
854
|
+
x = position.x - this.cursorDisplayInfo.x;
|
|
855
|
+
y = position.y - this.cursorDisplayInfo.y;
|
|
856
|
+
coordinateSystem = "display-relative";
|
|
857
|
+
}
|
|
858
|
+
}
|
|
713
859
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
860
|
+
const cursorData = {
|
|
861
|
+
x: x,
|
|
862
|
+
y: y,
|
|
863
|
+
timestamp: timestamp,
|
|
864
|
+
unixTimeMs: Date.now(),
|
|
865
|
+
cursorType: position.cursorType,
|
|
866
|
+
type: position.eventType || "move",
|
|
867
|
+
coordinateSystem: coordinateSystem,
|
|
868
|
+
// Include recording context for window-relative coordinates
|
|
869
|
+
...(this.cursorDisplayInfo?.windowRelative && {
|
|
870
|
+
windowInfo: {
|
|
871
|
+
width: this.cursorDisplayInfo.width,
|
|
872
|
+
height: this.cursorDisplayInfo.height,
|
|
873
|
+
displayId: this.cursorDisplayInfo.displayId
|
|
874
|
+
}
|
|
875
|
+
}),
|
|
876
|
+
// Include display context for display-relative coordinates
|
|
877
|
+
...(this.cursorDisplayInfo?.displayRelative && {
|
|
878
|
+
displayInfo: {
|
|
879
|
+
displayId: this.cursorDisplayInfo.displayId,
|
|
880
|
+
width: this.cursorDisplayInfo.width,
|
|
881
|
+
height: this.cursorDisplayInfo.height
|
|
882
|
+
}
|
|
883
|
+
})
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
// Sadece eventType değiştiğinde veya pozisyon değiştiğinde kaydet
|
|
887
|
+
if (this.shouldCaptureEvent(cursorData)) {
|
|
888
|
+
// Dosyaya ekle
|
|
889
|
+
const jsonString = JSON.stringify(cursorData);
|
|
890
|
+
|
|
891
|
+
if (this.cursorCaptureFirstWrite) {
|
|
892
|
+
fs.appendFileSync(filepath, jsonString);
|
|
893
|
+
this.cursorCaptureFirstWrite = false;
|
|
894
|
+
} else {
|
|
895
|
+
fs.appendFileSync(filepath, "," + jsonString);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Son pozisyonu sakla
|
|
899
|
+
this.lastCapturedData = { ...cursorData };
|
|
900
|
+
}
|
|
901
|
+
} catch (error) {
|
|
902
|
+
console.error("Cursor capture error:", error);
|
|
903
|
+
}
|
|
904
|
+
}, interval); // Configurable FPS
|
|
905
|
+
|
|
906
|
+
this.emit("cursorCaptureStarted", filepath);
|
|
907
|
+
resolve(true);
|
|
908
|
+
} catch (error) {
|
|
909
|
+
reject(error);
|
|
910
|
+
}
|
|
911
|
+
});
|
|
719
912
|
}
|
|
720
913
|
|
|
721
914
|
/**
|
|
722
|
-
*
|
|
915
|
+
* Cursor capture durdurur - dosya yazma işlemini sonlandırır
|
|
723
916
|
*/
|
|
724
|
-
async
|
|
917
|
+
async stopCursorCapture() {
|
|
725
918
|
return new Promise((resolve, reject) => {
|
|
726
919
|
try {
|
|
727
920
|
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..."
|