node-mac-recorder 2.24.6 → 2.24.7
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/index.js +25 -0
- package/package.json +1 -1
- package/src/screen_capture_kit.mm +44 -19
- package/src/window_selector.mm +75 -0
package/index.js
CHANGED
|
@@ -1416,6 +1416,31 @@ class MacRecorder extends EventEmitter {
|
|
|
1416
1416
|
};
|
|
1417
1417
|
}
|
|
1418
1418
|
|
|
1419
|
+
/**
|
|
1420
|
+
* Pencere kaydında hedef pencerenin uygulamasını aktive eder.
|
|
1421
|
+
*
|
|
1422
|
+
* Kayıt başlarken kaydedilen uygulama pasif kalırsa, odağını kaybedince
|
|
1423
|
+
* gizlenen pencereler (iTerm2 hotkey window vb.) kendini gizler ve kayıtta
|
|
1424
|
+
* görünmez. Kayıt komutu verildiğinde odak bizde olduğu için bu adım şart.
|
|
1425
|
+
*
|
|
1426
|
+
* @param {number} windowId CGWindowID
|
|
1427
|
+
* @returns {boolean}
|
|
1428
|
+
*/
|
|
1429
|
+
activateWindowOwnerApp(windowId) {
|
|
1430
|
+
const id = Number(windowId);
|
|
1431
|
+
if (!Number.isFinite(id) || id <= 0) return false;
|
|
1432
|
+
if (typeof nativeBinding.activateWindowOwnerApp !== "function") return false;
|
|
1433
|
+
try {
|
|
1434
|
+
return !!nativeBinding.activateWindowOwnerApp(id);
|
|
1435
|
+
} catch (error) {
|
|
1436
|
+
console.warn(
|
|
1437
|
+
"[MacRecorder] activateWindowOwnerApp başarısız:",
|
|
1438
|
+
error?.message || error,
|
|
1439
|
+
);
|
|
1440
|
+
return false;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1419
1444
|
/**
|
|
1420
1445
|
* Encoder'ın gerçek zamanlı yetişip yetişmediğini gösteren kare sayaçları.
|
|
1421
1446
|
* Kayıt sırasında canlı, bittikten sonra son oturumun değerlerini döndürür.
|
package/package.json
CHANGED
|
@@ -1600,6 +1600,7 @@ static void SCKPerformRecordingSetup(NSDictionary *config, SCShareableContent *c
|
|
|
1600
1600
|
|
|
1601
1601
|
// Find display containing this window to get scale factor
|
|
1602
1602
|
CGFloat scaleFactor = 1.0;
|
|
1603
|
+
SCDisplay *windowDisplay = nil;
|
|
1603
1604
|
CGPoint windowCenter = CGPointMake(
|
|
1604
1605
|
targetWindow.frame.origin.x + windowLogicalWidth / 2.0,
|
|
1605
1606
|
targetWindow.frame.origin.y + windowLogicalHeight / 2.0
|
|
@@ -1608,6 +1609,7 @@ static void SCKPerformRecordingSetup(NSDictionary *config, SCShareableContent *c
|
|
|
1608
1609
|
CGRect dispBounds = CGRectMake(disp.frame.origin.x, disp.frame.origin.y,
|
|
1609
1610
|
disp.frame.size.width, disp.frame.size.height);
|
|
1610
1611
|
if (CGRectContainsPoint(dispBounds, windowCenter)) {
|
|
1612
|
+
windowDisplay = disp;
|
|
1611
1613
|
scaleFactor = SCKBackingScaleForDisplay(disp);
|
|
1612
1614
|
break;
|
|
1613
1615
|
}
|
|
@@ -1615,24 +1617,32 @@ static void SCKPerformRecordingSetup(NSDictionary *config, SCShareableContent *c
|
|
|
1615
1617
|
// Fallback: use main display scale factor
|
|
1616
1618
|
if (scaleFactor == 1.0 && content.displays.count > 0) {
|
|
1617
1619
|
SCDisplay *mainDisp = content.displays.firstObject;
|
|
1620
|
+
if (!windowDisplay) windowDisplay = mainDisp;
|
|
1618
1621
|
scaleFactor = SCKBackingScaleForDisplay(mainDisp);
|
|
1619
1622
|
}
|
|
1620
1623
|
|
|
1624
|
+
// DOGRUDAN pencere yakalama. Ekrandan kirpma/kompozit YOK: bu
|
|
1625
|
+
// filtre pencereyi kendi basina yakalar, pencere tasinsa da takip
|
|
1626
|
+
// eder. (Display kompozit alternatifi denendi ve reddedildi.)
|
|
1621
1627
|
filter = [[SCContentFilter alloc] initWithDesktopIndependentWindow:targetWindow];
|
|
1628
|
+
|
|
1629
|
+
CGFloat displayScale = scaleFactor;
|
|
1630
|
+
CGFloat filterScaleLog = -1.0;
|
|
1622
1631
|
if (@available(macOS 14.0, *)) {
|
|
1623
|
-
//
|
|
1624
|
-
//
|
|
1625
|
-
//
|
|
1632
|
+
// filter.pointPixelScale, SCK'nin bu filtre icin GERCEKTEN
|
|
1633
|
+
// uretecegi point->pixel oranidir; tek yetkili kaynak odur.
|
|
1634
|
+
//
|
|
1635
|
+
// Eskiden MAX(displayScale, filterScale) aliniyordu. Bu yanlis:
|
|
1636
|
+
// olculdu ki harici 3440x1440 (1x) ekranda display heuristigi
|
|
1637
|
+
// 2.00 donerken filtre dogru sekilde 1.00 donuyor. MAX ile 2x
|
|
1638
|
+
// istenince SCK yine 1x uretiyor ve icerik karenin sol ust
|
|
1639
|
+
// ceyregine sikisip geri kalani siyah kaliyordu.
|
|
1640
|
+
// Retina'da filtre zaten 2.00 donuyor (olculdu), yani Retina
|
|
1641
|
+
// keskinligi kaybi YOK.
|
|
1626
1642
|
CGFloat filterScale = filter.pointPixelScale;
|
|
1643
|
+
filterScaleLog = filterScale;
|
|
1627
1644
|
if (isfinite(filterScale) && filterScale >= 1.0 && filterScale <= 4.0) {
|
|
1628
|
-
|
|
1629
|
-
// kombinasyonlarında Retina pencerede bile 1.0 dönebiliyor.
|
|
1630
|
-
// Display geometrisinden bulunan 2x değeri 1x'e indirirsek
|
|
1631
|
-
// 1500x960 pencere gerçek 3000x1920 yerine logical boyutta
|
|
1632
|
-
// kaydediliyor ve zoom'da metin detayı geri dönülemez
|
|
1633
|
-
// biçimde kayboluyor. Filter yalnızca daha yüksek/güvenli
|
|
1634
|
-
// bir backing scale bildiriyorsa heuristiği yükseltsin.
|
|
1635
|
-
scaleFactor = MAX(scaleFactor, filterScale);
|
|
1645
|
+
scaleFactor = filterScale;
|
|
1636
1646
|
}
|
|
1637
1647
|
}
|
|
1638
1648
|
scaleFactor = MIN(4.0, MAX(1.0, scaleFactor));
|
|
@@ -1640,9 +1650,11 @@ static void SCKPerformRecordingSetup(NSDictionary *config, SCShareableContent *c
|
|
|
1640
1650
|
NSInteger physicalWindowWidth = (NSInteger)llround(windowLogicalWidth * scaleFactor);
|
|
1641
1651
|
NSInteger physicalWindowHeight = (NSInteger)llround(windowLogicalHeight * scaleFactor);
|
|
1642
1652
|
|
|
1643
|
-
MRLog(@"🪟 Recording window: %@ logical=%ux%u, physical=%ldx%ld, scale=%.2fx"
|
|
1653
|
+
MRLog(@"🪟 Recording window: %@ logical=%ux%u, physical=%ldx%ld, scale=%.2fx "
|
|
1654
|
+
@"(displayScale=%.2f filterScale=%.2f)",
|
|
1644
1655
|
targetWindow.title, (unsigned)windowLogicalWidth, (unsigned)windowLogicalHeight,
|
|
1645
|
-
(long)physicalWindowWidth, (long)physicalWindowHeight, scaleFactor
|
|
1656
|
+
(long)physicalWindowWidth, (long)physicalWindowHeight, scaleFactor,
|
|
1657
|
+
displayScale, filterScaleLog);
|
|
1646
1658
|
recordingWidth = physicalWindowWidth;
|
|
1647
1659
|
recordingHeight = physicalWindowHeight;
|
|
1648
1660
|
} else {
|
|
@@ -1696,8 +1708,12 @@ static void SCKPerformRecordingSetup(NSDictionary *config, SCShareableContent *c
|
|
|
1696
1708
|
logicalWidth = filterRect.size.width;
|
|
1697
1709
|
logicalHeight = filterRect.size.height;
|
|
1698
1710
|
}
|
|
1711
|
+
// Pencere dalindaki ile ayni kural: SCK'nin uretecegi olcek
|
|
1712
|
+
// filtreden okunur. MAX(display, filter) yanlisti — harici
|
|
1713
|
+
// 3440x1440 (1x) ekranda display heuristigi 2.00 donuyor ve
|
|
1714
|
+
// istenen boyut SCK'nin urettiginin iki kati oluyordu.
|
|
1699
1715
|
if (isfinite(filterScale) && filterScale >= 1.0 && filterScale <= 4.0) {
|
|
1700
|
-
scaleFactor =
|
|
1716
|
+
scaleFactor = filterScale;
|
|
1701
1717
|
}
|
|
1702
1718
|
}
|
|
1703
1719
|
scaleFactor = MIN(4.0, MAX(1.0, scaleFactor));
|
|
@@ -1732,9 +1748,10 @@ static void SCKPerformRecordingSetup(NSDictionary *config, SCShareableContent *c
|
|
|
1732
1748
|
if (targetDisplay) {
|
|
1733
1749
|
cropScaleFactor = SCKBackingScaleForDisplay(targetDisplay);
|
|
1734
1750
|
if (@available(macOS 14.0, *)) {
|
|
1751
|
+
// Ekran/pencere dallariyla ayni kural: yetkili kaynak filtre.
|
|
1735
1752
|
CGFloat filterScale = filter.pointPixelScale;
|
|
1736
1753
|
if (isfinite(filterScale) && filterScale >= 1.0 && filterScale <= 4.0) {
|
|
1737
|
-
cropScaleFactor =
|
|
1754
|
+
cropScaleFactor = filterScale;
|
|
1738
1755
|
}
|
|
1739
1756
|
}
|
|
1740
1757
|
}
|
|
@@ -1779,11 +1796,19 @@ static void SCKPerformRecordingSetup(NSDictionary *config, SCShareableContent *c
|
|
|
1779
1796
|
streamConfig.queueDepth = 8;
|
|
1780
1797
|
}
|
|
1781
1798
|
if (@available(macOS 14.0, *)) {
|
|
1782
|
-
//
|
|
1783
|
-
|
|
1784
|
-
//
|
|
1799
|
+
// captureResolution = SCCaptureResolutionBest BILEREK KULLANILMIYOR.
|
|
1800
|
+
//
|
|
1801
|
+
// Retina kalitesi icin eklenmisti ama GPU (Metal) ile cizen
|
|
1802
|
+
// uygulamalarda pencere kaydinda ICERIGI TAMAMEN DUSURUYOR: iTerm2'de
|
|
1803
|
+
// sekme cubugu/bolme basliklari (AppKit) geliyor, oturum icerigi
|
|
1804
|
+
// (Metal katmani) simsiyah cikiyordu. Olculdu: ayni pencere, ayni
|
|
1805
|
+
// kosullar, sadece bu satir kapaliyken icerik geri geldi.
|
|
1806
|
+
// Cozunurluk zaten streamConfig.width/height ile isteniyor; bu ayara
|
|
1807
|
+
// ihtiyac yok.
|
|
1808
|
+
//
|
|
1809
|
+
// shouldBeOpaque ve colorSpaceName ayni deneyde TEST EDILDI ve
|
|
1810
|
+
// masum cikti (ikisi de acikken icerik geliyor) - dokunulmadi.
|
|
1785
1811
|
streamConfig.shouldBeOpaque = YES;
|
|
1786
|
-
MRLog(@"🎯 Using SCCaptureResolutionBest + shouldBeOpaque for maximum quality (macOS 14+)");
|
|
1787
1812
|
}
|
|
1788
1813
|
if (@available(macOS 13.0, *)) {
|
|
1789
1814
|
// Frame'leri bilinen bir renk uzayında (sRGB) iste; encoder tarafında
|
package/src/window_selector.mm
CHANGED
|
@@ -239,6 +239,7 @@ void updateOverlay();
|
|
|
239
239
|
NSDictionary* getWindowUnderCursor(CGPoint point);
|
|
240
240
|
NSArray* getAllSelectableWindows();
|
|
241
241
|
bool bringWindowToFront(int windowId);
|
|
242
|
+
bool activateWindowOwnerApp(int windowId);
|
|
242
243
|
void cleanupRecordingPreview();
|
|
243
244
|
bool showRecordingPreview(NSDictionary *windowInfo);
|
|
244
245
|
bool hideRecordingPreview();
|
|
@@ -842,6 +843,61 @@ static void ApplyBrandButtonStyle(NSButton *button) {
|
|
|
842
843
|
|
|
843
844
|
static WindowSelectorDelegate *g_delegate = nil;
|
|
844
845
|
|
|
846
|
+
// Pencerenin SAHIBI UYGULAMAYI aktive eder.
|
|
847
|
+
//
|
|
848
|
+
// bringWindowToFront() yalnizca AXRaise + AXFocused yapar; bu pencereyi one
|
|
849
|
+
// getirir ama uygulamayi AKTIF hale getirmez. Kayit basladiginda kaydedilen
|
|
850
|
+
// uygulama pasif kalirsa "odagi kaybedince gizlen" davranisindaki pencereler
|
|
851
|
+
// (iTerm2 hotkey window vb.) kendini gizler ve kayitta gorunmez. Pencere kaydi
|
|
852
|
+
// baslarken hedef uygulama gercekten aktif olmalidir ki kullanici icine
|
|
853
|
+
// yazabilsin.
|
|
854
|
+
bool activateWindowOwnerApp(int windowId) {
|
|
855
|
+
@autoreleasepool {
|
|
856
|
+
@try {
|
|
857
|
+
CFArrayRef cgWindowList = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
|
|
858
|
+
if (!cgWindowList) return false;
|
|
859
|
+
|
|
860
|
+
NSArray *windowArray = (__bridge NSArray *)cgWindowList;
|
|
861
|
+
pid_t ownerPid = 0;
|
|
862
|
+
for (NSDictionary *windowInfo in windowArray) {
|
|
863
|
+
NSNumber *cgWindowId = [windowInfo objectForKey:(NSString *)kCGWindowNumber];
|
|
864
|
+
if ([cgWindowId intValue] == windowId) {
|
|
865
|
+
NSNumber *processId = [windowInfo objectForKey:(NSString *)kCGWindowOwnerPID];
|
|
866
|
+
ownerPid = (pid_t)[processId intValue];
|
|
867
|
+
break;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
CFRelease(cgWindowList);
|
|
871
|
+
|
|
872
|
+
if (ownerPid <= 0) {
|
|
873
|
+
NSLog(@"⚠️ activateWindowOwnerApp: window %d icin PID bulunamadi", windowId);
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
NSRunningApplication *app =
|
|
878
|
+
[NSRunningApplication runningApplicationWithProcessIdentifier:ownerPid];
|
|
879
|
+
if (!app) {
|
|
880
|
+
NSLog(@"⚠️ activateWindowOwnerApp: PID %d icin uygulama yok", ownerPid);
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Once uygulamayi aktive et, sonra hedef pencereyi one kaldir.
|
|
885
|
+
// Ters sirada yapilirsa aktivasyon uygulamanin kendi ana penceresini
|
|
886
|
+
// one getirip secilen pencereyi geri plana itebiliyor.
|
|
887
|
+
BOOL activated = [app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
|
|
888
|
+
NSLog(@"🔝 activateWindowOwnerApp: PID %d aktive=%d (window %d)",
|
|
889
|
+
ownerPid, activated, windowId);
|
|
890
|
+
|
|
891
|
+
bringWindowToFront(windowId);
|
|
892
|
+
return activated ? true : false;
|
|
893
|
+
|
|
894
|
+
} @catch (NSException *exception) {
|
|
895
|
+
NSLog(@"❌ activateWindowOwnerApp exception: %@", exception.reason);
|
|
896
|
+
return false;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
845
901
|
// Bring window to front using Accessibility API
|
|
846
902
|
bool bringWindowToFront(int windowId) {
|
|
847
903
|
@autoreleasepool {
|
|
@@ -2696,6 +2752,24 @@ Napi::Value BringWindowToFront(const Napi::CallbackInfo& info) {
|
|
|
2696
2752
|
}
|
|
2697
2753
|
}
|
|
2698
2754
|
|
|
2755
|
+
// NAPI Function: Pencerenin sahibi uygulamayi aktive et (pencere kaydi icin)
|
|
2756
|
+
Napi::Value ActivateWindowOwnerApp(const Napi::CallbackInfo& info) {
|
|
2757
|
+
Napi::Env env = info.Env();
|
|
2758
|
+
|
|
2759
|
+
if (info.Length() < 1 || !info[0].IsNumber()) {
|
|
2760
|
+
Napi::TypeError::New(env, "Window ID required").ThrowAsJavaScriptException();
|
|
2761
|
+
return env.Null();
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
int windowId = info[0].As<Napi::Number>().Int32Value();
|
|
2765
|
+
|
|
2766
|
+
@try {
|
|
2767
|
+
return Napi::Boolean::New(env, activateWindowOwnerApp(windowId));
|
|
2768
|
+
} @catch (NSException *exception) {
|
|
2769
|
+
return Napi::Boolean::New(env, false);
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2699
2773
|
// NAPI Function: Enable/Disable Auto Bring To Front
|
|
2700
2774
|
Napi::Value SetBringToFrontEnabled(const Napi::CallbackInfo& info) {
|
|
2701
2775
|
Napi::Env env = info.Env();
|
|
@@ -2937,6 +3011,7 @@ Napi::Object InitWindowSelector(Napi::Env env, Napi::Object exports) {
|
|
|
2937
3011
|
exports.Set("getSelectedWindowInfo", Napi::Function::New(env, GetSelectedWindowInfo));
|
|
2938
3012
|
exports.Set("getWindowSelectionStatus", Napi::Function::New(env, GetWindowSelectionStatus));
|
|
2939
3013
|
exports.Set("bringWindowToFront", Napi::Function::New(env, BringWindowToFront));
|
|
3014
|
+
exports.Set("activateWindowOwnerApp", Napi::Function::New(env, ActivateWindowOwnerApp));
|
|
2940
3015
|
exports.Set("setBringToFrontEnabled", Napi::Function::New(env, SetBringToFrontEnabled));
|
|
2941
3016
|
exports.Set("showRecordingPreview", Napi::Function::New(env, ShowRecordingPreview));
|
|
2942
3017
|
exports.Set("hideRecordingPreview", Napi::Function::New(env, HideRecordingPreview));
|