1+ //
2+ // FLEXSwiftPrintRedirector.m
3+ // FLEX
4+ //
5+ // Created by 김인환 on 2025.
6+ // Copyright © 2025 FLEX Team. All rights reserved.
7+ //
8+
9+ #import " FLEXSwiftPrintRedirector.h"
10+ #import " FLEXSystemLogMessage.h"
11+
12+ @implementation FLEXSwiftPrintRedirector
13+
14+ static BOOL _isRedirectionEnabled = NO ;
15+ static int _originalStdout = -1 ;
16+ static int _originalStderr = -1 ;
17+ static NSPipe *_stdoutPipe = nil ;
18+ static NSPipe *_stderrPipe = nil ;
19+ static id _stdoutObserver = nil ;
20+ static id _stderrObserver = nil ;
21+ static dispatch_queue_t _logQueue = nil ;
22+ static void (^_messageHandler)(FLEXSystemLogMessage *) = nil;
23+ static NSMutableString *_stdoutBuffer = nil ;
24+ static NSMutableString *_stderrBuffer = nil ;
25+
26+ + (void )enableSwiftPrintRedirection {
27+ @synchronized (self) {
28+ if (_isRedirectionEnabled) {
29+ return ;
30+ }
31+
32+ _isRedirectionEnabled = YES ;
33+ _logQueue = dispatch_queue_create (" com.flex.swiftprint" , DISPATCH_QUEUE_SERIAL);
34+ _stdoutBuffer = [NSMutableString string ];
35+ _stderrBuffer = [NSMutableString string ];
36+
37+ // Save original stdout and stderr
38+ _originalStdout = dup (STDOUT_FILENO);
39+ _originalStderr = dup (STDERR_FILENO);
40+
41+ // Create pipes
42+ _stdoutPipe = [NSPipe pipe ];
43+ _stderrPipe = [NSPipe pipe ];
44+
45+ // Redirect stdout
46+ dup2 (_stdoutPipe.fileHandleForWriting .fileDescriptor , STDOUT_FILENO);
47+
48+ // Redirect stderr
49+ dup2 (_stderrPipe.fileHandleForWriting .fileDescriptor , STDERR_FILENO);
50+
51+ // Start reading from stdout pipe
52+ NSFileHandle *stdoutReadHandle = _stdoutPipe.fileHandleForReading ;
53+ _stdoutObserver = [[NSNotificationCenter defaultCenter ]
54+ addObserverForName: NSFileHandleReadCompletionNotification
55+ object: stdoutReadHandle
56+ queue: nil
57+ usingBlock: ^(NSNotification *note) {
58+ NSData *data = note.userInfo [NSFileHandleNotificationDataItem ];
59+ if (data.length > 0 ) {
60+ [self processOutputData: data isError: NO buffer: _stdoutBuffer];
61+ [stdoutReadHandle readInBackgroundAndNotify ];
62+ }
63+ }];
64+ [stdoutReadHandle readInBackgroundAndNotify ];
65+
66+ // Start reading from stderr pipe
67+ NSFileHandle *stderrReadHandle = _stderrPipe.fileHandleForReading ;
68+ _stderrObserver = [[NSNotificationCenter defaultCenter ]
69+ addObserverForName: NSFileHandleReadCompletionNotification
70+ object: stderrReadHandle
71+ queue: nil
72+ usingBlock: ^(NSNotification *note) {
73+ NSData *data = note.userInfo [NSFileHandleNotificationDataItem ];
74+ if (data.length > 0 ) {
75+ [self processOutputData: data isError: YES buffer: _stderrBuffer];
76+ [stderrReadHandle readInBackgroundAndNotify ];
77+ }
78+ }];
79+ [stderrReadHandle readInBackgroundAndNotify ];
80+ }
81+ }
82+
83+ + (void )disableSwiftPrintRedirection {
84+ @synchronized (self) {
85+ if (!_isRedirectionEnabled) {
86+ return ;
87+ }
88+
89+ _isRedirectionEnabled = NO ;
90+
91+ // Remove observers
92+ if (_stdoutObserver) {
93+ [[NSNotificationCenter defaultCenter ] removeObserver: _stdoutObserver];
94+ _stdoutObserver = nil ;
95+ }
96+ if (_stderrObserver) {
97+ [[NSNotificationCenter defaultCenter ] removeObserver: _stderrObserver];
98+ _stderrObserver = nil ;
99+ }
100+
101+ // Restore original stdout and stderr
102+ if (_originalStdout != -1 ) {
103+ dup2 (_originalStdout, STDOUT_FILENO);
104+ close (_originalStdout);
105+ _originalStdout = -1 ;
106+ }
107+ if (_originalStderr != -1 ) {
108+ dup2 (_originalStderr, STDERR_FILENO);
109+ close (_originalStderr);
110+ _originalStderr = -1 ;
111+ }
112+
113+ // Clean up pipes
114+ _stdoutPipe = nil ;
115+ _stderrPipe = nil ;
116+ }
117+ }
118+
119+ + (BOOL )isRedirectionEnabled {
120+ @synchronized (self) {
121+ return _isRedirectionEnabled;
122+ }
123+ }
124+
125+ + (void )setMessageHandler : (void (^)(FLEXSystemLogMessage *message))handler {
126+ @synchronized (self) {
127+ _messageHandler = [handler copy ];
128+ }
129+ }
130+
131+ + (void )processOutputData : (NSData *)data isError : (BOOL )isError buffer : (NSMutableString *)buffer {
132+ // Write to original file descriptor first to preserve console output
133+ int originalFd = isError ? _originalStderr : _originalStdout;
134+ if (originalFd != -1 ) {
135+ write (originalFd, data.bytes , data.length );
136+ }
137+
138+ // Process on background queue to avoid blocking
139+ dispatch_async (_logQueue, ^{
140+ NSString *output = [[NSString alloc ] initWithData: data encoding: NSUTF8StringEncoding];
141+ if (!output) {
142+ output = [[NSString alloc ] initWithData: data encoding: NSASCIIStringEncoding];
143+ }
144+
145+ if (output && _messageHandler) {
146+ // Append to buffer to handle partial lines
147+ @synchronized (buffer) {
148+ [buffer appendString: output];
149+
150+ // Process complete lines (ending with newline)
151+ while (YES ) {
152+ NSRange newlineRange = [buffer rangeOfString: @" \n " ];
153+ if (newlineRange.location == NSNotFound ) {
154+ break ;
155+ }
156+
157+ // Extract complete line
158+ NSString *line = [buffer substringToIndex: newlineRange.location];
159+ [buffer deleteCharactersInRange: NSMakeRange (0 , newlineRange.location + 1 )];
160+
161+ NSString *trimmedLine = [line stringByTrimmingCharactersInSet:
162+ [NSCharacterSet whitespaceCharacterSet ]];
163+ if (trimmedLine.length > 0 ) {
164+ // Create FLEX message directly without going through NSLog/os_log
165+ NSString *formattedMessage = [NSString stringWithFormat: @" [SwiftPrint] %@ " , trimmedLine];
166+ FLEXSystemLogMessage *message = [FLEXSystemLogMessage
167+ logMessageFromDate: [NSDate date ]
168+ text: formattedMessage];
169+
170+ dispatch_async (dispatch_get_main_queue (), ^{
171+ _messageHandler (message);
172+ });
173+ }
174+ }
175+ }
176+ }
177+ });
178+ }
179+
180+ @end
0 commit comments