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.
- package/package.json +1 -1
- package/src/cursor_tracker.mm +233 -427
package/package.json
CHANGED
package/src/cursor_tracker.mm
CHANGED
|
@@ -53,37 +53,61 @@ static NSString* CopyAndReleaseCFString(CFStringRef value) {
|
|
|
53
53
|
return result;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
static BOOL
|
|
57
|
-
if (!
|
|
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
|
-
|
|
62
|
-
|
|
68
|
+
static NSString* CopyAttributeString(AXUIElementRef element, CFStringRef attribute) {
|
|
69
|
+
if (!element || !attribute) {
|
|
70
|
+
return nil;
|
|
71
|
+
}
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
AXError
|
|
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 (
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
79
|
-
|
|
99
|
+
BOOL result = NO;
|
|
100
|
+
if (CFGetTypeID(rawValue) == CFBooleanGetTypeID()) {
|
|
101
|
+
result = CFBooleanGetValue((CFBooleanRef)rawValue);
|
|
102
|
+
}
|
|
80
103
|
|
|
81
|
-
|
|
82
|
-
|
|
104
|
+
CFRelease(rawValue);
|
|
105
|
+
*outValue = result;
|
|
106
|
+
return YES;
|
|
83
107
|
}
|
|
84
108
|
|
|
85
|
-
static BOOL
|
|
86
|
-
if (!element || !
|
|
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
|
|
100
|
-
if (CFStringCompare(
|
|
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
|
|
133
|
+
static BOOL PointInsideElementFrame(AXUIElementRef element, CGPoint point) {
|
|
111
134
|
if (!element) {
|
|
112
135
|
return NO;
|
|
113
136
|
}
|
|
114
137
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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 (
|
|
152
|
-
|
|
153
|
-
urlValue = NULL;
|
|
201
|
+
if ((nearRight && nearTop) || (nearLeft && nearBottom)) {
|
|
202
|
+
return @"nesw-resize";
|
|
154
203
|
}
|
|
155
|
-
|
|
156
|
-
|
|
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 (
|
|
166
|
-
|
|
167
|
-
isEnabled = NULL;
|
|
207
|
+
if (nearTop || nearBottom) {
|
|
208
|
+
return @"ns-resize";
|
|
168
209
|
}
|
|
169
210
|
|
|
170
|
-
return
|
|
211
|
+
return nil;
|
|
171
212
|
}
|
|
172
213
|
|
|
173
|
-
static
|
|
214
|
+
static NSString* CursorTypeFromAccessibilityElement(AXUIElementRef element, CGPoint cursorPos) {
|
|
174
215
|
if (!element) {
|
|
175
|
-
return
|
|
216
|
+
return nil;
|
|
176
217
|
}
|
|
177
218
|
|
|
178
|
-
|
|
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
|
-
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
294
|
+
return @"text";
|
|
212
295
|
}
|
|
213
296
|
}
|
|
214
297
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
298
|
+
if (ElementHasAction(element, kAXPressAction) ||
|
|
299
|
+
ElementHasAction(element, kAXShowMenuAction) ||
|
|
300
|
+
ElementHasAction(element, CFSTR("AXConfirm"))) {
|
|
301
|
+
return @"pointer";
|
|
219
302
|
}
|
|
220
303
|
|
|
221
|
-
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
228
|
-
if (
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
495
|
-
if (AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXEditable"), (CFTypeRef*)&isEditable) == kAXErrorSuccess &&
|
|
496
|
-
isEditable && CFBooleanGetValue(isEditable)) {
|
|
497
|
-
cursorType = @"text";
|
|
498
|
-
}
|
|
346
|
+
NSString *cursorType = nil;
|
|
499
347
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
|