node-mac-recorder 2.17.12 → 2.17.13

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 +140 -39
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.17.12",
3
+ "version": "2.17.13",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -57,12 +57,12 @@ static CGEventRef eventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEv
57
57
  return event;
58
58
  }
59
59
 
60
- // Enhanced cursor type detection with multi-layer approach and stability
60
+ // Enhanced cursor type detection with better NSCursor analysis
61
61
  NSString* detectCursorTypeFromNSCursor() {
62
62
  @try {
63
63
  NSCursor *currentCursor = [NSCursor currentSystemCursor];
64
64
  if (!currentCursor) {
65
- return @"default";
65
+ return nil; // Return nil to indicate we should try other methods
66
66
  }
67
67
 
68
68
  // Compare with known system cursors using identity comparison
@@ -86,24 +86,43 @@ NSString* detectCursorTypeFromNSCursor() {
86
86
  return @"not-allowed";
87
87
  }
88
88
 
89
- // Fallback to image-based comparison for custom cursors
89
+ // Check for additional resize cursors using image analysis
90
90
  NSImage *cursorImage = [currentCursor image];
91
91
  if (cursorImage) {
92
92
  NSSize imageSize = [cursorImage size];
93
+ NSPoint hotSpot = [currentCursor hotSpot];
94
+
95
+ // Analyze cursor image to detect resize cursors
96
+ if (imageSize.width > 10 && imageSize.height > 10) {
97
+ // Check for diagonal resize cursors (corner resize)
98
+ if (imageSize.width >= 15 && imageSize.height >= 15) {
99
+ // These are likely diagonal resize cursors
100
+ // hotSpot can help distinguish between nwse and nesw
101
+ if (hotSpot.x < imageSize.width / 2 && hotSpot.y < imageSize.height / 2) {
102
+ return @"nwse-resize"; // northwest-southeast
103
+ } else if (hotSpot.x > imageSize.width / 2 && hotSpot.y < imageSize.height / 2) {
104
+ return @"nesw-resize"; // northeast-southwest
105
+ }
106
+ }
107
+
108
+ // Check for horizontal/vertical resize cursors
109
+ if (imageSize.width > imageSize.height + 5) {
110
+ return @"col-resize"; // horizontal resize
111
+ } else if (imageSize.height > imageSize.width + 5) {
112
+ return @"ns-resize"; // vertical resize
113
+ }
114
+ }
93
115
 
94
116
  // Text cursors typically have I-beam shape (narrow width)
95
117
  if (imageSize.width < 8 && imageSize.height > 15) {
96
118
  return @"text";
97
119
  }
98
- // Pointer cursors are typically hand-shaped
99
- else if (imageSize.width > 15 && imageSize.height > 15) {
100
- return @"pointer";
101
- }
102
120
  }
103
121
 
104
- return @"default";
122
+ // Return nil to indicate we should use contextual detection
123
+ return nil;
105
124
  } @catch (NSException *exception) {
106
- return @"default";
125
+ return nil;
107
126
  }
108
127
  }
109
128
 
@@ -114,48 +133,130 @@ NSString* getCursorType() {
114
133
  NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
115
134
 
116
135
  @try {
117
- // Layer 1: Fast NSCursor detection (most reliable)
136
+ // Layer 1: Fast NSCursor detection (high confidence cases)
118
137
  NSString *nsCursorType = detectCursorTypeFromNSCursor();
119
138
 
120
- // Layer 2: Accessibility API for context (when NSCursor isn't enough)
121
- NSString *contextualCursorType = nsCursorType;
139
+ // Layer 2: Contextual detection using Accessibility API
140
+ NSString *contextualCursorType = nil;
122
141
 
123
- // Only use expensive Accessibility API if NSCursor gives us "default"
124
- if ([nsCursorType isEqualToString:@"default"]) {
125
- CGPoint cursorPos = CGEventGetLocation(CGEventCreate(NULL));
126
- AXUIElementRef systemWide = AXUIElementCreateSystemWide();
127
- AXUIElementRef elementAtPosition = NULL;
128
- AXError error = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &elementAtPosition);
142
+ CGPoint cursorPos = CGEventGetLocation(CGEventCreate(NULL));
143
+ AXUIElementRef systemWide = AXUIElementCreateSystemWide();
144
+ AXUIElementRef elementAtPosition = NULL;
145
+ AXError error = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &elementAtPosition);
129
146
 
130
- if (error == kAXErrorSuccess && elementAtPosition) {
131
- CFStringRef role = NULL;
132
- error = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleAttribute, (CFTypeRef*)&role);
147
+ if (error == kAXErrorSuccess && elementAtPosition) {
148
+ CFStringRef role = NULL;
149
+ error = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleAttribute, (CFTypeRef*)&role);
133
150
 
134
- if (error == kAXErrorSuccess && role) {
135
- NSString *elementRole = (__bridge_transfer NSString*)role;
151
+ if (error == kAXErrorSuccess && role) {
152
+ NSString *elementRole = (__bridge_transfer NSString*)role;
136
153
 
137
- // Simplified, high-confidence role mappings only
138
- if ([elementRole isEqualToString:@"AXTextField"] ||
139
- [elementRole isEqualToString:@"AXTextArea"] ||
140
- [elementRole isEqualToString:@"AXSearchField"]) {
141
- contextualCursorType = @"text";
142
- }
143
- else if ([elementRole isEqualToString:@"AXLink"] ||
144
- [elementRole isEqualToString:@"AXButton"] ||
145
- [elementRole isEqualToString:@"AXMenuItem"]) {
146
- contextualCursorType = @"pointer";
154
+ // TEXT CURSORS - high priority
155
+ if ([elementRole isEqualToString:@"AXTextField"] ||
156
+ [elementRole isEqualToString:@"AXTextArea"] ||
157
+ [elementRole isEqualToString:@"AXSearchField"]) {
158
+ contextualCursorType = @"text";
159
+ }
160
+ // POINTER CURSORS - only for interactive elements
161
+ else if ([elementRole isEqualToString:@"AXLink"] ||
162
+ [elementRole isEqualToString:@"AXButton"] ||
163
+ [elementRole isEqualToString:@"AXMenuItem"] ||
164
+ [elementRole isEqualToString:@"AXRadioButton"] ||
165
+ [elementRole isEqualToString:@"AXCheckBox"]) {
166
+ contextualCursorType = @"pointer";
167
+ }
168
+ // WINDOW BORDER RESIZE - critical for resize detection
169
+ else if ([elementRole isEqualToString:@"AXWindow"]) {
170
+ CFTypeRef position = NULL;
171
+ CFTypeRef size = NULL;
172
+ AXError posErr = AXUIElementCopyAttributeValue(elementAtPosition, kAXPositionAttribute, &position);
173
+ AXError sizeErr = AXUIElementCopyAttributeValue(elementAtPosition, kAXSizeAttribute, &size);
174
+
175
+ if (posErr == kAXErrorSuccess && sizeErr == kAXErrorSuccess && position && size) {
176
+ CGPoint windowPos;
177
+ CGSize windowSize;
178
+ AXValueGetValue((AXValueRef)position, kAXValueTypeCGPoint, &windowPos);
179
+ AXValueGetValue((AXValueRef)size, kAXValueTypeCGSize, &windowSize);
180
+
181
+ CGFloat x = cursorPos.x - windowPos.x;
182
+ CGFloat y = cursorPos.y - windowPos.y;
183
+ CGFloat w = windowSize.width;
184
+ CGFloat h = windowSize.height;
185
+ CGFloat edge = 5.0; // 5px edge detection
186
+
187
+ // Corner resize detection
188
+ if (x <= edge && y <= edge) {
189
+ contextualCursorType = @"nwse-resize";
190
+ }
191
+ else if (x >= w-edge && y <= edge) {
192
+ contextualCursorType = @"nesw-resize";
193
+ }
194
+ else if (x <= edge && y >= h-edge) {
195
+ contextualCursorType = @"nesw-resize";
196
+ }
197
+ else if (x >= w-edge && y >= h-edge) {
198
+ contextualCursorType = @"nwse-resize";
199
+ }
200
+ // Edge resize detection
201
+ else if (x <= edge || x >= w-edge) {
202
+ contextualCursorType = @"col-resize";
203
+ }
204
+ else if (y <= edge || y >= h-edge) {
205
+ contextualCursorType = @"ns-resize";
206
+ }
207
+
208
+ if (position) CFRelease(position);
209
+ if (size) CFRelease(size);
147
210
  }
148
- else if ([elementRole isEqualToString:@"AXProgressIndicator"]) {
149
- contextualCursorType = @"progress";
211
+ }
212
+ // SPLITTER RESIZE
213
+ else if ([elementRole isEqualToString:@"AXSplitter"]) {
214
+ CFStringRef orientation = NULL;
215
+ error = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXOrientation"), (CFTypeRef*)&orientation);
216
+ if (error == kAXErrorSuccess && orientation) {
217
+ NSString *orientationStr = (__bridge_transfer NSString*)orientation;
218
+ if ([orientationStr isEqualToString:@"AXHorizontalOrientation"]) {
219
+ contextualCursorType = @"ns-resize";
220
+ } else if ([orientationStr isEqualToString:@"AXVerticalOrientation"]) {
221
+ contextualCursorType = @"col-resize";
222
+ }
150
223
  }
151
224
  }
152
- CFRelease(elementAtPosition);
225
+ // PROGRESS INDICATORS
226
+ else if ([elementRole isEqualToString:@"AXProgressIndicator"]) {
227
+ contextualCursorType = @"progress";
228
+ }
229
+ // OTHER ELEMENTS - be conservative, don't assume pointer
230
+ else {
231
+ // Don't override NSCursor for unknown elements
232
+ contextualCursorType = nil;
233
+ }
153
234
  }
154
- if (systemWide) CFRelease(systemWide);
235
+ CFRelease(elementAtPosition);
236
+ }
237
+ if (systemWide) CFRelease(systemWide);
238
+
239
+ // Layer 3: Intelligent fusion of NSCursor and contextual results
240
+ NSString *detectedCursorType = @"default";
241
+
242
+ // Priority logic:
243
+ // 1. If contextual gives resize cursor, always use it (resize has highest priority)
244
+ if (contextualCursorType != nil &&
245
+ ([contextualCursorType hasSuffix:@"resize"] ||
246
+ [contextualCursorType isEqualToString:@"col-resize"] ||
247
+ [contextualCursorType isEqualToString:@"ns-resize"])) {
248
+ detectedCursorType = contextualCursorType;
249
+ }
250
+ // 2. If NSCursor gives a definitive answer and no resize context, use it
251
+ else if (nsCursorType != nil) {
252
+ detectedCursorType = nsCursorType;
253
+ }
254
+ // 3. If NSCursor is nil/unknown, but contextual is available, use contextual
255
+ else if (contextualCursorType != nil) {
256
+ detectedCursorType = contextualCursorType;
155
257
  }
156
258
 
157
- // Layer 3: Stability filtering to prevent oscillation
158
- NSString *detectedCursorType = contextualCursorType;
259
+ // Layer 4: Stability filtering to prevent oscillation
159
260
 
160
261
  // Time-based stability check
161
262
  if (currentTime - g_lastCursorCheckTime > CURSOR_STABILITY_THRESHOLD) {