node-mac-recorder 2.24.15 → 2.24.16
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/CURSOR_MAPPING.md +33 -54
- package/package.json +3 -1
- package/src/cursor_tracker.mm +191 -780
- package/tests/cursor-detection-probe.mm +207 -0
- package/tests/cursor-detection.test.cjs +37 -0
- package/tests/cursor-electron.test.cjs +26 -0
- package/tests/electron-cursor-fixture.cjs +67 -0
package/src/cursor_tracker.mm
CHANGED
|
@@ -72,9 +72,7 @@ static void initCGSCursorNameFunc() {
|
|
|
72
72
|
"CGSCopyCurrentCursorName",
|
|
73
73
|
"CGSCopyGlobalCursorName",
|
|
74
74
|
"SLSCopyCurrentCursorName",
|
|
75
|
-
"SLSCopyGlobalCursorName"
|
|
76
|
-
"CGSCopyCurrentCursor",
|
|
77
|
-
"SLSCopyCurrentCursor"
|
|
75
|
+
"SLSCopyGlobalCursorName"
|
|
78
76
|
};
|
|
79
77
|
size_t candidateCount = sizeof(symbolCandidates) / sizeof(symbolCandidates[0]);
|
|
80
78
|
for (size_t i = 0; i < candidateCount; ++i) {
|
|
@@ -113,6 +111,10 @@ static NSString* CopyCurrentCursorNameFromCGS(void) {
|
|
|
113
111
|
if (!cgsName) {
|
|
114
112
|
return nil;
|
|
115
113
|
}
|
|
114
|
+
if (CFGetTypeID(cgsName) != CFStringGetTypeID()) {
|
|
115
|
+
CFRelease(cgsName);
|
|
116
|
+
return nil;
|
|
117
|
+
}
|
|
116
118
|
NSString *name = [NSString stringWithString:(NSString *)cgsName];
|
|
117
119
|
CFRelease(cgsName);
|
|
118
120
|
return name;
|
|
@@ -129,12 +131,10 @@ static int g_debugCallbackCount = 0;
|
|
|
129
131
|
static NSFileHandle *g_fileHandle = nil;
|
|
130
132
|
static bool g_isFirstWrite = true;
|
|
131
133
|
static NSMutableDictionary<NSString*, NSString*> *g_cursorFingerprintMap = nil;
|
|
132
|
-
static NSMutableDictionary<NSValue*, NSString*> *g_cursorPointerCache = nil;
|
|
133
134
|
static NSMutableDictionary<NSString*, NSString*> *g_cursorNameMap = nil;
|
|
134
135
|
static dispatch_once_t g_cursorFingerprintInitToken;
|
|
135
136
|
static void LoadSystemCursorResourceFingerprints(void);
|
|
136
137
|
static void LoadCursorMappingOverrides(void);
|
|
137
|
-
static NSMutableDictionary<NSNumber*, NSString*> *g_seedOverrides = nil;
|
|
138
138
|
|
|
139
139
|
typedef NSCursor* (*CursorFactoryFunc)(id, SEL);
|
|
140
140
|
typedef NSString* (*CursorNameFunc)(id, SEL);
|
|
@@ -265,6 +265,28 @@ static NSArray<NSString *>* CursorImageFingerprintsAllReps(NSImage *image, NSPoi
|
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
+
NSSize size = image.size;
|
|
269
|
+
if (size.width > 0 && size.height > 0) {
|
|
270
|
+
for (NSUInteger scale = 1; scale <= 2; scale++) {
|
|
271
|
+
NSBitmapImageRep *bitmap = [[[NSBitmapImageRep alloc]
|
|
272
|
+
initWithBitmapDataPlanes:NULL pixelsWide:lround(size.width * scale)
|
|
273
|
+
pixelsHigh:lround(size.height * scale) bitsPerSample:8 samplesPerPixel:4
|
|
274
|
+
hasAlpha:YES isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace
|
|
275
|
+
bytesPerRow:0 bitsPerPixel:0] autorelease];
|
|
276
|
+
if (!bitmap) continue;
|
|
277
|
+
bitmap.size = size;
|
|
278
|
+
[NSGraphicsContext saveGraphicsState];
|
|
279
|
+
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap]];
|
|
280
|
+
[image drawInRect:NSMakeRect(0, 0, size.width, size.height) fromRect:NSZeroRect
|
|
281
|
+
operation:NSCompositingOperationCopy fraction:1.0];
|
|
282
|
+
[NSGraphicsContext restoreGraphicsState];
|
|
283
|
+
NSString *fingerprint = CursorImageFingerprintFromCGImage(bitmap.CGImage, hotspot);
|
|
284
|
+
if (fingerprint && ![fingerprints containsObject:fingerprint]) {
|
|
285
|
+
[fingerprints addObject:fingerprint];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
268
290
|
return fingerprints;
|
|
269
291
|
}
|
|
270
292
|
|
|
@@ -308,6 +330,10 @@ static NSString* CursorNameFromNSCursor(NSCursor *cursor) {
|
|
|
308
330
|
for (NSString *selectorName in selectorNames) {
|
|
309
331
|
SEL selector = NSSelectorFromString(selectorName);
|
|
310
332
|
if (selector && [cursor respondsToSelector:selector]) {
|
|
333
|
+
NSMethodSignature *signature = [cursor methodSignatureForSelector:selector];
|
|
334
|
+
if (!signature || signature.methodReturnType[0] != '@') {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
311
337
|
IMP imp = [cursor methodForSelector:selector];
|
|
312
338
|
if (!imp) {
|
|
313
339
|
continue;
|
|
@@ -389,13 +415,81 @@ static void AddCursorIfAvailableByName(NSString *selectorName, NSString *cursorT
|
|
|
389
415
|
AddCursorIfAvailable(selector, cursorType);
|
|
390
416
|
}
|
|
391
417
|
|
|
418
|
+
// Chromium and WebKit use CoreCursor for types with no public NSCursor factory.
|
|
419
|
+
// Ask AppKit to render these references, including the system's shadow; the raw
|
|
420
|
+
// HIServices PDF is not pixel-identical to the cursor returned by WindowServer.
|
|
421
|
+
// CoreCursor's enum is distinct from CGSCurrentCursorSeed (a change counter).
|
|
422
|
+
// https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/mac/CursorMac.mm
|
|
423
|
+
@interface MRSystemReferenceCursor : NSCursor {
|
|
424
|
+
NSInteger _referenceType;
|
|
425
|
+
}
|
|
426
|
+
- (instancetype)initWithCoreType:(NSInteger)type;
|
|
427
|
+
@end
|
|
428
|
+
|
|
429
|
+
@implementation MRSystemReferenceCursor
|
|
430
|
+
- (instancetype)initWithCoreType:(NSInteger)type {
|
|
431
|
+
self = [super init];
|
|
432
|
+
if (self) _referenceType = type;
|
|
433
|
+
return self;
|
|
434
|
+
}
|
|
435
|
+
- (NSInteger)_coreCursorType { return _referenceType; }
|
|
436
|
+
@end
|
|
437
|
+
|
|
438
|
+
static void AddCoreCursorFingerprints(void) {
|
|
439
|
+
// This SPI is optional. Keep public factories and resource matching on
|
|
440
|
+
// macOS versions where AppKit no longer exposes it.
|
|
441
|
+
if (![NSCursor instancesRespondToSelector:NSSelectorFromString(@"_coreCursorType")]) return;
|
|
442
|
+
NSDictionary<NSNumber *, NSString *> *types = @{
|
|
443
|
+
@4: @"progress", @11: @"grabbing", @12: @"grab",
|
|
444
|
+
@27: @"ew-resize", @28: @"ew-resize", @29: @"nesw-resize",
|
|
445
|
+
@30: @"nesw-resize", @31: @"ns-resize", @32: @"ns-resize",
|
|
446
|
+
@33: @"nwse-resize", @34: @"nwse-resize", @35: @"nwse-resize",
|
|
447
|
+
@36: @"ns-resize", @37: @"nesw-resize", @38: @"ew-resize",
|
|
448
|
+
@39: @"all-scroll", @40: @"help", @41: @"crosshair",
|
|
449
|
+
@42: @"zoom-in", @43: @"zoom-out"
|
|
450
|
+
};
|
|
451
|
+
for (NSNumber *type in types) {
|
|
452
|
+
@try {
|
|
453
|
+
NSCursor *cursor = [[[MRSystemReferenceCursor alloc] initWithCoreType:type.integerValue] autorelease];
|
|
454
|
+
AddStandardCursorFingerprint(cursor, types[type]);
|
|
455
|
+
} @catch (NSException *exception) {
|
|
456
|
+
MRLog(@"CoreCursor reference %@ unavailable: %@", type, exception.reason);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
static void AddFrameResizeCursorFingerprints(void) {
|
|
462
|
+
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000
|
|
463
|
+
if (@available(macOS 15.0, *)) {
|
|
464
|
+
const NSCursorFrameResizePosition positions[] = {
|
|
465
|
+
NSCursorFrameResizePositionTop, NSCursorFrameResizePositionBottom,
|
|
466
|
+
NSCursorFrameResizePositionLeft, NSCursorFrameResizePositionRight,
|
|
467
|
+
NSCursorFrameResizePositionTopLeft, NSCursorFrameResizePositionBottomRight,
|
|
468
|
+
NSCursorFrameResizePositionTopRight, NSCursorFrameResizePositionBottomLeft
|
|
469
|
+
};
|
|
470
|
+
NSArray<NSString *> *types = @[@"ns-resize", @"ns-resize", @"ew-resize", @"ew-resize",
|
|
471
|
+
@"nwse-resize", @"nwse-resize", @"nesw-resize", @"nesw-resize"];
|
|
472
|
+
for (NSUInteger direction = 1; direction <= 3; direction++) {
|
|
473
|
+
AddStandardCursorFingerprint([NSCursor columnResizeCursorInDirections:(NSHorizontalDirections)direction], @"col-resize");
|
|
474
|
+
AddStandardCursorFingerprint([NSCursor rowResizeCursorInDirections:(NSVerticalDirections)direction], @"row-resize");
|
|
475
|
+
for (NSUInteger i = 0; i < sizeof(positions) / sizeof(positions[0]); i++) {
|
|
476
|
+
AddStandardCursorFingerprint([NSCursor frameResizeCursorFromPosition:positions[i]
|
|
477
|
+
inDirections:(NSCursorFrameResizeDirections)direction], types[i]);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
#endif
|
|
482
|
+
}
|
|
483
|
+
|
|
392
484
|
static void InitializeCursorFingerprintMap(void) {
|
|
393
485
|
dispatch_once(&g_cursorFingerprintInitToken, ^{
|
|
394
486
|
g_cursorFingerprintMap = [[NSMutableDictionary alloc] init];
|
|
395
|
-
g_cursorPointerCache = [[NSMutableDictionary alloc] init];
|
|
396
487
|
g_cursorNameMap = [[NSMutableDictionary alloc] init];
|
|
397
488
|
|
|
398
489
|
void (^buildMap)(void) = ^{
|
|
490
|
+
// Node worker processes may not have initialized AppKit yet. Without
|
|
491
|
+
// it arrow/I-beam images can be empty and stay absent from this map.
|
|
492
|
+
[NSApplication sharedApplication];
|
|
399
493
|
AddStandardCursorFingerprint([NSCursor arrowCursor], @"default");
|
|
400
494
|
AddStandardCursorFingerprint([NSCursor pointingHandCursor], @"pointer");
|
|
401
495
|
AddStandardCursorFingerprint([NSCursor IBeamCursor], @"text");
|
|
@@ -413,8 +507,8 @@ static void InitializeCursorFingerprintMap(void) {
|
|
|
413
507
|
AddCursorIfAvailable(@selector(resizeUpDownCursor), @"row-resize");
|
|
414
508
|
AddCursorIfAvailableByName(@"resizeLeftCursor", @"col-resize");
|
|
415
509
|
AddCursorIfAvailableByName(@"resizeRightCursor", @"col-resize");
|
|
416
|
-
AddCursorIfAvailableByName(@"resizeUpCursor", @"
|
|
417
|
-
AddCursorIfAvailableByName(@"resizeDownCursor", @"
|
|
510
|
+
AddCursorIfAvailableByName(@"resizeUpCursor", @"row-resize");
|
|
511
|
+
AddCursorIfAvailableByName(@"resizeDownCursor", @"row-resize");
|
|
418
512
|
AddCursorIfAvailableByName(@"resizeNorthWestSouthEastCursor", @"nwse-resize");
|
|
419
513
|
AddCursorIfAvailableByName(@"resizeNorthEastSouthWestCursor", @"nesw-resize");
|
|
420
514
|
AddCursorIfAvailable(@selector(zoomInCursor), @"zoom-in");
|
|
@@ -422,6 +516,8 @@ static void InitializeCursorFingerprintMap(void) {
|
|
|
422
516
|
AddCursorIfAvailable(@selector(columnResizeCursor), @"col-resize");
|
|
423
517
|
AddCursorIfAvailable(@selector(rowResizeCursor), @"row-resize");
|
|
424
518
|
|
|
519
|
+
AddFrameResizeCursorFingerprints();
|
|
520
|
+
AddCoreCursorFingerprints();
|
|
425
521
|
LoadSystemCursorResourceFingerprints();
|
|
426
522
|
LoadCursorMappingOverrides();
|
|
427
523
|
};
|
|
@@ -440,16 +536,6 @@ static NSString* LookupCursorTypeByFingerprint(NSCursor *cursor, NSString **outF
|
|
|
440
536
|
}
|
|
441
537
|
InitializeCursorFingerprintMap();
|
|
442
538
|
|
|
443
|
-
NSValue *pointerKey = [NSValue valueWithPointer:(__bridge const void *)cursor];
|
|
444
|
-
|
|
445
|
-
// DISABLED: Pointer cache causes stale cursor type detection
|
|
446
|
-
// Since macOS may reuse the same NSCursor object for different contexts,
|
|
447
|
-
// we need to check the actual cursor state every time for real-time accuracy
|
|
448
|
-
// NSString *cachedType = [g_cursorPointerCache objectForKey:pointerKey];
|
|
449
|
-
// if (cachedType) {
|
|
450
|
-
// return cachedType;
|
|
451
|
-
// }
|
|
452
|
-
|
|
453
539
|
NSString *fingerprint = CursorImageFingerprint(cursor);
|
|
454
540
|
if (!fingerprint) {
|
|
455
541
|
return nil;
|
|
@@ -459,39 +545,7 @@ static NSString* LookupCursorTypeByFingerprint(NSCursor *cursor, NSString **outF
|
|
|
459
545
|
*outFingerprint = fingerprint;
|
|
460
546
|
}
|
|
461
547
|
|
|
462
|
-
|
|
463
|
-
if (mappedType) {
|
|
464
|
-
// DISABLED: Don't cache by pointer for real-time detection
|
|
465
|
-
// if (pointerKey) {
|
|
466
|
-
// [g_cursorPointerCache setObject:mappedType forKey:pointerKey];
|
|
467
|
-
// }
|
|
468
|
-
return mappedType;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
return nil;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
static void CacheCursorFingerprint(NSCursor *cursor, NSString *cursorType, NSString *knownFingerprint) {
|
|
475
|
-
if (!cursor || !cursorType || [cursorType length] == 0) {
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
InitializeCursorFingerprintMap();
|
|
479
|
-
NSString *fingerprint = knownFingerprint;
|
|
480
|
-
if (!fingerprint) {
|
|
481
|
-
fingerprint = CursorImageFingerprint(cursor);
|
|
482
|
-
}
|
|
483
|
-
if (!fingerprint) {
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
// Only cache fingerprint mapping (image hash -> type), not pointer mapping
|
|
487
|
-
if (![g_cursorFingerprintMap objectForKey:fingerprint]) {
|
|
488
|
-
[g_cursorFingerprintMap setObject:cursorType forKey:fingerprint];
|
|
489
|
-
}
|
|
490
|
-
// DISABLED: Pointer cache for real-time detection
|
|
491
|
-
// NSValue *pointerKey = [NSValue valueWithPointer:(__bridge const void *)cursor];
|
|
492
|
-
// if (pointerKey && g_cursorPointerCache) {
|
|
493
|
-
// [g_cursorPointerCache setObject:cursorType forKey:pointerKey];
|
|
494
|
-
// }
|
|
548
|
+
return [g_cursorFingerprintMap objectForKey:fingerprint];
|
|
495
549
|
}
|
|
496
550
|
|
|
497
551
|
// Forward declaration
|
|
@@ -553,22 +607,22 @@ static BOOL ShouldEmitCursorEvent(CGPoint location, NSString *cursorType, NSStri
|
|
|
553
607
|
BOOL moved = fabs(location.x - g_lastCursorLocation.x) >= movementThreshold ||
|
|
554
608
|
fabs(location.y - g_lastCursorLocation.y) >= movementThreshold;
|
|
555
609
|
BOOL eventChanged = !StringsEqual(eventType, g_lastCursorEventType);
|
|
556
|
-
BOOL
|
|
610
|
+
BOOL cursorChanged = !StringsEqual(cursorType, g_lastCursorType);
|
|
611
|
+
BOOL isMoveEvent = StringsEqual(eventType, @"move") || StringsEqual(eventType, @"drag") || StringsEqual(eventType, @"rightdrag");
|
|
557
612
|
BOOL isClickEvent = StringsEqual(eventType, @"mousedown") ||
|
|
558
613
|
StringsEqual(eventType, @"mouseup") ||
|
|
559
614
|
StringsEqual(eventType, @"rightmousedown") ||
|
|
560
615
|
StringsEqual(eventType, @"rightmouseup");
|
|
561
616
|
|
|
562
617
|
if (isMoveEvent) {
|
|
563
|
-
return moved;
|
|
618
|
+
return moved || cursorChanged || eventChanged;
|
|
564
619
|
}
|
|
565
620
|
|
|
566
621
|
if (isClickEvent) {
|
|
567
|
-
return eventChanged || moved;
|
|
622
|
+
return eventChanged || moved || cursorChanged;
|
|
568
623
|
}
|
|
569
624
|
|
|
570
625
|
// Fallback: only emit when something actually changed
|
|
571
|
-
BOOL cursorChanged = !StringsEqual(cursorType, g_lastCursorType);
|
|
572
626
|
return moved || cursorChanged || eventChanged;
|
|
573
627
|
}
|
|
574
628
|
|
|
@@ -932,163 +986,45 @@ static NSString* detectCursorTypeUsingAccessibility(CGPoint cursorPos) {
|
|
|
932
986
|
}
|
|
933
987
|
|
|
934
988
|
static NSString* cursorTypeFromCursorName(NSString *value) {
|
|
935
|
-
if (!value ||
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
NSString *
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
[
|
|
949
|
-
|
|
950
|
-
[
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
return
|
|
957
|
-
}
|
|
958
|
-
if ([
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
if ([
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
[
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
if ([
|
|
973
|
-
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
// Not allowed patterns
|
|
977
|
-
if ([normalized containsString:@"not-allowed"] ||
|
|
978
|
-
[normalized containsString:@"notallowed"] ||
|
|
979
|
-
[normalized containsString:@"forbidden"] ||
|
|
980
|
-
[normalized containsString:@"operation-not-allowed"]) {
|
|
981
|
-
return @"not-allowed";
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// Copy cursor patterns
|
|
985
|
-
if ([normalized containsString:@"dragcopy"] ||
|
|
986
|
-
[normalized containsString:@"drag-copy"] ||
|
|
987
|
-
[normalized containsString:@"copy"]) {
|
|
988
|
-
return @"copy";
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
// Alias cursor patterns
|
|
992
|
-
if ([normalized containsString:@"draglink"] ||
|
|
993
|
-
[normalized containsString:@"drag-link"] ||
|
|
994
|
-
[normalized containsString:@"alias"]) {
|
|
995
|
-
return @"alias";
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
// Context menu patterns
|
|
999
|
-
if (([normalized containsString:@"context"] && [normalized containsString:@"menu"]) ||
|
|
1000
|
-
[normalized containsString:@"contextual-menu"]) {
|
|
1001
|
-
return @"context-menu";
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
// Zoom patterns
|
|
1005
|
-
if ([normalized containsString:@"zoom"]) {
|
|
1006
|
-
if ([normalized containsString:@"out"]) {
|
|
1007
|
-
return @"zoom-out";
|
|
1008
|
-
}
|
|
1009
|
-
return @"zoom-in";
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
// All-scroll pattern (move in all directions)
|
|
1013
|
-
if ([normalized containsString:@"all-scroll"] ||
|
|
1014
|
-
[normalized containsString:@"allscroll"] ||
|
|
1015
|
-
([normalized containsString:@"move"] && [normalized containsString:@"all"]) ||
|
|
1016
|
-
[normalized containsString:@"omnidirectional"]) {
|
|
1017
|
-
return @"all-scroll";
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
// Resize cursor patterns - more comprehensive with Electron CSS names
|
|
1021
|
-
if ([normalized containsString:@"resize"] || [normalized containsString:@"size"]) {
|
|
1022
|
-
// Check for specific directional patterns first
|
|
1023
|
-
// North-East/South-West diagonal
|
|
1024
|
-
if ([normalized containsString:@"nesw"] ||
|
|
1025
|
-
([normalized containsString:@"northeast"] && [normalized containsString:@"southwest"]) ||
|
|
1026
|
-
([normalized containsString:@"ne"] && [normalized containsString:@"sw"])) {
|
|
1027
|
-
return @"nesw-resize";
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
// North-West/South-East diagonal
|
|
1031
|
-
if ([normalized containsString:@"nwse"] ||
|
|
1032
|
-
([normalized containsString:@"northwest"] && [normalized containsString:@"southeast"]) ||
|
|
1033
|
-
([normalized containsString:@"nw"] && [normalized containsString:@"se"])) {
|
|
1034
|
-
return @"nwse-resize";
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
// Generic diagonal patterns
|
|
1038
|
-
BOOL diagonalUp = [normalized containsString:@"diagonalup"] ||
|
|
1039
|
-
[normalized containsString:@"diagonal-up"];
|
|
1040
|
-
BOOL diagonalDown = [normalized containsString:@"diagonaldown"] ||
|
|
1041
|
-
[normalized containsString:@"diagonal-down"];
|
|
1042
|
-
|
|
1043
|
-
// Horizontal resize (East-West)
|
|
1044
|
-
BOOL horizontal = [normalized containsString:@"ew-resize"] ||
|
|
1045
|
-
[normalized containsString:@"ewresize"] ||
|
|
1046
|
-
[normalized containsString:@"leftright"] ||
|
|
1047
|
-
[normalized containsString:@"left-right"] ||
|
|
1048
|
-
[normalized containsString:@"horizontal"] ||
|
|
1049
|
-
([normalized containsString:@"left"] && [normalized containsString:@"right"]) ||
|
|
1050
|
-
[normalized containsString:@"col-resize"] ||
|
|
1051
|
-
[normalized containsString:@"column"];
|
|
1052
|
-
|
|
1053
|
-
// Vertical resize (North-South)
|
|
1054
|
-
BOOL vertical = [normalized containsString:@"ns-resize"] ||
|
|
1055
|
-
[normalized containsString:@"nsresize"] ||
|
|
1056
|
-
[normalized containsString:@"updown"] ||
|
|
1057
|
-
[normalized containsString:@"up-down"] ||
|
|
1058
|
-
[normalized containsString:@"vertical"] ||
|
|
1059
|
-
([normalized containsString:@"up"] && [normalized containsString:@"down"]) ||
|
|
1060
|
-
[normalized containsString:@"row-resize"];
|
|
1061
|
-
|
|
1062
|
-
if (diagonalUp) {
|
|
1063
|
-
return @"nesw-resize";
|
|
1064
|
-
}
|
|
1065
|
-
if (diagonalDown) {
|
|
1066
|
-
return @"nwse-resize";
|
|
1067
|
-
}
|
|
1068
|
-
if (horizontal) {
|
|
1069
|
-
return @"col-resize"; // Desktop SVG var: col-resize
|
|
1070
|
-
}
|
|
1071
|
-
if (vertical) {
|
|
1072
|
-
return @"ns-resize"; // Desktop SVG var: ns-resize
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
// If contains "resize" but no specific direction, return generic resize
|
|
1076
|
-
// This catches window resize cursors
|
|
1077
|
-
return @"nwse-resize"; // Default to diagonal for generic resize
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
// Progress/wait patterns - Electron uses 'progress'
|
|
1081
|
-
if ([normalized containsString:@"wait"] ||
|
|
1082
|
-
[normalized containsString:@"busy"] ||
|
|
1083
|
-
[normalized containsString:@"progress"]) {
|
|
1084
|
-
return @"progress";
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
// Help pattern
|
|
1088
|
-
if ([normalized containsString:@"help"] || [normalized containsString:@"question"]) {
|
|
1089
|
-
return @"help";
|
|
1090
|
-
}
|
|
1091
|
-
|
|
989
|
+
if (![value isKindOfClass:[NSString class]] || value.length == 0) return nil;
|
|
990
|
+
NSString *name = NormalizeCursorName(value);
|
|
991
|
+
// Treat separators uniformly, without letting "text" match "context" or
|
|
992
|
+
// "link" consume dragLink before the alias rule.
|
|
993
|
+
NSString *compact = [[name componentsSeparatedByCharactersInSet:
|
|
994
|
+
[[NSCharacterSet alphanumericCharacterSet] invertedSet]] componentsJoinedByString:@""];
|
|
995
|
+
if ([compact hasSuffix:@"cursor"]) compact = [compact substringToIndex:compact.length - 6];
|
|
996
|
+
|
|
997
|
+
if ([compact containsString:@"resize"]) {
|
|
998
|
+
if ([compact containsString:@"column"] || [compact isEqualToString:@"colresize"]) return @"col-resize";
|
|
999
|
+
if ([compact containsString:@"row"]) return @"row-resize";
|
|
1000
|
+
NSString *direction = [compact stringByReplacingOccurrencesOfString:@"resize" withString:@""];
|
|
1001
|
+
if ([direction hasPrefix:@"frame"]) direction = [direction substringFromIndex:5];
|
|
1002
|
+
if (StringEqualsAny(direction, @[@"nesw", @"ne", @"sw", @"northeast", @"southwest",
|
|
1003
|
+
@"northeastsouthwest", @"topright", @"bottomleft", @"diagonalup"])) return @"nesw-resize";
|
|
1004
|
+
if (StringEqualsAny(direction, @[@"nwse", @"nw", @"se", @"northwest", @"southeast",
|
|
1005
|
+
@"northwestsoutheast", @"topleft", @"bottomright", @"diagonaldown"])) return @"nwse-resize";
|
|
1006
|
+
if (StringEqualsAny(direction, @[@"ew", @"e", @"w", @"east", @"west", @"eastwest",
|
|
1007
|
+
@"left", @"right", @"leftright", @"horizontal"])) return @"ew-resize";
|
|
1008
|
+
if (StringEqualsAny(direction, @[@"ns", @"n", @"s", @"north", @"south", @"northsouth",
|
|
1009
|
+
@"up", @"down", @"updown", @"top", @"bottom", @"vertical"])) return @"ns-resize";
|
|
1010
|
+
return nil; // A name without an axis cannot identify a diagonal.
|
|
1011
|
+
}
|
|
1012
|
+
if ([compact containsString:@"contextualmenu"] || [compact containsString:@"contextmenu"]) return @"context-menu";
|
|
1013
|
+
if ([compact containsString:@"draglink"] || [compact containsString:@"alias"]) return @"alias";
|
|
1014
|
+
if ([compact containsString:@"copy"]) return @"copy";
|
|
1015
|
+
if ([compact containsString:@"notallowed"] || [compact containsString:@"nodrop"]) return @"not-allowed";
|
|
1016
|
+
if ([compact containsString:@"closedhand"] || [compact isEqualToString:@"grabbing"]) return @"grabbing";
|
|
1017
|
+
if ([compact containsString:@"openhand"] || [compact isEqualToString:@"grab"]) return @"grab";
|
|
1018
|
+
if ([compact containsString:@"pointinghand"] || StringEqualsAny(compact, @[@"pointer", @"hand", @"link"])) return @"pointer";
|
|
1019
|
+
if ([compact containsString:@"ibeam"] || [compact containsString:@"insertion"] ||
|
|
1020
|
+
StringEqualsAny(compact, @[@"text", @"verticaltext"])) return @"text";
|
|
1021
|
+
if ([compact containsString:@"zoomout"]) return @"zoom-out";
|
|
1022
|
+
if ([compact containsString:@"zoomin"]) return @"zoom-in";
|
|
1023
|
+
if (StringEqualsAny(compact, @[@"move", @"allscroll", @"moveall", @"omnidirectional"])) return @"all-scroll";
|
|
1024
|
+
if ([compact containsString:@"crosshair"] || StringEqualsAny(compact, @[@"cross", @"cell"])) return @"crosshair";
|
|
1025
|
+
if ([compact containsString:@"wait"] || [compact containsString:@"busy"] || [compact containsString:@"progress"]) return @"progress";
|
|
1026
|
+
if ([compact containsString:@"help"]) return @"help";
|
|
1027
|
+
if (StringEqualsAny(compact, @[@"arrow", @"default", @"auto", @"none"])) return @"default";
|
|
1092
1028
|
return nil;
|
|
1093
1029
|
}
|
|
1094
1030
|
|
|
@@ -1111,40 +1047,19 @@ static void AddCursorFingerprintFromResource(const CursorResourceEntry &entry) {
|
|
|
1111
1047
|
NSString *basePath = [@"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors" stringByAppendingPathComponent:resourceName];
|
|
1112
1048
|
NSFileManager *fm = [NSFileManager defaultManager];
|
|
1113
1049
|
NSArray<NSString *> *imageCandidates = @[ @"cursor_1only_.png", @"cursor.png", @"cursor.pdf" ];
|
|
1114
|
-
NSString *imagePath = nil;
|
|
1115
|
-
for (NSString *candidate in imageCandidates) {
|
|
1116
|
-
NSString *fullPath = [basePath stringByAppendingPathComponent:candidate];
|
|
1117
|
-
if ([fm fileExistsAtPath:fullPath]) {
|
|
1118
|
-
imagePath = fullPath;
|
|
1119
|
-
break;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
if (!imagePath) {
|
|
1123
|
-
return;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
NSImage *image = [[[NSImage alloc] initWithContentsOfFile:imagePath] autorelease];
|
|
1127
|
-
if (!image) {
|
|
1128
|
-
return;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
1050
|
NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[basePath stringByAppendingPathComponent:@"info.plist"]];
|
|
1132
1051
|
double hotx = [[info objectForKey:@"hotx"] doubleValue];
|
|
1133
1052
|
double hoty = [[info objectForKey:@"hoty"] doubleValue];
|
|
1134
1053
|
NSPoint hotspot = NSMakePoint(hotx, hoty);
|
|
1135
1054
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
if (![g_cursorFingerprintMap objectForKey:fingerprint]) {
|
|
1147
|
-
[g_cursorFingerprintMap setObject:cursorType forKey:fingerprint];
|
|
1055
|
+
// PNG and PDF versions can differ (notably help). Both are used by apps.
|
|
1056
|
+
for (NSString *candidate in imageCandidates) {
|
|
1057
|
+
NSString *imagePath = [basePath stringByAppendingPathComponent:candidate];
|
|
1058
|
+
if (![fm fileExistsAtPath:imagePath]) continue;
|
|
1059
|
+
NSImage *image = [[[NSImage alloc] initWithContentsOfFile:imagePath] autorelease];
|
|
1060
|
+
if (!image) continue;
|
|
1061
|
+
NSCursor *cursor = [[[NSCursor alloc] initWithImage:image hotSpot:hotspot] autorelease];
|
|
1062
|
+
AddStandardCursorFingerprint(cursor, cursorType);
|
|
1148
1063
|
}
|
|
1149
1064
|
}
|
|
1150
1065
|
|
|
@@ -1153,7 +1068,7 @@ static void LoadSystemCursorResourceFingerprints(void) {
|
|
|
1153
1068
|
{"progress", "busybutclickable"},
|
|
1154
1069
|
{"wait", "countinguphand"},
|
|
1155
1070
|
{"wait", "countingdownhand"},
|
|
1156
|
-
{"wait", "
|
|
1071
|
+
{"wait", "countingupandownhand"},
|
|
1157
1072
|
{"context-menu", "contextualmenu"},
|
|
1158
1073
|
{"copy", "copy"},
|
|
1159
1074
|
{"alias", "makealias"},
|
|
@@ -1171,10 +1086,14 @@ static void LoadSystemCursorResourceFingerprints(void) {
|
|
|
1171
1086
|
{"zoom-out", "zoomout"},
|
|
1172
1087
|
{"text", "ibeamhorizontal"},
|
|
1173
1088
|
{"vertical-text", "ibeamvertical"},
|
|
1089
|
+
{"col-resize", "resizeleft"},
|
|
1090
|
+
{"col-resize", "resizeright"},
|
|
1091
|
+
{"row-resize", "resizeup"},
|
|
1092
|
+
{"row-resize", "resizedown"},
|
|
1174
1093
|
{"col-resize", "resizeleftright"},
|
|
1175
1094
|
{"col-resize", "resizeeastwest"},
|
|
1176
1095
|
{"row-resize", "resizeupdown"},
|
|
1177
|
-
{"
|
|
1096
|
+
{"ns-resize", "resizenorthsouth"},
|
|
1178
1097
|
{"ew-resize", "resizeeastwest"},
|
|
1179
1098
|
{"ew-resize", "resizeleftright"},
|
|
1180
1099
|
{"ns-resize", "resizenorthsouth"},
|
|
@@ -1210,18 +1129,6 @@ static void RegisterCursorNameMapping(NSString *name, NSString *cursorType) {
|
|
|
1210
1129
|
}
|
|
1211
1130
|
}
|
|
1212
1131
|
|
|
1213
|
-
static void RegisterSeedMapping(NSNumber *seedValue, NSString *cursorType) {
|
|
1214
|
-
if (!seedValue || !cursorType) {
|
|
1215
|
-
return;
|
|
1216
|
-
}
|
|
1217
|
-
if (!g_seedOverrides) {
|
|
1218
|
-
g_seedOverrides = [[NSMutableDictionary alloc] init];
|
|
1219
|
-
}
|
|
1220
|
-
if (![g_seedOverrides objectForKey:seedValue]) {
|
|
1221
|
-
[g_seedOverrides setObject:cursorType forKey:seedValue];
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
1132
|
static NSString* FindCursorMappingFile(void) {
|
|
1226
1133
|
NSMutableArray<NSString *> *candidates = [NSMutableArray array];
|
|
1227
1134
|
const char *envPath = getenv("MAC_RECORDER_CURSOR_MAP");
|
|
@@ -1310,554 +1217,57 @@ static void LoadCursorMappingOverrides(void) {
|
|
|
1310
1217
|
RegisterCursorNameMapping(privateName, cursorType);
|
|
1311
1218
|
}
|
|
1312
1219
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
RegisterSeedMapping(seed, cursorType);
|
|
1316
|
-
}
|
|
1220
|
+
// Cursor seeds are change counters, not stable cursor identities.
|
|
1221
|
+
// Old calibration files may contain them; only image/name entries apply.
|
|
1317
1222
|
}];
|
|
1318
1223
|
}
|
|
1319
1224
|
|
|
1320
|
-
// Runtime seed mapping - built dynamically on first use
|
|
1321
|
-
// Seeds change between app launches, so we build the mapping at runtime by querying NSCursor objects
|
|
1322
|
-
// SAFETY: Protected with try-catch to prevent crashes in Electron environments
|
|
1323
|
-
static BOOL g_enableSeedLearning = YES; // Runtime seed learning enabled with crash protection
|
|
1324
|
-
static NSMutableDictionary<NSNumber*, NSString*> *g_seedToTypeMap = nil;
|
|
1325
|
-
static dispatch_once_t g_seedMapInitToken;
|
|
1326
|
-
|
|
1327
|
-
static void buildRuntimeSeedMapping() {
|
|
1328
|
-
dispatch_once(&g_seedMapInitToken, ^{
|
|
1329
|
-
@try {
|
|
1330
|
-
@autoreleasepool {
|
|
1331
|
-
g_seedToTypeMap = [[NSMutableDictionary alloc] init];
|
|
1332
|
-
|
|
1333
|
-
// Instead of trying to build mapping upfront (which crashes),
|
|
1334
|
-
// we'll build it lazily as we encounter cursors during actual usage
|
|
1335
|
-
// For now, just initialize the empty map
|
|
1336
|
-
|
|
1337
|
-
NSLog(@"✅ Runtime seed mapping initialized (will build lazily)");
|
|
1338
|
-
}
|
|
1339
|
-
} @catch (NSException *exception) {
|
|
1340
|
-
NSLog(@"⚠️ Failed to initialize runtime seed mapping: %@", exception.reason);
|
|
1341
|
-
g_seedToTypeMap = nil;
|
|
1342
|
-
}
|
|
1343
|
-
});
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
// Add a cursor seed to the runtime mapping
|
|
1347
|
-
// NOTE: We don't pass cursor object to avoid potential crashes - we only need seed and type
|
|
1348
|
-
static void addCursorToSeedMap(NSString *detectedType, int seed) {
|
|
1349
|
-
// Safety: Check if learning is enabled
|
|
1350
|
-
if (!g_enableSeedLearning) return;
|
|
1351
|
-
|
|
1352
|
-
if (seed <= 0 || !detectedType || [detectedType length] == 0) return;
|
|
1353
|
-
|
|
1354
|
-
@try {
|
|
1355
|
-
@autoreleasepool {
|
|
1356
|
-
buildRuntimeSeedMapping(); // Ensure map is initialized
|
|
1357
|
-
|
|
1358
|
-
// If initialization failed, don't proceed
|
|
1359
|
-
if (!g_seedToTypeMap) return;
|
|
1360
|
-
|
|
1361
|
-
NSNumber *key = @(seed);
|
|
1362
|
-
|
|
1363
|
-
// Only add if we don't have this seed yet
|
|
1364
|
-
if (![g_seedToTypeMap objectForKey:key]) {
|
|
1365
|
-
[g_seedToTypeMap setObject:detectedType forKey:key];
|
|
1366
|
-
// Always log new seed mappings for debugging
|
|
1367
|
-
NSLog(@"📝 Learned seed mapping: %d -> %@", seed, detectedType);
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
} @catch (NSException *exception) {
|
|
1371
|
-
// Silently fail - don't crash the app for cursor learning
|
|
1372
|
-
NSLog(@"⚠️ Failed to add cursor seed mapping: %@", exception.reason);
|
|
1373
|
-
} @catch (...) {
|
|
1374
|
-
NSLog(@"⚠️ Failed to add cursor seed mapping (unknown exception)");
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
static NSString* cursorTypeFromSeed(int seed) {
|
|
1379
|
-
if (seed > 0) {
|
|
1380
|
-
@try {
|
|
1381
|
-
@autoreleasepool {
|
|
1382
|
-
NSNumber *key = @(seed);
|
|
1383
|
-
NSString *override = [g_seedOverrides objectForKey:key];
|
|
1384
|
-
if (override) {
|
|
1385
|
-
return override;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
// Only check runtime mappings if learning is enabled
|
|
1389
|
-
if (g_enableSeedLearning) {
|
|
1390
|
-
buildRuntimeSeedMapping();
|
|
1391
|
-
if (g_seedToTypeMap) {
|
|
1392
|
-
NSString *runtime = [g_seedToTypeMap objectForKey:key];
|
|
1393
|
-
if (runtime) {
|
|
1394
|
-
return runtime;
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
} @catch (NSException *exception) {
|
|
1400
|
-
// Silently fail - don't crash for cursor lookup
|
|
1401
|
-
NSLog(@"⚠️ Exception in cursorTypeFromSeed: %@", exception.reason);
|
|
1402
|
-
} @catch (...) {
|
|
1403
|
-
NSLog(@"⚠️ Unknown exception in cursorTypeFromSeed");
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
switch(seed) {
|
|
1407
|
-
// Desktop'ta SVG karşılığı olan tiplere normalize edilmiş seed map
|
|
1408
|
-
case 741324: return @"default"; // auto → default
|
|
1409
|
-
case 741336: return @"default"; // none → default (gizli cursor kayıtta default gösterilir)
|
|
1410
|
-
case 741338: return @"default"; // context-menu → default (SVG yok)
|
|
1411
|
-
case 741339: return @"pointer";
|
|
1412
|
-
case 741341: return @"progress";
|
|
1413
|
-
case 741343: return @"progress"; // wait → progress
|
|
1414
|
-
case 741345: return @"crosshair"; // cell → crosshair (en yakın SVG)
|
|
1415
|
-
case 741347: return @"crosshair";
|
|
1416
|
-
case 741357: return @"text";
|
|
1417
|
-
case 741359: return @"text"; // vertical-text → text
|
|
1418
|
-
case 741361: return @"alias";
|
|
1419
|
-
case 741362: return @"copy";
|
|
1420
|
-
case 741364: return @"all-scroll"; // move → all-scroll
|
|
1421
|
-
case 741368: return @"not-allowed"; // no-drop → not-allowed
|
|
1422
|
-
case 741370: return @"not-allowed";
|
|
1423
|
-
case 741381: return @"grab";
|
|
1424
|
-
case 741385: return @"grabbing";
|
|
1425
|
-
case 741389: return @"col-resize";
|
|
1426
|
-
case 741393: return @"row-resize";
|
|
1427
|
-
case 741397: return @"ns-resize"; // n-resize → ns-resize
|
|
1428
|
-
case 741398: return @"col-resize"; // e-resize → col-resize
|
|
1429
|
-
case 741409: return @"ns-resize"; // s-resize → ns-resize
|
|
1430
|
-
case 741413: return @"col-resize"; // w-resize → col-resize
|
|
1431
|
-
case 741417: return @"nesw-resize"; // ne-resize → nesw-resize
|
|
1432
|
-
case 741418: return @"nwse-resize"; // nw-resize → nwse-resize
|
|
1433
|
-
case 741420: return @"nwse-resize"; // se-resize → nwse-resize
|
|
1434
|
-
case 741424: return @"nesw-resize"; // sw-resize → nesw-resize
|
|
1435
|
-
case 741426: return @"col-resize"; // ew-resize → col-resize
|
|
1436
|
-
case 741436: return @"ns-resize";
|
|
1437
|
-
case 741438: return @"nesw-resize";
|
|
1438
|
-
case 741442: return @"nwse-resize";
|
|
1439
|
-
case 741444: return @"zoom-in";
|
|
1440
|
-
case 741446: return @"zoom-out";
|
|
1441
|
-
default: return nil;
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
// Image-based cursor detection using known patterns from mapping
|
|
1446
|
-
static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, NSCursor *cursor) {
|
|
1447
|
-
if (!image) {
|
|
1448
|
-
return nil;
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
NSSize size = [image size];
|
|
1452
|
-
CGFloat width = size.width;
|
|
1453
|
-
CGFloat height = size.height;
|
|
1454
|
-
CGFloat aspectRatio = width > 0 ? width / height : 0;
|
|
1455
|
-
CGFloat relativeX = width > 0 ? hotspot.x / width : 0;
|
|
1456
|
-
CGFloat relativeY = height > 0 ? hotspot.y / height : 0;
|
|
1457
|
-
|
|
1458
|
-
// Tolerance for floating point comparison
|
|
1459
|
-
CGFloat tolerance = 0.05;
|
|
1460
|
-
CGFloat tightTolerance = 0.02; // For precise hotspot matching
|
|
1461
|
-
|
|
1462
|
-
// Helper lambda for approximate comparison
|
|
1463
|
-
auto approx = [tolerance](CGFloat a, CGFloat b) -> BOOL {
|
|
1464
|
-
return fabs(a - b) < tolerance;
|
|
1465
|
-
};
|
|
1466
|
-
|
|
1467
|
-
auto approxTight = [tightTolerance](CGFloat a, CGFloat b) -> BOOL {
|
|
1468
|
-
return fabs(a - b) < tightTolerance;
|
|
1469
|
-
};
|
|
1470
|
-
|
|
1471
|
-
// Pattern matching based on cursor-nscursor-mapping.json
|
|
1472
|
-
|
|
1473
|
-
// none: 1x1, ratio=1.0, hotspot=(0,0)
|
|
1474
|
-
if (approx(width, 1) && approx(height, 1)) {
|
|
1475
|
-
return @"none";
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
// text: 22x23, ratio=0.956, hotspot rel=(0.52, 0.48)
|
|
1479
|
-
if (approx(width, 22) && approx(height, 23) && approx(aspectRatio, 0.956)) {
|
|
1480
|
-
return @"text";
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
// vertical-text: 22x21, ratio=1.047, hotspot rel=(0.5, 0.476)
|
|
1484
|
-
if (approx(width, 22) && approx(height, 21) && approx(aspectRatio, 1.047)) {
|
|
1485
|
-
return @"vertical-text";
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
// pointer: 32x32, ratio=1.0, hotspot rel=(0.406, 0.25)
|
|
1489
|
-
if (approx(width, 32) && approx(height, 32) && approx(relativeY, 0.25)) {
|
|
1490
|
-
return @"pointer";
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
// grab/grabbing: 32x32, ratio=1.0, hotspot rel=(0.5, 0.531)
|
|
1494
|
-
// Distinguished by pointer equality
|
|
1495
|
-
if (approx(width, 32) && approx(height, 32) && approx(relativeY, 0.531)) {
|
|
1496
|
-
if (cursor) {
|
|
1497
|
-
if (cursor == [NSCursor closedHandCursor]) {
|
|
1498
|
-
return @"grabbing";
|
|
1499
|
-
}
|
|
1500
|
-
if (cursor == [NSCursor openHandCursor]) {
|
|
1501
|
-
return @"grab";
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
return @"grab"; // Default to grab if can't distinguish
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
// 24x24 cursors: crosshair vs move/all-scroll
|
|
1508
|
-
// Distinguished by precise hotspot position
|
|
1509
|
-
if (approx(width, 24) && approx(height, 24)) {
|
|
1510
|
-
// crosshair: hotspot rel=(0.458, 0.458)
|
|
1511
|
-
if (approxTight(relativeX, 0.458) && approxTight(relativeY, 0.458)) {
|
|
1512
|
-
return @"crosshair";
|
|
1513
|
-
}
|
|
1514
|
-
// move/all-scroll: hotspot rel=(0.5, 0.5)
|
|
1515
|
-
if (approxTight(relativeX, 0.5) && approxTight(relativeY, 0.5)) {
|
|
1516
|
-
return @"move"; // or all-scroll, they're identical
|
|
1517
|
-
}
|
|
1518
|
-
// Fallback for 24x24
|
|
1519
|
-
return @"crosshair";
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
// help/cell: 18x18, ratio=1.0, hotspot rel=(0.5, 0.5)
|
|
1523
|
-
// NOTE: Cannot distinguish between help and cell by image alone
|
|
1524
|
-
if (approx(width, 18) && approx(height, 18)) {
|
|
1525
|
-
return @"cell"; // Default to cell for compatibility
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
// col-resize: 30x24, ratio=1.25, hotspot rel=(0.5, 0.5)
|
|
1529
|
-
if (approx(width, 30) && approx(height, 24) && approx(aspectRatio, 1.25)) {
|
|
1530
|
-
return @"col-resize";
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
// e-resize/w-resize/ew-resize: 24x18, ratio=1.333, hotspot rel=(0.5, 0.5)
|
|
1534
|
-
// Distinguish using pointer equality
|
|
1535
|
-
if (approx(width, 24) && approx(height, 18) && approx(aspectRatio, 1.333)) {
|
|
1536
|
-
if (cursor) {
|
|
1537
|
-
if ([NSCursor respondsToSelector:@selector(resizeLeftCursor)] &&
|
|
1538
|
-
cursor == [NSCursor resizeLeftCursor]) {
|
|
1539
|
-
return @"w-resize";
|
|
1540
|
-
}
|
|
1541
|
-
if ([NSCursor respondsToSelector:@selector(resizeRightCursor)] &&
|
|
1542
|
-
cursor == [NSCursor resizeRightCursor]) {
|
|
1543
|
-
return @"e-resize";
|
|
1544
|
-
}
|
|
1545
|
-
if ([NSCursor respondsToSelector:@selector(resizeLeftRightCursor)] &&
|
|
1546
|
-
cursor == [NSCursor resizeLeftRightCursor]) {
|
|
1547
|
-
return @"ew-resize";
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
return @"ew-resize"; // Default to ew-resize
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
// row-resize: 24x28, ratio=0.857, hotspot rel=(0.5, 0.5)
|
|
1554
|
-
if (approx(width, 24) && approx(height, 28) && approx(aspectRatio, 0.857)) {
|
|
1555
|
-
return @"row-resize";
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
// n-resize/s-resize/ns-resize: 18x28, ratio=0.643, hotspot rel=(0.5, 0.5)
|
|
1559
|
-
// Distinguish using pointer equality
|
|
1560
|
-
if (approx(width, 18) && approx(height, 28) && approx(aspectRatio, 0.643)) {
|
|
1561
|
-
if (cursor) {
|
|
1562
|
-
if ([NSCursor respondsToSelector:@selector(resizeUpCursor)] &&
|
|
1563
|
-
cursor == [NSCursor resizeUpCursor]) {
|
|
1564
|
-
return @"n-resize";
|
|
1565
|
-
}
|
|
1566
|
-
if ([NSCursor respondsToSelector:@selector(resizeDownCursor)] &&
|
|
1567
|
-
cursor == [NSCursor resizeDownCursor]) {
|
|
1568
|
-
return @"s-resize";
|
|
1569
|
-
}
|
|
1570
|
-
if ([NSCursor respondsToSelector:@selector(resizeUpDownCursor)] &&
|
|
1571
|
-
cursor == [NSCursor resizeUpDownCursor]) {
|
|
1572
|
-
return @"ns-resize";
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
return @"ns-resize"; // Default to ns-resize
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
// ne-resize/nw-resize/se-resize/sw-resize/nesw-resize/nwse-resize: 22x22, ratio=1.0, hotspot rel=(0.5, 0.5)
|
|
1579
|
-
if (approx(width, 22) && approx(height, 22)) {
|
|
1580
|
-
return @"nwse-resize"; // Default to nwse-resize for all diagonal cursors
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
// zoom-in/zoom-out: 28x26, ratio=1.077, hotspot rel=(0.428, 0.423)
|
|
1584
|
-
// NOTE: Cannot distinguish between zoom-in and zoom-out by image or pointer alone
|
|
1585
|
-
// They use the same image and there's no standard NSCursor for zoom
|
|
1586
|
-
if (approx(width, 28) && approx(height, 26) && approx(aspectRatio, 1.077)) {
|
|
1587
|
-
return @"zoom-in"; // Default to zoom-in (cannot distinguish from zoom-out)
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
// alias: 16x21, ratio=0.762, hotspot rel=(0.688, 0.143)
|
|
1591
|
-
if (approx(width, 16) && approx(height, 21) && approx(aspectRatio, 0.762)) {
|
|
1592
|
-
return @"alias";
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
// 28x40 cursors: default/auto vs context-menu/progress/wait/copy/no-drop/not-allowed
|
|
1596
|
-
// Distinguished by precise hotspot position and pointer equality
|
|
1597
|
-
if (approx(width, 28) && approx(height, 40) && approx(aspectRatio, 0.7)) {
|
|
1598
|
-
// auto/default: hotspot rel=(0.161, 0.1) - hotspot at (4.5, 4)
|
|
1599
|
-
if (approxTight(relativeX, 0.161) && approxTight(relativeY, 0.1)) {
|
|
1600
|
-
return @"default";
|
|
1601
|
-
}
|
|
1602
|
-
// context-menu/progress/wait/copy/no-drop/not-allowed: hotspot rel=(0.179, 0.125) - hotspot at (5, 5)
|
|
1603
|
-
if (approxTight(relativeX, 0.179) && approxTight(relativeY, 0.125)) {
|
|
1604
|
-
// Try pointer equality for standard cursors
|
|
1605
|
-
if (cursor) {
|
|
1606
|
-
if (cursor == [NSCursor contextualMenuCursor]) {
|
|
1607
|
-
return @"context-menu";
|
|
1608
|
-
}
|
|
1609
|
-
if (cursor == [NSCursor dragCopyCursor]) {
|
|
1610
|
-
return @"copy";
|
|
1611
|
-
}
|
|
1612
|
-
if (cursor == [NSCursor operationNotAllowedCursor]) {
|
|
1613
|
-
return @"not-allowed";
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
// NOTE: progress, wait, no-drop don't have standard NSCursor pointers
|
|
1617
|
-
// Return "progress" as default for this hotspot pattern (better than "default")
|
|
1618
|
-
// Let cursor name detection in caller distinguish between progress/wait
|
|
1619
|
-
return @"progress";
|
|
1620
|
-
}
|
|
1621
|
-
return @"default";
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
return nil;
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
1225
|
static NSString* cursorTypeFromNSCursor(NSCursor *cursor) {
|
|
1628
|
-
if (!cursor)
|
|
1629
|
-
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
// PRIORITY 1: Standard macOS cursor pointer equality (fastest and most reliable)
|
|
1633
|
-
if (cursor == [NSCursor arrowCursor]) {
|
|
1634
|
-
return @"default";
|
|
1635
|
-
}
|
|
1636
|
-
if (cursor == [NSCursor IBeamCursor]) {
|
|
1637
|
-
return @"text";
|
|
1638
|
-
}
|
|
1639
|
-
if ([NSCursor respondsToSelector:@selector(IBeamCursorForVerticalLayout)] &&
|
|
1640
|
-
cursor == [NSCursor IBeamCursorForVerticalLayout]) {
|
|
1641
|
-
return @"text";
|
|
1642
|
-
}
|
|
1643
|
-
if (cursor == [NSCursor pointingHandCursor]) {
|
|
1644
|
-
return @"pointer";
|
|
1645
|
-
}
|
|
1646
|
-
if (cursor == [NSCursor crosshairCursor]) {
|
|
1647
|
-
return @"crosshair";
|
|
1648
|
-
}
|
|
1649
|
-
if (cursor == [NSCursor openHandCursor]) {
|
|
1650
|
-
return @"grab";
|
|
1651
|
-
}
|
|
1652
|
-
if (cursor == [NSCursor closedHandCursor]) {
|
|
1653
|
-
return @"grabbing";
|
|
1654
|
-
}
|
|
1655
|
-
if (cursor == [NSCursor operationNotAllowedCursor]) {
|
|
1656
|
-
return @"not-allowed";
|
|
1657
|
-
}
|
|
1658
|
-
if (cursor == [NSCursor dragCopyCursor]) {
|
|
1659
|
-
return @"copy";
|
|
1660
|
-
}
|
|
1661
|
-
if (cursor == [NSCursor dragLinkCursor]) {
|
|
1662
|
-
return @"alias";
|
|
1663
|
-
}
|
|
1664
|
-
if (cursor == [NSCursor contextualMenuCursor]) {
|
|
1665
|
-
return @"context-menu";
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
// Resize cursors
|
|
1669
|
-
if ([NSCursor respondsToSelector:@selector(resizeLeftRightCursor)]) {
|
|
1670
|
-
if (cursor == [NSCursor resizeLeftRightCursor]) {
|
|
1671
|
-
return @"col-resize";
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
if ([NSCursor respondsToSelector:@selector(resizeUpDownCursor)]) {
|
|
1675
|
-
if (cursor == [NSCursor resizeUpDownCursor]) {
|
|
1676
|
-
return @"row-resize";
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
NSString *privateCursorName = CursorNameFromNSCursor(cursor);
|
|
1681
|
-
if (privateCursorName) {
|
|
1682
|
-
NSString *normalizedName = NormalizeCursorName(privateCursorName);
|
|
1683
|
-
NSString *mappedType = normalizedName ? [g_cursorNameMap objectForKey:normalizedName] : nil;
|
|
1684
|
-
if (mappedType) {
|
|
1685
|
-
CacheCursorFingerprint(cursor, mappedType, nil);
|
|
1686
|
-
return mappedType;
|
|
1687
|
-
}
|
|
1688
|
-
NSString *typeFromName = cursorTypeFromCursorName(privateCursorName);
|
|
1689
|
-
if (typeFromName) {
|
|
1690
|
-
RegisterCursorNameMapping(privateCursorName, typeFromName);
|
|
1691
|
-
CacheCursorFingerprint(cursor, typeFromName, nil);
|
|
1692
|
-
return typeFromName;
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1226
|
+
if (!cursor) return @"default";
|
|
1227
|
+
InitializeCursorFingerprintMap();
|
|
1695
1228
|
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1229
|
+
// Image evidence is authoritative. currentSystemCursor returns detached
|
|
1230
|
+
// NSCursor objects, so pointer identity and image dimensions cannot identify
|
|
1231
|
+
// the shape (opposite diagonals and zoom +/- share the same dimensions).
|
|
1232
|
+
NSString *fingerprintType = LookupCursorTypeByFingerprint(cursor, NULL);
|
|
1233
|
+
if (fingerprintType) return fingerprintType;
|
|
1701
1234
|
|
|
1702
|
-
//
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
NSString *imageBasedType = cursorTypeFromImageSignature(cursorImage, hotspot, cursor);
|
|
1706
|
-
if (imageBasedType) {
|
|
1707
|
-
if (![imageBasedType isEqualToString:@"default"]) {
|
|
1708
|
-
CacheCursorFingerprint(cursor, imageBasedType, fingerprintHint);
|
|
1709
|
-
}
|
|
1710
|
-
return imageBasedType;
|
|
1711
|
-
}
|
|
1235
|
+
// Some standard cursors have lazily populated images in headless Node hosts.
|
|
1236
|
+
if (cursor == [NSCursor arrowCursor]) return @"default";
|
|
1237
|
+
if (cursor == [NSCursor IBeamCursor] || cursor == [NSCursor IBeamCursorForVerticalLayout]) return @"text";
|
|
1712
1238
|
|
|
1713
|
-
|
|
1714
|
-
NSString *
|
|
1715
|
-
|
|
1716
|
-
if (derived) {
|
|
1717
|
-
if (![derived isEqualToString:@"default"]) {
|
|
1718
|
-
CacheCursorFingerprint(cursor, derived, fingerprintHint);
|
|
1719
|
-
}
|
|
1720
|
-
return derived;
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
// Default fallback
|
|
1724
|
-
return @"default";
|
|
1239
|
+
NSString *name = CursorNameFromNSCursor(cursor);
|
|
1240
|
+
NSString *mapped = name ? [g_cursorNameMap objectForKey:NormalizeCursorName(name)] : nil;
|
|
1241
|
+
return mapped ?: cursorTypeFromCursorName(name) ?: @"default";
|
|
1725
1242
|
}
|
|
1726
1243
|
|
|
1727
1244
|
static NSString* detectSystemCursorType(void) {
|
|
1728
1245
|
InitializeCursorFingerprintMap();
|
|
1729
1246
|
__block NSString *cursorType = nil;
|
|
1730
|
-
__block NSCursor *detectedCursor = nil;
|
|
1731
|
-
|
|
1732
|
-
NSString *cgsName = CopyCurrentCursorNameFromCGS();
|
|
1733
|
-
if (cgsName && [cgsName length] > 0) {
|
|
1734
|
-
NSString *normalized = NormalizeCursorName(cgsName);
|
|
1735
|
-
NSString *mapped = normalized ? [g_cursorNameMap objectForKey:normalized] : nil;
|
|
1736
|
-
if (mapped) {
|
|
1737
|
-
return mapped;
|
|
1738
|
-
}
|
|
1739
|
-
NSString *derivedFromName = cursorTypeFromCursorName(cgsName);
|
|
1740
|
-
if (derivedFromName) {
|
|
1741
|
-
RegisterCursorNameMapping(cgsName, derivedFromName);
|
|
1742
|
-
return derivedFromName;
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
int cursorSeed = SafeCGSCurrentCursorSeed();
|
|
1747
|
-
if (cursorSeed > 0) {
|
|
1748
|
-
NSString *seedType = cursorTypeFromSeed(cursorSeed);
|
|
1749
|
-
if (seedType) {
|
|
1750
|
-
return seedType;
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
1247
|
void (^fetchCursorBlock)(void) = ^{
|
|
1755
|
-
NSCursor *
|
|
1756
|
-
|
|
1757
|
-
// Try different methods to get current cursor
|
|
1248
|
+
NSCursor *cursor = nil;
|
|
1758
1249
|
if ([NSCursor respondsToSelector:@selector(currentSystemCursor)]) {
|
|
1759
|
-
|
|
1250
|
+
cursor = [NSCursor currentSystemCursor];
|
|
1760
1251
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1252
|
+
if (cursor && cursor.image.size.width > 0 && cursor.image.size.height > 0) {
|
|
1253
|
+
cursorType = cursorTypeFromNSCursor(cursor);
|
|
1254
|
+
return;
|
|
1764
1255
|
}
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
if (directType && ![directType isEqualToString:@"default"]) {
|
|
1773
|
-
cursorType = directType;
|
|
1774
|
-
return;
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
NSString *className = NSStringFromClass([currentCursor class]);
|
|
1778
|
-
NSString *description = [currentCursor description];
|
|
1779
|
-
// Use more direct cursor detection approach
|
|
1780
|
-
NSImage *cursorImage = [currentCursor image];
|
|
1781
|
-
NSPoint hotspot = [currentCursor hotSpot];
|
|
1782
|
-
NSSize imageSize = [cursorImage size];
|
|
1783
|
-
|
|
1784
|
-
// ROBUST cursor detection - works with any cursor size
|
|
1785
|
-
CGFloat aspectRatio = imageSize.width / imageSize.height;
|
|
1786
|
-
CGFloat relativeHotspotX = hotspot.x / imageSize.width;
|
|
1787
|
-
CGFloat relativeHotspotY = hotspot.y / imageSize.height;
|
|
1788
|
-
BOOL fallbackDefaults = (fallbackType && [fallbackType isEqualToString:@"default"]);
|
|
1789
|
-
BOOL cursorNameSuggestsText = NO;
|
|
1790
|
-
if (className && ([className localizedCaseInsensitiveContainsString:@"ibeam"] ||
|
|
1791
|
-
[className localizedCaseInsensitiveContainsString:@"text"])) {
|
|
1792
|
-
cursorNameSuggestsText = YES;
|
|
1793
|
-
}
|
|
1794
|
-
if (description && ([description localizedCaseInsensitiveContainsString:@"ibeam"] ||
|
|
1795
|
-
[description localizedCaseInsensitiveContainsString:@"text"])) {
|
|
1796
|
-
cursorNameSuggestsText = YES;
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
// UPDATED with real cursor data:
|
|
1802
|
-
// Arrow: 17x23 ratio=0.74 hotspot=(0.24,0.17)
|
|
1803
|
-
// Text: 9x18 ratio=0.50 hotspot=(0.44,0.50)
|
|
1804
|
-
// Pointer: 32x32 ratio=1.00 hotspot=(0.41,0.25)
|
|
1805
|
-
|
|
1806
|
-
// 1. TEXT/I-BEAM CURSOR - narrow ratio, center hotspot
|
|
1807
|
-
if (aspectRatio >= 0.45 && aspectRatio <= 0.60 && // Narrow (0.50 typical)
|
|
1808
|
-
relativeHotspotX >= 0.35 && relativeHotspotX <= 0.55 && // Center X (0.44 typical)
|
|
1809
|
-
relativeHotspotY >= 0.40 && relativeHotspotY <= 0.60) { // Center Y (0.50 typical)
|
|
1810
|
-
BOOL directSaysText = (directType && [directType isEqualToString:@"text"]);
|
|
1811
|
-
if (cursorNameSuggestsText || directSaysText) {
|
|
1812
|
-
cursorType = @"text";
|
|
1813
|
-
} else {
|
|
1814
|
-
cursorType = fallbackType ?: @"default";
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
// 2. ARROW CURSOR - medium ratio, top-left hotspot
|
|
1818
|
-
else if (aspectRatio >= 0.65 && aspectRatio <= 0.85 && // Medium (0.74 typical)
|
|
1819
|
-
relativeHotspotX >= 0.15 && relativeHotspotX <= 0.35 && // Left side (0.24 typical)
|
|
1820
|
-
relativeHotspotY >= 0.10 && relativeHotspotY <= 0.25) { // Top area (0.17 typical)
|
|
1821
|
-
cursorType = @"default";
|
|
1822
|
-
}
|
|
1823
|
-
// 3. POINTER CURSOR - square ratio, left-center hotspot
|
|
1824
|
-
else if (aspectRatio >= 0.90 && aspectRatio <= 1.10 && // Square (1.00 typical)
|
|
1825
|
-
relativeHotspotX >= 0.30 && relativeHotspotX <= 0.50 && // Left-center (0.41 typical)
|
|
1826
|
-
relativeHotspotY >= 0.15 && relativeHotspotY <= 0.35) { // Upper area (0.25 typical)
|
|
1827
|
-
cursorType = @"pointer";
|
|
1828
|
-
}
|
|
1829
|
-
else {
|
|
1830
|
-
// Try to use a different approach - cursor name introspection
|
|
1831
|
-
NSString *derived = cursorTypeFromNSCursor(currentCursor);
|
|
1832
|
-
if (derived && ![derived isEqualToString:@"default"]) {
|
|
1833
|
-
cursorType = derived;
|
|
1834
|
-
// NSLog(@"🎯 DERIVED FROM ANALYSIS: %@", cursorType);
|
|
1835
|
-
} else {
|
|
1836
|
-
cursorType = fallbackType ?: @"default";
|
|
1837
|
-
// NSLog(@"🎯 FALLBACK TO DEFAULT (will check AX)");
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
if (cursorType && ![cursorType isEqualToString:@"default"]) {
|
|
1842
|
-
CacheCursorFingerprint(currentCursor, cursorType, nil);
|
|
1843
|
-
}
|
|
1844
|
-
} else {
|
|
1845
|
-
// NSLog(@"🖱️ No current cursor found");
|
|
1846
|
-
cursorType = @"default";
|
|
1256
|
+
NSString *name = CopyCurrentCursorNameFromCGS();
|
|
1257
|
+
NSString *mapped = name ? [g_cursorNameMap objectForKey:NormalizeCursorName(name)] : nil;
|
|
1258
|
+
cursorType = mapped ?: cursorTypeFromCursorName(name);
|
|
1259
|
+
// currentCursor belongs to this process. It must never replace another
|
|
1260
|
+
// application's system cursor while the recorder runs in the background.
|
|
1261
|
+
if (!cursorType && NSApp.isActive) {
|
|
1262
|
+
cursorType = cursorTypeFromNSCursor([NSCursor currentCursor]);
|
|
1847
1263
|
}
|
|
1848
1264
|
};
|
|
1849
|
-
|
|
1850
1265
|
if ([NSThread isMainThread]) {
|
|
1851
1266
|
fetchCursorBlock();
|
|
1852
1267
|
} else {
|
|
1853
1268
|
dispatch_sync(dispatch_get_main_queue(), fetchCursorBlock);
|
|
1854
1269
|
}
|
|
1855
|
-
|
|
1856
|
-
if (cursorType && ![cursorType isEqualToString:@"default"] && cursorSeed > 0) {
|
|
1857
|
-
addCursorToSeedMap(cursorType, cursorSeed);
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
return cursorType;
|
|
1270
|
+
return cursorType ?: @"default";
|
|
1861
1271
|
}
|
|
1862
1272
|
|
|
1863
1273
|
// Desktop'ta SVG karşılığı olmayan cursor tiplerini desteklenen tiplere normalize et
|
|
@@ -1950,6 +1360,7 @@ NSString* getCursorType() {
|
|
|
1950
1360
|
} else {
|
|
1951
1361
|
NSLog(@"🎯 %@", finalType);
|
|
1952
1362
|
}
|
|
1363
|
+
[lastLoggedType release];
|
|
1953
1364
|
lastLoggedType = [finalType copy];
|
|
1954
1365
|
}
|
|
1955
1366
|
return finalType;
|