node-mac-recorder 2.21.44 → 2.21.46

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 +116 -16
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.46",
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) {
@@ -1192,6 +1261,12 @@ static NSString* cursorTypeFromSeed(int seed) {
1192
1261
  if (override) {
1193
1262
  return override;
1194
1263
  }
1264
+
1265
+ buildRuntimeSeedMapping();
1266
+ NSString *runtime = [g_seedToTypeMap objectForKey:key];
1267
+ if (runtime) {
1268
+ return runtime;
1269
+ }
1195
1270
  }
1196
1271
  switch(seed) {
1197
1272
  case 741324: return @"auto";
@@ -1641,10 +1716,9 @@ static NSString* detectSystemCursorType(void) {
1641
1716
  dispatch_sync(dispatch_get_main_queue(), fetchCursorBlock);
1642
1717
  }
1643
1718
 
1644
- // Seed learning disabled - using hardcoded mapping instead
1645
- // if (cursorType && ![cursorType isEqualToString:@"default"] && cursorSeed > 0 && detectedCursor) {
1646
- // addCursorToSeedMap(detectedCursor, cursorType, cursorSeed);
1647
- // }
1719
+ if (cursorType && ![cursorType isEqualToString:@"default"] && cursorSeed > 0 && detectedCursor) {
1720
+ addCursorToSeedMap(detectedCursor, cursorType, cursorSeed);
1721
+ }
1648
1722
 
1649
1723
  return cursorType;
1650
1724
  }
@@ -1752,6 +1826,9 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
1752
1826
  NSTimeInterval timestamp = [currentDate timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
1753
1827
  NSTimeInterval unixTimeMs = [currentDate timeIntervalSince1970] * 1000; // unix timestamp in milliseconds
1754
1828
  NSString *cursorType = getCursorType();
1829
+ if (!cursorType) {
1830
+ cursorType = @"default";
1831
+ }
1755
1832
  // (already captured above)
1756
1833
  NSString *eventType = @"move";
1757
1834
 
@@ -1777,6 +1854,10 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
1777
1854
  eventType = @"move";
1778
1855
  break;
1779
1856
  }
1857
+
1858
+ if (!ShouldEmitCursorEvent(location, cursorType, eventType)) {
1859
+ return event;
1860
+ }
1780
1861
 
1781
1862
  // Cursor data oluştur
1782
1863
  NSDictionary *cursorInfo = @{
@@ -1790,6 +1871,7 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
1790
1871
 
1791
1872
  // Direkt dosyaya yaz
1792
1873
  writeToFile(cursorInfo);
1874
+ RememberCursorEvent(location, cursorType, eventType);
1793
1875
 
1794
1876
  return event;
1795
1877
  }
@@ -1818,6 +1900,14 @@ void cursorTimerCallback() {
1818
1900
  NSTimeInterval timestamp = [currentDate timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
1819
1901
  NSTimeInterval unixTimeMs = [currentDate timeIntervalSince1970] * 1000; // unix timestamp in milliseconds
1820
1902
  NSString *cursorType = getCursorType();
1903
+ if (!cursorType) {
1904
+ cursorType = @"default";
1905
+ }
1906
+ NSString *eventType = @"move";
1907
+
1908
+ if (!ShouldEmitCursorEvent(location, cursorType, eventType)) {
1909
+ return;
1910
+ }
1821
1911
 
1822
1912
  // Cursor data oluştur
1823
1913
  NSDictionary *cursorInfo = @{
@@ -1826,11 +1916,12 @@ void cursorTimerCallback() {
1826
1916
  @"timestamp": @(timestamp),
1827
1917
  @"unixTimeMs": @(unixTimeMs),
1828
1918
  @"cursorType": cursorType,
1829
- @"type": @"move"
1919
+ @"type": eventType
1830
1920
  };
1831
1921
 
1832
1922
  // Direkt dosyaya yaz
1833
1923
  writeToFile(cursorInfo);
1924
+ RememberCursorEvent(location, cursorType, eventType);
1834
1925
  }
1835
1926
  }
1836
1927
 
@@ -1885,6 +1976,7 @@ void cleanupCursorTracking() {
1885
1976
  g_lastDetectedCursorType = nil;
1886
1977
  g_cursorTypeCounter = 0;
1887
1978
  g_isFirstWrite = true;
1979
+ ResetCursorEventHistory();
1888
1980
  }
1889
1981
 
1890
1982
  // NAPI Function: Start Cursor Tracking
@@ -1922,6 +2014,7 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
1922
2014
  g_isFirstWrite = true;
1923
2015
 
1924
2016
  g_trackingStartTime = [NSDate date];
2017
+ ResetCursorEventHistory();
1925
2018
 
1926
2019
  // Create event tap for mouse events
1927
2020
  CGEventMask eventMask = (CGEventMaskBit(kCGEventLeftMouseDown) |
@@ -1935,6 +2028,7 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
1935
2028
  CGEventMaskBit(kCGEventRightMouseDragged) |
1936
2029
  CGEventMaskBit(kCGEventOtherMouseDragged));
1937
2030
 
2031
+ bool eventTapActive = false;
1938
2032
  g_eventTap = CGEventTapCreate(kCGSessionEventTap,
1939
2033
  kCGHeadInsertEventTap,
1940
2034
  kCGEventTapOptionListenOnly,
@@ -1947,19 +2041,25 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
1947
2041
  g_runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, g_eventTap, 0);
1948
2042
  CFRunLoopAddSource(CFRunLoopGetMain(), g_runLoopSource, kCFRunLoopCommonModes);
1949
2043
  CGEventTapEnable(g_eventTap, true);
2044
+ eventTapActive = true;
2045
+ NSLog(@"✅ Cursor event tap active - event-driven tracking");
2046
+ } else {
2047
+ NSLog(@"⚠️ Failed to create cursor event tap; falling back to timer-based tracking (requires Accessibility permission)");
1950
2048
  }
1951
2049
 
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];
2050
+ if (!eventTapActive) {
2051
+ // NSTimer fallback (main thread)
2052
+ g_timerTarget = [[CursorTimerTarget alloc] init];
2053
+
2054
+ g_cursorTimer = [NSTimer timerWithTimeInterval:0.05 // 50ms (20 FPS)
2055
+ target:g_timerTarget
2056
+ selector:@selector(timerCallback:)
2057
+ userInfo:nil
2058
+ repeats:YES];
2059
+
2060
+ // Main run loop'a ekle
2061
+ [[NSRunLoop mainRunLoop] addTimer:g_cursorTimer forMode:NSRunLoopCommonModes];
2062
+ }
1963
2063
 
1964
2064
  g_isCursorTracking = true;
1965
2065
  return Napi::Boolean::New(env, true);