node-mac-recorder 2.17.21 → 2.18.1
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-data-1751364226346.json +1 -0
- package/cursor-data-1751364314136.json +1 -0
- package/cursor-data.json +1 -0
- package/index.js +152 -64
- package/package.json +1 -1
- package/publish.sh +31 -18
- package/src/cursor_tracker.mm +218 -456
- package/src/screen_capture_kit.mm +1 -34
- package/auto-front-demo.js +0 -71
- package/simple-api-example.js +0 -182
- package/usage-examples.js +0 -202
- package/working-example.js +0 -94
package/src/cursor_tracker.mm
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
#import <ApplicationServices/ApplicationServices.h>
|
|
6
6
|
#import <Carbon/Carbon.h>
|
|
7
7
|
#import <Accessibility/Accessibility.h>
|
|
8
|
-
#import <CoreServices/CoreServices.h>
|
|
9
8
|
|
|
10
9
|
// Global state for cursor tracking
|
|
11
10
|
static bool g_isCursorTracking = false;
|
|
@@ -13,7 +12,7 @@ static CFMachPortRef g_eventTap = NULL;
|
|
|
13
12
|
static CFRunLoopSourceRef g_runLoopSource = NULL;
|
|
14
13
|
static NSDate *g_trackingStartTime = nil;
|
|
15
14
|
static NSString *g_outputPath = nil;
|
|
16
|
-
static
|
|
15
|
+
static NSTimer *g_cursorTimer = nil;
|
|
17
16
|
static int g_debugCallbackCount = 0;
|
|
18
17
|
static NSFileHandle *g_fileHandle = nil;
|
|
19
18
|
static bool g_isFirstWrite = true;
|
|
@@ -23,78 +22,6 @@ void cursorTimerCallback();
|
|
|
23
22
|
void writeToFile(NSDictionary *cursorData);
|
|
24
23
|
NSDictionary* getDisplayScalingInfo(CGPoint globalPoint);
|
|
25
24
|
|
|
26
|
-
// Core Graphics cursor detection as ultimate fallback
|
|
27
|
-
NSString* detectCursorTypeFromCoreGraphics() {
|
|
28
|
-
@try {
|
|
29
|
-
// Use Core Graphics to get cursor information
|
|
30
|
-
// This is more reliable than NSCursor in some cases
|
|
31
|
-
|
|
32
|
-
// Get the current cursor seed (changes when cursor changes)
|
|
33
|
-
static uint32_t lastCursorSeed = 0;
|
|
34
|
-
// Note: CGEventSourceGetUserData requires actual source, simplified for now
|
|
35
|
-
static uint32_t currentSeed = 0;
|
|
36
|
-
currentSeed++; // Simple increment for basic tracking
|
|
37
|
-
|
|
38
|
-
// If cursor hasn't changed, use cached result for performance
|
|
39
|
-
static NSString *cachedCGCursorType = nil;
|
|
40
|
-
if (currentSeed == lastCursorSeed && cachedCGCursorType) {
|
|
41
|
-
return cachedCGCursorType;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
lastCursorSeed = currentSeed;
|
|
45
|
-
|
|
46
|
-
// Try to detect cursor based on system state
|
|
47
|
-
// Check if we're in a text editing context by looking at input methods
|
|
48
|
-
TISInputSourceRef currentInputSource = TISCopyCurrentKeyboardInputSource();
|
|
49
|
-
if (currentInputSource) {
|
|
50
|
-
// If input source is active, likely in text field
|
|
51
|
-
CFStringRef inputSourceID = (CFStringRef)TISGetInputSourceProperty(currentInputSource, kTISPropertyInputSourceID);
|
|
52
|
-
if (inputSourceID) {
|
|
53
|
-
// Text input is active, likely text cursor
|
|
54
|
-
cachedCGCursorType = @"text";
|
|
55
|
-
CFRelease(currentInputSource);
|
|
56
|
-
return cachedCGCursorType;
|
|
57
|
-
}
|
|
58
|
-
CFRelease(currentInputSource);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Check current application and window state for context
|
|
62
|
-
NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
|
|
63
|
-
if (frontApp) {
|
|
64
|
-
NSString *bundleId = frontApp.bundleIdentifier;
|
|
65
|
-
|
|
66
|
-
// Web browsers often have custom cursor handling
|
|
67
|
-
if ([bundleId containsString:@"chrome"] ||
|
|
68
|
-
[bundleId containsString:@"firefox"] ||
|
|
69
|
-
[bundleId containsString:@"safari"] ||
|
|
70
|
-
[bundleId containsString:@"webkit"]) {
|
|
71
|
-
|
|
72
|
-
// In browsers, use heuristics based on recent patterns
|
|
73
|
-
// This is speculative but can improve accuracy
|
|
74
|
-
return nil; // Let other methods handle browser cursors
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Text editors typically show text cursors
|
|
78
|
-
if ([bundleId containsString:@"textedit"] ||
|
|
79
|
-
[bundleId containsString:@"xcode"] ||
|
|
80
|
-
[bundleId containsString:@"vscode"] ||
|
|
81
|
-
[bundleId containsString:@"sublime"] ||
|
|
82
|
-
[bundleId containsString:@"atom"]) {
|
|
83
|
-
|
|
84
|
-
cachedCGCursorType = @"text";
|
|
85
|
-
return cachedCGCursorType;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// No specific cursor detected
|
|
90
|
-
cachedCGCursorType = nil;
|
|
91
|
-
return nil;
|
|
92
|
-
|
|
93
|
-
} @catch (NSException *exception) {
|
|
94
|
-
return nil;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
25
|
// Timer helper class
|
|
99
26
|
@interface CursorTimerTarget : NSObject
|
|
100
27
|
- (void)timerCallback:(NSTimer *)timer;
|
|
@@ -108,17 +35,9 @@ NSString* detectCursorTypeFromCoreGraphics() {
|
|
|
108
35
|
|
|
109
36
|
static CursorTimerTarget *g_timerTarget = nil;
|
|
110
37
|
|
|
111
|
-
//
|
|
38
|
+
// Global cursor state tracking
|
|
112
39
|
static NSString *g_lastDetectedCursorType = nil;
|
|
113
|
-
static NSString *g_stableCursorType = @"default";
|
|
114
40
|
static int g_cursorTypeCounter = 0;
|
|
115
|
-
static NSTimeInterval g_lastCursorCheckTime = 0;
|
|
116
|
-
static int g_sameCursorDetectionCount = 0;
|
|
117
|
-
static NSString *g_pendingCursorType = nil;
|
|
118
|
-
|
|
119
|
-
// Enhanced cursor stability constants - ultra-responsive for real-time cursor tracking
|
|
120
|
-
static const NSTimeInterval CURSOR_STABILITY_THRESHOLD = 0.005; // 5ms (ultra fast response)
|
|
121
|
-
static const int CURSOR_CONFIRMATION_COUNT = 0; // No confirmation needed - immediate response
|
|
122
41
|
|
|
123
42
|
// Mouse button state tracking
|
|
124
43
|
static bool g_leftMouseDown = false;
|
|
@@ -130,204 +49,38 @@ static CGEventRef eventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEv
|
|
|
130
49
|
return event;
|
|
131
50
|
}
|
|
132
51
|
|
|
133
|
-
//
|
|
134
|
-
NSString* detectCursorTypeFromNSCursor() {
|
|
135
|
-
@try {
|
|
136
|
-
// Try multiple methods to get current cursor since currentSystemCursor often fails
|
|
137
|
-
NSCursor *currentCursor = nil;
|
|
138
|
-
|
|
139
|
-
// Method 1: currentSystemCursor (often returns nil but try anyway)
|
|
140
|
-
currentCursor = [NSCursor currentSystemCursor];
|
|
141
|
-
|
|
142
|
-
// Method 2: If nil, try getting cursor from current window
|
|
143
|
-
if (!currentCursor) {
|
|
144
|
-
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
|
|
145
|
-
if (keyWindow) {
|
|
146
|
-
// Try to get cursor from window's current event
|
|
147
|
-
NSEvent *currentEvent = [[NSApplication sharedApplication] currentEvent];
|
|
148
|
-
if (currentEvent) {
|
|
149
|
-
// This is a fallback - may not always work but worth trying
|
|
150
|
-
@try {
|
|
151
|
-
// Get window at mouse position
|
|
152
|
-
NSPoint mouseLocation = [NSEvent mouseLocation];
|
|
153
|
-
// This approach has limited success, fallback to image analysis
|
|
154
|
-
} @catch (NSException *e) {
|
|
155
|
-
// Ignore and continue
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (!currentCursor) {
|
|
162
|
-
return nil; // No cursor available, use contextual detection
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Compare with known system cursors using identity comparison first
|
|
166
|
-
if (currentCursor == [NSCursor arrowCursor]) {
|
|
167
|
-
return @"default";
|
|
168
|
-
} else if (currentCursor == [NSCursor IBeamCursor]) {
|
|
169
|
-
return @"text";
|
|
170
|
-
} else if (currentCursor == [NSCursor pointingHandCursor]) {
|
|
171
|
-
return @"pointer";
|
|
172
|
-
} else if (currentCursor == [NSCursor resizeLeftRightCursor]) {
|
|
173
|
-
return @"col-resize";
|
|
174
|
-
} else if (currentCursor == [NSCursor resizeUpDownCursor]) {
|
|
175
|
-
return @"ns-resize";
|
|
176
|
-
} else if (currentCursor == [NSCursor crosshairCursor]) {
|
|
177
|
-
return @"crosshair";
|
|
178
|
-
} else if (currentCursor == [NSCursor openHandCursor]) {
|
|
179
|
-
return @"grab";
|
|
180
|
-
} else if (currentCursor == [NSCursor closedHandCursor]) {
|
|
181
|
-
return @"grabbing";
|
|
182
|
-
} else if (currentCursor == [NSCursor operationNotAllowedCursor]) {
|
|
183
|
-
return @"not-allowed";
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Enhanced image analysis for better cursor detection
|
|
187
|
-
NSImage *cursorImage = [currentCursor image];
|
|
188
|
-
if (cursorImage) {
|
|
189
|
-
NSSize imageSize = [cursorImage size];
|
|
190
|
-
NSPoint hotSpot = [currentCursor hotSpot];
|
|
191
|
-
|
|
192
|
-
// Get image data for more detailed analysis
|
|
193
|
-
CGImageRef cgImage = [cursorImage CGImageForProposedRect:NULL context:nil hints:nil];
|
|
194
|
-
if (cgImage) {
|
|
195
|
-
size_t width = CGImageGetWidth(cgImage);
|
|
196
|
-
size_t height = CGImageGetHeight(cgImage);
|
|
197
|
-
|
|
198
|
-
// More precise size-based detection
|
|
199
|
-
if (width > 0 && height > 0) {
|
|
200
|
-
float aspectRatio = (float)width / (float)height;
|
|
201
|
-
|
|
202
|
-
// Text cursor: Very narrow (I-beam)
|
|
203
|
-
if (width <= 3 && height >= 15) {
|
|
204
|
-
return @"text";
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Hand cursors: Usually square-ish with specific sizes
|
|
208
|
-
if (width >= 12 && width <= 20 && height >= 12 && height <= 20 && aspectRatio > 0.8 && aspectRatio < 1.2) {
|
|
209
|
-
// Try to distinguish hand types by hotspot
|
|
210
|
-
if (hotSpot.x > width * 0.6) { // hotspot towards right
|
|
211
|
-
return @"pointer";
|
|
212
|
-
} else if (hotSpot.x < width * 0.4) { // hotspot towards left
|
|
213
|
-
return @"grab";
|
|
214
|
-
}
|
|
215
|
-
return @"pointer"; // default to pointer for hand-like cursors
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Resize cursors: Usually larger and directional
|
|
219
|
-
if (width >= 10 && height >= 10) {
|
|
220
|
-
// Very wide: horizontal resize
|
|
221
|
-
if (aspectRatio > 2.0) {
|
|
222
|
-
return @"col-resize";
|
|
223
|
-
}
|
|
224
|
-
// Very tall: vertical resize
|
|
225
|
-
else if (aspectRatio < 0.5) {
|
|
226
|
-
return @"ns-resize";
|
|
227
|
-
}
|
|
228
|
-
// Square-ish but large: diagonal resize
|
|
229
|
-
else if (width >= 15 && height >= 15 && aspectRatio > 0.7 && aspectRatio < 1.4) {
|
|
230
|
-
// Use hotspot to determine diagonal direction
|
|
231
|
-
float hotSpotX = hotSpot.x / width;
|
|
232
|
-
float hotSpotY = hotSpot.y / height;
|
|
233
|
-
|
|
234
|
-
if ((hotSpotX < 0.5 && hotSpotY < 0.5) || (hotSpotX > 0.5 && hotSpotY > 0.5)) {
|
|
235
|
-
return @"nwse-resize"; // northwest-southeast
|
|
236
|
-
} else {
|
|
237
|
-
return @"nesw-resize"; // northeast-southwest
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Crosshair: Usually symmetric and medium sized
|
|
243
|
-
if (width >= 12 && width <= 25 && height >= 12 && height <= 25 &&
|
|
244
|
-
aspectRatio > 0.8 && aspectRatio < 1.2 &&
|
|
245
|
-
hotSpot.x > width * 0.4 && hotSpot.x < width * 0.6 &&
|
|
246
|
-
hotSpot.y > height * 0.4 && hotSpot.y < height * 0.6) {
|
|
247
|
-
return @"crosshair";
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Fallback to original size-based analysis
|
|
253
|
-
if (imageSize.width > 10 && imageSize.height > 10) {
|
|
254
|
-
// Check for diagonal resize cursors (corner resize)
|
|
255
|
-
if (imageSize.width >= 15 && imageSize.height >= 15) {
|
|
256
|
-
// These are likely diagonal resize cursors
|
|
257
|
-
// hotSpot can help distinguish between nwse and nesw
|
|
258
|
-
if (hotSpot.x < imageSize.width / 2 && hotSpot.y < imageSize.height / 2) {
|
|
259
|
-
return @"nwse-resize"; // northwest-southeast
|
|
260
|
-
} else if (hotSpot.x > imageSize.width / 2 && hotSpot.y < imageSize.height / 2) {
|
|
261
|
-
return @"nesw-resize"; // northeast-southwest
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Check for horizontal/vertical resize cursors
|
|
266
|
-
if (imageSize.width > imageSize.height + 5) {
|
|
267
|
-
return @"col-resize"; // horizontal resize
|
|
268
|
-
} else if (imageSize.height > imageSize.width + 5) {
|
|
269
|
-
return @"ns-resize"; // vertical resize
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Text cursors typically have I-beam shape (narrow width)
|
|
274
|
-
if (imageSize.width < 8 && imageSize.height > 15) {
|
|
275
|
-
return @"text";
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Return nil to indicate we should use contextual detection
|
|
280
|
-
return nil;
|
|
281
|
-
} @catch (NSException *exception) {
|
|
282
|
-
return nil;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Improved cursor type detection with stability and multi-layer approach
|
|
52
|
+
// Cursor type detection helper - sistem genelindeki cursor type'ı al
|
|
287
53
|
NSString* getCursorType() {
|
|
288
54
|
@autoreleasepool {
|
|
289
55
|
g_cursorTypeCounter++;
|
|
290
|
-
|
|
291
|
-
|
|
56
|
+
|
|
292
57
|
@try {
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
// Layer 1.5: Core Graphics detection as primary fallback
|
|
297
|
-
NSString *cgCursorType = detectCursorTypeFromCoreGraphics();
|
|
298
|
-
|
|
299
|
-
// Layer 2: Optimized Accessibility API detection with caching
|
|
300
|
-
NSString *contextualCursorType = nil;
|
|
301
|
-
|
|
302
|
-
// Get cursor position once and reuse
|
|
303
|
-
CGEventRef event = CGEventCreate(NULL);
|
|
304
|
-
CGPoint cursorPos = CGEventGetLocation(event);
|
|
305
|
-
CFRelease(event);
|
|
306
|
-
|
|
307
|
-
// Use cached system-wide reference for better performance
|
|
308
|
-
static AXUIElementRef cachedSystemWide = NULL;
|
|
309
|
-
if (!cachedSystemWide) {
|
|
310
|
-
cachedSystemWide = AXUIElementCreateSystemWide();
|
|
311
|
-
}
|
|
58
|
+
// ACCESSIBILITY API BASED CURSOR DETECTION
|
|
59
|
+
// Determine cursor type based on the UI element under the cursor
|
|
312
60
|
|
|
61
|
+
CGPoint cursorPos = CGEventGetLocation(CGEventCreate(NULL));
|
|
62
|
+
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
313
63
|
AXUIElementRef elementAtPosition = NULL;
|
|
314
|
-
AXError error = AXUIElementCopyElementAtPosition(
|
|
64
|
+
AXError error = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &elementAtPosition);
|
|
65
|
+
|
|
66
|
+
NSString *cursorType = @"default"; // Default fallback
|
|
315
67
|
|
|
316
68
|
if (error == kAXErrorSuccess && elementAtPosition) {
|
|
317
|
-
// Fast role detection with immediate processing
|
|
318
69
|
CFStringRef role = NULL;
|
|
319
70
|
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleAttribute, (CFTypeRef*)&role);
|
|
320
71
|
|
|
321
72
|
if (error == kAXErrorSuccess && role) {
|
|
322
73
|
NSString *elementRole = (__bridge_transfer NSString*)role;
|
|
74
|
+
NSLog(@"🎯 ELEMENT ROLE: %@", elementRole);
|
|
323
75
|
|
|
324
|
-
// TEXT CURSORS
|
|
76
|
+
// TEXT CURSORS
|
|
325
77
|
if ([elementRole isEqualToString:@"AXTextField"] ||
|
|
326
78
|
[elementRole isEqualToString:@"AXTextArea"] ||
|
|
79
|
+
[elementRole isEqualToString:@"AXStaticText"] ||
|
|
327
80
|
[elementRole isEqualToString:@"AXSearchField"]) {
|
|
328
|
-
|
|
81
|
+
cursorType = @"text";
|
|
329
82
|
}
|
|
330
|
-
// POINTER CURSORS
|
|
83
|
+
// POINTER CURSORS (clickable elements)
|
|
331
84
|
else if ([elementRole isEqualToString:@"AXLink"] ||
|
|
332
85
|
[elementRole isEqualToString:@"AXButton"] ||
|
|
333
86
|
[elementRole isEqualToString:@"AXMenuItem"] ||
|
|
@@ -335,182 +88,216 @@ NSString* getCursorType() {
|
|
|
335
88
|
[elementRole isEqualToString:@"AXCheckBox"] ||
|
|
336
89
|
[elementRole isEqualToString:@"AXPopUpButton"] ||
|
|
337
90
|
[elementRole isEqualToString:@"AXTab"]) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
// Also check subroles for links and buttons
|
|
341
|
-
CFStringRef subrole = NULL;
|
|
342
|
-
AXError subroleError = AXUIElementCopyAttributeValue(elementAtPosition, kAXSubroleAttribute, (CFTypeRef*)&subrole);
|
|
343
|
-
if (subroleError == kAXErrorSuccess && subrole) {
|
|
344
|
-
NSString *elementSubrole = (__bridge_transfer NSString*)subrole;
|
|
345
|
-
if ([elementSubrole isEqualToString:@"AXCloseButton"] ||
|
|
346
|
-
[elementSubrole isEqualToString:@"AXMinimizeButton"] ||
|
|
347
|
-
[elementSubrole isEqualToString:@"AXZoomButton"] ||
|
|
348
|
-
[elementSubrole isEqualToString:@"AXToolbarButton"]) {
|
|
349
|
-
contextualCursorType = @"pointer";
|
|
350
|
-
}
|
|
351
|
-
}
|
|
91
|
+
cursorType = @"pointer";
|
|
352
92
|
}
|
|
353
|
-
//
|
|
354
|
-
else if ([elementRole isEqualToString:@"
|
|
355
|
-
[elementRole isEqualToString:@"
|
|
356
|
-
// Check if
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
NSString *actionStr = (__bridge NSString*)action;
|
|
364
|
-
if ([actionStr isEqualToString:@"AXPress"] ||
|
|
365
|
-
[actionStr isEqualToString:@"AXShowMenu"]) {
|
|
366
|
-
contextualCursorType = @"pointer";
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
CFRelease(actions);
|
|
93
|
+
// GRAB CURSORS (draggable elements)
|
|
94
|
+
else if ([elementRole isEqualToString:@"AXImage"] ||
|
|
95
|
+
[elementRole isEqualToString:@"AXGroup"]) {
|
|
96
|
+
// Check if element is draggable
|
|
97
|
+
CFBooleanRef draggable = NULL;
|
|
98
|
+
error = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXMovable"), (CFTypeRef*)&draggable);
|
|
99
|
+
if (error == kAXErrorSuccess && draggable && CFBooleanGetValue(draggable)) {
|
|
100
|
+
cursorType = @"grab";
|
|
101
|
+
} else {
|
|
102
|
+
cursorType = @"default";
|
|
371
103
|
}
|
|
372
104
|
}
|
|
373
|
-
//
|
|
374
|
-
else if ([elementRole isEqualToString:@"
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
AXError posErr = AXUIElementCopyAttributeValue(elementAtPosition, kAXPositionAttribute, &position);
|
|
378
|
-
AXError sizeErr = AXUIElementCopyAttributeValue(elementAtPosition, kAXSizeAttribute, &size);
|
|
379
|
-
|
|
380
|
-
if (posErr == kAXErrorSuccess && sizeErr == kAXErrorSuccess && position && size) {
|
|
381
|
-
CGPoint windowPos;
|
|
382
|
-
CGSize windowSize;
|
|
383
|
-
AXValueGetValue((AXValueRef)position, kAXValueTypeCGPoint, &windowPos);
|
|
384
|
-
AXValueGetValue((AXValueRef)size, kAXValueTypeCGSize, &windowSize);
|
|
385
|
-
|
|
386
|
-
CGFloat x = cursorPos.x - windowPos.x;
|
|
387
|
-
CGFloat y = cursorPos.y - windowPos.y;
|
|
388
|
-
CGFloat w = windowSize.width;
|
|
389
|
-
CGFloat h = windowSize.height;
|
|
390
|
-
CGFloat edge = 5.0; // 5px edge detection
|
|
391
|
-
|
|
392
|
-
// Corner resize detection
|
|
393
|
-
if (x <= edge && y <= edge) {
|
|
394
|
-
contextualCursorType = @"nwse-resize";
|
|
395
|
-
}
|
|
396
|
-
else if (x >= w-edge && y <= edge) {
|
|
397
|
-
contextualCursorType = @"nesw-resize";
|
|
398
|
-
}
|
|
399
|
-
else if (x <= edge && y >= h-edge) {
|
|
400
|
-
contextualCursorType = @"nesw-resize";
|
|
401
|
-
}
|
|
402
|
-
else if (x >= w-edge && y >= h-edge) {
|
|
403
|
-
contextualCursorType = @"nwse-resize";
|
|
404
|
-
}
|
|
405
|
-
// Edge resize detection
|
|
406
|
-
else if (x <= edge || x >= w-edge) {
|
|
407
|
-
contextualCursorType = @"col-resize";
|
|
408
|
-
}
|
|
409
|
-
else if (y <= edge || y >= h-edge) {
|
|
410
|
-
contextualCursorType = @"ns-resize";
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if (position) CFRelease(position);
|
|
414
|
-
if (size) CFRelease(size);
|
|
415
|
-
}
|
|
105
|
+
// PROGRESS CURSORS (loading/busy elements)
|
|
106
|
+
else if ([elementRole isEqualToString:@"AXProgressIndicator"] ||
|
|
107
|
+
[elementRole isEqualToString:@"AXBusyIndicator"]) {
|
|
108
|
+
cursorType = @"progress";
|
|
416
109
|
}
|
|
417
|
-
//
|
|
110
|
+
// HELP CURSORS (help buttons/tooltips)
|
|
111
|
+
else if ([elementRole isEqualToString:@"AXHelpTag"] ||
|
|
112
|
+
[elementRole isEqualToString:@"AXTooltip"]) {
|
|
113
|
+
cursorType = @"help";
|
|
114
|
+
}
|
|
115
|
+
// RESIZE CURSORS - sadece AXSplitter için
|
|
418
116
|
else if ([elementRole isEqualToString:@"AXSplitter"]) {
|
|
117
|
+
// Get splitter orientation to determine resize direction
|
|
419
118
|
CFStringRef orientation = NULL;
|
|
420
119
|
error = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXOrientation"), (CFTypeRef*)&orientation);
|
|
421
120
|
if (error == kAXErrorSuccess && orientation) {
|
|
422
121
|
NSString *orientationStr = (__bridge_transfer NSString*)orientation;
|
|
423
122
|
if ([orientationStr isEqualToString:@"AXHorizontalOrientation"]) {
|
|
424
|
-
|
|
123
|
+
cursorType = @"ns-resize"; // Yatay splitter -> dikey hareket (north-south)
|
|
425
124
|
} else if ([orientationStr isEqualToString:@"AXVerticalOrientation"]) {
|
|
426
|
-
|
|
125
|
+
cursorType = @"col-resize"; // Dikey splitter -> yatay hareket (east-west)
|
|
126
|
+
} else {
|
|
127
|
+
cursorType = @"default"; // Bilinmeyen orientation
|
|
427
128
|
}
|
|
129
|
+
} else {
|
|
130
|
+
cursorType = @"default"; // Orientation alınamazsa default
|
|
428
131
|
}
|
|
429
132
|
}
|
|
430
|
-
//
|
|
431
|
-
else if ([elementRole isEqualToString:@"
|
|
432
|
-
|
|
133
|
+
// SCROLL CURSORS - hep default olsun, all-scroll görünmesin
|
|
134
|
+
else if ([elementRole isEqualToString:@"AXScrollBar"]) {
|
|
135
|
+
cursorType = @"default"; // ScrollBar'lar için de default
|
|
136
|
+
}
|
|
137
|
+
// AXScrollArea - hep default
|
|
138
|
+
else if ([elementRole isEqualToString:@"AXScrollArea"]) {
|
|
139
|
+
cursorType = @"default"; // ScrollArea her zaman default
|
|
140
|
+
}
|
|
141
|
+
// CROSSHAIR CURSORS (drawing/selection tools)
|
|
142
|
+
else if ([elementRole isEqualToString:@"AXCanvas"] ||
|
|
143
|
+
[elementRole isEqualToString:@"AXDrawingArea"]) {
|
|
144
|
+
cursorType = @"crosshair";
|
|
145
|
+
}
|
|
146
|
+
// ZOOM CURSORS (zoom controls)
|
|
147
|
+
else if ([elementRole isEqualToString:@"AXZoomButton"]) {
|
|
148
|
+
cursorType = @"zoom-in";
|
|
433
149
|
}
|
|
434
|
-
//
|
|
435
|
-
else if ([elementRole isEqualToString:@"
|
|
436
|
-
[elementRole isEqualToString:@"AXWindow"] ||
|
|
437
|
-
[elementRole isEqualToString:@"AXScrollArea"] ||
|
|
438
|
-
[elementRole isEqualToString:@"AXScrollBar"] ||
|
|
439
|
-
[elementRole isEqualToString:@"AXStaticText"] ||
|
|
150
|
+
// NOT-ALLOWED CURSORS (disabled elements)
|
|
151
|
+
else if ([elementRole isEqualToString:@"AXStaticText"] ||
|
|
440
152
|
[elementRole isEqualToString:@"AXGroup"]) {
|
|
441
|
-
//
|
|
442
|
-
|
|
153
|
+
// Check if element is disabled/readonly
|
|
154
|
+
CFBooleanRef enabled = NULL;
|
|
155
|
+
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXEnabledAttribute, (CFTypeRef*)&enabled);
|
|
156
|
+
if (error == kAXErrorSuccess && enabled && !CFBooleanGetValue(enabled)) {
|
|
157
|
+
cursorType = @"not-allowed";
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// WINDOW BORDER RESIZE - sadece pencere kenarlarında
|
|
161
|
+
else if ([elementRole isEqualToString:@"AXWindow"]) {
|
|
162
|
+
// Check window attributes to see if it's resizable
|
|
163
|
+
CFBooleanRef resizable = NULL;
|
|
164
|
+
AXError resizableError = AXUIElementCopyAttributeValue(elementAtPosition, CFSTR("AXResizeButton"), (CFTypeRef*)&resizable);
|
|
165
|
+
|
|
166
|
+
// Sadece resize edilebilir pencereler için cursor değişimi
|
|
167
|
+
if (resizableError == kAXErrorSuccess || true) { // AXResizeButton bulunamazsa da devam et
|
|
168
|
+
CFTypeRef position = NULL;
|
|
169
|
+
CFTypeRef size = NULL;
|
|
170
|
+
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXPositionAttribute, &position);
|
|
171
|
+
AXError sizeError = AXUIElementCopyAttributeValue(elementAtPosition, kAXSizeAttribute, &size);
|
|
172
|
+
|
|
173
|
+
if (error == kAXErrorSuccess && sizeError == kAXErrorSuccess && position && size) {
|
|
174
|
+
CGPoint windowPos;
|
|
175
|
+
CGSize windowSize;
|
|
176
|
+
AXValueGetValue((AXValueRef)position, kAXValueTypeCGPoint, &windowPos);
|
|
177
|
+
AXValueGetValue((AXValueRef)size, kAXValueTypeCGSize, &windowSize);
|
|
178
|
+
|
|
179
|
+
CGFloat x = cursorPos.x - windowPos.x;
|
|
180
|
+
CGFloat y = cursorPos.y - windowPos.y;
|
|
181
|
+
CGFloat w = windowSize.width;
|
|
182
|
+
CGFloat h = windowSize.height;
|
|
183
|
+
CGFloat edge = 3.0; // Daha küçük edge detection (3px)
|
|
184
|
+
|
|
185
|
+
// Sadece çok kenar köşelerde resize cursor'ı göster
|
|
186
|
+
BOOL isOnBorder = NO;
|
|
187
|
+
|
|
188
|
+
// Corner resize detection - çok dar alanda, doğru açılar
|
|
189
|
+
if (x <= edge && y <= edge) {
|
|
190
|
+
cursorType = @"nwse-resize"; // Sol üst köşe - northwest-southeast
|
|
191
|
+
isOnBorder = YES;
|
|
192
|
+
}
|
|
193
|
+
else if (x >= w-edge && y <= edge) {
|
|
194
|
+
cursorType = @"nesw-resize"; // Sağ üst köşe - northeast-southwest
|
|
195
|
+
isOnBorder = YES;
|
|
196
|
+
}
|
|
197
|
+
else if (x <= edge && y >= h-edge) {
|
|
198
|
+
cursorType = @"nesw-resize"; // Sol alt köşe - southwest-northeast
|
|
199
|
+
isOnBorder = YES;
|
|
200
|
+
}
|
|
201
|
+
else if (x >= w-edge && y >= h-edge) {
|
|
202
|
+
cursorType = @"nwse-resize"; // Sağ alt köşe - southeast-northwest
|
|
203
|
+
isOnBorder = YES;
|
|
204
|
+
}
|
|
205
|
+
// Edge resize detection - sadece çok kenarlarda
|
|
206
|
+
else if (x <= edge && y > edge && y < h-edge) {
|
|
207
|
+
cursorType = @"col-resize"; // Sol kenar - column resize (yatay)
|
|
208
|
+
isOnBorder = YES;
|
|
209
|
+
}
|
|
210
|
+
else if (x >= w-edge && y > edge && y < h-edge) {
|
|
211
|
+
cursorType = @"col-resize"; // Sağ kenar - column resize (yatay)
|
|
212
|
+
isOnBorder = YES;
|
|
213
|
+
}
|
|
214
|
+
else if (y <= edge && x > edge && x < w-edge) {
|
|
215
|
+
cursorType = @"ns-resize"; // Üst kenar - north-south resize (dikey)
|
|
216
|
+
isOnBorder = YES;
|
|
217
|
+
}
|
|
218
|
+
else if (y >= h-edge && x > edge && x < w-edge) {
|
|
219
|
+
cursorType = @"ns-resize"; // Alt kenar - north-south resize (dikey)
|
|
220
|
+
isOnBorder = YES;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Eğer border'da değilse default
|
|
224
|
+
if (!isOnBorder) {
|
|
225
|
+
cursorType = @"default";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (position) CFRelease(position);
|
|
229
|
+
if (size) CFRelease(size);
|
|
230
|
+
} else {
|
|
231
|
+
cursorType = @"default";
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
cursorType = @"default";
|
|
235
|
+
}
|
|
443
236
|
}
|
|
444
|
-
//
|
|
237
|
+
// HER DURUM İÇİN DEFAULT FALLBACK
|
|
445
238
|
else {
|
|
446
|
-
|
|
239
|
+
// Bilinmeyen elementler için her zaman default
|
|
240
|
+
cursorType = @"default";
|
|
447
241
|
}
|
|
448
|
-
}
|
|
449
|
-
CFRelease(elementAtPosition);
|
|
450
|
-
}
|
|
451
242
|
|
|
452
|
-
|
|
453
|
-
|
|
243
|
+
// Check subroles for additional context
|
|
244
|
+
CFStringRef subrole = NULL;
|
|
245
|
+
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXSubroleAttribute, (CFTypeRef*)&subrole);
|
|
246
|
+
if (error == kAXErrorSuccess && subrole) {
|
|
247
|
+
NSString *elementSubrole = (__bridge_transfer NSString*)subrole;
|
|
248
|
+
NSLog(@"🎯 ELEMENT SUBROLE: %@", elementSubrole);
|
|
249
|
+
|
|
250
|
+
// Subrole override'ları - sadece çok spesifik durumlar için
|
|
251
|
+
if ([elementSubrole isEqualToString:@"AXCloseButton"] ||
|
|
252
|
+
[elementSubrole isEqualToString:@"AXMinimizeButton"] ||
|
|
253
|
+
[elementSubrole isEqualToString:@"AXZoomButton"] ||
|
|
254
|
+
[elementSubrole isEqualToString:@"AXToolbarButton"]) {
|
|
255
|
+
cursorType = @"pointer";
|
|
256
|
+
}
|
|
257
|
+
// Copy/alias subroles - sadece bu durumlar için override
|
|
258
|
+
else if ([elementSubrole isEqualToString:@"AXFileDrop"] ||
|
|
259
|
+
[elementSubrole isEqualToString:@"AXDropTarget"]) {
|
|
260
|
+
cursorType = @"copy";
|
|
261
|
+
}
|
|
262
|
+
// Alias/shortcut subroles
|
|
263
|
+
else if ([elementSubrole isEqualToString:@"AXAlias"] ||
|
|
264
|
+
[elementSubrole isEqualToString:@"AXShortcut"]) {
|
|
265
|
+
cursorType = @"alias";
|
|
266
|
+
}
|
|
267
|
+
// Grabbing state (being dragged) - sadece gerçek drag sırasında
|
|
268
|
+
else if ([elementSubrole isEqualToString:@"AXDragging"] ||
|
|
269
|
+
[elementSubrole isEqualToString:@"AXMoving"]) {
|
|
270
|
+
cursorType = @"grabbing";
|
|
271
|
+
}
|
|
272
|
+
// Zoom controls - sadece spesifik zoom butonları için
|
|
273
|
+
else if ([elementSubrole isEqualToString:@"AXZoomIn"]) {
|
|
274
|
+
cursorType = @"zoom-in";
|
|
275
|
+
}
|
|
276
|
+
else if ([elementSubrole isEqualToString:@"AXZoomOut"]) {
|
|
277
|
+
cursorType = @"zoom-out";
|
|
278
|
+
}
|
|
279
|
+
// Subrole'dan bir şey bulamazsa role-based cursor'ı koruyoruz
|
|
280
|
+
}
|
|
281
|
+
}
|
|
454
282
|
|
|
455
|
-
|
|
456
|
-
// 1. If contextual (Accessibility) gives ANY specific cursor, use it (highest priority)
|
|
457
|
-
if (contextualCursorType != nil) {
|
|
458
|
-
detectedCursorType = contextualCursorType;
|
|
459
|
-
}
|
|
460
|
-
// 2. If NSCursor gives reliable results, use them
|
|
461
|
-
else if (nsCursorType != nil &&
|
|
462
|
-
([nsCursorType isEqualToString:@"text"] ||
|
|
463
|
-
[nsCursorType isEqualToString:@"pointer"] ||
|
|
464
|
-
[nsCursorType hasSuffix:@"resize"])) {
|
|
465
|
-
// Trust NSCursor for these specific types
|
|
466
|
-
detectedCursorType = nsCursorType;
|
|
467
|
-
}
|
|
468
|
-
// 3. Core Graphics context-based detection
|
|
469
|
-
else if (cgCursorType != nil) {
|
|
470
|
-
detectedCursorType = cgCursorType;
|
|
471
|
-
}
|
|
472
|
-
// 4. If NSCursor gives any result, use it as last resort
|
|
473
|
-
else if (nsCursorType != nil) {
|
|
474
|
-
detectedCursorType = nsCursorType;
|
|
475
|
-
}
|
|
476
|
-
// 5. Everything else defaults to default
|
|
477
|
-
else {
|
|
478
|
-
detectedCursorType = @"default";
|
|
283
|
+
CFRelease(elementAtPosition);
|
|
479
284
|
}
|
|
480
285
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
// Ultra-fast response - immediate updates for all cursor types
|
|
485
|
-
if (currentTime - g_lastCursorCheckTime > CURSOR_STABILITY_THRESHOLD) {
|
|
486
|
-
// Immediate update for all cursor types (no confirmation needed)
|
|
487
|
-
g_stableCursorType = detectedCursorType;
|
|
488
|
-
finalCursorType = detectedCursorType;
|
|
489
|
-
g_lastCursorCheckTime = currentTime;
|
|
490
|
-
|
|
491
|
-
// Reset pending state since we're updating immediately
|
|
492
|
-
g_pendingCursorType = nil;
|
|
493
|
-
g_sameCursorDetectionCount = 0;
|
|
494
|
-
} else {
|
|
495
|
-
// Still use immediate update for ultra-responsiveness
|
|
496
|
-
// Only apply minimal delay for very rapid changes
|
|
497
|
-
finalCursorType = detectedCursorType;
|
|
286
|
+
if (systemWide) {
|
|
287
|
+
CFRelease(systemWide);
|
|
498
288
|
}
|
|
499
289
|
|
|
500
|
-
//
|
|
501
|
-
if (!
|
|
502
|
-
|
|
290
|
+
// Son güvence - eğer cursorType hala nil veya geçersizse default'a çevir
|
|
291
|
+
if (!cursorType || [cursorType length] == 0) {
|
|
292
|
+
cursorType = @"default";
|
|
503
293
|
}
|
|
504
294
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
return finalCursorType;
|
|
510
|
-
|
|
295
|
+
NSLog(@"🎯 FINAL CURSOR TYPE: %@", cursorType);
|
|
296
|
+
return cursorType;
|
|
297
|
+
|
|
511
298
|
} @catch (NSException *exception) {
|
|
512
299
|
NSLog(@"Error in getCursorType: %@", exception);
|
|
513
|
-
return
|
|
300
|
+
return @"default";
|
|
514
301
|
}
|
|
515
302
|
}
|
|
516
303
|
}
|
|
@@ -601,15 +388,13 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
|
|
|
601
388
|
}
|
|
602
389
|
|
|
603
390
|
// Cursor data oluştur
|
|
604
|
-
// Cursor data oluştur - global koordinat sistemi kullan
|
|
605
391
|
NSDictionary *cursorInfo = @{
|
|
606
392
|
@"x": @((int)location.x),
|
|
607
393
|
@"y": @((int)location.y),
|
|
608
394
|
@"timestamp": @(timestamp),
|
|
609
395
|
@"unixTimeMs": @(unixTimeMs),
|
|
610
396
|
@"cursorType": cursorType,
|
|
611
|
-
@"type": eventType
|
|
612
|
-
@"coordinateSystem": @"global"
|
|
397
|
+
@"type": eventType
|
|
613
398
|
};
|
|
614
399
|
|
|
615
400
|
// Direkt dosyaya yaz
|
|
@@ -623,16 +408,8 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef eve
|
|
|
623
408
|
void cursorTimerCallback() {
|
|
624
409
|
@autoreleasepool {
|
|
625
410
|
g_debugCallbackCount++; // Timer callback çağrıldığını say
|
|
626
|
-
|
|
627
|
-
static int callCount = 0;
|
|
628
|
-
callCount++;
|
|
629
|
-
if (callCount <= 3) {
|
|
630
|
-
NSLog(@"🔄 Timer callback #%d called", callCount);
|
|
631
|
-
}
|
|
632
|
-
|
|
411
|
+
|
|
633
412
|
if (!g_isCursorTracking || !g_trackingStartTime || !g_fileHandle) {
|
|
634
|
-
NSLog(@"❌ Timer callback stopped - tracking:%d, startTime:%@, fileHandle:%@",
|
|
635
|
-
g_isCursorTracking, g_trackingStartTime ? @"OK" : @"nil", g_fileHandle ? @"OK" : @"nil");
|
|
636
413
|
return;
|
|
637
414
|
}
|
|
638
415
|
|
|
@@ -661,15 +438,13 @@ void cursorTimerCallback() {
|
|
|
661
438
|
NSString *cursorType = getCursorType();
|
|
662
439
|
|
|
663
440
|
// Cursor data oluştur
|
|
664
|
-
// Cursor data oluştur - global koordinat sistemi kullan
|
|
665
441
|
NSDictionary *cursorInfo = @{
|
|
666
442
|
@"x": @((int)location.x),
|
|
667
443
|
@"y": @((int)location.y),
|
|
668
444
|
@"timestamp": @(timestamp),
|
|
669
445
|
@"unixTimeMs": @(unixTimeMs),
|
|
670
446
|
@"cursorType": cursorType,
|
|
671
|
-
@"type": @"move"
|
|
672
|
-
@"coordinateSystem": @"global"
|
|
447
|
+
@"type": @"move"
|
|
673
448
|
};
|
|
674
449
|
|
|
675
450
|
// Direkt dosyaya yaz
|
|
@@ -681,14 +456,16 @@ void cursorTimerCallback() {
|
|
|
681
456
|
void cleanupCursorTracking() {
|
|
682
457
|
g_isCursorTracking = false;
|
|
683
458
|
|
|
684
|
-
//
|
|
459
|
+
// Timer temizle
|
|
685
460
|
if (g_cursorTimer) {
|
|
686
|
-
|
|
687
|
-
dispatch_release(g_cursorTimer);
|
|
461
|
+
[g_cursorTimer invalidate];
|
|
688
462
|
g_cursorTimer = nil;
|
|
689
463
|
}
|
|
690
464
|
|
|
691
|
-
|
|
465
|
+
if (g_timerTarget) {
|
|
466
|
+
[g_timerTarget autorelease];
|
|
467
|
+
g_timerTarget = nil;
|
|
468
|
+
}
|
|
692
469
|
|
|
693
470
|
// Dosyayı önce kapat (en önemli işlem)
|
|
694
471
|
if (g_fileHandle) {
|
|
@@ -724,11 +501,7 @@ void cleanupCursorTracking() {
|
|
|
724
501
|
g_outputPath = nil;
|
|
725
502
|
g_debugCallbackCount = 0;
|
|
726
503
|
g_lastDetectedCursorType = nil;
|
|
727
|
-
g_stableCursorType = @"default";
|
|
728
504
|
g_cursorTypeCounter = 0;
|
|
729
|
-
g_lastCursorCheckTime = 0;
|
|
730
|
-
g_sameCursorDetectionCount = 0;
|
|
731
|
-
g_pendingCursorType = nil;
|
|
732
505
|
g_isFirstWrite = true;
|
|
733
506
|
}
|
|
734
507
|
|
|
@@ -794,30 +567,19 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
|
|
|
794
567
|
CGEventTapEnable(g_eventTap, true);
|
|
795
568
|
}
|
|
796
569
|
|
|
797
|
-
//
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
dispatch_resume(g_cursorTimer);
|
|
812
|
-
}
|
|
813
|
-
|
|
570
|
+
// NSTimer kullan (main thread'de çalışır)
|
|
571
|
+
g_timerTarget = [[CursorTimerTarget alloc] init];
|
|
572
|
+
|
|
573
|
+
g_cursorTimer = [NSTimer timerWithTimeInterval:0.05 // 50ms (20 FPS)
|
|
574
|
+
target:g_timerTarget
|
|
575
|
+
selector:@selector(timerCallback:)
|
|
576
|
+
userInfo:nil
|
|
577
|
+
repeats:YES];
|
|
578
|
+
|
|
579
|
+
// Main run loop'a ekle
|
|
580
|
+
[[NSRunLoop mainRunLoop] addTimer:g_cursorTimer forMode:NSRunLoopCommonModes];
|
|
581
|
+
|
|
814
582
|
g_isCursorTracking = true;
|
|
815
|
-
|
|
816
|
-
NSLog(@"🎯 Native cursor tracking started with timer and event tap");
|
|
817
|
-
NSLog(@"🎯 File handle: %@", g_fileHandle ? @"OK" : @"FAILED");
|
|
818
|
-
NSLog(@"🎯 Timer: %@", g_cursorTimer ? @"OK" : @"FAILED");
|
|
819
|
-
NSLog(@"🎯 Event tap: %@", g_eventTap ? @"OK" : @"FAILED");
|
|
820
|
-
|
|
821
583
|
return Napi::Boolean::New(env, true);
|
|
822
584
|
|
|
823
585
|
} @catch (NSException *exception) {
|