node-mac-recorder 2.17.4 → 2.17.6
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 +179 -80
package/package.json
CHANGED
package/src/cursor_tracker.mm
CHANGED
|
@@ -55,110 +55,209 @@ NSString* getCursorType() {
|
|
|
55
55
|
g_cursorTypeCounter++;
|
|
56
56
|
|
|
57
57
|
@try {
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
NSString *cursorType = @"default";
|
|
61
|
-
|
|
62
|
-
// Get cursor image info
|
|
63
|
-
NSImage *cursorImage = [currentCursor image];
|
|
64
|
-
NSPoint hotSpot = [currentCursor hotSpot];
|
|
65
|
-
NSSize imageSize = [cursorImage size];
|
|
66
|
-
|
|
67
|
-
// Check cursor type by comparing with standard cursors
|
|
68
|
-
if ([currentCursor isEqual:[NSCursor pointingHandCursor]] ||
|
|
69
|
-
(hotSpot.x >= 5 && hotSpot.x <= 7 && hotSpot.y >= 0 && hotSpot.y <= 4) ||
|
|
70
|
-
(hotSpot.x >= 12 && hotSpot.x <= 14 && hotSpot.y >= 7 && hotSpot.y <= 9)) {
|
|
71
|
-
return @"pointer";
|
|
72
|
-
} else if ([currentCursor isEqual:[NSCursor IBeamCursor]] ||
|
|
73
|
-
(hotSpot.x >= 3 && hotSpot.x <= 5 && hotSpot.y >= 8 && hotSpot.y <= 10 &&
|
|
74
|
-
imageSize.width <= 10 && imageSize.height >= 16)) {
|
|
75
|
-
return @"text";
|
|
76
|
-
} else if ([currentCursor isEqual:[NSCursor resizeLeftRightCursor]]) {
|
|
77
|
-
return @"col-resize";
|
|
78
|
-
} else if ([currentCursor isEqual:[NSCursor resizeUpDownCursor]]) {
|
|
79
|
-
return @"row-resize";
|
|
80
|
-
} else if ([currentCursor isEqual:[NSCursor openHandCursor]]) {
|
|
81
|
-
return @"grab";
|
|
82
|
-
} else if ([currentCursor isEqual:[NSCursor closedHandCursor]]) {
|
|
83
|
-
return @"grabbing";
|
|
84
|
-
} else if ([currentCursor isEqual:[NSCursor crosshairCursor]]) {
|
|
85
|
-
return @"crosshair";
|
|
86
|
-
} else if ([currentCursor isEqual:[NSCursor disappearingItemCursor]]) {
|
|
87
|
-
return @"alias";
|
|
88
|
-
} else if ([currentCursor isEqual:[NSCursor dragCopyCursor]]) {
|
|
89
|
-
return @"copy";
|
|
90
|
-
} else if ([currentCursor isEqual:[NSCursor operationNotAllowedCursor]]) {
|
|
91
|
-
return @"not-allowed";
|
|
92
|
-
} else if ([currentCursor isEqual:[NSCursor contextualMenuCursor]]) {
|
|
93
|
-
return @"help";
|
|
94
|
-
}
|
|
58
|
+
// ACCESSIBILITY API BASED CURSOR DETECTION
|
|
59
|
+
// Determine cursor type based on the UI element under the cursor
|
|
95
60
|
|
|
96
|
-
|
|
97
|
-
// Check if we're in a drag operation
|
|
98
|
-
CGEventRef event = CGEventCreate(NULL);
|
|
99
|
-
if (event) {
|
|
100
|
-
CGEventType eventType = (CGEventType)CGEventGetType(event);
|
|
101
|
-
if (eventType == kCGEventLeftMouseDragged ||
|
|
102
|
-
eventType == kCGEventRightMouseDragged) {
|
|
103
|
-
CFRelease(event);
|
|
104
|
-
return @"grabbing";
|
|
105
|
-
}
|
|
106
|
-
CFRelease(event);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Get the window under the cursor
|
|
110
61
|
CGPoint cursorPos = CGEventGetLocation(CGEventCreate(NULL));
|
|
111
62
|
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
112
63
|
AXUIElementRef elementAtPosition = NULL;
|
|
113
64
|
AXError error = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &elementAtPosition);
|
|
114
|
-
|
|
65
|
+
|
|
66
|
+
NSString *cursorType = @"default"; // Default fallback
|
|
67
|
+
|
|
115
68
|
if (error == kAXErrorSuccess && elementAtPosition) {
|
|
116
69
|
CFStringRef role = NULL;
|
|
117
70
|
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleAttribute, (CFTypeRef*)&role);
|
|
118
|
-
|
|
71
|
+
|
|
119
72
|
if (error == kAXErrorSuccess && role) {
|
|
120
73
|
NSString *elementRole = (__bridge_transfer NSString*)role;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
[elementRole isEqualToString:@"
|
|
126
|
-
[elementRole isEqualToString:@"
|
|
127
|
-
[elementRole isEqualToString:@"
|
|
128
|
-
|
|
74
|
+
NSLog(@"🎯 ELEMENT ROLE: %@", elementRole);
|
|
75
|
+
|
|
76
|
+
// TEXT CURSORS
|
|
77
|
+
if ([elementRole isEqualToString:@"AXTextField"] ||
|
|
78
|
+
[elementRole isEqualToString:@"AXTextArea"] ||
|
|
79
|
+
[elementRole isEqualToString:@"AXStaticText"] ||
|
|
80
|
+
[elementRole isEqualToString:@"AXSearchField"]) {
|
|
81
|
+
cursorType = @"text";
|
|
129
82
|
}
|
|
130
|
-
|
|
131
|
-
|
|
83
|
+
// POINTER CURSORS (clickable elements)
|
|
84
|
+
else if ([elementRole isEqualToString:@"AXLink"] ||
|
|
85
|
+
[elementRole isEqualToString:@"AXButton"] ||
|
|
86
|
+
[elementRole isEqualToString:@"AXMenuItem"] ||
|
|
87
|
+
[elementRole isEqualToString:@"AXRadioButton"] ||
|
|
88
|
+
[elementRole isEqualToString:@"AXCheckBox"] ||
|
|
89
|
+
[elementRole isEqualToString:@"AXPopUpButton"] ||
|
|
90
|
+
[elementRole isEqualToString:@"AXTab"]) {
|
|
91
|
+
cursorType = @"pointer";
|
|
92
|
+
}
|
|
93
|
+
// GRAB CURSORS (draggable elements)
|
|
94
|
+
else if ([elementRole isEqualToString:@"AXImage"] ||
|
|
95
|
+
[elementRole isEqualToString:@"AXGroup"]) {
|
|
96
|
+
// Check if element is draggable
|
|
97
|
+
CFBooleanRef draggable = NULL;
|
|
98
|
+
error = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXMovable"), (CFTypeRef*)&draggable);
|
|
99
|
+
if (error == kAXErrorSuccess && draggable && CFBooleanGetValue(draggable)) {
|
|
100
|
+
cursorType = @"grab";
|
|
101
|
+
} else {
|
|
102
|
+
cursorType = @"default";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// PROGRESS CURSORS (loading/busy elements)
|
|
106
|
+
else if ([elementRole isEqualToString:@"AXProgressIndicator"] ||
|
|
107
|
+
[elementRole isEqualToString:@"AXBusyIndicator"]) {
|
|
108
|
+
cursorType = @"progress";
|
|
109
|
+
}
|
|
110
|
+
// HELP CURSORS (help buttons/tooltips)
|
|
111
|
+
else if ([elementRole isEqualToString:@"AXHelpTag"] ||
|
|
112
|
+
[elementRole isEqualToString:@"AXTooltip"]) {
|
|
113
|
+
cursorType = @"help";
|
|
114
|
+
}
|
|
115
|
+
// RESIZE CURSORS
|
|
116
|
+
else if ([elementRole isEqualToString:@"AXSplitter"]) {
|
|
117
|
+
// Get splitter orientation to determine resize direction
|
|
118
|
+
CFStringRef orientation = NULL;
|
|
119
|
+
error = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXOrientation"), (CFTypeRef*)&orientation);
|
|
120
|
+
if (error == kAXErrorSuccess && orientation) {
|
|
121
|
+
NSString *orientationStr = (__bridge_transfer NSString*)orientation;
|
|
122
|
+
if ([orientationStr isEqualToString:@"AXHorizontalOrientation"]) {
|
|
123
|
+
cursorType = @"row-resize"; // vertical splitter -> row resize
|
|
124
|
+
} else {
|
|
125
|
+
cursorType = @"col-resize"; // horizontal splitter -> col resize
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
cursorType = @"col-resize"; // default
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// SCROLL CURSORS
|
|
132
|
+
else if ([elementRole isEqualToString:@"AXScrollBar"]) {
|
|
133
|
+
// Check orientation for scroll direction
|
|
134
|
+
CFStringRef orientation = NULL;
|
|
135
|
+
error = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXOrientation"), (CFTypeRef*)&orientation);
|
|
136
|
+
if (error == kAXErrorSuccess && orientation) {
|
|
137
|
+
NSString *orientationStr = (__bridge_transfer NSString*)orientation;
|
|
138
|
+
if ([orientationStr isEqualToString:@"AXVerticalOrientation"]) {
|
|
139
|
+
cursorType = @"ns-resize"; // vertical scrollbar
|
|
140
|
+
} else {
|
|
141
|
+
cursorType = @"col-resize"; // horizontal scrollbar
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
cursorType = @"all-scroll";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if ([elementRole isEqualToString:@"AXScrollArea"]) {
|
|
148
|
+
cursorType = @"all-scroll";
|
|
149
|
+
}
|
|
150
|
+
// CROSSHAIR CURSORS (drawing/selection tools)
|
|
151
|
+
else if ([elementRole isEqualToString:@"AXCanvas"] ||
|
|
152
|
+
[elementRole isEqualToString:@"AXDrawingArea"]) {
|
|
153
|
+
cursorType = @"crosshair";
|
|
154
|
+
}
|
|
155
|
+
// ZOOM CURSORS (zoom controls)
|
|
156
|
+
else if ([elementRole isEqualToString:@"AXZoomButton"]) {
|
|
157
|
+
cursorType = @"zoom-in";
|
|
158
|
+
}
|
|
159
|
+
// NOT-ALLOWED CURSORS (disabled elements)
|
|
160
|
+
else if ([elementRole isEqualToString:@"AXStaticText"] ||
|
|
161
|
+
[elementRole isEqualToString:@"AXGroup"]) {
|
|
162
|
+
// Check if element is disabled/readonly
|
|
163
|
+
CFBooleanRef enabled = NULL;
|
|
164
|
+
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXEnabledAttribute, (CFTypeRef*)&enabled);
|
|
165
|
+
if (error == kAXErrorSuccess && enabled && !CFBooleanGetValue(enabled)) {
|
|
166
|
+
cursorType = @"not-allowed";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// WINDOW BORDER RESIZE
|
|
170
|
+
else if ([elementRole isEqualToString:@"AXWindow"]) {
|
|
171
|
+
// Check mouse position relative to window bounds for resize detection
|
|
172
|
+
CFTypeRef position = NULL;
|
|
173
|
+
CFTypeRef size = NULL;
|
|
174
|
+
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXPositionAttribute, &position);
|
|
175
|
+
AXError sizeError = AXUIElementCopyAttributeValue(elementAtPosition, kAXSizeAttribute, &size);
|
|
176
|
+
|
|
177
|
+
if (error == kAXErrorSuccess && sizeError == kAXErrorSuccess && position && size) {
|
|
178
|
+
CGPoint windowPos;
|
|
179
|
+
CGSize windowSize;
|
|
180
|
+
AXValueGetValue((AXValueRef)position, kAXValueTypeCGPoint, &windowPos);
|
|
181
|
+
AXValueGetValue((AXValueRef)size, kAXValueTypeCGSize, &windowSize);
|
|
182
|
+
|
|
183
|
+
CGFloat x = cursorPos.x - windowPos.x;
|
|
184
|
+
CGFloat y = cursorPos.y - windowPos.y;
|
|
185
|
+
CGFloat w = windowSize.width;
|
|
186
|
+
CGFloat h = windowSize.height;
|
|
187
|
+
CGFloat edge = 5.0; // 5px edge detection
|
|
188
|
+
|
|
189
|
+
// Corner resize detection
|
|
190
|
+
if ((x <= edge && y <= edge) || (x >= w-edge && y >= h-edge)) {
|
|
191
|
+
cursorType = @"nwse-resize"; // Top-left or bottom-right corner
|
|
192
|
+
}
|
|
193
|
+
else if ((x >= w-edge && y <= edge) || (x <= edge && y >= h-edge)) {
|
|
194
|
+
cursorType = @"nesw-resize"; // Top-right or bottom-left corner
|
|
195
|
+
}
|
|
196
|
+
// Edge resize detection
|
|
197
|
+
else if (x <= edge || x >= w-edge) {
|
|
198
|
+
cursorType = @"col-resize"; // Left or right edge
|
|
199
|
+
}
|
|
200
|
+
else if (y <= edge || y >= h-edge) {
|
|
201
|
+
cursorType = @"row-resize"; // Top or bottom edge
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
cursorType = @"default";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (position) CFRelease(position);
|
|
208
|
+
if (size) CFRelease(size);
|
|
209
|
+
} else {
|
|
210
|
+
cursorType = @"default";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check subroles for additional context
|
|
132
215
|
CFStringRef subrole = NULL;
|
|
133
216
|
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXSubroleAttribute, (CFTypeRef*)&subrole);
|
|
134
217
|
if (error == kAXErrorSuccess && subrole) {
|
|
135
218
|
NSString *elementSubrole = (__bridge_transfer NSString*)subrole;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
[elementSubrole isEqualToString:@"AXCloseButton"] ||
|
|
219
|
+
NSLog(@"🎯 ELEMENT SUBROLE: %@", elementSubrole);
|
|
220
|
+
|
|
221
|
+
// Special button subroles
|
|
222
|
+
if ([elementSubrole isEqualToString:@"AXCloseButton"] ||
|
|
141
223
|
[elementSubrole isEqualToString:@"AXMinimizeButton"] ||
|
|
142
|
-
[elementSubrole isEqualToString:@"AXZoomButton"]
|
|
143
|
-
|
|
224
|
+
[elementSubrole isEqualToString:@"AXZoomButton"] ||
|
|
225
|
+
[elementSubrole isEqualToString:@"AXToolbarButton"]) {
|
|
226
|
+
cursorType = @"pointer";
|
|
227
|
+
}
|
|
228
|
+
// Copy/alias subroles
|
|
229
|
+
else if ([elementSubrole isEqualToString:@"AXFileDrop"] ||
|
|
230
|
+
[elementSubrole isEqualToString:@"AXDropTarget"]) {
|
|
231
|
+
cursorType = @"copy";
|
|
232
|
+
}
|
|
233
|
+
// Alias/shortcut subroles
|
|
234
|
+
else if ([elementSubrole isEqualToString:@"AXAlias"] ||
|
|
235
|
+
[elementSubrole isEqualToString:@"AXShortcut"]) {
|
|
236
|
+
cursorType = @"alias";
|
|
237
|
+
}
|
|
238
|
+
// Grabbing state (being dragged)
|
|
239
|
+
else if ([elementSubrole isEqualToString:@"AXDragging"] ||
|
|
240
|
+
[elementSubrole isEqualToString:@"AXMoving"]) {
|
|
241
|
+
cursorType = @"grabbing";
|
|
242
|
+
}
|
|
243
|
+
// Zoom controls
|
|
244
|
+
else if ([elementSubrole isEqualToString:@"AXZoomIn"]) {
|
|
245
|
+
cursorType = @"zoom-in";
|
|
246
|
+
}
|
|
247
|
+
else if ([elementSubrole isEqualToString:@"AXZoomOut"]) {
|
|
248
|
+
cursorType = @"zoom-out";
|
|
144
249
|
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Check for text elements
|
|
148
|
-
if ([elementRole isEqualToString:@"AXTextField"] ||
|
|
149
|
-
[elementRole isEqualToString:@"AXTextArea"] ||
|
|
150
|
-
[elementRole isEqualToString:@"AXStaticText"]) {
|
|
151
|
-
return @"text";
|
|
152
250
|
}
|
|
153
251
|
}
|
|
154
|
-
|
|
252
|
+
|
|
155
253
|
CFRelease(elementAtPosition);
|
|
156
254
|
}
|
|
157
|
-
|
|
255
|
+
|
|
158
256
|
if (systemWide) {
|
|
159
257
|
CFRelease(systemWide);
|
|
160
258
|
}
|
|
161
|
-
|
|
259
|
+
|
|
260
|
+
NSLog(@"🎯 FINAL CURSOR TYPE: %@", cursorType);
|
|
162
261
|
return cursorType;
|
|
163
262
|
|
|
164
263
|
} @catch (NSException *exception) {
|