node-mac-recorder 2.17.5 → 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 +173 -181
package/package.json
CHANGED
package/src/cursor_tracker.mm
CHANGED
|
@@ -55,217 +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
|
-
// DEBUG: Log cursor details
|
|
68
|
-
NSLog(@"🖱️ CURSOR DEBUG: size=(%.0f,%.0f), hotspot=(%.0f,%.0f)",
|
|
69
|
-
imageSize.width, imageSize.height, hotSpot.x, hotSpot.y);
|
|
70
|
-
|
|
71
|
-
// Check cursor type by comparing with standard cursors
|
|
72
|
-
if ([currentCursor isEqual:[NSCursor pointingHandCursor]] ||
|
|
73
|
-
(hotSpot.x >= 5 && hotSpot.x <= 7 && hotSpot.y >= 0 && hotSpot.y <= 4) ||
|
|
74
|
-
(hotSpot.x >= 12 && hotSpot.x <= 14 && hotSpot.y >= 7 && hotSpot.y <= 9)) {
|
|
75
|
-
return @"pointer";
|
|
76
|
-
} else if ([currentCursor isEqual:[NSCursor IBeamCursor]] ||
|
|
77
|
-
(hotSpot.x >= 3 && hotSpot.x <= 5 && hotSpot.y >= 8 && hotSpot.y <= 10 &&
|
|
78
|
-
imageSize.width <= 10 && imageSize.height >= 16)) {
|
|
79
|
-
return @"text";
|
|
80
|
-
} else if ([currentCursor isEqual:[NSCursor resizeLeftRightCursor]]) {
|
|
81
|
-
return @"col-resize";
|
|
82
|
-
} else if ([currentCursor isEqual:[NSCursor resizeUpDownCursor]]) {
|
|
83
|
-
return @"ns-resize";
|
|
84
|
-
} else if ([currentCursor isEqual:[NSCursor openHandCursor]]) {
|
|
85
|
-
return @"grab";
|
|
86
|
-
} else if ([currentCursor isEqual:[NSCursor closedHandCursor]]) {
|
|
87
|
-
return @"grabbing";
|
|
88
|
-
} else if ([currentCursor isEqual:[NSCursor crosshairCursor]]) {
|
|
89
|
-
return @"crosshair";
|
|
90
|
-
} else if ([currentCursor isEqual:[NSCursor disappearingItemCursor]]) {
|
|
91
|
-
return @"alias";
|
|
92
|
-
} else if ([currentCursor isEqual:[NSCursor dragCopyCursor]]) {
|
|
93
|
-
return @"copy";
|
|
94
|
-
} else if ([currentCursor isEqual:[NSCursor operationNotAllowedCursor]]) {
|
|
95
|
-
return @"not-allowed";
|
|
96
|
-
} else if ([currentCursor isEqual:[NSCursor contextualMenuCursor]]) {
|
|
97
|
-
return @"help";
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Additional detection based on image characteristics
|
|
101
|
-
if (imageSize.width > 0 && imageSize.height > 0) {
|
|
102
|
-
NSLog(@"🖱️ Checking cursor: size=(%.0f,%.0f), hotspot=(%.0f,%.0f)",
|
|
103
|
-
imageSize.width, imageSize.height, hotSpot.x, hotSpot.y);
|
|
58
|
+
// ACCESSIBILITY API BASED CURSOR DETECTION
|
|
59
|
+
// Determine cursor type based on the UI element under the cursor
|
|
104
60
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
61
|
+
CGPoint cursorPos = CGEventGetLocation(CGEventCreate(NULL));
|
|
62
|
+
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
63
|
+
AXUIElementRef elementAtPosition = NULL;
|
|
64
|
+
AXError error = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &elementAtPosition);
|
|
108
65
|
|
|
109
|
-
|
|
110
|
-
if (imageSize.width <= 12 && imageSize.height >= 16 &&
|
|
111
|
-
hotSpot.x >= imageSize.width/2 - 2 && hotSpot.x <= imageSize.width/2 + 2) {
|
|
112
|
-
isKnownPattern = true;
|
|
113
|
-
}
|
|
66
|
+
NSString *cursorType = @"default"; // Default fallback
|
|
114
67
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
isKnownPattern = true;
|
|
119
|
-
}
|
|
68
|
+
if (error == kAXErrorSuccess && elementAtPosition) {
|
|
69
|
+
CFStringRef role = NULL;
|
|
70
|
+
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleAttribute, (CFTypeRef*)&role);
|
|
120
71
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
isKnownPattern = true;
|
|
125
|
-
}
|
|
72
|
+
if (error == kAXErrorSuccess && role) {
|
|
73
|
+
NSString *elementRole = (__bridge_transfer NSString*)role;
|
|
74
|
+
NSLog(@"🎯 ELEMENT ROLE: %@", elementRole);
|
|
126
75
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
hotSpot.y >= imageSize.height/2 - 4 && hotSpot.y <= imageSize.height/2 + 4) {
|
|
134
|
-
NSLog(@"🖱️ DETECTED PROGRESS: size=(%.0f,%.0f), hotspot=(%.0f,%.0f)",
|
|
135
|
-
imageSize.width, imageSize.height, hotSpot.x, hotSpot.y);
|
|
136
|
-
return @"progress";
|
|
76
|
+
// TEXT CURSORS
|
|
77
|
+
if ([elementRole isEqualToString:@"AXTextField"] ||
|
|
78
|
+
[elementRole isEqualToString:@"AXTextArea"] ||
|
|
79
|
+
[elementRole isEqualToString:@"AXStaticText"] ||
|
|
80
|
+
[elementRole isEqualToString:@"AXSearchField"]) {
|
|
81
|
+
cursorType = @"text";
|
|
137
82
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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";
|
|
151
103
|
}
|
|
152
104
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
abs(imageSize.width - imageSize.height) <= 2 &&
|
|
158
|
-
hotSpot.x >= imageSize.width/2 - 2 && hotSpot.x <= imageSize.width/2 + 2 &&
|
|
159
|
-
hotSpot.y >= imageSize.height/2 - 2 && hotSpot.y <= imageSize.height/2 + 2) {
|
|
160
|
-
NSLog(@"🖱️ DETECTED ALL-SCROLL: size=(%.0f,%.0f)", imageSize.width, imageSize.height);
|
|
161
|
-
return @"all-scroll";
|
|
105
|
+
// PROGRESS CURSORS (loading/busy elements)
|
|
106
|
+
else if ([elementRole isEqualToString:@"AXProgressIndicator"] ||
|
|
107
|
+
[elementRole isEqualToString:@"AXBusyIndicator"]) {
|
|
108
|
+
cursorType = @"progress";
|
|
162
109
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
171
129
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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";
|
|
176
145
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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";
|
|
181
167
|
}
|
|
182
168
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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);
|
|
195
209
|
} else {
|
|
196
|
-
|
|
197
|
-
return @"grabbing";
|
|
210
|
+
cursorType = @"default";
|
|
198
211
|
}
|
|
199
212
|
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
213
|
|
|
203
|
-
|
|
204
|
-
// Check if we're in a drag operation
|
|
205
|
-
CGEventRef event = CGEventCreate(NULL);
|
|
206
|
-
if (event) {
|
|
207
|
-
CGEventType eventType = (CGEventType)CGEventGetType(event);
|
|
208
|
-
if (eventType == kCGEventLeftMouseDragged ||
|
|
209
|
-
eventType == kCGEventRightMouseDragged) {
|
|
210
|
-
CFRelease(event);
|
|
211
|
-
return @"grabbing";
|
|
212
|
-
}
|
|
213
|
-
CFRelease(event);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Get the window under the cursor
|
|
217
|
-
CGPoint cursorPos = CGEventGetLocation(CGEventCreate(NULL));
|
|
218
|
-
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
219
|
-
AXUIElementRef elementAtPosition = NULL;
|
|
220
|
-
AXError error = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &elementAtPosition);
|
|
221
|
-
|
|
222
|
-
if (error == kAXErrorSuccess && elementAtPosition) {
|
|
223
|
-
CFStringRef role = NULL;
|
|
224
|
-
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleAttribute, (CFTypeRef*)&role);
|
|
225
|
-
|
|
226
|
-
if (error == kAXErrorSuccess && role) {
|
|
227
|
-
NSString *elementRole = (__bridge_transfer NSString*)role;
|
|
228
|
-
|
|
229
|
-
// Check for clickable elements that should show pointer cursor
|
|
230
|
-
if ([elementRole isEqualToString:@"AXLink"] ||
|
|
231
|
-
[elementRole isEqualToString:@"AXButton"] ||
|
|
232
|
-
[elementRole isEqualToString:@"AXMenuItem"] ||
|
|
233
|
-
[elementRole isEqualToString:@"AXRadioButton"] ||
|
|
234
|
-
[elementRole isEqualToString:@"AXCheckBox"]) {
|
|
235
|
-
return @"pointer";
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Check subrole for additional pointer cursor elements
|
|
214
|
+
// Check subroles for additional context
|
|
239
215
|
CFStringRef subrole = NULL;
|
|
240
216
|
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXSubroleAttribute, (CFTypeRef*)&subrole);
|
|
241
217
|
if (error == kAXErrorSuccess && subrole) {
|
|
242
218
|
NSString *elementSubrole = (__bridge_transfer NSString*)subrole;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
[elementSubrole isEqualToString:@"AXCloseButton"] ||
|
|
219
|
+
NSLog(@"🎯 ELEMENT SUBROLE: %@", elementSubrole);
|
|
220
|
+
|
|
221
|
+
// Special button subroles
|
|
222
|
+
if ([elementSubrole isEqualToString:@"AXCloseButton"] ||
|
|
248
223
|
[elementSubrole isEqualToString:@"AXMinimizeButton"] ||
|
|
249
|
-
[elementSubrole isEqualToString:@"AXZoomButton"]
|
|
250
|
-
|
|
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";
|
|
251
249
|
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Check for text elements
|
|
255
|
-
if ([elementRole isEqualToString:@"AXTextField"] ||
|
|
256
|
-
[elementRole isEqualToString:@"AXTextArea"] ||
|
|
257
|
-
[elementRole isEqualToString:@"AXStaticText"]) {
|
|
258
|
-
return @"text";
|
|
259
250
|
}
|
|
260
251
|
}
|
|
261
|
-
|
|
252
|
+
|
|
262
253
|
CFRelease(elementAtPosition);
|
|
263
254
|
}
|
|
264
|
-
|
|
255
|
+
|
|
265
256
|
if (systemWide) {
|
|
266
257
|
CFRelease(systemWide);
|
|
267
258
|
}
|
|
268
|
-
|
|
259
|
+
|
|
260
|
+
NSLog(@"🎯 FINAL CURSOR TYPE: %@", cursorType);
|
|
269
261
|
return cursorType;
|
|
270
262
|
|
|
271
263
|
} @catch (NSException *exception) {
|