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.
- package/package.json +1 -1
- package/src/cursor_tracker.mm +116 -16
package/package.json
CHANGED
package/src/cursor_tracker.mm
CHANGED
|
@@ -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
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
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":
|
|
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
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
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);
|