node-mac-recorder 2.24.8 → 2.24.10
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/package.json +1 -1
- package/src/avfoundation_recorder.mm +58 -7
- package/src/cursor_tracker.mm +21 -0
- package/src/screen_capture_kit.mm +89 -4
- package/src/window_selector.mm +49 -29
package/package.json
CHANGED
|
@@ -123,6 +123,10 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
123
123
|
MRLog(@"🔍 Scale factor: %.1fx → Standard display", scaleFactor);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// H.264 4:2:0 CIFT boyut ister — bkz. screen_capture_kit.mm'deki ayni guard.
|
|
127
|
+
recordingSize.width = MAX(2.0, floor(recordingSize.width / 2.0) * 2.0);
|
|
128
|
+
recordingSize.height = MAX(2.0, floor(recordingSize.height / 2.0) * 2.0);
|
|
129
|
+
|
|
126
130
|
MRLog(@"🎯 Recording size: %.0fx%.0f (using actual physical dimensions for Retina fix)", recordingSize.width, recordingSize.height);
|
|
127
131
|
|
|
128
132
|
NSString *normalizedQuality = @"high";
|
|
@@ -140,7 +144,6 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
140
144
|
|
|
141
145
|
NSString *codecKey = AVVideoCodecTypeH264;
|
|
142
146
|
NSInteger bitrate = kAVFoundationHighQualityVideoBitrate;
|
|
143
|
-
NSNumber *qualityHint = @1.0;
|
|
144
147
|
|
|
145
148
|
if ([normalizedQuality isEqualToString:@"low"]) {
|
|
146
149
|
NSInteger multiplier = 10;
|
|
@@ -149,7 +152,6 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
149
152
|
bitrate = (NSInteger)(recordingSize.width * recordingSize.height * multiplier);
|
|
150
153
|
bitrate = MAX(bitrate, minBitrate);
|
|
151
154
|
bitrate = MIN(bitrate, maxBitrate);
|
|
152
|
-
qualityHint = @0.85;
|
|
153
155
|
} else if ([normalizedQuality isEqualToString:@"medium"]) {
|
|
154
156
|
NSInteger multiplier = 18;
|
|
155
157
|
NSInteger minBitrate = 18 * 1000 * 1000;
|
|
@@ -157,7 +159,6 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
157
159
|
bitrate = (NSInteger)(recordingSize.width * recordingSize.height * multiplier);
|
|
158
160
|
bitrate = MAX(bitrate, minBitrate);
|
|
159
161
|
bitrate = MIN(bitrate, maxBitrate);
|
|
160
|
-
qualityHint = @0.9;
|
|
161
162
|
} else { // high - çözünürlüğe DUYARLI yüksek kalite (eski sabit 50 Mbps yerine)
|
|
162
163
|
NSInteger multiplier = 32; // ~0.53 bpp @60fps
|
|
163
164
|
NSInteger minBitrate = kAVFoundationHighQualityVideoBitrate; // 100 Mbps taban
|
|
@@ -165,7 +166,6 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
165
166
|
bitrate = (NSInteger)(recordingSize.width * recordingSize.height * multiplier);
|
|
166
167
|
bitrate = MAX(bitrate, minBitrate);
|
|
167
168
|
bitrate = MIN(bitrate, maxBitrate);
|
|
168
|
-
qualityHint = @1.0;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
MRLog(@"🎬 AVFoundation encoder (%@): %dx%d, codec=H.264 High Profile, bitrate=%.2fMbps",
|
|
@@ -183,14 +183,65 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
183
183
|
AVVideoMaxKeyFrameIntervalKey: @((int)fps),
|
|
184
184
|
AVVideoAllowFrameReorderingKey: @YES,
|
|
185
185
|
AVVideoExpectedSourceFrameRateKey: @((int)fps),
|
|
186
|
-
AVVideoQualityKey:
|
|
186
|
+
// AVVideoQualityKey BILEREK YOK — bkz. screen_capture_kit.mm:
|
|
187
|
+
// avc1 icin gecersiz, yazilim encoder yolunda AVAssetWriterInput
|
|
188
|
+
// istisna atip kaydi sessizce bos birakiyor.
|
|
187
189
|
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
|
|
188
190
|
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC
|
|
189
191
|
}
|
|
190
192
|
};
|
|
191
193
|
|
|
192
|
-
// Create video input
|
|
193
|
-
|
|
194
|
+
// Create video input — kademeli geri dusus (bkz. screen_capture_kit.mm).
|
|
195
|
+
// AVFoundation reddettigi sikistirma ozelliginde NSError degil ISTISNA
|
|
196
|
+
// atiyor; yakalanmazsa kayit sessizce bos cikiyor.
|
|
197
|
+
NSArray<NSDictionary *> *settingsTiers = @[
|
|
198
|
+
videoSettings,
|
|
199
|
+
@{
|
|
200
|
+
AVVideoCodecKey: codecKey,
|
|
201
|
+
AVVideoWidthKey: @((int)recordingSize.width),
|
|
202
|
+
AVVideoHeightKey: @((int)recordingSize.height),
|
|
203
|
+
AVVideoCompressionPropertiesKey: @{
|
|
204
|
+
AVVideoAverageBitRateKey: @(bitrate),
|
|
205
|
+
AVVideoMaxKeyFrameIntervalKey: @((int)fps),
|
|
206
|
+
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
@{
|
|
210
|
+
AVVideoCodecKey: codecKey,
|
|
211
|
+
AVVideoWidthKey: @((int)recordingSize.width),
|
|
212
|
+
AVVideoHeightKey: @((int)recordingSize.height),
|
|
213
|
+
AVVideoCompressionPropertiesKey: @{ AVVideoAverageBitRateKey: @(bitrate) }
|
|
214
|
+
},
|
|
215
|
+
@{
|
|
216
|
+
AVVideoCodecKey: codecKey,
|
|
217
|
+
AVVideoWidthKey: @((int)recordingSize.width),
|
|
218
|
+
AVVideoHeightKey: @((int)recordingSize.height)
|
|
219
|
+
}
|
|
220
|
+
];
|
|
221
|
+
g_avVideoInput = nil;
|
|
222
|
+
NSString *lastRejection = nil;
|
|
223
|
+
for (NSUInteger tier = 0; tier < settingsTiers.count; tier++) {
|
|
224
|
+
@try {
|
|
225
|
+
g_avVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
|
|
226
|
+
outputSettings:settingsTiers[tier]];
|
|
227
|
+
} @catch (NSException *exception) {
|
|
228
|
+
g_avVideoInput = nil;
|
|
229
|
+
lastRejection = exception.reason;
|
|
230
|
+
NSLog(@"⚠️ AVFoundation encoder settings tier %lu rejected: %@", (unsigned long)tier, exception.reason);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (g_avVideoInput) {
|
|
234
|
+
if (tier > 0) {
|
|
235
|
+
NSLog(@"⚠️ AVFoundation encoder fell back to settings tier %lu; last rejection: %@",
|
|
236
|
+
(unsigned long)tier, lastRejection);
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (!g_avVideoInput) {
|
|
242
|
+
NSLog(@"❌ AVFoundation: no accepted H.264 encoder settings (%@)", lastRejection ?: @"unknown");
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
194
245
|
g_avVideoInput.expectsMediaDataInRealTime = YES;
|
|
195
246
|
|
|
196
247
|
// Create pixel buffer adaptor with compatibility
|
package/src/cursor_tracker.mm
CHANGED
|
@@ -865,9 +865,30 @@ static bool g_leftMouseDown = false;
|
|
|
865
865
|
static bool g_rightMouseDown = false;
|
|
866
866
|
static NSString *g_lastEventType = @"move";
|
|
867
867
|
|
|
868
|
+
// Erisilebilirlik izni durumu (istem GOSTERMEDEN).
|
|
869
|
+
// NEDEN: izin yokken bile AX cagrilari yapiliyordu; macOS bunun uzerine
|
|
870
|
+
// "Erisilebilirlik" izin dialogunu aciyor ve bu fonksiyon kayit boyunca
|
|
871
|
+
// yuksek frekansta cagrildigi icin istem tekrar tekrar cikiyor. Durum
|
|
872
|
+
// saniyede bir tazelenir: kullanici izni verdigi anda yol kendiliginden
|
|
873
|
+
// devreye girer.
|
|
874
|
+
static bool accessibilityTrustedCached(void) {
|
|
875
|
+
static CFAbsoluteTime lastCheck = 0;
|
|
876
|
+
static bool trusted = false;
|
|
877
|
+
CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
|
|
878
|
+
if (lastCheck == 0 || now - lastCheck > 1.0) {
|
|
879
|
+
trusted = AXIsProcessTrusted();
|
|
880
|
+
lastCheck = now;
|
|
881
|
+
}
|
|
882
|
+
return trusted;
|
|
883
|
+
}
|
|
884
|
+
|
|
868
885
|
// Accessibility tabanlı cursor tip tespiti
|
|
869
886
|
static NSString* detectCursorTypeUsingAccessibility(CGPoint cursorPos) {
|
|
870
887
|
@autoreleasepool {
|
|
888
|
+
if (!accessibilityTrustedCached()) {
|
|
889
|
+
return nil;
|
|
890
|
+
}
|
|
891
|
+
|
|
871
892
|
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
872
893
|
if (!systemWide) {
|
|
873
894
|
return nil;
|
|
@@ -936,8 +936,6 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
936
936
|
SCKQualityBitrateForDimensions(normalizedQuality, width, height, encoderFPS,
|
|
937
937
|
&bitrate, &bpp, &minBitrate, &maxBitrate);
|
|
938
938
|
|
|
939
|
-
NSNumber *qualityHint = [normalizedQuality isEqualToString:@"high"] ? @1.0 : ([normalizedQuality isEqualToString:@"medium"] ? @0.9 : @0.85);
|
|
940
|
-
|
|
941
939
|
MRLog(@"🎬 Screen encoder (%@): %ldx%ld@%ldfps, codec=H.264 High Profile, bitrate=%.2fMbps (%.2f bpp, min=%ldMbps max=%ldMbps)",
|
|
942
940
|
normalizedQuality,
|
|
943
941
|
(long)width,
|
|
@@ -957,7 +955,14 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
957
955
|
// edilebilir. Kapatmak fansiz makinelerde kare dusmesini azaltir.
|
|
958
956
|
AVVideoAllowFrameReorderingKey: @NO,
|
|
959
957
|
AVVideoExpectedSourceFrameRateKey: @(encoderFPS),
|
|
960
|
-
AVVideoQualityKey:
|
|
958
|
+
// AVVideoQualityKey BILEREK YOK: Apple belgesine gore yalnizca JPEG ve
|
|
959
|
+
// ProRes icin gecerli. H.264'te (avc1) donanim encoder'i onu sessizce
|
|
960
|
+
// yok sayiyordu, ama encoder yazilim yoluna dustugunde AVFoundation
|
|
961
|
+
// dogrulamasi sertlesiyor ve AVAssetWriterInput ISTISNA atiyor:
|
|
962
|
+
// "Compression property Quality is not supported for video codec type
|
|
963
|
+
// avc1". Istisna tum kayit kurulumunu iptal ettigi icin kayit "basladi"
|
|
964
|
+
// gorunup dosya hic olusmuyordu (editorde bos canvas). Kaliteyi zaten
|
|
965
|
+
// AVVideoAverageBitRateKey belirliyor.
|
|
961
966
|
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
|
|
962
967
|
AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC,
|
|
963
968
|
AVVideoAverageNonDroppableFrameRateKey: @(encoderFPS),
|
|
@@ -982,7 +987,75 @@ extern "C" NSString *ScreenCaptureKitCurrentAudioPath(void) {
|
|
|
982
987
|
AVVideoCompressionPropertiesKey: compressionProps
|
|
983
988
|
};
|
|
984
989
|
|
|
985
|
-
|
|
990
|
+
// AVFoundation, avc1 icin hangi sikistirma ozelligini kabul ettigine
|
|
991
|
+
// encoder yoluna gore karar veriyor ve reddettiginde NSError DEGIL ISTISNA
|
|
992
|
+
// atiyor. Istisna tum kayit kurulumunu iptal ettigi icin kayit "basladi"
|
|
993
|
+
// gorunup dosya hic olusmuyor; kullanici ancak editorde bos canvas gorunce
|
|
994
|
+
// anliyor. OLCULDU (macOS 26, 1x harici ekran, iTerm penceresi): once
|
|
995
|
+
// "Compression property Quality is not supported for video codec type avc1",
|
|
996
|
+
// o kaldirilinca "AverageNonDroppableFrameRate is not supported" — yani tek
|
|
997
|
+
// tek anahtar elemek guvenilir degil. Ayni ozelliklerle ayni ekrandaki baska
|
|
998
|
+
// pencereler sorunsuz calisiyor.
|
|
999
|
+
//
|
|
1000
|
+
// Bu yuzden ayarlar KADEMELI denenir: once tam set, sonra yalnizca her
|
|
1001
|
+
// encoder'in destekledigi cekirdek set, en sonda codec+boyut. Ilk kabul
|
|
1002
|
+
// edilen kazanir; kayit hicbir kosulda sessizce bos cikmaz.
|
|
1003
|
+
NSMutableArray<NSDictionary *> *settingsTiers = [NSMutableArray array];
|
|
1004
|
+
[settingsTiers addObject:videoSettings];
|
|
1005
|
+
[settingsTiers addObject:@{
|
|
1006
|
+
AVVideoCodecKey: AVVideoCodecTypeH264,
|
|
1007
|
+
AVVideoWidthKey: @(width),
|
|
1008
|
+
AVVideoHeightKey: @(height),
|
|
1009
|
+
AVVideoColorPropertiesKey: colorProps,
|
|
1010
|
+
AVVideoCompressionPropertiesKey: @{
|
|
1011
|
+
AVVideoAverageBitRateKey: @(bitrate),
|
|
1012
|
+
AVVideoMaxKeyFrameIntervalKey: @(encoderFPS),
|
|
1013
|
+
AVVideoAllowFrameReorderingKey: @NO,
|
|
1014
|
+
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel
|
|
1015
|
+
}
|
|
1016
|
+
}];
|
|
1017
|
+
[settingsTiers addObject:@{
|
|
1018
|
+
AVVideoCodecKey: AVVideoCodecTypeH264,
|
|
1019
|
+
AVVideoWidthKey: @(width),
|
|
1020
|
+
AVVideoHeightKey: @(height),
|
|
1021
|
+
AVVideoCompressionPropertiesKey: @{ AVVideoAverageBitRateKey: @(bitrate) }
|
|
1022
|
+
}];
|
|
1023
|
+
[settingsTiers addObject:@{
|
|
1024
|
+
AVVideoCodecKey: AVVideoCodecTypeH264,
|
|
1025
|
+
AVVideoWidthKey: @(width),
|
|
1026
|
+
AVVideoHeightKey: @(height)
|
|
1027
|
+
}];
|
|
1028
|
+
|
|
1029
|
+
g_videoInput = nil;
|
|
1030
|
+
NSString *lastRejection = nil;
|
|
1031
|
+
for (NSUInteger tier = 0; tier < settingsTiers.count; tier++) {
|
|
1032
|
+
@try {
|
|
1033
|
+
g_videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
|
|
1034
|
+
outputSettings:settingsTiers[tier]];
|
|
1035
|
+
} @catch (NSException *exception) {
|
|
1036
|
+
g_videoInput = nil;
|
|
1037
|
+
lastRejection = exception.reason;
|
|
1038
|
+
NSLog(@"⚠️ Video encoder settings tier %lu rejected: %@", (unsigned long)tier, exception.reason);
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if (g_videoInput) {
|
|
1042
|
+
if (tier > 0) {
|
|
1043
|
+
NSLog(@"⚠️ Video encoder fell back to settings tier %lu (%ldx%ld); last rejection: %@",
|
|
1044
|
+
(unsigned long)tier, (long)width, (long)height, lastRejection);
|
|
1045
|
+
}
|
|
1046
|
+
break;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
if (!g_videoInput) {
|
|
1050
|
+
MRLog(@"❌ Video writer failed: no accepted encoder settings (%@)", lastRejection ?: @"unknown");
|
|
1051
|
+
if (error) {
|
|
1052
|
+
*error = [NSError errorWithDomain:@"ScreenCaptureKitRecorder" code:-101 userInfo:@{
|
|
1053
|
+
NSLocalizedDescriptionKey: [NSString stringWithFormat:@"No accepted H.264 encoder settings: %@",
|
|
1054
|
+
lastRejection ?: @"unknown"]
|
|
1055
|
+
}];
|
|
1056
|
+
}
|
|
1057
|
+
return NO;
|
|
1058
|
+
}
|
|
986
1059
|
g_videoInput.expectsMediaDataInRealTime = YES;
|
|
987
1060
|
|
|
988
1061
|
AVAssetWriterInputPixelBufferAdaptor *pixelAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:g_videoInput sourcePixelBufferAttributes:@{
|
|
@@ -1786,6 +1859,18 @@ static void SCKPerformRecordingSetup(NSDictionary *config, SCShareableContent *c
|
|
|
1786
1859
|
(long)recordingHeight);
|
|
1787
1860
|
}
|
|
1788
1861
|
|
|
1862
|
+
// H.264 4:2:0 luma duzlemi CIFT boyut ister; tek sayili genislik/yukseklik
|
|
1863
|
+
// encoder'i gereksiz yere yazilim yoluna itebiliyor. 1x harici ekranda
|
|
1864
|
+
// pencere boyutlari dogrudan piksele esitlendigi icin (Retina'da 2x ile
|
|
1865
|
+
// her zaman cift olur) tek sayilar yalnizca orada ortaya cikiyor.
|
|
1866
|
+
// En fazla 1 piksel kirpilir; olcek/konum degismez.
|
|
1867
|
+
// NOT: bu tek basina "bos kayit" hatasini cozmez — asil koruma asagidaki
|
|
1868
|
+
// kademeli encoder ayari geri dususudur.
|
|
1869
|
+
if (recordingWidth % 2 != 0) recordingWidth -= 1;
|
|
1870
|
+
if (recordingHeight % 2 != 0) recordingHeight -= 1;
|
|
1871
|
+
recordingWidth = MAX(2, recordingWidth);
|
|
1872
|
+
recordingHeight = MAX(2, recordingHeight);
|
|
1873
|
+
|
|
1789
1874
|
SCStreamConfiguration *streamConfig = [[SCStreamConfiguration alloc] init];
|
|
1790
1875
|
streamConfig.width = recordingWidth;
|
|
1791
1876
|
streamConfig.height = recordingHeight;
|
package/src/window_selector.mm
CHANGED
|
@@ -898,10 +898,57 @@ bool activateWindowOwnerApp(int windowId) {
|
|
|
898
898
|
}
|
|
899
899
|
}
|
|
900
900
|
|
|
901
|
+
// Hedef pencerenin sahibi uygulamayi ONE ALIR (Accessibility GEREKTIRMEZ).
|
|
902
|
+
// bringWindowToFront'un AX yolu izin isterken bu yol yalnizca
|
|
903
|
+
// NSRunningApplication kullanir; izin yokken tek calisan yol budur.
|
|
904
|
+
static bool activateOwnerAppForWindow(int windowId) {
|
|
905
|
+
@autoreleasepool {
|
|
906
|
+
// Method 2: Light activation fallback (minimal app activation)
|
|
907
|
+
NSLog(@" 🔄 Trying minimal activation for window %d", windowId);
|
|
908
|
+
|
|
909
|
+
// Get window info to find the process
|
|
910
|
+
CFArrayRef cgWindowList = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
|
|
911
|
+
if (cgWindowList) {
|
|
912
|
+
NSArray *windowArray = (__bridge NSArray *)cgWindowList;
|
|
913
|
+
|
|
914
|
+
for (NSDictionary *windowInfo in windowArray) {
|
|
915
|
+
NSNumber *cgWindowId = [windowInfo objectForKey:(NSString *)kCGWindowNumber];
|
|
916
|
+
if ([cgWindowId intValue] == windowId) {
|
|
917
|
+
// Get process ID
|
|
918
|
+
NSNumber *processId = [windowInfo objectForKey:(NSString *)kCGWindowOwnerPID];
|
|
919
|
+
if (processId) {
|
|
920
|
+
// Light activation - only bring app to front, don't activate all windows
|
|
921
|
+
NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:[processId intValue]];
|
|
922
|
+
if (app) {
|
|
923
|
+
// Use NSApplicationActivateIgnoringOtherApps only (no NSApplicationActivateAllWindows)
|
|
924
|
+
[app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
|
|
925
|
+
NSLog(@" ✅ App minimally activated: PID %d (specific window should be frontmost)", [processId intValue]);
|
|
926
|
+
CFRelease(cgWindowList);
|
|
927
|
+
return true;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
break;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
CFRelease(cgWindowList);
|
|
934
|
+
}
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
901
939
|
// Bring window to front using Accessibility API
|
|
902
940
|
bool bringWindowToFront(int windowId) {
|
|
903
941
|
@autoreleasepool {
|
|
904
942
|
@try {
|
|
943
|
+
// TCC istemi CIKMASIN: untrusted bir surecin systemWide element
|
|
944
|
+
// uzerindeki AX cagrilari macOS'a "Erisilebilirlik" dialogunu
|
|
945
|
+
// actiriyor — pencere kaydi HER baslatildiginda yeniden. Izin yoksa
|
|
946
|
+
// AX yolunu hic deneme; hafif aktivasyon zaten ayni isi goruyor.
|
|
947
|
+
// AXIsProcessTrusted() kendisi istem GOSTERMEZ.
|
|
948
|
+
if (!AXIsProcessTrusted()) {
|
|
949
|
+
return activateOwnerAppForWindow(windowId);
|
|
950
|
+
}
|
|
951
|
+
|
|
905
952
|
// Method 1: Using Accessibility API (most reliable)
|
|
906
953
|
AXUIElementRef systemWide = AXUIElementCreateSystemWide();
|
|
907
954
|
if (!systemWide) return false;
|
|
@@ -965,35 +1012,8 @@ bool bringWindowToFront(int windowId) {
|
|
|
965
1012
|
|
|
966
1013
|
CFRelease(systemWide);
|
|
967
1014
|
|
|
968
|
-
//
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
// Get window info to find the process
|
|
972
|
-
CFArrayRef cgWindowList = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
|
|
973
|
-
if (cgWindowList) {
|
|
974
|
-
NSArray *windowArray = (__bridge NSArray *)cgWindowList;
|
|
975
|
-
|
|
976
|
-
for (NSDictionary *windowInfo in windowArray) {
|
|
977
|
-
NSNumber *cgWindowId = [windowInfo objectForKey:(NSString *)kCGWindowNumber];
|
|
978
|
-
if ([cgWindowId intValue] == windowId) {
|
|
979
|
-
// Get process ID
|
|
980
|
-
NSNumber *processId = [windowInfo objectForKey:(NSString *)kCGWindowOwnerPID];
|
|
981
|
-
if (processId) {
|
|
982
|
-
// Light activation - only bring app to front, don't activate all windows
|
|
983
|
-
NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:[processId intValue]];
|
|
984
|
-
if (app) {
|
|
985
|
-
// Use NSApplicationActivateIgnoringOtherApps only (no NSApplicationActivateAllWindows)
|
|
986
|
-
[app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
|
|
987
|
-
NSLog(@" ✅ App minimally activated: PID %d (specific window should be frontmost)", [processId intValue]);
|
|
988
|
-
CFRelease(cgWindowList);
|
|
989
|
-
return true;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
break;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
CFRelease(cgWindowList);
|
|
996
|
-
}
|
|
1015
|
+
// AX yolu pencereyi bulamadi → izinsiz de calisan hafif aktivasyon.
|
|
1016
|
+
if (activateOwnerAppForWindow(windowId)) return true;
|
|
997
1017
|
|
|
998
1018
|
return false;
|
|
999
1019
|
|