node-mac-recorder 2.22.24 → 2.22.33

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.
@@ -7,8 +7,8 @@
7
7
  #import <Accessibility/Accessibility.h>
8
8
  #import <dispatch/dispatch.h>
9
9
  #import "logging.h"
10
- #import "text_input_ax_snapshot.h"
11
10
  #include <vector>
11
+ #include <cstring>
12
12
  #include <math.h>
13
13
 
14
14
  #ifndef kAXHitTestParameterizedAttribute
@@ -310,6 +310,180 @@ static NSCursor* CursorFromSelector(SEL selector) {
310
310
  return func([NSCursor class], selector);
311
311
  }
312
312
 
313
+ static BOOL CursorEqualsFactoryNamed(NSCursor *cursor, NSString *factoryMethodName) {
314
+ if (!cursor || !factoryMethodName || [factoryMethodName length] == 0) {
315
+ return NO;
316
+ }
317
+ SEL sel = NSSelectorFromString(factoryMethodName);
318
+ NSCursor *ref = CursorFromSelector(sel);
319
+ return ref != nil && cursor == ref;
320
+ }
321
+
322
+ static uint64_t CursorImagePixelHash(NSImage *image) {
323
+ if (!image) {
324
+ return 0;
325
+ }
326
+ NSRect imageRect = NSMakeRect(0, 0, [image size].width, [image size].height);
327
+ CGImageRef cgImage = [image CGImageForProposedRect:&imageRect context:nil hints:nil];
328
+ if (!cgImage) {
329
+ for (NSImageRep *rep in [image representations]) {
330
+ if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
331
+ cgImage = [(NSBitmapImageRep *)rep CGImage];
332
+ if (cgImage) {
333
+ break;
334
+ }
335
+ }
336
+ }
337
+ }
338
+ if (!cgImage) {
339
+ return 0;
340
+ }
341
+ size_t width = CGImageGetWidth(cgImage);
342
+ size_t height = CGImageGetHeight(cgImage);
343
+ if (width == 0 || height == 0) {
344
+ return 0;
345
+ }
346
+ size_t bytesPerPixel = 4;
347
+ size_t bytesPerRow = width * bytesPerPixel;
348
+ size_t bufferSize = bytesPerRow * height;
349
+ if (bufferSize == 0) {
350
+ return 0;
351
+ }
352
+ std::vector<unsigned char> buffer(bufferSize);
353
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
354
+ if (!colorSpace) {
355
+ return 0;
356
+ }
357
+ CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Little | (CGBitmapInfo)kCGImageAlphaPremultipliedLast;
358
+ CGContextRef context = CGBitmapContextCreate(buffer.data(),
359
+ width,
360
+ height,
361
+ 8,
362
+ bytesPerRow,
363
+ colorSpace,
364
+ bitmapInfo);
365
+ CGColorSpaceRelease(colorSpace);
366
+ if (!context) {
367
+ return 0;
368
+ }
369
+ CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
370
+ CGContextRelease(context);
371
+ return FNV1AHash(buffer.data(), buffer.size());
372
+ }
373
+
374
+ static uint64_t CursorImagePixelHashNormalized32(NSImage *image) {
375
+ if (!image) {
376
+ return 0;
377
+ }
378
+ const int dim = 32;
379
+ size_t bytesPerPixel = 4;
380
+ size_t bytesPerRow = (size_t)dim * bytesPerPixel;
381
+ std::vector<unsigned char> buffer(bytesPerRow * (size_t)dim);
382
+ memset(buffer.data(), 0, buffer.size());
383
+
384
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
385
+ if (!colorSpace) {
386
+ return 0;
387
+ }
388
+ CGContextRef ctx = CGBitmapContextCreate(buffer.data(),
389
+ (size_t)dim,
390
+ (size_t)dim,
391
+ 8,
392
+ bytesPerRow,
393
+ colorSpace,
394
+ kCGBitmapByteOrder32Little | (CGBitmapInfo)kCGImageAlphaPremultipliedLast);
395
+ CGColorSpaceRelease(colorSpace);
396
+ if (!ctx) {
397
+ return 0;
398
+ }
399
+
400
+ CGContextClearRect(ctx, CGRectMake(0, 0, dim, dim));
401
+ NSGraphicsContext *nsgc = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:YES];
402
+ [NSGraphicsContext saveGraphicsState];
403
+ [NSGraphicsContext setCurrentContext:nsgc];
404
+
405
+ NSSize sz = [image size];
406
+ if (sz.width <= 0 || sz.height <= 0) {
407
+ [NSGraphicsContext restoreGraphicsState];
408
+ CGContextRelease(ctx);
409
+ return 0;
410
+ }
411
+ CGFloat scale = MIN((CGFloat)dim / sz.width, (CGFloat)dim / sz.height);
412
+ CGFloat rw = sz.width * scale;
413
+ CGFloat rh = sz.height * scale;
414
+ CGFloat ox = ((CGFloat)dim - rw) / 2.0;
415
+ CGFloat oy = ((CGFloat)dim - rh) / 2.0;
416
+ [image drawInRect:NSMakeRect(ox, oy, rw, rh)
417
+ fromRect:NSZeroRect
418
+ operation:NSCompositingOperationSourceOver
419
+ fraction:1.0
420
+ respectFlipped:YES
421
+ hints:nil];
422
+
423
+ [NSGraphicsContext restoreGraphicsState];
424
+ CGContextRelease(ctx);
425
+ return FNV1AHash(buffer.data(), buffer.size());
426
+ }
427
+
428
+ static NSString* DiagonalResizeTypeByVisualMatch(NSCursor *cursor) {
429
+ if (!cursor) {
430
+ return nil;
431
+ }
432
+ if (CursorEqualsFactoryNamed(cursor, @"resizeNorthWestSouthEastCursor")) {
433
+ return @"nwse-resize";
434
+ }
435
+ if (CursorEqualsFactoryNamed(cursor, @"resizeNorthEastSouthWestCursor")) {
436
+ return @"nesw-resize";
437
+ }
438
+
439
+ NSImage *curImg = [cursor image];
440
+ if (!curImg) {
441
+ return nil;
442
+ }
443
+
444
+ NSData *curTIFF = [curImg TIFFRepresentation];
445
+ uint64_t curRaw = CursorImagePixelHash(curImg);
446
+ uint64_t curNorm = CursorImagePixelHashNormalized32(curImg);
447
+
448
+ struct DiagonalFactoryEntry {
449
+ const char *selectorName;
450
+ const char *type;
451
+ };
452
+ static const DiagonalFactoryEntry kDiagonalFactories[] = {
453
+ { "resizeNorthWestSouthEastCursor", "nwse-resize" },
454
+ { "resizeNorthEastSouthWestCursor", "nesw-resize" },
455
+ };
456
+
457
+ for (size_t i = 0; i < sizeof(kDiagonalFactories) / sizeof(kDiagonalFactories[0]); i++) {
458
+ NSString *selStr = [NSString stringWithUTF8String:kDiagonalFactories[i].selectorName];
459
+ SEL sel = NSSelectorFromString(selStr);
460
+ if (![NSCursor respondsToSelector:sel]) {
461
+ continue;
462
+ }
463
+ NSCursor *ref = CursorFromSelector(sel);
464
+ if (!ref) {
465
+ continue;
466
+ }
467
+ NSImage *refImg = [ref image];
468
+ if (!refImg) {
469
+ continue;
470
+ }
471
+ NSData *refTIFF = [refImg TIFFRepresentation];
472
+ if (curTIFF && refTIFF && [curTIFF isEqualToData:refTIFF]) {
473
+ return [NSString stringWithUTF8String:kDiagonalFactories[i].type];
474
+ }
475
+ uint64_t refRaw = CursorImagePixelHash(refImg);
476
+ if (curRaw != 0 && curRaw == refRaw) {
477
+ return [NSString stringWithUTF8String:kDiagonalFactories[i].type];
478
+ }
479
+ uint64_t refNorm = CursorImagePixelHashNormalized32(refImg);
480
+ if (curNorm != 0 && curNorm == refNorm) {
481
+ return [NSString stringWithUTF8String:kDiagonalFactories[i].type];
482
+ }
483
+ }
484
+ return nil;
485
+ }
486
+
313
487
  static void AddStandardCursorFingerprint(NSCursor *cursor, NSString *cursorType) {
314
488
  if (!cursor || !cursorType) {
315
489
  return;
@@ -360,7 +534,7 @@ static void InitializeCursorFingerprintMap(void) {
360
534
  AddCursorIfAvailable(@selector(dragCopyCursor), @"copy");
361
535
  AddCursorIfAvailable(@selector(dragLinkCursor), @"alias");
362
536
  AddCursorIfAvailable(@selector(resizeLeftRightCursor), @"col-resize");
363
- AddCursorIfAvailable(@selector(resizeUpDownCursor), @"row-resize");
537
+ AddCursorIfAvailable(@selector(resizeUpDownCursor), @"ns-resize");
364
538
  AddCursorIfAvailableByName(@"resizeLeftCursor", @"col-resize");
365
539
  AddCursorIfAvailableByName(@"resizeRightCursor", @"col-resize");
366
540
  AddCursorIfAvailableByName(@"resizeUpCursor", @"ns-resize");
@@ -503,21 +677,26 @@ static BOOL ShouldEmitCursorEvent(CGPoint location, NSString *cursorType, NSStri
503
677
  BOOL moved = fabs(location.x - g_lastCursorLocation.x) >= movementThreshold ||
504
678
  fabs(location.y - g_lastCursorLocation.y) >= movementThreshold;
505
679
  BOOL eventChanged = !StringsEqual(eventType, g_lastCursorEventType);
506
- BOOL isMoveEvent = StringsEqual(eventType, @"move") || StringsEqual(eventType, @"drag");
680
+ BOOL isDragEvent = StringsEqual(eventType, @"drag") || StringsEqual(eventType, @"rightdrag");
681
+ BOOL isMoveOnly = StringsEqual(eventType, @"move");
507
682
  BOOL isClickEvent = StringsEqual(eventType, @"mousedown") ||
508
683
  StringsEqual(eventType, @"mouseup") ||
509
684
  StringsEqual(eventType, @"rightmousedown") ||
510
685
  StringsEqual(eventType, @"rightmouseup");
511
686
 
512
- if (isMoveEvent) {
513
- return moved;
687
+ if (isDragEvent) {
688
+ return YES;
689
+ }
690
+
691
+ if (isMoveOnly) {
692
+ BOOL cursorChanged = !StringsEqual(cursorType, g_lastCursorType);
693
+ return moved || cursorChanged;
514
694
  }
515
695
 
516
696
  if (isClickEvent) {
517
697
  return eventChanged || moved;
518
698
  }
519
699
 
520
- // Fallback: only emit when something actually changed
521
700
  BOOL cursorChanged = !StringsEqual(cursorType, g_lastCursorType);
522
701
  return moved || cursorChanged || eventChanged;
523
702
  }
@@ -671,7 +850,10 @@ static NSString* CursorTypeForWindowBorder(AXUIElementRef element, CGPoint curso
671
850
  CFRelease(positionValue);
672
851
  CFRelease(sizeValue);
673
852
 
674
- CGFloat edge = 4.0;
853
+ // Köşe: köşe noktasına uzaklık (kutu sınırında col ↔ diagonal sıçramasını azaltır).
854
+ const CGFloat cornerRadius = 9.0;
855
+ const CGFloat edgeInset = 12.0;
856
+
675
857
  CGFloat x = cursorPos.x - windowOrigin.x;
676
858
  CGFloat y = cursorPos.y - windowOrigin.y;
677
859
  CGFloat w = windowSize.width;
@@ -681,17 +863,44 @@ static NSString* CursorTypeForWindowBorder(AXUIElementRef element, CGPoint curso
681
863
  return nil;
682
864
  }
683
865
 
684
- BOOL nearLeft = (x >= 0 && x <= edge);
685
- BOOL nearRight = (x >= w - edge && x <= w);
686
- BOOL nearTop = (y >= 0 && y <= edge);
687
- BOOL nearBottom = (y >= h - edge && y <= h);
866
+ if (w < cornerRadius * 2.1 || h < cornerRadius * 2.1) {
867
+ CGFloat edge = edgeInset;
868
+ BOOL nearLeft = (x >= 0 && x <= edge);
869
+ BOOL nearRight = (x >= w - edge && x <= w);
870
+ BOOL nearTop = (y >= 0 && y <= edge);
871
+ BOOL nearBottom = (y >= h - edge && y <= h);
872
+ if ((nearLeft && nearTop) || (nearRight && nearBottom)) {
873
+ return @"nwse-resize";
874
+ }
875
+ if ((nearRight && nearTop) || (nearLeft && nearBottom)) {
876
+ return @"nesw-resize";
877
+ }
878
+ if (nearLeft || nearRight) {
879
+ return @"col-resize";
880
+ }
881
+ if (nearTop || nearBottom) {
882
+ return @"ns-resize";
883
+ }
884
+ return nil;
885
+ }
886
+
887
+ CGFloat dBottomLeft = hypot(x, y);
888
+ CGFloat dTopRight = hypot(w - x, h - y);
889
+ CGFloat dBottomRight = hypot(w - x, y);
890
+ CGFloat dTopLeft = hypot(x, h - y);
688
891
 
689
- if ((nearLeft && nearTop) || (nearRight && nearBottom)) {
892
+ if (dBottomLeft <= cornerRadius || dTopRight <= cornerRadius) {
690
893
  return @"nwse-resize";
691
894
  }
692
- if ((nearRight && nearTop) || (nearLeft && nearBottom)) {
895
+ if (dBottomRight <= cornerRadius || dTopLeft <= cornerRadius) {
693
896
  return @"nesw-resize";
694
897
  }
898
+
899
+ BOOL nearLeft = (x >= 0 && x <= edgeInset);
900
+ BOOL nearRight = (x >= w - edgeInset && x <= w);
901
+ BOOL nearTop = (y >= 0 && y <= edgeInset);
902
+ BOOL nearBottom = (y >= h - edgeInset && y <= h);
903
+
695
904
  if (nearLeft || nearRight) {
696
905
  return @"col-resize";
697
906
  }
@@ -702,6 +911,42 @@ static NSString* CursorTypeForWindowBorder(AXUIElementRef element, CGPoint curso
702
911
  return nil;
703
912
  }
704
913
 
914
+ static NSString* ResizeTypeFromWindowUnderCursor(CGPoint cursorPos) {
915
+ @autoreleasepool {
916
+ AXUIElementRef systemWide = AXUIElementCreateSystemWide();
917
+ if (!systemWide) {
918
+ return nil;
919
+ }
920
+ AXUIElementRef hit = NULL;
921
+ AXError err = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &hit);
922
+ CFRelease(systemWide);
923
+ if (err != kAXErrorSuccess || !hit) {
924
+ if (hit) {
925
+ CFRelease(hit);
926
+ }
927
+ return nil;
928
+ }
929
+
930
+ AXUIElementRef node = hit;
931
+ for (int depth = 0; depth < 24 && node; depth++) {
932
+ NSString *role = CopyAttributeString(node, kAXRoleAttribute);
933
+ if ([role isEqualToString:@"AXWindow"]) {
934
+ NSString *borderType = CursorTypeForWindowBorder(node, cursorPos);
935
+ CFRelease(node);
936
+ return borderType;
937
+ }
938
+ AXUIElementRef parent = NULL;
939
+ AXError perr = AXUIElementCopyAttributeValue(node, kAXParentAttribute, (CFTypeRef *)&parent);
940
+ CFRelease(node);
941
+ node = NULL;
942
+ if (perr == kAXErrorSuccess && parent) {
943
+ node = parent;
944
+ }
945
+ }
946
+ return nil;
947
+ }
948
+ }
949
+
705
950
  static NSString* CursorTypeFromAccessibilityElement(AXUIElementRef element, CGPoint cursorPos) {
706
951
  if (!element) {
707
952
  return nil;
@@ -815,10 +1060,6 @@ static bool g_leftMouseDown = false;
815
1060
  static bool g_rightMouseDown = false;
816
1061
  static NSString *g_lastEventType = @"move";
817
1062
 
818
- // Text input (keyboard) tracking state
819
- static NSTimeInterval g_lastTextInputEmitTime = 0; // Throttle: son textInput event zamanı
820
- static const NSTimeInterval TEXT_INPUT_THROTTLE_MS = 50; // Min 50ms aralık (20 FPS)
821
-
822
1063
  // Accessibility tabanlı cursor tip tespiti
823
1064
  static NSString* detectCursorTypeUsingAccessibility(CGPoint cursorPos) {
824
1065
  @autoreleasepool {
@@ -942,6 +1183,14 @@ static NSString* cursorTypeFromCursorName(NSString *value) {
942
1183
  return @"zoom-in";
943
1184
  }
944
1185
 
1186
+ // CGS / tema kısa diagonal tokenları (nwse / nesw)
1187
+ if ([normalized containsString:@"nesw"]) {
1188
+ return @"nesw-resize";
1189
+ }
1190
+ if ([normalized containsString:@"nwse"]) {
1191
+ return @"nwse-resize";
1192
+ }
1193
+
945
1194
  // All-scroll pattern (move in all directions)
946
1195
  if ([normalized containsString:@"all-scroll"] ||
947
1196
  [normalized containsString:@"allscroll"] ||
@@ -1388,6 +1637,13 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
1388
1637
  CGFloat relativeX = width > 0 ? hotspot.x / width : 0;
1389
1638
  CGFloat relativeY = height > 0 ? hotspot.y / height : 0;
1390
1639
 
1640
+ if (cursor) {
1641
+ NSString *diag = DiagonalResizeTypeByVisualMatch(cursor);
1642
+ if (diag) {
1643
+ return diag;
1644
+ }
1645
+ }
1646
+
1391
1647
  // Tolerance for floating point comparison
1392
1648
  CGFloat tolerance = 0.05;
1393
1649
  CGFloat tightTolerance = 0.02; // For precise hotspot matching
@@ -1427,29 +1683,35 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
1427
1683
  // Distinguished by pointer equality
1428
1684
  if (approx(width, 32) && approx(height, 32) && approx(relativeY, 0.531)) {
1429
1685
  if (cursor) {
1430
- if (cursor == [NSCursor closedHandCursor]) {
1686
+ if ([NSCursor respondsToSelector:@selector(closedHandCursor)] &&
1687
+ cursor == [NSCursor closedHandCursor]) {
1431
1688
  return @"grabbing";
1432
1689
  }
1433
- if (cursor == [NSCursor openHandCursor]) {
1690
+ if ([NSCursor respondsToSelector:@selector(openHandCursor)] &&
1691
+ cursor == [NSCursor openHandCursor]) {
1434
1692
  return @"grab";
1435
1693
  }
1436
1694
  }
1437
1695
  return @"grab"; // Default to grab if can't distinguish
1438
1696
  }
1439
1697
 
1440
- // 24x24 cursors: crosshair vs move/all-scroll
1441
- // Distinguished by precise hotspot position
1698
+ // 24x24: köşe diagonal resize ile crosshair/move aynı boyutta — önce diagonal fabrika
1442
1699
  if (approx(width, 24) && approx(height, 24)) {
1700
+ if (cursor) {
1701
+ NSString *diag = DiagonalResizeTypeByVisualMatch(cursor);
1702
+ if (diag) {
1703
+ return diag;
1704
+ }
1705
+ }
1443
1706
  // crosshair: hotspot rel=(0.458, 0.458)
1444
1707
  if (approxTight(relativeX, 0.458) && approxTight(relativeY, 0.458)) {
1445
1708
  return @"crosshair";
1446
1709
  }
1447
1710
  // move/all-scroll: hotspot rel=(0.5, 0.5)
1448
1711
  if (approxTight(relativeX, 0.5) && approxTight(relativeY, 0.5)) {
1449
- return @"move"; // or all-scroll, they're identical
1712
+ return @"move";
1450
1713
  }
1451
- // Fallback for 24x24
1452
- return @"crosshair";
1714
+ return nil;
1453
1715
  }
1454
1716
 
1455
1717
  // help/cell: 18x18, ratio=1.0, hotspot rel=(0.5, 0.5)
@@ -1510,7 +1772,29 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
1510
1772
 
1511
1773
  // ne-resize/nw-resize/se-resize/sw-resize/nesw-resize/nwse-resize: 22x22, ratio=1.0, hotspot rel=(0.5, 0.5)
1512
1774
  if (approx(width, 22) && approx(height, 22)) {
1513
- return @"nwse-resize"; // Default to nwse-resize for all diagonal cursors
1775
+ if (cursor) {
1776
+ NSString *diag = DiagonalResizeTypeByVisualMatch(cursor);
1777
+ if (diag) {
1778
+ return diag;
1779
+ }
1780
+ if ([NSCursor respondsToSelector:@selector(resizeUpCursor)] &&
1781
+ cursor == [NSCursor resizeUpCursor]) {
1782
+ return @"n-resize";
1783
+ }
1784
+ if ([NSCursor respondsToSelector:@selector(resizeDownCursor)] &&
1785
+ cursor == [NSCursor resizeDownCursor]) {
1786
+ return @"s-resize";
1787
+ }
1788
+ if ([NSCursor respondsToSelector:@selector(resizeLeftCursor)] &&
1789
+ cursor == [NSCursor resizeLeftCursor]) {
1790
+ return @"w-resize";
1791
+ }
1792
+ if ([NSCursor respondsToSelector:@selector(resizeRightCursor)] &&
1793
+ cursor == [NSCursor resizeRightCursor]) {
1794
+ return @"e-resize";
1795
+ }
1796
+ }
1797
+ return nil;
1514
1798
  }
1515
1799
 
1516
1800
  // zoom-in/zoom-out: 28x26, ratio=1.077, hotspot rel=(0.428, 0.423)
@@ -1532,10 +1816,12 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
1532
1816
  if (approxTight(relativeX, 0.161) && approxTight(relativeY, 0.1)) {
1533
1817
  return @"default";
1534
1818
  }
1535
- // context-menu/progress/wait/copy/no-drop/not-allowed: hotspot rel=(0.179, 0.125) - hotspot at (5, 5)
1819
+ // Ortak 28x40 ok/beachball hotspot bandı: spinner ile karışmasın diye önce ok
1536
1820
  if (approxTight(relativeX, 0.179) && approxTight(relativeY, 0.125)) {
1537
- // Try pointer equality for standard cursors
1538
1821
  if (cursor) {
1822
+ if (cursor == [NSCursor arrowCursor]) {
1823
+ return @"default";
1824
+ }
1539
1825
  if (cursor == [NSCursor contextualMenuCursor]) {
1540
1826
  return @"context-menu";
1541
1827
  }
@@ -1546,10 +1832,7 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
1546
1832
  return @"not-allowed";
1547
1833
  }
1548
1834
  }
1549
- // NOTE: progress, wait, no-drop don't have standard NSCursor pointers
1550
- // Return "progress" as default for this hotspot pattern (better than "default")
1551
- // Let cursor name detection in caller distinguish between progress/wait
1552
- return @"progress";
1835
+ return @"default";
1553
1836
  }
1554
1837
  return @"default";
1555
1838
  }
@@ -1576,13 +1859,16 @@ static NSString* cursorTypeFromNSCursor(NSCursor *cursor) {
1576
1859
  if (cursor == [NSCursor pointingHandCursor]) {
1577
1860
  return @"pointer";
1578
1861
  }
1579
- if (cursor == [NSCursor crosshairCursor]) {
1862
+ if ([NSCursor respondsToSelector:@selector(crosshairCursor)] &&
1863
+ cursor == [NSCursor crosshairCursor]) {
1580
1864
  return @"crosshair";
1581
1865
  }
1582
- if (cursor == [NSCursor openHandCursor]) {
1866
+ if ([NSCursor respondsToSelector:@selector(openHandCursor)] &&
1867
+ cursor == [NSCursor openHandCursor]) {
1583
1868
  return @"grab";
1584
1869
  }
1585
- if (cursor == [NSCursor closedHandCursor]) {
1870
+ if ([NSCursor respondsToSelector:@selector(closedHandCursor)] &&
1871
+ cursor == [NSCursor closedHandCursor]) {
1586
1872
  return @"grabbing";
1587
1873
  }
1588
1874
  if (cursor == [NSCursor operationNotAllowedCursor]) {
@@ -1598,14 +1884,42 @@ static NSString* cursorTypeFromNSCursor(NSCursor *cursor) {
1598
1884
  return @"context-menu";
1599
1885
  }
1600
1886
 
1601
- // Resize cursors
1602
- if ([NSCursor respondsToSelector:@selector(resizeLeftRightCursor)]) {
1603
- if (cursor == [NSCursor resizeLeftRightCursor]) {
1887
+ NSString *diagonalVisual = DiagonalResizeTypeByVisualMatch(cursor);
1888
+ if (diagonalVisual) {
1889
+ return diagonalVisual;
1890
+ }
1891
+
1892
+ if ([NSCursor respondsToSelector:@selector(resizeLeftCursor)] &&
1893
+ cursor == [NSCursor resizeLeftCursor]) {
1894
+ return @"col-resize";
1895
+ }
1896
+ if ([NSCursor respondsToSelector:@selector(resizeRightCursor)] &&
1897
+ cursor == [NSCursor resizeRightCursor]) {
1898
+ return @"col-resize";
1899
+ }
1900
+ if ([NSCursor respondsToSelector:@selector(resizeUpCursor)] &&
1901
+ cursor == [NSCursor resizeUpCursor]) {
1902
+ return @"n-resize";
1903
+ }
1904
+ if ([NSCursor respondsToSelector:@selector(resizeDownCursor)] &&
1905
+ cursor == [NSCursor resizeDownCursor]) {
1906
+ return @"s-resize";
1907
+ }
1908
+ if ([NSCursor respondsToSelector:@selector(resizeLeftRightCursor)] &&
1909
+ cursor == [NSCursor resizeLeftRightCursor]) {
1910
+ return @"col-resize";
1911
+ }
1912
+ if ([NSCursor respondsToSelector:@selector(resizeUpDownCursor)] &&
1913
+ cursor == [NSCursor resizeUpDownCursor]) {
1914
+ return @"ns-resize";
1915
+ }
1916
+ if (@available(macOS 15.0, *)) {
1917
+ if ([NSCursor respondsToSelector:@selector(columnResizeCursor)] &&
1918
+ cursor == [NSCursor columnResizeCursor]) {
1604
1919
  return @"col-resize";
1605
1920
  }
1606
- }
1607
- if ([NSCursor respondsToSelector:@selector(resizeUpDownCursor)]) {
1608
- if (cursor == [NSCursor resizeUpDownCursor]) {
1921
+ if ([NSCursor respondsToSelector:@selector(rowResizeCursor)] &&
1922
+ cursor == [NSCursor rowResizeCursor]) {
1609
1923
  return @"row-resize";
1610
1924
  }
1611
1925
  }
@@ -1677,12 +1991,6 @@ static NSString* detectSystemCursorType(void) {
1677
1991
  }
1678
1992
 
1679
1993
  int cursorSeed = SafeCGSCurrentCursorSeed();
1680
- if (cursorSeed > 0) {
1681
- NSString *seedType = cursorTypeFromSeed(cursorSeed);
1682
- if (seedType) {
1683
- return seedType;
1684
- }
1685
- }
1686
1994
 
1687
1995
  void (^fetchCursorBlock)(void) = ^{
1688
1996
  NSCursor *currentCursor = nil;
@@ -1786,14 +2094,23 @@ static NSString* detectSystemCursorType(void) {
1786
2094
  dispatch_sync(dispatch_get_main_queue(), fetchCursorBlock);
1787
2095
  }
1788
2096
 
1789
- if (cursorType && ![cursorType isEqualToString:@"default"] && cursorSeed > 0) {
1790
- addCursorToSeedMap(cursorType, cursorSeed);
2097
+ if (cursorType && ![cursorType isEqualToString:@"default"]) {
2098
+ if (cursorSeed > 0) {
2099
+ addCursorToSeedMap(cursorType, cursorSeed);
2100
+ }
2101
+ return cursorType;
1791
2102
  }
1792
2103
 
1793
- return cursorType;
2104
+ if (cursorSeed > 0) {
2105
+ NSString *seedType = cursorTypeFromSeed(cursorSeed);
2106
+ if (seedType) {
2107
+ return seedType;
2108
+ }
2109
+ }
2110
+
2111
+ return cursorType ?: @"default";
1794
2112
  }
1795
2113
 
1796
- // Desktop'ta SVG karşılığı olmayan cursor tiplerini desteklenen tiplere normalize et
1797
2114
  static NSString* normalizeCursorTypeForDesktop(NSString *cursorType) {
1798
2115
  if (!cursorType || [cursorType length] == 0) {
1799
2116
  return @"default";
@@ -1891,11 +2208,23 @@ NSString* getCursorType() {
1891
2208
  int currentSeed = SafeCGSCurrentCursorSeed();
1892
2209
  g_lastCursorSeed = currentSeed; // Save for getCursorPosition()
1893
2210
 
1894
- // Use cursorTypeFromNSCursor for detection (pointer equality + image-based)
1895
- // DO NOT use accessibility detection as it's unreliable and causes false positives
2211
+ // Önce NSCursor/CGS; köşe diagonal için sistem imleci güvenilir olmayabiliyor — AX ile pencere çerçevesi düzeltmesi
1896
2212
  NSString *systemCursorType = detectSystemCursorType();
1897
2213
  NSString *rawType = systemCursorType && [systemCursorType length] > 0 ? systemCursorType : @"default";
1898
2214
 
2215
+ __block NSString *chromeResize = nil;
2216
+ if ([NSThread isMainThread]) {
2217
+ chromeResize = ResizeTypeFromWindowUnderCursor(cursorPos);
2218
+ } else {
2219
+ dispatch_sync(dispatch_get_main_queue(), ^{
2220
+ chromeResize = ResizeTypeFromWindowUnderCursor(cursorPos);
2221
+ });
2222
+ }
2223
+
2224
+ if (chromeResize) {
2225
+ rawType = chromeResize;
2226
+ }
2227
+
1899
2228
  // Desktop SVG'lerine uyumlu tipe normalize et
1900
2229
  NSString *finalType = normalizeCursorTypeForDesktop(rawType);
1901
2230
 
@@ -1947,29 +2276,6 @@ void writeToFile(NSDictionary *cursorData) {
1947
2276
  }
1948
2277
  }
1949
2278
 
1950
- // Text input event: Klavye basıldığında focused text field'in caret pozisyonunu yakala
1951
- static void emitTextInputEvent(NSTimeInterval timestamp, NSTimeInterval unixTimeMs, CGPoint mouseLocation) {
1952
- if (unixTimeMs - g_lastTextInputEmitTime < TEXT_INPUT_THROTTLE_MS) {
1953
- return;
1954
- }
1955
-
1956
- NSDictionary *snap = MRTextInputSnapshotDictionary();
1957
- if (!snap) {
1958
- return;
1959
- }
1960
-
1961
- NSMutableDictionary *textInputInfo = [NSMutableDictionary dictionaryWithDictionary:snap];
1962
- textInputInfo[@"x"] = @((int)mouseLocation.x);
1963
- textInputInfo[@"y"] = @((int)mouseLocation.y);
1964
- textInputInfo[@"timestamp"] = @(timestamp);
1965
- textInputInfo[@"unixTimeMs"] = @(unixTimeMs);
1966
- textInputInfo[@"cursorType"] = @"text";
1967
- textInputInfo[@"type"] = @"textInput";
1968
-
1969
- writeToFile(textInputInfo);
1970
- g_lastTextInputEmitTime = unixTimeMs;
1971
- }
1972
-
1973
2279
  // Event callback for mouse events
1974
2280
  CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
1975
2281
  @autoreleasepool {
@@ -1996,23 +2302,6 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
1996
2302
  // Event tipini belirle
1997
2303
  switch (type) {
1998
2304
  case kCGEventLeftMouseDown:
1999
- eventType = @"mousedown";
2000
- // Odak/caret çoğu uygulamada tıklamadan hemen sonra oluşur; kısa gecikmeyle AX caret yaz.
2001
- // (Sadece tuşta emit edilince ilk tıkta timeline'da textInput olmuyordu.)
2002
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.028 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
2003
- if (!g_isCursorTracking || !g_trackingStartTime || !g_fileHandle) return;
2004
- NSDate *now = [NSDate date];
2005
- NSTimeInterval ts = [now timeIntervalSinceDate:g_trackingStartTime] * 1000.0;
2006
- NSTimeInterval unixMs = [now timeIntervalSince1970] * 1000.0;
2007
- CGPoint loc = CGPointZero;
2008
- CGEventRef posEv = CGEventCreate(NULL);
2009
- if (posEv) {
2010
- loc = CGEventGetLocation(posEv);
2011
- CFRelease(posEv);
2012
- }
2013
- emitTextInputEvent(ts, unixMs, loc);
2014
- });
2015
- break;
2016
2305
  case kCGEventRightMouseDown:
2017
2306
  case kCGEventOtherMouseDown:
2018
2307
  eventType = @"mousedown";
@@ -2027,10 +2316,6 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
2027
2316
  case kCGEventOtherMouseDragged:
2028
2317
  eventType = @"drag";
2029
2318
  break;
2030
- case kCGEventKeyDown:
2031
- // Klavye event'i — text caret tracking için
2032
- emitTextInputEvent(timestamp, unixTimeMs, location);
2033
- return event; // Mouse event olarak işleme, ayrı handle edildi
2034
2319
  case kCGEventMouseMoved:
2035
2320
  default:
2036
2321
  eventType = @"move";
@@ -2040,7 +2325,7 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
2040
2325
  if (!ShouldEmitCursorEvent(location, cursorType, eventType)) {
2041
2326
  return event;
2042
2327
  }
2043
-
2328
+
2044
2329
  // Cursor data oluştur
2045
2330
  NSDictionary *cursorInfo = @{
2046
2331
  @"x": @((int)location.x),
@@ -2050,7 +2335,7 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
2050
2335
  @"cursorType": cursorType,
2051
2336
  @"type": eventType
2052
2337
  };
2053
-
2338
+
2054
2339
  // Direkt dosyaya yaz
2055
2340
  writeToFile(cursorInfo);
2056
2341
  RememberCursorEvent(location, cursorType, eventType);
@@ -2086,13 +2371,6 @@ void cursorTimerCallback() {
2086
2371
  cursorType = @"default";
2087
2372
  }
2088
2373
 
2089
- // Timer-only mod: CGEventTap yokken kCGEventKeyDown gelmez; caret satırları hiç yazılmazdı.
2090
- // I-beam görünürken AX ile periyodik textInput üret (emitTextInputEvent içinde throttle var).
2091
- if ([cursorType isEqualToString:@"text"] ||
2092
- [cursorType isEqualToString:@"vertical-text"]) {
2093
- emitTextInputEvent(timestamp, unixTimeMs, location);
2094
- }
2095
-
2096
2374
  // Mouse button state polling — event tap olmadığında click/drag tespiti
2097
2375
  bool currentLeftMouseDown = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonLeft);
2098
2376
  bool currentRightMouseDown = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonRight);
@@ -2196,7 +2474,6 @@ void cleanupCursorTracking() {
2196
2474
  g_lastDetectedCursorType = nil;
2197
2475
  g_cursorTypeCounter = 0;
2198
2476
  g_isFirstWrite = true;
2199
- g_lastTextInputEmitTime = 0;
2200
2477
  ResetCursorEventHistory();
2201
2478
  }
2202
2479
 
@@ -2237,7 +2514,7 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
2237
2514
  g_trackingStartTime = [NSDate date];
2238
2515
  ResetCursorEventHistory();
2239
2516
 
2240
- // Create event tap for mouse + keyboard events
2517
+ // Create event tap for mouse events
2241
2518
  CGEventMask eventMask = (CGEventMaskBit(kCGEventLeftMouseDown) |
2242
2519
  CGEventMaskBit(kCGEventLeftMouseUp) |
2243
2520
  CGEventMaskBit(kCGEventRightMouseDown) |
@@ -2247,8 +2524,7 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
2247
2524
  CGEventMaskBit(kCGEventMouseMoved) |
2248
2525
  CGEventMaskBit(kCGEventLeftMouseDragged) |
2249
2526
  CGEventMaskBit(kCGEventRightMouseDragged) |
2250
- CGEventMaskBit(kCGEventOtherMouseDragged) |
2251
- CGEventMaskBit(kCGEventKeyDown));
2527
+ CGEventMaskBit(kCGEventOtherMouseDragged));
2252
2528
 
2253
2529
  bool eventTapActive = false;
2254
2530
  g_eventTap = CGEventTapCreate(kCGSessionEventTap,
@@ -2578,33 +2854,6 @@ Napi::Value GetCursorDebugInfo(const Napi::CallbackInfo& info) {
2578
2854
  }
2579
2855
  }
2580
2856
 
2581
- static Napi::Value DictToNapiTextInputSnapshot(Napi::Env env, NSDictionary *snap) {
2582
- Napi::Object o = Napi::Object::New(env);
2583
- NSNumber *cx = snap[@"caretX"];
2584
- NSNumber *cy = snap[@"caretY"];
2585
- o.Set("caretX", Napi::Number::New(env, cx ? [cx doubleValue] : 0));
2586
- o.Set("caretY", Napi::Number::New(env, cy ? [cy doubleValue] : 0));
2587
- NSDictionary *frame = snap[@"inputFrame"];
2588
- Napi::Object fo = Napi::Object::New(env);
2589
- if ([frame isKindOfClass:[NSDictionary class]]) {
2590
- fo.Set("x", Napi::Number::New(env, [frame[@"x"] doubleValue]));
2591
- fo.Set("y", Napi::Number::New(env, [frame[@"y"] doubleValue]));
2592
- fo.Set("width", Napi::Number::New(env, [frame[@"width"] doubleValue]));
2593
- fo.Set("height", Napi::Number::New(env, [frame[@"height"] doubleValue]));
2594
- }
2595
- o.Set("inputFrame", fo);
2596
- return o;
2597
- }
2598
-
2599
- static Napi::Value GetTextInputSnapshot(const Napi::CallbackInfo& info) {
2600
- Napi::Env env = info.Env();
2601
- NSDictionary *snap = MRTextInputSnapshotDictionary();
2602
- if (!snap) {
2603
- return env.Null();
2604
- }
2605
- return DictToNapiTextInputSnapshot(env, snap);
2606
- }
2607
-
2608
2857
  // Export functions
2609
2858
  Napi::Object InitCursorTracker(Napi::Env env, Napi::Object exports) {
2610
2859
  exports.Set("startCursorTracking", Napi::Function::New(env, StartCursorTracking));
@@ -2612,7 +2861,6 @@ Napi::Object InitCursorTracker(Napi::Env env, Napi::Object exports) {
2612
2861
  exports.Set("getCursorPosition", Napi::Function::New(env, GetCursorPosition));
2613
2862
  exports.Set("getCursorTrackingStatus", Napi::Function::New(env, GetCursorTrackingStatus));
2614
2863
  exports.Set("getCursorDebugInfo", Napi::Function::New(env, GetCursorDebugInfo));
2615
- exports.Set("getTextInputSnapshot", Napi::Function::New(env, GetTextInputSnapshot));
2616
2864
 
2617
2865
  return exports;
2618
2866
  }