node-mac-recorder 2.17.11 → 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/.claude/settings.local.json +3 -1
- package/package.json +1 -1
- package/src/cursor_tracker.mm +202 -199
package/package.json
CHANGED
package/src/cursor_tracker.mm
CHANGED
|
@@ -35,9 +35,17 @@ NSDictionary* getDisplayScalingInfo(CGPoint globalPoint);
|
|
|
35
35
|
|
|
36
36
|
static CursorTimerTarget *g_timerTarget = nil;
|
|
37
37
|
|
|
38
|
-
//
|
|
38
|
+
// Enhanced cursor state tracking with stability
|
|
39
39
|
static NSString *g_lastDetectedCursorType = nil;
|
|
40
|
+
static NSString *g_stableCursorType = @"default";
|
|
40
41
|
static int g_cursorTypeCounter = 0;
|
|
42
|
+
static NSTimeInterval g_lastCursorCheckTime = 0;
|
|
43
|
+
static int g_sameCursorDetectionCount = 0;
|
|
44
|
+
static NSString *g_pendingCursorType = nil;
|
|
45
|
+
|
|
46
|
+
// Cursor stability constants
|
|
47
|
+
static const NSTimeInterval CURSOR_STABILITY_THRESHOLD = 0.1; // 100ms
|
|
48
|
+
static const int CURSOR_CONFIRMATION_COUNT = 2; // Need 2 consecutive detections
|
|
41
49
|
|
|
42
50
|
// Mouse button state tracking
|
|
43
51
|
static bool g_leftMouseDown = false;
|
|
@@ -49,255 +57,246 @@ static CGEventRef eventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEv
|
|
|
49
57
|
return event;
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
//
|
|
60
|
+
// Enhanced cursor type detection with better NSCursor analysis
|
|
61
|
+
NSString* detectCursorTypeFromNSCursor() {
|
|
62
|
+
@try {
|
|
63
|
+
NSCursor *currentCursor = [NSCursor currentSystemCursor];
|
|
64
|
+
if (!currentCursor) {
|
|
65
|
+
return nil; // Return nil to indicate we should try other methods
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Compare with known system cursors using identity comparison
|
|
69
|
+
if (currentCursor == [NSCursor arrowCursor]) {
|
|
70
|
+
return @"default";
|
|
71
|
+
} else if (currentCursor == [NSCursor IBeamCursor]) {
|
|
72
|
+
return @"text";
|
|
73
|
+
} else if (currentCursor == [NSCursor pointingHandCursor]) {
|
|
74
|
+
return @"pointer";
|
|
75
|
+
} else if (currentCursor == [NSCursor resizeLeftRightCursor]) {
|
|
76
|
+
return @"col-resize";
|
|
77
|
+
} else if (currentCursor == [NSCursor resizeUpDownCursor]) {
|
|
78
|
+
return @"ns-resize";
|
|
79
|
+
} else if (currentCursor == [NSCursor crosshairCursor]) {
|
|
80
|
+
return @"crosshair";
|
|
81
|
+
} else if (currentCursor == [NSCursor openHandCursor]) {
|
|
82
|
+
return @"grab";
|
|
83
|
+
} else if (currentCursor == [NSCursor closedHandCursor]) {
|
|
84
|
+
return @"grabbing";
|
|
85
|
+
} else if (currentCursor == [NSCursor operationNotAllowedCursor]) {
|
|
86
|
+
return @"not-allowed";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check for additional resize cursors using image analysis
|
|
90
|
+
NSImage *cursorImage = [currentCursor image];
|
|
91
|
+
if (cursorImage) {
|
|
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
|
+
}
|
|
115
|
+
|
|
116
|
+
// Text cursors typically have I-beam shape (narrow width)
|
|
117
|
+
if (imageSize.width < 8 && imageSize.height > 15) {
|
|
118
|
+
return @"text";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Return nil to indicate we should use contextual detection
|
|
123
|
+
return nil;
|
|
124
|
+
} @catch (NSException *exception) {
|
|
125
|
+
return nil;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Improved cursor type detection with stability and multi-layer approach
|
|
53
130
|
NSString* getCursorType() {
|
|
54
131
|
@autoreleasepool {
|
|
55
132
|
g_cursorTypeCounter++;
|
|
56
|
-
|
|
133
|
+
NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
|
|
134
|
+
|
|
57
135
|
@try {
|
|
58
|
-
//
|
|
59
|
-
|
|
136
|
+
// Layer 1: Fast NSCursor detection (high confidence cases)
|
|
137
|
+
NSString *nsCursorType = detectCursorTypeFromNSCursor();
|
|
138
|
+
|
|
139
|
+
// Layer 2: Contextual detection using Accessibility API
|
|
140
|
+
NSString *contextualCursorType = nil;
|
|
60
141
|
|
|
61
142
|
CGPoint cursorPos = CGEventGetLocation(CGEventCreate(NULL));
|
|
62
143
|
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
63
144
|
AXUIElementRef elementAtPosition = NULL;
|
|
64
145
|
AXError error = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &elementAtPosition);
|
|
65
146
|
|
|
66
|
-
NSString *cursorType = @"default"; // Default fallback
|
|
67
|
-
|
|
68
147
|
if (error == kAXErrorSuccess && elementAtPosition) {
|
|
69
148
|
CFStringRef role = NULL;
|
|
70
149
|
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleAttribute, (CFTypeRef*)&role);
|
|
71
150
|
|
|
72
151
|
if (error == kAXErrorSuccess && role) {
|
|
73
152
|
NSString *elementRole = (__bridge_transfer NSString*)role;
|
|
74
|
-
NSLog(@"🎯 ELEMENT ROLE: %@", elementRole);
|
|
75
153
|
|
|
76
|
-
// TEXT CURSORS
|
|
154
|
+
// TEXT CURSORS - high priority
|
|
77
155
|
if ([elementRole isEqualToString:@"AXTextField"] ||
|
|
78
156
|
[elementRole isEqualToString:@"AXTextArea"] ||
|
|
79
|
-
[elementRole isEqualToString:@"AXStaticText"] ||
|
|
80
157
|
[elementRole isEqualToString:@"AXSearchField"]) {
|
|
81
|
-
|
|
158
|
+
contextualCursorType = @"text";
|
|
82
159
|
}
|
|
83
|
-
// POINTER CURSORS
|
|
160
|
+
// POINTER CURSORS - only for interactive elements
|
|
84
161
|
else if ([elementRole isEqualToString:@"AXLink"] ||
|
|
85
162
|
[elementRole isEqualToString:@"AXButton"] ||
|
|
86
163
|
[elementRole isEqualToString:@"AXMenuItem"] ||
|
|
87
164
|
[elementRole isEqualToString:@"AXRadioButton"] ||
|
|
88
|
-
[elementRole isEqualToString:@"AXCheckBox"]
|
|
89
|
-
|
|
90
|
-
[elementRole isEqualToString:@"AXTab"]) {
|
|
91
|
-
cursorType = @"pointer";
|
|
165
|
+
[elementRole isEqualToString:@"AXCheckBox"]) {
|
|
166
|
+
contextualCursorType = @"pointer";
|
|
92
167
|
}
|
|
93
|
-
//
|
|
94
|
-
else if ([elementRole isEqualToString:@"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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);
|
|
103
210
|
}
|
|
104
211
|
}
|
|
105
|
-
//
|
|
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 - sadece AXSplitter için
|
|
212
|
+
// SPLITTER RESIZE
|
|
116
213
|
else if ([elementRole isEqualToString:@"AXSplitter"]) {
|
|
117
|
-
// Get splitter orientation to determine resize direction
|
|
118
214
|
CFStringRef orientation = NULL;
|
|
119
215
|
error = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXOrientation"), (CFTypeRef*)&orientation);
|
|
120
216
|
if (error == kAXErrorSuccess && orientation) {
|
|
121
217
|
NSString *orientationStr = (__bridge_transfer NSString*)orientation;
|
|
122
218
|
if ([orientationStr isEqualToString:@"AXHorizontalOrientation"]) {
|
|
123
|
-
|
|
219
|
+
contextualCursorType = @"ns-resize";
|
|
124
220
|
} else if ([orientationStr isEqualToString:@"AXVerticalOrientation"]) {
|
|
125
|
-
|
|
126
|
-
} else {
|
|
127
|
-
cursorType = @"default"; // Bilinmeyen orientation
|
|
221
|
+
contextualCursorType = @"col-resize";
|
|
128
222
|
}
|
|
129
|
-
} else {
|
|
130
|
-
cursorType = @"default"; // Orientation alınamazsa default
|
|
131
223
|
}
|
|
132
224
|
}
|
|
133
|
-
//
|
|
134
|
-
else if ([elementRole isEqualToString:@"
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
// AXScrollArea - hep default
|
|
138
|
-
else if ([elementRole isEqualToString:@"AXScrollArea"]) {
|
|
139
|
-
cursorType = @"default"; // ScrollArea her zaman default
|
|
140
|
-
}
|
|
141
|
-
// CROSSHAIR CURSORS (drawing/selection tools)
|
|
142
|
-
else if ([elementRole isEqualToString:@"AXCanvas"] ||
|
|
143
|
-
[elementRole isEqualToString:@"AXDrawingArea"]) {
|
|
144
|
-
cursorType = @"crosshair";
|
|
145
|
-
}
|
|
146
|
-
// ZOOM CURSORS (zoom controls)
|
|
147
|
-
else if ([elementRole isEqualToString:@"AXZoomButton"]) {
|
|
148
|
-
cursorType = @"zoom-in";
|
|
225
|
+
// PROGRESS INDICATORS
|
|
226
|
+
else if ([elementRole isEqualToString:@"AXProgressIndicator"]) {
|
|
227
|
+
contextualCursorType = @"progress";
|
|
149
228
|
}
|
|
150
|
-
//
|
|
151
|
-
else if ([elementRole isEqualToString:@"AXStaticText"] ||
|
|
152
|
-
[elementRole isEqualToString:@"AXGroup"]) {
|
|
153
|
-
// Check if element is disabled/readonly
|
|
154
|
-
CFBooleanRef enabled = NULL;
|
|
155
|
-
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXEnabledAttribute, (CFTypeRef*)&enabled);
|
|
156
|
-
if (error == kAXErrorSuccess && enabled && !CFBooleanGetValue(enabled)) {
|
|
157
|
-
cursorType = @"not-allowed";
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
// WINDOW BORDER RESIZE - sadece pencere kenarlarında
|
|
161
|
-
else if ([elementRole isEqualToString:@"AXWindow"]) {
|
|
162
|
-
// Check window attributes to see if it's resizable
|
|
163
|
-
CFBooleanRef resizable = NULL;
|
|
164
|
-
AXError resizableError = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXResizeButton"), (CFTypeRef*)&resizable);
|
|
165
|
-
|
|
166
|
-
// Sadece resize edilebilir pencereler için cursor değişimi
|
|
167
|
-
if (resizableError == kAXErrorSuccess || true) { // AXResizeButton bulunamazsa da devam et
|
|
168
|
-
CFTypeRef position = NULL;
|
|
169
|
-
CFTypeRef size = NULL;
|
|
170
|
-
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXPositionAttribute, &position);
|
|
171
|
-
AXError sizeError = AXUIElementCopyAttributeValue(elementAtPosition, kAXSizeAttribute, &size);
|
|
172
|
-
|
|
173
|
-
if (error == kAXErrorSuccess && sizeError == kAXErrorSuccess && position && size) {
|
|
174
|
-
CGPoint windowPos;
|
|
175
|
-
CGSize windowSize;
|
|
176
|
-
AXValueGetValue((AXValueRef)position, kAXValueTypeCGPoint, &windowPos);
|
|
177
|
-
AXValueGetValue((AXValueRef)size, kAXValueTypeCGSize, &windowSize);
|
|
178
|
-
|
|
179
|
-
CGFloat x = cursorPos.x - windowPos.x;
|
|
180
|
-
CGFloat y = cursorPos.y - windowPos.y;
|
|
181
|
-
CGFloat w = windowSize.width;
|
|
182
|
-
CGFloat h = windowSize.height;
|
|
183
|
-
CGFloat edge = 3.0; // Daha küçük edge detection (3px)
|
|
184
|
-
|
|
185
|
-
// Sadece çok kenar köşelerde resize cursor'ı göster
|
|
186
|
-
BOOL isOnBorder = NO;
|
|
187
|
-
|
|
188
|
-
// Corner resize detection - çok dar alanda, doğru açılar
|
|
189
|
-
if (x <= edge && y <= edge) {
|
|
190
|
-
cursorType = @"nwse-resize"; // Sol üst köşe - northwest-southeast
|
|
191
|
-
isOnBorder = YES;
|
|
192
|
-
}
|
|
193
|
-
else if (x >= w-edge && y <= edge) {
|
|
194
|
-
cursorType = @"nesw-resize"; // Sağ üst köşe - northeast-southwest
|
|
195
|
-
isOnBorder = YES;
|
|
196
|
-
}
|
|
197
|
-
else if (x <= edge && y >= h-edge) {
|
|
198
|
-
cursorType = @"nesw-resize"; // Sol alt köşe - southwest-northeast
|
|
199
|
-
isOnBorder = YES;
|
|
200
|
-
}
|
|
201
|
-
else if (x >= w-edge && y >= h-edge) {
|
|
202
|
-
cursorType = @"nwse-resize"; // Sağ alt köşe - southeast-northwest
|
|
203
|
-
isOnBorder = YES;
|
|
204
|
-
}
|
|
205
|
-
// Edge resize detection - sadece çok kenarlarda
|
|
206
|
-
else if (x <= edge && y > edge && y < h-edge) {
|
|
207
|
-
cursorType = @"col-resize"; // Sol kenar - column resize (yatay)
|
|
208
|
-
isOnBorder = YES;
|
|
209
|
-
}
|
|
210
|
-
else if (x >= w-edge && y > edge && y < h-edge) {
|
|
211
|
-
cursorType = @"col-resize"; // Sağ kenar - column resize (yatay)
|
|
212
|
-
isOnBorder = YES;
|
|
213
|
-
}
|
|
214
|
-
else if (y <= edge && x > edge && x < w-edge) {
|
|
215
|
-
cursorType = @"ns-resize"; // Üst kenar - north-south resize (dikey)
|
|
216
|
-
isOnBorder = YES;
|
|
217
|
-
}
|
|
218
|
-
else if (y >= h-edge && x > edge && x < w-edge) {
|
|
219
|
-
cursorType = @"ns-resize"; // Alt kenar - north-south resize (dikey)
|
|
220
|
-
isOnBorder = YES;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Eğer border'da değilse default
|
|
224
|
-
if (!isOnBorder) {
|
|
225
|
-
cursorType = @"default";
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (position) CFRelease(position);
|
|
229
|
-
if (size) CFRelease(size);
|
|
230
|
-
} else {
|
|
231
|
-
cursorType = @"default";
|
|
232
|
-
}
|
|
233
|
-
} else {
|
|
234
|
-
cursorType = @"default";
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
// HER DURUM İÇİN DEFAULT FALLBACK
|
|
229
|
+
// OTHER ELEMENTS - be conservative, don't assume pointer
|
|
238
230
|
else {
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Check subroles for additional context
|
|
244
|
-
CFStringRef subrole = NULL;
|
|
245
|
-
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXSubroleAttribute, (CFTypeRef*)&subrole);
|
|
246
|
-
if (error == kAXErrorSuccess && subrole) {
|
|
247
|
-
NSString *elementSubrole = (__bridge_transfer NSString*)subrole;
|
|
248
|
-
NSLog(@"🎯 ELEMENT SUBROLE: %@", elementSubrole);
|
|
249
|
-
|
|
250
|
-
// Subrole override'ları - sadece çok spesifik durumlar için
|
|
251
|
-
if ([elementSubrole isEqualToString:@"AXCloseButton"] ||
|
|
252
|
-
[elementSubrole isEqualToString:@"AXMinimizeButton"] ||
|
|
253
|
-
[elementSubrole isEqualToString:@"AXZoomButton"] ||
|
|
254
|
-
[elementSubrole isEqualToString:@"AXToolbarButton"]) {
|
|
255
|
-
cursorType = @"pointer";
|
|
256
|
-
}
|
|
257
|
-
// Copy/alias subroles - sadece bu durumlar için override
|
|
258
|
-
else if ([elementSubrole isEqualToString:@"AXFileDrop"] ||
|
|
259
|
-
[elementSubrole isEqualToString:@"AXDropTarget"]) {
|
|
260
|
-
cursorType = @"copy";
|
|
261
|
-
}
|
|
262
|
-
// Alias/shortcut subroles
|
|
263
|
-
else if ([elementSubrole isEqualToString:@"AXAlias"] ||
|
|
264
|
-
[elementSubrole isEqualToString:@"AXShortcut"]) {
|
|
265
|
-
cursorType = @"alias";
|
|
266
|
-
}
|
|
267
|
-
// Grabbing state (being dragged) - sadece gerçek drag sırasında
|
|
268
|
-
else if ([elementSubrole isEqualToString:@"AXDragging"] ||
|
|
269
|
-
[elementSubrole isEqualToString:@"AXMoving"]) {
|
|
270
|
-
cursorType = @"grabbing";
|
|
271
|
-
}
|
|
272
|
-
// Zoom controls - sadece spesifik zoom butonları için
|
|
273
|
-
else if ([elementSubrole isEqualToString:@"AXZoomIn"]) {
|
|
274
|
-
cursorType = @"zoom-in";
|
|
275
|
-
}
|
|
276
|
-
else if ([elementSubrole isEqualToString:@"AXZoomOut"]) {
|
|
277
|
-
cursorType = @"zoom-out";
|
|
278
|
-
}
|
|
279
|
-
// Subrole'dan bir şey bulamazsa role-based cursor'ı koruyoruz
|
|
231
|
+
// Don't override NSCursor for unknown elements
|
|
232
|
+
contextualCursorType = nil;
|
|
280
233
|
}
|
|
281
234
|
}
|
|
282
|
-
|
|
283
235
|
CFRelease(elementAtPosition);
|
|
284
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;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Layer 4: Stability filtering to prevent oscillation
|
|
285
260
|
|
|
286
|
-
|
|
287
|
-
|
|
261
|
+
// Time-based stability check
|
|
262
|
+
if (currentTime - g_lastCursorCheckTime > CURSOR_STABILITY_THRESHOLD) {
|
|
263
|
+
// Enough time has passed, reset counters
|
|
264
|
+
g_sameCursorDetectionCount = 0;
|
|
265
|
+
g_pendingCursorType = detectedCursorType;
|
|
288
266
|
}
|
|
289
267
|
|
|
290
|
-
//
|
|
291
|
-
if (
|
|
292
|
-
|
|
268
|
+
// Check if detected cursor matches pending cursor
|
|
269
|
+
if ([detectedCursorType isEqualToString:g_pendingCursorType]) {
|
|
270
|
+
g_sameCursorDetectionCount++;
|
|
271
|
+
|
|
272
|
+
// If we have enough confirmations, update stable cursor
|
|
273
|
+
if (g_sameCursorDetectionCount >= CURSOR_CONFIRMATION_COUNT) {
|
|
274
|
+
g_stableCursorType = detectedCursorType;
|
|
275
|
+
g_lastDetectedCursorType = detectedCursorType;
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
// Different cursor detected, start new pending
|
|
279
|
+
g_pendingCursorType = detectedCursorType;
|
|
280
|
+
g_sameCursorDetectionCount = 1;
|
|
293
281
|
}
|
|
294
282
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
283
|
+
g_lastCursorCheckTime = currentTime;
|
|
284
|
+
|
|
285
|
+
// Final validation
|
|
286
|
+
NSString *finalCursorType = g_stableCursorType;
|
|
287
|
+
if (!finalCursorType || [finalCursorType length] == 0) {
|
|
288
|
+
finalCursorType = @"default";
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Debug logging for stability tracking
|
|
292
|
+
NSLog(@"🎯 CURSOR DETECTION - NSCursor: %@, Contextual: %@, Stable: %@, Count: %d",
|
|
293
|
+
nsCursorType, contextualCursorType, finalCursorType, g_sameCursorDetectionCount);
|
|
294
|
+
|
|
295
|
+
return finalCursorType;
|
|
296
|
+
|
|
298
297
|
} @catch (NSException *exception) {
|
|
299
298
|
NSLog(@"Error in getCursorType: %@", exception);
|
|
300
|
-
return @"default";
|
|
299
|
+
return g_stableCursorType ?: @"default";
|
|
301
300
|
}
|
|
302
301
|
}
|
|
303
302
|
}
|
|
@@ -501,7 +500,11 @@ void cleanupCursorTracking() {
|
|
|
501
500
|
g_outputPath = nil;
|
|
502
501
|
g_debugCallbackCount = 0;
|
|
503
502
|
g_lastDetectedCursorType = nil;
|
|
503
|
+
g_stableCursorType = @"default";
|
|
504
504
|
g_cursorTypeCounter = 0;
|
|
505
|
+
g_lastCursorCheckTime = 0;
|
|
506
|
+
g_sameCursorDetectionCount = 0;
|
|
507
|
+
g_pendingCursorType = nil;
|
|
505
508
|
g_isFirstWrite = true;
|
|
506
509
|
}
|
|
507
510
|
|