From 516dd6bd1ad8f9ff939b72f87e88c5803f82eaaf Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 24 Jun 2023 05:02:48 -0700 Subject: [PATCH] support windows console Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 764 +++++++++--------- launcher/CMakeLists.txt | 11 +- .../updater/prismupdater/PrismUpdater.cpp | 287 +++++-- launcher/updater/prismupdater/PrismUpdater.h | 10 +- .../updater/prismupdater/UpdaterDialogs.cpp | 8 +- 5 files changed, 602 insertions(+), 478 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 20eabc8af..f094a8ec9 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -48,27 +48,27 @@ #include "pathmatcher/MultiMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h" #include "settings/INIFile.h" -#include "ui/MainWindow.h" #include "ui/InstanceWindow.h" +#include "ui/MainWindow.h" #include "ui/dialogs/ProgressDialog.h" #include "ui/instanceview/AccessibleInstanceView.h" #include "ui/pages/BasePageProvider.h" -#include "ui/pages/global/LauncherPage.h" -#include "ui/pages/global/MinecraftPage.h" +#include "ui/pages/global/APIPage.h" +#include "ui/pages/global/AccountListPage.h" +#include "ui/pages/global/CustomCommandsPage.h" +#include "ui/pages/global/ExternalToolsPage.h" #include "ui/pages/global/JavaPage.h" #include "ui/pages/global/LanguagePage.h" +#include "ui/pages/global/LauncherPage.h" +#include "ui/pages/global/MinecraftPage.h" #include "ui/pages/global/ProxyPage.h" -#include "ui/pages/global/ExternalToolsPage.h" -#include "ui/pages/global/AccountListPage.h" -#include "ui/pages/global/APIPage.h" -#include "ui/pages/global/CustomCommandsPage.h" -#include "ui/setupwizard/SetupWizard.h" -#include "ui/setupwizard/LanguageWizardPage.h" #include "ui/setupwizard/JavaWizardPage.h" +#include "ui/setupwizard/LanguageWizardPage.h" #include "ui/setupwizard/PasteWizardPage.h" +#include "ui/setupwizard/SetupWizard.h" #include "ui/setupwizard/ThemeWizardPage.h" #include "ui/dialogs/CustomMessageBox.h" @@ -82,20 +82,20 @@ #include #include -#include #include #include +#include #include #include -#include -#include +#include +#include #include #include +#include #include -#include #include +#include #include -#include #include "InstanceList.h" #include "MTPixmapCache.h" @@ -115,32 +115,35 @@ #include "settings/INISettingsObject.h" #include "settings/Setting.h" -#include "translations/TranslationsModel.h" #include "meta/Index.h" +#include "translations/TranslationsModel.h" -#include #include +#include #include #include #ifdef Q_OS_LINUX #include -#include "gamemode_client.h" #include "MangoHud.h" +#include "gamemode_client.h" #endif #ifdef Q_OS_MAC #include "updater/MacSparkleUpdater.h" #endif - #if defined Q_OS_WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif -#include +#include +#include #include +#include +#include + #endif #define STRINGIFY(x) #x @@ -153,10 +156,10 @@ PixmapCache* PixmapCache::s_instance = nullptr; namespace { /** This is used so that we can output to the log file in addition to the CLI. */ -void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) +void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QString& msg) { static std::mutex loggerMutex; - const std::lock_guard lock(loggerMutex); // synchronized, QFile logFile is not thread-safe + const std::lock_guard lock(loggerMutex); // synchronized, QFile logFile is not thread-safe QString out = qFormatLogMessage(type, context, msg); out += QChar::LineFeed; @@ -167,31 +170,110 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt fflush(stderr); } -} +} // namespace -Application::Application(int &argc, char **argv) : QApplication(argc, argv) +#if defined Q_OS_WIN32 + +// taken from https://stackoverflow.com/a/25927081 +// getting a proper output to console with redirection support on windows is apearently 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 + +Application::Application(int& argc, char** argv) : QApplication(argc, argv) { #if defined Q_OS_WIN32 // attach the parent console - if(AttachConsole(ATTACH_PARENT_PROCESS)) - { - // if attach succeeds, reopen and sync all the i/o - if(freopen("CON", "w", stdout)) - { - std::cout.sync_with_stdio(); - } - if(freopen("CON", "w", stderr)) - { - std::cerr.sync_with_stdio(); - } - if(freopen("CON", "r", stdin)) - { - std::cin.sync_with_stdio(); - } - auto out = GetStdHandle (STD_OUTPUT_HANDLE); - DWORD written; - const char * endline = "\n"; - WriteConsole(out, endline, strlen(endline), &written, NULL); + if (AttachConsole(ATTACH_PARENT_PROCESS)) { + BindCrtHandlesToStdHandles(true, true, true); consoleAttached = true; } #endif @@ -210,15 +292,14 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QCommandLineParser parser; parser.setApplicationDescription(BuildConfig.LAUNCHER_DISPLAYNAME); - parser.addOptions({ - {{"d", "dir"}, "Use a custom path as application root (use '.' for current directory)", "directory"}, - {{"l", "launch"}, "Launch the specified instance (by instance ID)", "instance"}, - {{"s", "server"}, "Join the specified server on launch (only valid in combination with --launch)", "address"}, - {{"a", "profile"}, "Use the account specified by its profile name (only valid in combination with --launch)", "profile"}, - {"alive", "Write a small '" + liveCheckFile + "' file after the launcher starts"}, - {{"I", "import"}, "Import instance from specified zip (local path or URL)", "file"}, - {"show", "Opens the window for the specified instance (by instance ID)", "show"} - }); + parser.addOptions( + { { { "d", "dir" }, "Use a custom path as application root (use '.' for current directory)", "directory" }, + { { "l", "launch" }, "Launch the specified instance (by instance ID)", "instance" }, + { { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" }, + { { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" }, + { "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" }, + { { "I", "import" }, "Import instance from specified zip (local path or URL)", "file" }, + { "show", "Opens the window for the specified instance (by instance ID)", "show" } }); parser.addHelpOption(); parser.addVersionOption(); @@ -231,7 +312,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_instanceIdToShowWindowOf = parser.value("show"); - for (auto zip_path : parser.values("import")){ + for (auto zip_path : parser.values("import")) { m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath())); } @@ -240,10 +321,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath())); } - // error if --launch is missing with --server or --profile - if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) - { + if ((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) { std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl; m_status = Application::Failed; return; @@ -255,7 +334,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) { // Root path is used for updates and portable data #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - QDir foo(FS::PathCombine(binPath, "..")); // typically portable-root or /usr + QDir foo(FS::PathCombine(binPath, "..")); // typically portable-root or /usr m_rootPath = foo.absolutePath(); #elif defined(Q_OS_WIN32) m_rootPath = binPath; @@ -271,15 +350,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QString dataPath; // change folder QString dirParam = parser.value("dir"); - if (!dirParam.isEmpty()) - { + if (!dirParam.isEmpty()) { // the dir param. it makes multimc data path point to whatever the user specified // on command line adjustedBy = "Command line"; dataPath = dirParam; - } - else - { + } else { QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "..")); dataPath = foo.absolutePath(); adjustedBy = "Persistent data path"; @@ -293,34 +369,27 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) #endif } - if (!FS::ensureFolderPathExists(dataPath)) - { + if (!FS::ensureFolderPathExists(dataPath)) { showFatalErrorMessage( "The launcher data folder could not be created.", - QString( - "The launcher data folder could not be created.\n" - "\n" - "Make sure you have the right permissions to the launcher data folder and any folder needed to access it.\n" - "(%1)\n" - "\n" - "The launcher cannot continue until you fix this problem." - ).arg(dataPath) - ); + QString("The launcher data folder could not be created.\n" + "\n" + "Make sure you have the right permissions to the launcher data folder and any folder needed to access it.\n" + "(%1)\n" + "\n" + "The launcher cannot continue until you fix this problem.") + .arg(dataPath)); return; } - if (!QDir::setCurrent(dataPath)) - { - showFatalErrorMessage( - "The launcher data folder could not be opened.", - QString( - "The launcher data folder could not be opened.\n" - "\n" - "Make sure you have the right permissions to the launcher data folder.\n" - "(%1)\n" - "\n" - "The launcher cannot continue until you fix this problem." - ).arg(dataPath) - ); + if (!QDir::setCurrent(dataPath)) { + showFatalErrorMessage("The launcher data folder could not be opened.", + QString("The launcher data folder could not be opened.\n" + "\n" + "Make sure you have the right permissions to the launcher data folder.\n" + "(%1)\n" + "\n" + "The launcher cannot continue until you fix this problem.") + .arg(dataPath)); return; } @@ -334,17 +403,15 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // FIXME: you can run the same binaries with multiple data dirs and they won't clash. This could cause issues for updates. m_peerInstance = new LocalPeer(this, appID); connect(m_peerInstance, &LocalPeer::messageReceived, this, &Application::messageReceived); - if(m_peerInstance->isClient()) { + if (m_peerInstance->isClient()) { int timeout = 2000; - if(m_instanceIdToLaunch.isEmpty()) - { + if (m_instanceIdToLaunch.isEmpty()) { ApplicationMessage activate; activate.command = "activate"; m_peerInstance->sendMessage(activate.serialize(), timeout); - if(!m_zipsToImport.isEmpty()) - { + if (!m_zipsToImport.isEmpty()) { for (auto zip_url : m_zipsToImport) { ApplicationMessage import; import.command = "import"; @@ -352,19 +419,15 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_peerInstance->sendMessage(import.serialize(), timeout); } } - } - else - { + } else { ApplicationMessage launch; launch.command = "launch"; launch.args["id"] = m_instanceIdToLaunch; - if(!m_serverToJoin.isEmpty()) - { + if (!m_serverToJoin.isEmpty()) { launch.args["server"] = m_serverToJoin; } - if(!m_profileToUse.isEmpty()) - { + if (!m_profileToUse.isEmpty()) { launch.args["profile"] = m_profileToUse; } m_peerInstance->sendMessage(launch.serialize(), timeout); @@ -408,40 +471,47 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) 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}"); - + "%{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; - + auto logRulesFile = QStringLiteral("qtlogging.ini"); auto logRulesPath = FS::PathCombine(dataPath, logRulesFile); - - qDebug() << "Testing" << logRulesPath << "..."; + + qDebug() << "Testing" << logRulesPath << "..."; foundLoggingRules = QFile::exists(logRulesPath); // search the dataPath() // seach app data standard path - if(!foundLoggingRules && !isPortable() && dirParam.isEmpty()) { + if (!foundLoggingRules && !isPortable() && dirParam.isEmpty()) { logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, FS::PathCombine("..", logRulesFile)); - if(!logRulesPath.isEmpty()) { + if (!logRulesPath.isEmpty()) { qDebug() << "Found" << logRulesPath << "..."; foundLoggingRules = true; } } // seach root path - if(!foundLoggingRules) { - logRulesPath = FS::PathCombine(m_rootPath, logRulesFile); + if (!foundLoggingRules) { + logRulesPath = FS::PathCombine(m_rootPath, logRulesFile); qDebug() << "Testing" << logRulesPath << "..."; foundLoggingRules = QFile::exists(logRulesPath); } - - if(foundLoggingRules) { + + if (foundLoggingRules) { // load and set logging rules qDebug() << "Loading logging rules from:" << logRulesPath; - QSettings loggingRules(logRulesPath, QSettings::IniFormat); + QSettings loggingRules(logRulesPath, QSettings::IniFormat); loggingRules.beginGroup("Rules"); QStringList rule_names = loggingRules.childKeys(); QStringList rules; @@ -462,48 +532,44 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) bool migrated = false; if (!migrated) - migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", "polymc.cfg"); + migrated = handleDataMigration( + dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", + "polymc.cfg"); if (!migrated) - migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "MultiMC", "multimc.cfg"); + migrated = handleDataMigration( + dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "MultiMC", + "multimc.cfg"); } { - - qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << ", (c) 2022-2023 " << qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", ")); + qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << ", (c) 2022-2023 " + << qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", ")); qDebug() << "Version : " << BuildConfig.printableVersionString(); qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; - if (adjustedBy.size()) - { + if (adjustedBy.size()) { qDebug() << "Work dir before adjustment : " << origcwdPath; qDebug() << "Work dir after adjustment : " << QDir::currentPath(); qDebug() << "Adjusted by : " << adjustedBy; - } - else - { + } else { qDebug() << "Work dir : " << QDir::currentPath(); } qDebug() << "Binary path : " << binPath; qDebug() << "Application root path : " << m_rootPath; - if(!m_instanceIdToLaunch.isEmpty()) - { + if (!m_instanceIdToLaunch.isEmpty()) { qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch; } - if(!m_serverToJoin.isEmpty()) - { + if (!m_serverToJoin.isEmpty()) { qDebug() << "Address of server to join :" << m_serverToJoin; } qDebug() << "<> Paths set."; } - if(m_liveCheck) - { + if (m_liveCheck) { QFile check(liveCheckFile); - if(check.open(QIODevice::WriteOnly | QIODevice::Truncate)) - { + if (check.open(QIODevice::WriteOnly | QIODevice::Truncate)) { auto payload = appID.toString().toUtf8(); - if(check.write(payload) == payload.size()) - { + if (check.write(payload) == payload.size()) { check.close(); } else { qWarning() << "Could not write into" << liveCheckFile << "!"; @@ -549,7 +615,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QString resolvedDefaultMonospace = consoleFontInfo.family(); QFont resolvedFont(resolvedDefaultMonospace); qDebug() << "Detected default console font:" << resolvedDefaultMonospace - << ", substitutions:" << resolvedFont.substitutions().join(','); + << ", substitutions:" << resolvedFont.substitutions().join(','); m_settings->registerSetting("ConsoleFont", resolvedDefaultMonospace); m_settings->registerSetting("ConsoleFontSize", defaultSize); @@ -558,7 +624,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Folders m_settings->registerSetting("InstanceDir", "instances"); - m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods"); + m_settings->registerSetting({ "CentralModsDir", "ModsDir" }, "mods"); m_settings->registerSetting("IconsDir", "icons"); m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); m_settings->registerSetting("DownloadsDirWatchRecursive", false); @@ -576,20 +642,20 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("LogPrePostOutput", true); // Window Size - m_settings->registerSetting({"LaunchMaximized", "MCWindowMaximize"}, false); - m_settings->registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854); - m_settings->registerSetting({"MinecraftWinHeight", "MCWindowHeight"}, 480); + m_settings->registerSetting({ "LaunchMaximized", "MCWindowMaximize" }, false); + m_settings->registerSetting({ "MinecraftWinWidth", "MCWindowWidth" }, 854); + m_settings->registerSetting({ "MinecraftWinHeight", "MCWindowHeight" }, 480); // Proxy Settings m_settings->registerSetting("ProxyType", "None"); - m_settings->registerSetting({"ProxyAddr", "ProxyHostName"}, "127.0.0.1"); + m_settings->registerSetting({ "ProxyAddr", "ProxyHostName" }, "127.0.0.1"); m_settings->registerSetting("ProxyPort", 8080); - m_settings->registerSetting({"ProxyUser", "ProxyUsername"}, ""); - m_settings->registerSetting({"ProxyPass", "ProxyPassword"}, ""); + m_settings->registerSetting({ "ProxyUser", "ProxyUsername" }, ""); + m_settings->registerSetting({ "ProxyPass", "ProxyPassword" }, ""); // Memory - m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512); - m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, suitableMaxMem()); + m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512); + m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, suitableMaxMem()); m_settings->registerSetting("PermGen", 128); // Java Settings @@ -631,8 +697,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("WrapperCommand", ""); // Custom Commands - m_settings->registerSetting({"PreLaunchCommand", "PreLaunchCmd"}, ""); - m_settings->registerSetting({"PostExitCommand", "PostExitCmd"}, ""); + m_settings->registerSetting({ "PreLaunchCommand", "PreLaunchCmd" }, ""); + m_settings->registerSetting({ "PostExitCommand", "PostExitCmd" }, ""); // The cat m_settings->registerSetting("TheCat", false); @@ -671,8 +737,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QString pastebinURL = m_settings->get("PastebinURL").toString(); bool userHadDefaultPastebin = pastebinURL == "https://0x0.st"; - if (!pastebinURL.isEmpty() && !userHadDefaultPastebin) - { + if (!pastebinURL.isEmpty() && !userHadDefaultPastebin) { m_settings->set("PastebinType", PasteUpload::PasteType::NullPointer); m_settings->set("PastebinCustomAPIBase", pastebinURL); m_settings->reset("PastebinURL"); @@ -681,8 +746,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) bool ok; int pasteType = m_settings->get("PastebinType").toInt(&ok); // If PastebinType is invalid then reset the related settings. - if (!ok || !(PasteUpload::PasteType::First <= pasteType && pasteType <= PasteUpload::PasteType::Last)) - { + if (!ok || !(PasteUpload::PasteType::First <= pasteType && pasteType <= PasteUpload::PasteType::Last)) { m_settings->reset("PastebinType"); m_settings->reset("PastebinCustomAPIBase"); } @@ -755,8 +819,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) } // initialize the updater - if(BuildConfig.UPDATER_ENABLED) - { + if (BuildConfig.UPDATER_ENABLED) { qDebug() << "Initializing updater"; #ifdef Q_OS_MAC m_updater.reset(new MacSparkleUpdater()); @@ -767,18 +830,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Instance icons { auto setting = APPLICATION->settings()->getSetting("IconsDir"); - QStringList instFolders = - { - ":/icons/multimc/32x32/instances/", - ":/icons/multimc/50x50/instances/", - ":/icons/multimc/128x128/instances/", - ":/icons/multimc/scalable/instances/" - }; + QStringList instFolders = { ":/icons/multimc/32x32/instances/", ":/icons/multimc/50x50/instances/", + ":/icons/multimc/128x128/instances/", ":/icons/multimc/scalable/instances/" }; m_icons.reset(new IconList(instFolders, setting->get().toString())); - connect(setting.get(), &Setting::SettingChanged,[&](const Setting &, QVariant value) - { - m_icons->directoryChanged(value.toString()); - }); + connect(setting.get(), &Setting::SettingChanged, + [&](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); }); qDebug() << "<> Instance icons intialized."; } @@ -792,8 +848,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // 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; - if (FS::checkProblemticPathJava(QDir(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)); @@ -843,11 +898,10 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // now we have network, download translation updates m_translations->downloadIndex(); - //FIXME: what to do with these? + // FIXME: what to do with these? m_profilers.insert("jprofiler", std::shared_ptr(new JProfilerFactory())); m_profilers.insert("jvisualvm", std::shared_ptr(new JVisualVMFactory())); - for (auto profiler : m_profilers.values()) - { + for (auto profiler : m_profilers.values()) { profiler->registerSettings(m_settings); } @@ -857,19 +911,15 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) } #ifdef Q_OS_MACOS - connect(this, &Application::clickedOnDock, [this]() { - this->showMainWindow(); - }); + connect(this, &Application::clickedOnDock, [this]() { this->showMainWindow(); }); #endif - connect(this, &Application::aboutToQuit, [this](){ - if(m_instances) - { + connect(this, &Application::aboutToQuit, [this]() { + if (m_instances) { // save any remaining instance state m_instances->saveNow(); } - if(logFile) - { + if (logFile) { logFile->flush(); logFile->close(); } @@ -879,8 +929,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) updateCapabilities(); - if(createSetupWizard()) - { + if (createSetupWizard()) { return; } @@ -889,29 +938,25 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) bool Application::createSetupWizard() { - bool javaRequired = [&]() - { + bool javaRequired = [&]() { bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool(); - if(ignoreJavaWizard) { + if (ignoreJavaWizard) { return false; } QString currentHostName = QHostInfo::localHostName(); QString oldHostName = settings()->get("LastHostname").toString(); - if (currentHostName != oldHostName) - { + if (currentHostName != oldHostName) { settings()->set("LastHostname", currentHostName); return true; } QString currentJavaPath = settings()->get("JavaPath").toString(); QString actualPath = FS::ResolveExecutable(currentJavaPath); - if (actualPath.isNull()) - { + if (actualPath.isNull()) { return true; } return false; }(); - bool languageRequired = [&]() - { + bool languageRequired = [&]() { if (settings()->get("Language").toString().isEmpty()) return true; return false; @@ -920,27 +965,22 @@ bool Application::createSetupWizard() bool themeInterventionRequired = settings()->get("ApplicationTheme") == ""; bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired; - if(wizardRequired) - { + if (wizardRequired) { m_setupWizard = new SetupWizard(nullptr); - if (languageRequired) - { + if (languageRequired) { m_setupWizard->addPage(new LanguageWizardPage(m_setupWizard)); } - if (javaRequired) - { + if (javaRequired) { m_setupWizard->addPage(new JavaWizardPage(m_setupWizard)); } - if (pasteInterventionRequired) - { + if (pasteInterventionRequired) { m_setupWizard->addPage(new PasteWizardPage(m_setupWizard)); } - if (themeInterventionRequired) - { - settings()->set("ApplicationTheme", QString("system")); // set default theme after going into theme wizard + if (themeInterventionRequired) { + settings()->set("ApplicationTheme", QString("system")); // set default theme after going into theme wizard m_setupWizard->addPage(new ThemeWizardPage(m_setupWizard)); } connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished); @@ -980,26 +1020,22 @@ void Application::setupWizardFinished(int status) void Application::performMainStartupAction() { m_status = Application::Initialized; - if(!m_instanceIdToLaunch.isEmpty()) - { + if (!m_instanceIdToLaunch.isEmpty()) { auto inst = instances()->getInstanceById(m_instanceIdToLaunch); - if(inst) - { + if (inst) { MinecraftServerTargetPtr serverToJoin = nullptr; MinecraftAccountPtr accountToUse = nullptr; qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching"; - if(!m_serverToJoin.isEmpty()) - { + if (!m_serverToJoin.isEmpty()) { // FIXME: validate the server string serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(m_serverToJoin))); qDebug() << " Launching with server" << m_serverToJoin; } - if(!m_profileToUse.isEmpty()) - { + if (!m_profileToUse.isEmpty()) { accountToUse = accounts()->getAccountByProfileName(m_profileToUse); - if(!accountToUse) { + if (!accountToUse) { return; } qDebug() << " Launching with account" << m_profileToUse; @@ -1009,26 +1045,22 @@ void Application::performMainStartupAction() return; } } - if(!m_instanceIdToShowWindowOf.isEmpty()) - { + if (!m_instanceIdToShowWindowOf.isEmpty()) { auto inst = instances()->getInstanceById(m_instanceIdToShowWindowOf); - if(inst) - { + if (inst) { qDebug() << "<> Showing window of instance " << m_instanceIdToShowWindowOf; showInstanceWindow(inst); return; } } - if(!m_mainWindow) - { + if (!m_mainWindow) { // normal main window showMainWindow(false); qDebug() << "<> Main window shown."; } - if(!m_zipsToImport.isEmpty()) - { + if (!m_zipsToImport.isEmpty()) { qDebug() << "<> Importing from zip:" << m_zipsToImport; - m_mainWindow->processURLs( m_zipsToImport ); + m_mainWindow->processURLs(m_zipsToImport); } } @@ -1046,8 +1078,7 @@ Application::~Application() #if defined Q_OS_WIN32 // Detach from Windows console - if(consoleAttached) - { + if (consoleAttached) { fclose(stdout); fclose(stdin); fclose(stderr); @@ -1058,8 +1089,7 @@ Application::~Application() void Application::messageReceived(const QByteArray& message) { - if(status() != Initialized) - { + if (status() != Initialized) { qDebug() << "Received message" << message << "while still initializing. It will be ignored."; return; } @@ -1067,66 +1097,51 @@ void Application::messageReceived(const QByteArray& message) ApplicationMessage received; received.parse(message); - auto & command = received.command; + auto& command = received.command; - if(command == "activate") - { + if (command == "activate") { showMainWindow(); - } - else if(command == "import") - { + } else if (command == "import") { QString path = received.args["path"]; - if(path.isEmpty()) - { + if (path.isEmpty()) { qWarning() << "Received" << command << "message without a zip path/URL."; return; } m_mainWindow->processURLs({ QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()) }); - } - else if(command == "launch") - { + } else if (command == "launch") { QString id = received.args["id"]; QString server = received.args["server"]; QString profile = received.args["profile"]; InstancePtr instance; - if(!id.isEmpty()) { + if (!id.isEmpty()) { instance = instances()->getInstanceById(id); - if(!instance) { + if (!instance) { qWarning() << "Launch command requires an valid instance ID. " << id << "resolves to nothing."; return; } - } - else { + } else { qWarning() << "Launch command called without an instance ID..."; return; } MinecraftServerTargetPtr serverObject = nullptr; - if(!server.isEmpty()) { + if (!server.isEmpty()) { serverObject = std::make_shared(MinecraftServerTarget::parse(server)); } MinecraftAccountPtr accountObject; - if(!profile.isEmpty()) { + if (!profile.isEmpty()) { accountObject = accounts()->getAccountByProfileName(profile); - if(!accountObject) { - qWarning() << "Launch command requires the specified profile to be valid. " << profile << "does not resolve to any account."; + if (!accountObject) { + qWarning() << "Launch command requires the specified profile to be valid. " << profile + << "does not resolve to any account."; return; } } - launch( - instance, - true, - false, - nullptr, - serverObject, - accountObject - ); - } - else - { + launch(instance, true, false, nullptr, serverObject, accountObject); + } else { qWarning() << "Received invalid message" << message; } } @@ -1138,8 +1153,7 @@ std::shared_ptr Application::translations() std::shared_ptr Application::javalist() { - if (!m_javalist) - { + if (!m_javalist) { m_javalist.reset(new JavaInstallList()); } return m_javalist; @@ -1167,50 +1181,41 @@ void Application::setIconTheme(const QString& name) QIcon Application::getThemedIcon(const QString& name) { - if(name == "logo") { + if (name == "logo") { return QIcon(":/" + BuildConfig.LAUNCHER_SVGFILENAME); } return QIcon::fromTheme(name); } -bool Application::openJsonEditor(const QString &filename) +bool Application::openJsonEditor(const QString& filename) { const QString file = QDir::current().absoluteFilePath(filename); - if (m_settings->get("JsonEditor").toString().isEmpty()) - { + if (m_settings->get("JsonEditor").toString().isEmpty()) { return DesktopServices::openUrl(QUrl::fromLocalFile(file)); - } - else - { - //return DesktopServices::openFile(m_settings->get("JsonEditor").toString(), file); - return DesktopServices::run(m_settings->get("JsonEditor").toString(), {file}); + } else { + // return DesktopServices::openFile(m_settings->get("JsonEditor").toString(), file); + return DesktopServices::run(m_settings->get("JsonEditor").toString(), { file }); } } -bool Application::launch( - InstancePtr instance, - bool online, - bool demo, - BaseProfilerFactory *profiler, - MinecraftServerTargetPtr serverToJoin, - MinecraftAccountPtr accountToUse -) { - if(m_updateRunning) - { +bool Application::launch(InstancePtr instance, + bool online, + bool demo, + BaseProfilerFactory* profiler, + MinecraftServerTargetPtr serverToJoin, + MinecraftAccountPtr accountToUse) +{ + if (m_updateRunning) { qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed."; - } - else if(instance->canLaunch()) - { - auto & extras = m_instanceExtras[instance->id()]; - auto & window = extras.window; - if(window) - { - if(!window->saveAll()) - { + } else if (instance->canLaunch()) { + auto& extras = m_instanceExtras[instance->id()]; + auto& window = extras.window; + if (window) { + if (!window->saveAll()) { return false; } } - auto & controller = extras.controller; + auto& controller = extras.controller; controller.reset(new LaunchController()); controller->setInstance(instance); controller->setOnline(online); @@ -1218,30 +1223,21 @@ bool Application::launch( controller->setProfiler(profiler); controller->setServerToJoin(serverToJoin); controller->setAccountToUse(accountToUse); - if(window) - { + if (window) { controller->setParentWidget(window); - } - else if(m_mainWindow) - { + } else if (m_mainWindow) { controller->setParentWidget(m_mainWindow); } connect(controller.get(), &LaunchController::succeeded, this, &Application::controllerSucceeded); connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed); - connect(controller.get(), &LaunchController::aborted, this, [this] { - controllerFailed(tr("Aborted")); - }); + connect(controller.get(), &LaunchController::aborted, this, [this] { controllerFailed(tr("Aborted")); }); addRunningInstance(); controller->start(); return true; - } - else if (instance->isRunning()) - { + } else if (instance->isRunning()) { showInstanceWindow(instance, "console"); return true; - } - else if (instance->canEdit()) - { + } else if (instance->canEdit()) { showInstanceWindow(instance); return true; } @@ -1250,16 +1246,14 @@ bool Application::launch( bool Application::kill(InstancePtr instance) { - if (!instance->isRunning()) - { + if (!instance->isRunning()) { qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running."; return false; } - auto & extras = m_instanceExtras[instance->id()]; + auto& extras = m_instanceExtras[instance->id()]; // NOTE: copy of the shared pointer keeps it alive auto controller = extras.controller; - if(controller) - { + if (controller) { return controller->abort(); } return true; @@ -1273,23 +1267,20 @@ void Application::closeCurrentWindow() void Application::addRunningInstance() { - m_runningInstances ++; - if(m_runningInstances == 1) - { + m_runningInstances++; + if (m_runningInstances == 1) { emit updateAllowedChanged(false); } } void Application::subRunningInstance() { - if(m_runningInstances == 0) - { + if (m_runningInstances == 0) { qCritical() << "Something went really wrong and we now have less than 0 running instances... WTF"; return; } - m_runningInstances --; - if(m_runningInstances == 0) - { + m_runningInstances--; + if (m_runningInstances == 0) { emit updateAllowedChanged(true); } } @@ -1309,20 +1300,17 @@ void Application::updateIsRunning(bool running) m_updateRunning = running; } - void Application::controllerSucceeded() { - auto controller = qobject_cast(QObject::sender()); - if(!controller) + auto controller = qobject_cast(QObject::sender()); + if (!controller) return; auto id = controller->id(); - auto & extras = m_instanceExtras[id]; + auto& extras = m_instanceExtras[id]; // on success, do... - if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) - { - if(extras.window) - { + if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) { + if (extras.window) { extras.window->close(); } } @@ -1330,8 +1318,7 @@ void Application::controllerSucceeded() subRunningInstance(); // quit when there are no more windows. - if(shouldExitNow()) - { + if (shouldExitNow()) { m_status = Status::Succeeded; exit(0); } @@ -1340,19 +1327,18 @@ void Application::controllerSucceeded() void Application::controllerFailed(const QString& error) { Q_UNUSED(error); - auto controller = qobject_cast(QObject::sender()); - if(!controller) + auto controller = qobject_cast(QObject::sender()); + if (!controller) return; auto id = controller->id(); - auto & extras = m_instanceExtras[id]; + auto& extras = m_instanceExtras[id]; // on failure, do... nothing extras.controller.reset(); subRunningInstance(); // quit when there are no more windows. - if(shouldExitNow()) - { + if (shouldExitNow()) { m_status = Status::Failed; exit(1); } @@ -1360,7 +1346,7 @@ void Application::controllerFailed(const QString& error) void Application::ShowGlobalSettings(class QWidget* parent, QString open_page) { - if(!m_globalSettingsProvider) { + if (!m_globalSettingsProvider) { return; } emit globalSettingsAboutToOpen(); @@ -1374,24 +1360,18 @@ void Application::ShowGlobalSettings(class QWidget* parent, QString open_page) MainWindow* Application::showMainWindow(bool minimized) { - if(m_mainWindow) - { + if (m_mainWindow) { m_mainWindow->setWindowState(m_mainWindow->windowState() & ~Qt::WindowMinimized); m_mainWindow->raise(); m_mainWindow->activateWindow(); - } - else - { + } else { m_mainWindow = new MainWindow(); m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray())); m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray())); - if(minimized) - { + if (minimized) { m_mainWindow->showMinimized(); - } - else - { + } else { m_mainWindow->show(); } @@ -1403,31 +1383,26 @@ MainWindow* Application::showMainWindow(bool minimized) return m_mainWindow; } -InstanceWindow *Application::showInstanceWindow(InstancePtr instance, QString page) +InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString page) { - if(!instance) + if (!instance) return nullptr; auto id = instance->id(); - auto & extras = m_instanceExtras[id]; - auto & window = extras.window; + auto& extras = m_instanceExtras[id]; + auto& window = extras.window; - if(window) - { + if (window) { window->raise(); window->activateWindow(); - } - else - { + } else { window = new InstanceWindow(instance); - m_openWindows ++; + m_openWindows++; connect(window, &InstanceWindow::isClosing, this, &Application::on_windowClose); } - if(!page.isEmpty()) - { + if (!page.isEmpty()) { window->selectPage(page); } - if(extras.controller) - { + if (extras.controller) { extras.controller->setParentWidget(window); } return window; @@ -1436,24 +1411,20 @@ InstanceWindow *Application::showInstanceWindow(InstancePtr instance, QString pa void Application::on_windowClose() { m_openWindows--; - auto instWindow = qobject_cast(QObject::sender()); - if(instWindow) - { - auto & extras = m_instanceExtras[instWindow->instanceId()]; + auto instWindow = qobject_cast(QObject::sender()); + if (instWindow) { + auto& extras = m_instanceExtras[instWindow->instanceId()]; extras.window = nullptr; - if(extras.controller) - { + if (extras.controller) { extras.controller->setParentWidget(m_mainWindow); } } - auto mainWindow = qobject_cast(QObject::sender()); - if(mainWindow) - { + auto mainWindow = qobject_cast(QObject::sender()); + if (mainWindow) { m_mainWindow = nullptr; } // quit when there are no more windows. - if(shouldExitNow()) - { + if (shouldExitNow()) { exit(0); } } @@ -1461,23 +1432,14 @@ void Application::on_windowClose() void Application::updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password) { // Set the application proxy settings. - if (proxyTypeStr == "SOCKS5") - { - QNetworkProxy::setApplicationProxy( - QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password)); - } - else if (proxyTypeStr == "HTTP") - { - QNetworkProxy::setApplicationProxy( - QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password)); - } - else if (proxyTypeStr == "None") - { + if (proxyTypeStr == "SOCKS5") { + QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password)); + } else if (proxyTypeStr == "HTTP") { + QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password)); + } else if (proxyTypeStr == "None") { // If we have no proxy set, set no proxy and return. QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::NoProxy)); - } - else - { + } else { // If we have "Default" selected, set Qt to use the system proxy settings. QNetworkProxyFactory::setUseSystemConfiguration(true); } @@ -1487,39 +1449,35 @@ void Application::updateProxySettings(QString proxyTypeStr, QString addr, int po m_network->setProxy(proxy); QString proxyDesc; - if (proxy.type() == QNetworkProxy::NoProxy) - { + if (proxy.type() == QNetworkProxy::NoProxy) { qDebug() << "Using no proxy is an option!"; return; } - switch (proxy.type()) - { - case QNetworkProxy::DefaultProxy: - proxyDesc = "Default proxy: "; - break; - case QNetworkProxy::Socks5Proxy: - proxyDesc = "Socks5 proxy: "; - break; - case QNetworkProxy::HttpProxy: - proxyDesc = "HTTP proxy: "; - break; - case QNetworkProxy::HttpCachingProxy: - proxyDesc = "HTTP caching: "; - break; - case QNetworkProxy::FtpCachingProxy: - proxyDesc = "FTP caching: "; - break; - default: - proxyDesc = "DERP proxy: "; - break; + switch (proxy.type()) { + case QNetworkProxy::DefaultProxy: + proxyDesc = "Default proxy: "; + break; + case QNetworkProxy::Socks5Proxy: + proxyDesc = "Socks5 proxy: "; + break; + case QNetworkProxy::HttpProxy: + proxyDesc = "HTTP proxy: "; + break; + case QNetworkProxy::HttpCachingProxy: + proxyDesc = "HTTP caching: "; + break; + case QNetworkProxy::FtpCachingProxy: + proxyDesc = "FTP caching: "; + break; + default: + proxyDesc = "DERP proxy: "; + break; } - proxyDesc += QString("%1:%2") - .arg(proxy.hostName()) - .arg(proxy.port()); + proxyDesc += QString("%1:%2").arg(proxy.hostName()).arg(proxy.port()); qDebug() << proxyDesc; } -shared_qobject_ptr< HttpMetaCache > Application::metacache() +shared_qobject_ptr Application::metacache() { return m_metacache; } @@ -1531,8 +1489,7 @@ shared_qobject_ptr Application::network() shared_qobject_ptr Application::metadataIndex() { - if (!m_metadataIndex) - { + if (!m_metadataIndex) { m_metadataIndex.reset(new Meta::Index()); } return m_metadataIndex; @@ -1563,10 +1520,9 @@ QString Application::getJarPath(QString jarFile) #endif FS::PathCombine(m_rootPath, "jars"), FS::PathCombine(applicationDirPath(), "jars"), - FS::PathCombine(applicationDirPath(), "..", "jars") // from inside build dir, for debuging + FS::PathCombine(applicationDirPath(), "..", "jars") // from inside build dir, for debuging }; - for(QString p : potentialPaths) - { + for (QString p : potentialPaths) { QString jarPath = FS::PathCombine(p, jarFile); if (QFileInfo(jarPath).isFile()) return jarPath; @@ -1631,7 +1587,7 @@ int Application::suitableMaxMem() // If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB if (totalRAM < (4096 * 1.5)) - maxMemoryAlloc = (int) (totalRAM / 1.5); + maxMemoryAlloc = (int)(totalRAM / 1.5); else maxMemoryAlloc = 4096; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 5cdb0383e..dc413bb62 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -625,6 +625,11 @@ set(PRISMUPDATER_SOURCES net/HeaderProxy.h net/RawHeaderProxy.h + ui/dialogs/ProgressDialog.cpp + ui/dialogs/ProgressDialog.h + ui/widgets/SubTaskProgressBar.h + ui/widgets/SubTaskProgressBar.cpp + ) ######## Logging categories ######## @@ -1144,6 +1149,8 @@ qt_add_resources(LAUNCHER_RESOURCES qt_wrap_ui(PRISMUPDATER_UI updater/prismupdater/SelectReleaseDialog.ui + ui/widgets/SubTaskProgressBar.ui + ui/dialogs/ProgressDialog.ui ) ######## Windows resource files ######## @@ -1231,11 +1238,13 @@ install(TARGETS ${Launcher_Name} FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime ) -if(NOT APPLE OR (DEFINED Launcher_BUILD_UPDATER AND Launcher_BUILD_UPDATER) ) +if(NOT APPLE OR (DEFINED Launcher_BUILD_UPDATER AND Launcher_BUILD_UPDATER)) # Updater add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI}) target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(prism_updater_logic + QuaZip::QuaZip + ${ZLIB_LIBRARIES} systeminfo BuildConfig ghcFilesystem::ghc_filesystem diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 001ecb10c..2b62feeb3 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -46,8 +46,11 @@ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif +#include +#include #include #include +#include #endif // Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header @@ -100,27 +103,108 @@ 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 apearently 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 (AttachConsole(ATTACH_PARENT_PROCESS)) { - FILE* _stream; - errno_t err; - // if attach succeeds, reopen and sync all the i/o - if (err = freopen_s(&_stream, "CON", "w", stdout); err == 0) { - std::cout.sync_with_stdio(); - } - if (err = freopen_s(&_stream, "CON", "w", stderr); err == 0) { - std::cerr.sync_with_stdio(); - } - if (err = freopen_s(&_stream, "CON", "r", stdin); err == 0) { - std::cin.sync_with_stdio(); - } - auto out = GetStdHandle(STD_OUTPUT_HANDLE); - DWORD written; - const char* endline = "\n"; - WriteConsole(out, endline, strlen(endline), &written, NULL); + BindCrtHandlesToStdHandles(true, true, true); consoleAttached = true; } #endif @@ -133,16 +217,20 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar QCommandLineParser parser; parser.setApplicationDescription(QObject::tr("An auto-updater for Prism Launcher")); - parser.addOptions({ { { "d", "dir" }, tr("Use a custom path as application root (use '.' for current directory)."), tr("directory") }, - { { "I", "install-version" }, "Install a specific version.", tr("version name") }, - { { "U", "update-url" }, tr("Update from the specified repo."), tr("github repo url") }, - { { "c", "check-only" }, - tr("Only check if an update is needed. Exit status 100 if true, 0 if false (or non 0 if there was an error).") }, - { { "F", "force" }, tr("Force an update, even if one is not needed.") }, - { { "l", "list" }, tr("List available releases.") }, - { "debug", tr("Log debug to console.") }, - { { "S", "select-ui" }, tr("Select the version to install with a GUI.") }, - { { "D", "allow-downgrade" }, tr("Allow the updater to downgrade to previous versions.") } }); + parser.addOptions( + { { { "d", "dir" }, tr("Use a custom path as application root (use '.' for current directory)."), tr("directory") }, + { { "V", "prism-version" }, + tr("Use this version as the installed launcher version. (provided because stdout can not be reliably captured on windows)"), + tr("installed launcher version") }, + { { "I", "install-version" }, "Install a specific version.", tr("version name") }, + { { "U", "update-url" }, tr("Update from the specified repo."), tr("github repo url") }, + { { "c", "check-only" }, + tr("Only check if an update is needed. Exit status 100 if true, 0 if false (or non 0 if there was an error).") }, + { { "F", "force" }, tr("Force an update, even if one is not needed.") }, + { { "l", "list" }, tr("List available releases.") }, + { "debug", tr("Log debug to console.") }, + { { "S", "select-ui" }, tr("Select the version to install with a GUI.") }, + { { "D", "allow-downgrade" }, tr("Allow the updater to downgrade to previous versions.") } }); parser.addHelpOption(); parser.addVersionOption(); @@ -150,24 +238,31 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar logToConsole = parser.isSet("debug"); - auto prism_executable = QCoreApplication::applicationFilePath(); + auto updater_executable = QCoreApplication::applicationFilePath(); if (BuildConfig.BUILD_PLATFORM.toLower() == "macos") showFatalErrorMessage(tr("MacOS Not Supported"), tr("The updater does not support installations on MacOS")); - if (!QFileInfo(prism_executable).isFile()) - showFatalErrorMessage(tr("Unsupported Installation"), tr("The updater can not find the main executable.")); - - if (prism_executable.startsWith("/tmp/.mount_")) { + if (updater_executable.startsWith("/tmp/.mount_")) { m_isAppimage = true; m_appimagePath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); - if (m_appimagePath.isEmpty()) + if (m_appimagePath.isEmpty()) { showFatalErrorMessage(tr("Unsupported Installation"), tr("Updater is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); + } } m_isFlatpak = DesktopServices::isFlatpak(); + QString prism_executable = QCoreApplication::applicationDirPath() + "/" + BuildConfig.LAUNCHER_APP_BINARY_NAME; +#if defined Q_OS_WIN32 + prism_executable += ".exe"; +#endif + + if (!QFileInfo(prism_executable).isFile()) { + showFatalErrorMessage(tr("Unsupported Installation"), tr("The updater can not find the main executable.")); + } + m_prismExecutable = prism_executable; auto prism_update_url = parser.value("update-url"); @@ -185,6 +280,20 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar m_selectUI = parser.isSet("select-ui"); m_allowDowngrade = parser.isSet("allow-downgrade"); + auto version = parser.value("prism-version"); + if (!version.isEmpty()) { + if (version.contains('-')) { + auto index = version.indexOf('-'); + m_prsimVersionChannel = version.mid(index + 1); + version = version.left(index); + } else { + m_prsimVersionChannel = "stable"; + } + auto version_parts = version.split('.'); + m_prismVersionMajor = version_parts.takeFirst().toInt(); + m_prismVersionMinor = version_parts.takeFirst().toInt(); + } + QString origCwdPath = QDir::currentPath(); QString binPath = applicationDirPath(); @@ -338,9 +447,9 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar PrismUpdaterApp::~PrismUpdaterApp() { - qDebug() << "updater shutting down"; // Shut down logger by setting the logger function to nothing qInstallMessageHandler(nullptr); + qDebug() << "updater shutting down"; #if defined Q_OS_WIN32 // Detach from Windows console @@ -392,13 +501,19 @@ void PrismUpdaterApp::run() return exit(0); } - loadPrismVersionFromExe(m_prismExecutable); + if (!loadPrismVersionFromExe(m_prismExecutable)) { + m_prismVersion = BuildConfig.printableVersionString(); + m_prismVersionMajor = BuildConfig.VERSION_MAJOR; + m_prismVersionMinor = BuildConfig.VERSION_MINOR; + m_prsimVersionChannel = BuildConfig.VERSION_CHANNEL; + m_prismGitCommit = BuildConfig.GIT_COMMIT; + } m_status = Succeeded; qDebug() << "Executable reports as:" << m_prismBinaryName << "version:" << m_prismVersion; qDebug() << "Version major:" << m_prismVersionMajor; - qDebug() << "Verison minor:" << m_prismVersionMinor; - qDebug() << "Verison channel:" << m_prsimVersionChannel; + qDebug() << "Version minor:" << m_prismVersionMinor; + qDebug() << "Version channel:" << m_prsimVersionChannel; qDebug() << "Git Commit:" << m_prismGitCommit; auto latest = getLatestRelease(); @@ -420,7 +535,7 @@ void PrismUpdaterApp::run() if (m_isAppimage) { bool result = true; if (need_update) - result = callAppimageUpdate(); + result = callAppImageUpdate(); return exit(result ? 0 : 1); } @@ -528,6 +643,13 @@ QList PrismUpdaterApp::validReleaseArtifacts(const GitHubRel bool for_platform = !BuildConfig.BUILD_PLATFORM.isEmpty() && asset.name.toLower().contains(BuildConfig.BUILD_PLATFORM.toLower()); bool for_portable = asset.name.toLower().contains("portable"); + if (for_platform && asset.name.toLower().contains("legacy") && !BuildConfig.BUILD_PLATFORM.toLower().contains("legacy")) + for_platform = false; + if (for_platform && asset.name.toLower().contains("arm64") && !QSysInfo::buildCpuArchitecture().contains("arm64")) + for_platform = false; + if (for_platform && !asset.name.toLower().contains("arm64") && QSysInfo::buildCpuArchitecture().contains("arm64")) + for_platform = false; + if (((m_isPortable && for_portable) || (!m_isPortable && !for_portable)) && for_platform) { valid.append(asset); } @@ -557,10 +679,11 @@ void PrismUpdaterApp::performUpdate(const GitHubRelease& release) GitHubReleaseAsset selected_asset; if (valid_assets.isEmpty()) { - return showFatalErrorMessage(tr("No Valid Release Assets"), - tr("Github release %1 has no valid assets for this platform: %2") - .arg(release.tag_name) - .arg(tr("%1 portable: %2").arg(BuildConfig.BUILD_PLATFORM).arg(m_isPortable))); + return showFatalErrorMessage( + tr("No Valid Release Assets"), + tr("Github release %1 has no valid assets for this platform: %2") + .arg(release.tag_name) + .arg(tr("%1 portable: %2").arg(BuildConfig.BUILD_PLATFORM).arg(m_isPortable ? tr("yes") : tr("no")))); } else if (valid_assets.length() > 1) { selected_asset = selectAsset(valid_assets); } else { @@ -587,18 +710,21 @@ QFileInfo PrismUpdaterApp::downloadAsset(const GitHubReleaseAsset& asset) auto file_url = QUrl(asset.browser_download_url); auto out_file_path = FS::PathCombine(temp_dir, file_url.fileName()); + qDebug() << "downloading" << file_url << "to" << out_file_path; auto download = Net::Download::makeFile(file_url, out_file_path); - + download->setNetwork(m_network); auto progress_dialog = ProgressDialog(); if (progress_dialog.execWithTask(download.get()) == QDialog::Rejected) showFatalErrorMessage(tr("Download Aborted"), tr("Download of %1 aborted by user").arg(file_url.toString())); + qDebug() << "download complete"; + QFileInfo out_file(out_file_path); return out_file; } -bool PrismUpdaterApp::callAppimageUpdate() +bool PrismUpdaterApp::callAppImageUpdate() { QProcess proc = QProcess(); proc.setProgram("AppImageUpdate"); @@ -614,20 +740,20 @@ void PrismUpdaterApp::clearUpdateLog() void PrismUpdaterApp::logUpdate(const QString& msg) { - qDebug() << msg; + qDebug() << qUtf8Printable(msg); auto update_log_path = FS::PathCombine(m_dataPath, "prism_launcher_update.log"); FS::append(update_log_path, QStringLiteral("%1\n").arg(msg).toUtf8()); } void PrismUpdaterApp::performInstall(QFileInfo file) { + qDebug() << "starting install"; auto update_lock_path = FS::PathCombine(m_dataPath, ".prism_launcher_update.lock"); FS::write(update_lock_path, QStringLiteral("FROM=%1\nTO=%2\n").arg(m_prismVersion).arg(m_install_release.tag_name).toUtf8()); clearUpdateLog(); logUpdate(tr("Updating from %1 to %2").arg(m_prismVersion).arg(m_install_release.tag_name)); - // TODO setup marker file - if (m_isPortable) { + if (m_isPortable || file.suffix().toLower() == "zip") { logUpdate(tr("Updating portable install at %1").arg(applicationDirPath())); unpackAndInstall(file); } else { @@ -635,6 +761,7 @@ void PrismUpdaterApp::performInstall(QFileInfo file) QProcess proc = QProcess(); proc.setProgram(file.absoluteFilePath()); bool result = proc.startDetached(); + logUpdate(tr("Process start result: %1").arg(result ? tr("yes") : tr("no"))); exit(result ? 0 : 1); } } @@ -643,8 +770,24 @@ void PrismUpdaterApp::unpackAndInstall(QFileInfo archive) { logUpdate(tr("Backing up install")); backupAppDir(); - auto loc = unpackArchive(archive); - // TODO: unpack (rename access failures) + + if (auto loc = unpackArchive(archive)) { + auto marker_file_path = loc.value().absoluteFilePath(".prism_launcher_updater_unpack.marker"); + FS::write(marker_file_path, applicationDirPath().toUtf8()); + auto new_updater_path = loc.value().absoluteFilePath("prismlauncher-updater"); +#if defined Q_OS_WIN32 + new_updater_path.append(".exe"); +#endif + logUpdate(tr("Starting new updater at '%1'").arg(new_updater_path)); + QProcess proc = QProcess(); + proc.startDetached(new_updater_path, {}, loc.value().absolutePath()); + if (!proc.waitForStarted(5000)) { + logUpdate(tr("Failed to launch '%1' %2").arg(new_updater_path).arg(proc.errorString())); + return exit(10); + } + return exit(); // up to the new updater now + } + return exit(1); // unpack failure } void PrismUpdaterApp::backupAppDir() @@ -680,6 +823,8 @@ void PrismUpdaterApp::backupAppDir() "styles/*", "styles/*", "tls/*", + "qt.conf", + "Qt*.dll", }); } file_list.append("portable.txt"); @@ -688,7 +833,10 @@ void PrismUpdaterApp::backupAppDir() logUpdate(tr("Backing up:\n %1").arg(file_list.join(",\n "))); QDir app_dir = QCoreApplication::applicationDirPath(); - auto backup_dir = FS::PathCombine(app_dir.absolutePath(), QStringLiteral("backup_") + m_prismVersion + ":" + m_prismGitCommit); + auto backup_dir = FS::PathCombine( + app_dir.absolutePath(), QStringLiteral("backup_") + + QString(m_prismVersion).replace(QRegularExpression("[" + QRegularExpression::escape("\\/:*?\"<>|") + "]"), QString("_")) + + "-" + m_prismGitCommit); FS::ensureFolderPathExists(backup_dir); for (auto glob : file_list) { @@ -723,13 +871,12 @@ std::optional PrismUpdaterApp::unpackArchive(QFileInfo archive) if (archive.fileName().endsWith(".zip")) { auto result = MMCZip::extractDir(archive.absoluteFilePath(), tmp_extract_dir.absolutePath()); if (result) { - logUpdate( - tr("Extracted the following to \"%1\":\n %2").arg(tmp_extract_dir.absolutePath()).arg(result->join("\n "))); + logUpdate(tr("Extracted the following to \"%1\":\n %2").arg(tmp_extract_dir.absolutePath()).arg(result->join("\n "))); } else { logUpdate(tr("Failed to extract %1 to %2").arg(archive.absoluteFilePath()).arg(tmp_extract_dir.absolutePath())); showFatalErrorMessage("Failed to extract archive", tr("Failed to extract %1 to %2").arg(archive.absoluteFilePath()).arg(tmp_extract_dir.absolutePath())); - return {}; + return std::nullopt; } } else if (archive.fileName().endsWith(".tar.gz")) { @@ -739,43 +886,52 @@ std::optional PrismUpdaterApp::unpackArchive(QFileInfo archive) QProcess proc = QProcess(); proc.start(cmd, args); if (!proc.waitForStarted(5000)) { // wait 5 seconds to start - showFatalErrorMessage(tr("Failed extract archive"), - tr("Failed to launcher child process \"%1 %2\".").arg(cmd).arg(args.join(" "))); - return {}; + auto msg = tr("Failed to launcher child process \"%1 %2\".").arg(cmd).arg(args.join(" ")); + logUpdate(msg); + showFatalErrorMessage(tr("Failed extract archive"), msg); + return std::nullopt; } auto result = proc.waitForFinished(5000); auto out = proc.readAll(); logUpdate(out); if (!result) { - showFatalErrorMessage(tr("Failed to extract archive"), tr("Child process \"%1 %2\" failed.").arg(cmd).arg(args.join(" "))); - return {}; + auto msg = tr("Child process \"%1 %2\" failed.").arg(cmd).arg(args.join(" ")); + logUpdate(msg); + showFatalErrorMessage(tr("Failed to extract archive"), msg); + return std::nullopt; } } else { logUpdate(tr("Unknown archive format for %1").arg(archive.absoluteFilePath())); showFatalErrorMessage("Can not extract", QStringLiteral("Unknown archive format %1").arg(archive.absoluteFilePath())); - return {}; + return std::nullopt; } return tmp_extract_dir; } -void PrismUpdaterApp::loadPrismVersionFromExe(const QString& exe_path) +bool PrismUpdaterApp::loadPrismVersionFromExe(const QString& exe_path) { QProcess proc = QProcess(); + proc.setProcessChannelMode(QProcess::MergedChannels); + proc.setReadChannel(QProcess::StandardOutput); proc.start(exe_path, { "-v" }); - if (!proc.waitForStarted(5000)) // wait 5 seconds to start - return showFatalErrorMessage(tr("Failed to Check Version"), tr("Failed to launcher child launcher process to read version.")); - if (!proc.waitForFinished(5000)) - return showFatalErrorMessage(tr("Failed to Check Version"), tr("Child launcher process failed.")); + if (!proc.waitForStarted(5000)) { + showFatalErrorMessage(tr("Failed to Check Version"), tr("Failed to launcher child launcher process to read version.")); + return false; + } // wait 5 seconds to start + if (!proc.waitForFinished(5000)) { + showFatalErrorMessage(tr("Failed to Check Version"), tr("Child launcher process failed.")); + return false; + } auto out = proc.readAll(); auto lines = out.split('\n'); if (lines.length() < 2) - return; + return false; auto first = lines.takeFirst(); auto first_parts = first.split(' '); if (first_parts.length() < 2) - return; + return false; m_prismBinaryName = first_parts.takeFirst(); auto version = first_parts.takeFirst(); m_prismVersion = version; @@ -790,6 +946,7 @@ void PrismUpdaterApp::loadPrismVersionFromExe(const QString& exe_path) m_prismVersionMajor = version_parts.takeFirst().toInt(); m_prismVersionMinor = version_parts.takeFirst().toInt(); m_prismGitCommit = lines.takeFirst().simplified(); + return true; } void PrismUpdaterApp::loadReleaseList() diff --git a/launcher/updater/prismupdater/PrismUpdater.h b/launcher/updater/prismupdater/PrismUpdater.h index af7af7dee..909e36056 100644 --- a/launcher/updater/prismupdater/PrismUpdater.h +++ b/launcher/updater/prismupdater/PrismUpdater.h @@ -60,7 +60,7 @@ class PrismUpdaterApp : public QApplication { void abort(const QString& reason); void showFatalErrorMessage(const QString& title, const QString& content); - void loadPrismVersionFromExe(const QString& exe_path); + bool loadPrismVersionFromExe(const QString& exe_path); void downloadReleasePage(const QString& api_url, int page); int parseReleasePage(const QByteArray* response); @@ -83,7 +83,9 @@ class PrismUpdaterApp : public QApplication { std::optional unpackArchive(QFileInfo file); QFileInfo downloadAsset(const GitHubReleaseAsset& asset); - bool callAppimageUpdate(); + bool callAppImageUpdate(); + + void moveAndPostProcess(QDir target); public slots: void downloadError(QString reason); @@ -114,8 +116,8 @@ class PrismUpdaterApp : public QApplication { QString m_prismBinaryName; QString m_prismVersion; - int m_prismVersionMajor; - int m_prismVersionMinor; + int m_prismVersionMajor = -1; + int m_prismVersionMinor = -1; QString m_prsimVersionChannel; QString m_prismGitCommit; diff --git a/launcher/updater/prismupdater/UpdaterDialogs.cpp b/launcher/updater/prismupdater/UpdaterDialogs.cpp index 72fdf3404..14c584918 100644 --- a/launcher/updater/prismupdater/UpdaterDialogs.cpp +++ b/launcher/updater/prismupdater/UpdaterDialogs.cpp @@ -17,8 +17,8 @@ SelectReleaseDialog::SelectReleaseDialog(const Version& current_version, const Q ui->versionsTree->setColumnCount(2); ui->versionsTree->header()->setSectionResizeMode(0, QHeaderView::Stretch); - ui->versionsTree->header()->setSectionResizeMode(1, QHeaderView::Stretch); - ui->versionsTree->setHeaderLabels({tr("Verison"), tr("Published Date")}); + ui->versionsTree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + ui->versionsTree->setHeaderLabels({tr("Version"), tr("Published Date")}); ui->versionsTree->header()->setStretchLastSection(false); ui->eplainLabel->setText(tr("Select a version to install.\n" @@ -90,8 +90,8 @@ SelectReleaseAssetDialog::SelectReleaseAssetDialog( const QListversionsTree->setColumnCount(2); ui->versionsTree->header()->setSectionResizeMode(0, QHeaderView::Stretch); - ui->versionsTree->header()->setSectionResizeMode(1, QHeaderView::Stretch); - ui->versionsTree->setHeaderLabels({tr("Verison"), tr("Published Date")}); + ui->versionsTree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + ui->versionsTree->setHeaderLabels({tr("Version"), tr("Published Date")}); ui->versionsTree->header()->setStretchLastSection(false); ui->eplainLabel->setText(tr("Select a version to install."));