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.
- package/package.json +1 -1
- package/src/cursor_tracker.mm +250 -429
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,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
|
|
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;
|
|
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
|
|
236
|
+
BOOL supportsSelection = NO;
|
|
237
|
+
if (CopyAttributeBoolean(element, CFSTR("AXSupportsTextSelection"), &supportsSelection) && supportsSelection) {
|
|
238
|
+
return @"text";
|
|
239
|
+
}
|
|
179
240
|
|
|
180
|
-
|
|
181
|
-
if (AXUIElementCopyAttributeValue(element,
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
288
|
+
return @"text";
|
|
212
289
|
}
|
|
213
290
|
}
|
|
214
291
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
328
|
+
AXUIElementRef parent = CopyParent(current);
|
|
329
|
+
if (current != element) CFRelease(current);
|
|
330
|
+
current = parent;
|
|
331
|
+
depth++;
|
|
234
332
|
}
|
|
235
|
-
|
|
236
|
-
return
|
|
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
|
-
|
|
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
|
-
}
|
|
345
|
+
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
346
|
+
if (!systemWide) {
|
|
347
|
+
return nil;
|
|
348
|
+
}
|
|
474
349
|
|
|
475
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|