node-mac-recorder 2.22.24 → 2.22.32

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/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  const { EventEmitter } = require("events");
2
2
  const path = require("path");
3
3
  const fs = require("fs");
4
- const cursorCapturePolling = require("./lib/cursorCapture/polling");
5
4
 
6
5
  // Auto-switch to Electron-safe implementation when running under Electron and binary exists
7
6
  let USE_ELECTRON_SAFE = false;
@@ -1341,17 +1340,365 @@ class MacRecorder extends EventEmitter {
1341
1340
  });
1342
1341
  }
1343
1342
 
1343
+ /**
1344
+ * Event'in kaydedilip kaydedilmeyeceğini belirler
1345
+ */
1346
+ shouldCaptureEvent(currentData) {
1347
+ if (!this.lastCapturedData) {
1348
+ return true; // İlk event
1349
+ }
1350
+
1351
+ const last = this.lastCapturedData;
1352
+
1353
+ // Event type değişmişse
1354
+ if (currentData.type !== last.type) {
1355
+ return true;
1356
+ }
1357
+
1358
+ // Pozisyon değişmişse (minimum 2 pixel tolerans)
1359
+ if (
1360
+ Math.abs(currentData.x - last.x) >= 2 ||
1361
+ Math.abs(currentData.y - last.y) >= 2
1362
+ ) {
1363
+ return true;
1364
+ }
1365
+
1366
+ // Cursor type değişmişse
1367
+ if (currentData.cursorType !== last.cursorType) {
1368
+ return true;
1369
+ }
1370
+
1371
+ // Hiçbir değişiklik yoksa kaydetme
1372
+ return false;
1373
+ }
1374
+
1375
+ /**
1376
+ * Unified cursor capture for all recording types - uses video-relative coordinates
1377
+ * @param {string|number} intervalOrFilepath - Cursor data JSON dosya yolu veya interval
1378
+ * @param {Object} options - Cursor capture seçenekleri
1379
+ * @param {boolean} options.videoRelative - Use video-relative coordinates (recommended)
1380
+ * @param {Object} options.displayInfo - Display information for coordinate transformation
1381
+ * @param {string} options.recordingType - Type of recording: 'display', 'window', 'area'
1382
+ * @param {Object} options.captureArea - Capture area for area recording coordinate transformation
1383
+ * @param {number} options.windowId - Window ID for window recording coordinate transformation
1384
+ * @param {number} options.startTimestamp - Pre-defined start timestamp for synchronization (optional)
1385
+ */
1344
1386
  async startCursorCapture(intervalOrFilepath = 100, options = {}) {
1345
- return cursorCapturePolling.startCursorCapture(
1346
- this,
1347
- nativeBinding,
1348
- intervalOrFilepath,
1349
- options,
1350
- );
1387
+ let filepath;
1388
+ let interval = 20; // Default 50 FPS
1389
+
1390
+ // Parameter parsing: number = interval, string = filepath
1391
+ if (typeof intervalOrFilepath === "number") {
1392
+ interval = Math.max(10, intervalOrFilepath); // Min 10ms
1393
+ filepath = `cursor-data-${Date.now()}.json`;
1394
+ } else if (typeof intervalOrFilepath === "string") {
1395
+ filepath = intervalOrFilepath;
1396
+ } else {
1397
+ throw new Error(
1398
+ "Parameter must be interval (number) or filepath (string)"
1399
+ );
1400
+ }
1401
+
1402
+ if (this.cursorCaptureInterval) {
1403
+ throw new Error("Cursor capture is already running");
1404
+ }
1405
+
1406
+ // SYNC FIX: Use pre-defined timestamp if provided for synchronization
1407
+ const syncStartTime = options.startTimestamp || Date.now();
1408
+
1409
+ // Fetch window bounds for multi-window recording
1410
+ if (options.multiWindowBounds && options.multiWindowBounds.length > 0) {
1411
+ try {
1412
+ const allWindows = await this.getWindows();
1413
+ // Match window IDs and populate bounds
1414
+ for (const windowInfo of options.multiWindowBounds) {
1415
+ const windowData = allWindows.find(w => w.id === windowInfo.windowId);
1416
+ if (windowData) {
1417
+ windowInfo.bounds = {
1418
+ x: windowData.x || 0,
1419
+ y: windowData.y || 0,
1420
+ width: windowData.width || 0,
1421
+ height: windowData.height || 0
1422
+ };
1423
+ }
1424
+ }
1425
+ } catch (error) {
1426
+ console.warn('Failed to fetch window bounds for multi-window cursor tracking:', error.message);
1427
+ }
1428
+ }
1429
+
1430
+ // Use video-relative coordinate system for all recording types
1431
+ if (options.videoRelative && options.displayInfo) {
1432
+ // Calculate video offset based on recording type
1433
+ let videoOffsetX = 0;
1434
+ let videoOffsetY = 0;
1435
+ let videoWidth = options.displayInfo.width || options.displayInfo.logicalWidth;
1436
+ let videoHeight = options.displayInfo.height || options.displayInfo.logicalHeight;
1437
+
1438
+ if (options.recordingType === 'window' && options.windowId) {
1439
+ // For window recording: offset = window position in display
1440
+ if (options.captureArea) {
1441
+ videoOffsetX = options.captureArea.x;
1442
+ videoOffsetY = options.captureArea.y;
1443
+ videoWidth = options.captureArea.width;
1444
+ videoHeight = options.captureArea.height;
1445
+ }
1446
+ } else if (options.recordingType === 'area' && options.captureArea) {
1447
+ // For area recording: offset = area position in display
1448
+ videoOffsetX = options.captureArea.x;
1449
+ videoOffsetY = options.captureArea.y;
1450
+ videoWidth = options.captureArea.width;
1451
+ videoHeight = options.captureArea.height;
1452
+ }
1453
+ // For display recording: offset remains 0,0
1454
+
1455
+ this.cursorDisplayInfo = {
1456
+ displayId: options.displayInfo.displayId || options.displayInfo.id,
1457
+ displayX: options.displayInfo.x || 0,
1458
+ displayY: options.displayInfo.y || 0,
1459
+ displayWidth: options.displayInfo.width || options.displayInfo.logicalWidth,
1460
+ displayHeight: options.displayInfo.height || options.displayInfo.logicalHeight,
1461
+ videoOffsetX: videoOffsetX,
1462
+ videoOffsetY: videoOffsetY,
1463
+ videoWidth: videoWidth,
1464
+ videoHeight: videoHeight,
1465
+ videoRelative: true,
1466
+ recordingType: options.recordingType || 'display',
1467
+ // Store additional context for debugging
1468
+ captureArea: options.captureArea,
1469
+ windowId: options.windowId,
1470
+ // Multi-window bounds for location detection
1471
+ multiWindowBounds: options.multiWindowBounds || null
1472
+ };
1473
+ } else if (this.recordingDisplayInfo) {
1474
+ // Fallback: Use recording display info if available
1475
+ this.cursorDisplayInfo = {
1476
+ ...this.recordingDisplayInfo,
1477
+ displayX: this.recordingDisplayInfo.x || 0,
1478
+ displayY: this.recordingDisplayInfo.y || 0,
1479
+ displayWidth: this.recordingDisplayInfo.width || this.recordingDisplayInfo.logicalWidth,
1480
+ displayHeight: this.recordingDisplayInfo.height || this.recordingDisplayInfo.logicalHeight,
1481
+ videoOffsetX: 0,
1482
+ videoOffsetY: 0,
1483
+ videoWidth: this.recordingDisplayInfo.width || this.recordingDisplayInfo.logicalWidth,
1484
+ videoHeight: this.recordingDisplayInfo.height || this.recordingDisplayInfo.logicalHeight,
1485
+ videoRelative: true,
1486
+ recordingType: options.recordingType || 'display',
1487
+ multiWindowBounds: options.multiWindowBounds || null
1488
+ };
1489
+ } else {
1490
+ // Final fallback: Main display global coordinates
1491
+ try {
1492
+ const displays = await this.getDisplays();
1493
+ const mainDisplay = displays.find((d) => d.isPrimary) || displays[0];
1494
+ if (mainDisplay) {
1495
+ this.cursorDisplayInfo = {
1496
+ displayId: mainDisplay.id,
1497
+ x: mainDisplay.x,
1498
+ y: mainDisplay.y,
1499
+ width: parseInt(mainDisplay.resolution.split("x")[0]),
1500
+ height: parseInt(mainDisplay.resolution.split("x")[1]),
1501
+ multiWindowBounds: options.multiWindowBounds || null
1502
+ };
1503
+ }
1504
+ } catch (error) {
1505
+ console.warn("Main display bilgisi alınamadı:", error.message);
1506
+ this.cursorDisplayInfo = null; // Fallback: global koordinatlar
1507
+ }
1508
+ }
1509
+
1510
+ return new Promise((resolve, reject) => {
1511
+ try {
1512
+ // Dosyayı oluştur ve temizle
1513
+ const fs = require("fs");
1514
+ fs.writeFileSync(filepath, "[");
1515
+
1516
+ this.cursorCaptureFile = filepath;
1517
+ // SYNC FIX: Use synchronized start time for accurate timestamp calculation
1518
+ this.cursorCaptureStartTime = syncStartTime;
1519
+ this.cursorCaptureFirstWrite = true;
1520
+ this.lastCapturedData = null;
1521
+ // Store session timestamp for sync metadata
1522
+ this.cursorCaptureSessionTimestamp = this.sessionTimestamp;
1523
+
1524
+ // JavaScript interval ile polling yap (daha sık - mouse event'leri yakalamak için)
1525
+ this.cursorCaptureInterval = setInterval(() => {
1526
+ try {
1527
+ const position = nativeBinding.getCursorPosition();
1528
+ const timestamp = Date.now() - this.cursorCaptureStartTime;
1529
+
1530
+ // Video-relative coordinate transformation for all recording types
1531
+ let x = position.x;
1532
+ let y = position.y;
1533
+ let coordinateSystem = "global";
1534
+
1535
+ // Apply video-relative transformation for all recording types
1536
+ if (this.cursorDisplayInfo && this.cursorDisplayInfo.videoRelative) {
1537
+ // Step 1: Transform global → display-relative coordinates
1538
+ const displayRelativeX = position.x - this.cursorDisplayInfo.displayX;
1539
+ const displayRelativeY = position.y - this.cursorDisplayInfo.displayY;
1540
+
1541
+ // Step 2: Transform display-relative → video-relative coordinates
1542
+ x = displayRelativeX - this.cursorDisplayInfo.videoOffsetX;
1543
+ y = displayRelativeY - this.cursorDisplayInfo.videoOffsetY;
1544
+ coordinateSystem = "video-relative";
1545
+
1546
+ // Bounds check for video area (don't skip, just note if outside)
1547
+ const outsideVideo = x < 0 || y < 0 ||
1548
+ x >= this.cursorDisplayInfo.videoWidth ||
1549
+ y >= this.cursorDisplayInfo.videoHeight;
1550
+
1551
+ // For debugging - add metadata if cursor is outside video area
1552
+ if (outsideVideo) {
1553
+ coordinateSystem = "video-relative-outside";
1554
+ }
1555
+ }
1556
+
1557
+ const cursorData = {
1558
+ x: x,
1559
+ y: y,
1560
+ timestamp: timestamp,
1561
+ unixTimeMs: Date.now(),
1562
+ cursorType: position.cursorType,
1563
+ type: position.eventType || "move",
1564
+ coordinateSystem: coordinateSystem,
1565
+ // Video-relative metadata for all recording types
1566
+ recordingType: this.cursorDisplayInfo?.recordingType || "display",
1567
+ videoInfo: this.cursorDisplayInfo ? {
1568
+ width: this.cursorDisplayInfo.videoWidth,
1569
+ height: this.cursorDisplayInfo.videoHeight,
1570
+ offsetX: this.cursorDisplayInfo.videoOffsetX,
1571
+ offsetY: this.cursorDisplayInfo.videoOffsetY
1572
+ } : {},
1573
+ displayInfo: this.cursorDisplayInfo ? {
1574
+ displayId: this.cursorDisplayInfo.displayId,
1575
+ width: this.cursorDisplayInfo.displayWidth,
1576
+ height: this.cursorDisplayInfo.displayHeight
1577
+ } : {}
1578
+ };
1579
+
1580
+ // Multi-window location detection with window-relative coordinates
1581
+ if (this.cursorDisplayInfo?.multiWindowBounds && this.cursorDisplayInfo.multiWindowBounds.length > 0) {
1582
+ const location = { hover: null, click: null };
1583
+ let windowRelativeCoords = null;
1584
+
1585
+ // Detect which window the cursor is over
1586
+ for (const windowInfo of this.cursorDisplayInfo.multiWindowBounds) {
1587
+ if (windowInfo.bounds) {
1588
+ const { x: wx, y: wy, width: ww, height: wh } = windowInfo.bounds;
1589
+ // Check if cursor is inside window bounds (using global coordinates)
1590
+ if (position.x >= wx && position.x <= wx + ww &&
1591
+ position.y >= wy && position.y <= wy + wh) {
1592
+ location.hover = windowInfo.windowId;
1593
+
1594
+ // Calculate window-relative coordinates
1595
+ // These coords are relative to the window's top-left corner (0,0)
1596
+ // This allows the desktop app to position cursor correctly
1597
+ // regardless of where the window is placed on canvas
1598
+ windowRelativeCoords = {
1599
+ windowId: windowInfo.windowId,
1600
+ x: position.x - wx,
1601
+ y: position.y - wy,
1602
+ // Also include window dimensions for reference
1603
+ windowWidth: ww,
1604
+ windowHeight: wh
1605
+ };
1606
+
1607
+ // If this is a click/drag event, mark click location
1608
+ // Native eventType values: 'mousedown', 'mouseup', 'drag', 'rightmousedown', 'rightmouseup', 'rightdrag'
1609
+ const eventType = position.eventType || '';
1610
+ if (eventType === 'mousedown' ||
1611
+ eventType === 'mouseup' ||
1612
+ eventType === 'drag' ||
1613
+ eventType === 'rightmousedown' ||
1614
+ eventType === 'rightmouseup' ||
1615
+ eventType === 'rightdrag') {
1616
+ location.click = windowInfo.windowId;
1617
+ }
1618
+ break; // Found the window, stop searching
1619
+ }
1620
+ }
1621
+ }
1622
+
1623
+ // Add location info to cursor data
1624
+ cursorData.location = location;
1625
+
1626
+ // Add window-relative coordinates if cursor is over a window
1627
+ if (windowRelativeCoords) {
1628
+ cursorData.windowRelative = windowRelativeCoords;
1629
+ }
1630
+ }
1631
+
1632
+ // Add sync metadata to first event only
1633
+ if (this.cursorCaptureFirstWrite && this.cursorCaptureSessionTimestamp) {
1634
+ cursorData._syncMetadata = {
1635
+ videoStartTime: this.cursorCaptureSessionTimestamp,
1636
+ cursorStartTime: this.cursorCaptureStartTime,
1637
+ offsetMs: this.cursorCaptureStartTime - this.cursorCaptureSessionTimestamp
1638
+ };
1639
+ }
1640
+
1641
+ // Sadece eventType değiştiğinde veya pozisyon değiştiğinde kaydet
1642
+ if (this.shouldCaptureEvent(cursorData)) {
1643
+ // Dosyaya ekle
1644
+ const jsonString = JSON.stringify(cursorData);
1645
+
1646
+ if (this.cursorCaptureFirstWrite) {
1647
+ fs.appendFileSync(filepath, jsonString);
1648
+ this.cursorCaptureFirstWrite = false;
1649
+ } else {
1650
+ fs.appendFileSync(filepath, "," + jsonString);
1651
+ }
1652
+
1653
+ // Son pozisyonu sakla
1654
+ this.lastCapturedData = { ...cursorData };
1655
+ }
1656
+ } catch (error) {
1657
+ console.error("Cursor capture error:", error);
1658
+ }
1659
+ }, interval); // Configurable FPS
1660
+
1661
+ this.emit("cursorCaptureStarted", filepath);
1662
+ resolve(true);
1663
+ } catch (error) {
1664
+ reject(error);
1665
+ }
1666
+ });
1351
1667
  }
1352
1668
 
1669
+ /**
1670
+ * Cursor capture durdurur - dosya yazma işlemini sonlandırır
1671
+ */
1353
1672
  async stopCursorCapture() {
1354
- return cursorCapturePolling.stopCursorCapture(this);
1673
+ return new Promise((resolve, reject) => {
1674
+ try {
1675
+ if (!this.cursorCaptureInterval) {
1676
+ return resolve(false);
1677
+ }
1678
+
1679
+ // Interval'ı durdur
1680
+ clearInterval(this.cursorCaptureInterval);
1681
+ this.cursorCaptureInterval = null;
1682
+
1683
+ // Dosyayı kapat
1684
+ if (this.cursorCaptureFile) {
1685
+ const fs = require("fs");
1686
+ fs.appendFileSync(this.cursorCaptureFile, "]");
1687
+ this.cursorCaptureFile = null;
1688
+ }
1689
+
1690
+ // Değişkenleri temizle
1691
+ this.lastCapturedData = null;
1692
+ this.cursorCaptureStartTime = null;
1693
+ this.cursorCaptureFirstWrite = true;
1694
+ this.cursorDisplayInfo = null;
1695
+
1696
+ this.emit("cursorCaptureStopped");
1697
+ resolve(true);
1698
+ } catch (error) {
1699
+ reject(error);
1700
+ }
1701
+ });
1355
1702
  }
1356
1703
 
1357
1704
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.22.24",
3
+ "version": "2.22.32",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -502,8 +502,7 @@ extern "C" bool stopAVFoundationRecording() {
502
502
 
503
503
  // Finish writing with null checks
504
504
  AVAssetWriterInput *writerInput = g_avVideoInput;
505
- AVAssetWriter *writerRef = g_avWriter;
506
- if (writerInput && writerRef && writerRef.status == AVAssetWriterStatusWriting) {
505
+ if (writerInput) {
507
506
  [writerInput markAsFinished];
508
507
  }
509
508
 
@@ -7,7 +7,6 @@
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>
12
11
  #include <math.h>
13
12
 
@@ -815,10 +814,6 @@ static bool g_leftMouseDown = false;
815
814
  static bool g_rightMouseDown = false;
816
815
  static NSString *g_lastEventType = @"move";
817
816
 
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
817
  // Accessibility tabanlı cursor tip tespiti
823
818
  static NSString* detectCursorTypeUsingAccessibility(CGPoint cursorPos) {
824
819
  @autoreleasepool {
@@ -1947,29 +1942,6 @@ void writeToFile(NSDictionary *cursorData) {
1947
1942
  }
1948
1943
  }
1949
1944
 
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
1945
  // Event callback for mouse events
1974
1946
  CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
1975
1947
  @autoreleasepool {
@@ -1996,23 +1968,6 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
1996
1968
  // Event tipini belirle
1997
1969
  switch (type) {
1998
1970
  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
1971
  case kCGEventRightMouseDown:
2017
1972
  case kCGEventOtherMouseDown:
2018
1973
  eventType = @"mousedown";
@@ -2027,10 +1982,6 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
2027
1982
  case kCGEventOtherMouseDragged:
2028
1983
  eventType = @"drag";
2029
1984
  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
1985
  case kCGEventMouseMoved:
2035
1986
  default:
2036
1987
  eventType = @"move";
@@ -2040,7 +1991,7 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
2040
1991
  if (!ShouldEmitCursorEvent(location, cursorType, eventType)) {
2041
1992
  return event;
2042
1993
  }
2043
-
1994
+
2044
1995
  // Cursor data oluştur
2045
1996
  NSDictionary *cursorInfo = @{
2046
1997
  @"x": @((int)location.x),
@@ -2050,7 +2001,7 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
2050
2001
  @"cursorType": cursorType,
2051
2002
  @"type": eventType
2052
2003
  };
2053
-
2004
+
2054
2005
  // Direkt dosyaya yaz
2055
2006
  writeToFile(cursorInfo);
2056
2007
  RememberCursorEvent(location, cursorType, eventType);
@@ -2086,13 +2037,6 @@ void cursorTimerCallback() {
2086
2037
  cursorType = @"default";
2087
2038
  }
2088
2039
 
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
2040
  // Mouse button state polling — event tap olmadığında click/drag tespiti
2097
2041
  bool currentLeftMouseDown = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonLeft);
2098
2042
  bool currentRightMouseDown = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonRight);
@@ -2196,7 +2140,6 @@ void cleanupCursorTracking() {
2196
2140
  g_lastDetectedCursorType = nil;
2197
2141
  g_cursorTypeCounter = 0;
2198
2142
  g_isFirstWrite = true;
2199
- g_lastTextInputEmitTime = 0;
2200
2143
  ResetCursorEventHistory();
2201
2144
  }
2202
2145
 
@@ -2237,7 +2180,7 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
2237
2180
  g_trackingStartTime = [NSDate date];
2238
2181
  ResetCursorEventHistory();
2239
2182
 
2240
- // Create event tap for mouse + keyboard events
2183
+ // Create event tap for mouse events
2241
2184
  CGEventMask eventMask = (CGEventMaskBit(kCGEventLeftMouseDown) |
2242
2185
  CGEventMaskBit(kCGEventLeftMouseUp) |
2243
2186
  CGEventMaskBit(kCGEventRightMouseDown) |
@@ -2247,8 +2190,7 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
2247
2190
  CGEventMaskBit(kCGEventMouseMoved) |
2248
2191
  CGEventMaskBit(kCGEventLeftMouseDragged) |
2249
2192
  CGEventMaskBit(kCGEventRightMouseDragged) |
2250
- CGEventMaskBit(kCGEventOtherMouseDragged) |
2251
- CGEventMaskBit(kCGEventKeyDown));
2193
+ CGEventMaskBit(kCGEventOtherMouseDragged));
2252
2194
 
2253
2195
  bool eventTapActive = false;
2254
2196
  g_eventTap = CGEventTapCreate(kCGSessionEventTap,
@@ -2578,33 +2520,6 @@ Napi::Value GetCursorDebugInfo(const Napi::CallbackInfo& info) {
2578
2520
  }
2579
2521
  }
2580
2522
 
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
2523
  // Export functions
2609
2524
  Napi::Object InitCursorTracker(Napi::Env env, Napi::Object exports) {
2610
2525
  exports.Set("startCursorTracking", Napi::Function::New(env, StartCursorTracking));
@@ -2612,7 +2527,6 @@ Napi::Object InitCursorTracker(Napi::Env env, Napi::Object exports) {
2612
2527
  exports.Set("getCursorPosition", Napi::Function::New(env, GetCursorPosition));
2613
2528
  exports.Set("getCursorTrackingStatus", Napi::Function::New(env, GetCursorTrackingStatus));
2614
2529
  exports.Set("getCursorDebugInfo", Napi::Function::New(env, GetCursorDebugInfo));
2615
- exports.Set("getTextInputSnapshot", Napi::Function::New(env, GetTextInputSnapshot));
2616
2530
 
2617
2531
  return exports;
2618
2532
  }
@@ -2,7 +2,6 @@
2
2
  #import <CoreGraphics/CoreGraphics.h>
3
3
  #import <AppKit/AppKit.h>
4
4
  #import "../logging.h"
5
- #import "../text_input_ax_snapshot.h"
6
5
 
7
6
  // Thread-safe cursor tracking for Electron
8
7
  static dispatch_queue_t g_cursorQueue = nil;
@@ -93,45 +92,12 @@ Napi::Value GetCursorPositionElectronSafe(const Napi::CallbackInfo& info) {
93
92
  }
94
93
  }
95
94
 
96
- static Napi::Value DictToNapiTextInputSnapshotElectron(Napi::Env env, NSDictionary *snap) {
97
- Napi::Object o = Napi::Object::New(env);
98
- NSNumber *cx = snap[@"caretX"];
99
- NSNumber *cy = snap[@"caretY"];
100
- o.Set("caretX", Napi::Number::New(env, cx ? [cx doubleValue] : 0));
101
- o.Set("caretY", Napi::Number::New(env, cy ? [cy doubleValue] : 0));
102
- NSDictionary *frame = snap[@"inputFrame"];
103
- Napi::Object fo = Napi::Object::New(env);
104
- if ([frame isKindOfClass:[NSDictionary class]]) {
105
- fo.Set("x", Napi::Number::New(env, [frame[@"x"] doubleValue]));
106
- fo.Set("y", Napi::Number::New(env, [frame[@"y"] doubleValue]));
107
- fo.Set("width", Napi::Number::New(env, [frame[@"width"] doubleValue]));
108
- fo.Set("height", Napi::Number::New(env, [frame[@"height"] doubleValue]));
109
- }
110
- o.Set("inputFrame", fo);
111
- return o;
112
- }
113
-
114
- Napi::Value GetTextInputSnapshotElectronSafe(const Napi::CallbackInfo& info) {
115
- Napi::Env env = info.Env();
116
- @try {
117
- NSDictionary *snap = MRTextInputSnapshotDictionary();
118
- if (!snap) {
119
- return env.Null();
120
- }
121
- return DictToNapiTextInputSnapshotElectron(env, snap);
122
- } @catch (NSException *e) {
123
- NSLog(@"❌ getTextInputSnapshot: %@", e.reason);
124
- return env.Null();
125
- }
126
- }
127
-
128
95
  // Initialize cursor tracker module
129
96
  Napi::Object InitCursorTrackerElectron(Napi::Env env, Napi::Object exports) {
130
97
  @try {
131
98
  initializeCursorQueue();
132
99
 
133
100
  exports.Set("getCursorPosition", Napi::Function::New(env, GetCursorPositionElectronSafe));
134
- exports.Set("getTextInputSnapshot", Napi::Function::New(env, GetTextInputSnapshotElectronSafe));
135
101
 
136
102
  MRLog(@"✅ Electron-safe cursor tracker initialized");
137
103
  return exports;
@@ -13,9 +13,12 @@ static void initializeWindowQueue() {
13
13
  }
14
14
 
15
15
  static BOOL ShouldAllowElectronWindows(void) {
16
- NSString *flag = [[[NSProcessInfo processInfo] environment] objectForKey:@"CREAVIT_ALLOW_ELECTRON_WINDOWS"];
17
- if (!flag) return NO;
16
+ // NSProcessInfo.environment snapshot'lanır ve runtime'da process.env değişikliklerini yansıtmaz.
17
+ // getenv() libc üzerinden her seferinde güncel değeri okur — menüden toggle'lanabilsin diye.
18
+ const char *raw = getenv("CREAVIT_ALLOW_ELECTRON_WINDOWS");
19
+ if (!raw) return NO;
18
20
 
21
+ NSString *flag = [NSString stringWithUTF8String:raw];
19
22
  NSString *normalized = [[flag lowercaseString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
20
23
  return [normalized isEqualToString:@"1"] ||
21
24
  [normalized isEqualToString:@"true"] ||