node-mac-recorder 2.4.11 → 2.4.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/binding.gyp +5 -12
- package/index.js +25 -104
- package/install.js +2 -19
- package/package.json +2 -6
- package/src/audio_capture.mm +40 -96
- package/src/cursor_tracker.mm +4 -3
- package/src/mac_recorder.mm +673 -753
- package/src/screen_capture.h +0 -5
- package/src/screen_capture.mm +60 -139
- package/src/window_selector.mm +113 -399
- package/window-selector.js +34 -112
- package/ELECTRON-INTEGRATION.md +0 -710
- package/WINDOW_SELECTOR_USAGE.md +0 -447
- package/backup/binding.gyp +0 -44
- package/backup/src/audio_capture.mm +0 -116
- package/backup/src/cursor_tracker.mm +0 -518
- package/backup/src/mac_recorder.mm +0 -829
- package/backup/src/screen_capture.h +0 -19
- package/backup/src/screen_capture.mm +0 -162
- package/backup/src/screen_capture_kit.h +0 -15
- package/backup/src/window_selector.mm +0 -1457
- package/electron-window-selector.js +0 -698
- package/node-mac-recorder-2.4.2.tgz +0 -0
- package/prebuilds/darwin-arm64/node.napi.node +0 -0
- package/test-api-compatibility.js +0 -92
- package/test-audio.js +0 -94
- package/test-comprehensive.js +0 -164
- package/test-electron-window-selector.js +0 -119
- package/test-overlay-fix.js +0 -72
- package/test-recording.js +0 -142
- package/test-sck-availability.js +0 -26
- package/test-sck-simple.js +0 -37
- package/test-sck.js +0 -56
- package/test-screencapture-overlay.js +0 -54
- package/test-simple-windows.js +0 -29
- package/test-sync.js +0 -52
- package/test-window-details.js +0 -34
- package/test-windows.js +0 -57
|
@@ -1,518 +0,0 @@
|
|
|
1
|
-
#import <napi.h>
|
|
2
|
-
#import <AppKit/AppKit.h>
|
|
3
|
-
#import <Foundation/Foundation.h>
|
|
4
|
-
#import <CoreGraphics/CoreGraphics.h>
|
|
5
|
-
#import <ApplicationServices/ApplicationServices.h>
|
|
6
|
-
#import <Carbon/Carbon.h>
|
|
7
|
-
#import <Accessibility/Accessibility.h>
|
|
8
|
-
|
|
9
|
-
// Global state for cursor tracking
|
|
10
|
-
static bool g_isCursorTracking = false;
|
|
11
|
-
static CFMachPortRef g_eventTap = NULL;
|
|
12
|
-
static CFRunLoopSourceRef g_runLoopSource = NULL;
|
|
13
|
-
static NSDate *g_trackingStartTime = nil;
|
|
14
|
-
static NSString *g_outputPath = nil;
|
|
15
|
-
static NSTimer *g_cursorTimer = nil;
|
|
16
|
-
static int g_debugCallbackCount = 0;
|
|
17
|
-
static NSFileHandle *g_fileHandle = nil;
|
|
18
|
-
static bool g_isFirstWrite = true;
|
|
19
|
-
|
|
20
|
-
// Forward declaration
|
|
21
|
-
void cursorTimerCallback();
|
|
22
|
-
void writeToFile(NSDictionary *cursorData);
|
|
23
|
-
|
|
24
|
-
// Timer helper class
|
|
25
|
-
@interface CursorTimerTarget : NSObject
|
|
26
|
-
- (void)timerCallback:(NSTimer *)timer;
|
|
27
|
-
@end
|
|
28
|
-
|
|
29
|
-
@implementation CursorTimerTarget
|
|
30
|
-
- (void)timerCallback:(NSTimer *)timer {
|
|
31
|
-
cursorTimerCallback();
|
|
32
|
-
}
|
|
33
|
-
@end
|
|
34
|
-
|
|
35
|
-
static CursorTimerTarget *g_timerTarget = nil;
|
|
36
|
-
|
|
37
|
-
// Global cursor state tracking
|
|
38
|
-
static NSString *g_lastDetectedCursorType = nil;
|
|
39
|
-
static int g_cursorTypeCounter = 0;
|
|
40
|
-
|
|
41
|
-
// Mouse button state tracking
|
|
42
|
-
static bool g_leftMouseDown = false;
|
|
43
|
-
static bool g_rightMouseDown = false;
|
|
44
|
-
static NSString *g_lastEventType = @"move";
|
|
45
|
-
|
|
46
|
-
// Event tap callback
|
|
47
|
-
static CGEventRef eventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *userInfo) {
|
|
48
|
-
return event;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Cursor type detection helper - sistem genelindeki cursor type'ı al
|
|
52
|
-
NSString* getCursorType() {
|
|
53
|
-
@autoreleasepool {
|
|
54
|
-
g_cursorTypeCounter++;
|
|
55
|
-
|
|
56
|
-
@try {
|
|
57
|
-
// Get current cursor info
|
|
58
|
-
NSCursor *currentCursor = [NSCursor currentSystemCursor];
|
|
59
|
-
NSString *cursorType = @"default";
|
|
60
|
-
|
|
61
|
-
// Get cursor image info
|
|
62
|
-
NSImage *cursorImage = [currentCursor image];
|
|
63
|
-
NSPoint hotSpot = [currentCursor hotSpot];
|
|
64
|
-
NSSize imageSize = [cursorImage size];
|
|
65
|
-
|
|
66
|
-
// Check cursor type by comparing with standard cursors
|
|
67
|
-
if ([currentCursor isEqual:[NSCursor pointingHandCursor]] ||
|
|
68
|
-
(hotSpot.x >= 5 && hotSpot.x <= 7 && hotSpot.y >= 0 && hotSpot.y <= 4) ||
|
|
69
|
-
(hotSpot.x >= 12 && hotSpot.x <= 14 && hotSpot.y >= 7 && hotSpot.y <= 9)) {
|
|
70
|
-
return @"pointer";
|
|
71
|
-
} else if ([currentCursor isEqual:[NSCursor IBeamCursor]] ||
|
|
72
|
-
(hotSpot.x >= 3 && hotSpot.x <= 5 && hotSpot.y >= 8 && hotSpot.y <= 10 &&
|
|
73
|
-
imageSize.width <= 10 && imageSize.height >= 16)) {
|
|
74
|
-
return @"text";
|
|
75
|
-
} else if ([currentCursor isEqual:[NSCursor resizeLeftRightCursor]]) {
|
|
76
|
-
return @"ew-resize";
|
|
77
|
-
} else if ([currentCursor isEqual:[NSCursor resizeUpDownCursor]]) {
|
|
78
|
-
return @"ns-resize";
|
|
79
|
-
} else if ([currentCursor isEqual:[NSCursor openHandCursor]] ||
|
|
80
|
-
[currentCursor isEqual:[NSCursor closedHandCursor]]) {
|
|
81
|
-
return @"grabbing";
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Check if we're in a drag operation
|
|
85
|
-
CGEventRef event = CGEventCreate(NULL);
|
|
86
|
-
if (event) {
|
|
87
|
-
CGEventType eventType = (CGEventType)CGEventGetType(event);
|
|
88
|
-
if (eventType == kCGEventLeftMouseDragged ||
|
|
89
|
-
eventType == kCGEventRightMouseDragged) {
|
|
90
|
-
CFRelease(event);
|
|
91
|
-
return @"grabbing";
|
|
92
|
-
}
|
|
93
|
-
CFRelease(event);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Get the window under the cursor
|
|
97
|
-
CGPoint cursorPos = CGEventGetLocation(CGEventCreate(NULL));
|
|
98
|
-
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
99
|
-
AXUIElementRef elementAtPosition = NULL;
|
|
100
|
-
AXError error = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &elementAtPosition);
|
|
101
|
-
|
|
102
|
-
if (error == kAXErrorSuccess && elementAtPosition) {
|
|
103
|
-
CFStringRef role = NULL;
|
|
104
|
-
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXRoleAttribute, (CFTypeRef*)&role);
|
|
105
|
-
|
|
106
|
-
if (error == kAXErrorSuccess && role) {
|
|
107
|
-
NSString *elementRole = (__bridge_transfer NSString*)role;
|
|
108
|
-
|
|
109
|
-
// Check for clickable elements that should show pointer cursor
|
|
110
|
-
if ([elementRole isEqualToString:@"AXLink"] ||
|
|
111
|
-
[elementRole isEqualToString:@"AXButton"] ||
|
|
112
|
-
[elementRole isEqualToString:@"AXMenuItem"] ||
|
|
113
|
-
[elementRole isEqualToString:@"AXRadioButton"] ||
|
|
114
|
-
[elementRole isEqualToString:@"AXCheckBox"]) {
|
|
115
|
-
return @"pointer";
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Check subrole for additional pointer cursor elements
|
|
119
|
-
CFStringRef subrole = NULL;
|
|
120
|
-
error = AXUIElementCopyAttributeValue(elementAtPosition, kAXSubroleAttribute, (CFTypeRef*)&subrole);
|
|
121
|
-
if (error == kAXErrorSuccess && subrole) {
|
|
122
|
-
NSString *elementSubrole = (__bridge_transfer NSString*)subrole;
|
|
123
|
-
|
|
124
|
-
if ([elementSubrole isEqualToString:@"AXClickable"] ||
|
|
125
|
-
[elementSubrole isEqualToString:@"AXDisclosureTriangle"] ||
|
|
126
|
-
[elementSubrole isEqualToString:@"AXToolbarButton"] ||
|
|
127
|
-
[elementSubrole isEqualToString:@"AXCloseButton"] ||
|
|
128
|
-
[elementSubrole isEqualToString:@"AXMinimizeButton"] ||
|
|
129
|
-
[elementSubrole isEqualToString:@"AXZoomButton"]) {
|
|
130
|
-
return @"pointer";
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Check for text elements
|
|
135
|
-
if ([elementRole isEqualToString:@"AXTextField"] ||
|
|
136
|
-
[elementRole isEqualToString:@"AXTextArea"] ||
|
|
137
|
-
[elementRole isEqualToString:@"AXStaticText"]) {
|
|
138
|
-
return @"text";
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
CFRelease(elementAtPosition);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (systemWide) {
|
|
146
|
-
CFRelease(systemWide);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return cursorType;
|
|
150
|
-
|
|
151
|
-
} @catch (NSException *exception) {
|
|
152
|
-
NSLog(@"Error in getCursorType: %@", exception);
|
|
153
|
-
return @"default";
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Dosyaya yazma helper fonksiyonu
|
|
159
|
-
void writeToFile(NSDictionary *cursorData) {
|
|
160
|
-
@autoreleasepool {
|
|
161
|
-
if (!g_fileHandle || !cursorData) {
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
@try {
|
|
166
|
-
NSError *error;
|
|
167
|
-
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:cursorData
|
|
168
|
-
options:0
|
|
169
|
-
error:&error];
|
|
170
|
-
if (jsonData && !error) {
|
|
171
|
-
NSString *jsonString = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] autorelease];
|
|
172
|
-
|
|
173
|
-
if (g_isFirstWrite) {
|
|
174
|
-
// İlk yazma - array başlat
|
|
175
|
-
[g_fileHandle writeData:[@"[" dataUsingEncoding:NSUTF8StringEncoding]];
|
|
176
|
-
[g_fileHandle writeData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]];
|
|
177
|
-
g_isFirstWrite = false;
|
|
178
|
-
} else {
|
|
179
|
-
// Sonraki yazmalar - virgül + json
|
|
180
|
-
[g_fileHandle writeData:[@"," dataUsingEncoding:NSUTF8StringEncoding]];
|
|
181
|
-
[g_fileHandle writeData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]];
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
[g_fileHandle synchronizeFile];
|
|
185
|
-
}
|
|
186
|
-
} @catch (NSException *exception) {
|
|
187
|
-
// Hata durumunda sessizce devam et
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Event callback for mouse events
|
|
193
|
-
CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
|
|
194
|
-
@autoreleasepool {
|
|
195
|
-
g_debugCallbackCount++; // Callback çağrıldığını say
|
|
196
|
-
|
|
197
|
-
if (!g_isCursorTracking || !g_trackingStartTime || !g_fileHandle) {
|
|
198
|
-
return event;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
CGPoint location = CGEventGetLocation(event);
|
|
202
|
-
NSDate *currentDate = [NSDate date];
|
|
203
|
-
NSTimeInterval timestamp = [currentDate timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
|
|
204
|
-
NSTimeInterval unixTimeMs = [currentDate timeIntervalSince1970] * 1000; // unix timestamp in milliseconds
|
|
205
|
-
NSString *cursorType = getCursorType();
|
|
206
|
-
NSString *eventType = @"move";
|
|
207
|
-
|
|
208
|
-
// Event tipini belirle
|
|
209
|
-
switch (type) {
|
|
210
|
-
case kCGEventLeftMouseDown:
|
|
211
|
-
case kCGEventRightMouseDown:
|
|
212
|
-
case kCGEventOtherMouseDown:
|
|
213
|
-
eventType = @"mousedown";
|
|
214
|
-
break;
|
|
215
|
-
case kCGEventLeftMouseUp:
|
|
216
|
-
case kCGEventRightMouseUp:
|
|
217
|
-
case kCGEventOtherMouseUp:
|
|
218
|
-
eventType = @"mouseup";
|
|
219
|
-
break;
|
|
220
|
-
case kCGEventLeftMouseDragged:
|
|
221
|
-
case kCGEventRightMouseDragged:
|
|
222
|
-
case kCGEventOtherMouseDragged:
|
|
223
|
-
eventType = @"drag";
|
|
224
|
-
break;
|
|
225
|
-
case kCGEventMouseMoved:
|
|
226
|
-
default:
|
|
227
|
-
eventType = @"move";
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Cursor data oluştur
|
|
232
|
-
NSDictionary *cursorInfo = @{
|
|
233
|
-
@"x": @((int)location.x),
|
|
234
|
-
@"y": @((int)location.y),
|
|
235
|
-
@"timestamp": @(timestamp),
|
|
236
|
-
@"unixTimeMs": @(unixTimeMs),
|
|
237
|
-
@"cursorType": cursorType,
|
|
238
|
-
@"type": eventType
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
// Direkt dosyaya yaz
|
|
242
|
-
writeToFile(cursorInfo);
|
|
243
|
-
|
|
244
|
-
return event;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Timer callback for periodic cursor position updates
|
|
249
|
-
void cursorTimerCallback() {
|
|
250
|
-
@autoreleasepool {
|
|
251
|
-
g_debugCallbackCount++; // Timer callback çağrıldığını say
|
|
252
|
-
|
|
253
|
-
if (!g_isCursorTracking || !g_trackingStartTime || !g_fileHandle) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// CGEventGetLocation direkt global koordinat verir - çoklu ekran desteği için daha doğru
|
|
258
|
-
CGEventRef event = CGEventCreate(NULL);
|
|
259
|
-
CGPoint location = CGEventGetLocation(event);
|
|
260
|
-
if (event) {
|
|
261
|
-
CFRelease(event);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
NSDate *currentDate = [NSDate date];
|
|
265
|
-
NSTimeInterval timestamp = [currentDate timeIntervalSinceDate:g_trackingStartTime] * 1000; // milliseconds
|
|
266
|
-
NSTimeInterval unixTimeMs = [currentDate timeIntervalSince1970] * 1000; // unix timestamp in milliseconds
|
|
267
|
-
NSString *cursorType = getCursorType();
|
|
268
|
-
|
|
269
|
-
// Cursor data oluştur
|
|
270
|
-
NSDictionary *cursorInfo = @{
|
|
271
|
-
@"x": @((int)location.x),
|
|
272
|
-
@"y": @((int)location.y),
|
|
273
|
-
@"timestamp": @(timestamp),
|
|
274
|
-
@"unixTimeMs": @(unixTimeMs),
|
|
275
|
-
@"cursorType": cursorType,
|
|
276
|
-
@"type": @"move"
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
// Direkt dosyaya yaz
|
|
280
|
-
writeToFile(cursorInfo);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Helper function to cleanup cursor tracking
|
|
285
|
-
void cleanupCursorTracking() {
|
|
286
|
-
g_isCursorTracking = false;
|
|
287
|
-
|
|
288
|
-
// Timer temizle
|
|
289
|
-
if (g_cursorTimer) {
|
|
290
|
-
[g_cursorTimer invalidate];
|
|
291
|
-
g_cursorTimer = nil;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (g_timerTarget) {
|
|
295
|
-
[g_timerTarget autorelease];
|
|
296
|
-
g_timerTarget = nil;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Dosyayı önce kapat (en önemli işlem)
|
|
300
|
-
if (g_fileHandle) {
|
|
301
|
-
@try {
|
|
302
|
-
if (g_isFirstWrite) {
|
|
303
|
-
// Hiç veri yazılmamışsa boş array
|
|
304
|
-
[g_fileHandle writeData:[@"[]" dataUsingEncoding:NSUTF8StringEncoding]];
|
|
305
|
-
} else {
|
|
306
|
-
// JSON array'i kapat
|
|
307
|
-
[g_fileHandle writeData:[@"]" dataUsingEncoding:NSUTF8StringEncoding]];
|
|
308
|
-
}
|
|
309
|
-
[g_fileHandle synchronizeFile];
|
|
310
|
-
[g_fileHandle closeFile];
|
|
311
|
-
} @catch (NSException *exception) {
|
|
312
|
-
// Dosya işlemi hata verirse sessizce devam et
|
|
313
|
-
}
|
|
314
|
-
g_fileHandle = nil;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Event tap'i durdur (non-blocking)
|
|
318
|
-
if (g_eventTap) {
|
|
319
|
-
CGEventTapEnable(g_eventTap, false);
|
|
320
|
-
g_eventTap = NULL; // CFRelease işlemini yapmıyoruz - system handle etsin
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Run loop source'unu kaldır (non-blocking)
|
|
324
|
-
if (g_runLoopSource) {
|
|
325
|
-
g_runLoopSource = NULL; // CFRelease işlemini yapmıyoruz
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Global değişkenleri sıfırla
|
|
329
|
-
g_trackingStartTime = nil;
|
|
330
|
-
g_outputPath = nil;
|
|
331
|
-
g_debugCallbackCount = 0;
|
|
332
|
-
g_lastDetectedCursorType = nil;
|
|
333
|
-
g_cursorTypeCounter = 0;
|
|
334
|
-
g_isFirstWrite = true;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// NAPI Function: Start Cursor Tracking
|
|
338
|
-
Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
|
|
339
|
-
Napi::Env env = info.Env();
|
|
340
|
-
|
|
341
|
-
if (info.Length() < 1) {
|
|
342
|
-
Napi::TypeError::New(env, "Output path required").ThrowAsJavaScriptException();
|
|
343
|
-
return env.Null();
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (g_isCursorTracking) {
|
|
347
|
-
return Napi::Boolean::New(env, false);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
std::string outputPath = info[0].As<Napi::String>().Utf8Value();
|
|
351
|
-
|
|
352
|
-
@try {
|
|
353
|
-
// Dosyayı oluştur ve aç
|
|
354
|
-
g_outputPath = [NSString stringWithUTF8String:outputPath.c_str()];
|
|
355
|
-
g_fileHandle = [[NSFileHandle fileHandleForWritingAtPath:g_outputPath] retain];
|
|
356
|
-
|
|
357
|
-
if (!g_fileHandle) {
|
|
358
|
-
// Dosya yoksa oluştur
|
|
359
|
-
[[NSFileManager defaultManager] createFileAtPath:g_outputPath contents:nil attributes:nil];
|
|
360
|
-
g_fileHandle = [[NSFileHandle fileHandleForWritingAtPath:g_outputPath] retain];
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (!g_fileHandle) {
|
|
364
|
-
return Napi::Boolean::New(env, false);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Dosyayı temizle (baştan başla)
|
|
368
|
-
[g_fileHandle truncateFileAtOffset:0];
|
|
369
|
-
g_isFirstWrite = true;
|
|
370
|
-
|
|
371
|
-
g_trackingStartTime = [NSDate date];
|
|
372
|
-
|
|
373
|
-
// Create event tap for mouse events
|
|
374
|
-
CGEventMask eventMask = (CGEventMaskBit(kCGEventLeftMouseDown) |
|
|
375
|
-
CGEventMaskBit(kCGEventLeftMouseUp) |
|
|
376
|
-
CGEventMaskBit(kCGEventRightMouseDown) |
|
|
377
|
-
CGEventMaskBit(kCGEventRightMouseUp) |
|
|
378
|
-
CGEventMaskBit(kCGEventOtherMouseDown) |
|
|
379
|
-
CGEventMaskBit(kCGEventOtherMouseUp) |
|
|
380
|
-
CGEventMaskBit(kCGEventMouseMoved) |
|
|
381
|
-
CGEventMaskBit(kCGEventLeftMouseDragged) |
|
|
382
|
-
CGEventMaskBit(kCGEventRightMouseDragged) |
|
|
383
|
-
CGEventMaskBit(kCGEventOtherMouseDragged));
|
|
384
|
-
|
|
385
|
-
g_eventTap = CGEventTapCreate(kCGSessionEventTap,
|
|
386
|
-
kCGHeadInsertEventTap,
|
|
387
|
-
kCGEventTapOptionListenOnly,
|
|
388
|
-
eventMask,
|
|
389
|
-
eventCallback,
|
|
390
|
-
NULL);
|
|
391
|
-
|
|
392
|
-
if (g_eventTap) {
|
|
393
|
-
// Event tap başarılı - detaylı event tracking aktif
|
|
394
|
-
g_runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, g_eventTap, 0);
|
|
395
|
-
CFRunLoopAddSource(CFRunLoopGetMain(), g_runLoopSource, kCFRunLoopCommonModes);
|
|
396
|
-
CGEventTapEnable(g_eventTap, true);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// NSTimer kullan (main thread'de çalışır)
|
|
400
|
-
g_timerTarget = [[CursorTimerTarget alloc] init];
|
|
401
|
-
|
|
402
|
-
g_cursorTimer = [NSTimer timerWithTimeInterval:0.05 // 50ms (20 FPS)
|
|
403
|
-
target:g_timerTarget
|
|
404
|
-
selector:@selector(timerCallback:)
|
|
405
|
-
userInfo:nil
|
|
406
|
-
repeats:YES];
|
|
407
|
-
|
|
408
|
-
// Main run loop'a ekle
|
|
409
|
-
[[NSRunLoop mainRunLoop] addTimer:g_cursorTimer forMode:NSRunLoopCommonModes];
|
|
410
|
-
|
|
411
|
-
g_isCursorTracking = true;
|
|
412
|
-
return Napi::Boolean::New(env, true);
|
|
413
|
-
|
|
414
|
-
} @catch (NSException *exception) {
|
|
415
|
-
cleanupCursorTracking();
|
|
416
|
-
return Napi::Boolean::New(env, false);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// NAPI Function: Stop Cursor Tracking
|
|
421
|
-
Napi::Value StopCursorTracking(const Napi::CallbackInfo& info) {
|
|
422
|
-
Napi::Env env = info.Env();
|
|
423
|
-
|
|
424
|
-
if (!g_isCursorTracking) {
|
|
425
|
-
return Napi::Boolean::New(env, false);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
@try {
|
|
429
|
-
cleanupCursorTracking();
|
|
430
|
-
return Napi::Boolean::New(env, true);
|
|
431
|
-
|
|
432
|
-
} @catch (NSException *exception) {
|
|
433
|
-
cleanupCursorTracking();
|
|
434
|
-
return Napi::Boolean::New(env, false);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// NAPI Function: Get Current Cursor Position
|
|
439
|
-
Napi::Value GetCursorPosition(const Napi::CallbackInfo& info) {
|
|
440
|
-
Napi::Env env = info.Env();
|
|
441
|
-
|
|
442
|
-
@try {
|
|
443
|
-
// NSEvent mouseLocation zaten global koordinatlarda (all displays combined)
|
|
444
|
-
// CGEventGetLocation kullanarak direkt global koordinat al - daha doğru
|
|
445
|
-
CGEventRef event = CGEventCreate(NULL);
|
|
446
|
-
CGPoint location = CGEventGetLocation(event);
|
|
447
|
-
if (event) {
|
|
448
|
-
CFRelease(event);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
NSString *cursorType = getCursorType();
|
|
452
|
-
|
|
453
|
-
// Mouse button state'ini kontrol et
|
|
454
|
-
bool currentLeftMouseDown = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonLeft);
|
|
455
|
-
bool currentRightMouseDown = CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonRight);
|
|
456
|
-
|
|
457
|
-
NSString *eventType = @"move";
|
|
458
|
-
|
|
459
|
-
// Mouse button state değişikliklerini tespit et
|
|
460
|
-
if (currentLeftMouseDown && !g_leftMouseDown) {
|
|
461
|
-
eventType = @"mousedown";
|
|
462
|
-
g_lastEventType = @"mousedown";
|
|
463
|
-
} else if (!currentLeftMouseDown && g_leftMouseDown) {
|
|
464
|
-
eventType = @"mouseup";
|
|
465
|
-
g_lastEventType = @"mouseup";
|
|
466
|
-
} else if (currentRightMouseDown && !g_rightMouseDown) {
|
|
467
|
-
eventType = @"rightmousedown";
|
|
468
|
-
g_lastEventType = @"rightmousedown";
|
|
469
|
-
} else if (!currentRightMouseDown && g_rightMouseDown) {
|
|
470
|
-
eventType = @"rightmouseup";
|
|
471
|
-
g_lastEventType = @"rightmouseup";
|
|
472
|
-
} else {
|
|
473
|
-
eventType = @"move";
|
|
474
|
-
g_lastEventType = @"move";
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// State'i güncelle
|
|
478
|
-
g_leftMouseDown = currentLeftMouseDown;
|
|
479
|
-
g_rightMouseDown = currentRightMouseDown;
|
|
480
|
-
|
|
481
|
-
Napi::Object result = Napi::Object::New(env);
|
|
482
|
-
result.Set("x", Napi::Number::New(env, (int)location.x));
|
|
483
|
-
result.Set("y", Napi::Number::New(env, (int)location.y));
|
|
484
|
-
result.Set("cursorType", Napi::String::New(env, [cursorType UTF8String]));
|
|
485
|
-
result.Set("eventType", Napi::String::New(env, [eventType UTF8String]));
|
|
486
|
-
|
|
487
|
-
return result;
|
|
488
|
-
|
|
489
|
-
} @catch (NSException *exception) {
|
|
490
|
-
return env.Null();
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// NAPI Function: Get Cursor Tracking Status
|
|
495
|
-
Napi::Value GetCursorTrackingStatus(const Napi::CallbackInfo& info) {
|
|
496
|
-
Napi::Env env = info.Env();
|
|
497
|
-
|
|
498
|
-
Napi::Object result = Napi::Object::New(env);
|
|
499
|
-
result.Set("isTracking", Napi::Boolean::New(env, g_isCursorTracking));
|
|
500
|
-
result.Set("hasEventTap", Napi::Boolean::New(env, g_eventTap != NULL));
|
|
501
|
-
result.Set("hasRunLoopSource", Napi::Boolean::New(env, g_runLoopSource != NULL));
|
|
502
|
-
result.Set("hasFileHandle", Napi::Boolean::New(env, g_fileHandle != NULL));
|
|
503
|
-
result.Set("hasTimer", Napi::Boolean::New(env, g_cursorTimer != NULL));
|
|
504
|
-
result.Set("debugCallbackCount", Napi::Number::New(env, g_debugCallbackCount));
|
|
505
|
-
result.Set("cursorTypeCounter", Napi::Number::New(env, g_cursorTypeCounter));
|
|
506
|
-
|
|
507
|
-
return result;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// Export functions
|
|
511
|
-
Napi::Object InitCursorTracker(Napi::Env env, Napi::Object exports) {
|
|
512
|
-
exports.Set("startCursorTracking", Napi::Function::New(env, StartCursorTracking));
|
|
513
|
-
exports.Set("stopCursorTracking", Napi::Function::New(env, StopCursorTracking));
|
|
514
|
-
exports.Set("getCursorPosition", Napi::Function::New(env, GetCursorPosition));
|
|
515
|
-
exports.Set("getCursorTrackingStatus", Napi::Function::New(env, GetCursorTrackingStatus));
|
|
516
|
-
|
|
517
|
-
return exports;
|
|
518
|
-
}
|