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 +1 -1
- package/src/cursor_tracker.mm +98 -40
package/package.json
CHANGED
package/src/cursor_tracker.mm
CHANGED
|
@@ -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.
|
|
48
|
-
static const int CURSOR_CONFIRMATION_COUNT =
|
|
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 -
|
|
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
|
-
//
|
|
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:
|
|
283
|
+
// Layer 3: Contextual-first approach (NSCursor is unreliable)
|
|
240
284
|
NSString *detectedCursorType = @"default";
|
|
241
285
|
|
|
242
|
-
// Priority logic:
|
|
243
|
-
// 1. If contextual gives
|
|
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.
|
|
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.
|
|
255
|
-
else
|
|
256
|
-
detectedCursorType =
|
|
298
|
+
// 3. Everything else defaults to default
|
|
299
|
+
else {
|
|
300
|
+
detectedCursorType = @"default";
|
|
257
301
|
}
|
|
258
302
|
|
|
259
|
-
// Layer 4:
|
|
303
|
+
// Layer 4: Minimal stability - mostly real-time with quick default fallback
|
|
304
|
+
NSString *finalCursorType = detectedCursorType;
|
|
260
305
|
|
|
261
|
-
//
|
|
306
|
+
// Quick validation and immediate response for most cases
|
|
262
307
|
if (currentTime - g_lastCursorCheckTime > CURSOR_STABILITY_THRESHOLD) {
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
279
|
-
|
|
280
|
-
g_sameCursorDetectionCount = 1;
|
|
340
|
+
// Too soon - use stable cursor
|
|
341
|
+
finalCursorType = g_stableCursorType;
|
|
281
342
|
}
|
|
282
343
|
|
|
283
|
-
|
|
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
|
}
|