node-mac-recorder 2.21.44 → 2.21.45

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cursor_tracker.mm +107 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.21.44",
3
+ "version": "2.21.45",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -8,6 +8,7 @@
8
8
  #import <dispatch/dispatch.h>
9
9
  #import "logging.h"
10
10
  #include <vector>
11
+ #include <math.h>
11
12
 
12
13
  #ifndef kAXHitTestParameterizedAttribute
13
14
  #define kAXHitTestParameterizedAttribute CFSTR("AXHitTest")
@@ -457,6 +458,74 @@ static CursorTimerTarget *g_timerTarget = nil;
457
458
  static NSString *g_lastDetectedCursorType = nil;
458
459
  static int g_cursorTypeCounter = 0;
459
460
  static int g_lastCursorSeed = -1; // Track cursor seed for change detection
461
+ static BOOL g_hasLastCursorEvent = NO;
462
+ static CGPoint g_lastCursorLocation = {0, 0};
463
+ static NSString *g_lastCursorType = nil;
464
+ static NSString *g_lastCursorEventType = nil;
465
+
466
+ static inline BOOL StringsEqual(NSString *a, NSString *b) {
467
+ if (a == b) {
468
+ return YES;
469
+ }
470
+ if (!a || !b) {
471
+ return NO;
472
+ }
473
+ return [a isEqualToString:b];
474
+ }
475
+
476
+ static void ResetCursorEventHistory(void) {
477
+ g_hasLastCursorEvent = NO;
478
+ g_lastCursorLocation = CGPointZero;
479
+ if (g_lastCursorType) {
480
+ [g_lastCursorType release];
481
+ g_lastCursorType = nil;
482
+ }
483
+ if (g_lastCursorEventType) {
484
+ [g_lastCursorEventType release];
485
+ g_lastCursorEventType = nil;
486
+ }
487
+ }
488
+
489
+ static BOOL ShouldEmitCursorEvent(CGPoint location, NSString *cursorType, NSString *eventType) {
490
+ if (!g_hasLastCursorEvent) {
491
+ return YES;
492
+ }
493
+
494
+ const CGFloat movementThreshold = 1.5; // Require ~2px change to treat as movement
495
+ BOOL moved = fabs(location.x - g_lastCursorLocation.x) >= movementThreshold ||
496
+ fabs(location.y - g_lastCursorLocation.y) >= movementThreshold;
497
+ BOOL eventChanged = !StringsEqual(eventType, g_lastCursorEventType);
498
+ BOOL isMoveEvent = StringsEqual(eventType, @"move") || StringsEqual(eventType, @"drag");
499
+ BOOL isClickEvent = StringsEqual(eventType, @"mousedown") ||
500
+ StringsEqual(eventType, @"mouseup") ||
501
+ StringsEqual(eventType, @"rightmousedown") ||
502
+ StringsEqual(eventType, @"rightmouseup");
503
+
504
+ if (isMoveEvent) {
505
+ return moved;
506
+ }
507
+
508
+ if (isClickEvent) {
509
+ return eventChanged || moved;
510
+ }
511
+
512
+ // Fallback: only emit when something actually changed
513
+ BOOL cursorChanged = !StringsEqual(cursorType, g_lastCursorType);
514
+ return moved || cursorChanged || eventChanged;
515
+ }
516
+
517
+ static void RememberCursorEvent(CGPoint location, NSString *cursorType, NSString *eventType) {
518
+ g_lastCursorLocation = location;
519
+ if (g_lastCursorType != cursorType) {
520
+ [g_lastCursorType release];
521
+ g_lastCursorType = cursorType ? [cursorType copy] : nil;
522
+ }
523
+ if (g_lastCursorEventType != eventType) {
524
+ [g_lastCursorEventType release];
525
+ g_lastCursorEventType = eventType ? [eventType copy] : nil;
526
+ }
527
+ g_hasLastCursorEvent = YES;
528
+ }
460
529
 
461
530
  static NSString* CopyAndReleaseCFString(CFStringRef value) {
462
531
  if (!value) {
@@ -1752,6 +1821,9 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
1752
1821
  NSTimeInterval timestamp = [currentDate timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
1753
1822
  NSTimeInterval unixTimeMs = [currentDate timeIntervalSince1970] * 1000; // unix timestamp in milliseconds
1754
1823
  NSString *cursorType = getCursorType();
1824
+ if (!cursorType) {
1825
+ cursorType = @"default";
1826
+ }
1755
1827
  // (already captured above)
1756
1828
  NSString *eventType = @"move";
1757
1829
 
@@ -1777,6 +1849,10 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
1777
1849
  eventType = @"move";
1778
1850
  break;
1779
1851
  }
1852
+
1853
+ if (!ShouldEmitCursorEvent(location, cursorType, eventType)) {
1854
+ return event;
1855
+ }
1780
1856
 
1781
1857
  // Cursor data oluştur
1782
1858
  NSDictionary *cursorInfo = @{
@@ -1790,6 +1866,7 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
1790
1866
 
1791
1867
  // Direkt dosyaya yaz
1792
1868
  writeToFile(cursorInfo);
1869
+ RememberCursorEvent(location, cursorType, eventType);
1793
1870
 
1794
1871
  return event;
1795
1872
  }
@@ -1818,6 +1895,14 @@ void cursorTimerCallback() {
1818
1895
  NSTimeInterval timestamp = [currentDate timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
1819
1896
  NSTimeInterval unixTimeMs = [currentDate timeIntervalSince1970] * 1000; // unix timestamp in milliseconds
1820
1897
  NSString *cursorType = getCursorType();
1898
+ if (!cursorType) {
1899
+ cursorType = @"default";
1900
+ }
1901
+ NSString *eventType = @"move";
1902
+
1903
+ if (!ShouldEmitCursorEvent(location, cursorType, eventType)) {
1904
+ return;
1905
+ }
1821
1906
 
1822
1907
  // Cursor data oluştur
1823
1908
  NSDictionary *cursorInfo = @{
@@ -1826,11 +1911,12 @@ void cursorTimerCallback() {
1826
1911
  @"timestamp": @(timestamp),
1827
1912
  @"unixTimeMs": @(unixTimeMs),
1828
1913
  @"cursorType": cursorType,
1829
- @"type": @"move"
1914
+ @"type": eventType
1830
1915
  };
1831
1916
 
1832
1917
  // Direkt dosyaya yaz
1833
1918
  writeToFile(cursorInfo);
1919
+ RememberCursorEvent(location, cursorType, eventType);
1834
1920
  }
1835
1921
  }
1836
1922
 
@@ -1885,6 +1971,7 @@ void cleanupCursorTracking() {
1885
1971
  g_lastDetectedCursorType = nil;
1886
1972
  g_cursorTypeCounter = 0;
1887
1973
  g_isFirstWrite = true;
1974
+ ResetCursorEventHistory();
1888
1975
  }
1889
1976
 
1890
1977
  // NAPI Function: Start Cursor Tracking
@@ -1922,6 +2009,7 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
1922
2009
  g_isFirstWrite = true;
1923
2010
 
1924
2011
  g_trackingStartTime = [NSDate date];
2012
+ ResetCursorEventHistory();
1925
2013
 
1926
2014
  // Create event tap for mouse events
1927
2015
  CGEventMask eventMask = (CGEventMaskBit(kCGEventLeftMouseDown) |
@@ -1935,6 +2023,7 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
1935
2023
  CGEventMaskBit(kCGEventRightMouseDragged) |
1936
2024
  CGEventMaskBit(kCGEventOtherMouseDragged));
1937
2025
 
2026
+ bool eventTapActive = false;
1938
2027
  g_eventTap = CGEventTapCreate(kCGSessionEventTap,
1939
2028
  kCGHeadInsertEventTap,
1940
2029
  kCGEventTapOptionListenOnly,
@@ -1947,19 +2036,25 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
1947
2036
  g_runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, g_eventTap, 0);
1948
2037
  CFRunLoopAddSource(CFRunLoopGetMain(), g_runLoopSource, kCFRunLoopCommonModes);
1949
2038
  CGEventTapEnable(g_eventTap, true);
2039
+ eventTapActive = true;
2040
+ NSLog(@"✅ Cursor event tap active - event-driven tracking");
2041
+ } else {
2042
+ NSLog(@"⚠️ Failed to create cursor event tap; falling back to timer-based tracking (requires Accessibility permission)");
1950
2043
  }
1951
2044
 
1952
- // NSTimer kullan (main thread'de çalışır)
1953
- g_timerTarget = [[CursorTimerTarget alloc] init];
1954
-
1955
- g_cursorTimer = [NSTimer timerWithTimeInterval:0.05 // 50ms (20 FPS)
1956
- target:g_timerTarget
1957
- selector:@selector(timerCallback:)
1958
- userInfo:nil
1959
- repeats:YES];
1960
-
1961
- // Main run loop'a ekle
1962
- [[NSRunLoop mainRunLoop] addTimer:g_cursorTimer forMode:NSRunLoopCommonModes];
2045
+ if (!eventTapActive) {
2046
+ // NSTimer fallback (main thread)
2047
+ g_timerTarget = [[CursorTimerTarget alloc] init];
2048
+
2049
+ g_cursorTimer = [NSTimer timerWithTimeInterval:0.05 // 50ms (20 FPS)
2050
+ target:g_timerTarget
2051
+ selector:@selector(timerCallback:)
2052
+ userInfo:nil
2053
+ repeats:YES];
2054
+
2055
+ // Main run loop'a ekle
2056
+ [[NSRunLoop mainRunLoop] addTimer:g_cursorTimer forMode:NSRunLoopCommonModes];
2057
+ }
1963
2058
 
1964
2059
  g_isCursorTracking = true;
1965
2060
  return Napi::Boolean::New(env, true);