node-mac-recorder 2.17.13 → 2.17.15

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.17.13",
3
+ "version": "2.17.15",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -43,9 +43,9 @@ static NSTimeInterval g_lastCursorCheckTime = 0;
43
43
  static int g_sameCursorDetectionCount = 0;
44
44
  static NSString *g_pendingCursorType = nil;
45
45
 
46
- // Cursor stability constants
47
- static const NSTimeInterval CURSOR_STABILITY_THRESHOLD = 0.1; // 100ms
48
- static const int CURSOR_CONFIRMATION_COUNT = 2; // Need 2 consecutive detections
46
+ // Cursor stability constants - minimal stability for real-time response
47
+ static const NSTimeInterval CURSOR_STABILITY_THRESHOLD = 0.02; // 20ms (very fast)
48
+ static const int CURSOR_CONFIRMATION_COUNT = 1; // Immediate response
49
49
 
50
50
  // Mouse button state tracking
51
51
  static bool g_leftMouseDown = false;
@@ -157,13 +157,48 @@ NSString* getCursorType() {
157
157
  [elementRole isEqualToString:@"AXSearchField"]) {
158
158
  contextualCursorType = @"text";
159
159
  }
160
- // POINTER CURSORS - only for interactive elements
160
+ // POINTER CURSORS - interactive elements with broader detection
161
161
  else if ([elementRole isEqualToString:@"AXLink"] ||
162
162
  [elementRole isEqualToString:@"AXButton"] ||
163
163
  [elementRole isEqualToString:@"AXMenuItem"] ||
164
164
  [elementRole isEqualToString:@"AXRadioButton"] ||
165
- [elementRole isEqualToString:@"AXCheckBox"]) {
165
+ [elementRole isEqualToString:@"AXCheckBox"] ||
166
+ [elementRole isEqualToString:@"AXPopUpButton"] ||
167
+ [elementRole isEqualToString:@"AXTab"]) {
166
168
  contextualCursorType = @"pointer";
169
+
170
+ // Also check subroles for links and buttons
171
+ CFStringRef subrole = NULL;
172
+ AXError subroleError = AXUIElementCopyAttributeValue(elementAtPosition, kAXSubroleAttribute, (CFTypeRef*)&subrole);
173
+ if (subroleError == kAXErrorSuccess && subrole) {
174
+ NSString *elementSubrole = (__bridge_transfer NSString*)subrole;
175
+ if ([elementSubrole isEqualToString:@"AXCloseButton"] ||
176
+ [elementSubrole isEqualToString:@"AXMinimizeButton"] ||
177
+ [elementSubrole isEqualToString:@"AXZoomButton"] ||
178
+ [elementSubrole isEqualToString:@"AXToolbarButton"]) {
179
+ contextualCursorType = @"pointer";
180
+ }
181
+ }
182
+ }
183
+ // WEB ELEMENTS - for web links that might not show as AXLink
184
+ else if ([elementRole isEqualToString:@"AXGroup"] ||
185
+ [elementRole isEqualToString:@"AXStaticText"]) {
186
+ // Check if it's clickable/has action
187
+ CFArrayRef actions = NULL;
188
+ AXError actionsError = AXUIElementCopyActionNames(elementAtPosition, &actions);
189
+ if (actionsError == kAXErrorSuccess && actions) {
190
+ CFIndex actionCount = CFArrayGetCount(actions);
191
+ for (CFIndex i = 0; i < actionCount; i++) {
192
+ CFStringRef action = (CFStringRef)CFArrayGetValueAtIndex(actions, i);
193
+ NSString *actionStr = (__bridge NSString*)action;
194
+ if ([actionStr isEqualToString:@"AXPress"] ||
195
+ [actionStr isEqualToString:@"AXShowMenu"]) {
196
+ contextualCursorType = @"pointer";
197
+ break;
198
+ }
199
+ }
200
+ CFRelease(actions);
201
+ }
167
202
  }
168
203
  // WINDOW BORDER RESIZE - critical for resize detection
169
204
  else if ([elementRole isEqualToString:@"AXWindow"]) {
@@ -226,9 +261,18 @@ NSString* getCursorType() {
226
261
  else if ([elementRole isEqualToString:@"AXProgressIndicator"]) {
227
262
  contextualCursorType = @"progress";
228
263
  }
229
- // OTHER ELEMENTS - be conservative, don't assume pointer
264
+ // DEFAULT AREAS - explicitly set default for known non-interactive elements
265
+ else if ([elementRole isEqualToString:@"AXApplication"] ||
266
+ [elementRole isEqualToString:@"AXWindow"] ||
267
+ [elementRole isEqualToString:@"AXScrollArea"] ||
268
+ [elementRole isEqualToString:@"AXScrollBar"] ||
269
+ [elementRole isEqualToString:@"AXStaticText"] ||
270
+ [elementRole isEqualToString:@"AXGroup"]) {
271
+ // These are typically non-interactive - should be default cursor
272
+ contextualCursorType = @"default";
273
+ }
274
+ // OTHER ELEMENTS - let NSCursor handle or default
230
275
  else {
231
- // Don't override NSCursor for unknown elements
232
276
  contextualCursorType = nil;
233
277
  }
234
278
  }
@@ -236,54 +280,68 @@ NSString* getCursorType() {
236
280
  }
237
281
  if (systemWide) CFRelease(systemWide);
238
282
 
239
- // Layer 3: Intelligent fusion of NSCursor and contextual results
283
+ // Layer 3: Contextual-first approach (NSCursor is unreliable)
240
284
  NSString *detectedCursorType = @"default";
241
285
 
242
- // Priority logic:
243
- // 1. If contextual gives resize cursor, always use it (resize has highest priority)
244
- if (contextualCursorType != nil &&
245
- ([contextualCursorType hasSuffix:@"resize"] ||
246
- [contextualCursorType isEqualToString:@"col-resize"] ||
247
- [contextualCursorType isEqualToString:@"ns-resize"])) {
286
+ // Priority logic - contextual detection first:
287
+ // 1. If contextual gives ANY specific cursor, use it
288
+ if (contextualCursorType != nil) {
248
289
  detectedCursorType = contextualCursorType;
249
290
  }
250
- // 2. If NSCursor gives a definitive answer and no resize context, use it
251
- else if (nsCursorType != nil) {
291
+ // 2. Fallback to NSCursor only for very specific cases
292
+ else if (nsCursorType != nil &&
293
+ ([nsCursorType isEqualToString:@"text"] ||
294
+ [nsCursorType isEqualToString:@"pointer"])) {
295
+ // Only trust NSCursor for text and pointer (most reliable)
252
296
  detectedCursorType = nsCursorType;
253
297
  }
254
- // 3. If NSCursor is nil/unknown, but contextual is available, use contextual
255
- else if (contextualCursorType != nil) {
256
- detectedCursorType = contextualCursorType;
298
+ // 3. Everything else defaults to default
299
+ else {
300
+ detectedCursorType = @"default";
257
301
  }
258
302
 
259
- // Layer 4: Stability filtering to prevent oscillation
303
+ // Layer 4: Minimal stability - mostly real-time with quick default fallback
304
+ NSString *finalCursorType = detectedCursorType;
260
305
 
261
- // Time-based stability check
306
+ // Quick validation and immediate response for most cases
262
307
  if (currentTime - g_lastCursorCheckTime > CURSOR_STABILITY_THRESHOLD) {
263
- // Enough time has passed, reset counters
264
- g_sameCursorDetectionCount = 0;
265
- g_pendingCursorType = detectedCursorType;
266
- }
267
-
268
- // Check if detected cursor matches pending cursor
269
- if ([detectedCursorType isEqualToString:g_pendingCursorType]) {
270
- g_sameCursorDetectionCount++;
271
-
272
- // If we have enough confirmations, update stable cursor
273
- if (g_sameCursorDetectionCount >= CURSOR_CONFIRMATION_COUNT) {
308
+ // Update immediately for most cursor types
309
+ if ([detectedCursorType isEqualToString:@"default"] ||
310
+ [detectedCursorType isEqualToString:@"text"] ||
311
+ [detectedCursorType isEqualToString:@"pointer"]) {
312
+ // High-confidence cursor types - update immediately
274
313
  g_stableCursorType = detectedCursorType;
275
- g_lastDetectedCursorType = detectedCursorType;
314
+ finalCursorType = detectedCursorType;
315
+ }
316
+ // Resize cursors need tiny bit of stability to avoid jitter
317
+ else if ([detectedCursorType hasSuffix:@"resize"]) {
318
+ if ([detectedCursorType isEqualToString:g_pendingCursorType]) {
319
+ g_sameCursorDetectionCount++;
320
+ if (g_sameCursorDetectionCount >= CURSOR_CONFIRMATION_COUNT) {
321
+ g_stableCursorType = detectedCursorType;
322
+ finalCursorType = detectedCursorType;
323
+ } else {
324
+ finalCursorType = g_stableCursorType; // Keep previous
325
+ }
326
+ } else {
327
+ g_pendingCursorType = detectedCursorType;
328
+ g_sameCursorDetectionCount = 1;
329
+ finalCursorType = g_stableCursorType; // Keep previous
330
+ }
276
331
  }
332
+ // Everything else - immediate update
333
+ else {
334
+ g_stableCursorType = detectedCursorType;
335
+ finalCursorType = detectedCursorType;
336
+ }
337
+
338
+ g_lastCursorCheckTime = currentTime;
277
339
  } else {
278
- // Different cursor detected, start new pending
279
- g_pendingCursorType = detectedCursorType;
280
- g_sameCursorDetectionCount = 1;
340
+ // Too soon - use stable cursor
341
+ finalCursorType = g_stableCursorType;
281
342
  }
282
343
 
283
- g_lastCursorCheckTime = currentTime;
284
-
285
- // Final validation
286
- NSString *finalCursorType = g_stableCursorType;
344
+ // Final fallback validation
287
345
  if (!finalCursorType || [finalCursorType length] == 0) {
288
346
  finalCursorType = @"default";
289
347
  }