From 45b6454222a83b932e0505cbd815d697226080cd Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 17 Apr 2025 23:18:57 -0700 Subject: [PATCH] feat(ansi-console): Format console with ansi excapes Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 181 +++++++++++++----- launcher/CMakeLists.txt | 31 ++- launcher/InstanceList.cpp | 6 +- launcher/console/Console.h | 33 ++++ launcher/console/WindowsConsole.cpp | 34 +++- launcher/filelink/FileLink.cpp | 5 +- .../updater/prismupdater/PrismUpdater.cpp | 109 +---------- 7 files changed, 225 insertions(+), 174 deletions(-) create mode 100644 launcher/console/Console.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index f98112641..70ffce12f 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -125,6 +125,7 @@ #include #include +#include #include #include #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 #include #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 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 diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 5d757a130..f60d7960e 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -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() diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 918fa1073..3df44f408 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -428,7 +428,7 @@ static QMap getIdMapping(const QList& QList InstanceList::discoverInstances() { - qDebug() << "Discovering instances in" << m_instDir; + qInfo() << "Discovering instances in" << m_instDir; QList 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 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(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) { diff --git a/launcher/console/Console.h b/launcher/console/Console.h new file mode 100644 index 000000000..7aaf83dcc --- /dev/null +++ b/launcher/console/Console.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include +#if defined Q_OS_WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#include +#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 diff --git a/launcher/console/WindowsConsole.cpp b/launcher/console/WindowsConsole.cpp index f388bd3b1..4a0eb3d3d 100644 --- a/launcher/console/WindowsConsole.cpp +++ b/launcher/console/WindowsConsole.cpp @@ -16,14 +16,18 @@ * */ +#include "WindowsConsole.h" +#include + #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif -#include "WindowsConsole.h" +#include + #include #include #include -#include +#include #include 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 {}; +} diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index a082b4b5b..1494fa8cc 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -37,7 +37,10 @@ #include #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 diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index f9ffeb658..365647db9 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -46,11 +46,8 @@ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif -#include -#include -#include #include -#include +#include "console/WindowsConsole.h" #endif #include @@ -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);