node-mac-recorder 2.20.3 → 2.20.5

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cursor_tracker.mm +250 -429
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.20.3",
3
+ "version": "2.20.5",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -53,37 +53,61 @@ static NSString* CopyAndReleaseCFString(CFStringRef value) {
53
53
  return result;
54
54
  }
55
55
 
56
- static BOOL pointInsideElementFrame(AXUIElementRef element, CGPoint point) {
57
- if (!element) {
56
+ static inline BOOL StringEqualsAny(NSString *value, NSArray<NSString *> *candidates) {
57
+ if (!value) {
58
58
  return NO;
59
59
  }
60
+ for (NSString *candidate in candidates) {
61
+ if ([value isEqualToString:candidate]) {
62
+ return YES;
63
+ }
64
+ }
65
+ return NO;
66
+ }
60
67
 
61
- AXValueRef positionValue = NULL;
62
- AXValueRef sizeValue = NULL;
68
+ static NSString* CopyAttributeString(AXUIElementRef element, CFStringRef attribute) {
69
+ if (!element || !attribute) {
70
+ return nil;
71
+ }
63
72
 
64
- AXError positionError = AXUIElementCopyAttributeValue(element, kAXPositionAttribute, (CFTypeRef *)&positionValue);
65
- AXError sizeError = AXUIElementCopyAttributeValue(element, kAXSizeAttribute, (CFTypeRef *)&sizeValue);
73
+ CFStringRef value = NULL;
74
+ AXError error = AXUIElementCopyAttributeValue(element, attribute, (CFTypeRef *)&value);
75
+ if (error == kAXErrorSuccess && value) {
76
+ return CopyAndReleaseCFString(value);
77
+ }
66
78
 
67
- if (positionError != kAXErrorSuccess || sizeError != kAXErrorSuccess || !positionValue || !sizeValue) {
68
- if (positionValue) CFRelease(positionValue);
69
- if (sizeValue) CFRelease(sizeValue);
79
+ if (value) {
80
+ CFRelease(value);
81
+ }
82
+ return nil;
83
+ }
84
+
85
+ static BOOL CopyAttributeBoolean(AXUIElementRef element, CFStringRef attribute, BOOL *outValue) {
86
+ if (!element || !attribute || !outValue) {
70
87
  return NO;
71
88
  }
72
89
 
73
- CGPoint elementOrigin = CGPointZero;
74
- CGSize elementSize = CGSizeZero;
75
- AXValueGetValue(positionValue, kAXValueTypeCGPoint, &elementOrigin);
76
- AXValueGetValue(sizeValue, kAXValueTypeCGSize, &elementSize);
90
+ CFTypeRef rawValue = NULL;
91
+ AXError error = AXUIElementCopyAttributeValue(element, attribute, &rawValue);
92
+ if (error != kAXErrorSuccess || !rawValue) {
93
+ if (rawValue) {
94
+ CFRelease(rawValue);
95
+ }
96
+ return NO;
97
+ }
77
98
 
78
- CFRelease(positionValue);
79
- CFRelease(sizeValue);
99
+ BOOL result = NO;
100
+ if (CFGetTypeID(rawValue) == CFBooleanGetTypeID()) {
101
+ result = CFBooleanGetValue((CFBooleanRef)rawValue);
102
+ }
80
103
 
81
- CGRect frame = CGRectMake(elementOrigin.x, elementOrigin.y, elementSize.width, elementSize.height);
82
- return CGRectContainsPoint(frame, point);
104
+ CFRelease(rawValue);
105
+ *outValue = result;
106
+ return YES;
83
107
  }
84
108
 
85
- static BOOL elementHasAction(AXUIElementRef element, CFStringRef actionName) {
86
- if (!element || !actionName) {
109
+ static BOOL ElementHasAction(AXUIElementRef element, CFStringRef action) {
110
+ if (!element || !action) {
87
111
  return NO;
88
112
  }
89
113
 
@@ -96,144 +120,218 @@ static BOOL elementHasAction(AXUIElementRef element, CFStringRef actionName) {
96
120
  BOOL hasAction = NO;
97
121
  CFIndex count = CFArrayGetCount(actions);
98
122
  for (CFIndex i = 0; i < count; i++) {
99
- CFStringRef action = (CFStringRef)CFArrayGetValueAtIndex(actions, i);
100
- if (CFStringCompare(action, actionName, 0) == kCFCompareEqualTo) {
123
+ CFStringRef candidate = (CFStringRef)CFArrayGetValueAtIndex(actions, i);
124
+ if (CFStringCompare(candidate, action, 0) == kCFCompareEqualTo) {
101
125
  hasAction = YES;
102
126
  break;
103
127
  }
104
128
  }
105
-
106
129
  CFRelease(actions);
107
130
  return hasAction;
108
131
  }
109
132
 
110
- static BOOL elementIsClickable(AXUIElementRef element) {
133
+ static BOOL PointInsideElementFrame(AXUIElementRef element, CGPoint point) {
111
134
  if (!element) {
112
135
  return NO;
113
136
  }
114
137
 
115
- CFStringRef roleRef = NULL;
116
- if (AXUIElementCopyAttributeValue(element, kAXRoleAttribute, (CFTypeRef *)&roleRef) == kAXErrorSuccess && roleRef) {
117
- NSString *role = CopyAndReleaseCFString(roleRef);
118
- if ([role isEqualToString:@"AXButton"] ||
119
- [role isEqualToString:@"AXLink"] ||
120
- [role isEqualToString:@"AXMenuItem"] ||
121
- [role isEqualToString:@"AXTab"] ||
122
- [role isEqualToString:@"AXRadioButton"] ||
123
- [role isEqualToString:@"AXCheckBox"] ||
124
- [role isEqualToString:@"AXPopUpButton"]) {
125
- return YES;
126
- }
138
+ AXValueRef positionValue = NULL;
139
+ AXValueRef sizeValue = NULL;
140
+
141
+ AXError positionError = AXUIElementCopyAttributeValue(element, kAXPositionAttribute, (CFTypeRef *)&positionValue);
142
+ AXError sizeError = AXUIElementCopyAttributeValue(element, kAXSizeAttribute, (CFTypeRef *)&sizeValue);
143
+
144
+ if (positionError != kAXErrorSuccess || sizeError != kAXErrorSuccess || !positionValue || !sizeValue) {
145
+ if (positionValue) CFRelease(positionValue);
146
+ if (sizeValue) CFRelease(sizeValue);
147
+ return NO;
127
148
  }
128
149
 
129
- CFStringRef subroleRef = NULL;
130
- if (AXUIElementCopyAttributeValue(element, kAXSubroleAttribute, (CFTypeRef *)&subroleRef) == kAXErrorSuccess && subroleRef) {
131
- NSString *subrole = CopyAndReleaseCFString(subroleRef);
132
- if ([subrole isEqualToString:@"AXLink"] ||
133
- [subrole isEqualToString:@"AXFileDrop"] ||
134
- [subrole isEqualToString:@"AXDropTarget"] ||
135
- [subrole isEqualToString:@"AXToolbarButton"]) {
136
- return YES;
137
- }
150
+ CGPoint origin = CGPointZero;
151
+ CGSize size = CGSizeZero;
152
+ AXValueGetValue(positionValue, kAXValueTypeCGPoint, &origin);
153
+ AXValueGetValue(sizeValue, kAXValueTypeCGSize, &size);
154
+
155
+ CFRelease(positionValue);
156
+ CFRelease(sizeValue);
157
+
158
+ CGRect frame = CGRectMake(origin.x, origin.y, size.width, size.height);
159
+ return CGRectContainsPoint(frame, point);
160
+ }
161
+
162
+ static NSString* CursorTypeForWindowBorder(AXUIElementRef element, CGPoint cursorPos) {
163
+ AXValueRef positionValue = NULL;
164
+ AXValueRef sizeValue = NULL;
165
+
166
+ AXError positionError = AXUIElementCopyAttributeValue(element, kAXPositionAttribute, (CFTypeRef *)&positionValue);
167
+ AXError sizeError = AXUIElementCopyAttributeValue(element, kAXSizeAttribute, (CFTypeRef *)&sizeValue);
168
+
169
+ if (positionError != kAXErrorSuccess || sizeError != kAXErrorSuccess || !positionValue || !sizeValue) {
170
+ if (positionValue) CFRelease(positionValue);
171
+ if (sizeValue) CFRelease(sizeValue);
172
+ return nil;
138
173
  }
139
174
 
140
- if (elementHasAction(element, kAXPressAction) ||
141
- elementHasAction(element, kAXShowMenuAction) ||
142
- elementHasAction(element, CFSTR("AXConfirm"))) {
143
- return YES;
175
+ CGPoint windowOrigin = CGPointZero;
176
+ CGSize windowSize = CGSizeZero;
177
+ AXValueGetValue(positionValue, kAXValueTypeCGPoint, &windowOrigin);
178
+ AXValueGetValue(sizeValue, kAXValueTypeCGSize, &windowSize);
179
+
180
+ CFRelease(positionValue);
181
+ CFRelease(sizeValue);
182
+
183
+ CGFloat edge = 4.0;
184
+ CGFloat x = cursorPos.x - windowOrigin.x;
185
+ CGFloat y = cursorPos.y - windowOrigin.y;
186
+ CGFloat w = windowSize.width;
187
+ CGFloat h = windowSize.height;
188
+
189
+ if (x < 0 || y < 0 || x > w || y > h) {
190
+ return nil;
144
191
  }
145
192
 
146
- CFTypeRef urlValue = NULL;
147
- if (AXUIElementCopyAttributeValue(element, kAXURLAttribute, &urlValue) == kAXErrorSuccess && urlValue) {
148
- CFRelease(urlValue);
149
- return YES;
193
+ BOOL nearLeft = (x >= 0 && x <= edge);
194
+ BOOL nearRight = (x >= w - edge && x <= w);
195
+ BOOL nearTop = (y >= 0 && y <= edge);
196
+ BOOL nearBottom = (y >= h - edge && y <= h);
197
+
198
+ if ((nearLeft && nearTop) || (nearRight && nearBottom)) {
199
+ return @"nwse-resize";
150
200
  }
151
- if (urlValue) {
152
- CFRelease(urlValue);
153
- urlValue = NULL;
201
+ if ((nearRight && nearTop) || (nearLeft && nearBottom)) {
202
+ return @"nesw-resize";
154
203
  }
155
-
156
- CFBooleanRef isEnabled = NULL;
157
- if (AXUIElementCopyAttributeValue(element, kAXEnabledAttribute, (CFTypeRef *)&isEnabled) == kAXErrorSuccess &&
158
- isEnabled && CFBooleanGetValue(isEnabled)) {
159
- CFRelease(isEnabled);
160
- isEnabled = NULL;
161
- if (elementHasAction(element, CFSTR("AXShowMenu")) || elementHasAction(element, CFSTR("AXDecrement")) || elementHasAction(element, CFSTR("AXIncrement"))) {
162
- return YES;
163
- }
204
+ if (nearLeft || nearRight) {
205
+ return @"col-resize";
164
206
  }
165
- if (isEnabled) {
166
- CFRelease(isEnabled);
167
- isEnabled = NULL;
207
+ if (nearTop || nearBottom) {
208
+ return @"ns-resize";
168
209
  }
169
210
 
170
- return NO;
211
+ return nil;
171
212
  }
172
213
 
173
- static BOOL elementSupportsText(AXUIElementRef element) {
214
+ static NSString* CursorTypeFromAccessibilityElement(AXUIElementRef element, CGPoint cursorPos) {
174
215
  if (!element) {
175
- return NO;
216
+ return nil;
217
+ }
218
+
219
+ NSString *role = CopyAttributeString(element, kAXRoleAttribute);
220
+ NSString *subrole = CopyAttributeString(element, kAXSubroleAttribute);
221
+ NSString *roleDescription = CopyAttributeString(element, kAXRoleDescriptionAttribute);
222
+
223
+ if (StringEqualsAny(role, @[@"AXTextField", @"AXTextArea", @"AXSearchField"])) {
224
+ return @"text";
225
+ }
226
+
227
+ if (StringEqualsAny(subrole, @[@"AXSecureTextField", @"AXTextField"])) {
228
+ return @"text";
229
+ }
230
+
231
+ BOOL isEditable = NO;
232
+ if (CopyAttributeBoolean(element, CFSTR("AXEditable"), &isEditable) && isEditable) {
233
+ return @"text";
176
234
  }
177
235
 
178
- BOOL supportsText = NO;
236
+ BOOL supportsSelection = NO;
237
+ if (CopyAttributeBoolean(element, CFSTR("AXSupportsTextSelection"), &supportsSelection) && supportsSelection) {
238
+ return @"text";
239
+ }
179
240
 
180
- CFStringRef roleRef = NULL;
181
- if (AXUIElementCopyAttributeValue(element, kAXRoleAttribute, (CFTypeRef *)&roleRef) == kAXErrorSuccess && roleRef) {
182
- NSString *role = CopyAndReleaseCFString(roleRef);
183
- if ([role isEqualToString:@"AXTextField"] ||
184
- [role isEqualToString:@"AXTextArea"] ||
185
- [role isEqualToString:@"AXSearchField"] ||
186
- [role isEqualToString:@"AXStaticText"] ||
187
- [role isEqualToString:@"AXDocument"] ||
188
- [role isEqualToString:@"AXWebArea"]) {
189
- supportsText = YES;
241
+ CFTypeRef valueAttribute = NULL;
242
+ if (AXUIElementCopyAttributeValue(element, kAXValueAttribute, &valueAttribute) == kAXErrorSuccess && valueAttribute) {
243
+ CFTypeID typeId = CFGetTypeID(valueAttribute);
244
+ if (typeId == CFAttributedStringGetTypeID() || typeId == CFStringGetTypeID()) {
245
+ CFRelease(valueAttribute);
246
+ return @"text";
190
247
  }
248
+ CFRelease(valueAttribute);
191
249
  }
192
250
 
193
- CFStringRef subroleRef = NULL;
194
- if (!supportsText && AXUIElementCopyAttributeValue(element, kAXSubroleAttribute, (CFTypeRef *)&subroleRef) == kAXErrorSuccess && subroleRef) {
195
- NSString *subrole = CopyAndReleaseCFString(subroleRef);
196
- if ([subrole isEqualToString:@"AXSecureTextField"] ||
197
- [subrole isEqualToString:@"AXTextAttachment"] ||
198
- [subrole isEqualToString:@"AXTextField"]) {
199
- supportsText = YES;
251
+ // Leave progress/help to system cursor; don't force via AX
252
+
253
+ if ([role isEqualToString:@"AXSplitter"]) {
254
+ NSString *orientation = CopyAttributeString(element, CFSTR("AXOrientation"));
255
+ if ([orientation isEqualToString:@"AXHorizontalOrientation"]) {
256
+ return @"ns-resize";
257
+ }
258
+ if ([orientation isEqualToString:@"AXVerticalOrientation"]) {
259
+ return @"col-resize";
200
260
  }
201
261
  }
202
262
 
203
- CFStringRef roleDescriptionRef = NULL;
204
- if (!supportsText && AXUIElementCopyAttributeValue(element, kAXRoleDescriptionAttribute, (CFTypeRef *)&roleDescriptionRef) == kAXErrorSuccess && roleDescriptionRef) {
205
- NSString *roleDescription = CopyAndReleaseCFString(roleDescriptionRef);
263
+ if ([role isEqualToString:@"AXWindow"]) {
264
+ NSString *windowCursor = CursorTypeForWindowBorder(element, cursorPos);
265
+ if (windowCursor) {
266
+ return windowCursor;
267
+ }
268
+ }
269
+
270
+ // Pointer (hand) only for actual links; buttons remain default arrow on macOS
271
+ if (StringEqualsAny(role, @[@"AXLink"])) {
272
+ return @"pointer";
273
+ }
274
+ if (StringEqualsAny(subrole, @[@"AXLink"])) {
275
+ return @"pointer";
276
+ }
277
+
278
+ if (roleDescription) {
206
279
  NSString *lower = [roleDescription lowercaseString];
280
+ if ([lower containsString:@"button"] ||
281
+ [lower containsString:@"link"] ||
282
+ [lower containsString:@"tab"]) {
283
+ return @"pointer";
284
+ }
207
285
  if ([lower containsString:@"text"] ||
208
286
  [lower containsString:@"editor"] ||
209
- [lower containsString:@"code"] ||
210
287
  [lower containsString:@"document"]) {
211
- supportsText = YES;
288
+ return @"text";
212
289
  }
213
290
  }
214
291
 
215
- CFBooleanRef editable = NULL;
216
- if (!supportsText && AXUIElementCopyAttributeValue(element, CFSTR("AXEditable"), (CFTypeRef *)&editable) == kAXErrorSuccess && editable) {
217
- supportsText = CFBooleanGetValue(editable);
218
- CFRelease(editable);
292
+ // Actions alone do not imply pointer hand on macOS; ignore
293
+
294
+ CFTypeRef urlValue = NULL;
295
+ if (AXUIElementCopyAttributeValue(element, kAXURLAttribute, &urlValue) == kAXErrorSuccess && urlValue) {
296
+ CFRelease(urlValue);
297
+ return @"pointer";
298
+ }
299
+ if (urlValue) {
300
+ CFRelease(urlValue);
219
301
  }
220
302
 
221
- CFBooleanRef supportsSelection = NULL;
222
- if (!supportsText && AXUIElementCopyAttributeValue(element, CFSTR("AXSupportsTextSelection"), (CFTypeRef *)&supportsSelection) == kAXErrorSuccess && supportsSelection) {
223
- supportsText = CFBooleanGetValue(supportsSelection);
224
- CFRelease(supportsSelection);
303
+ // Grab/open-hand often comes from system cursor; avoid forcing via AX
304
+
305
+ // Zoom is rare; prefer system cursor unless explicitly needed
306
+
307
+ return nil;
308
+ }
309
+
310
+ static AXUIElementRef CopyParent(AXUIElementRef element) {
311
+ if (!element) return NULL;
312
+ AXUIElementRef parent = NULL;
313
+ if (AXUIElementCopyAttributeValue(element, kAXParentAttribute, (CFTypeRef *)&parent) == kAXErrorSuccess && parent) {
314
+ return parent; // retained
225
315
  }
316
+ if (parent) CFRelease(parent);
317
+ return NULL;
318
+ }
226
319
 
227
- CFTypeRef valueAttribute = NULL;
228
- if (!supportsText && AXUIElementCopyAttributeValue(element, kAXValueAttribute, &valueAttribute) == kAXErrorSuccess && valueAttribute) {
229
- CFTypeID typeId = CFGetTypeID(valueAttribute);
230
- if (typeId == CFAttributedStringGetTypeID() || typeId == CFStringGetTypeID()) {
231
- supportsText = YES;
320
+ static NSString* CursorTypeFromElementOrAncestors(AXUIElementRef element, CGPoint cursorPos, int maxDepth) {
321
+ AXUIElementRef current = element;
322
+ int depth = 0;
323
+ while (current && depth < maxDepth) {
324
+ NSString *t = CursorTypeFromAccessibilityElement(current, cursorPos);
325
+ if (t && [t length] > 0) {
326
+ return t;
232
327
  }
233
- CFRelease(valueAttribute);
328
+ AXUIElementRef parent = CopyParent(current);
329
+ if (current != element) CFRelease(current);
330
+ current = parent;
331
+ depth++;
234
332
  }
235
-
236
- return supportsText;
333
+ if (current && current != element) CFRelease(current);
334
+ return nil;
237
335
  }
238
336
 
239
337
  // Mouse button state tracking
@@ -244,333 +342,45 @@ static NSString *g_lastEventType = @"move";
244
342
  // Accessibility tabanlı cursor tip tespiti
245
343
  static NSString* detectCursorTypeUsingAccessibility(CGPoint cursorPos) {
246
344
  @autoreleasepool {
247
- @try {
248
- // ACCESSIBILITY API BASED CURSOR DETECTION
249
- // Determine cursor type based on the UI element under the cursor
250
-
251
- AXUIElementRef systemWide = AXUIElementCreateSystemWide();
252
- AXUIElementRef elementAtPosition = NULL;
253
- AXError error = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &elementAtPosition);
254
-
255
- NSString *cursorType = @"default"; // Default fallback
256
-
257
- if (error == kAXErrorSuccess && elementAtPosition) {
258
- NSString *elementRole = nil;
259
- CFStringRef role = NULL;
260
- error = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleAttribute, (CFTypeRef*)&role);
261
- if (error == kAXErrorSuccess && role) {
262
- elementRole = CopyAndReleaseCFString(role);
263
- }
264
-
265
- if (elementRole) {
266
- NSLog(@"🎯 ELEMENT ROLE: %@", elementRole);
267
- }
268
-
269
- // TEXT CURSORS
270
- if ([elementRole isEqualToString:@"AXTextField"] ||
271
- [elementRole isEqualToString:@"AXTextArea"] ||
272
- [elementRole isEqualToString:@"AXStaticText"] ||
273
- [elementRole isEqualToString:@"AXSearchField"]) {
274
- cursorType = @"text";
275
- }
276
- // POINTER CURSORS (clickable elements)
277
- else if ([elementRole isEqualToString:@"AXLink"] ||
278
- [elementRole isEqualToString:@"AXButton"] ||
279
- [elementRole isEqualToString:@"AXMenuItem"] ||
280
- [elementRole isEqualToString:@"AXRadioButton"] ||
281
- [elementRole isEqualToString:@"AXCheckBox"] ||
282
- [elementRole isEqualToString:@"AXPopUpButton"] ||
283
- [elementRole isEqualToString:@"AXTab"]) {
284
- cursorType = @"pointer";
285
- }
286
- // GRAB CURSORS (draggable elements)
287
- else if ([elementRole isEqualToString:@"AXImage"] ||
288
- [elementRole isEqualToString:@"AXGroup"]) {
289
- // Check if element is draggable
290
- CFBooleanRef draggable = NULL;
291
- error = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXMovable"), (CFTypeRef*)&draggable);
292
- if (error == kAXErrorSuccess && draggable && CFBooleanGetValue(draggable)) {
293
- cursorType = @"grab";
294
- } else {
295
- cursorType = @"default";
296
- }
297
- }
298
- // PROGRESS CURSORS (loading/busy elements)
299
- else if ([elementRole isEqualToString:@"AXProgressIndicator"] ||
300
- [elementRole isEqualToString:@"AXBusyIndicator"]) {
301
- cursorType = @"progress";
302
- }
303
- // HELP CURSORS (help buttons/tooltips)
304
- else if ([elementRole isEqualToString:@"AXHelpTag"] ||
305
- [elementRole isEqualToString:@"AXTooltip"]) {
306
- cursorType = @"help";
307
- }
308
- // RESIZE CURSORS - sadece AXSplitter için
309
- else if ([elementRole isEqualToString:@"AXSplitter"]) {
310
- // Get splitter orientation to determine resize direction
311
- CFStringRef orientation = NULL;
312
- error = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXOrientation"), (CFTypeRef*)&orientation);
313
- if (error == kAXErrorSuccess && orientation) {
314
- NSString *orientationStr = CopyAndReleaseCFString(orientation);
315
- if ([orientationStr isEqualToString:@"AXHorizontalOrientation"]) {
316
- cursorType = @"ns-resize"; // Yatay splitter -> dikey hareket (north-south)
317
- } else if ([orientationStr isEqualToString:@"AXVerticalOrientation"]) {
318
- cursorType = @"col-resize"; // Dikey splitter -> yatay hareket (east-west)
319
- } else {
320
- cursorType = @"default"; // Bilinmeyen orientation
321
- }
322
- } else {
323
- cursorType = @"default"; // Orientation alınamazsa default
324
- }
325
- }
326
- // SCROLL CURSORS - hep default olsun, all-scroll görünmesin
327
- else if ([elementRole isEqualToString:@"AXScrollBar"]) {
328
- cursorType = @"default"; // ScrollBar'lar için de default
329
- }
330
- // AXScrollArea - hep default
331
- else if ([elementRole isEqualToString:@"AXScrollArea"]) {
332
- cursorType = @"default"; // ScrollArea her zaman default
333
- }
334
- // CROSSHAIR CURSORS (drawing/selection tools)
335
- else if ([elementRole isEqualToString:@"AXCanvas"] ||
336
- [elementRole isEqualToString:@"AXDrawingArea"]) {
337
- cursorType = @"crosshair";
338
- }
339
- // ZOOM CURSORS (zoom controls)
340
- else if ([elementRole isEqualToString:@"AXZoomButton"]) {
341
- cursorType = @"zoom-in";
342
- }
343
- // NOT-ALLOWED CURSORS (disabled elements)
344
- else if ([elementRole isEqualToString:@"AXStaticText"] ||
345
- [elementRole isEqualToString:@"AXGroup"]) {
346
- // Check if element is disabled/readonly
347
- CFBooleanRef enabled = NULL;
348
- error = AXUIElementCopyAttributeValue(elementAtPosition, kAXEnabledAttribute, (CFTypeRef*)&enabled);
349
- if (error == kAXErrorSuccess && enabled && !CFBooleanGetValue(enabled)) {
350
- cursorType = @"not-allowed";
351
- }
352
- }
353
- // WINDOW BORDER RESIZE - sadece pencere kenarlarında
354
- else if ([elementRole isEqualToString:@"AXWindow"]) {
355
- // Check window attributes to see if it's resizable
356
- CFBooleanRef resizable = NULL;
357
- AXError resizableError = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXResizeButton"), (CFTypeRef*)&resizable);
358
-
359
- // Sadece resize edilebilir pencereler için cursor değişimi
360
- if (resizableError == kAXErrorSuccess || true) { // AXResizeButton bulunamazsa da devam et
361
- CFTypeRef position = NULL;
362
- CFTypeRef size = NULL;
363
- error = AXUIElementCopyAttributeValue(elementAtPosition, kAXPositionAttribute, &position);
364
- AXError sizeError = AXUIElementCopyAttributeValue(elementAtPosition, kAXSizeAttribute, &size);
365
-
366
- if (error == kAXErrorSuccess && sizeError == kAXErrorSuccess && position && size) {
367
- CGPoint windowPos;
368
- CGSize windowSize;
369
- AXValueGetValue((AXValueRef)position, kAXValueTypeCGPoint, &windowPos);
370
- AXValueGetValue((AXValueRef)size, kAXValueTypeCGSize, &windowSize);
371
-
372
- CGFloat x = cursorPos.x - windowPos.x;
373
- CGFloat y = cursorPos.y - windowPos.y;
374
- CGFloat w = windowSize.width;
375
- CGFloat h = windowSize.height;
376
- CGFloat edge = 3.0; // Daha küçük edge detection (3px)
377
-
378
- // Sadece çok kenar köşelerde resize cursor'ı göster
379
- BOOL isOnBorder = NO;
380
-
381
- // Corner resize detection - çok dar alanda, doğru açılar
382
- if (x <= edge && y <= edge) {
383
- cursorType = @"nwse-resize"; // Sol üst köşe - northwest-southeast
384
- isOnBorder = YES;
385
- }
386
- else if (x >= w-edge && y <= edge) {
387
- cursorType = @"nesw-resize"; // Sağ üst köşe - northeast-southwest
388
- isOnBorder = YES;
389
- }
390
- else if (x <= edge && y >= h-edge) {
391
- cursorType = @"nesw-resize"; // Sol alt köşe - southwest-northeast
392
- isOnBorder = YES;
393
- }
394
- else if (x >= w-edge && y >= h-edge) {
395
- cursorType = @"nwse-resize"; // Sağ alt köşe - southeast-northwest
396
- isOnBorder = YES;
397
- }
398
- // Edge resize detection - sadece çok kenarlarda
399
- else if (x <= edge && y > edge && y < h-edge) {
400
- cursorType = @"col-resize"; // Sol kenar - column resize (yatay)
401
- isOnBorder = YES;
402
- }
403
- else if (x >= w-edge && y > edge && y < h-edge) {
404
- cursorType = @"col-resize"; // Sağ kenar - column resize (yatay)
405
- isOnBorder = YES;
406
- }
407
- else if (y <= edge && x > edge && x < w-edge) {
408
- cursorType = @"ns-resize"; // Üst kenar - north-south resize (dikey)
409
- isOnBorder = YES;
410
- }
411
- else if (y >= h-edge && x > edge && x < w-edge) {
412
- cursorType = @"ns-resize"; // Alt kenar - north-south resize (dikey)
413
- isOnBorder = YES;
414
- }
415
-
416
- // Eğer border'da değilse default
417
- if (!isOnBorder) {
418
- cursorType = @"default";
419
- }
420
-
421
- if (position) CFRelease(position);
422
- if (size) CFRelease(size);
423
- } else {
424
- cursorType = @"default";
425
- }
426
- } else {
427
- cursorType = @"default";
428
- }
429
- }
430
- // HER DURUM İÇİN DEFAULT FALLBACK
431
- else {
432
- // Bilinmeyen elementler için her zaman default
433
- cursorType = @"default";
434
- }
435
-
436
- // Check subroles for additional context
437
- CFStringRef subrole = NULL;
438
- error = AXUIElementCopyAttributeValue(elementAtPosition, kAXSubroleAttribute, (CFTypeRef*)&subrole);
439
- if (error == kAXErrorSuccess && subrole) {
440
- NSString *elementSubrole = CopyAndReleaseCFString(subrole);
441
- NSLog(@"🎯 ELEMENT SUBROLE: %@", elementSubrole);
442
-
443
- // Subrole override'ları - sadece çok spesifik durumlar için
444
- if ([elementSubrole isEqualToString:@"AXCloseButton"] ||
445
- [elementSubrole isEqualToString:@"AXMinimizeButton"] ||
446
- [elementSubrole isEqualToString:@"AXZoomButton"] ||
447
- [elementSubrole isEqualToString:@"AXToolbarButton"]) {
448
- cursorType = @"pointer";
449
- }
450
- // Copy/alias subroles - sadece bu durumlar için override
451
- else if ([elementSubrole isEqualToString:@"AXFileDrop"] ||
452
- [elementSubrole isEqualToString:@"AXDropTarget"]) {
453
- cursorType = @"copy";
454
- }
455
- // Alias/shortcut subroles
456
- else if ([elementSubrole isEqualToString:@"AXAlias"] ||
457
- [elementSubrole isEqualToString:@"AXShortcut"]) {
458
- cursorType = @"alias";
459
- }
460
- // Grabbing state (being dragged) - sadece gerçek drag sırasında
461
- else if ([elementSubrole isEqualToString:@"AXDragging"] ||
462
- [elementSubrole isEqualToString:@"AXMoving"]) {
463
- cursorType = @"grabbing";
464
- }
465
- // Zoom controls - sadece spesifik zoom butonları için
466
- else if ([elementSubrole isEqualToString:@"AXZoomIn"]) {
467
- cursorType = @"zoom-in";
468
- }
469
- else if ([elementSubrole isEqualToString:@"AXZoomOut"]) {
470
- cursorType = @"zoom-out";
471
- }
472
- // Subrole'dan bir şey bulamazsa role-based cursor'ı koruyoruz
473
- }
345
+ AXUIElementRef systemWide = AXUIElementCreateSystemWide();
346
+ if (!systemWide) {
347
+ return nil;
348
+ }
474
349
 
475
- CFStringRef roleDescriptionRef = NULL;
476
- AXError descriptionError = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleDescriptionAttribute, (CFTypeRef*)&roleDescriptionRef);
477
- if (descriptionError == kAXErrorSuccess && roleDescriptionRef) {
478
- NSString *roleDescription = CopyAndReleaseCFString(roleDescriptionRef);
479
- if (roleDescription) {
480
- NSString *roleDescriptionLower = [roleDescription lowercaseString];
481
- if ([roleDescriptionLower containsString:@"text"] ||
482
- [roleDescriptionLower containsString:@"editor"] ||
483
- [roleDescriptionLower containsString:@"code"] ||
484
- [roleDescriptionLower containsString:@"document"]) {
485
- cursorType = @"text";
486
- } else if ([roleDescriptionLower containsString:@"button"] ||
487
- [roleDescriptionLower containsString:@"link"] ||
488
- [roleDescriptionLower containsString:@"tab"]) {
489
- cursorType = @"pointer";
490
- }
491
- }
492
- }
350
+ NSString *cursorType = nil;
493
351
 
494
- CFBooleanRef isEditable = NULL;
495
- if (AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXEditable"), (CFTypeRef*)&isEditable) == kAXErrorSuccess &&
496
- isEditable && CFBooleanGetValue(isEditable)) {
497
- cursorType = @"text";
498
- }
499
-
500
- CFBooleanRef supportsTextSelection = NULL;
501
- if (AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXSupportsTextSelection"), (CFTypeRef*)&supportsTextSelection) == kAXErrorSuccess &&
502
- supportsTextSelection && CFBooleanGetValue(supportsTextSelection)) {
503
- cursorType = @"text";
504
- }
352
+ AXUIElementRef elementAtPosition = NULL;
353
+ AXError error = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &elementAtPosition);
354
+ if (error == kAXErrorSuccess && elementAtPosition) {
355
+ cursorType = CursorTypeFromElementOrAncestors(elementAtPosition, cursorPos, 6);
356
+ CFRelease(elementAtPosition);
357
+ }
505
358
 
506
- CFTypeRef valueAttribute = NULL;
507
- if (AXUIElementCopyAttributeValue(elementAtPosition, kAXValueAttribute, &valueAttribute) == kAXErrorSuccess && valueAttribute) {
508
- CFTypeID typeId = CFGetTypeID(valueAttribute);
509
- if (typeId == CFAttributedStringGetTypeID() ||
510
- typeId == CFStringGetTypeID()) {
511
- cursorType = @"text";
512
- }
513
- CFRelease(valueAttribute);
359
+ if (!cursorType) {
360
+ AXValueRef pointValue = AXValueCreate(kAXValueTypeCGPoint, &cursorPos);
361
+ if (pointValue) {
362
+ AXUIElementRef hitElement = NULL;
363
+ AXError hitError = AXUIElementCopyParameterizedAttributeValue(systemWide, kAXHitTestParameterizedAttribute, pointValue, (CFTypeRef *)&hitElement);
364
+ CFRelease(pointValue);
365
+ if (hitError == kAXErrorSuccess && hitElement) {
366
+ cursorType = CursorTypeFromElementOrAncestors(hitElement, cursorPos, 6);
367
+ CFRelease(hitElement);
514
368
  }
515
-
516
369
  }
370
+ }
517
371
 
518
- if (elementAtPosition) {
519
- if ([cursorType isEqualToString:@"default"] && elementSupportsText(elementAtPosition)) {
520
- cursorType = @"text";
521
- }
522
-
523
- if ([cursorType isEqualToString:@"default"] && elementIsClickable(elementAtPosition)) {
524
- cursorType = @"pointer";
525
- }
526
-
527
- if ([cursorType isEqualToString:@"default"]) {
528
- AXValueRef pointValue = AXValueCreate(kAXValueTypeCGPoint, &cursorPos);
529
- if (pointValue) {
530
- AXUIElementRef deepElement = NULL;
531
- AXError hitError = AXUIElementCopyParameterizedAttributeValue(systemWide, kAXHitTestParameterizedAttribute, pointValue, (CFTypeRef *)&deepElement);
532
- CFRelease(pointValue);
533
- if (hitError == kAXErrorSuccess && deepElement) {
534
- if (elementSupportsText(deepElement)) {
535
- cursorType = @"text";
536
- } else if (elementIsClickable(deepElement)) {
537
- cursorType = @"pointer";
538
- }
539
- CFRelease(deepElement);
540
- }
541
- }
542
- }
543
-
544
- if ([cursorType isEqualToString:@"default"]) {
545
- AXUIElementRef focusedElement = NULL;
546
- if (AXUIElementCopyAttributeValue(systemWide, kAXFocusedUIElementAttribute, (CFTypeRef *)&focusedElement) == kAXErrorSuccess && focusedElement) {
547
- if (elementSupportsText(focusedElement) && pointInsideElementFrame(focusedElement, cursorPos)) {
548
- cursorType = @"text";
549
- } else if (elementIsClickable(focusedElement) && pointInsideElementFrame(focusedElement, cursorPos)) {
550
- cursorType = @"pointer";
551
- }
552
- CFRelease(focusedElement);
553
- }
372
+ if (!cursorType) {
373
+ AXUIElementRef focusedElement = NULL;
374
+ if (AXUIElementCopyAttributeValue(systemWide, kAXFocusedUIElementAttribute, (CFTypeRef *)&focusedElement) == kAXErrorSuccess && focusedElement) {
375
+ if (PointInsideElementFrame(focusedElement, cursorPos)) {
376
+ cursorType = CursorTypeFromAccessibilityElement(focusedElement, cursorPos);
554
377
  }
555
-
556
- CFRelease(elementAtPosition);
557
- }
558
- if (systemWide) {
559
- CFRelease(systemWide);
378
+ CFRelease(focusedElement);
560
379
  }
561
-
562
- // Son güvence - eğer cursorType hala nil veya geçersizse default'a çevir
563
- if (!cursorType || [cursorType length] == 0) {
564
- cursorType = @"default";
565
- }
566
-
567
- NSLog(@"🎯 AX CURSOR TYPE: %@", cursorType);
568
- return cursorType;
569
-
570
- } @catch (NSException *exception) {
571
- NSLog(@"Error in detectCursorTypeUsingAccessibility: %@", exception);
572
- return @"default";
573
380
  }
381
+
382
+ CFRelease(systemWide);
383
+ return cursorType;
574
384
  }
575
385
  }
576
386
 
@@ -852,6 +662,10 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
852
662
  NSTimeInterval timestamp = [currentDate timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
853
663
  NSTimeInterval unixTimeMs = [currentDate timeIntervalSince1970] * 1000; // unix timestamp in milliseconds
854
664
  NSString *cursorType = getCursorType();
665
+ // Debug fields: capture raw AX and system cursor types
666
+ NSString *axTypeDbg = detectCursorTypeUsingAccessibility(location);
667
+ NSString *sysTypeDbg = detectSystemCursorType();
668
+ // (already captured above)
855
669
  NSString *eventType = @"move";
856
670
 
857
671
  // Event tipini belirle
@@ -884,7 +698,9 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
884
698
  @"timestamp": @(timestamp),
885
699
  @"unixTimeMs": @(unixTimeMs),
886
700
  @"cursorType": cursorType,
887
- @"type": eventType
701
+ @"type": eventType,
702
+ @"axCursorType": axTypeDbg ? axTypeDbg : @"",
703
+ @"systemCursorType": sysTypeDbg ? sysTypeDbg : @""
888
704
  };
889
705
 
890
706
  // Direkt dosyaya yaz
@@ -917,6 +733,9 @@ void cursorTimerCallback() {
917
733
  NSTimeInterval timestamp = [currentDate timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
918
734
  NSTimeInterval unixTimeMs = [currentDate timeIntervalSince1970] * 1000; // unix timestamp in milliseconds
919
735
  NSString *cursorType = getCursorType();
736
+ // Debug fields: capture raw AX and system cursor types
737
+ NSString *axTypeDbg = detectCursorTypeUsingAccessibility(location);
738
+ NSString *sysTypeDbg = detectSystemCursorType();
920
739
 
921
740
  // Cursor data oluştur
922
741
  NSDictionary *cursorInfo = @{
@@ -925,7 +744,9 @@ void cursorTimerCallback() {
925
744
  @"timestamp": @(timestamp),
926
745
  @"unixTimeMs": @(unixTimeMs),
927
746
  @"cursorType": cursorType,
928
- @"type": @"move"
747
+ @"type": @"move",
748
+ @"axCursorType": axTypeDbg ? axTypeDbg : @"",
749
+ @"systemCursorType": sysTypeDbg ? sysTypeDbg : @""
929
750
  };
930
751
 
931
752
  // Direkt dosyaya yaz