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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cursor_tracker.mm +179 -80
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.17.4",
3
+ "version": "2.17.6",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -55,110 +55,209 @@ NSString* getCursorType() {
55
55
  g_cursorTypeCounter++;
56
56
 
57
57
  @try {
58
- // Get current cursor info
59
- NSCursor *currentCursor = [NSCursor currentSystemCursor];
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
- // Check for clickable elements that should show pointer cursor
123
- if ([elementRole isEqualToString:@"AXLink"] ||
124
- [elementRole isEqualToString:@"AXButton"] ||
125
- [elementRole isEqualToString:@"AXMenuItem"] ||
126
- [elementRole isEqualToString:@"AXRadioButton"] ||
127
- [elementRole isEqualToString:@"AXCheckBox"]) {
128
- return @"pointer";
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
- // Check subrole for additional pointer cursor elements
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
- if ([elementSubrole isEqualToString:@"AXClickable"] ||
138
- [elementSubrole isEqualToString:@"AXDisclosureTriangle"] ||
139
- [elementSubrole isEqualToString:@"AXToolbarButton"] ||
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
- return @"pointer";
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) {