1 ///////////////////////////////////////////////////////////////////////////////
2 // MuldeR's Utilities for Qt
3 // Copyright (C) 2004-2023 LoRd_MuldeR <MuldeR2@GMX.de>
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version, but always including the *additional*
9 // restrictions defined in the "License.txt" file.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License along
17 // with this program; if not, write to the Free Software Foundation, Inc.,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // http://www.gnu.org/licenses/gpl-2.0.txt
21 ///////////////////////////////////////////////////////////////////////////////
25 #define WIN32_LEAN_AND_MEAN 1
29 #include <MUtils/Terminal.h>
30 #include <MUtils/Global.h>
31 #include <MUtils/OSSupport.h>
32 #include "Utils_Win32.h"
33 #include "CriticalSection_Win32.h"
37 #include <QStringList>
49 #define stricmp(X,Y) _stricmp((X),(Y))
52 #define VALID_HANLDE(X) (((X) != NULL) && ((X) != INVALID_HANDLE_VALUE))
54 ///////////////////////////////////////////////////////////////////////////////
56 ///////////////////////////////////////////////////////////////////////////////
59 static MUtils::Internal::CriticalSection g_terminal_lock
;
61 //Is terminal attached?
62 static QAtomicInt g_terminal_attached
;
64 //Terminal output buffer
65 static const size_t BUFF_SIZE
= 8192;
66 static char g_conOutBuff
[BUFF_SIZE
] = { '\0' };
69 static QScopedPointer
<std::filebuf
> g_fileBuf_stdout
;
70 static QScopedPointer
<std::filebuf
> g_fileBuf_stderr
;
73 static QScopedPointer
<QFile
> g_terminal_log_file
;
76 static HICON g_terminal_icon
= NULL
;
78 ///////////////////////////////////////////////////////////////////////////////
80 ///////////////////////////////////////////////////////////////////////////////
82 static inline void make_timestamp(char *timestamp
, const size_t &buffsize
)
88 if(localtime_s(&timeinfo
, &rawtime
) == 0)
90 strftime(timestamp
, buffsize
, "%H:%M:%S", &timeinfo
);
98 static inline bool null_or_whitespace(const char *const str
)
105 if (!(isspace(str
[pos
]) || iscntrl(str
[pos
])))
115 static inline size_t clean_string(char *const str
)
117 bool space_flag
= true;
118 size_t src
= 0, out
= 0;
122 if (isspace(str
[src
]) || iscntrl(str
[src
])) /*replace any space-sequence with a single space character*/
131 else /*otherwise we'll just copy over the current character*/
142 if (space_flag
&& (out
> 0)) /*trim trailing space, if any*/
151 static inline void set_hicon(HICON
*const ptr
, const HICON val
)
160 ///////////////////////////////////////////////////////////////////////////////
162 ///////////////////////////////////////////////////////////////////////////////
164 static inline std::filebuf
*terminal_connect(FILE *const fs
, std::ostream
&os
)
166 std::filebuf
*result
= NULL
;
168 if (freopen_s(&temp
, "CONOUT$", "wb", fs
) == 0)
170 os
.rdbuf(result
= new std::filebuf(temp
));
175 static void terminal_shutdown(void)
177 MUtils::Internal::CSLocker
lock(g_terminal_lock
);
179 if (g_terminal_attached
.fetchAndStoreOrdered(0) > 0)
181 g_fileBuf_stdout
.reset();
182 g_fileBuf_stderr
.reset();
184 if(stdout
) freopen_s(&temp
[0], "NUL", "wb", stdout
);
185 if(stderr
) freopen_s(&temp
[1], "NUL", "wb", stderr
);
187 set_hicon(&g_terminal_icon
, NULL
);
191 void MUtils::Terminal::setup(int &argc
, char **argv
, const char* const appName
, const bool forceEnabled
)
193 MUtils::Internal::CSLocker
lock(g_terminal_lock
);
194 bool enableConsole
= (MUTILS_DEBUG
) || forceEnabled
;
198 wchar_t *logfile
= NULL
; size_t logfile_len
= 0;
199 if(!_wdupenv_s(&logfile
, &logfile_len
, L
"MUTILS_LOGFILE"))
201 if(logfile
&& (logfile_len
> 0))
203 g_terminal_log_file
.reset(new QFile(MUTILS_QSTR(logfile
)));
204 if(g_terminal_log_file
->open(QIODevice::WriteOnly
))
206 static const char MARKER
[3] = { '\xEF', '\xBB', '\xBF' };
207 g_terminal_log_file
->write(MARKER
, 3);
216 for(int i
= 0; i
< argc
; i
++)
218 if(!stricmp(argv
[i
], "--console"))
220 enableConsole
= true;
222 else if(!stricmp(argv
[i
], "--no-console"))
224 enableConsole
= false;
231 if(!g_terminal_attached
.fetchAndStoreOrdered(1))
233 if(AllocConsole() != FALSE
)
235 SetConsoleOutputCP(CP_UTF8
);
236 SetConsoleCtrlHandler(NULL
, TRUE
);
237 if(appName
&& appName
[0])
240 _snprintf_s(title
, 128, _TRUNCATE
, "%s | Debug Console", appName
);
241 SetConsoleTitleA(title
);
246 g_terminal_attached
.fetchAndStoreOrdered(0); /*failed*/
250 if(MUTILS_BOOLIFY(g_terminal_attached
))
252 g_fileBuf_stdout
.reset(terminal_connect(stdout
, std::cout
));
253 g_fileBuf_stderr
.reset(terminal_connect(stderr
, std::cerr
));
255 atexit(terminal_shutdown
);
257 const HWND hwndConsole
= GetConsoleWindow();
258 if((hwndConsole
!= NULL
) && (hwndConsole
!= INVALID_HANDLE_VALUE
))
260 HMENU hMenu
= GetSystemMenu(hwndConsole
, 0);
261 EnableMenuItem(hMenu
, SC_CLOSE
, MF_BYCOMMAND
| MF_GRAYED
);
262 RemoveMenu(hMenu
, SC_CLOSE
, MF_BYCOMMAND
);
264 SetWindowPos (hwndConsole
, HWND_TOP
, 0, 0, 0, 0, SWP_NOMOVE
|SWP_NOSIZE
|SWP_NOZORDER
|SWP_FRAMECHANGED
);
265 SetWindowLong(hwndConsole
, GWL_STYLE
, GetWindowLong(hwndConsole
, GWL_STYLE
) & (~WS_MAXIMIZEBOX
) & (~WS_MINIMIZEBOX
));
266 SetWindowPos (hwndConsole
, HWND_TOP
, 0, 0, 0, 0, SWP_NOMOVE
|SWP_NOSIZE
|SWP_NOZORDER
|SWP_FRAMECHANGED
);
272 ///////////////////////////////////////////////////////////////////////////////
274 ///////////////////////////////////////////////////////////////////////////////
277 static const WORD COLOR_RED
= FOREGROUND_RED
| FOREGROUND_INTENSITY
;
278 static const WORD COLOR_YELLOW
= FOREGROUND_GREEN
| FOREGROUND_RED
| FOREGROUND_INTENSITY
;
279 static const WORD COLOR_WHITE
= FOREGROUND_BLUE
| FOREGROUND_GREEN
| FOREGROUND_RED
| FOREGROUND_INTENSITY
;
280 static const WORD COLOR_DEFAULT
= FOREGROUND_BLUE
| FOREGROUND_GREEN
| FOREGROUND_RED
;
282 static void set_terminal_color(FILE *const fp
, const WORD
&attributes
)
284 if(_isatty(_fileno(fp
)))
286 const HANDLE hConsole
= (HANDLE
)(_get_osfhandle(_fileno(fp
)));
287 if (VALID_HANLDE(hConsole
))
289 SetConsoleTextAttribute(hConsole
, attributes
);
294 ///////////////////////////////////////////////////////////////////////////////
296 ///////////////////////////////////////////////////////////////////////////////
298 static const char *const FORMAT
= "[%c][%s] %s\r\n";
299 static const char *const GURU_MEDITATION
= "\n\nGURU MEDITATION !!!\n\n";
301 static void write_to_logfile(QFile
*const file
, const int &type
, const char *const message
)
305 if (null_or_whitespace(message
))
307 return; /*don't write empty message to log file*/
310 static char timestamp
[32];
311 make_timestamp(timestamp
, 32);
317 len
= _snprintf_s(g_conOutBuff
, BUFF_SIZE
, _TRUNCATE
, FORMAT
, 'C', timestamp
, message
);
320 len
= _snprintf_s(g_conOutBuff
, BUFF_SIZE
, _TRUNCATE
, FORMAT
, 'W', timestamp
, message
);
323 len
= _snprintf_s(g_conOutBuff
, BUFF_SIZE
, _TRUNCATE
, FORMAT
, 'I', timestamp
, message
);
329 if (clean_string(g_conOutBuff
) > 0)
331 file
->write(g_conOutBuff
);
337 static void write_to_debugger(const int &type
, const char *const message
)
341 if (null_or_whitespace(message
))
343 return; /*don't send empty message to debugger*/
346 static char timestamp
[32];
347 make_timestamp(timestamp
, 32);
353 len
= _snprintf_s(g_conOutBuff
, BUFF_SIZE
, _TRUNCATE
, FORMAT
, 'C', timestamp
, message
);
356 len
= _snprintf_s(g_conOutBuff
, BUFF_SIZE
, _TRUNCATE
, FORMAT
, 'W', timestamp
, message
);
359 len
= _snprintf_s(g_conOutBuff
, BUFF_SIZE
, _TRUNCATE
, FORMAT
, 'I', timestamp
, message
);
365 if (clean_string(g_conOutBuff
) > 0)
367 OutputDebugStringA(g_conOutBuff
);
372 static void write_to_terminal(const int &type
, const char *const message
)
378 set_terminal_color(stderr
, COLOR_RED
);
379 fprintf(stderr
, GURU_MEDITATION
);
380 fprintf(stderr
, "%s\n", message
);
383 set_terminal_color(stderr
, COLOR_YELLOW
);
384 fprintf(stderr
, "%s\n", message
);
387 set_terminal_color(stderr
, COLOR_WHITE
);
388 fprintf(stderr
, "%s\n", message
);
395 void MUtils::Terminal::write(const int &type
, const char *const message
)
397 MUtils::Internal::CSLocker
lock(g_terminal_lock
);
399 if(MUTILS_BOOLIFY(g_terminal_attached
))
401 write_to_terminal(type
, message
);
405 write_to_debugger(type
, message
);
408 if(!g_terminal_log_file
.isNull())
410 write_to_logfile(g_terminal_log_file
.data(), type
, message
);
414 ///////////////////////////////////////////////////////////////////////////////
416 ///////////////////////////////////////////////////////////////////////////////
418 void MUtils::Terminal::set_icon(const QIcon
&icon
)
420 MUtils::Internal::CSLocker
lock(g_terminal_lock
);
422 if(MUTILS_BOOLIFY(g_terminal_attached
) && (!(icon
.isNull() || MUtils::OS::running_on_wine())))
424 if(const HICON hIcon
= (HICON
) MUtils::Win32Utils::qicon_to_hicon(&icon
, 16, 16))
426 typedef BOOL(__stdcall
*SetConsoleIconFun
)(HICON
);
427 bool success
= false;
428 if (const SetConsoleIconFun pSetConsoleIconFun
= MUtils::Win32Utils::resolve
<SetConsoleIconFun
>(QLatin1String("kernel32"), QLatin1String("SetConsoleIcon")))
430 //const DWORD before = GetLastError();
431 if (pSetConsoleIconFun(hIcon
))
437 const DWORD error
= GetLastError();
438 qWarning("SetConsoleIcon() has failed! [Error: 0x%08X]", error
);
443 const HWND hwndConsole
= GetConsoleWindow();
444 if ((hwndConsole
!= NULL
) && (hwndConsole
!= INVALID_HANDLE_VALUE
))
446 SendMessage(hwndConsole
, WM_SETICON
, ICON_SMALL
, LPARAM(hIcon
));
452 set_hicon(&g_terminal_icon
, hIcon
);