node-mac-recorder 2.22.9 → 2.22.11

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/index.js CHANGED
@@ -1604,13 +1604,15 @@ class MacRecorder extends EventEmitter {
1604
1604
  windowHeight: wh
1605
1605
  };
1606
1606
 
1607
- // If this is a click event, mark click location
1608
- // Native eventType values: 'mousedown', 'mouseup', 'rightmousedown', 'rightmouseup'
1607
+ // If this is a click/drag event, mark click location
1608
+ // Native eventType values: 'mousedown', 'mouseup', 'drag', 'rightmousedown', 'rightmouseup', 'rightdrag'
1609
1609
  const eventType = position.eventType || '';
1610
1610
  if (eventType === 'mousedown' ||
1611
1611
  eventType === 'mouseup' ||
1612
+ eventType === 'drag' ||
1612
1613
  eventType === 'rightmousedown' ||
1613
- eventType === 'rightmouseup') {
1614
+ eventType === 'rightmouseup' ||
1615
+ eventType === 'rightdrag') {
1614
1616
  location.click = windowInfo.windowId;
1615
1617
  }
1616
1618
  break; // Found the window, stop searching
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.22.9",
3
+ "version": "2.22.11",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -360,10 +360,10 @@ static void InitializeCursorFingerprintMap(void) {
360
360
  AddCursorIfAvailable(@selector(dragLinkCursor), @"alias");
361
361
  AddCursorIfAvailable(@selector(resizeLeftRightCursor), @"col-resize");
362
362
  AddCursorIfAvailable(@selector(resizeUpDownCursor), @"row-resize");
363
- AddCursorIfAvailableByName(@"resizeLeftCursor", @"w-resize");
364
- AddCursorIfAvailableByName(@"resizeRightCursor", @"e-resize");
365
- AddCursorIfAvailableByName(@"resizeUpCursor", @"n-resize");
366
- AddCursorIfAvailableByName(@"resizeDownCursor", @"s-resize");
363
+ AddCursorIfAvailableByName(@"resizeLeftCursor", @"col-resize");
364
+ AddCursorIfAvailableByName(@"resizeRightCursor", @"col-resize");
365
+ AddCursorIfAvailableByName(@"resizeUpCursor", @"ns-resize");
366
+ AddCursorIfAvailableByName(@"resizeDownCursor", @"ns-resize");
367
367
  AddCursorIfAvailableByName(@"resizeNorthWestSouthEastCursor", @"nwse-resize");
368
368
  AddCursorIfAvailableByName(@"resizeNorthEastSouthWestCursor", @"nesw-resize");
369
369
  AddCursorIfAvailable(@selector(zoomInCursor), @"zoom-in");
@@ -994,10 +994,10 @@ static NSString* cursorTypeFromCursorName(NSString *value) {
994
994
  return @"nwse-resize";
995
995
  }
996
996
  if (horizontal) {
997
- return @"ew-resize"; // Use ew-resize as primary horizontal
997
+ return @"col-resize"; // Desktop SVG var: col-resize
998
998
  }
999
999
  if (vertical) {
1000
- return @"ns-resize"; // Use ns-resize as primary vertical
1000
+ return @"ns-resize"; // Desktop SVG var: ns-resize
1001
1001
  }
1002
1002
 
1003
1003
  // If contains "resize" but no specific direction, return generic resize
@@ -1332,34 +1332,35 @@ static NSString* cursorTypeFromSeed(int seed) {
1332
1332
  }
1333
1333
  }
1334
1334
  switch(seed) {
1335
- case 741324: return @"auto";
1336
- case 741336: return @"none";
1337
- case 741338: return @"context-menu";
1335
+ // Desktop'ta SVG karşılığı olan tiplere normalize edilmiş seed map
1336
+ case 741324: return @"default"; // auto → default
1337
+ case 741336: return @"default"; // none → default (gizli cursor kayıtta default gösterilir)
1338
+ case 741338: return @"default"; // context-menu → default (SVG yok)
1338
1339
  case 741339: return @"pointer";
1339
1340
  case 741341: return @"progress";
1340
- case 741343: return @"wait";
1341
- case 741345: return @"cell";
1341
+ case 741343: return @"progress"; // wait → progress
1342
+ case 741345: return @"crosshair"; // cell → crosshair (en yakın SVG)
1342
1343
  case 741347: return @"crosshair";
1343
1344
  case 741357: return @"text";
1344
- case 741359: return @"vertical-text";
1345
+ case 741359: return @"text"; // vertical-text → text
1345
1346
  case 741361: return @"alias";
1346
1347
  case 741362: return @"copy";
1347
- case 741364: return @"move";
1348
- case 741368: return @"no-drop";
1348
+ case 741364: return @"all-scroll"; // move → all-scroll
1349
+ case 741368: return @"not-allowed"; // no-drop → not-allowed
1349
1350
  case 741370: return @"not-allowed";
1350
1351
  case 741381: return @"grab";
1351
1352
  case 741385: return @"grabbing";
1352
1353
  case 741389: return @"col-resize";
1353
1354
  case 741393: return @"row-resize";
1354
- case 741397: return @"n-resize";
1355
- case 741398: return @"e-resize";
1356
- case 741409: return @"s-resize";
1357
- case 741413: return @"w-resize";
1358
- case 741417: return @"ne-resize";
1359
- case 741418: return @"nw-resize";
1360
- case 741420: return @"se-resize";
1361
- case 741424: return @"sw-resize";
1362
- case 741426: return @"ew-resize";
1355
+ case 741397: return @"ns-resize"; // n-resize → ns-resize
1356
+ case 741398: return @"col-resize"; // e-resize → col-resize
1357
+ case 741409: return @"ns-resize"; // s-resize → ns-resize
1358
+ case 741413: return @"col-resize"; // w-resize → col-resize
1359
+ case 741417: return @"nesw-resize"; // ne-resize → nesw-resize
1360
+ case 741418: return @"nwse-resize"; // nw-resize → nwse-resize
1361
+ case 741420: return @"nwse-resize"; // se-resize → nwse-resize
1362
+ case 741424: return @"nesw-resize"; // sw-resize → nesw-resize
1363
+ case 741426: return @"col-resize"; // ew-resize → col-resize
1363
1364
  case 741436: return @"ns-resize";
1364
1365
  case 741438: return @"nesw-resize";
1365
1366
  case 741442: return @"nwse-resize";
@@ -1787,6 +1788,71 @@ static NSString* detectSystemCursorType(void) {
1787
1788
  return cursorType;
1788
1789
  }
1789
1790
 
1791
+ // Desktop'ta SVG karşılığı olmayan cursor tiplerini desteklenen tiplere normalize et
1792
+ static NSString* normalizeCursorTypeForDesktop(NSString *cursorType) {
1793
+ if (!cursorType || [cursorType length] == 0) {
1794
+ return @"default";
1795
+ }
1796
+
1797
+ // Desteklenen tipler — desktop/public/cursor/default/ dizinindeki SVG'lere karşılık gelir
1798
+ static NSSet *supportedTypes = nil;
1799
+ static dispatch_once_t onceToken;
1800
+ dispatch_once(&onceToken, ^{
1801
+ supportedTypes = [[NSSet alloc] initWithArray:@[
1802
+ @"default", @"pointer", @"grabbing", @"text", @"grab",
1803
+ @"alias", @"copy", @"not-allowed", @"help", @"progress",
1804
+ @"crosshair", @"all-scroll", @"zoom-in", @"zoom-out",
1805
+ @"row-resize", @"col-resize", @"ns-resize",
1806
+ @"nwse-resize", @"nesw-resize"
1807
+ ]];
1808
+ });
1809
+
1810
+ if ([supportedTypes containsObject:cursorType]) {
1811
+ return cursorType;
1812
+ }
1813
+
1814
+ // Normalize edilmemiş tipleri en yakın desteklenen tipe eşle
1815
+ if ([cursorType isEqualToString:@"auto"] || [cursorType isEqualToString:@"none"] ||
1816
+ [cursorType isEqualToString:@"context-menu"]) {
1817
+ return @"default";
1818
+ }
1819
+ if ([cursorType isEqualToString:@"wait"]) {
1820
+ return @"progress";
1821
+ }
1822
+ if ([cursorType isEqualToString:@"cell"]) {
1823
+ return @"crosshair";
1824
+ }
1825
+ if ([cursorType isEqualToString:@"vertical-text"]) {
1826
+ return @"text";
1827
+ }
1828
+ if ([cursorType isEqualToString:@"move"]) {
1829
+ return @"all-scroll";
1830
+ }
1831
+ if ([cursorType isEqualToString:@"no-drop"]) {
1832
+ return @"not-allowed";
1833
+ }
1834
+ // Yönlü resize → iki yönlü resize
1835
+ if ([cursorType isEqualToString:@"ew-resize"] ||
1836
+ [cursorType isEqualToString:@"e-resize"] ||
1837
+ [cursorType isEqualToString:@"w-resize"]) {
1838
+ return @"col-resize";
1839
+ }
1840
+ if ([cursorType isEqualToString:@"n-resize"] ||
1841
+ [cursorType isEqualToString:@"s-resize"]) {
1842
+ return @"ns-resize";
1843
+ }
1844
+ if ([cursorType isEqualToString:@"ne-resize"] ||
1845
+ [cursorType isEqualToString:@"sw-resize"]) {
1846
+ return @"nesw-resize";
1847
+ }
1848
+ if ([cursorType isEqualToString:@"nw-resize"] ||
1849
+ [cursorType isEqualToString:@"se-resize"]) {
1850
+ return @"nwse-resize";
1851
+ }
1852
+
1853
+ return @"default";
1854
+ }
1855
+
1790
1856
  NSString* getCursorType() {
1791
1857
  @autoreleasepool {
1792
1858
  g_cursorTypeCounter++;
@@ -1823,7 +1889,10 @@ NSString* getCursorType() {
1823
1889
  // Use cursorTypeFromNSCursor for detection (pointer equality + image-based)
1824
1890
  // DO NOT use accessibility detection as it's unreliable and causes false positives
1825
1891
  NSString *systemCursorType = detectSystemCursorType();
1826
- NSString *finalType = systemCursorType && [systemCursorType length] > 0 ? systemCursorType : @"default";
1892
+ NSString *rawType = systemCursorType && [systemCursorType length] > 0 ? systemCursorType : @"default";
1893
+
1894
+ // Desktop SVG'lerine uyumlu tipe normalize et
1895
+ NSString *finalType = normalizeCursorTypeForDesktop(rawType);
1827
1896
 
1828
1897
  // Only log when cursor type changes
1829
1898
  static NSString *lastLoggedType = nil;
@@ -1967,12 +2036,43 @@ void cursorTimerCallback() {
1967
2036
  if (!cursorType) {
1968
2037
  cursorType = @"default";
1969
2038
  }
2039
+
2040
+ // Mouse button state polling — event tap olmadığında click/drag tespiti
2041
+ bool currentLeftMouseDown = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonLeft);
2042
+ bool currentRightMouseDown = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonRight);
2043
+
1970
2044
  NSString *eventType = @"move";
1971
2045
 
2046
+ if (currentLeftMouseDown && !g_leftMouseDown) {
2047
+ eventType = @"mousedown";
2048
+ g_lastEventType = @"mousedown";
2049
+ } else if (!currentLeftMouseDown && g_leftMouseDown) {
2050
+ eventType = @"mouseup";
2051
+ g_lastEventType = @"mouseup";
2052
+ } else if (currentRightMouseDown && !g_rightMouseDown) {
2053
+ eventType = @"rightmousedown";
2054
+ g_lastEventType = @"rightmousedown";
2055
+ } else if (!currentRightMouseDown && g_rightMouseDown) {
2056
+ eventType = @"rightmouseup";
2057
+ g_lastEventType = @"rightmouseup";
2058
+ } else if (currentLeftMouseDown) {
2059
+ eventType = @"drag";
2060
+ g_lastEventType = @"drag";
2061
+ } else if (currentRightMouseDown) {
2062
+ eventType = @"rightdrag";
2063
+ g_lastEventType = @"rightdrag";
2064
+ } else {
2065
+ eventType = @"move";
2066
+ g_lastEventType = @"move";
2067
+ }
2068
+
2069
+ g_leftMouseDown = currentLeftMouseDown;
2070
+ g_rightMouseDown = currentRightMouseDown;
2071
+
1972
2072
  if (!ShouldEmitCursorEvent(location, cursorType, eventType)) {
1973
2073
  return;
1974
2074
  }
1975
-
2075
+
1976
2076
  // Cursor data oluştur
1977
2077
  NSDictionary *cursorInfo = @{
1978
2078
  @"x": @((int)location.x),
@@ -1982,7 +2082,7 @@ void cursorTimerCallback() {
1982
2082
  @"cursorType": cursorType,
1983
2083
  @"type": eventType
1984
2084
  };
1985
-
2085
+
1986
2086
  // Direkt dosyaya yaz
1987
2087
  writeToFile(cursorInfo);
1988
2088
  RememberCursorEvent(location, cursorType, eventType);
@@ -2237,20 +2337,32 @@ Napi::Value GetCursorPosition(const Napi::CallbackInfo& info) {
2237
2337
  bool currentRightMouseDown = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonRight);
2238
2338
 
2239
2339
  NSString *eventType = @"move";
2240
-
2340
+
2241
2341
  // Mouse button state değişikliklerini tespit et
2242
2342
  if (currentLeftMouseDown && !g_leftMouseDown) {
2343
+ // Sol tuş basıldı (geçiş: up → down)
2243
2344
  eventType = @"mousedown";
2244
2345
  g_lastEventType = @"mousedown";
2245
2346
  } else if (!currentLeftMouseDown && g_leftMouseDown) {
2347
+ // Sol tuş bırakıldı (geçiş: down → up)
2246
2348
  eventType = @"mouseup";
2247
2349
  g_lastEventType = @"mouseup";
2248
2350
  } else if (currentRightMouseDown && !g_rightMouseDown) {
2351
+ // Sağ tuş basıldı
2249
2352
  eventType = @"rightmousedown";
2250
2353
  g_lastEventType = @"rightmousedown";
2251
2354
  } else if (!currentRightMouseDown && g_rightMouseDown) {
2355
+ // Sağ tuş bırakıldı
2252
2356
  eventType = @"rightmouseup";
2253
2357
  g_lastEventType = @"rightmouseup";
2358
+ } else if (currentLeftMouseDown) {
2359
+ // Sol tuş basılı tutuluyor — sürükleme
2360
+ eventType = @"drag";
2361
+ g_lastEventType = @"drag";
2362
+ } else if (currentRightMouseDown) {
2363
+ // Sağ tuş basılı tutuluyor
2364
+ eventType = @"rightdrag";
2365
+ g_lastEventType = @"rightdrag";
2254
2366
  } else {
2255
2367
  eventType = @"move";
2256
2368
  g_lastEventType = @"move";
@@ -50,6 +50,30 @@ static void initializeSafeQueue() {
50
50
  initializeSafeQueue();
51
51
  }
52
52
 
53
+ + (BOOL)shouldAllowElectronWindows {
54
+ NSString *flag = [[[NSProcessInfo processInfo] environment] objectForKey:@"CREAVIT_ALLOW_ELECTRON_WINDOWS"];
55
+ if (!flag) return NO;
56
+
57
+ NSString *normalized = [[flag lowercaseString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
58
+ return [normalized isEqualToString:@"1"] ||
59
+ [normalized isEqualToString:@"true"] ||
60
+ [normalized isEqualToString:@"yes"] ||
61
+ [normalized isEqualToString:@"on"];
62
+ }
63
+
64
+ + (BOOL)shouldSkipWindowOwner:(NSString *)appName {
65
+ if (!appName || appName.length == 0) return YES;
66
+ if ([appName containsString:@"WindowServer"] || [appName containsString:@"Dock"]) return YES;
67
+
68
+ if (![self shouldAllowElectronWindows]) {
69
+ if ([appName containsString:@"Electron"] || [appName containsString:@"node"]) {
70
+ return YES;
71
+ }
72
+ }
73
+
74
+ return NO;
75
+ }
76
+
53
77
  + (BOOL)startRecordingWithPath:(NSString *)outputPath options:(NSDictionary *)options {
54
78
  if (@available(macOS 12.3, *)) {
55
79
  return [self startRecordingModern:outputPath options:options];
@@ -432,8 +456,7 @@ static void initializeSafeQueue() {
432
456
 
433
457
  NSString *appName = window.owningApplication.applicationName ?: @"Unknown";
434
458
 
435
- // Skip Electron windows (our overlay)
436
- if ([appName containsString:@"Electron"] || [appName containsString:@"node"]) continue;
459
+ if ([self shouldSkipWindowOwner:appName]) continue;
437
460
 
438
461
  NSDictionary *windowInfo = @{
439
462
  @"id": @(window.windowID),
@@ -12,6 +12,30 @@ static void initializeWindowQueue() {
12
12
  });
13
13
  }
14
14
 
15
+ static BOOL ShouldAllowElectronWindows(void) {
16
+ NSString *flag = [[[NSProcessInfo processInfo] environment] objectForKey:@"CREAVIT_ALLOW_ELECTRON_WINDOWS"];
17
+ if (!flag) return NO;
18
+
19
+ NSString *normalized = [[flag lowercaseString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
20
+ return [normalized isEqualToString:@"1"] ||
21
+ [normalized isEqualToString:@"true"] ||
22
+ [normalized isEqualToString:@"yes"] ||
23
+ [normalized isEqualToString:@"on"];
24
+ }
25
+
26
+ static BOOL ShouldSkipWindowOwner(NSString *appName) {
27
+ if (!appName || appName.length == 0) return YES;
28
+ if ([appName containsString:@"WindowServer"] || [appName containsString:@"Dock"]) return YES;
29
+
30
+ if (!ShouldAllowElectronWindows()) {
31
+ if ([appName containsString:@"Electron"] || [appName containsString:@"node"]) {
32
+ return YES;
33
+ }
34
+ }
35
+
36
+ return NO;
37
+ }
38
+
15
39
  // NAPI Function: Get Windows (Electron-safe)
16
40
  Napi::Value GetWindowsElectronSafe(const Napi::CallbackInfo& info) {
17
41
  Napi::Env env = info.Env();
@@ -38,11 +62,7 @@ Napi::Value GetWindowsElectronSafe(const Napi::CallbackInfo& info) {
38
62
 
39
63
  NSString *appName = window.owningApplication.applicationName ?: @"Unknown";
40
64
 
41
- // Skip Electron windows (our overlay) and system windows
42
- if ([appName containsString:@"Electron"] ||
43
- [appName containsString:@"node"] ||
44
- [appName containsString:@"WindowServer"] ||
45
- [appName containsString:@"Dock"]) continue;
65
+ if (ShouldSkipWindowOwner(appName)) continue;
46
66
 
47
67
  NSDictionary *windowInfo = @{
48
68
  @"id": @(window.windowID),
@@ -94,11 +114,7 @@ Napi::Value GetWindowsElectronSafe(const Napi::CallbackInfo& info) {
94
114
  NSString *appName = (__bridge NSString*)ownerName;
95
115
  NSString *windowTitle = windowName ? (__bridge NSString*)windowName : @"";
96
116
 
97
- // Skip Electron windows and system windows
98
- if ([appName containsString:@"Electron"] ||
99
- [appName containsString:@"node"] ||
100
- [appName containsString:@"WindowServer"] ||
101
- [appName containsString:@"Dock"]) continue;
117
+ if (ShouldSkipWindowOwner(appName)) continue;
102
118
 
103
119
  // Get window bounds
104
120
  CGRect bounds = CGRectZero;
@@ -56,6 +56,31 @@ static id g_screenKeyEventMonitor = nil;
56
56
  static NSTimer *g_screenTrackingTimer = nil;
57
57
  static NSInteger g_currentActiveScreenIndex = -1;
58
58
 
59
+ static bool shouldAllowElectronWindows() {
60
+ NSString *flag = [[[NSProcessInfo processInfo] environment] objectForKey:@"CREAVIT_ALLOW_ELECTRON_WINDOWS"];
61
+ if (!flag) return false;
62
+
63
+ NSString *normalized = [[flag lowercaseString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
64
+ return [normalized isEqualToString:@"1"] ||
65
+ [normalized isEqualToString:@"true"] ||
66
+ [normalized isEqualToString:@"yes"] ||
67
+ [normalized isEqualToString:@"on"];
68
+ }
69
+
70
+ static bool shouldSkipSelectableWindowOwner(NSString *windowOwner) {
71
+ if (!windowOwner || [windowOwner length] == 0) return true;
72
+ if ([windowOwner isEqualToString:@"WindowServer"]) return true;
73
+ if ([windowOwner isEqualToString:@"Dock"]) return true;
74
+
75
+ if (!shouldAllowElectronWindows()) {
76
+ if ([windowOwner containsString:@"Electron"] || [windowOwner containsString:@"node"]) {
77
+ return true;
78
+ }
79
+ }
80
+
81
+ return false;
82
+ }
83
+
59
84
  // Record icon helpers
60
85
  static NSImage *CreateRecordIconImage(CGFloat size) {
61
86
  const CGFloat leadingInset = 24.0;
@@ -757,12 +782,7 @@ NSArray* getAllSelectableWindows() {
757
782
 
758
783
  // Skip system windows, dock, menu bar, etc.
759
784
  if ([windowLayer intValue] != 0) continue; // Only normal windows
760
- if (!windowOwner || [windowOwner length] == 0) continue;
761
- if ([windowOwner isEqualToString:@"WindowServer"]) continue;
762
- if ([windowOwner isEqualToString:@"Dock"]) continue;
763
-
764
- // Skip Electron windows (our own overlay)
765
- if ([windowOwner containsString:@"Electron"] || [windowOwner containsString:@"node"]) continue;
785
+ if (shouldSkipSelectableWindowOwner(windowOwner)) continue;
766
786
 
767
787
  // Extract bounds
768
788
  int x = [[bounds objectForKey:@"X"] intValue];
@@ -802,8 +822,7 @@ NSDictionary* getWindowUnderCursor(CGPoint point) {
802
822
  for (NSDictionary *window in g_allWindows) {
803
823
  NSString *appName = [window objectForKey:@"appName"];
804
824
 
805
- // Skip Electron windows (our own overlay)
806
- if (appName && ([appName containsString:@"Electron"] || [appName containsString:@"node"])) {
825
+ if (shouldSkipSelectableWindowOwner(appName)) {
807
826
  continue;
808
827
  }
809
828