feat(ansi-console): Format console with ansi excapes

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers 2025-04-17 23:18:57 -07:00
parent 4a2b5c72dc
commit 45b6454222
No known key found for this signature in database
GPG Key ID: E10E321EB160949B
7 changed files with 225 additions and 174 deletions

View File

@ -125,6 +125,7 @@
#include <FileSystem.h>
#include <LocalPeer.h>
#include <QStringLiteral>
#include <stdlib.h>
#include <sys.h>
#include "SysInfo.h"
@ -153,11 +154,16 @@
#endif
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <QStyleHints>
#include "console/WindowsConsole.h"
#endif
#include "console/Console.h"
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
@ -165,6 +171,69 @@ static const QLatin1String liveCheckFile("live.check");
PixmapCache* PixmapCache::s_instance = nullptr;
static bool isANSIColorConsole;
static QString defaultLogFormat = QStringLiteral(
"%{time process}"
" "
"%{if-debug}D%{endif}"
"%{if-info}I%{endif}"
"%{if-warning}W%{endif}"
"%{if-critical}C%{endif}"
"%{if-fatal}F%{endif}"
" "
"|"
" "
"%{function}:%{line}"
" "
"|"
" "
"%{if-category}[%{category}]: %{endif}"
"%{message}");
#define ansi_reset "\x1b[0m"
#define ansi_bold "\x1b[1m"
#define ansi_faint "\x1b[2m"
#define ansi_italic "\x1b[3m"
#define ansi_red_fg "\x1b[31m"
#define ansi_green_fg "\x1b[32m"
#define ansi_yellow_fg "\x1b[33m"
#define ansi_blue_fg "\x1b[34m"
#define ansi_purple_fg "\x1b[35m"
static QString ansiLogFormat = QStringLiteral(
"%{time process}"
" "
"%{if-debug}" ansi_bold ansi_blue_fg "D" ansi_reset
"%{endif}"
"%{if-info}" ansi_bold ansi_green_fg "I" ansi_reset
"%{endif}"
"%{if-warning}" ansi_bold ansi_yellow_fg "W" ansi_reset
"%{endif}"
"%{if-critical}" ansi_bold ansi_red_fg "C" ansi_reset
"%{endif}"
"%{if-fatal}" ansi_bold ansi_red_fg "F" ansi_reset
"%{endif}"
" "
"|"
" " ansi_faint ansi_italic "%{function}:%{line}" ansi_reset
" "
"|"
" "
"%{if-category}[" ansi_bold ansi_purple_fg "%{category}" ansi_reset
"]: %{endif}"
"%{message}");
#undef ansi_purple_fg
#undef ansi_blue_fg
#undef ansi_yellow_fg
#undef ansi_green_fg
#undef ansi_red_fg
#undef ansi_italic
#undef ansi_faint
#undef ansi_bold
#undef ansi_reset
namespace {
/** This is used so that we can output to the log file in addition to the CLI. */
@ -173,11 +242,24 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt
static std::mutex loggerMutex;
const std::lock_guard<std::mutex> lock(loggerMutex); // synchronized, QFile logFile is not thread-safe
if (isANSIColorConsole) {
// ensure default is set for log file
qSetMessagePattern(defaultLogFormat);
}
QString out = qFormatLogMessage(type, context, msg);
out += QChar::LineFeed;
APPLICATION->logFile->write(out.toUtf8());
APPLICATION->logFile->flush();
if (isANSIColorConsole) {
// format ansi for console;
qSetMessagePattern(ansiLogFormat);
out = qFormatLogMessage(type, context, msg);
out += QChar::LineFeed;
}
QTextStream(stderr) << out.toLocal8Bit();
fflush(stderr);
}
@ -218,8 +300,18 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// attach the parent console if stdout not already captured
if (AttachWindowsConsole()) {
consoleAttached = true;
if (auto err = EnableAnsiSupport(); !err) {
isANSIColorConsole = true;
} else {
std::cout << "Error setting up ansi console" << err.message() << std::endl;
}
}
#else
if (console::isConsole()) {
isANSIColorConsole = true;
}
#endif
setOrganizationName(BuildConfig.LAUNCHER_NAME);
setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN);
setApplicationName(BuildConfig.LAUNCHER_NAME);
@ -448,27 +540,14 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
return;
}
qInstallMessageHandler(appDebugOutput);
qSetMessagePattern(
"%{time process}"
" "
"%{if-debug}D%{endif}"
"%{if-info}I%{endif}"
"%{if-warning}W%{endif}"
"%{if-critical}C%{endif}"
"%{if-fatal}F%{endif}"
" "
"|"
" "
"%{if-category}[%{category}]: %{endif}"
"%{message}");
qSetMessagePattern(defaultLogFormat);
bool foundLoggingRules = false;
auto logRulesFile = QStringLiteral("qtlogging.ini");
auto logRulesPath = FS::PathCombine(dataPath, logRulesFile);
qDebug() << "Testing" << logRulesPath << "...";
qInfo() << "Testing" << logRulesPath << "...";
foundLoggingRules = QFile::exists(logRulesPath);
// search the dataPath()
@ -476,7 +555,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
if (!foundLoggingRules && !isPortable() && dirParam.isEmpty() && dataDirEnv.isEmpty()) {
logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, FS::PathCombine("..", logRulesFile));
if (!logRulesPath.isEmpty()) {
qDebug() << "Found" << logRulesPath << "...";
qInfo() << "Found" << logRulesPath << "...";
foundLoggingRules = true;
}
}
@ -487,28 +566,28 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
#else
logRulesPath = FS::PathCombine(m_rootPath, logRulesFile);
#endif
qDebug() << "Testing" << logRulesPath << "...";
qInfo() << "Testing" << logRulesPath << "...";
foundLoggingRules = QFile::exists(logRulesPath);
}
if (foundLoggingRules) {
// load and set logging rules
qDebug() << "Loading logging rules from:" << logRulesPath;
qInfo() << "Loading logging rules from:" << logRulesPath;
QSettings loggingRules(logRulesPath, QSettings::IniFormat);
loggingRules.beginGroup("Rules");
QStringList rule_names = loggingRules.childKeys();
QStringList rules;
qDebug() << "Setting log rules:";
qInfo() << "Setting log rules:";
for (auto rule_name : rule_names) {
auto rule = QString("%1=%2").arg(rule_name).arg(loggingRules.value(rule_name).toString());
rules.append(rule);
qDebug() << " " << rule;
qInfo() << " " << rule;
}
auto rules_str = rules.join("\n");
QLoggingCategory::setFilterRules(rules_str);
}
qDebug() << "<> Log initialized.";
qInfo() << "<> Log initialized.";
}
{
@ -525,33 +604,33 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
{
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
qDebug() << "Version : " << BuildConfig.printableVersionString();
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
qDebug() << "Compiled for : " << BuildConfig.systemID();
qDebug() << "Compiled by : " << BuildConfig.compilerID();
qDebug() << "Build Artifact : " << BuildConfig.BUILD_ARTIFACT;
qDebug() << "Updates Enabled : " << (updaterEnabled() ? "Yes" : "No");
qInfo() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
qInfo() << "Version : " << BuildConfig.printableVersionString();
qInfo() << "Platform : " << BuildConfig.BUILD_PLATFORM;
qInfo() << "Git commit : " << BuildConfig.GIT_COMMIT;
qInfo() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
qInfo() << "Compiled for : " << BuildConfig.systemID();
qInfo() << "Compiled by : " << BuildConfig.compilerID();
qInfo() << "Build Artifact : " << BuildConfig.BUILD_ARTIFACT;
qInfo() << "Updates Enabled : " << (updaterEnabled() ? "Yes" : "No");
if (adjustedBy.size()) {
qDebug() << "Work dir before adjustment : " << origcwdPath;
qDebug() << "Work dir after adjustment : " << QDir::currentPath();
qDebug() << "Adjusted by : " << adjustedBy;
qInfo() << "Work dir before adjustment : " << origcwdPath;
qInfo() << "Work dir after adjustment : " << QDir::currentPath();
qInfo() << "Adjusted by : " << adjustedBy;
} else {
qDebug() << "Work dir : " << QDir::currentPath();
qInfo() << "Work dir : " << QDir::currentPath();
}
qDebug() << "Binary path : " << binPath;
qDebug() << "Application root path : " << m_rootPath;
qInfo() << "Binary path : " << binPath;
qInfo() << "Application root path : " << m_rootPath;
if (!m_instanceIdToLaunch.isEmpty()) {
qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch;
qInfo() << "ID of instance to launch : " << m_instanceIdToLaunch;
}
if (!m_serverToJoin.isEmpty()) {
qDebug() << "Address of server to join :" << m_serverToJoin;
qInfo() << "Address of server to join :" << m_serverToJoin;
} else if (!m_worldToJoin.isEmpty()) {
qDebug() << "Name of the world to join :" << m_worldToJoin;
qInfo() << "Name of the world to join :" << m_worldToJoin;
}
qDebug() << "<> Paths set.";
qInfo() << "<> Paths set.";
}
if (m_liveCheck) {
@ -818,7 +897,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
PixmapCache::setInstance(new PixmapCache(this));
qDebug() << "<> Settings loaded.";
qInfo() << "<> Settings loaded.";
}
#ifndef QT_NO_ACCESSIBILITY
@ -834,7 +913,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
QString user = settings()->get("ProxyUser").toString();
QString pass = settings()->get("ProxyPass").toString();
updateProxySettings(proxyTypeStr, addr, port, user, pass);
qDebug() << "<> Network done.";
qInfo() << "<> Network done.";
}
// load translations
@ -842,8 +921,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_translations.reset(new TranslationsModel("translations"));
auto bcp47Name = m_settings->get("Language").toString();
m_translations->selectLanguage(bcp47Name);
qDebug() << "Your language is" << bcp47Name;
qDebug() << "<> Translations loaded.";
qInfo() << "Your language is" << bcp47Name;
qInfo() << "<> Translations loaded.";
}
// Instance icons
@ -854,7 +933,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_icons.reset(new IconList(instFolders, setting->get().toString()));
connect(setting.get(), &Setting::SettingChanged,
[this](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
qDebug() << "<> Instance icons initialized.";
qInfo() << "<> Instance icons initialized.";
}
// Themes
@ -866,25 +945,25 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// instance path: check for problems with '!' in instance path and warn the user in the log
// and remember that we have to show him a dialog when the gui starts (if it does so)
QString instDir = InstDirSetting->get().toString();
qDebug() << "Instance path : " << instDir;
qInfo() << "Instance path : " << instDir;
if (FS::checkProblemticPathJava(QDir(instDir))) {
qWarning() << "Your instance path contains \'!\' and this is known to cause java problems!";
}
m_instances.reset(new InstanceList(m_settings, instDir, this));
connect(InstDirSetting.get(), &Setting::SettingChanged, m_instances.get(), &InstanceList::on_InstFolderChanged);
qDebug() << "Loading Instances...";
qInfo() << "Loading Instances...";
m_instances->loadList();
qDebug() << "<> Instances loaded.";
qInfo() << "<> Instances loaded.";
}
// and accounts
{
m_accounts.reset(new AccountList(this));
qDebug() << "Loading accounts...";
qInfo() << "Loading accounts...";
m_accounts->setListFilePath("accounts.json", true);
m_accounts->loadList();
m_accounts->fillQueue();
qDebug() << "<> Accounts loaded.";
qInfo() << "<> Accounts loaded.";
}
// init the http meta cache
@ -905,7 +984,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_metacache->addBase("meta", QDir("meta").absolutePath());
m_metacache->addBase("java", QDir("cache/java").absolutePath());
m_metacache->Load();
qDebug() << "<> Cache initialized.";
qInfo() << "<> Cache initialized.";
}
// now we have network, download translation updates

View File

@ -99,7 +99,7 @@ set(CORE_SOURCES
MTPixmapCache.h
)
if (UNIX AND NOT CYGWIN AND NOT APPLE)
set(CORE_SOURCES
set(CORE_SOURCES
${CORE_SOURCES}
# MangoHud
@ -589,8 +589,8 @@ set(ATLAUNCHER_SOURCES
)
set(LINKEXE_SOURCES
console/WindowsConsole.cpp
console/WindowsConsole.h
console/WindowsConsole.cpp
filelink/FileLink.h
filelink/FileLink.cpp
@ -659,6 +659,14 @@ set(PRISMUPDATER_SOURCES
)
if(WIN32)
set(PRISMUPDATER_SOURCES
console/WindowsConsole.h
console/WindowsConsole.cpp
${PRISMUPDATER_SOURCES}
)
endif()
######## Logging categories ########
ecm_qt_declare_logging_category(CORE_SOURCES
@ -786,6 +794,9 @@ SET(LAUNCHER_SOURCES
SysInfo.h
SysInfo.cpp
# console utils
console/Console.h
# GUI - general utilities
DesktopServices.h
DesktopServices.cpp
@ -926,7 +937,7 @@ SET(LAUNCHER_SOURCES
ui/pages/instance/McResolver.h
ui/pages/instance/ServerPingTask.cpp
ui/pages/instance/ServerPingTask.h
# GUI - global settings pages
ui/pages/global/AccountListPage.cpp
ui/pages/global/AccountListPage.h
@ -1154,7 +1165,7 @@ SET(LAUNCHER_SOURCES
)
if (NOT Apple)
set(LAUNCHER_SOURCES
set(LAUNCHER_SOURCES
${LAUNCHER_SOURCES}
ui/dialogs/UpdateAvailableDialog.h
@ -1164,8 +1175,8 @@ endif()
if(WIN32)
set(LAUNCHER_SOURCES
console/WindowsConsole.cpp
console/WindowsConsole.h
console/WindowsConsole.cpp
${LAUNCHER_SOURCES}
)
endif()
@ -1324,11 +1335,11 @@ if(APPLE)
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")
if(Launcher_ENABLE_UPDATER)
file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256})
file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle)
file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256})
file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle)
find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
add_compile_definitions(SPARKLE_ENABLED)
find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
add_compile_definitions(SPARKLE_ENABLED)
endif()
target_link_libraries(Launcher_logic
@ -1338,7 +1349,7 @@ if(APPLE)
"-framework ApplicationServices"
)
if(Launcher_ENABLE_UPDATER)
target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
endif()
endif()

View File

@ -428,7 +428,7 @@ static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr>&
QList<InstanceId> InstanceList::discoverInstances()
{
qDebug() << "Discovering instances in" << m_instDir;
qInfo() << "Discovering instances in" << m_instDir;
QList<InstanceId> out;
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
while (iter.hasNext()) {
@ -447,7 +447,7 @@ QList<InstanceId> InstanceList::discoverInstances()
}
auto id = dirInfo.fileName();
out.append(id);
qDebug() << "Found instance ID" << id;
qInfo() << "Found instance ID" << id;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
instanceSet = QSet<QString>(out.begin(), out.end());
@ -468,7 +468,7 @@ InstanceList::InstListError InstanceList::loadList()
if (existingIds.contains(id)) {
auto instPair = existingIds[id];
existingIds.remove(id);
qDebug() << "Should keep and soft-reload" << id;
qInfo() << "Should keep and soft-reload" << id;
} else {
InstancePtr instPtr = loadInstance(id);
if (instPtr) {

View File

@ -0,0 +1,33 @@
#pragma once
#include <QString>
#include <ostream>
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#else
#include <unistd.h>
#include <cstdio>
#endif
namespace console {
inline bool isConsole()
{
#if defined Q_OS_WIN32
DWORD procIDs[2];
DWORD maxCount = 2;
DWORD result = GetConsoleProcessList((LPDWORD)procIDs, maxCount);
return result > 1;
#else
if (isatty(fileno(stdout))) {
return true;
}
return false;
#endif
}
} // namespace console

View File

@ -16,14 +16,18 @@
*
*/
#include "WindowsConsole.h"
#include <system_error>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include "WindowsConsole.h"
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <cstddef>
#include <iostream>
void RedirectHandle(DWORD handle, FILE* stream, const char* mode)
@ -128,4 +132,28 @@ bool AttachWindowsConsole()
return false;
}
std::error_code
std::error_code EnableAnsiSupport()
{
// ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
// Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected
HANDLE console_handle = CreateFileW(L"CONOUT$", FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
if (console_handle == INVALID_HANDLE_VALUE) {
return std::error_code(GetLastError(), std::system_category());
}
// ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode
DWORD console_mode;
if (0 == GetConsoleMode(console_handle, &console_mode)) {
return std::error_code(GetLastError(), std::system_category());
}
// VT processing not already enabled?
if ((console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0) {
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
if (0 == SetConsoleMode(console_handle, console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
return std::error_code(GetLastError(), std::system_category());
}
}
return {};
}

View File

@ -37,7 +37,10 @@
#include <sys.h>
#if defined Q_OS_WIN32
#include "WindowsConsole.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include "console/WindowsConsole.h"
#endif
#include <filesystem>

View File

@ -46,11 +46,8 @@
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
#include "console/WindowsConsole.h"
#endif
#include <filesystem>
@ -87,112 +84,12 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt
}
}
#if defined Q_OS_WIN32
// taken from https://stackoverflow.com/a/25927081
// getting a proper output to console with redirection support on windows is apparently hell
void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr)
{
// Re-initialize the C runtime "FILE" handles with clean handles bound to "nul". We do this because it has been
// observed that the file number of our standard handle file objects can be assigned internally to a value of -2
// when not bound to a valid target, which represents some kind of unknown internal invalid state. In this state our
// call to "_dup2" fails, as it specifically tests to ensure that the target file number isn't equal to this value
// before allowing the operation to continue. We can resolve this issue by first "re-opening" the target files to
// use the "nul" device, which will place them into a valid state, after which we can redirect them to our target
// using the "_dup2" function.
if (bindStdIn) {
FILE* dummyFile;
freopen_s(&dummyFile, "nul", "r", stdin);
}
if (bindStdOut) {
FILE* dummyFile;
freopen_s(&dummyFile, "nul", "w", stdout);
}
if (bindStdErr) {
FILE* dummyFile;
freopen_s(&dummyFile, "nul", "w", stderr);
}
// Redirect unbuffered stdin from the current standard input handle
if (bindStdIn) {
HANDLE stdHandle = GetStdHandle(STD_INPUT_HANDLE);
if (stdHandle != INVALID_HANDLE_VALUE) {
int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
if (fileDescriptor != -1) {
FILE* file = _fdopen(fileDescriptor, "r");
if (file != NULL) {
int dup2Result = _dup2(_fileno(file), _fileno(stdin));
if (dup2Result == 0) {
setvbuf(stdin, NULL, _IONBF, 0);
}
}
}
}
}
// Redirect unbuffered stdout to the current standard output handle
if (bindStdOut) {
HANDLE stdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
if (stdHandle != INVALID_HANDLE_VALUE) {
int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
if (fileDescriptor != -1) {
FILE* file = _fdopen(fileDescriptor, "w");
if (file != NULL) {
int dup2Result = _dup2(_fileno(file), _fileno(stdout));
if (dup2Result == 0) {
setvbuf(stdout, NULL, _IONBF, 0);
}
}
}
}
}
// Redirect unbuffered stderr to the current standard error handle
if (bindStdErr) {
HANDLE stdHandle = GetStdHandle(STD_ERROR_HANDLE);
if (stdHandle != INVALID_HANDLE_VALUE) {
int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
if (fileDescriptor != -1) {
FILE* file = _fdopen(fileDescriptor, "w");
if (file != NULL) {
int dup2Result = _dup2(_fileno(file), _fileno(stderr));
if (dup2Result == 0) {
setvbuf(stderr, NULL, _IONBF, 0);
}
}
}
}
}
// Clear the error state for each of the C++ standard stream objects. We need to do this, as attempts to access the
// standard streams before they refer to a valid target will cause the iostream objects to enter an error state. In
// versions of Visual Studio after 2005, this seems to always occur during startup regardless of whether anything
// has been read from or written to the targets or not.
if (bindStdIn) {
std::wcin.clear();
std::cin.clear();
}
if (bindStdOut) {
std::wcout.clear();
std::cout.clear();
}
if (bindStdErr) {
std::wcerr.clear();
std::cerr.clear();
}
}
#endif
PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, argv)
{
#if defined Q_OS_WIN32
// attach the parent console if stdout not already captured
auto stdout_type = GetFileType(GetStdHandle(STD_OUTPUT_HANDLE));
if (stdout_type == FILE_TYPE_CHAR || stdout_type == FILE_TYPE_UNKNOWN) {
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
BindCrtHandlesToStdHandles(true, true, true);
consoleAttached = true;
}
if (AttachWindowsConsole()) {
consoleAttached = true;
}
#endif
setOrganizationName(BuildConfig.LAUNCHER_NAME);