Console colors and log invocation location (#3654)

This commit is contained in:
Rachel Powers 2025-04-18 20:38:55 -07:00 committed by GitHub
commit 0e00c28450
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 226 additions and 175 deletions

View File

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

View File

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

View File

@ -428,7 +428,7 @@ static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr>&
QList<InstanceId> InstanceList::discoverInstances() QList<InstanceId> InstanceList::discoverInstances()
{ {
qDebug() << "Discovering instances in" << m_instDir; qInfo() << "Discovering instances in" << m_instDir;
QList<InstanceId> out; QList<InstanceId> out;
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
while (iter.hasNext()) { while (iter.hasNext()) {
@ -447,7 +447,7 @@ QList<InstanceId> InstanceList::discoverInstances()
} }
auto id = dirInfo.fileName(); auto id = dirInfo.fileName();
out.append(id); out.append(id);
qDebug() << "Found instance ID" << id; qInfo() << "Found instance ID" << id;
} }
instanceSet = QSet<QString>(out.begin(), out.end()); instanceSet = QSet<QString>(out.begin(), out.end());
m_instancesProbed = true; m_instancesProbed = true;
@ -464,7 +464,7 @@ InstanceList::InstListError InstanceList::loadList()
if (existingIds.contains(id)) { if (existingIds.contains(id)) {
auto instPair = existingIds[id]; auto instPair = existingIds[id];
existingIds.remove(id); existingIds.remove(id);
qDebug() << "Should keep and soft-reload" << id; qInfo() << "Should keep and soft-reload" << id;
} else { } else {
InstancePtr instPtr = loadInstance(id); InstancePtr instPtr = loadInstance(id);
if (instPtr) { 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,13 +16,18 @@
* *
*/ */
#include "WindowsConsole.h"
#include <system_error>
#ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#endif #endif
#include <windows.h>
#include <fcntl.h> #include <fcntl.h>
#include <io.h> #include <io.h>
#include <stdio.h> #include <stdio.h>
#include <windows.h> #include <cstddef>
#include <iostream> #include <iostream>
void RedirectHandle(DWORD handle, FILE* stream, const char* mode) void RedirectHandle(DWORD handle, FILE* stream, const char* mode)
@ -126,3 +131,29 @@ bool AttachWindowsConsole()
return false; return false;
} }
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

@ -21,5 +21,8 @@
#pragma once #pragma once
#include <system_error>
void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr); void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr);
bool AttachWindowsConsole(); bool AttachWindowsConsole();
std::error_code EnableAnsiSupport();

View File

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

View File

@ -46,11 +46,8 @@
#ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#endif #endif
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#include <windows.h> #include <windows.h>
#include <iostream> #include "console/WindowsConsole.h"
#endif #endif
#include <filesystem> #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) PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, argv)
{ {
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
// attach the parent console if stdout not already captured // attach the parent console if stdout not already captured
auto stdout_type = GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)); if (AttachWindowsConsole()) {
if (stdout_type == FILE_TYPE_CHAR || stdout_type == FILE_TYPE_UNKNOWN) { consoleAttached = true;
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
BindCrtHandlesToStdHandles(true, true, true);
consoleAttached = true;
}
} }
#endif #endif
setOrganizationName(BuildConfig.LAUNCHER_NAME); setOrganizationName(BuildConfig.LAUNCHER_NAME);