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