node-mac-recorder 2.20.1 → 2.20.2

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 +247 -193
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.20.1",
3
+ "version": "2.20.2",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -40,6 +40,15 @@ static CursorTimerTarget *g_timerTarget = nil;
40
40
  static NSString *g_lastDetectedCursorType = nil;
41
41
  static int g_cursorTypeCounter = 0;
42
42
 
43
+ static NSString* CopyAndReleaseCFString(CFStringRef value) {
44
+ if (!value) {
45
+ return nil;
46
+ }
47
+ NSString *result = [NSString stringWithString:(NSString *)value];
48
+ CFRelease(value);
49
+ return result;
50
+ }
51
+
43
52
  // Mouse button state tracking
44
53
  static bool g_leftMouseDown = false;
45
54
  static bool g_rightMouseDown = false;
@@ -64,220 +73,264 @@ static NSString* detectCursorTypeUsingAccessibility(CGPoint cursorPos) {
64
73
  NSString *cursorType = @"default"; // Default fallback
65
74
 
66
75
  if (error == kAXErrorSuccess && elementAtPosition) {
76
+ NSString *elementRole = nil;
67
77
  CFStringRef role = NULL;
68
78
  error = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleAttribute, (CFTypeRef*)&role);
69
-
70
79
  if (error == kAXErrorSuccess && role) {
71
- NSString *elementRole = (__bridge_transfer NSString*)role;
80
+ elementRole = CopyAndReleaseCFString(role);
81
+ }
82
+
83
+ if (elementRole) {
72
84
  NSLog(@"🎯 ELEMENT ROLE: %@", elementRole);
85
+ }
73
86
 
74
- // TEXT CURSORS
75
- if ([elementRole isEqualToString:@"AXTextField"] ||
76
- [elementRole isEqualToString:@"AXTextArea"] ||
77
- [elementRole isEqualToString:@"AXStaticText"] ||
78
- [elementRole isEqualToString:@"AXSearchField"]) {
79
- cursorType = @"text";
80
- }
81
- // POINTER CURSORS (clickable elements)
82
- else if ([elementRole isEqualToString:@"AXLink"] ||
83
- [elementRole isEqualToString:@"AXButton"] ||
84
- [elementRole isEqualToString:@"AXMenuItem"] ||
85
- [elementRole isEqualToString:@"AXRadioButton"] ||
86
- [elementRole isEqualToString:@"AXCheckBox"] ||
87
- [elementRole isEqualToString:@"AXPopUpButton"] ||
88
- [elementRole isEqualToString:@"AXTab"]) {
89
- cursorType = @"pointer";
87
+ // TEXT CURSORS
88
+ if ([elementRole isEqualToString:@"AXTextField"] ||
89
+ [elementRole isEqualToString:@"AXTextArea"] ||
90
+ [elementRole isEqualToString:@"AXStaticText"] ||
91
+ [elementRole isEqualToString:@"AXSearchField"]) {
92
+ cursorType = @"text";
93
+ }
94
+ // POINTER CURSORS (clickable elements)
95
+ else if ([elementRole isEqualToString:@"AXLink"] ||
96
+ [elementRole isEqualToString:@"AXButton"] ||
97
+ [elementRole isEqualToString:@"AXMenuItem"] ||
98
+ [elementRole isEqualToString:@"AXRadioButton"] ||
99
+ [elementRole isEqualToString:@"AXCheckBox"] ||
100
+ [elementRole isEqualToString:@"AXPopUpButton"] ||
101
+ [elementRole isEqualToString:@"AXTab"]) {
102
+ cursorType = @"pointer";
103
+ }
104
+ // GRAB CURSORS (draggable elements)
105
+ else if ([elementRole isEqualToString:@"AXImage"] ||
106
+ [elementRole isEqualToString:@"AXGroup"]) {
107
+ // Check if element is draggable
108
+ CFBooleanRef draggable = NULL;
109
+ error = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXMovable"), (CFTypeRef*)&draggable);
110
+ if (error == kAXErrorSuccess && draggable && CFBooleanGetValue(draggable)) {
111
+ cursorType = @"grab";
112
+ } else {
113
+ cursorType = @"default";
90
114
  }
91
- // GRAB CURSORS (draggable elements)
92
- else if ([elementRole isEqualToString:@"AXImage"] ||
93
- [elementRole isEqualToString:@"AXGroup"]) {
94
- // Check if element is draggable
95
- CFBooleanRef draggable = NULL;
96
- error = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXMovable"), (CFTypeRef*)&draggable);
97
- if (error == kAXErrorSuccess && draggable && CFBooleanGetValue(draggable)) {
98
- cursorType = @"grab";
115
+ }
116
+ // PROGRESS CURSORS (loading/busy elements)
117
+ else if ([elementRole isEqualToString:@"AXProgressIndicator"] ||
118
+ [elementRole isEqualToString:@"AXBusyIndicator"]) {
119
+ cursorType = @"progress";
120
+ }
121
+ // HELP CURSORS (help buttons/tooltips)
122
+ else if ([elementRole isEqualToString:@"AXHelpTag"] ||
123
+ [elementRole isEqualToString:@"AXTooltip"]) {
124
+ cursorType = @"help";
125
+ }
126
+ // RESIZE CURSORS - sadece AXSplitter için
127
+ else if ([elementRole isEqualToString:@"AXSplitter"]) {
128
+ // Get splitter orientation to determine resize direction
129
+ CFStringRef orientation = NULL;
130
+ error = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXOrientation"), (CFTypeRef*)&orientation);
131
+ if (error == kAXErrorSuccess && orientation) {
132
+ NSString *orientationStr = CopyAndReleaseCFString(orientation);
133
+ if ([orientationStr isEqualToString:@"AXHorizontalOrientation"]) {
134
+ cursorType = @"ns-resize"; // Yatay splitter -> dikey hareket (north-south)
135
+ } else if ([orientationStr isEqualToString:@"AXVerticalOrientation"]) {
136
+ cursorType = @"col-resize"; // Dikey splitter -> yatay hareket (east-west)
99
137
  } else {
100
- cursorType = @"default";
138
+ cursorType = @"default"; // Bilinmeyen orientation
101
139
  }
140
+ } else {
141
+ cursorType = @"default"; // Orientation alınamazsa default
102
142
  }
103
- // PROGRESS CURSORS (loading/busy elements)
104
- else if ([elementRole isEqualToString:@"AXProgressIndicator"] ||
105
- [elementRole isEqualToString:@"AXBusyIndicator"]) {
106
- cursorType = @"progress";
107
- }
108
- // HELP CURSORS (help buttons/tooltips)
109
- else if ([elementRole isEqualToString:@"AXHelpTag"] ||
110
- [elementRole isEqualToString:@"AXTooltip"]) {
111
- cursorType = @"help";
143
+ }
144
+ // SCROLL CURSORS - hep default olsun, all-scroll görünmesin
145
+ else if ([elementRole isEqualToString:@"AXScrollBar"]) {
146
+ cursorType = @"default"; // ScrollBar'lar için de default
147
+ }
148
+ // AXScrollArea - hep default
149
+ else if ([elementRole isEqualToString:@"AXScrollArea"]) {
150
+ cursorType = @"default"; // ScrollArea her zaman default
151
+ }
152
+ // CROSSHAIR CURSORS (drawing/selection tools)
153
+ else if ([elementRole isEqualToString:@"AXCanvas"] ||
154
+ [elementRole isEqualToString:@"AXDrawingArea"]) {
155
+ cursorType = @"crosshair";
156
+ }
157
+ // ZOOM CURSORS (zoom controls)
158
+ else if ([elementRole isEqualToString:@"AXZoomButton"]) {
159
+ cursorType = @"zoom-in";
160
+ }
161
+ // NOT-ALLOWED CURSORS (disabled elements)
162
+ else if ([elementRole isEqualToString:@"AXStaticText"] ||
163
+ [elementRole isEqualToString:@"AXGroup"]) {
164
+ // Check if element is disabled/readonly
165
+ CFBooleanRef enabled = NULL;
166
+ error = AXUIElementCopyAttributeValue(elementAtPosition, kAXEnabledAttribute, (CFTypeRef*)&enabled);
167
+ if (error == kAXErrorSuccess && enabled && !CFBooleanGetValue(enabled)) {
168
+ cursorType = @"not-allowed";
112
169
  }
113
- // RESIZE CURSORS - sadece AXSplitter için
114
- else if ([elementRole isEqualToString:@"AXSplitter"]) {
115
- // Get splitter orientation to determine resize direction
116
- CFStringRef orientation = NULL;
117
- error = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXOrientation"), (CFTypeRef*)&orientation);
118
- if (error == kAXErrorSuccess && orientation) {
119
- NSString *orientationStr = (__bridge_transfer NSString*)orientation;
120
- if ([orientationStr isEqualToString:@"AXHorizontalOrientation"]) {
121
- cursorType = @"ns-resize"; // Yatay splitter -> dikey hareket (north-south)
122
- } else if ([orientationStr isEqualToString:@"AXVerticalOrientation"]) {
123
- cursorType = @"col-resize"; // Dikey splitter -> yatay hareket (east-west)
124
- } else {
125
- cursorType = @"default"; // Bilinmeyen orientation
170
+ }
171
+ // WINDOW BORDER RESIZE - sadece pencere kenarlarında
172
+ else if ([elementRole isEqualToString:@"AXWindow"]) {
173
+ // Check window attributes to see if it's resizable
174
+ CFBooleanRef resizable = NULL;
175
+ AXError resizableError = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXResizeButton"), (CFTypeRef*)&resizable);
176
+
177
+ // Sadece resize edilebilir pencereler için cursor değişimi
178
+ if (resizableError == kAXErrorSuccess || true) { // AXResizeButton bulunamazsa da devam et
179
+ CFTypeRef position = NULL;
180
+ CFTypeRef size = NULL;
181
+ error = AXUIElementCopyAttributeValue(elementAtPosition, kAXPositionAttribute, &position);
182
+ AXError sizeError = AXUIElementCopyAttributeValue(elementAtPosition, kAXSizeAttribute, &size);
183
+
184
+ if (error == kAXErrorSuccess && sizeError == kAXErrorSuccess && position && size) {
185
+ CGPoint windowPos;
186
+ CGSize windowSize;
187
+ AXValueGetValue((AXValueRef)position, kAXValueTypeCGPoint, &windowPos);
188
+ AXValueGetValue((AXValueRef)size, kAXValueTypeCGSize, &windowSize);
189
+
190
+ CGFloat x = cursorPos.x - windowPos.x;
191
+ CGFloat y = cursorPos.y - windowPos.y;
192
+ CGFloat w = windowSize.width;
193
+ CGFloat h = windowSize.height;
194
+ CGFloat edge = 3.0; // Daha küçük edge detection (3px)
195
+
196
+ // Sadece çok kenar köşelerde resize cursor'ı göster
197
+ BOOL isOnBorder = NO;
198
+
199
+ // Corner resize detection - çok dar alanda, doğru açılar
200
+ if (x <= edge && y <= edge) {
201
+ cursorType = @"nwse-resize"; // Sol üst köşe - northwest-southeast
202
+ isOnBorder = YES;
203
+ }
204
+ else if (x >= w-edge && y <= edge) {
205
+ cursorType = @"nesw-resize"; // Sağ üst köşe - northeast-southwest
206
+ isOnBorder = YES;
207
+ }
208
+ else if (x <= edge && y >= h-edge) {
209
+ cursorType = @"nesw-resize"; // Sol alt köşe - southwest-northeast
210
+ isOnBorder = YES;
211
+ }
212
+ else if (x >= w-edge && y >= h-edge) {
213
+ cursorType = @"nwse-resize"; // Sağ alt köşe - southeast-northwest
214
+ isOnBorder = YES;
215
+ }
216
+ // Edge resize detection - sadece çok kenarlarda
217
+ else if (x <= edge && y > edge && y < h-edge) {
218
+ cursorType = @"col-resize"; // Sol kenar - column resize (yatay)
219
+ isOnBorder = YES;
220
+ }
221
+ else if (x >= w-edge && y > edge && y < h-edge) {
222
+ cursorType = @"col-resize"; // Sağ kenar - column resize (yatay)
223
+ isOnBorder = YES;
224
+ }
225
+ else if (y <= edge && x > edge && x < w-edge) {
226
+ cursorType = @"ns-resize"; // Üst kenar - north-south resize (dikey)
227
+ isOnBorder = YES;
228
+ }
229
+ else if (y >= h-edge && x > edge && x < w-edge) {
230
+ cursorType = @"ns-resize"; // Alt kenar - north-south resize (dikey)
231
+ isOnBorder = YES;
126
232
  }
233
+
234
+ // Eğer border'da değilse default
235
+ if (!isOnBorder) {
236
+ cursorType = @"default";
237
+ }
238
+
239
+ if (position) CFRelease(position);
240
+ if (size) CFRelease(size);
127
241
  } else {
128
- cursorType = @"default"; // Orientation alınamazsa default
242
+ cursorType = @"default";
129
243
  }
244
+ } else {
245
+ cursorType = @"default";
130
246
  }
131
- // SCROLL CURSORS - hep default olsun, all-scroll görünmesin
132
- else if ([elementRole isEqualToString:@"AXScrollBar"]) {
133
- cursorType = @"default"; // ScrollBar'lar için de default
134
- }
135
- // AXScrollArea - hep default
136
- else if ([elementRole isEqualToString:@"AXScrollArea"]) {
137
- cursorType = @"default"; // ScrollArea her zaman default
247
+ }
248
+ // HER DURUM İÇİN DEFAULT FALLBACK
249
+ else {
250
+ // Bilinmeyen elementler için her zaman default
251
+ cursorType = @"default";
252
+ }
253
+
254
+ // Check subroles for additional context
255
+ CFStringRef subrole = NULL;
256
+ error = AXUIElementCopyAttributeValue(elementAtPosition, kAXSubroleAttribute, (CFTypeRef*)&subrole);
257
+ if (error == kAXErrorSuccess && subrole) {
258
+ NSString *elementSubrole = CopyAndReleaseCFString(subrole);
259
+ NSLog(@"🎯 ELEMENT SUBROLE: %@", elementSubrole);
260
+
261
+ // Subrole override'ları - sadece çok spesifik durumlar için
262
+ if ([elementSubrole isEqualToString:@"AXCloseButton"] ||
263
+ [elementSubrole isEqualToString:@"AXMinimizeButton"] ||
264
+ [elementSubrole isEqualToString:@"AXZoomButton"] ||
265
+ [elementSubrole isEqualToString:@"AXToolbarButton"]) {
266
+ cursorType = @"pointer";
138
267
  }
139
- // CROSSHAIR CURSORS (drawing/selection tools)
140
- else if ([elementRole isEqualToString:@"AXCanvas"] ||
141
- [elementRole isEqualToString:@"AXDrawingArea"]) {
142
- cursorType = @"crosshair";
268
+ // Copy/alias subroles - sadece bu durumlar için override
269
+ else if ([elementSubrole isEqualToString:@"AXFileDrop"] ||
270
+ [elementSubrole isEqualToString:@"AXDropTarget"]) {
271
+ cursorType = @"copy";
143
272
  }
144
- // ZOOM CURSORS (zoom controls)
145
- else if ([elementRole isEqualToString:@"AXZoomButton"]) {
146
- cursorType = @"zoom-in";
273
+ // Alias/shortcut subroles
274
+ else if ([elementSubrole isEqualToString:@"AXAlias"] ||
275
+ [elementSubrole isEqualToString:@"AXShortcut"]) {
276
+ cursorType = @"alias";
147
277
  }
148
- // NOT-ALLOWED CURSORS (disabled elements)
149
- else if ([elementRole isEqualToString:@"AXStaticText"] ||
150
- [elementRole isEqualToString:@"AXGroup"]) {
151
- // Check if element is disabled/readonly
152
- CFBooleanRef enabled = NULL;
153
- error = AXUIElementCopyAttributeValue(elementAtPosition, kAXEnabledAttribute, (CFTypeRef*)&enabled);
154
- if (error == kAXErrorSuccess && enabled && !CFBooleanGetValue(enabled)) {
155
- cursorType = @"not-allowed";
156
- }
278
+ // Grabbing state (being dragged) - sadece gerçek drag sırasında
279
+ else if ([elementSubrole isEqualToString:@"AXDragging"] ||
280
+ [elementSubrole isEqualToString:@"AXMoving"]) {
281
+ cursorType = @"grabbing";
157
282
  }
158
- // WINDOW BORDER RESIZE - sadece pencere kenarlarında
159
- else if ([elementRole isEqualToString:@"AXWindow"]) {
160
- // Check window attributes to see if it's resizable
161
- CFBooleanRef resizable = NULL;
162
- AXError resizableError = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXResizeButton"), (CFTypeRef*)&resizable);
163
-
164
- // Sadece resize edilebilir pencereler için cursor değişimi
165
- if (resizableError == kAXErrorSuccess || true) { // AXResizeButton bulunamazsa da devam et
166
- CFTypeRef position = NULL;
167
- CFTypeRef size = NULL;
168
- error = AXUIElementCopyAttributeValue(elementAtPosition, kAXPositionAttribute, &position);
169
- AXError sizeError = AXUIElementCopyAttributeValue(elementAtPosition, kAXSizeAttribute, &size);
170
-
171
- if (error == kAXErrorSuccess && sizeError == kAXErrorSuccess && position && size) {
172
- CGPoint windowPos;
173
- CGSize windowSize;
174
- AXValueGetValue((AXValueRef)position, kAXValueTypeCGPoint, &windowPos);
175
- AXValueGetValue((AXValueRef)size, kAXValueTypeCGSize, &windowSize);
176
-
177
- CGFloat x = cursorPos.x - windowPos.x;
178
- CGFloat y = cursorPos.y - windowPos.y;
179
- CGFloat w = windowSize.width;
180
- CGFloat h = windowSize.height;
181
- CGFloat edge = 3.0; // Daha küçük edge detection (3px)
182
-
183
- // Sadece çok kenar köşelerde resize cursor'ı göster
184
- BOOL isOnBorder = NO;
185
-
186
- // Corner resize detection - çok dar alanda, doğru açılar
187
- if (x <= edge && y <= edge) {
188
- cursorType = @"nwse-resize"; // Sol üst köşe - northwest-southeast
189
- isOnBorder = YES;
190
- }
191
- else if (x >= w-edge && y <= edge) {
192
- cursorType = @"nesw-resize"; // Sağ üst köşe - northeast-southwest
193
- isOnBorder = YES;
194
- }
195
- else if (x <= edge && y >= h-edge) {
196
- cursorType = @"nesw-resize"; // Sol alt köşe - southwest-northeast
197
- isOnBorder = YES;
198
- }
199
- else if (x >= w-edge && y >= h-edge) {
200
- cursorType = @"nwse-resize"; // Sağ alt köşe - southeast-northwest
201
- isOnBorder = YES;
202
- }
203
- // Edge resize detection - sadece çok kenarlarda
204
- else if (x <= edge && y > edge && y < h-edge) {
205
- cursorType = @"col-resize"; // Sol kenar - column resize (yatay)
206
- isOnBorder = YES;
207
- }
208
- else if (x >= w-edge && y > edge && y < h-edge) {
209
- cursorType = @"col-resize"; // Sağ kenar - column resize (yatay)
210
- isOnBorder = YES;
211
- }
212
- else if (y <= edge && x > edge && x < w-edge) {
213
- cursorType = @"ns-resize"; // Üst kenar - north-south resize (dikey)
214
- isOnBorder = YES;
215
- }
216
- else if (y >= h-edge && x > edge && x < w-edge) {
217
- cursorType = @"ns-resize"; // Alt kenar - north-south resize (dikey)
218
- isOnBorder = YES;
219
- }
220
-
221
- // Eğer border'da değilse default
222
- if (!isOnBorder) {
223
- cursorType = @"default";
224
- }
225
-
226
- if (position) CFRelease(position);
227
- if (size) CFRelease(size);
228
- } else {
229
- cursorType = @"default";
230
- }
231
- } else {
232
- cursorType = @"default";
233
- }
283
+ // Zoom controls - sadece spesifik zoom butonları için
284
+ else if ([elementSubrole isEqualToString:@"AXZoomIn"]) {
285
+ cursorType = @"zoom-in";
234
286
  }
235
- // HER DURUM İÇİN DEFAULT FALLBACK
236
- else {
237
- // Bilinmeyen elementler için her zaman default
238
- cursorType = @"default";
287
+ else if ([elementSubrole isEqualToString:@"AXZoomOut"]) {
288
+ cursorType = @"zoom-out";
239
289
  }
290
+ // Subrole'dan bir şey bulamazsa role-based cursor'ı koruyoruz
291
+ }
240
292
 
241
- // Check subroles for additional context
242
- CFStringRef subrole = NULL;
243
- error = AXUIElementCopyAttributeValue(elementAtPosition, kAXSubroleAttribute, (CFTypeRef*)&subrole);
244
- if (error == kAXErrorSuccess && subrole) {
245
- NSString *elementSubrole = (__bridge_transfer NSString*)subrole;
246
- NSLog(@"🎯 ELEMENT SUBROLE: %@", elementSubrole);
247
-
248
- // Subrole override'ları - sadece çok spesifik durumlar için
249
- if ([elementSubrole isEqualToString:@"AXCloseButton"] ||
250
- [elementSubrole isEqualToString:@"AXMinimizeButton"] ||
251
- [elementSubrole isEqualToString:@"AXZoomButton"] ||
252
- [elementSubrole isEqualToString:@"AXToolbarButton"]) {
293
+ CFStringRef roleDescriptionRef = NULL;
294
+ AXError descriptionError = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleDescriptionAttribute, (CFTypeRef*)&roleDescriptionRef);
295
+ if (descriptionError == kAXErrorSuccess && roleDescriptionRef) {
296
+ NSString *roleDescription = CopyAndReleaseCFString(roleDescriptionRef);
297
+ if (roleDescription) {
298
+ NSString *roleDescriptionLower = [roleDescription lowercaseString];
299
+ if ([roleDescriptionLower containsString:@"text"] ||
300
+ [roleDescriptionLower containsString:@"editor"] ||
301
+ [roleDescriptionLower containsString:@"code"] ||
302
+ [roleDescriptionLower containsString:@"document"]) {
303
+ cursorType = @"text";
304
+ } else if ([roleDescriptionLower containsString:@"button"] ||
305
+ [roleDescriptionLower containsString:@"link"] ||
306
+ [roleDescriptionLower containsString:@"tab"]) {
253
307
  cursorType = @"pointer";
254
308
  }
255
- // Copy/alias subroles - sadece bu durumlar için override
256
- else if ([elementSubrole isEqualToString:@"AXFileDrop"] ||
257
- [elementSubrole isEqualToString:@"AXDropTarget"]) {
258
- cursorType = @"copy";
259
- }
260
- // Alias/shortcut subroles
261
- else if ([elementSubrole isEqualToString:@"AXAlias"] ||
262
- [elementSubrole isEqualToString:@"AXShortcut"]) {
263
- cursorType = @"alias";
264
- }
265
- // Grabbing state (being dragged) - sadece gerçek drag sırasında
266
- else if ([elementSubrole isEqualToString:@"AXDragging"] ||
267
- [elementSubrole isEqualToString:@"AXMoving"]) {
268
- cursorType = @"grabbing";
269
- }
270
- // Zoom controls - sadece spesifik zoom butonları için
271
- else if ([elementSubrole isEqualToString:@"AXZoomIn"]) {
272
- cursorType = @"zoom-in";
273
- }
274
- else if ([elementSubrole isEqualToString:@"AXZoomOut"]) {
275
- cursorType = @"zoom-out";
276
- }
277
- // Subrole'dan bir şey bulamazsa role-based cursor'ı koruyoruz
278
309
  }
279
310
  }
280
311
 
312
+ CFBooleanRef isEditable = NULL;
313
+ if (AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXEditable"), (CFTypeRef*)&isEditable) == kAXErrorSuccess &&
314
+ isEditable && CFBooleanGetValue(isEditable)) {
315
+ cursorType = @"text";
316
+ }
317
+
318
+ CFBooleanRef supportsTextSelection = NULL;
319
+ if (AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXSupportsTextSelection"), (CFTypeRef*)&supportsTextSelection) == kAXErrorSuccess &&
320
+ supportsTextSelection && CFBooleanGetValue(supportsTextSelection)) {
321
+ cursorType = @"text";
322
+ }
323
+
324
+ CFTypeRef valueAttribute = NULL;
325
+ if (AXUIElementCopyAttributeValue(elementAtPosition, kAXValueAttribute, &valueAttribute) == kAXErrorSuccess && valueAttribute) {
326
+ CFTypeID typeId = CFGetTypeID(valueAttribute);
327
+ if (typeId == CFAttributedStringGetTypeID() ||
328
+ typeId == CFStringGetTypeID()) {
329
+ cursorType = @"text";
330
+ }
331
+ CFRelease(valueAttribute);
332
+ }
333
+
281
334
  }
282
335
 
283
336
  if (elementAtPosition) {
@@ -482,10 +535,6 @@ NSString* getCursorType() {
482
535
  g_cursorTypeCounter++;
483
536
 
484
537
  NSString *systemCursorType = detectSystemCursorType();
485
- if (systemCursorType && ![systemCursorType isEqualToString:@"default"]) {
486
- NSLog(@"🎯 FINAL CURSOR TYPE: %@", systemCursorType);
487
- return systemCursorType;
488
- }
489
538
 
490
539
  NSString *axCursorType = nil;
491
540
  BOOL hasCursorPosition = NO;
@@ -520,7 +569,12 @@ NSString* getCursorType() {
520
569
  if (axCursorType && ![axCursorType isEqualToString:@"default"]) {
521
570
  finalType = axCursorType;
522
571
  } else if (systemCursorType && [systemCursorType length] > 0) {
523
- finalType = systemCursorType;
572
+ if ([systemCursorType isEqualToString:@"pointer"] &&
573
+ (!axCursorType || [axCursorType isEqualToString:@"default"])) {
574
+ finalType = @"default";
575
+ } else {
576
+ finalType = systemCursorType;
577
+ }
524
578
  } else {
525
579
  finalType = @"default";
526
580
  }