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.
- package/package.json +1 -1
- package/src/cursor_tracker.mm +140 -39
package/package.json
CHANGED
package/src/cursor_tracker.mm
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
122
|
+
// Return nil to indicate we should use contextual detection
|
|
123
|
+
return nil;
|
|
105
124
|
} @catch (NSException *exception) {
|
|
106
|
-
return
|
|
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 (
|
|
136
|
+
// Layer 1: Fast NSCursor detection (high confidence cases)
|
|
118
137
|
NSString *nsCursorType = detectCursorTypeFromNSCursor();
|
|
119
138
|
|
|
120
|
-
// Layer 2:
|
|
121
|
-
NSString *contextualCursorType =
|
|
139
|
+
// Layer 2: Contextual detection using Accessibility API
|
|
140
|
+
NSString *contextualCursorType = nil;
|
|
122
141
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
147
|
+
if (error == kAXErrorSuccess && elementAtPosition) {
|
|
148
|
+
CFStringRef role = NULL;
|
|
149
|
+
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleAttribute, (CFTypeRef*)&role);
|
|
133
150
|
|
|
134
|
-
|
|
135
|
-
|
|
151
|
+
if (error == kAXErrorSuccess && role) {
|
|
152
|
+
NSString *elementRole = (__bridge_transfer NSString*)role;
|
|
136
153
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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) {
|