node-mac-recorder 2.20.3 → 2.20.4

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 +233 -427
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.4",
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,214 @@ 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;
176
217
  }
177
218
 
178
- BOOL supportsText = NO;
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";
234
+ }
179
235
 
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;
236
+ BOOL supportsSelection = NO;
237
+ if (CopyAttributeBoolean(element, CFSTR("AXSupportsTextSelection"), &supportsSelection) && supportsSelection) {
238
+ return @"text";
239
+ }
240
+
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);
249
+ }
250
+
251
+ if (StringEqualsAny(role, @[@"AXProgressIndicator", @"AXBusyIndicator"])) {
252
+ return @"progress";
191
253
  }
192
254
 
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;
255
+ if (StringEqualsAny(role, @[@"AXHelpTag", @"AXTooltip"])) {
256
+ return @"help";
257
+ }
258
+
259
+ if ([role isEqualToString:@"AXSplitter"]) {
260
+ NSString *orientation = CopyAttributeString(element, CFSTR("AXOrientation"));
261
+ if ([orientation isEqualToString:@"AXHorizontalOrientation"]) {
262
+ return @"ns-resize";
263
+ }
264
+ if ([orientation isEqualToString:@"AXVerticalOrientation"]) {
265
+ return @"col-resize";
200
266
  }
201
267
  }
202
268
 
203
- CFStringRef roleDescriptionRef = NULL;
204
- if (!supportsText && AXUIElementCopyAttributeValue(element, kAXRoleDescriptionAttribute, (CFTypeRef *)&roleDescriptionRef) == kAXErrorSuccess && roleDescriptionRef) {
205
- NSString *roleDescription = CopyAndReleaseCFString(roleDescriptionRef);
269
+ if ([role isEqualToString:@"AXWindow"]) {
270
+ NSString *windowCursor = CursorTypeForWindowBorder(element, cursorPos);
271
+ if (windowCursor) {
272
+ return windowCursor;
273
+ }
274
+ }
275
+
276
+ if (StringEqualsAny(role, @[@"AXMenuItem", @"AXButton", @"AXLink", @"AXTab", @"AXRadioButton", @"AXCheckBox", @"AXPopUpButton"])) {
277
+ return @"pointer";
278
+ }
279
+
280
+ if (StringEqualsAny(subrole, @[@"AXLink", @"AXToolbarButton", @"AXFileDrop", @"AXDropTarget"])) {
281
+ return @"pointer";
282
+ }
283
+
284
+ if (roleDescription) {
206
285
  NSString *lower = [roleDescription lowercaseString];
286
+ if ([lower containsString:@"button"] ||
287
+ [lower containsString:@"link"] ||
288
+ [lower containsString:@"tab"]) {
289
+ return @"pointer";
290
+ }
207
291
  if ([lower containsString:@"text"] ||
208
292
  [lower containsString:@"editor"] ||
209
- [lower containsString:@"code"] ||
210
293
  [lower containsString:@"document"]) {
211
- supportsText = YES;
294
+ return @"text";
212
295
  }
213
296
  }
214
297
 
215
- CFBooleanRef editable = NULL;
216
- if (!supportsText && AXUIElementCopyAttributeValue(element, CFSTR("AXEditable"), (CFTypeRef *)&editable) == kAXErrorSuccess && editable) {
217
- supportsText = CFBooleanGetValue(editable);
218
- CFRelease(editable);
298
+ if (ElementHasAction(element, kAXPressAction) ||
299
+ ElementHasAction(element, kAXShowMenuAction) ||
300
+ ElementHasAction(element, CFSTR("AXConfirm"))) {
301
+ return @"pointer";
219
302
  }
220
303
 
221
- CFBooleanRef supportsSelection = NULL;
222
- if (!supportsText && AXUIElementCopyAttributeValue(element, CFSTR("AXSupportsTextSelection"), (CFTypeRef *)&supportsSelection) == kAXErrorSuccess && supportsSelection) {
223
- supportsText = CFBooleanGetValue(supportsSelection);
224
- CFRelease(supportsSelection);
304
+ CFTypeRef urlValue = NULL;
305
+ if (AXUIElementCopyAttributeValue(element, kAXURLAttribute, &urlValue) == kAXErrorSuccess && urlValue) {
306
+ CFRelease(urlValue);
307
+ return @"pointer";
308
+ }
309
+ if (urlValue) {
310
+ CFRelease(urlValue);
225
311
  }
226
312
 
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;
232
- }
233
- CFRelease(valueAttribute);
313
+ BOOL isDraggable = NO;
314
+ if (CopyAttributeBoolean(element, CFSTR("AXDraggable"), &isDraggable) && isDraggable) {
315
+ return @"grab";
234
316
  }
235
317
 
236
- return supportsText;
318
+ BOOL isMovable = NO;
319
+ if (CopyAttributeBoolean(element, CFSTR("AXMovable"), &isMovable) && isMovable) {
320
+ return @"grab";
321
+ }
322
+
323
+ if (subrole && [subrole isEqualToString:@"AXZoomIn"]) {
324
+ return @"zoom-in";
325
+ }
326
+ if (subrole && [subrole isEqualToString:@"AXZoomOut"]) {
327
+ return @"zoom-out";
328
+ }
329
+
330
+ return nil;
237
331
  }
238
332
 
239
333
  // Mouse button state tracking
@@ -244,333 +338,45 @@ static NSString *g_lastEventType = @"move";
244
338
  // Accessibility tabanlı cursor tip tespiti
245
339
  static NSString* detectCursorTypeUsingAccessibility(CGPoint cursorPos) {
246
340
  @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
- }
474
-
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
- }
341
+ AXUIElementRef systemWide = AXUIElementCreateSystemWide();
342
+ if (!systemWide) {
343
+ return nil;
344
+ }
493
345
 
494
- CFBooleanRef isEditable = NULL;
495
- if (AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXEditable"), (CFTypeRef*)&isEditable) == kAXErrorSuccess &&
496
- isEditable && CFBooleanGetValue(isEditable)) {
497
- cursorType = @"text";
498
- }
346
+ NSString *cursorType = nil;
499
347
 
500
- CFBooleanRef supportsTextSelection = NULL;
501
- if (AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXSupportsTextSelection"), (CFTypeRef*)&supportsTextSelection) == kAXErrorSuccess &&
502
- supportsTextSelection && CFBooleanGetValue(supportsTextSelection)) {
503
- cursorType = @"text";
504
- }
348
+ AXUIElementRef elementAtPosition = NULL;
349
+ AXError error = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &elementAtPosition);
350
+ if (error == kAXErrorSuccess && elementAtPosition) {
351
+ cursorType = CursorTypeFromAccessibilityElement(elementAtPosition, cursorPos);
352
+ CFRelease(elementAtPosition);
353
+ }
505
354
 
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);
355
+ if (!cursorType) {
356
+ AXValueRef pointValue = AXValueCreate(kAXValueTypeCGPoint, &cursorPos);
357
+ if (pointValue) {
358
+ AXUIElementRef hitElement = NULL;
359
+ AXError hitError = AXUIElementCopyParameterizedAttributeValue(systemWide, kAXHitTestParameterizedAttribute, pointValue, (CFTypeRef *)&hitElement);
360
+ CFRelease(pointValue);
361
+ if (hitError == kAXErrorSuccess && hitElement) {
362
+ cursorType = CursorTypeFromAccessibilityElement(hitElement, cursorPos);
363
+ CFRelease(hitElement);
514
364
  }
515
-
516
365
  }
366
+ }
517
367
 
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
- }
368
+ if (!cursorType) {
369
+ AXUIElementRef focusedElement = NULL;
370
+ if (AXUIElementCopyAttributeValue(systemWide, kAXFocusedUIElementAttribute, (CFTypeRef *)&focusedElement) == kAXErrorSuccess && focusedElement) {
371
+ if (PointInsideElementFrame(focusedElement, cursorPos)) {
372
+ cursorType = CursorTypeFromAccessibilityElement(focusedElement, cursorPos);
554
373
  }
555
-
556
- CFRelease(elementAtPosition);
557
- }
558
- if (systemWide) {
559
- CFRelease(systemWide);
374
+ CFRelease(focusedElement);
560
375
  }
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
376
  }
377
+
378
+ CFRelease(systemWide);
379
+ return cursorType;
574
380
  }
575
381
  }
576
382