node-mac-recorder 2.22.33 → 2.22.34
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/MultiWindowRecorder.js +0 -1
- package/index.js +2 -15
- package/package.json +2 -4
- package/src/cursor_tracker.mm +47 -381
- package/src/electron_safe/cursor_tracker_electron.mm +4 -36
- package/src/electron_safe/window_selector_electron.mm +10 -6
- package/src/mac_recorder.mm +6 -0
- package/scripts/cursor-type-live.js +0 -32
- package/scripts/cursor-types-15s-test.js +0 -122
package/MultiWindowRecorder.js
CHANGED
|
@@ -223,7 +223,6 @@ class MultiWindowRecorder extends EventEmitter {
|
|
|
223
223
|
} : null,
|
|
224
224
|
recordingType: 'multi-window', // Multi-window recording type
|
|
225
225
|
startTimestamp: startTimestamp, // Use same timestamp as video
|
|
226
|
-
interval: 33,
|
|
227
226
|
// Pass window information for location detection
|
|
228
227
|
multiWindowBounds: windowBounds
|
|
229
228
|
};
|
package/index.js
CHANGED
|
@@ -869,8 +869,7 @@ class MacRecorder extends EventEmitter {
|
|
|
869
869
|
this.options.captureArea ? 'area' : 'display',
|
|
870
870
|
captureArea: this.options.captureArea,
|
|
871
871
|
windowId: this.options.windowId,
|
|
872
|
-
startTimestamp: syncTimestamp
|
|
873
|
-
interval: 33,
|
|
872
|
+
startTimestamp: syncTimestamp // Align cursor timeline to actual start
|
|
874
873
|
};
|
|
875
874
|
|
|
876
875
|
try {
|
|
@@ -1349,13 +1348,6 @@ class MacRecorder extends EventEmitter {
|
|
|
1349
1348
|
return true; // İlk event
|
|
1350
1349
|
}
|
|
1351
1350
|
|
|
1352
|
-
if (
|
|
1353
|
-
currentData.type === "drag" ||
|
|
1354
|
-
currentData.type === "rightdrag"
|
|
1355
|
-
) {
|
|
1356
|
-
return true;
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
1351
|
const last = this.lastCapturedData;
|
|
1360
1352
|
|
|
1361
1353
|
// Event type değişmişse
|
|
@@ -1390,11 +1382,10 @@ class MacRecorder extends EventEmitter {
|
|
|
1390
1382
|
* @param {Object} options.captureArea - Capture area for area recording coordinate transformation
|
|
1391
1383
|
* @param {number} options.windowId - Window ID for window recording coordinate transformation
|
|
1392
1384
|
* @param {number} options.startTimestamp - Pre-defined start timestamp for synchronization (optional)
|
|
1393
|
-
* @param {number} options.interval - Örnekleme aralığı (ms), filepath ile çağrıda kullanılır; varsayılan 33ms (~30 Hz)
|
|
1394
1385
|
*/
|
|
1395
1386
|
async startCursorCapture(intervalOrFilepath = 100, options = {}) {
|
|
1396
1387
|
let filepath;
|
|
1397
|
-
let interval =
|
|
1388
|
+
let interval = 20; // Default 50 FPS
|
|
1398
1389
|
|
|
1399
1390
|
// Parameter parsing: number = interval, string = filepath
|
|
1400
1391
|
if (typeof intervalOrFilepath === "number") {
|
|
@@ -1408,10 +1399,6 @@ class MacRecorder extends EventEmitter {
|
|
|
1408
1399
|
);
|
|
1409
1400
|
}
|
|
1410
1401
|
|
|
1411
|
-
if (typeof options.interval === "number") {
|
|
1412
|
-
interval = Math.max(10, options.interval);
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
1402
|
if (this.cursorCaptureInterval) {
|
|
1416
1403
|
throw new Error("Cursor capture is already running");
|
|
1417
1404
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-mac-recorder",
|
|
3
|
-
"version": "2.22.
|
|
3
|
+
"version": "2.22.34",
|
|
4
4
|
"description": "Native macOS screen recording package for Node.js applications",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -43,9 +43,7 @@
|
|
|
43
43
|
"build:electron-safe": "node build-electron-safe.js",
|
|
44
44
|
"test:electron-safe": "node test-electron-safe.js",
|
|
45
45
|
"clean:electron-safe": "node-gyp clean && rm -rf build",
|
|
46
|
-
"canvas": "node make-canvas.js"
|
|
47
|
-
"cursor:live": "node scripts/cursor-type-live.js",
|
|
48
|
-
"test:cursor-types": "node scripts/cursor-types-15s-test.js"
|
|
46
|
+
"canvas": "node make-canvas.js"
|
|
49
47
|
},
|
|
50
48
|
"dependencies": {
|
|
51
49
|
"node-addon-api": "^7.0.0"
|
package/src/cursor_tracker.mm
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
#import <dispatch/dispatch.h>
|
|
9
9
|
#import "logging.h"
|
|
10
10
|
#include <vector>
|
|
11
|
-
#include <cstring>
|
|
12
11
|
#include <math.h>
|
|
13
12
|
|
|
14
13
|
#ifndef kAXHitTestParameterizedAttribute
|
|
@@ -310,180 +309,6 @@ static NSCursor* CursorFromSelector(SEL selector) {
|
|
|
310
309
|
return func([NSCursor class], selector);
|
|
311
310
|
}
|
|
312
311
|
|
|
313
|
-
static BOOL CursorEqualsFactoryNamed(NSCursor *cursor, NSString *factoryMethodName) {
|
|
314
|
-
if (!cursor || !factoryMethodName || [factoryMethodName length] == 0) {
|
|
315
|
-
return NO;
|
|
316
|
-
}
|
|
317
|
-
SEL sel = NSSelectorFromString(factoryMethodName);
|
|
318
|
-
NSCursor *ref = CursorFromSelector(sel);
|
|
319
|
-
return ref != nil && cursor == ref;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
static uint64_t CursorImagePixelHash(NSImage *image) {
|
|
323
|
-
if (!image) {
|
|
324
|
-
return 0;
|
|
325
|
-
}
|
|
326
|
-
NSRect imageRect = NSMakeRect(0, 0, [image size].width, [image size].height);
|
|
327
|
-
CGImageRef cgImage = [image CGImageForProposedRect:&imageRect context:nil hints:nil];
|
|
328
|
-
if (!cgImage) {
|
|
329
|
-
for (NSImageRep *rep in [image representations]) {
|
|
330
|
-
if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
|
|
331
|
-
cgImage = [(NSBitmapImageRep *)rep CGImage];
|
|
332
|
-
if (cgImage) {
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
if (!cgImage) {
|
|
339
|
-
return 0;
|
|
340
|
-
}
|
|
341
|
-
size_t width = CGImageGetWidth(cgImage);
|
|
342
|
-
size_t height = CGImageGetHeight(cgImage);
|
|
343
|
-
if (width == 0 || height == 0) {
|
|
344
|
-
return 0;
|
|
345
|
-
}
|
|
346
|
-
size_t bytesPerPixel = 4;
|
|
347
|
-
size_t bytesPerRow = width * bytesPerPixel;
|
|
348
|
-
size_t bufferSize = bytesPerRow * height;
|
|
349
|
-
if (bufferSize == 0) {
|
|
350
|
-
return 0;
|
|
351
|
-
}
|
|
352
|
-
std::vector<unsigned char> buffer(bufferSize);
|
|
353
|
-
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
354
|
-
if (!colorSpace) {
|
|
355
|
-
return 0;
|
|
356
|
-
}
|
|
357
|
-
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Little | (CGBitmapInfo)kCGImageAlphaPremultipliedLast;
|
|
358
|
-
CGContextRef context = CGBitmapContextCreate(buffer.data(),
|
|
359
|
-
width,
|
|
360
|
-
height,
|
|
361
|
-
8,
|
|
362
|
-
bytesPerRow,
|
|
363
|
-
colorSpace,
|
|
364
|
-
bitmapInfo);
|
|
365
|
-
CGColorSpaceRelease(colorSpace);
|
|
366
|
-
if (!context) {
|
|
367
|
-
return 0;
|
|
368
|
-
}
|
|
369
|
-
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
|
|
370
|
-
CGContextRelease(context);
|
|
371
|
-
return FNV1AHash(buffer.data(), buffer.size());
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
static uint64_t CursorImagePixelHashNormalized32(NSImage *image) {
|
|
375
|
-
if (!image) {
|
|
376
|
-
return 0;
|
|
377
|
-
}
|
|
378
|
-
const int dim = 32;
|
|
379
|
-
size_t bytesPerPixel = 4;
|
|
380
|
-
size_t bytesPerRow = (size_t)dim * bytesPerPixel;
|
|
381
|
-
std::vector<unsigned char> buffer(bytesPerRow * (size_t)dim);
|
|
382
|
-
memset(buffer.data(), 0, buffer.size());
|
|
383
|
-
|
|
384
|
-
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
385
|
-
if (!colorSpace) {
|
|
386
|
-
return 0;
|
|
387
|
-
}
|
|
388
|
-
CGContextRef ctx = CGBitmapContextCreate(buffer.data(),
|
|
389
|
-
(size_t)dim,
|
|
390
|
-
(size_t)dim,
|
|
391
|
-
8,
|
|
392
|
-
bytesPerRow,
|
|
393
|
-
colorSpace,
|
|
394
|
-
kCGBitmapByteOrder32Little | (CGBitmapInfo)kCGImageAlphaPremultipliedLast);
|
|
395
|
-
CGColorSpaceRelease(colorSpace);
|
|
396
|
-
if (!ctx) {
|
|
397
|
-
return 0;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
CGContextClearRect(ctx, CGRectMake(0, 0, dim, dim));
|
|
401
|
-
NSGraphicsContext *nsgc = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:YES];
|
|
402
|
-
[NSGraphicsContext saveGraphicsState];
|
|
403
|
-
[NSGraphicsContext setCurrentContext:nsgc];
|
|
404
|
-
|
|
405
|
-
NSSize sz = [image size];
|
|
406
|
-
if (sz.width <= 0 || sz.height <= 0) {
|
|
407
|
-
[NSGraphicsContext restoreGraphicsState];
|
|
408
|
-
CGContextRelease(ctx);
|
|
409
|
-
return 0;
|
|
410
|
-
}
|
|
411
|
-
CGFloat scale = MIN((CGFloat)dim / sz.width, (CGFloat)dim / sz.height);
|
|
412
|
-
CGFloat rw = sz.width * scale;
|
|
413
|
-
CGFloat rh = sz.height * scale;
|
|
414
|
-
CGFloat ox = ((CGFloat)dim - rw) / 2.0;
|
|
415
|
-
CGFloat oy = ((CGFloat)dim - rh) / 2.0;
|
|
416
|
-
[image drawInRect:NSMakeRect(ox, oy, rw, rh)
|
|
417
|
-
fromRect:NSZeroRect
|
|
418
|
-
operation:NSCompositingOperationSourceOver
|
|
419
|
-
fraction:1.0
|
|
420
|
-
respectFlipped:YES
|
|
421
|
-
hints:nil];
|
|
422
|
-
|
|
423
|
-
[NSGraphicsContext restoreGraphicsState];
|
|
424
|
-
CGContextRelease(ctx);
|
|
425
|
-
return FNV1AHash(buffer.data(), buffer.size());
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
static NSString* DiagonalResizeTypeByVisualMatch(NSCursor *cursor) {
|
|
429
|
-
if (!cursor) {
|
|
430
|
-
return nil;
|
|
431
|
-
}
|
|
432
|
-
if (CursorEqualsFactoryNamed(cursor, @"resizeNorthWestSouthEastCursor")) {
|
|
433
|
-
return @"nwse-resize";
|
|
434
|
-
}
|
|
435
|
-
if (CursorEqualsFactoryNamed(cursor, @"resizeNorthEastSouthWestCursor")) {
|
|
436
|
-
return @"nesw-resize";
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
NSImage *curImg = [cursor image];
|
|
440
|
-
if (!curImg) {
|
|
441
|
-
return nil;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
NSData *curTIFF = [curImg TIFFRepresentation];
|
|
445
|
-
uint64_t curRaw = CursorImagePixelHash(curImg);
|
|
446
|
-
uint64_t curNorm = CursorImagePixelHashNormalized32(curImg);
|
|
447
|
-
|
|
448
|
-
struct DiagonalFactoryEntry {
|
|
449
|
-
const char *selectorName;
|
|
450
|
-
const char *type;
|
|
451
|
-
};
|
|
452
|
-
static const DiagonalFactoryEntry kDiagonalFactories[] = {
|
|
453
|
-
{ "resizeNorthWestSouthEastCursor", "nwse-resize" },
|
|
454
|
-
{ "resizeNorthEastSouthWestCursor", "nesw-resize" },
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
for (size_t i = 0; i < sizeof(kDiagonalFactories) / sizeof(kDiagonalFactories[0]); i++) {
|
|
458
|
-
NSString *selStr = [NSString stringWithUTF8String:kDiagonalFactories[i].selectorName];
|
|
459
|
-
SEL sel = NSSelectorFromString(selStr);
|
|
460
|
-
if (![NSCursor respondsToSelector:sel]) {
|
|
461
|
-
continue;
|
|
462
|
-
}
|
|
463
|
-
NSCursor *ref = CursorFromSelector(sel);
|
|
464
|
-
if (!ref) {
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
|
-
NSImage *refImg = [ref image];
|
|
468
|
-
if (!refImg) {
|
|
469
|
-
continue;
|
|
470
|
-
}
|
|
471
|
-
NSData *refTIFF = [refImg TIFFRepresentation];
|
|
472
|
-
if (curTIFF && refTIFF && [curTIFF isEqualToData:refTIFF]) {
|
|
473
|
-
return [NSString stringWithUTF8String:kDiagonalFactories[i].type];
|
|
474
|
-
}
|
|
475
|
-
uint64_t refRaw = CursorImagePixelHash(refImg);
|
|
476
|
-
if (curRaw != 0 && curRaw == refRaw) {
|
|
477
|
-
return [NSString stringWithUTF8String:kDiagonalFactories[i].type];
|
|
478
|
-
}
|
|
479
|
-
uint64_t refNorm = CursorImagePixelHashNormalized32(refImg);
|
|
480
|
-
if (curNorm != 0 && curNorm == refNorm) {
|
|
481
|
-
return [NSString stringWithUTF8String:kDiagonalFactories[i].type];
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
return nil;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
312
|
static void AddStandardCursorFingerprint(NSCursor *cursor, NSString *cursorType) {
|
|
488
313
|
if (!cursor || !cursorType) {
|
|
489
314
|
return;
|
|
@@ -534,7 +359,7 @@ static void InitializeCursorFingerprintMap(void) {
|
|
|
534
359
|
AddCursorIfAvailable(@selector(dragCopyCursor), @"copy");
|
|
535
360
|
AddCursorIfAvailable(@selector(dragLinkCursor), @"alias");
|
|
536
361
|
AddCursorIfAvailable(@selector(resizeLeftRightCursor), @"col-resize");
|
|
537
|
-
AddCursorIfAvailable(@selector(resizeUpDownCursor), @"
|
|
362
|
+
AddCursorIfAvailable(@selector(resizeUpDownCursor), @"row-resize");
|
|
538
363
|
AddCursorIfAvailableByName(@"resizeLeftCursor", @"col-resize");
|
|
539
364
|
AddCursorIfAvailableByName(@"resizeRightCursor", @"col-resize");
|
|
540
365
|
AddCursorIfAvailableByName(@"resizeUpCursor", @"ns-resize");
|
|
@@ -677,26 +502,21 @@ static BOOL ShouldEmitCursorEvent(CGPoint location, NSString *cursorType, NSStri
|
|
|
677
502
|
BOOL moved = fabs(location.x - g_lastCursorLocation.x) >= movementThreshold ||
|
|
678
503
|
fabs(location.y - g_lastCursorLocation.y) >= movementThreshold;
|
|
679
504
|
BOOL eventChanged = !StringsEqual(eventType, g_lastCursorEventType);
|
|
680
|
-
BOOL
|
|
681
|
-
BOOL isMoveOnly = StringsEqual(eventType, @"move");
|
|
505
|
+
BOOL isMoveEvent = StringsEqual(eventType, @"move") || StringsEqual(eventType, @"drag");
|
|
682
506
|
BOOL isClickEvent = StringsEqual(eventType, @"mousedown") ||
|
|
683
507
|
StringsEqual(eventType, @"mouseup") ||
|
|
684
508
|
StringsEqual(eventType, @"rightmousedown") ||
|
|
685
509
|
StringsEqual(eventType, @"rightmouseup");
|
|
686
510
|
|
|
687
|
-
if (
|
|
688
|
-
return
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
if (isMoveOnly) {
|
|
692
|
-
BOOL cursorChanged = !StringsEqual(cursorType, g_lastCursorType);
|
|
693
|
-
return moved || cursorChanged;
|
|
511
|
+
if (isMoveEvent) {
|
|
512
|
+
return moved;
|
|
694
513
|
}
|
|
695
514
|
|
|
696
515
|
if (isClickEvent) {
|
|
697
516
|
return eventChanged || moved;
|
|
698
517
|
}
|
|
699
518
|
|
|
519
|
+
// Fallback: only emit when something actually changed
|
|
700
520
|
BOOL cursorChanged = !StringsEqual(cursorType, g_lastCursorType);
|
|
701
521
|
return moved || cursorChanged || eventChanged;
|
|
702
522
|
}
|
|
@@ -850,10 +670,7 @@ static NSString* CursorTypeForWindowBorder(AXUIElementRef element, CGPoint curso
|
|
|
850
670
|
CFRelease(positionValue);
|
|
851
671
|
CFRelease(sizeValue);
|
|
852
672
|
|
|
853
|
-
|
|
854
|
-
const CGFloat cornerRadius = 9.0;
|
|
855
|
-
const CGFloat edgeInset = 12.0;
|
|
856
|
-
|
|
673
|
+
CGFloat edge = 4.0;
|
|
857
674
|
CGFloat x = cursorPos.x - windowOrigin.x;
|
|
858
675
|
CGFloat y = cursorPos.y - windowOrigin.y;
|
|
859
676
|
CGFloat w = windowSize.width;
|
|
@@ -863,44 +680,17 @@ static NSString* CursorTypeForWindowBorder(AXUIElementRef element, CGPoint curso
|
|
|
863
680
|
return nil;
|
|
864
681
|
}
|
|
865
682
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
BOOL nearTop = (y >= 0 && y <= edge);
|
|
871
|
-
BOOL nearBottom = (y >= h - edge && y <= h);
|
|
872
|
-
if ((nearLeft && nearTop) || (nearRight && nearBottom)) {
|
|
873
|
-
return @"nwse-resize";
|
|
874
|
-
}
|
|
875
|
-
if ((nearRight && nearTop) || (nearLeft && nearBottom)) {
|
|
876
|
-
return @"nesw-resize";
|
|
877
|
-
}
|
|
878
|
-
if (nearLeft || nearRight) {
|
|
879
|
-
return @"col-resize";
|
|
880
|
-
}
|
|
881
|
-
if (nearTop || nearBottom) {
|
|
882
|
-
return @"ns-resize";
|
|
883
|
-
}
|
|
884
|
-
return nil;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
CGFloat dBottomLeft = hypot(x, y);
|
|
888
|
-
CGFloat dTopRight = hypot(w - x, h - y);
|
|
889
|
-
CGFloat dBottomRight = hypot(w - x, y);
|
|
890
|
-
CGFloat dTopLeft = hypot(x, h - y);
|
|
683
|
+
BOOL nearLeft = (x >= 0 && x <= edge);
|
|
684
|
+
BOOL nearRight = (x >= w - edge && x <= w);
|
|
685
|
+
BOOL nearTop = (y >= 0 && y <= edge);
|
|
686
|
+
BOOL nearBottom = (y >= h - edge && y <= h);
|
|
891
687
|
|
|
892
|
-
if (
|
|
688
|
+
if ((nearLeft && nearTop) || (nearRight && nearBottom)) {
|
|
893
689
|
return @"nwse-resize";
|
|
894
690
|
}
|
|
895
|
-
if (
|
|
691
|
+
if ((nearRight && nearTop) || (nearLeft && nearBottom)) {
|
|
896
692
|
return @"nesw-resize";
|
|
897
693
|
}
|
|
898
|
-
|
|
899
|
-
BOOL nearLeft = (x >= 0 && x <= edgeInset);
|
|
900
|
-
BOOL nearRight = (x >= w - edgeInset && x <= w);
|
|
901
|
-
BOOL nearTop = (y >= 0 && y <= edgeInset);
|
|
902
|
-
BOOL nearBottom = (y >= h - edgeInset && y <= h);
|
|
903
|
-
|
|
904
694
|
if (nearLeft || nearRight) {
|
|
905
695
|
return @"col-resize";
|
|
906
696
|
}
|
|
@@ -911,42 +701,6 @@ static NSString* CursorTypeForWindowBorder(AXUIElementRef element, CGPoint curso
|
|
|
911
701
|
return nil;
|
|
912
702
|
}
|
|
913
703
|
|
|
914
|
-
static NSString* ResizeTypeFromWindowUnderCursor(CGPoint cursorPos) {
|
|
915
|
-
@autoreleasepool {
|
|
916
|
-
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
917
|
-
if (!systemWide) {
|
|
918
|
-
return nil;
|
|
919
|
-
}
|
|
920
|
-
AXUIElementRef hit = NULL;
|
|
921
|
-
AXError err = AXUIElementCopyElementAtPosition(systemWide, cursorPos.x, cursorPos.y, &hit);
|
|
922
|
-
CFRelease(systemWide);
|
|
923
|
-
if (err != kAXErrorSuccess || !hit) {
|
|
924
|
-
if (hit) {
|
|
925
|
-
CFRelease(hit);
|
|
926
|
-
}
|
|
927
|
-
return nil;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
AXUIElementRef node = hit;
|
|
931
|
-
for (int depth = 0; depth < 24 && node; depth++) {
|
|
932
|
-
NSString *role = CopyAttributeString(node, kAXRoleAttribute);
|
|
933
|
-
if ([role isEqualToString:@"AXWindow"]) {
|
|
934
|
-
NSString *borderType = CursorTypeForWindowBorder(node, cursorPos);
|
|
935
|
-
CFRelease(node);
|
|
936
|
-
return borderType;
|
|
937
|
-
}
|
|
938
|
-
AXUIElementRef parent = NULL;
|
|
939
|
-
AXError perr = AXUIElementCopyAttributeValue(node, kAXParentAttribute, (CFTypeRef *)&parent);
|
|
940
|
-
CFRelease(node);
|
|
941
|
-
node = NULL;
|
|
942
|
-
if (perr == kAXErrorSuccess && parent) {
|
|
943
|
-
node = parent;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
return nil;
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
|
|
950
704
|
static NSString* CursorTypeFromAccessibilityElement(AXUIElementRef element, CGPoint cursorPos) {
|
|
951
705
|
if (!element) {
|
|
952
706
|
return nil;
|
|
@@ -1183,14 +937,6 @@ static NSString* cursorTypeFromCursorName(NSString *value) {
|
|
|
1183
937
|
return @"zoom-in";
|
|
1184
938
|
}
|
|
1185
939
|
|
|
1186
|
-
// CGS / tema kısa diagonal tokenları (nwse / nesw)
|
|
1187
|
-
if ([normalized containsString:@"nesw"]) {
|
|
1188
|
-
return @"nesw-resize";
|
|
1189
|
-
}
|
|
1190
|
-
if ([normalized containsString:@"nwse"]) {
|
|
1191
|
-
return @"nwse-resize";
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
940
|
// All-scroll pattern (move in all directions)
|
|
1195
941
|
if ([normalized containsString:@"all-scroll"] ||
|
|
1196
942
|
[normalized containsString:@"allscroll"] ||
|
|
@@ -1637,13 +1383,6 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
|
|
|
1637
1383
|
CGFloat relativeX = width > 0 ? hotspot.x / width : 0;
|
|
1638
1384
|
CGFloat relativeY = height > 0 ? hotspot.y / height : 0;
|
|
1639
1385
|
|
|
1640
|
-
if (cursor) {
|
|
1641
|
-
NSString *diag = DiagonalResizeTypeByVisualMatch(cursor);
|
|
1642
|
-
if (diag) {
|
|
1643
|
-
return diag;
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
1386
|
// Tolerance for floating point comparison
|
|
1648
1387
|
CGFloat tolerance = 0.05;
|
|
1649
1388
|
CGFloat tightTolerance = 0.02; // For precise hotspot matching
|
|
@@ -1683,35 +1422,29 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
|
|
|
1683
1422
|
// Distinguished by pointer equality
|
|
1684
1423
|
if (approx(width, 32) && approx(height, 32) && approx(relativeY, 0.531)) {
|
|
1685
1424
|
if (cursor) {
|
|
1686
|
-
if ([NSCursor
|
|
1687
|
-
cursor == [NSCursor closedHandCursor]) {
|
|
1425
|
+
if (cursor == [NSCursor closedHandCursor]) {
|
|
1688
1426
|
return @"grabbing";
|
|
1689
1427
|
}
|
|
1690
|
-
if ([NSCursor
|
|
1691
|
-
cursor == [NSCursor openHandCursor]) {
|
|
1428
|
+
if (cursor == [NSCursor openHandCursor]) {
|
|
1692
1429
|
return @"grab";
|
|
1693
1430
|
}
|
|
1694
1431
|
}
|
|
1695
1432
|
return @"grab"; // Default to grab if can't distinguish
|
|
1696
1433
|
}
|
|
1697
1434
|
|
|
1698
|
-
// 24x24:
|
|
1435
|
+
// 24x24 cursors: crosshair vs move/all-scroll
|
|
1436
|
+
// Distinguished by precise hotspot position
|
|
1699
1437
|
if (approx(width, 24) && approx(height, 24)) {
|
|
1700
|
-
if (cursor) {
|
|
1701
|
-
NSString *diag = DiagonalResizeTypeByVisualMatch(cursor);
|
|
1702
|
-
if (diag) {
|
|
1703
|
-
return diag;
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
1438
|
// crosshair: hotspot rel=(0.458, 0.458)
|
|
1707
1439
|
if (approxTight(relativeX, 0.458) && approxTight(relativeY, 0.458)) {
|
|
1708
1440
|
return @"crosshair";
|
|
1709
1441
|
}
|
|
1710
1442
|
// move/all-scroll: hotspot rel=(0.5, 0.5)
|
|
1711
1443
|
if (approxTight(relativeX, 0.5) && approxTight(relativeY, 0.5)) {
|
|
1712
|
-
return @"move";
|
|
1444
|
+
return @"move"; // or all-scroll, they're identical
|
|
1713
1445
|
}
|
|
1714
|
-
|
|
1446
|
+
// Fallback for 24x24
|
|
1447
|
+
return @"crosshair";
|
|
1715
1448
|
}
|
|
1716
1449
|
|
|
1717
1450
|
// help/cell: 18x18, ratio=1.0, hotspot rel=(0.5, 0.5)
|
|
@@ -1772,29 +1505,7 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
|
|
|
1772
1505
|
|
|
1773
1506
|
// ne-resize/nw-resize/se-resize/sw-resize/nesw-resize/nwse-resize: 22x22, ratio=1.0, hotspot rel=(0.5, 0.5)
|
|
1774
1507
|
if (approx(width, 22) && approx(height, 22)) {
|
|
1775
|
-
|
|
1776
|
-
NSString *diag = DiagonalResizeTypeByVisualMatch(cursor);
|
|
1777
|
-
if (diag) {
|
|
1778
|
-
return diag;
|
|
1779
|
-
}
|
|
1780
|
-
if ([NSCursor respondsToSelector:@selector(resizeUpCursor)] &&
|
|
1781
|
-
cursor == [NSCursor resizeUpCursor]) {
|
|
1782
|
-
return @"n-resize";
|
|
1783
|
-
}
|
|
1784
|
-
if ([NSCursor respondsToSelector:@selector(resizeDownCursor)] &&
|
|
1785
|
-
cursor == [NSCursor resizeDownCursor]) {
|
|
1786
|
-
return @"s-resize";
|
|
1787
|
-
}
|
|
1788
|
-
if ([NSCursor respondsToSelector:@selector(resizeLeftCursor)] &&
|
|
1789
|
-
cursor == [NSCursor resizeLeftCursor]) {
|
|
1790
|
-
return @"w-resize";
|
|
1791
|
-
}
|
|
1792
|
-
if ([NSCursor respondsToSelector:@selector(resizeRightCursor)] &&
|
|
1793
|
-
cursor == [NSCursor resizeRightCursor]) {
|
|
1794
|
-
return @"e-resize";
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
return nil;
|
|
1508
|
+
return @"nwse-resize"; // Default to nwse-resize for all diagonal cursors
|
|
1798
1509
|
}
|
|
1799
1510
|
|
|
1800
1511
|
// zoom-in/zoom-out: 28x26, ratio=1.077, hotspot rel=(0.428, 0.423)
|
|
@@ -1816,12 +1527,10 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
|
|
|
1816
1527
|
if (approxTight(relativeX, 0.161) && approxTight(relativeY, 0.1)) {
|
|
1817
1528
|
return @"default";
|
|
1818
1529
|
}
|
|
1819
|
-
//
|
|
1530
|
+
// context-menu/progress/wait/copy/no-drop/not-allowed: hotspot rel=(0.179, 0.125) - hotspot at (5, 5)
|
|
1820
1531
|
if (approxTight(relativeX, 0.179) && approxTight(relativeY, 0.125)) {
|
|
1532
|
+
// Try pointer equality for standard cursors
|
|
1821
1533
|
if (cursor) {
|
|
1822
|
-
if (cursor == [NSCursor arrowCursor]) {
|
|
1823
|
-
return @"default";
|
|
1824
|
-
}
|
|
1825
1534
|
if (cursor == [NSCursor contextualMenuCursor]) {
|
|
1826
1535
|
return @"context-menu";
|
|
1827
1536
|
}
|
|
@@ -1832,7 +1541,10 @@ static NSString* cursorTypeFromImageSignature(NSImage *image, NSPoint hotspot, N
|
|
|
1832
1541
|
return @"not-allowed";
|
|
1833
1542
|
}
|
|
1834
1543
|
}
|
|
1835
|
-
|
|
1544
|
+
// NOTE: progress, wait, no-drop don't have standard NSCursor pointers
|
|
1545
|
+
// Return "progress" as default for this hotspot pattern (better than "default")
|
|
1546
|
+
// Let cursor name detection in caller distinguish between progress/wait
|
|
1547
|
+
return @"progress";
|
|
1836
1548
|
}
|
|
1837
1549
|
return @"default";
|
|
1838
1550
|
}
|
|
@@ -1859,16 +1571,13 @@ static NSString* cursorTypeFromNSCursor(NSCursor *cursor) {
|
|
|
1859
1571
|
if (cursor == [NSCursor pointingHandCursor]) {
|
|
1860
1572
|
return @"pointer";
|
|
1861
1573
|
}
|
|
1862
|
-
if ([NSCursor
|
|
1863
|
-
cursor == [NSCursor crosshairCursor]) {
|
|
1574
|
+
if (cursor == [NSCursor crosshairCursor]) {
|
|
1864
1575
|
return @"crosshair";
|
|
1865
1576
|
}
|
|
1866
|
-
if ([NSCursor
|
|
1867
|
-
cursor == [NSCursor openHandCursor]) {
|
|
1577
|
+
if (cursor == [NSCursor openHandCursor]) {
|
|
1868
1578
|
return @"grab";
|
|
1869
1579
|
}
|
|
1870
|
-
if ([NSCursor
|
|
1871
|
-
cursor == [NSCursor closedHandCursor]) {
|
|
1580
|
+
if (cursor == [NSCursor closedHandCursor]) {
|
|
1872
1581
|
return @"grabbing";
|
|
1873
1582
|
}
|
|
1874
1583
|
if (cursor == [NSCursor operationNotAllowedCursor]) {
|
|
@@ -1884,42 +1593,14 @@ static NSString* cursorTypeFromNSCursor(NSCursor *cursor) {
|
|
|
1884
1593
|
return @"context-menu";
|
|
1885
1594
|
}
|
|
1886
1595
|
|
|
1887
|
-
|
|
1888
|
-
if (
|
|
1889
|
-
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
if ([NSCursor respondsToSelector:@selector(resizeLeftCursor)] &&
|
|
1893
|
-
cursor == [NSCursor resizeLeftCursor]) {
|
|
1894
|
-
return @"col-resize";
|
|
1895
|
-
}
|
|
1896
|
-
if ([NSCursor respondsToSelector:@selector(resizeRightCursor)] &&
|
|
1897
|
-
cursor == [NSCursor resizeRightCursor]) {
|
|
1898
|
-
return @"col-resize";
|
|
1899
|
-
}
|
|
1900
|
-
if ([NSCursor respondsToSelector:@selector(resizeUpCursor)] &&
|
|
1901
|
-
cursor == [NSCursor resizeUpCursor]) {
|
|
1902
|
-
return @"n-resize";
|
|
1903
|
-
}
|
|
1904
|
-
if ([NSCursor respondsToSelector:@selector(resizeDownCursor)] &&
|
|
1905
|
-
cursor == [NSCursor resizeDownCursor]) {
|
|
1906
|
-
return @"s-resize";
|
|
1907
|
-
}
|
|
1908
|
-
if ([NSCursor respondsToSelector:@selector(resizeLeftRightCursor)] &&
|
|
1909
|
-
cursor == [NSCursor resizeLeftRightCursor]) {
|
|
1910
|
-
return @"col-resize";
|
|
1911
|
-
}
|
|
1912
|
-
if ([NSCursor respondsToSelector:@selector(resizeUpDownCursor)] &&
|
|
1913
|
-
cursor == [NSCursor resizeUpDownCursor]) {
|
|
1914
|
-
return @"ns-resize";
|
|
1915
|
-
}
|
|
1916
|
-
if (@available(macOS 15.0, *)) {
|
|
1917
|
-
if ([NSCursor respondsToSelector:@selector(columnResizeCursor)] &&
|
|
1918
|
-
cursor == [NSCursor columnResizeCursor]) {
|
|
1596
|
+
// Resize cursors
|
|
1597
|
+
if ([NSCursor respondsToSelector:@selector(resizeLeftRightCursor)]) {
|
|
1598
|
+
if (cursor == [NSCursor resizeLeftRightCursor]) {
|
|
1919
1599
|
return @"col-resize";
|
|
1920
1600
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
1601
|
+
}
|
|
1602
|
+
if ([NSCursor respondsToSelector:@selector(resizeUpDownCursor)]) {
|
|
1603
|
+
if (cursor == [NSCursor resizeUpDownCursor]) {
|
|
1923
1604
|
return @"row-resize";
|
|
1924
1605
|
}
|
|
1925
1606
|
}
|
|
@@ -1991,6 +1672,12 @@ static NSString* detectSystemCursorType(void) {
|
|
|
1991
1672
|
}
|
|
1992
1673
|
|
|
1993
1674
|
int cursorSeed = SafeCGSCurrentCursorSeed();
|
|
1675
|
+
if (cursorSeed > 0) {
|
|
1676
|
+
NSString *seedType = cursorTypeFromSeed(cursorSeed);
|
|
1677
|
+
if (seedType) {
|
|
1678
|
+
return seedType;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1994
1681
|
|
|
1995
1682
|
void (^fetchCursorBlock)(void) = ^{
|
|
1996
1683
|
NSCursor *currentCursor = nil;
|
|
@@ -2094,23 +1781,14 @@ static NSString* detectSystemCursorType(void) {
|
|
|
2094
1781
|
dispatch_sync(dispatch_get_main_queue(), fetchCursorBlock);
|
|
2095
1782
|
}
|
|
2096
1783
|
|
|
2097
|
-
if (cursorType && ![cursorType isEqualToString:@"default"]) {
|
|
2098
|
-
|
|
2099
|
-
addCursorToSeedMap(cursorType, cursorSeed);
|
|
2100
|
-
}
|
|
2101
|
-
return cursorType;
|
|
1784
|
+
if (cursorType && ![cursorType isEqualToString:@"default"] && cursorSeed > 0) {
|
|
1785
|
+
addCursorToSeedMap(cursorType, cursorSeed);
|
|
2102
1786
|
}
|
|
2103
1787
|
|
|
2104
|
-
|
|
2105
|
-
NSString *seedType = cursorTypeFromSeed(cursorSeed);
|
|
2106
|
-
if (seedType) {
|
|
2107
|
-
return seedType;
|
|
2108
|
-
}
|
|
2109
|
-
}
|
|
2110
|
-
|
|
2111
|
-
return cursorType ?: @"default";
|
|
1788
|
+
return cursorType;
|
|
2112
1789
|
}
|
|
2113
1790
|
|
|
1791
|
+
// Desktop'ta SVG karşılığı olmayan cursor tiplerini desteklenen tiplere normalize et
|
|
2114
1792
|
static NSString* normalizeCursorTypeForDesktop(NSString *cursorType) {
|
|
2115
1793
|
if (!cursorType || [cursorType length] == 0) {
|
|
2116
1794
|
return @"default";
|
|
@@ -2208,23 +1886,11 @@ NSString* getCursorType() {
|
|
|
2208
1886
|
int currentSeed = SafeCGSCurrentCursorSeed();
|
|
2209
1887
|
g_lastCursorSeed = currentSeed; // Save for getCursorPosition()
|
|
2210
1888
|
|
|
2211
|
-
//
|
|
1889
|
+
// Use cursorTypeFromNSCursor for detection (pointer equality + image-based)
|
|
1890
|
+
// DO NOT use accessibility detection as it's unreliable and causes false positives
|
|
2212
1891
|
NSString *systemCursorType = detectSystemCursorType();
|
|
2213
1892
|
NSString *rawType = systemCursorType && [systemCursorType length] > 0 ? systemCursorType : @"default";
|
|
2214
1893
|
|
|
2215
|
-
__block NSString *chromeResize = nil;
|
|
2216
|
-
if ([NSThread isMainThread]) {
|
|
2217
|
-
chromeResize = ResizeTypeFromWindowUnderCursor(cursorPos);
|
|
2218
|
-
} else {
|
|
2219
|
-
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
2220
|
-
chromeResize = ResizeTypeFromWindowUnderCursor(cursorPos);
|
|
2221
|
-
});
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
if (chromeResize) {
|
|
2225
|
-
rawType = chromeResize;
|
|
2226
|
-
}
|
|
2227
|
-
|
|
2228
1894
|
// Desktop SVG'lerine uyumlu tipe normalize et
|
|
2229
1895
|
NSString *finalType = normalizeCursorTypeForDesktop(rawType);
|
|
2230
1896
|
|
|
@@ -3,22 +3,6 @@
|
|
|
3
3
|
#import <AppKit/AppKit.h>
|
|
4
4
|
#import "../logging.h"
|
|
5
5
|
|
|
6
|
-
static NSCursor *CursorFactoryNamed(NSString *name) {
|
|
7
|
-
if (!name || [name length] == 0) return nil;
|
|
8
|
-
SEL sel = NSSelectorFromString(name);
|
|
9
|
-
if (!sel || ![NSCursor respondsToSelector:sel]) return nil;
|
|
10
|
-
IMP imp = [NSCursor methodForSelector:sel];
|
|
11
|
-
if (!imp) return nil;
|
|
12
|
-
typedef NSCursor *(*CursorFactoryFunc)(id, SEL);
|
|
13
|
-
CursorFactoryFunc fn = (CursorFactoryFunc)imp;
|
|
14
|
-
return fn([NSCursor class], sel);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
static BOOL CursorEqualsFactoryNamed(NSCursor *cursor, NSString *factoryName) {
|
|
18
|
-
NSCursor *ref = CursorFactoryNamed(factoryName);
|
|
19
|
-
return ref != nil && cursor == ref;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
6
|
// Thread-safe cursor tracking for Electron
|
|
23
7
|
static dispatch_queue_t g_cursorQueue = nil;
|
|
24
8
|
|
|
@@ -39,34 +23,18 @@ static NSString* MapCursorToType(NSCursor *cursor) {
|
|
|
39
23
|
if (cursor == [NSCursor pointingHandCursor]) return @"pointer";
|
|
40
24
|
if ([NSCursor respondsToSelector:@selector(resizeLeftRightCursor)]) {
|
|
41
25
|
if (cursor == [NSCursor resizeLeftRightCursor] ||
|
|
42
|
-
|
|
43
|
-
|
|
26
|
+
[NSCursor instancesRespondToSelector:@selector(resizeLeftCursor)] && (cursor == [NSCursor resizeLeftCursor]) ||
|
|
27
|
+
[NSCursor instancesRespondToSelector:@selector(resizeRightCursor)] && (cursor == [NSCursor resizeRightCursor])) {
|
|
44
28
|
return @"col-resize";
|
|
45
29
|
}
|
|
46
30
|
}
|
|
47
31
|
if ([NSCursor respondsToSelector:@selector(resizeUpDownCursor)]) {
|
|
48
32
|
if (cursor == [NSCursor resizeUpDownCursor] ||
|
|
49
|
-
|
|
50
|
-
|
|
33
|
+
[NSCursor instancesRespondToSelector:@selector(resizeUpCursor)] && (cursor == [NSCursor resizeUpCursor]) ||
|
|
34
|
+
[NSCursor instancesRespondToSelector:@selector(resizeDownCursor)] && (cursor == [NSCursor resizeDownCursor])) {
|
|
51
35
|
return @"ns-resize";
|
|
52
36
|
}
|
|
53
37
|
}
|
|
54
|
-
if (CursorEqualsFactoryNamed(cursor, @"resizeNorthWestSouthEastCursor")) {
|
|
55
|
-
return @"nwse-resize";
|
|
56
|
-
}
|
|
57
|
-
if (CursorEqualsFactoryNamed(cursor, @"resizeNorthEastSouthWestCursor")) {
|
|
58
|
-
return @"nesw-resize";
|
|
59
|
-
}
|
|
60
|
-
if (@available(macOS 15.0, *)) {
|
|
61
|
-
if ([NSCursor respondsToSelector:@selector(columnResizeCursor)] &&
|
|
62
|
-
cursor == [NSCursor columnResizeCursor]) {
|
|
63
|
-
return @"col-resize";
|
|
64
|
-
}
|
|
65
|
-
if ([NSCursor respondsToSelector:@selector(rowResizeCursor)] &&
|
|
66
|
-
cursor == [NSCursor rowResizeCursor]) {
|
|
67
|
-
return @"row-resize";
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
38
|
if ([NSCursor respondsToSelector:@selector(openHandCursor)] && cursor == [NSCursor openHandCursor]) return @"grab";
|
|
71
39
|
if ([NSCursor respondsToSelector:@selector(closedHandCursor)] && cursor == [NSCursor closedHandCursor]) return @"grabbing";
|
|
72
40
|
if ([NSCursor respondsToSelector:@selector(crosshairCursor)] && cursor == [NSCursor crosshairCursor]) return @"crosshair";
|
|
@@ -61,15 +61,17 @@ Napi::Value GetWindowsElectronSafe(const Napi::CallbackInfo& info) {
|
|
|
61
61
|
for (SCWindow *window in content.windows) {
|
|
62
62
|
// Filter out system and small windows
|
|
63
63
|
if (window.frame.size.width < 50 || window.frame.size.height < 50) continue;
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
|
|
66
65
|
NSString *appName = window.owningApplication.applicationName ?: @"Unknown";
|
|
67
|
-
|
|
66
|
+
|
|
68
67
|
if (ShouldSkipWindowOwner(appName)) continue;
|
|
69
|
-
|
|
68
|
+
|
|
69
|
+
// Fallback: frameless Electron pencereleri kCGWindowName/title döndürmez
|
|
70
|
+
NSString *displayTitle = (window.title && window.title.length > 0) ? window.title : appName;
|
|
71
|
+
|
|
70
72
|
NSDictionary *windowInfo = @{
|
|
71
73
|
@"id": @(window.windowID),
|
|
72
|
-
@"name":
|
|
74
|
+
@"name": displayTitle,
|
|
73
75
|
@"appName": appName,
|
|
74
76
|
@"bundleId": window.owningApplication.bundleIdentifier ?: @"",
|
|
75
77
|
@"x": @((int)window.frame.origin.x),
|
|
@@ -116,7 +118,9 @@ Napi::Value GetWindowsElectronSafe(const Napi::CallbackInfo& info) {
|
|
|
116
118
|
|
|
117
119
|
NSString *appName = (__bridge NSString*)ownerName;
|
|
118
120
|
NSString *windowTitle = windowName ? (__bridge NSString*)windowName : @"";
|
|
119
|
-
|
|
121
|
+
// Fallback: frameless Electron pencereleri kCGWindowName döndürmez
|
|
122
|
+
if (windowTitle.length == 0) windowTitle = appName;
|
|
123
|
+
|
|
120
124
|
if (ShouldSkipWindowOwner(appName)) continue;
|
|
121
125
|
|
|
122
126
|
// Get window bounds
|
package/src/mac_recorder.mm
CHANGED
|
@@ -1042,6 +1042,12 @@ Napi::Value GetWindows(const Napi::CallbackInfo& info) {
|
|
|
1042
1042
|
CGRectMakeWithDictionaryRepresentation(boundsRef, &bounds);
|
|
1043
1043
|
}
|
|
1044
1044
|
|
|
1045
|
+
// Fallback: frameless Electron pencereleri kCGWindowName döndürmez,
|
|
1046
|
+
// bu durumda appName'i kullan ki pencere listede görünsün.
|
|
1047
|
+
if (windowName.empty() && !appName.empty()) {
|
|
1048
|
+
windowName = appName;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1045
1051
|
// Skip windows without name or very small windows
|
|
1046
1052
|
if (windowName.empty() || bounds.size.width < 50 || bounds.size.height < 50) {
|
|
1047
1053
|
continue;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const MacRecorder = require('../index.js');
|
|
4
|
-
|
|
5
|
-
const INTERVAL_MS = 33;
|
|
6
|
-
|
|
7
|
-
const recorder = new MacRecorder();
|
|
8
|
-
|
|
9
|
-
process.stderr.write(
|
|
10
|
-
`Canlı cursor tipi (${INTERVAL_MS}ms). Çıkmak: Ctrl+C\n`
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
function tick() {
|
|
14
|
-
try {
|
|
15
|
-
const p = recorder.getCursorPosition();
|
|
16
|
-
const type = p.cursorType != null ? String(p.cursorType) : '?';
|
|
17
|
-
const seed =
|
|
18
|
-
typeof p.seed === 'number' && p.seed > 0 ? ` seed:${p.seed}` : '';
|
|
19
|
-
process.stdout.write(`\r\x1b[KcursorType: ${type}${seed}`);
|
|
20
|
-
} catch (e) {
|
|
21
|
-
process.stdout.write(`\r\x1b[K${e.message || e}`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const id = setInterval(tick, INTERVAL_MS);
|
|
26
|
-
tick();
|
|
27
|
-
|
|
28
|
-
process.on('SIGINT', () => {
|
|
29
|
-
clearInterval(id);
|
|
30
|
-
process.stdout.write('\n');
|
|
31
|
-
process.exit(0);
|
|
32
|
-
});
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
const MacRecorder = require('../index.js');
|
|
5
|
-
|
|
6
|
-
const OUT_DIR = path.join(__dirname, '..', 'test-output');
|
|
7
|
-
const DURATION_SEC = 15;
|
|
8
|
-
|
|
9
|
-
async function sleep(ms) {
|
|
10
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function summarizeCursorTypes(cursorJsonPath) {
|
|
14
|
-
if (!cursorJsonPath || !fs.existsSync(cursorJsonPath)) {
|
|
15
|
-
return { counts: {}, ordered: [] };
|
|
16
|
-
}
|
|
17
|
-
const raw = fs.readFileSync(cursorJsonPath, 'utf8');
|
|
18
|
-
let events;
|
|
19
|
-
try {
|
|
20
|
-
events = JSON.parse(raw);
|
|
21
|
-
} catch {
|
|
22
|
-
return { counts: {}, ordered: [] };
|
|
23
|
-
}
|
|
24
|
-
if (!Array.isArray(events)) {
|
|
25
|
-
return { counts: {}, ordered: [] };
|
|
26
|
-
}
|
|
27
|
-
const counts = {};
|
|
28
|
-
const order = [];
|
|
29
|
-
for (const ev of events) {
|
|
30
|
-
const t = ev && ev.cursorType ? String(ev.cursorType) : '?';
|
|
31
|
-
if (counts[t] === undefined) {
|
|
32
|
-
counts[t] = 0;
|
|
33
|
-
order.push(t);
|
|
34
|
-
}
|
|
35
|
-
counts[t]++;
|
|
36
|
-
}
|
|
37
|
-
return { counts, ordered: order };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function main() {
|
|
41
|
-
fs.mkdirSync(OUT_DIR, { recursive: true });
|
|
42
|
-
|
|
43
|
-
const stamp = Date.now();
|
|
44
|
-
const screenPath = path.join(OUT_DIR, `cursor-types-test-${stamp}.mov`);
|
|
45
|
-
|
|
46
|
-
const recorder = new MacRecorder();
|
|
47
|
-
|
|
48
|
-
const displays = await recorder.getDisplays();
|
|
49
|
-
const cameras = await recorder.getCameraDevices();
|
|
50
|
-
const audioDevices = await recorder.getAudioDevices();
|
|
51
|
-
|
|
52
|
-
if (!displays.length) {
|
|
53
|
-
process.stderr.write('No displays.\n');
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const display = displays[0];
|
|
58
|
-
|
|
59
|
-
let cursorPathFromStart = null;
|
|
60
|
-
recorder.once('recordingStarted', (info) => {
|
|
61
|
-
cursorPathFromStart = info.cursorOutputPath || null;
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const options = {
|
|
65
|
-
displayId: display.id,
|
|
66
|
-
captureCamera: cameras.length > 0,
|
|
67
|
-
cameraDeviceId: cameras.length > 0 ? cameras[0].id : undefined,
|
|
68
|
-
includeMicrophone: audioDevices.length > 0,
|
|
69
|
-
audioDeviceId: audioDevices.length > 0 ? audioDevices[0].id : undefined,
|
|
70
|
-
includeSystemAudio: true,
|
|
71
|
-
captureCursor: true,
|
|
72
|
-
frameRate: 60,
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
process.stdout.write(
|
|
76
|
-
`Kayıt ${DURATION_SEC}s: ekran + sistem sesi + mik ${
|
|
77
|
-
options.includeMicrophone ? 'açık' : 'kapalı'
|
|
78
|
-
} + kamera ${options.captureCamera ? 'açık' : 'kapalı'} + cursor JSON.\n` +
|
|
79
|
-
`Kenarlarda resize, köşelerde çapraz, grab/grabbing ve crosshair deneyin.\n\n`
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
await recorder.startRecording(screenPath, options);
|
|
83
|
-
|
|
84
|
-
const cursorPath = cursorPathFromStart;
|
|
85
|
-
|
|
86
|
-
for (let i = 1; i <= DURATION_SEC; i++) {
|
|
87
|
-
await sleep(1000);
|
|
88
|
-
process.stdout.write(`${i}s `);
|
|
89
|
-
}
|
|
90
|
-
process.stdout.write('\n');
|
|
91
|
-
|
|
92
|
-
await recorder.stopRecording();
|
|
93
|
-
|
|
94
|
-
process.stdout.write('\nDosyalar:\n');
|
|
95
|
-
process.stdout.write(` Ekran: ${screenPath}\n`);
|
|
96
|
-
if (options.captureCamera && cameras.length) {
|
|
97
|
-
process.stdout.write(
|
|
98
|
-
` Kamera: kayıt başladıktan sonra native temp_camera_* ile aynı oturum\n`
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
if (options.includeMicrophone || options.includeSystemAudio) {
|
|
102
|
-
process.stdout.write(
|
|
103
|
-
` Ses: kayıt başladıktan sonra native temp_audio_* ile aynı oturum\n`
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
process.stdout.write(` Cursor JSON: ${cursorPath || 'bilinmiyor'}\n`);
|
|
107
|
-
|
|
108
|
-
if (cursorPath && fs.existsSync(cursorPath)) {
|
|
109
|
-
const { counts, ordered } = summarizeCursorTypes(cursorPath);
|
|
110
|
-
process.stdout.write('\nCursor JSON — cursorType özeti (ilk görülme sırası):\n');
|
|
111
|
-
for (const t of ordered) {
|
|
112
|
-
process.stdout.write(` ${t}: ${counts[t]}\n`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
recorder.removeAllListeners();
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
main().catch((e) => {
|
|
120
|
-
process.stderr.write(String(e && e.message ? e.message : e) + '\n');
|
|
121
|
-
process.exit(1);
|
|
122
|
-
});
|