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.
@@ -0,0 +1 @@
1
+ []
@@ -0,0 +1 @@
1
+ []
@@ -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
- // Hem window hem display recording için aynı native cursor tracking kullan
363
- this.startCursorCapture(cursorFilePath).catch(cursorError => {
364
- console.warn('Cursor tracking failed to start:', cursorError.message);
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
- try {
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
- return new Promise((resolve, reject) => {
667
- try {
668
- // Native cursor tracking kullan - hem screen hem window için aynı yöntem
669
- const success = nativeBinding.startCursorTracking(filepath);
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
- if (success) {
672
- this.cursorCaptureFile = filepath;
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
- this.emit("cursorCaptureStarted", {
677
- filepath: filepath,
678
- timestamp: Date.now()
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
- resolve({
682
- filepath: filepath,
683
- started: true
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
- reject(new Error("Failed to start native cursor tracking"));
732
+ globalX = hasNumber(captureArea?.x) ? captureArea.x : 0;
687
733
  }
688
- } catch (error) {
689
- reject(error);
690
734
  }
691
- });
692
- }
693
735
 
694
- /**
695
- * Cursor capture durdurur - dosya yazma işlemini sonlandırır
696
- */
697
- stopCursorCapture() {
698
- if (!this.cursorCaptureInterval) {
699
- return false;
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
- try {
703
- // Native cursor tracking'i durdur
704
- const success = nativeBinding.stopCursorTracking();
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
- this.cursorCaptureInterval = null;
707
- this.cursorCaptureFile = null;
708
- this.cursorCaptureStartTime = null;
815
+ this.cursorCaptureFile = filepath;
816
+ this.cursorCaptureStartTime = Date.now();
817
+ this.cursorCaptureFirstWrite = true;
818
+ this.lastCapturedData = null;
709
819
 
710
- this.emit("cursorCaptureStopped", {
711
- timestamp: Date.now()
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
- return success;
715
- } catch (error) {
716
- console.warn("Error stopping cursor tracking:", error.message);
717
- return false;
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
- * LEGACY: Eski JS-based cursor capture durdurmak için geriye uyumluluk
915
+ * Cursor capture durdurur - dosya yazma işlemini sonlandırır
723
916
  */
724
- async _legacyStopCursorCapture() {
917
+ async stopCursorCapture() {
725
918
  return new Promise((resolve, reject) => {
726
919
  try {
727
920
  if (!this.cursorCaptureInterval) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.17.21",
3
+ "version": "2.18.0",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
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..."