node-mac-recorder 2.19.5 → 2.20.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/.claude/settings.local.json +2 -6
- package/package.json +1 -1
- package/src/cursor_tracker.mm +237 -9
- package/src/screen_capture_kit.mm +14 -23
package/package.json
CHANGED
package/src/cursor_tracker.mm
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#import <ApplicationServices/ApplicationServices.h>
|
|
6
6
|
#import <Carbon/Carbon.h>
|
|
7
7
|
#import <Accessibility/Accessibility.h>
|
|
8
|
+
#import <dispatch/dispatch.h>
|
|
8
9
|
|
|
9
10
|
// Global state for cursor tracking
|
|
10
11
|
static bool g_isCursorTracking = false;
|
|
@@ -49,16 +50,13 @@ static CGEventRef eventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEv
|
|
|
49
50
|
return event;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
//
|
|
53
|
-
NSString*
|
|
53
|
+
// Accessibility tabanlı cursor tip tespiti
|
|
54
|
+
static NSString* detectCursorTypeUsingAccessibility(CGPoint cursorPos) {
|
|
54
55
|
@autoreleasepool {
|
|
55
|
-
g_cursorTypeCounter++;
|
|
56
|
-
|
|
57
56
|
@try {
|
|
58
57
|
// ACCESSIBILITY API BASED CURSOR DETECTION
|
|
59
58
|
// Determine cursor type based on the UI element under the cursor
|
|
60
59
|
|
|
61
|
-
CGPoint cursorPos = CGEventGetLocation(CGEventCreate(NULL));
|
|
62
60
|
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
63
61
|
AXUIElementRef elementAtPosition = NULL;
|
|
64
62
|
AXError error = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &elementAtPosition);
|
|
@@ -280,9 +278,11 @@ NSString* getCursorType() {
|
|
|
280
278
|
}
|
|
281
279
|
}
|
|
282
280
|
|
|
283
|
-
CFRelease(elementAtPosition);
|
|
284
281
|
}
|
|
285
282
|
|
|
283
|
+
if (elementAtPosition) {
|
|
284
|
+
CFRelease(elementAtPosition);
|
|
285
|
+
}
|
|
286
286
|
if (systemWide) {
|
|
287
287
|
CFRelease(systemWide);
|
|
288
288
|
}
|
|
@@ -292,16 +292,244 @@ NSString* getCursorType() {
|
|
|
292
292
|
cursorType = @"default";
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
-
NSLog(@"🎯
|
|
295
|
+
NSLog(@"🎯 AX CURSOR TYPE: %@", cursorType);
|
|
296
296
|
return cursorType;
|
|
297
297
|
|
|
298
298
|
} @catch (NSException *exception) {
|
|
299
|
-
NSLog(@"Error in
|
|
299
|
+
NSLog(@"Error in detectCursorTypeUsingAccessibility: %@", exception);
|
|
300
300
|
return @"default";
|
|
301
301
|
}
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
static NSString* cursorTypeFromCursorName(NSString *value) {
|
|
306
|
+
if (!value || [value length] == 0) {
|
|
307
|
+
return nil;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
NSString *normalized = [[value stringByReplacingOccurrencesOfString:@"_" withString:@"-"] lowercaseString];
|
|
311
|
+
|
|
312
|
+
if ([normalized containsString:@"arrow"]) {
|
|
313
|
+
return @"default";
|
|
314
|
+
}
|
|
315
|
+
if ([normalized containsString:@"ibeam"] ||
|
|
316
|
+
[normalized containsString:@"insertion"] ||
|
|
317
|
+
[normalized containsString:@"text"]) {
|
|
318
|
+
return @"text";
|
|
319
|
+
}
|
|
320
|
+
if ([normalized containsString:@"openhand"]) {
|
|
321
|
+
return @"grab";
|
|
322
|
+
}
|
|
323
|
+
if ([normalized containsString:@"closedhand"]) {
|
|
324
|
+
return @"grabbing";
|
|
325
|
+
}
|
|
326
|
+
if ([normalized containsString:@"pointing"] ||
|
|
327
|
+
([normalized containsString:@"hand"] && ![normalized containsString:@"closed"])) {
|
|
328
|
+
return @"pointer";
|
|
329
|
+
}
|
|
330
|
+
if ([normalized containsString:@"crosshair"]) {
|
|
331
|
+
return @"crosshair";
|
|
332
|
+
}
|
|
333
|
+
if ([normalized containsString:@"not-allowed"] ||
|
|
334
|
+
[normalized containsString:@"notallowed"] ||
|
|
335
|
+
[normalized containsString:@"forbidden"]) {
|
|
336
|
+
return @"not-allowed";
|
|
337
|
+
}
|
|
338
|
+
if ([normalized containsString:@"dragcopy"] || [normalized containsString:@"copy"]) {
|
|
339
|
+
return @"copy";
|
|
340
|
+
}
|
|
341
|
+
if ([normalized containsString:@"draglink"] || [normalized containsString:@"alias"]) {
|
|
342
|
+
return @"alias";
|
|
343
|
+
}
|
|
344
|
+
if ([normalized containsString:@"context"] && [normalized containsString:@"menu"]) {
|
|
345
|
+
return @"context-menu";
|
|
346
|
+
}
|
|
347
|
+
if ([normalized containsString:@"zoom"]) {
|
|
348
|
+
if ([normalized containsString:@"out"]) {
|
|
349
|
+
return @"zoom-out";
|
|
350
|
+
}
|
|
351
|
+
return @"zoom-in";
|
|
352
|
+
}
|
|
353
|
+
if ([normalized containsString:@"resize"] || [normalized containsString:@"size"]) {
|
|
354
|
+
BOOL diagonalUp = [normalized containsString:@"diagonalup"] || [normalized containsString:@"nesw"];
|
|
355
|
+
BOOL diagonalDown = [normalized containsString:@"diagonaldown"] || [normalized containsString:@"nwse"];
|
|
356
|
+
BOOL horizontal = [normalized containsString:@"leftright"] ||
|
|
357
|
+
[normalized containsString:@"horizontal"] ||
|
|
358
|
+
([normalized containsString:@"left"] && [normalized containsString:@"right"]);
|
|
359
|
+
BOOL vertical = [normalized containsString:@"updown"] ||
|
|
360
|
+
[normalized containsString:@"vertical"] ||
|
|
361
|
+
([normalized containsString:@"up"] && [normalized containsString:@"down"]);
|
|
362
|
+
|
|
363
|
+
if (diagonalUp) {
|
|
364
|
+
return @"nesw-resize";
|
|
365
|
+
}
|
|
366
|
+
if (diagonalDown) {
|
|
367
|
+
return @"nwse-resize";
|
|
368
|
+
}
|
|
369
|
+
if (vertical) {
|
|
370
|
+
return @"ns-resize";
|
|
371
|
+
}
|
|
372
|
+
if (horizontal) {
|
|
373
|
+
return @"col-resize";
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return nil;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
static NSString* cursorTypeFromNSCursor(NSCursor *cursor) {
|
|
381
|
+
if (!cursor) {
|
|
382
|
+
return nil;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (cursor == [NSCursor arrowCursor]) {
|
|
386
|
+
return @"default";
|
|
387
|
+
}
|
|
388
|
+
if (cursor == [NSCursor IBeamCursor]) {
|
|
389
|
+
return @"text";
|
|
390
|
+
}
|
|
391
|
+
if ([NSCursor respondsToSelector:@selector(IBeamCursorForVerticalLayout)] &&
|
|
392
|
+
cursor == [NSCursor IBeamCursorForVerticalLayout]) {
|
|
393
|
+
return @"text";
|
|
394
|
+
}
|
|
395
|
+
if (cursor == [NSCursor pointingHandCursor]) {
|
|
396
|
+
return @"pointer";
|
|
397
|
+
}
|
|
398
|
+
if (cursor == [NSCursor crosshairCursor]) {
|
|
399
|
+
return @"crosshair";
|
|
400
|
+
}
|
|
401
|
+
if (cursor == [NSCursor openHandCursor]) {
|
|
402
|
+
return @"grab";
|
|
403
|
+
}
|
|
404
|
+
if (cursor == [NSCursor closedHandCursor]) {
|
|
405
|
+
return @"grabbing";
|
|
406
|
+
}
|
|
407
|
+
if (cursor == [NSCursor operationNotAllowedCursor]) {
|
|
408
|
+
return @"not-allowed";
|
|
409
|
+
}
|
|
410
|
+
if (cursor == [NSCursor dragCopyCursor]) {
|
|
411
|
+
return @"copy";
|
|
412
|
+
}
|
|
413
|
+
if (cursor == [NSCursor dragLinkCursor]) {
|
|
414
|
+
return @"alias";
|
|
415
|
+
}
|
|
416
|
+
if (cursor == [NSCursor contextualMenuCursor]) {
|
|
417
|
+
return @"context-menu";
|
|
418
|
+
}
|
|
419
|
+
if ([NSCursor respondsToSelector:@selector(resizeLeftRightCursor)] &&
|
|
420
|
+
(cursor == [NSCursor resizeLeftRightCursor] ||
|
|
421
|
+
cursor == [NSCursor resizeLeftCursor] ||
|
|
422
|
+
cursor == [NSCursor resizeRightCursor])) {
|
|
423
|
+
return @"col-resize";
|
|
424
|
+
}
|
|
425
|
+
if ([NSCursor respondsToSelector:@selector(resizeUpDownCursor)] &&
|
|
426
|
+
(cursor == [NSCursor resizeUpDownCursor] ||
|
|
427
|
+
cursor == [NSCursor resizeUpCursor] ||
|
|
428
|
+
cursor == [NSCursor resizeDownCursor])) {
|
|
429
|
+
return @"ns-resize";
|
|
430
|
+
}
|
|
431
|
+
if ([NSCursor respondsToSelector:@selector(disappearingItemCursor)] &&
|
|
432
|
+
cursor == [NSCursor disappearingItemCursor]) {
|
|
433
|
+
return @"default";
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
NSString *derived = cursorTypeFromCursorName(NSStringFromClass([cursor class]));
|
|
437
|
+
if (derived) {
|
|
438
|
+
return derived;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
derived = cursorTypeFromCursorName([cursor description]);
|
|
442
|
+
if (derived) {
|
|
443
|
+
return derived;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return nil;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
static NSString* detectSystemCursorType(void) {
|
|
450
|
+
__block NSString *cursorType = nil;
|
|
451
|
+
|
|
452
|
+
void (^fetchCursorBlock)(void) = ^{
|
|
453
|
+
NSCursor *currentCursor = nil;
|
|
454
|
+
if ([NSCursor respondsToSelector:@selector(currentSystemCursor)]) {
|
|
455
|
+
currentCursor = [NSCursor currentSystemCursor];
|
|
456
|
+
}
|
|
457
|
+
if (!currentCursor) {
|
|
458
|
+
currentCursor = [NSCursor currentCursor];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
NSString *derivedType = cursorTypeFromNSCursor(currentCursor);
|
|
462
|
+
if (derivedType) {
|
|
463
|
+
cursorType = derivedType;
|
|
464
|
+
} else if (currentCursor) {
|
|
465
|
+
cursorType = @"default";
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
NSLog(@"🎯 SYSTEM CURSOR TYPE: %@", cursorType ? cursorType : @"(nil)");
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
if ([NSThread isMainThread]) {
|
|
472
|
+
fetchCursorBlock();
|
|
473
|
+
} else {
|
|
474
|
+
dispatch_sync(dispatch_get_main_queue(), fetchCursorBlock);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return cursorType;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
NSString* getCursorType() {
|
|
481
|
+
@autoreleasepool {
|
|
482
|
+
g_cursorTypeCounter++;
|
|
483
|
+
|
|
484
|
+
NSString *systemCursorType = detectSystemCursorType();
|
|
485
|
+
if (systemCursorType && ![systemCursorType isEqualToString:@"default"]) {
|
|
486
|
+
NSLog(@"🎯 FINAL CURSOR TYPE: %@", systemCursorType);
|
|
487
|
+
return systemCursorType;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
NSString *axCursorType = nil;
|
|
491
|
+
BOOL hasCursorPosition = NO;
|
|
492
|
+
CGPoint cursorPos = CGPointZero;
|
|
493
|
+
|
|
494
|
+
CGEventRef event = CGEventCreate(NULL);
|
|
495
|
+
if (event) {
|
|
496
|
+
cursorPos = CGEventGetLocation(event);
|
|
497
|
+
hasCursorPosition = YES;
|
|
498
|
+
CFRelease(event);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (!hasCursorPosition) {
|
|
502
|
+
if ([NSThread isMainThread]) {
|
|
503
|
+
cursorPos = [NSEvent mouseLocation];
|
|
504
|
+
hasCursorPosition = YES;
|
|
505
|
+
} else {
|
|
506
|
+
__block CGPoint fallbackPos = CGPointZero;
|
|
507
|
+
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
508
|
+
fallbackPos = [NSEvent mouseLocation];
|
|
509
|
+
});
|
|
510
|
+
cursorPos = fallbackPos;
|
|
511
|
+
hasCursorPosition = YES;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (hasCursorPosition) {
|
|
516
|
+
axCursorType = detectCursorTypeUsingAccessibility(cursorPos);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
NSString *finalType = nil;
|
|
520
|
+
if (axCursorType && ![axCursorType isEqualToString:@"default"]) {
|
|
521
|
+
finalType = axCursorType;
|
|
522
|
+
} else if (systemCursorType && [systemCursorType length] > 0) {
|
|
523
|
+
finalType = systemCursorType;
|
|
524
|
+
} else {
|
|
525
|
+
finalType = @"default";
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
NSLog(@"🎯 FINAL CURSOR TYPE: %@", finalType);
|
|
529
|
+
return finalType;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
305
533
|
// Dosyaya yazma helper fonksiyonu
|
|
306
534
|
void writeToFile(NSDictionary *cursorData) {
|
|
307
535
|
@autoreleasepool {
|
|
@@ -860,4 +1088,4 @@ Napi::Object InitCursorTracker(Napi::Env env, Napi::Object exports) {
|
|
|
860
1088
|
exports.Set("getCursorTrackingStatus", Napi::Function::New(env, GetCursorTrackingStatus));
|
|
861
1089
|
|
|
862
1090
|
return exports;
|
|
863
|
-
}
|
|
1091
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// Pure ScreenCaptureKit implementation - NO AVFoundation
|
|
4
4
|
static SCStream * API_AVAILABLE(macos(12.3)) g_stream = nil;
|
|
5
|
-
static
|
|
5
|
+
static SCRecordingOutput * API_AVAILABLE(macos(15.0)) g_recordingOutput = nil;
|
|
6
6
|
static id<SCStreamDelegate> API_AVAILABLE(macos(12.3)) g_streamDelegate = nil;
|
|
7
7
|
static BOOL g_isRecording = NO;
|
|
8
8
|
static BOOL g_isCleaningUp = NO; // Prevent recursive cleanup
|
|
@@ -42,8 +42,7 @@ static NSString *g_outputPath = nil;
|
|
|
42
42
|
|
|
43
43
|
+ (BOOL)isScreenCaptureKitAvailable {
|
|
44
44
|
if (@available(macOS 15.0, *)) {
|
|
45
|
-
|
|
46
|
-
return [SCShareableContent class] != nil && [SCStream class] != nil && recordingOutputClass != nil;
|
|
45
|
+
return [SCShareableContent class] != nil && [SCStream class] != nil && [SCRecordingOutput class] != nil;
|
|
47
46
|
}
|
|
48
47
|
return NO;
|
|
49
48
|
}
|
|
@@ -276,23 +275,17 @@ static NSString *g_outputPath = nil;
|
|
|
276
275
|
}
|
|
277
276
|
|
|
278
277
|
if (@available(macOS 15.0, *)) {
|
|
279
|
-
// Create recording output configuration
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
// Note: Specific audio routing handled by ScreenCaptureKit automatically
|
|
291
|
-
|
|
292
|
-
// Create recording output with correct initializer
|
|
293
|
-
g_recordingOutput = [[recordingOutputClass alloc] initWithConfiguration:recordingConfig
|
|
294
|
-
delegate:nil];
|
|
295
|
-
}
|
|
278
|
+
// Create recording output configuration
|
|
279
|
+
SCRecordingOutputConfiguration *recordingConfig = [[SCRecordingOutputConfiguration alloc] init];
|
|
280
|
+
recordingConfig.outputURL = outputURL;
|
|
281
|
+
recordingConfig.videoCodecType = AVVideoCodecTypeH264;
|
|
282
|
+
|
|
283
|
+
// Audio configuration - using available properties
|
|
284
|
+
// Note: Specific audio routing handled by ScreenCaptureKit automatically
|
|
285
|
+
|
|
286
|
+
// Create recording output with correct initializer
|
|
287
|
+
g_recordingOutput = [[SCRecordingOutput alloc] initWithConfiguration:recordingConfig
|
|
288
|
+
delegate:nil];
|
|
296
289
|
if (shouldCaptureMic && shouldCaptureSystemAudio) {
|
|
297
290
|
NSLog(@"🔧 Created SCRecordingOutput with microphone and system audio");
|
|
298
291
|
} else if (shouldCaptureMic) {
|
|
@@ -327,9 +320,7 @@ static NSString *g_outputPath = nil;
|
|
|
327
320
|
BOOL outputAdded = NO;
|
|
328
321
|
|
|
329
322
|
if (@available(macOS 15.0, *)) {
|
|
330
|
-
|
|
331
|
-
outputAdded = [g_stream addRecordingOutput:g_recordingOutput error:&outputError];
|
|
332
|
-
}
|
|
323
|
+
outputAdded = [g_stream addRecordingOutput:g_recordingOutput error:&outputError];
|
|
333
324
|
}
|
|
334
325
|
|
|
335
326
|
if (!outputAdded || outputError) {
|