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.
@@ -1,12 +1,8 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(npm test)",
5
- "Bash(node:*)",
6
- "Bash(npm install)",
7
- "Bash(npm run clean:*)",
8
- "Bash(npm run build:*)",
9
- "Bash(npm run rebuild:*)"
4
+ "Bash(node-gyp:*)",
5
+ "Bash(node:*)"
10
6
  ],
11
7
  "deny": [],
12
8
  "ask": []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.19.5",
3
+ "version": "2.20.1",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -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
- // Cursor type detection helper - sistem genelindeki cursor type'ı al
53
- NSString* getCursorType() {
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(@"🎯 FINAL CURSOR TYPE: %@", cursorType);
295
+ NSLog(@"🎯 AX CURSOR TYPE: %@", cursorType);
296
296
  return cursorType;
297
297
 
298
298
  } @catch (NSException *exception) {
299
- NSLog(@"Error in getCursorType: %@", exception);
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 id g_recordingOutput API_AVAILABLE(macos(15.0)) = nil;
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
- Class recordingOutputClass = NSClassFromString(@"SCRecordingOutput");
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 using runtime class lookup
280
- Class recordingConfigClass = NSClassFromString(@"SCRecordingOutputConfiguration");
281
- Class recordingOutputClass = NSClassFromString(@"SCRecordingOutput");
282
-
283
- if (recordingConfigClass && recordingOutputClass) {
284
- id recordingConfig = [[recordingConfigClass alloc] init];
285
- [recordingConfig setValue:outputURL forKey:@"outputURL"];
286
- // Use string constant for video codec
287
- [recordingConfig setValue:@"avc1" forKey:@"videoCodecType"];
288
-
289
- // Audio configuration - using available properties
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
- if (g_recordingOutput && [g_stream respondsToSelector:@selector(addRecordingOutput:error:)]) {
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) {