From a737d5df42ed0a7149bbced10e8fd38fc2b6fe2f Mon Sep 17 00:00:00 2001 From: sshcrack <34072808+sshcrack@users.noreply.github.com> Date: Tue, 22 Oct 2024 20:53:57 +0200 Subject: [PATCH 001/171] added instance shortcut feature Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com> --- launcher/Application.cpp | 3 + launcher/FileSystem.cpp | 5 + launcher/FileSystem.h | 3 + launcher/ui/MainWindow.cpp | 245 ++++++++++++---------- launcher/ui/pages/global/LauncherPage.cpp | 35 ++++ launcher/ui/pages/global/LauncherPage.ui | 96 +++++---- 6 files changed, 238 insertions(+), 149 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ea749ca4c..8714799ff 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -701,6 +701,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("InstSortMode", "Name"); m_settings->registerSetting("SelectedInstance", QString()); + // Shortcut creation + m_settings->registerSetting("ShortcutCreationMode", "Desktop"); + // Window state and geometry m_settings->registerSetting("MainWindowState", ""); m_settings->registerSetting("MainWindowGeometry", ""); diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 512de28c2..8f683a9fb 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -915,6 +915,11 @@ QString getDesktopDir() return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); } +QString getApplicationsDir() +{ + return QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); +} + // Cross-platform Shortcut creation bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon) { diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index c5beef7bd..4aa5596ae 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -353,6 +353,9 @@ bool checkProblemticPathJava(QDir folder); // Get the Directory representing the User's Desktop QString getDesktopDir(); +// Get the Directory representing the User's Applications directory +QString getApplicationsDir(); + // Overrides one folder with the contents of another, preserving items exclusive to the first folder // Equivalent to doing QDir::rename, but allowing for overrides bool overrideFolder(QString overwritten_path, QString override_path); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 09c47b609..0961a5c4e 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1495,141 +1495,158 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered() { if (!m_selectedInstance) return; - auto desktopPath = FS::getDesktopDir(); - if (desktopPath.isEmpty()) { - // TODO come up with an alternative solution (open "save file" dialog) - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!")); - return; + + std::vector paths; + QString mode = APPLICATION->settings()->get("ShortcutCreationMode").toString(); + if (mode == "Applications") { + paths.push_back(FS::getApplicationsDir()); + } else if (mode == "Both") { + paths.push_back(FS::getDesktopDir()); + paths.push_back(FS::getApplicationsDir()); + } else { + // Default to desktop + paths.push_back(FS::getDesktopDir()); } - QString desktopFilePath; - QString appPath = QApplication::applicationFilePath(); - QString iconPath; - QStringList args; -#if defined(Q_OS_MACOS) - appPath = QApplication::applicationFilePath(); - if (appPath.startsWith("/private/var/")) { - QMessageBox::critical(this, tr("Create instance shortcut"), - tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts.")); - return; - } - - auto pIcon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); - if (pIcon == nullptr) { - pIcon = APPLICATION->icons()->icon("grass"); - } - - iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "Icon.icns"); - - QFile iconFile(iconPath); - if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); - return; - } - - QIcon icon = pIcon->icon(); - - bool success = icon.pixmap(1024, 1024).save(iconPath, "ICNS"); - iconFile.close(); - - if (!success) { - iconFile.remove(); - QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); - return; - } -#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - if (appPath.startsWith("/tmp/.mount_")) { - // AppImage! - appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); - if (appPath.isEmpty()) { - QMessageBox::critical(this, tr("Create instance shortcut"), - tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); - } else if (appPath.endsWith("/")) { - appPath.chop(1); + for (const QString& shortcutDirPath : paths) { + if (shortcutDirPath.isEmpty()) { + // TODO come up with an alternative solution (open "save file" dialog) + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!")); + return; } - } - auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); - if (icon == nullptr) { - icon = APPLICATION->icons()->icon("grass"); - } + QString shortcutFilePath; + QString appPath = QApplication::applicationFilePath(); + QString iconPath; + QStringList args; +#if defined(Q_OS_MACOS) + appPath = QApplication::applicationFilePath(); + if (appPath.startsWith("/private/var/")) { + QMessageBox::critical(this, tr("Create instance shortcut"), + tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts.")); + return; + } - iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png"); + auto pIcon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); + if (pIcon == nullptr) { + pIcon = APPLICATION->icons()->icon("grass"); + } - QFile iconFile(iconPath); - if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } - bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG"); - iconFile.close(); + iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "Icon.icns"); - if (!success) { - iconFile.remove(); - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) { + QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); + return; + } - if (DesktopServices::isFlatpak()) { - desktopFilePath = FS::PathCombine(desktopPath, FS::RemoveInvalidFilenameChars(m_selectedInstance->name()) + ".desktop"); - QFileDialog fileDialog; - // workaround to make sure the portal file dialog opens in the desktop directory - fileDialog.setDirectoryUrl(desktopPath); - desktopFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), desktopFilePath, tr("Desktop Entries") + " (*.desktop)"); - if (desktopFilePath.isEmpty()) - return; // file dialog canceled by user - appPath = "flatpak"; - QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME; - flatpakAppId.remove(".desktop"); - args.append({ "run", flatpakAppId }); - } + QIcon icon = pIcon->icon(); + + bool success = icon.pixmap(1024, 1024).save(iconPath, "ICNS"); + iconFile.close(); + + if (!success) { + iconFile.remove(); + QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); + return; + } +#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + if (appPath.startsWith("/tmp/.mount_")) { + // AppImage! + appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); + if (appPath.isEmpty()) { + QMessageBox::critical(this, tr("Create instance shortcut"), + tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); + } else if (appPath.endsWith("/")) { + appPath.chop(1); + } + } + + auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); + if (icon == nullptr) { + icon = APPLICATION->icons()->icon("grass"); + } + + iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png"); + + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG"); + iconFile.close(); + + if (!success) { + iconFile.remove(); + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + + if (DesktopServices::isFlatpak()) { + shortcutFilePath = FS::PathCombine(shortcutDirPath, FS::RemoveInvalidFilenameChars(m_selectedInstance->name()) + ".desktop"); + QFileDialog fileDialog; + // workaround to make sure the portal file dialog opens in the desktop directory + fileDialog.setDirectoryUrl(shortcutDirPath); + shortcutFilePath = + fileDialog.getSaveFileName(this, tr("Create Shortcut"), shortcutFilePath, tr("Desktop Entries") + " (*.desktop)"); + if (shortcutFilePath.isEmpty()) + return; // file dialog canceled by user + appPath = "flatpak"; + QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME; + flatpakAppId.remove(".desktop"); + args.append({ "run", flatpakAppId }); + } #elif defined(Q_OS_WIN) - auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); - if (icon == nullptr) { - icon = APPLICATION->icons()->icon("grass"); - } + auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); + if (icon == nullptr) { + icon = APPLICATION->icons()->icon("grass"); + } - iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico"); + iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico"); - // part of fix for weird bug involving the window icon being replaced - // dunno why it happens, but this 2-line fix seems to be enough, so w/e - auto appIcon = APPLICATION->getThemedIcon("logo"); + // part of fix for weird bug involving the window icon being replaced + // dunno why it happens, but this 2-line fix seems to be enough, so w/e + auto appIcon = APPLICATION->getThemedIcon("logo"); - QFile iconFile(iconPath); - if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } - bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO"); - iconFile.close(); + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO"); + iconFile.close(); - // restore original window icon - QGuiApplication::setWindowIcon(appIcon); + // restore original window icon + QGuiApplication::setWindowIcon(appIcon); - if (!success) { - iconFile.remove(); - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } + if (!success) { + iconFile.remove(); + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } #else - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!")); - return; + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!")); + return; #endif - args.append({ "--launch", m_selectedInstance->id() }); - if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) { + args.append({ "--launch", m_selectedInstance->id() }); + + if (shortcutFilePath.isEmpty()) + shortcutFilePath = FS::PathCombine(shortcutDirPath, FS::RemoveInvalidFilenameChars(m_selectedInstance->name())); + if (!FS::createShortcut(shortcutFilePath, appPath, args, m_selectedInstance->name(), iconPath)) { #if not defined(Q_OS_MACOS) - QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!")); -#else - QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!")); + iconFile.remove(); #endif - } else { -#if not defined(Q_OS_MACOS) - iconFile.remove(); -#endif - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!")); + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!")); + } } +#if not defined(Q_OS_MACOS) + QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!")); +#else + QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!")); +#endif } void MainWindow::taskEnd() diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 8bbed9643..90540247e 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -65,6 +65,15 @@ enum InstSortMode { Sort_LastLaunch }; +enum ShortcutCreationMode { + // Create a shortcut in the applications + Shortcut_OnlyApplications, + // Create a shortcut in both locations + Shortcut_Both, + // Create a shortcut on the desktop + Shortcut_OnlyDesktop +}; + LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage) { ui->setupUi(this); @@ -254,6 +263,19 @@ void LauncherPage::applySettings() s->set("ModMetadataDisabled", ui->metadataDisableBtn->isChecked()); s->set("ModDependenciesDisabled", ui->dependenciesDisableBtn->isChecked()); s->set("SkipModpackUpdatePrompt", ui->skipModpackUpdatePromptBtn->isChecked()); + + auto shortcutMode = (ShortcutCreationMode) ui->createShortcutActionComboBox->currentIndex(); + switch (shortcutMode) { + case Shortcut_OnlyApplications: + s->set("ShortcutCreationMode", "Applications"); + break; + case Shortcut_Both: + s->set("ShortcutCreationMode", "Both"); + break; + case Shortcut_OnlyDesktop: + s->set("ShortcutCreationMode", "Desktop"); + break; + } } void LauncherPage::loadSettings() { @@ -319,6 +341,19 @@ void LauncherPage::loadSettings() ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); ui->dependenciesDisableBtn->setChecked(s->get("ModDependenciesDisabled").toBool()); ui->skipModpackUpdatePromptBtn->setChecked(s->get("SkipModpackUpdatePrompt").toBool()); + + QString shortcutModeStr = s->get("ShortcutCreationMode").toString(); + ShortcutCreationMode shortcutMode = Shortcut_OnlyDesktop; + if(shortcutModeStr == "Applications") { + shortcutMode = Shortcut_OnlyApplications; + } else if(shortcutModeStr == "Desktop") { + // Guess we don't need that, but it's here for completeness + shortcutMode = Shortcut_OnlyDesktop; + } else if(shortcutModeStr == "Both") { + shortcutMode = Shortcut_Both; + } + + ui->createShortcutActionComboBox->setCurrentIndex(shortcutMode); } void LauncherPage::refreshFontPreview() diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 3cba468ff..1e08d8266 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -282,41 +282,6 @@ Miscellaneous - - - - 1 - - - - - - - Number of concurrent tasks - - - - - - - 1 - - - - - - - Number of concurrent downloads - - - - - - - Number of manual retries - - - @@ -334,6 +299,13 @@ + + + + 1 + + + @@ -341,6 +313,60 @@ + + + + Number of concurrent downloads + + + + + + + Number of manual retries + + + + + + + Number of concurrent tasks + + + + + + + 1 + + + + + + + Create shortcut action + + + + + + + + Applications only + + + + + Applications & Desktop + + + + + Desktop only + + + + From 59efca764c03450717f8544d96902624bcd6cad0 Mon Sep 17 00:00:00 2001 From: sshcrack <34072808+sshcrack@users.noreply.github.com> Date: Tue, 22 Oct 2024 21:20:22 +0200 Subject: [PATCH 002/171] removed creation of shortcuts for flatpak / appimage users Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 0961a5c4e..8979667f0 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1633,6 +1633,7 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered() #endif args.append({ "--launch", m_selectedInstance->id() }); + bool userDefinedShortcutPath = !shortcutFilePath.isEmpty(); if (shortcutFilePath.isEmpty()) shortcutFilePath = FS::PathCombine(shortcutDirPath, FS::RemoveInvalidFilenameChars(m_selectedInstance->name())); if (!FS::createShortcut(shortcutFilePath, appPath, args, m_selectedInstance->name(), iconPath)) { @@ -1640,7 +1641,11 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered() iconFile.remove(); #endif QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!")); + return; } + + if(userDefinedShortcutPath) + break; } #if not defined(Q_OS_MACOS) QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!")); From b182a888aa3559ab3c0e8034533cc63040f84c34 Mon Sep 17 00:00:00 2001 From: sshcrack <34072808+sshcrack@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:39:03 +0200 Subject: [PATCH 003/171] revert changes to settings and used menu for shortcuts Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com> --- launcher/Application.cpp | 3 - launcher/ui/MainWindow.cpp | 330 ++++++++++++---------- launcher/ui/MainWindow.h | 9 +- launcher/ui/MainWindow.ui | 166 +++++------ launcher/ui/pages/global/LauncherPage.cpp | 37 +-- launcher/ui/pages/global/LauncherPage.ui | 92 +++--- 6 files changed, 293 insertions(+), 344 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 8714799ff..ea749ca4c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -701,9 +701,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("InstSortMode", "Name"); m_settings->registerSetting("SelectedInstance", QString()); - // Shortcut creation - m_settings->registerSetting("ShortcutCreationMode", "Desktop"); - // Window state and geometry m_settings->registerSetting("MainWindowState", ""); m_settings->registerSetting("MainWindowGeometry", ""); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 8979667f0..511055b07 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -208,6 +208,13 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi exportInstanceMenu->addAction(ui->actionExportInstanceMrPack); exportInstanceMenu->addAction(ui->actionExportInstanceFlamePack); ui->actionExportInstance->setMenu(exportInstanceMenu); + + auto shortcutInstanceMenu = new QMenu(this); + shortcutInstanceMenu->addAction(ui->actionCreateInstanceShortcutDesktop); + shortcutInstanceMenu->addAction(ui->actionCreateInstanceShortcutApplications); + shortcutInstanceMenu->addAction(ui->actionCreateInstanceShortcutOther); + + ui->actionCreateInstanceShortcut->setMenu(shortcutInstanceMenu); } // hide, disable and show stuff @@ -235,6 +242,13 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi } ui->actionViewJavaFolder->setEnabled(BuildConfig.JAVA_DOWNLOADER_ENABLED); + +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + bool isFlatpak = DesktopServices::isFlatpak(); + + ui->actionCreateInstanceShortcutDesktop->setEnabled(isFlatpak); + ui->actionCreateInstanceShortcutApplications->setEnabled(isFlatpak); +#endif } // add the toolbar toggles to the view menu @@ -1491,167 +1505,169 @@ void MainWindow::on_actionKillInstance_triggered() } } -void MainWindow::on_actionCreateInstanceShortcut_triggered() +void MainWindow::createInstanceShortcut(QString shortcutFilePath) { + + QString appPath = QApplication::applicationFilePath(); + QString iconPath; + QStringList args; +#if defined(Q_OS_MACOS) + appPath = QApplication::applicationFilePath(); + if (appPath.startsWith("/private/var/")) { + QMessageBox::critical(this, tr("Create instance shortcut"), + tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts.")); + return; + } + + auto pIcon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); + if (pIcon == nullptr) { + pIcon = APPLICATION->icons()->icon("grass"); + } + + iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "Icon.icns"); + + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) { + QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); + return; + } + + QIcon icon = pIcon->icon(); + + bool success = icon.pixmap(1024, 1024).save(iconPath, "ICNS"); + iconFile.close(); + + if (!success) { + iconFile.remove(); + QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); + return; + } +#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + if (appPath.startsWith("/tmp/.mount_")) { + // AppImage! + appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); + if (appPath.isEmpty()) { + QMessageBox::critical(this, tr("Create instance shortcut"), + tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); + } else if (appPath.endsWith("/")) { + appPath.chop(1); + } + } + + auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); + if (icon == nullptr) { + icon = APPLICATION->icons()->icon("grass"); + } + + iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png"); + + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG"); + iconFile.close(); + + if (!success) { + iconFile.remove(); + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + + if (DesktopServices::isFlatpak()) { + appPath = "flatpak"; + QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME; + flatpakAppId.remove(".desktop"); + args.append({ "run", flatpakAppId }); + } + +#elif defined(Q_OS_WIN) + auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); + if (icon == nullptr) { + icon = APPLICATION->icons()->icon("grass"); + } + + iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico"); + + // part of fix for weird bug involving the window icon being replaced + // dunno why it happens, but this 2-line fix seems to be enough, so w/e + auto appIcon = APPLICATION->getThemedIcon("logo"); + + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO"); + iconFile.close(); + + // restore original window icon + QGuiApplication::setWindowIcon(appIcon); + + if (!success) { + iconFile.remove(); + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + +#else + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!")); + return; +#endif + args.append({ "--launch", m_selectedInstance->id() }); + + if (!FS::createShortcut(std::move(shortcutFilePath), appPath, args, m_selectedInstance->name(), iconPath)) { +#if not defined(Q_OS_MACOS) + iconFile.remove(); +#endif + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!")); + return; + } +} + +void MainWindow::on_actionCreateInstanceShortcutOther_triggered() { + if (!m_selectedInstance) + return; + + QString defaultedDir = FS::getDesktopDir(); +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + QString extension = ".desktop"; +#elif defined(Q_OS_WINDOWS) + QString extension = ".lnk"; +#else + QString extension = ""; +#endif + + QString shortcutFilePath = FS::PathCombine(defaultedDir, FS::RemoveInvalidFilenameChars(m_selectedInstance->name()) + extension); + QFileDialog fileDialog; + // workaround to make sure the portal file dialog opens in the desktop directory + fileDialog.setDirectoryUrl(defaultedDir); + + shortcutFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), shortcutFilePath, tr("Desktop Entries") + " (*.desktop)"); + if (shortcutFilePath.isEmpty()) + return; // file dialog canceled by user + + createInstanceShortcut(shortcutFilePath); + QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!")); +} + +void MainWindow::on_actionCreateInstanceShortcutDesktop_triggered() { + if (!m_selectedInstance) + return; + + QString shortcutFilePath = FS::PathCombine(FS::getDesktopDir(), FS::RemoveInvalidFilenameChars(m_selectedInstance->name())); + createInstanceShortcut(shortcutFilePath); + QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!")); +} + +void MainWindow::on_actionCreateInstanceShortcutApplications_triggered() { if (!m_selectedInstance) return; - std::vector paths; - QString mode = APPLICATION->settings()->get("ShortcutCreationMode").toString(); - if (mode == "Applications") { - paths.push_back(FS::getApplicationsDir()); - } else if (mode == "Both") { - paths.push_back(FS::getDesktopDir()); - paths.push_back(FS::getApplicationsDir()); - } else { - // Default to desktop - paths.push_back(FS::getDesktopDir()); - } - - for (const QString& shortcutDirPath : paths) { - if (shortcutDirPath.isEmpty()) { - // TODO come up with an alternative solution (open "save file" dialog) - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!")); - return; - } - - QString shortcutFilePath; - QString appPath = QApplication::applicationFilePath(); - QString iconPath; - QStringList args; -#if defined(Q_OS_MACOS) - appPath = QApplication::applicationFilePath(); - if (appPath.startsWith("/private/var/")) { - QMessageBox::critical(this, tr("Create instance shortcut"), - tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts.")); - return; - } - - auto pIcon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); - if (pIcon == nullptr) { - pIcon = APPLICATION->icons()->icon("grass"); - } - - iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "Icon.icns"); - - QFile iconFile(iconPath); - if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); - return; - } - - QIcon icon = pIcon->icon(); - - bool success = icon.pixmap(1024, 1024).save(iconPath, "ICNS"); - iconFile.close(); - - if (!success) { - iconFile.remove(); - QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); - return; - } -#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - if (appPath.startsWith("/tmp/.mount_")) { - // AppImage! - appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); - if (appPath.isEmpty()) { - QMessageBox::critical(this, tr("Create instance shortcut"), - tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); - } else if (appPath.endsWith("/")) { - appPath.chop(1); - } - } - - auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); - if (icon == nullptr) { - icon = APPLICATION->icons()->icon("grass"); - } - - iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png"); - - QFile iconFile(iconPath); - if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } - bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG"); - iconFile.close(); - - if (!success) { - iconFile.remove(); - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } - - if (DesktopServices::isFlatpak()) { - shortcutFilePath = FS::PathCombine(shortcutDirPath, FS::RemoveInvalidFilenameChars(m_selectedInstance->name()) + ".desktop"); - QFileDialog fileDialog; - // workaround to make sure the portal file dialog opens in the desktop directory - fileDialog.setDirectoryUrl(shortcutDirPath); - shortcutFilePath = - fileDialog.getSaveFileName(this, tr("Create Shortcut"), shortcutFilePath, tr("Desktop Entries") + " (*.desktop)"); - if (shortcutFilePath.isEmpty()) - return; // file dialog canceled by user - appPath = "flatpak"; - QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME; - flatpakAppId.remove(".desktop"); - args.append({ "run", flatpakAppId }); - } - -#elif defined(Q_OS_WIN) - auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); - if (icon == nullptr) { - icon = APPLICATION->icons()->icon("grass"); - } - - iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico"); - - // part of fix for weird bug involving the window icon being replaced - // dunno why it happens, but this 2-line fix seems to be enough, so w/e - auto appIcon = APPLICATION->getThemedIcon("logo"); - - QFile iconFile(iconPath); - if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } - bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO"); - iconFile.close(); - - // restore original window icon - QGuiApplication::setWindowIcon(appIcon); - - if (!success) { - iconFile.remove(); - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } - -#else - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!")); - return; -#endif - args.append({ "--launch", m_selectedInstance->id() }); - - bool userDefinedShortcutPath = !shortcutFilePath.isEmpty(); - if (shortcutFilePath.isEmpty()) - shortcutFilePath = FS::PathCombine(shortcutDirPath, FS::RemoveInvalidFilenameChars(m_selectedInstance->name())); - if (!FS::createShortcut(shortcutFilePath, appPath, args, m_selectedInstance->name(), iconPath)) { -#if not defined(Q_OS_MACOS) - iconFile.remove(); -#endif - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!")); - return; - } - - if(userDefinedShortcutPath) - break; - } -#if not defined(Q_OS_MACOS) - QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!")); -#else - QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!")); -#endif + QString shortcutFilePath = FS::PathCombine(FS::getApplicationsDir(), FS::RemoveInvalidFilenameChars(m_selectedInstance->name())); + createInstanceShortcut(shortcutFilePath); + QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance in your applications folder!")); } void MainWindow::taskEnd() diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 0e692eda7..bdd4a1890 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -165,7 +165,13 @@ class MainWindow : public QMainWindow { void on_actionEditInstance_triggered(); - void on_actionCreateInstanceShortcut_triggered(); + inline void on_actionCreateInstanceShortcut_triggered() { + on_actionCreateInstanceShortcutDesktop_triggered(); + }; + + void on_actionCreateInstanceShortcutDesktop_triggered(); + void on_actionCreateInstanceShortcutApplications_triggered(); + void on_actionCreateInstanceShortcutOther_triggered(); void taskEnd(); @@ -226,6 +232,7 @@ class MainWindow : public QMainWindow { void setSelectedInstanceById(const QString& id); void updateStatusCenter(); void setInstanceActionsEnabled(bool enabled); + void createInstanceShortcut(QString shortcutDirPath); void runModalTask(Task* task); void instanceFromInstanceTask(InstanceTask* task); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index f20c34206..3fa30c97e 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -131,7 +131,7 @@ 0 0 800 - 27 + 22 @@ -235,8 +235,7 @@ - - .. + More news... @@ -250,8 +249,7 @@ true - - .. + &Meow @@ -286,8 +284,7 @@ - - .. + Add Instanc&e... @@ -298,8 +295,7 @@ - - .. + &Update... @@ -313,8 +309,7 @@ - - .. + Setti&ngs... @@ -328,8 +323,7 @@ - - .. + &Manage Accounts... @@ -337,8 +331,7 @@ - - .. + &Launch @@ -349,8 +342,7 @@ - - .. + &Kill @@ -364,8 +356,7 @@ - - .. + Rename @@ -376,8 +367,7 @@ - - .. + &Change Group... @@ -399,8 +389,7 @@ - - .. + &Edit... @@ -414,8 +403,7 @@ - - .. + &Folder @@ -426,8 +414,7 @@ - - .. + Dele&te @@ -441,8 +428,7 @@ - - .. + Cop&y... @@ -456,8 +442,7 @@ - - .. + E&xport... @@ -468,8 +453,7 @@ - - .. + Prism Launcher (zip) @@ -477,8 +461,7 @@ - - .. + Modrinth (mrpack) @@ -486,8 +469,7 @@ - - .. + CurseForge (zip) @@ -495,20 +477,18 @@ - - .. + Create Shortcut - Creates a shortcut on your desktop to launch the selected instance. + Creates a shortcut on a selected folder to launch the selected instance. - - .. + No accounts added! @@ -519,8 +499,7 @@ true - - .. + No Default Account @@ -531,8 +510,7 @@ - - .. + Close &Window @@ -546,8 +524,7 @@ - - .. + &Instances @@ -558,8 +535,7 @@ - - .. + Launcher &Root @@ -570,8 +546,7 @@ - - .. + &Central Mods @@ -582,8 +557,7 @@ - - .. + &Skins @@ -594,8 +568,7 @@ - - .. + Instance Icons @@ -606,8 +579,7 @@ - - .. + Logs @@ -623,8 +595,7 @@ - - .. + Report a Bug or Suggest a Feature @@ -635,8 +606,7 @@ - - .. + &Discord Guild @@ -647,8 +617,7 @@ - - .. + &Matrix Space @@ -659,8 +628,7 @@ - - .. + Sub&reddit @@ -671,8 +639,7 @@ - - .. + &About %1 @@ -686,8 +653,7 @@ - - .. + &Clear Metadata Cache @@ -698,8 +664,7 @@ - - .. + Install to &PATH @@ -710,8 +675,7 @@ - - .. + Folders @@ -722,8 +686,7 @@ - - .. + Help @@ -734,8 +697,7 @@ - - .. + Accounts @@ -743,8 +705,7 @@ - - .. + %1 &Help @@ -755,8 +716,7 @@ - - .. + &Widget Themes @@ -767,8 +727,7 @@ - - .. + I&con Theme @@ -779,8 +738,7 @@ - - .. + Cat Packs @@ -791,8 +749,7 @@ - - .. + Java @@ -801,6 +758,39 @@ Open the Java folder in a file browser. Only available if the built-in Java downloader is used. + + + Desktop + + + Creates an shortcut to this instance on your desktop + + + QAction::TextHeuristicRole + + + + + Applications + + + Create a shortcut of this instance on your start menu + + + QAction::TextHeuristicRole + + + + + Other... + + + Creates a shortcut in a folder selected by you + + + QAction::TextHeuristicRole + + diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 90540247e..da4ba9023 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -65,15 +65,6 @@ enum InstSortMode { Sort_LastLaunch }; -enum ShortcutCreationMode { - // Create a shortcut in the applications - Shortcut_OnlyApplications, - // Create a shortcut in both locations - Shortcut_Both, - // Create a shortcut on the desktop - Shortcut_OnlyDesktop -}; - LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage) { ui->setupUi(this); @@ -263,19 +254,6 @@ void LauncherPage::applySettings() s->set("ModMetadataDisabled", ui->metadataDisableBtn->isChecked()); s->set("ModDependenciesDisabled", ui->dependenciesDisableBtn->isChecked()); s->set("SkipModpackUpdatePrompt", ui->skipModpackUpdatePromptBtn->isChecked()); - - auto shortcutMode = (ShortcutCreationMode) ui->createShortcutActionComboBox->currentIndex(); - switch (shortcutMode) { - case Shortcut_OnlyApplications: - s->set("ShortcutCreationMode", "Applications"); - break; - case Shortcut_Both: - s->set("ShortcutCreationMode", "Both"); - break; - case Shortcut_OnlyDesktop: - s->set("ShortcutCreationMode", "Desktop"); - break; - } } void LauncherPage::loadSettings() { @@ -341,19 +319,6 @@ void LauncherPage::loadSettings() ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); ui->dependenciesDisableBtn->setChecked(s->get("ModDependenciesDisabled").toBool()); ui->skipModpackUpdatePromptBtn->setChecked(s->get("SkipModpackUpdatePrompt").toBool()); - - QString shortcutModeStr = s->get("ShortcutCreationMode").toString(); - ShortcutCreationMode shortcutMode = Shortcut_OnlyDesktop; - if(shortcutModeStr == "Applications") { - shortcutMode = Shortcut_OnlyApplications; - } else if(shortcutModeStr == "Desktop") { - // Guess we don't need that, but it's here for completeness - shortcutMode = Shortcut_OnlyDesktop; - } else if(shortcutModeStr == "Both") { - shortcutMode = Shortcut_Both; - } - - ui->createShortcutActionComboBox->setCurrentIndex(shortcutMode); } void LauncherPage::refreshFontPreview() @@ -404,4 +369,4 @@ void LauncherPage::refreshFontPreview() void LauncherPage::retranslate() { ui->retranslateUi(this); -} +} \ No newline at end of file diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 1e08d8266..ff95bdfbb 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -282,23 +282,6 @@ Miscellaneous - - - - 0 - - - - - - - Seconds to wait until the requests are terminated - - - Timeout for HTTP requests - - - @@ -306,27 +289,6 @@ - - - - s - - - - - - - Number of concurrent downloads - - - - - - - Number of manual retries - - - @@ -341,30 +303,42 @@ - - + + - Create shortcut action + Number of concurrent downloads - - - - - Applications only - - - - - Applications & Desktop - - - - - Desktop only - - + + + + Number of manual retries + + + + + + + 0 + + + + + + + Seconds to wait until the requests are terminated + + + Timeout for HTTP requests + + + + + + + s + @@ -694,4 +668,4 @@ - + \ No newline at end of file From ef2f865159b23dd5715e77c838d7e7b1e0c2a694 Mon Sep 17 00:00:00 2001 From: sshcrack <34072808+sshcrack@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:45:50 +0200 Subject: [PATCH 004/171] add back folder checks / use specific extension Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 511055b07..599497224 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1506,6 +1506,8 @@ void MainWindow::on_actionKillInstance_triggered() } void MainWindow::createInstanceShortcut(QString shortcutFilePath) { + if(!m_selectedInstance) + return; QString appPath = QApplication::applicationFilePath(); QString iconPath; @@ -1643,7 +1645,7 @@ void MainWindow::on_actionCreateInstanceShortcutOther_triggered() { // workaround to make sure the portal file dialog opens in the desktop directory fileDialog.setDirectoryUrl(defaultedDir); - shortcutFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), shortcutFilePath, tr("Desktop Entries") + " (*.desktop)"); + shortcutFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), shortcutFilePath, tr("Desktop Entries") + " (*." + extension + ")"); if (shortcutFilePath.isEmpty()) return; // file dialog canceled by user @@ -1655,6 +1657,12 @@ void MainWindow::on_actionCreateInstanceShortcutDesktop_triggered() { if (!m_selectedInstance) return; + QString desktopDir = FS::getDesktopDir(); + if (desktopDir.isEmpty()) { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!")); + return; + } + QString shortcutFilePath = FS::PathCombine(FS::getDesktopDir(), FS::RemoveInvalidFilenameChars(m_selectedInstance->name())); createInstanceShortcut(shortcutFilePath); QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!")); @@ -1665,7 +1673,13 @@ void MainWindow::on_actionCreateInstanceShortcutApplications_triggered() if (!m_selectedInstance) return; - QString shortcutFilePath = FS::PathCombine(FS::getApplicationsDir(), FS::RemoveInvalidFilenameChars(m_selectedInstance->name())); + QString applicationsDir = FS::getApplicationsDir(); + if (applicationsDir.isEmpty()) { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find applications folder?!")); + return; + } + + QString shortcutFilePath = FS::PathCombine(applicationsDir, FS::RemoveInvalidFilenameChars(m_selectedInstance->name())); createInstanceShortcut(shortcutFilePath); QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance in your applications folder!")); } From f1048c2e0d7593069e59282708838a5a5359f951 Mon Sep 17 00:00:00 2001 From: sshcrack <34072808+sshcrack@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:46:27 +0200 Subject: [PATCH 005/171] removed unnecessary macro Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 599497224..6b9daa398 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -243,12 +243,10 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi ui->actionViewJavaFolder->setEnabled(BuildConfig.JAVA_DOWNLOADER_ENABLED); -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) bool isFlatpak = DesktopServices::isFlatpak(); ui->actionCreateInstanceShortcutDesktop->setEnabled(isFlatpak); ui->actionCreateInstanceShortcutApplications->setEnabled(isFlatpak); -#endif } // add the toolbar toggles to the view menu From b16e12c9af4288a853b1c0b013a7d406d8952be8 Mon Sep 17 00:00:00 2001 From: sshcrack <34072808+sshcrack@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:49:08 +0200 Subject: [PATCH 006/171] remove dot Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 6b9daa398..37d4422e4 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1643,7 +1643,7 @@ void MainWindow::on_actionCreateInstanceShortcutOther_triggered() { // workaround to make sure the portal file dialog opens in the desktop directory fileDialog.setDirectoryUrl(defaultedDir); - shortcutFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), shortcutFilePath, tr("Desktop Entries") + " (*." + extension + ")"); + shortcutFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), shortcutFilePath, tr("Desktop Entries") + " (*" + extension + ")"); if (shortcutFilePath.isEmpty()) return; // file dialog canceled by user From 43ccf18449fa7eeee0b1fa001c64b01c7d072ec3 Mon Sep 17 00:00:00 2001 From: sshcrack <34072808+sshcrack@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:51:47 +0200 Subject: [PATCH 007/171] fix default action for flatpak Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 10 ++++++++++ launcher/ui/MainWindow.h | 4 +--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 37d4422e4..e73a702f8 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1651,6 +1651,16 @@ void MainWindow::on_actionCreateInstanceShortcutOther_triggered() { QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!")); } +void MainWindow::on_actionCreateInstanceShortcut_triggered() { + if(!m_selectedInstance) + return; + + if(DesktopServices::isFlatpak()) + onactionCreateInstanceShortcutOther_triggered(); + else + on_actionCreateInstanceShortcutDesktop_triggered(); +} + void MainWindow::on_actionCreateInstanceShortcutDesktop_triggered() { if (!m_selectedInstance) return; diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index bdd4a1890..f3f2de730 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -165,9 +165,7 @@ class MainWindow : public QMainWindow { void on_actionEditInstance_triggered(); - inline void on_actionCreateInstanceShortcut_triggered() { - on_actionCreateInstanceShortcutDesktop_triggered(); - }; + void on_actionCreateInstanceShortcut_triggered(); void on_actionCreateInstanceShortcutDesktop_triggered(); void on_actionCreateInstanceShortcutApplications_triggered(); From cd3db28fceea38602979571aa254e7d7e6a34c2f Mon Sep 17 00:00:00 2001 From: sshcrack <34072808+sshcrack@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:52:44 +0200 Subject: [PATCH 008/171] fixed typo Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index e73a702f8..535089343 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1656,7 +1656,7 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered() { return; if(DesktopServices::isFlatpak()) - onactionCreateInstanceShortcutOther_triggered(); + on_actionCreateInstanceShortcutOther_triggered(); else on_actionCreateInstanceShortcutDesktop_triggered(); } From 7c60f375f35cebbbaaa72555e8195096cd14e9aa Mon Sep 17 00:00:00 2001 From: sshcrack <34072808+sshcrack@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:15:04 +0200 Subject: [PATCH 009/171] hide actions if not available Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 117 ++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 52 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 535089343..b6b85bb62 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -209,12 +209,25 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi exportInstanceMenu->addAction(ui->actionExportInstanceFlamePack); ui->actionExportInstance->setMenu(exportInstanceMenu); - auto shortcutInstanceMenu = new QMenu(this); - shortcutInstanceMenu->addAction(ui->actionCreateInstanceShortcutDesktop); - shortcutInstanceMenu->addAction(ui->actionCreateInstanceShortcutApplications); - shortcutInstanceMenu->addAction(ui->actionCreateInstanceShortcutOther); + QList shortcutActions = { ui->actionCreateInstanceShortcutOther }; + if (!DesktopServices::isFlatpak()) { + QString desktopDir = FS::getDesktopDir(); + QString applicationDir = FS::getApplicationsDir(); - ui->actionCreateInstanceShortcut->setMenu(shortcutInstanceMenu); + if(!applicationDir.isEmpty()) + shortcutActions.push_front(ui->actionCreateInstanceShortcutApplications); + + if(!desktopDir.isEmpty()) + shortcutActions.push_front(ui->actionCreateInstanceShortcutDesktop); + } + + if(shortcutActions.length() > 1) { + auto shortcutInstanceMenu = new QMenu(this); + + for(auto action : shortcutActions) + shortcutInstanceMenu->addAction(action); + ui->actionCreateInstanceShortcut->setMenu(shortcutInstanceMenu); + } } // hide, disable and show stuff @@ -242,11 +255,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi } ui->actionViewJavaFolder->setEnabled(BuildConfig.JAVA_DOWNLOADER_ENABLED); - - bool isFlatpak = DesktopServices::isFlatpak(); - - ui->actionCreateInstanceShortcutDesktop->setEnabled(isFlatpak); - ui->actionCreateInstanceShortcutApplications->setEnabled(isFlatpak); } // add the toolbar toggles to the view menu @@ -1503,8 +1511,9 @@ void MainWindow::on_actionKillInstance_triggered() } } -void MainWindow::createInstanceShortcut(QString shortcutFilePath) { - if(!m_selectedInstance) +void MainWindow::createInstanceShortcut(QString shortcutFilePath) +{ + if (!m_selectedInstance) return; QString appPath = QApplication::applicationFilePath(); @@ -1625,55 +1634,59 @@ void MainWindow::createInstanceShortcut(QString shortcutFilePath) { } } -void MainWindow::on_actionCreateInstanceShortcutOther_triggered() { - if (!m_selectedInstance) - return; - - QString defaultedDir = FS::getDesktopDir(); -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - QString extension = ".desktop"; -#elif defined(Q_OS_WINDOWS) - QString extension = ".lnk"; -#else - QString extension = ""; -#endif - - QString shortcutFilePath = FS::PathCombine(defaultedDir, FS::RemoveInvalidFilenameChars(m_selectedInstance->name()) + extension); - QFileDialog fileDialog; - // workaround to make sure the portal file dialog opens in the desktop directory - fileDialog.setDirectoryUrl(defaultedDir); - - shortcutFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), shortcutFilePath, tr("Desktop Entries") + " (*" + extension + ")"); - if (shortcutFilePath.isEmpty()) - return; // file dialog canceled by user - - createInstanceShortcut(shortcutFilePath); - QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!")); -} - -void MainWindow::on_actionCreateInstanceShortcut_triggered() { - if(!m_selectedInstance) +void MainWindow::on_actionCreateInstanceShortcutOther_triggered() +{ + if (!m_selectedInstance) return; - if(DesktopServices::isFlatpak()) + QString defaultedDir = FS::getDesktopDir(); +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + QString extension = ".desktop"; +#elif defined(Q_OS_WINDOWS) + QString extension = ".lnk"; +#else + QString extension = ""; +#endif + + QString shortcutFilePath = FS::PathCombine(defaultedDir, FS::RemoveInvalidFilenameChars(m_selectedInstance->name()) + extension); + QFileDialog fileDialog; + // workaround to make sure the portal file dialog opens in the desktop directory + fileDialog.setDirectoryUrl(defaultedDir); + + shortcutFilePath = + fileDialog.getSaveFileName(this, tr("Create Shortcut"), shortcutFilePath, tr("Desktop Entries") + " (*" + extension + ")"); + if (shortcutFilePath.isEmpty()) + return; // file dialog canceled by user + + createInstanceShortcut(shortcutFilePath); + QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!")); +} + +void MainWindow::on_actionCreateInstanceShortcut_triggered() +{ + if (!m_selectedInstance) + return; + + if (DesktopServices::isFlatpak()) on_actionCreateInstanceShortcutOther_triggered(); else on_actionCreateInstanceShortcutDesktop_triggered(); } -void MainWindow::on_actionCreateInstanceShortcutDesktop_triggered() { - if (!m_selectedInstance) - return; +void MainWindow::on_actionCreateInstanceShortcutDesktop_triggered() +{ + if (!m_selectedInstance) + return; - QString desktopDir = FS::getDesktopDir(); - if (desktopDir.isEmpty()) { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!")); - return; - } + QString desktopDir = FS::getDesktopDir(); + if (desktopDir.isEmpty()) { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!")); + return; + } - QString shortcutFilePath = FS::PathCombine(FS::getDesktopDir(), FS::RemoveInvalidFilenameChars(m_selectedInstance->name())); - createInstanceShortcut(shortcutFilePath); - QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!")); + QString shortcutFilePath = FS::PathCombine(FS::getDesktopDir(), FS::RemoveInvalidFilenameChars(m_selectedInstance->name())); + createInstanceShortcut(shortcutFilePath); + QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!")); } void MainWindow::on_actionCreateInstanceShortcutApplications_triggered() From 8bb35f5b0b517177cb9869ee9ba60265e010e7a6 Mon Sep 17 00:00:00 2001 From: sshcrack <34072808+sshcrack@users.noreply.github.com> Date: Sat, 7 Dec 2024 19:43:09 +0100 Subject: [PATCH 010/171] add macos support for shortcuts Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com> --- launcher/FileSystem.cpp | 11 +---------- launcher/ui/MainWindow.cpp | 12 ++++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 8f683a9fb..a02a0d642 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -931,16 +931,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri return false; } #if defined(Q_OS_MACOS) - // Create the Application - QDir applicationDirectory = - QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/" + BuildConfig.LAUNCHER_NAME + " Instances/"; - - if (!applicationDirectory.mkpath(".")) { - qWarning() << "Couldn't create application directory"; - return false; - } - - QDir application = applicationDirectory.path() + "/" + name + ".app/"; + QDir application = destination + ".app/"; if (application.exists()) { qWarning() << "Application already exists!"; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index b6b85bb62..70659741e 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1658,6 +1658,8 @@ void MainWindow::on_actionCreateInstanceShortcutOther_triggered() if (shortcutFilePath.isEmpty()) return; // file dialog canceled by user + if(shortcutFilePath.endsWith(extension)) + shortcutFilePath = shortcutFilePath.mid(0, shortcutFilePath.length() - extension.length()); createInstanceShortcut(shortcutFilePath); QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!")); } @@ -1700,6 +1702,16 @@ void MainWindow::on_actionCreateInstanceShortcutApplications_triggered() return; } +#if defined(Q_OS_MACOS) || defined(Q_OS_WIN) + applicationsDir = FS::PathCombine(applicationsDir, BuildConfig.LAUNCHER_DISPLAYNAME + " Instances"); + + QDir applicationsDirQ(applicationsDir); + if (!applicationsDirQ.mkpath(".")) { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instances folder in applications folder!")); + return; + } +#endif + QString shortcutFilePath = FS::PathCombine(applicationsDir, FS::RemoveInvalidFilenameChars(m_selectedInstance->name())); createInstanceShortcut(shortcutFilePath); QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance in your applications folder!")); From c4ba7fc40159f74a10b8cff272727b5af98ed5bd Mon Sep 17 00:00:00 2001 From: sshcrack <34072808+sshcrack@users.noreply.github.com> Date: Sat, 7 Dec 2024 20:06:52 +0100 Subject: [PATCH 011/171] add newline back Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com> --- launcher/ui/pages/global/LauncherPage.cpp | 2 +- launcher/ui/pages/global/LauncherPage.ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index da4ba9023..8bbed9643 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -369,4 +369,4 @@ void LauncherPage::refreshFontPreview() void LauncherPage::retranslate() { ui->retranslateUi(this); -} \ No newline at end of file +} diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index ff95bdfbb..3cba468ff 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -668,4 +668,4 @@ - \ No newline at end of file + From 1cd4b4978908cf0a5745055638bea1c2b85c8acb Mon Sep 17 00:00:00 2001 From: sshcrack <34072808+sshcrack@users.noreply.github.com> Date: Sat, 7 Dec 2024 20:26:48 +0100 Subject: [PATCH 012/171] git is making me crazy at this point Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com> --- launcher/ui/pages/global/LauncherPage.cpp | 1 + launcher/ui/pages/global/LauncherPage.ui | 1 + 2 files changed, 2 insertions(+) diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 8bbed9643..154dddba5 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -370,3 +370,4 @@ void LauncherPage::retranslate() { ui->retranslateUi(this); } + diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 3cba468ff..acca897c4 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -669,3 +669,4 @@ + From c06f83507cde69b7b8dca1aada5a89419cf7050c Mon Sep 17 00:00:00 2001 From: sshcrack <34072808+sshcrack@users.noreply.github.com> Date: Sat, 7 Dec 2024 20:29:09 +0100 Subject: [PATCH 013/171] now its too much newlines?? Signed-off-by: sshcrack <34072808+sshcrack@users.noreply.github.com> --- launcher/ui/pages/global/LauncherPage.cpp | 1 - launcher/ui/pages/global/LauncherPage.ui | 1 - 2 files changed, 2 deletions(-) diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 154dddba5..8bbed9643 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -370,4 +370,3 @@ void LauncherPage::retranslate() { ui->retranslateUi(this); } - diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index acca897c4..3cba468ff 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -669,4 +669,3 @@ - From c9471d083b18de96c3f5095ac64367a7724bf2db Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 1 Feb 2025 20:25:57 +0200 Subject: [PATCH 014/171] change java on modpack update Signed-off-by: Trial97 --- launcher/BaseInstance.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index ccfd0b847..6c1c1a574 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -44,6 +44,7 @@ #include #include +#include "Application.h" #include "settings/INISettingsObject.h" #include "settings/OverrideSetting.h" #include "settings/Setting.h" @@ -174,6 +175,12 @@ void BaseInstance::copyManagedPack(BaseInstance& other) m_settings->set("ManagedPackName", other.getManagedPackName()); m_settings->set("ManagedPackVersionID", other.getManagedPackVersionID()); m_settings->set("ManagedPackVersionName", other.getManagedPackVersionName()); + + if (APPLICATION->settings()->get("AutomaticJavaSwitch").toBool() && m_settings->get("AutomaticJava").toBool() && + m_settings->get("OverrideJavaLocation").toBool()) { + m_settings->set("OverrideJavaLocation", false); + m_settings->set("JavaPath", ""); + } } int BaseInstance::getConsoleMaxLines() const From 58100328613bc7e1280ce0981f6a3ce66a34a2be Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 12 Feb 2025 19:04:48 +0200 Subject: [PATCH 015/171] Fix icon removal in icon picker Signed-off-by: Trial97 --- launcher/icons/IconList.cpp | 6 ++++-- launcher/ui/dialogs/IconPickerDialog.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index f4022e0fb..bdf57acaa 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -166,7 +166,8 @@ void IconList::directoryChanged(const QString& path) for (const MMCIcon& it : m_icons) { if (!it.has(IconType::FileBased)) continue; - currentSet.insert(it.m_images[IconType::FileBased].filename); + QFileInfo icon(it.getFilePath()); + currentSet.insert(icon.absoluteFilePath()); } QSet toRemove = currentSet - newSet; QSet toAdd = newSet - currentSet; @@ -174,7 +175,8 @@ void IconList::directoryChanged(const QString& path) for (const QString& removedPath : toRemove) { qDebug() << "Removing icon " << removedPath; QFileInfo removedFile(removedPath); - QString key = m_dir.relativeFilePath(removedFile.absoluteFilePath()); + QString relativePath = m_dir.relativeFilePath(removedFile.absoluteFilePath()); + QString key = QFileInfo(relativePath).completeBaseName(); int idx = getIconIndex(key); if (idx == -1) diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index b6e928a3d..8f53995f9 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -58,7 +58,7 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui contentsWidget->setTextElideMode(Qt::ElideRight); contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - contentsWidget->setItemDelegate(new ListViewDelegate()); + contentsWidget->setItemDelegate(new ListViewDelegate(contentsWidget)); // contentsWidget->setAcceptDrops(true); contentsWidget->setDropIndicatorShown(true); From a501441e6ee6a4bba3bbf9c0847b6ee9c0007197 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 17 Mar 2025 21:32:37 +0000 Subject: [PATCH 016/171] Paint project item backgrounds with native style Signed-off-by: TheKodeToad --- launcher/ui/widgets/ProjectItem.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 6946df41f..f313b58e8 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -2,6 +2,7 @@ #include "Common.h" +#include #include #include @@ -16,12 +17,12 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o auto rect = opt.rect; - if (opt.state & QStyle::State_Selected) { - painter->fillRect(rect, opt.palette.highlight()); + const QStyle* style = opt.widget == nullptr ? QApplication::style() : opt.widget->style(); + + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); + + if (option.state & QStyle::State_Selected) painter->setPen(opt.palette.highlightedText().color()); - } else if (opt.state & QStyle::State_MouseOver) { - painter->fillRect(rect, opt.palette.window()); - } // The default icon size will be a square (and height is usually the lower value). auto icon_width = rect.height(), icon_height = rect.height(); From 5f70335a07f8502f49a965f93f77fff49fd1bf1f Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 18 Mar 2025 19:44:13 +0000 Subject: [PATCH 017/171] Render checkbox in project items Signed-off-by: TheKodeToad --- .../ui/pages/modplatform/ResourceModel.cpp | 2 + launcher/ui/widgets/ProjectItem.cpp | 46 +++++++++++-------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 6b8309fb7..b571ead0a 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -84,6 +84,8 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant return pack->description; case UserDataTypes::SELECTED: return pack->isAnyVersionSelected(); + case Qt::CheckStateRole: + return pack->isAnyVersionSelected() ? Qt::Checked : Qt::Unchecked; case UserDataTypes::INSTALLED: return this->isPackInstalled(pack); default: diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index f313b58e8..141a5191b 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -1,10 +1,11 @@ #include "ProjectItem.h" -#include "Common.h" - #include + +#include #include #include +#include "Common.h" ProjectItemDelegate::ProjectItemDelegate(QWidget* parent) : QStyledItemDelegate(parent) {} @@ -24,6 +25,27 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o if (option.state & QStyle::State_Selected) painter->setPen(opt.palette.highlightedText().color()); + if (opt.features & QStyleOptionViewItem::HasCheckIndicator) { + // 5px will be the typical margin with 48px icon size + // we don't want the checkbox to be all over the place + rect.translate(5, 0); + + QStyleOptionViewItem checkboxOpt = opt; + + checkboxOpt.state &= ~QStyle::State_HasFocus; + + if (checkboxOpt.checkState == Qt::Checked) + checkboxOpt.state |= QStyle::State_On; + + QRect checkboxRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &checkboxOpt, opt.widget); + checkboxOpt.rect = + QRect(rect.x(), rect.y() + (rect.height() / 2 - checkboxRect.height() / 2), checkboxRect.width(), checkboxRect.height()); + + style->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &checkboxOpt, painter, opt.widget); + + rect.setX(rect.x() + checkboxRect.width()); + } + // The default icon size will be a square (and height is usually the lower value). auto icon_width = rect.height(), icon_height = rect.height(); int icon_x_margin = (rect.height() - icon_width) / 2; @@ -43,6 +65,9 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o int x = rect.x() + icon_x_margin; int y = rect.y() + icon_y_margin; + if (opt.features & QStyleOptionViewItem::HasCheckIndicator) + rect.translate(icon_x_margin / 2, 0); + // Prevent 'scaling null pixmap' warnings if (icon_width > 0 && icon_height > 0) opt.icon.paint(painter, x, y, icon_width, icon_height); @@ -60,23 +85,6 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o painter->save(); auto font = opt.font; - if (index.data(UserDataTypes::SELECTED).toBool()) { - // Set nice font - font.setBold(true); - font.setUnderline(true); - } - if (index.data(UserDataTypes::INSTALLED).toBool()) { - auto hRect = opt.rect; - hRect.setX(hRect.x() + 1); - hRect.setY(hRect.y() + 1); - hRect.setHeight(hRect.height() - 2); - hRect.setWidth(hRect.width() - 2); - // Set nice font - font.setItalic(true); - font.setOverline(true); - painter->drawRect(hRect); - } - font.setPointSize(font.pointSize() + 2); painter->setFont(font); From 8577f58fe3b807c504656fdbaac2c64016e91f59 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 18 Mar 2025 20:44:01 +0000 Subject: [PATCH 018/171] Remove UserDataTypes::SELECTED Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ResourceModel.cpp | 3 --- launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp | 2 -- launcher/ui/pages/modplatform/flame/FlameModel.cpp | 2 -- launcher/ui/pages/modplatform/import_ftb/ListModel.cpp | 2 -- launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp | 2 -- launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp | 2 -- launcher/ui/pages/modplatform/technic/TechnicModel.cpp | 2 -- launcher/ui/widgets/ProjectItem.h | 1 - 8 files changed, 16 deletions(-) diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index b571ead0a..7fcb6edb1 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -82,8 +82,6 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant return pack->name; case UserDataTypes::DESCRIPTION: return pack->description; - case UserDataTypes::SELECTED: - return pack->isAnyVersionSelected(); case Qt::CheckStateRole: return pack->isAnyVersionSelected() ? Qt::Checked : Qt::Unchecked; case UserDataTypes::INSTALLED: @@ -105,7 +103,6 @@ QHash ResourceModel::roleNames() const roles[Qt::UserRole] = "pack"; roles[UserDataTypes::TITLE] = "title"; roles[UserDataTypes::DESCRIPTION] = "description"; - roles[UserDataTypes::SELECTED] = "selected"; roles[UserDataTypes::INSTALLED] = "installed"; return roles; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp index f116ca915..e381f2a16 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp @@ -82,8 +82,6 @@ QVariant ListModel::data(const QModelIndex& index, int role) const return pack.name; case UserDataTypes::DESCRIPTION: return pack.description; - case UserDataTypes::SELECTED: - return false; case UserDataTypes::INSTALLED: return false; default: diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 18a2adc49..d501bf9f4 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -65,8 +65,6 @@ QVariant ListModel::data(const QModelIndex& index, int role) const return pack.name; case UserDataTypes::DESCRIPTION: return pack.description; - case UserDataTypes::SELECTED: - return false; case UserDataTypes::INSTALLED: return false; default: diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index f3c737977..eaec76a04 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -128,8 +128,6 @@ QVariant ListModel::data(const QModelIndex& index, int role) const return pack.name; case UserDataTypes::DESCRIPTION: return tr("Minecraft %1").arg(pack.mcVersion); - case UserDataTypes::SELECTED: - return false; case UserDataTypes::INSTALLED: return false; default: diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index 98922123c..4ddc6da5d 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -195,8 +195,6 @@ QVariant ListModel::data(const QModelIndex& index, int role) const return pack.name; case UserDataTypes::DESCRIPTION: return pack.description; - case UserDataTypes::SELECTED: - return false; case UserDataTypes::INSTALLED: return false; default: diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 416c69d28..4681b1a7f 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -106,8 +106,6 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian return pack.name; case UserDataTypes::DESCRIPTION: return pack.description; - case UserDataTypes::SELECTED: - return false; case UserDataTypes::INSTALLED: return false; default: diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp index f7e7f4433..c689ab0d2 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp @@ -89,8 +89,6 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const return pack.name; case UserDataTypes::DESCRIPTION: return pack.description; - case UserDataTypes::SELECTED: - return false; case UserDataTypes::INSTALLED: return false; default: diff --git a/launcher/ui/widgets/ProjectItem.h b/launcher/ui/widgets/ProjectItem.h index c3d0dce70..96e62af12 100644 --- a/launcher/ui/widgets/ProjectItem.h +++ b/launcher/ui/widgets/ProjectItem.h @@ -6,7 +6,6 @@ enum UserDataTypes { TITLE = 257, // QString DESCRIPTION = 258, // QString - SELECTED = 259, // bool INSTALLED = 260 // bool }; From 900579eea6f0732239b5dd1c7907fb8cb7ee3f7e Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 01:17:25 +0000 Subject: [PATCH 019/171] Handle checkbox toggle Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ModModel.cpp | 4 +- launcher/ui/pages/modplatform/ModModel.h | 4 +- launcher/ui/pages/modplatform/ModPage.cpp | 1 - launcher/ui/pages/modplatform/ModPage.h | 2 +- .../ui/pages/modplatform/ResourceModel.cpp | 6 +- launcher/ui/pages/modplatform/ResourceModel.h | 14 +-- .../pages/modplatform/ResourcePackModel.cpp | 4 +- .../ui/pages/modplatform/ResourcePackModel.h | 4 +- .../ui/pages/modplatform/ResourcePackPage.cpp | 4 +- .../ui/pages/modplatform/ResourcePackPage.h | 2 +- .../ui/pages/modplatform/ResourcePage.cpp | 113 +++++++++++++----- launcher/ui/pages/modplatform/ResourcePage.h | 7 +- .../ui/pages/modplatform/ShaderPackModel.cpp | 4 +- .../ui/pages/modplatform/ShaderPackModel.h | 4 +- .../ui/pages/modplatform/ShaderPackPage.cpp | 5 +- .../ui/pages/modplatform/ShaderPackPage.h | 2 +- .../ui/pages/modplatform/TexturePackModel.cpp | 2 +- .../ui/pages/modplatform/TexturePackModel.h | 2 +- .../ui/pages/modplatform/TexturePackPage.h | 7 +- .../modplatform/flame/FlameResourceModels.cpp | 2 +- .../modplatform/flame/FlameResourceModels.h | 2 +- launcher/ui/widgets/ProjectItem.cpp | 70 ++++++++--- launcher/ui/widgets/ProjectItem.h | 10 +- tests/ResourceModel_test.cpp | 4 +- 24 files changed, 185 insertions(+), 94 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index cfc262b62..6e98a88bc 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -44,7 +44,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments() }; } -ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) +ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(const QModelIndex& entry) { auto& pack = *m_packs[entry.row()]; auto profile = static_cast(m_base_instance).getPackProfile(); @@ -62,7 +62,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en return { pack, versions, loaders }; } -ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry) +ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(const QModelIndex& entry) { auto& pack = *m_packs[entry.row()]; return { pack }; diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index 5c994f373..bb9255cd0 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -39,8 +39,8 @@ class ModModel : public ResourceModel { public slots: ResourceAPI::SearchArgs createSearchArguments() override; - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; - ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; + ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) override; protected: auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index f0cc2df54..8b4919015 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -59,7 +59,6 @@ namespace ResourceDownload { ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) { connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); - connect(m_ui->packView, &QListView::doubleClicked, this, &ModPage::onResourceSelected); } void ModPage::setFilterWidget(unique_qobject_ptr& widget) diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 5c9a82303..397b77d94 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -35,7 +35,7 @@ class ModPage : public ResourcePage { page->setFilterWidget(filter_widget); model->setFilter(page->getFilter()); - connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); return page; diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 7fcb6edb1..8e3be5e8f 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -192,7 +192,7 @@ void ResourceModel::search() runSearchJob(job); } -void ResourceModel::loadEntry(QModelIndex& entry) +void ResourceModel::loadEntry(const QModelIndex& entry) { auto const& pack = m_packs[entry.row()]; @@ -503,7 +503,7 @@ void ResourceModel::versionRequestSucceeded(QJsonDocument& doc, ModPlatform::Ind return; } - emit versionListUpdated(); + emit versionListUpdated(index); } void ResourceModel::infoRequestSucceeded(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) @@ -530,7 +530,7 @@ void ResourceModel::infoRequestSucceeded(QJsonDocument& doc, ModPlatform::Indexe return; } - emit projectInfoUpdated(); + emit projectInfoUpdated(index); } void ResourceModel::addPack(ModPlatform::IndexedPack::Ptr pack, diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 4c7ea33a0..3f1e633ec 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -80,17 +80,17 @@ class ResourceModel : public QAbstractListModel { virtual ResourceAPI::SearchArgs createSearchArguments() = 0; virtual ResourceAPI::SearchCallbacks createSearchCallbacks() { return {}; } - virtual ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) = 0; - virtual ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(QModelIndex&) { return {}; } + virtual ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) = 0; + virtual ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(const QModelIndex&) { return {}; } - virtual ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) = 0; - virtual ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(QModelIndex&) { return {}; } + virtual ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) = 0; + virtual ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(const QModelIndex&) { return {}; } /** Requests the API for more entries. */ virtual void search(); /** Applies any processing / extra requests needed to fully load the specified entry's information. */ - virtual void loadEntry(QModelIndex&); + virtual void loadEntry(const QModelIndex&); /** Schedule a refresh, clearing the current state. */ void refresh(); @@ -170,8 +170,8 @@ class ResourceModel : public QAbstractListModel { void infoRequestSucceeded(QJsonDocument&, ModPlatform::IndexedPack&, const QModelIndex&); signals: - void versionListUpdated(); - void projectInfoUpdated(); + void versionListUpdated(const QModelIndex& index); + void projectInfoUpdated(const QModelIndex& index); }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.cpp b/launcher/ui/pages/modplatform/ResourcePackModel.cpp index d436f320f..0de980ed8 100644 --- a/launcher/ui/pages/modplatform/ResourcePackModel.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackModel.cpp @@ -20,13 +20,13 @@ ResourceAPI::SearchArgs ResourcePackResourceModel::createSearchArguments() return { ModPlatform::ResourceType::RESOURCE_PACK, m_next_search_offset, m_search_term, sort }; } -ResourceAPI::VersionSearchArgs ResourcePackResourceModel::createVersionsArguments(QModelIndex& entry) +ResourceAPI::VersionSearchArgs ResourcePackResourceModel::createVersionsArguments(const QModelIndex& entry) { auto& pack = m_packs[entry.row()]; return { *pack }; } -ResourceAPI::ProjectInfoArgs ResourcePackResourceModel::createInfoArguments(QModelIndex& entry) +ResourceAPI::ProjectInfoArgs ResourcePackResourceModel::createInfoArguments(const QModelIndex& entry) { auto& pack = m_packs[entry.row()]; return { *pack }; diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.h b/launcher/ui/pages/modplatform/ResourcePackModel.h index e2b4a1957..4f00808e8 100644 --- a/launcher/ui/pages/modplatform/ResourcePackModel.h +++ b/launcher/ui/pages/modplatform/ResourcePackModel.h @@ -31,8 +31,8 @@ class ResourcePackResourceModel : public ResourceModel { public slots: ResourceAPI::SearchArgs createSearchArguments() override; - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; - ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; + ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) override; protected: const BaseInstance& m_base_instance; diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.cpp b/launcher/ui/pages/modplatform/ResourcePackPage.cpp index 99039476e..8a7ed2720 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackPage.cpp @@ -14,9 +14,7 @@ namespace ResourceDownload { ResourcePackResourcePage::ResourcePackResourcePage(ResourceDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) -{ - connect(m_ui->packView, &QListView::doubleClicked, this, &ResourcePackResourcePage::onResourceSelected); -} +{} /******** Callbacks to events in the UI (set up in the derived classes) ********/ diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.h b/launcher/ui/pages/modplatform/ResourcePackPage.h index 440d91ab0..3f925fd23 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.h +++ b/launcher/ui/pages/modplatform/ResourcePackPage.h @@ -25,7 +25,7 @@ class ResourcePackResourcePage : public ResourcePage { auto page = new T(dialog, instance); auto model = static_cast(page->getModel()); - connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); return page; diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 2dd5ccf0f..407684f8b 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -78,10 +78,14 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in m_ui->verticalLayout->insertWidget(1, &m_fetchProgress); - m_ui->packView->setItemDelegate(new ProjectItemDelegate(this)); + auto delegate = new ProjectItemDelegate(this); + m_ui->packView->setItemDelegate(delegate); m_ui->packView->installEventFilter(this); connect(m_ui->packDescription, &QTextBrowser::anchorClicked, this, &ResourcePage::openUrl); + + connect(m_ui->packView, &QListView::doubleClicked, this, &ResourcePage::onToggle); + connect(delegate, &ProjectItemDelegate::checkboxClicked, this, &ResourcePage::onToggle); } ResourcePage::~ResourcePage() @@ -177,8 +181,11 @@ ModPlatform::IndexedPack::Ptr ResourcePage::getCurrentPack() const return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value(); } -void ResourcePage::updateUi() +void ResourcePage::updateUi(const QModelIndex& index) { + if (index != m_ui->packView->currentIndex()) + return; + auto current_pack = getCurrentPack(); if (!current_pack) { m_ui->packDescription->setHtml({}); @@ -268,39 +275,48 @@ void ResourcePage::updateSelectionButton() } } -void ResourcePage::updateVersionList() +void ResourcePage::versionListUpdated(const QModelIndex& index) { - auto current_pack = getCurrentPack(); + if (index == m_ui->packView->currentIndex()) { + auto current_pack = getCurrentPack(); - m_ui->versionSelectionBox->blockSignals(true); - m_ui->versionSelectionBox->clear(); - m_ui->versionSelectionBox->blockSignals(false); + m_ui->versionSelectionBox->blockSignals(true); + m_ui->versionSelectionBox->clear(); + m_ui->versionSelectionBox->blockSignals(false); - if (current_pack) { - auto installedVersion = m_model->getInstalledPackVersion(current_pack); + if (current_pack) { + auto installedVersion = m_model->getInstalledPackVersion(current_pack); - for (int i = 0; i < current_pack->versions.size(); i++) { - auto& version = current_pack->versions[i]; - if (!m_model->checkVersionFilters(version)) - continue; + for (int i = 0; i < current_pack->versions.size(); i++) { + auto& version = current_pack->versions[i]; + if (!m_model->checkVersionFilters(version)) + continue; - auto versionText = version.version; - if (version.version_type.isValid()) { - versionText += QString(" [%1]").arg(version.version_type.toString()); + auto versionText = version.version; + if (version.version_type.isValid()) { + versionText += QString(" [%1]").arg(version.version_type.toString()); + } + if (version.fileId == installedVersion) { + versionText += tr(" [installed]", "Mod version select"); + } + + m_ui->versionSelectionBox->addItem(versionText, QVariant(i)); } - if (version.fileId == installedVersion) { - versionText += tr(" [installed]", "Mod version select"); - } - - m_ui->versionSelectionBox->addItem(versionText, QVariant(i)); } - } - if (m_ui->versionSelectionBox->count() == 0) { - m_ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1)); - m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :(")); - } + if (m_ui->versionSelectionBox->count() == 0) { + m_ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1)); + m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :(")); + } - updateSelectionButton(); + if (m_enableQueue.contains(index.row())) { + m_enableQueue.remove(index.row()); + onToggle(index); + } else + updateSelectionButton(); + } else if (m_enableQueue.contains(index.row())) { + m_enableQueue.remove(index.row()); + onToggle(index); + } } void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev) @@ -318,16 +334,20 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI request_load = true; } else { - updateVersionList(); + versionListUpdated(curr); } if (current_pack && !current_pack->extraDataLoaded) request_load = true; + // we are already requesting this + if (m_enableQueue.contains(curr.row())) + request_load = false; + if (request_load) m_model->loadEntry(curr); - updateUi(); + updateUi(curr); } void ResourcePage::onVersionSelectionChanged(int index) @@ -385,6 +405,41 @@ void ResourcePage::onResourceSelected() m_ui->packView->repaint(); } +void ResourcePage::onToggle(const QModelIndex& index) +{ + const bool is_selected = index == m_ui->packView->currentIndex(); + auto pack = m_model->data(index, Qt::UserRole).value(); + + if (pack->versionsLoaded) { + if (pack->isAnyVersionSelected()) + removeResourceFromDialog(pack->name); + else { + auto version = std::find_if(pack->versions.begin(), pack->versions.end(), [this](const ModPlatform::IndexedVersion& version) { + return m_model->checkVersionFilters(version); + }); + + if (version != pack->versions.end()) + addResourceToDialog(pack, *version); + } + + if (is_selected) + updateSelectionButton(); + + // force update + QVariant variant; + variant.setValue(pack); + m_model->setData(index, variant, Qt::UserRole); + } else { + // the model is just 1 dimensional so this is fine + m_enableQueue.insert(index.row()); + + // we can't be sure that this hasn't already been requested... + // but this does the job well enough and there's not much point preventing edgecases + if (!is_selected) + m_model->loadEntry(index); + } +} + void ResourcePage::openUrl(const QUrl& url) { // do not allow other url schemes for security reasons diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 09c512df4..6a44dcf6d 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -71,9 +71,9 @@ class ResourcePage : public QWidget, public BasePage { void addSortings(); public slots: - virtual void updateUi(); + virtual void updateUi(const QModelIndex& index); virtual void updateSelectionButton(); - virtual void updateVersionList(); + virtual void versionListUpdated(const QModelIndex& index); void addResourceToDialog(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); void removeResourceFromDialog(const QString& pack_name); @@ -91,6 +91,7 @@ class ResourcePage : public QWidget, public BasePage { void onSelectionChanged(QModelIndex first, QModelIndex second); void onVersionSelectionChanged(int index); void onResourceSelected(); + void onToggle(const QModelIndex& index); // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 @@ -115,6 +116,8 @@ class ResourcePage : public QWidget, public BasePage { QTimer m_searchTimer; bool m_doNotJumpToMod = false; + + QSet m_enableQueue; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.cpp b/launcher/ui/pages/modplatform/ShaderPackModel.cpp index 8c913657a..efc6bfaf9 100644 --- a/launcher/ui/pages/modplatform/ShaderPackModel.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackModel.cpp @@ -20,13 +20,13 @@ ResourceAPI::SearchArgs ShaderPackResourceModel::createSearchArguments() return { ModPlatform::ResourceType::SHADER_PACK, m_next_search_offset, m_search_term, sort }; } -ResourceAPI::VersionSearchArgs ShaderPackResourceModel::createVersionsArguments(QModelIndex& entry) +ResourceAPI::VersionSearchArgs ShaderPackResourceModel::createVersionsArguments(const QModelIndex& entry) { auto& pack = m_packs[entry.row()]; return { *pack }; } -ResourceAPI::ProjectInfoArgs ShaderPackResourceModel::createInfoArguments(QModelIndex& entry) +ResourceAPI::ProjectInfoArgs ShaderPackResourceModel::createInfoArguments(const QModelIndex& entry) { auto& pack = m_packs[entry.row()]; return { *pack }; diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.h b/launcher/ui/pages/modplatform/ShaderPackModel.h index f3c695e9f..5bb9e58b1 100644 --- a/launcher/ui/pages/modplatform/ShaderPackModel.h +++ b/launcher/ui/pages/modplatform/ShaderPackModel.h @@ -31,8 +31,8 @@ class ShaderPackResourceModel : public ResourceModel { public slots: ResourceAPI::SearchArgs createSearchArguments() override; - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; - ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; + ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) override; protected: const BaseInstance& m_base_instance; diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp index 08acf361a..ace07db0e 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -15,10 +15,7 @@ namespace ResourceDownload { -ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) -{ - connect(m_ui->packView, &QListView::doubleClicked, this, &ShaderPackResourcePage::onResourceSelected); -} +ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) {} /******** Callbacks to events in the UI (set up in the derived classes) ********/ diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h index 4b92c33dc..68fdf6699 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.h +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -25,7 +25,7 @@ class ShaderPackResourcePage : public ResourcePage { auto page = new T(dialog, instance); auto model = static_cast(page->getModel()); - connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); return page; diff --git a/launcher/ui/pages/modplatform/TexturePackModel.cpp b/launcher/ui/pages/modplatform/TexturePackModel.cpp index cb4cafd41..d56f9334b 100644 --- a/launcher/ui/pages/modplatform/TexturePackModel.cpp +++ b/launcher/ui/pages/modplatform/TexturePackModel.cpp @@ -70,7 +70,7 @@ ResourceAPI::SearchArgs TexturePackResourceModel::createSearchArguments() return args; } -ResourceAPI::VersionSearchArgs TexturePackResourceModel::createVersionsArguments(QModelIndex& entry) +ResourceAPI::VersionSearchArgs TexturePackResourceModel::createVersionsArguments(const QModelIndex& entry) { auto args = ResourcePackResourceModel::createVersionsArguments(entry); if (!m_version_list->isLoaded()) { diff --git a/launcher/ui/pages/modplatform/TexturePackModel.h b/launcher/ui/pages/modplatform/TexturePackModel.h index 607a03be3..45b5734ee 100644 --- a/launcher/ui/pages/modplatform/TexturePackModel.h +++ b/launcher/ui/pages/modplatform/TexturePackModel.h @@ -18,7 +18,7 @@ class TexturePackResourceModel : public ResourcePackResourceModel { [[nodiscard]] inline ::Version maximumTexturePackVersion() const { return { "1.6" }; } ResourceAPI::SearchArgs createSearchArguments() override; - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; + ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; protected: Meta::VersionList::Ptr m_version_list; diff --git a/launcher/ui/pages/modplatform/TexturePackPage.h b/launcher/ui/pages/modplatform/TexturePackPage.h index 42aa921c5..393ccc21e 100644 --- a/launcher/ui/pages/modplatform/TexturePackPage.h +++ b/launcher/ui/pages/modplatform/TexturePackPage.h @@ -27,7 +27,7 @@ class TexturePackResourcePage : public ResourcePackResourcePage { auto page = new T(dialog, instance); auto model = static_cast(page->getModel()); - connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); return page; @@ -39,10 +39,7 @@ class TexturePackResourcePage : public ResourcePackResourcePage { [[nodiscard]] inline QString resourceString() const override { return tr("texture pack"); } protected: - TexturePackResourcePage(TexturePackDownloadDialog* dialog, BaseInstance& instance) : ResourcePackResourcePage(dialog, instance) - { - connect(m_ui->packView, &QListView::doubleClicked, this, &TexturePackResourcePage::onResourceSelected); - } + TexturePackResourcePage(TexturePackDownloadDialog* dialog, BaseInstance& instance) : ResourcePackResourcePage(dialog, instance) {} }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index ae4562be4..d2dc29dfd 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -122,7 +122,7 @@ ResourceAPI::SearchArgs FlameTexturePackModel::createSearchArguments() return args; } -ResourceAPI::VersionSearchArgs FlameTexturePackModel::createVersionsArguments(QModelIndex& entry) +ResourceAPI::VersionSearchArgs FlameTexturePackModel::createVersionsArguments(const QModelIndex& entry) { auto args = TexturePackResourceModel::createVersionsArguments(entry); diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index 458fd85d0..9b86a0944 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -69,7 +69,7 @@ class FlameTexturePackModel : public TexturePackResourceModel { void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; ResourceAPI::SearchArgs createSearchArguments() override; - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; + ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; }; diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 141a5191b..b20a3e013 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -16,34 +16,20 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o QStyleOptionViewItem opt(option); initStyleOption(&opt, index); - auto rect = opt.rect; - const QStyle* style = opt.widget == nullptr ? QApplication::style() : opt.widget->style(); + auto rect = opt.rect; + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); if (option.state & QStyle::State_Selected) painter->setPen(opt.palette.highlightedText().color()); if (opt.features & QStyleOptionViewItem::HasCheckIndicator) { - // 5px will be the typical margin with 48px icon size - // we don't want the checkbox to be all over the place - rect.translate(5, 0); - - QStyleOptionViewItem checkboxOpt = opt; - - checkboxOpt.state &= ~QStyle::State_HasFocus; - - if (checkboxOpt.checkState == Qt::Checked) - checkboxOpt.state |= QStyle::State_On; - - QRect checkboxRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &checkboxOpt, opt.widget); - checkboxOpt.rect = - QRect(rect.x(), rect.y() + (rect.height() / 2 - checkboxRect.height() / 2), checkboxRect.width(), checkboxRect.height()); - + QStyleOptionViewItem checkboxOpt = makeCheckboxStyleOption(opt, style); style->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &checkboxOpt, painter, opt.widget); - rect.setX(rect.x() + checkboxRect.width()); + rect.setX(checkboxOpt.rect.right()); } // The default icon size will be a square (and height is usually the lower value). @@ -141,3 +127,51 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o painter->restore(); } + +bool ProjectItemDelegate::editorEvent(QEvent* event, + QAbstractItemModel* model, + const QStyleOptionViewItem& option, + const QModelIndex& index) +{ + if (!(event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::MouseButtonDblClick)) + return false; + + auto mouseEvent = (QMouseEvent*)event; + + QStyleOptionViewItem opt(option); + initStyleOption(&opt, index); + + const QStyle* style = opt.widget == nullptr ? QApplication::style() : opt.widget->style(); + + const QStyleOptionViewItem checkboxOpt = makeCheckboxStyleOption(opt, style); + + if (!checkboxOpt.rect.contains(mouseEvent->x(), mouseEvent->y())) + return false; + + // swallow other events + // (prevents item being selected or double click action triggering) + if (event->type() != QEvent::MouseButtonRelease) + return true; + + emit checkboxClicked(index); + return true; +} + +QStyleOptionViewItem ProjectItemDelegate::makeCheckboxStyleOption(const QStyleOptionViewItem& opt, const QStyle* style) const +{ + QStyleOptionViewItem checkboxOpt = opt; + + checkboxOpt.state &= ~QStyle::State_HasFocus; + + if (checkboxOpt.checkState == Qt::Checked) + checkboxOpt.state |= QStyle::State_On; + + QRect checkboxRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &checkboxOpt, opt.widget); + // 5px is the typical top margin for image + // we don't want the checkboxes to be all over the place :) + checkboxOpt.rect = QRect(opt.rect.x() + 5, opt.rect.y() + (opt.rect.height() / 2 - checkboxRect.height() / 2), checkboxRect.width(), + checkboxRect.height()); + + return checkboxOpt; +} diff --git a/launcher/ui/widgets/ProjectItem.h b/launcher/ui/widgets/ProjectItem.h index 96e62af12..068358ade 100644 --- a/launcher/ui/widgets/ProjectItem.h +++ b/launcher/ui/widgets/ProjectItem.h @@ -6,7 +6,7 @@ enum UserDataTypes { TITLE = 257, // QString DESCRIPTION = 258, // QString - INSTALLED = 260 // bool + INSTALLED = 259 // bool }; /** This is an item delegate composed of: @@ -21,4 +21,12 @@ class ProjectItemDelegate final : public QStyledItemDelegate { ProjectItemDelegate(QWidget* parent); void paint(QPainter*, const QStyleOptionViewItem&, const QModelIndex&) const override; + + bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) override; + + signals: + void checkboxClicked(const QModelIndex& index); + + private: + QStyleOptionViewItem makeCheckboxStyleOption(const QStyleOptionViewItem& opt, const QStyle* style) const; }; diff --git a/tests/ResourceModel_test.cpp b/tests/ResourceModel_test.cpp index b589758aa..30bb99fb8 100644 --- a/tests/ResourceModel_test.cpp +++ b/tests/ResourceModel_test.cpp @@ -43,8 +43,8 @@ class DummyResourceModel : public ResourceModel { [[nodiscard]] auto metaEntryBase() const -> QString override { return ""; } ResourceAPI::SearchArgs createSearchArguments() override { return {}; } - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override { return {}; } - ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override { return {}; } + ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override { return {}; } + ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) override { return {}; } QJsonArray documentToArray(QJsonDocument& doc) const override { return doc.object().value("hits").toArray(); } From c6066fe6ecff2b2b374834894a7ab531ffa3b249 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 01:41:56 +0000 Subject: [PATCH 020/171] Add warning dialog if there are no versions available Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ResourcePage.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 407684f8b..1f2261e2c 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -418,7 +418,14 @@ void ResourcePage::onToggle(const QModelIndex& index) return m_model->checkVersionFilters(version); }); - if (version != pack->versions.end()) + if (version == pack->versions.end()) { + auto errorMessage = new QMessageBox( + QMessageBox::Warning, tr("No versions available"), + tr("No versions for '%1' are available.\nThe author likely blocked third-party launchers.").arg(pack->name), + QMessageBox::Ok, this); + + errorMessage->open(); + } else addResourceToDialog(pack, *version); } From a5c62e657aa95b50992b63d590e66d1a0d685c7c Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 01:43:43 +0000 Subject: [PATCH 021/171] Snek case Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ResourcePage.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 1f2261e2c..4e5de4933 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -407,7 +407,7 @@ void ResourcePage::onResourceSelected() void ResourcePage::onToggle(const QModelIndex& index) { - const bool is_selected = index == m_ui->packView->currentIndex(); + const bool isSelected = index == m_ui->packView->currentIndex(); auto pack = m_model->data(index, Qt::UserRole).value(); if (pack->versionsLoaded) { @@ -429,7 +429,7 @@ void ResourcePage::onToggle(const QModelIndex& index) addResourceToDialog(pack, *version); } - if (is_selected) + if (isSelected) updateSelectionButton(); // force update @@ -442,7 +442,7 @@ void ResourcePage::onToggle(const QModelIndex& index) // we can't be sure that this hasn't already been requested... // but this does the job well enough and there's not much point preventing edgecases - if (!is_selected) + if (!isSelected) m_model->loadEntry(index); } } From 75321722339ff6df854131623774a8e7420b6e76 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 10:31:55 +0000 Subject: [PATCH 022/171] Clear enableQueue on model reset Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ModPage.h | 1 + launcher/ui/pages/modplatform/ResourcePackPage.h | 1 + launcher/ui/pages/modplatform/ResourcePage.cpp | 5 +++++ launcher/ui/pages/modplatform/ResourcePage.h | 2 ++ launcher/ui/pages/modplatform/ShaderPackPage.h | 1 + launcher/ui/pages/modplatform/TexturePackPage.h | 1 + 6 files changed, 11 insertions(+) diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 397b77d94..47fe21e0f 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -37,6 +37,7 @@ class ModPage : public ResourcePage { connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + connect(model, &QAbstractListModel::modelReset, page, &ResourcePage::modelReset); return page; } diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.h b/launcher/ui/pages/modplatform/ResourcePackPage.h index 3f925fd23..8d967f73a 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.h +++ b/launcher/ui/pages/modplatform/ResourcePackPage.h @@ -27,6 +27,7 @@ class ResourcePackResourcePage : public ResourcePage { connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + connect(model, &QAbstractListModel::modelReset, page, &ResourcePage::modelReset); return page; } diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 4e5de4933..5acfec5da 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -374,6 +374,11 @@ void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, m_model->addPack(pack, ver, base_model, is_indexed); } +void ResourcePage::modelReset() +{ + m_enableQueue.clear(); +} + void ResourcePage::removeResourceFromPage(const QString& name) { m_model->removePack(name); diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 6a44dcf6d..23309333b 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -80,6 +80,8 @@ class ResourcePage : public QWidget, public BasePage { virtual void removeResourceFromPage(const QString& name); virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, std::shared_ptr); + virtual void modelReset(); + QList selectedPacks() { return m_model->selectedPacks(); } bool hasSelectedPacks() { return !(m_model->selectedPacks().isEmpty()); } diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h index 68fdf6699..d436e218a 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.h +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -27,6 +27,7 @@ class ShaderPackResourcePage : public ResourcePage { connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + connect(model, &QAbstractListModel::modelReset, page, &ResourcePage::modelReset); return page; } diff --git a/launcher/ui/pages/modplatform/TexturePackPage.h b/launcher/ui/pages/modplatform/TexturePackPage.h index 393ccc21e..27fd8bcfc 100644 --- a/launcher/ui/pages/modplatform/TexturePackPage.h +++ b/launcher/ui/pages/modplatform/TexturePackPage.h @@ -29,6 +29,7 @@ class TexturePackResourcePage : public ResourcePackResourcePage { connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + connect(model, &QAbstractListModel::modelReset, page, &ResourcePage::modelReset); return page; } From 0f3ac57fddc7a364e7313759cc3784ce01fa4818 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 10:42:38 +0000 Subject: [PATCH 023/171] Trigger onToggle instead of onResourceSelected when pressing enter Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ResourcePage.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 5acfec5da..07d296d48 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -132,13 +132,9 @@ auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool m_searchTimer.start(350); } } else if (watched == m_ui->packView) { + // stop the event from going to the confirm button if (keyEvent->key() == Qt::Key_Return) { - onResourceSelected(); - - // To have the 'select mod' button outlined instead of the 'review and confirm' one - m_ui->resourceSelectionButton->setFocus(Qt::FocusReason::ShortcutFocusReason); - m_ui->packView->setFocus(Qt::FocusReason::NoFocusReason); - + onToggle(m_ui->packView->currentIndex()); keyEvent->accept(); return true; } From 6c44a3f6dfb629682a3741336d1a83faa973250c Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 10:46:32 +0000 Subject: [PATCH 024/171] onToggle -> onResourceToggle Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ResourcePage.cpp | 12 ++++++------ launcher/ui/pages/modplatform/ResourcePage.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 07d296d48..153392075 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -84,8 +84,8 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in connect(m_ui->packDescription, &QTextBrowser::anchorClicked, this, &ResourcePage::openUrl); - connect(m_ui->packView, &QListView::doubleClicked, this, &ResourcePage::onToggle); - connect(delegate, &ProjectItemDelegate::checkboxClicked, this, &ResourcePage::onToggle); + connect(m_ui->packView, &QListView::doubleClicked, this, &ResourcePage::onResourceToggle); + connect(delegate, &ProjectItemDelegate::checkboxClicked, this, &ResourcePage::onResourceToggle); } ResourcePage::~ResourcePage() @@ -134,7 +134,7 @@ auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool } else if (watched == m_ui->packView) { // stop the event from going to the confirm button if (keyEvent->key() == Qt::Key_Return) { - onToggle(m_ui->packView->currentIndex()); + onResourceToggle(m_ui->packView->currentIndex()); keyEvent->accept(); return true; } @@ -306,12 +306,12 @@ void ResourcePage::versionListUpdated(const QModelIndex& index) if (m_enableQueue.contains(index.row())) { m_enableQueue.remove(index.row()); - onToggle(index); + onResourceToggle(index); } else updateSelectionButton(); } else if (m_enableQueue.contains(index.row())) { m_enableQueue.remove(index.row()); - onToggle(index); + onResourceToggle(index); } } @@ -406,7 +406,7 @@ void ResourcePage::onResourceSelected() m_ui->packView->repaint(); } -void ResourcePage::onToggle(const QModelIndex& index) +void ResourcePage::onResourceToggle(const QModelIndex& index) { const bool isSelected = index == m_ui->packView->currentIndex(); auto pack = m_model->data(index, Qt::UserRole).value(); diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 23309333b..055db441a 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -93,7 +93,7 @@ class ResourcePage : public QWidget, public BasePage { void onSelectionChanged(QModelIndex first, QModelIndex second); void onVersionSelectionChanged(int index); void onResourceSelected(); - void onToggle(const QModelIndex& index); + void onResourceToggle(const QModelIndex& index); // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 From 6dc16c48a2b1c6056fa06762f5d4a34602adaab5 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 10:56:07 +0000 Subject: [PATCH 025/171] Append [installed] to installed mods Signed-off-by: TheKodeToad --- launcher/ui/widgets/ProjectItem.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index b20a3e013..d1634591f 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -68,6 +68,9 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o { // Title painting auto title = index.data(UserDataTypes::TITLE).toString(); + if (index.data(UserDataTypes::INSTALLED).toBool()) + title += " [installed]"; + painter->save(); auto font = opt.font; From 9f768f76bbf309a80685f5870c5d881cfb7900df Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 11:00:51 +0000 Subject: [PATCH 026/171] Translate installed indicator Signed-off-by: TheKodeToad --- launcher/ui/widgets/ProjectItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index d1634591f..1224b853b 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -69,7 +69,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o auto title = index.data(UserDataTypes::TITLE).toString(); if (index.data(UserDataTypes::INSTALLED).toBool()) - title += " [installed]"; + title = tr("%1 [installed]").arg(title); painter->save(); From 5832fb8b95b8853c66ddea114e3b9f3a888e287b Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 11:09:25 +0000 Subject: [PATCH 027/171] Implement middle click Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ResourcePage.cpp | 8 ++++++++ launcher/ui/widgets/ProjectItem.cpp | 3 +++ 2 files changed, 11 insertions(+) diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 153392075..ea8e8d5e9 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -81,6 +81,7 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in auto delegate = new ProjectItemDelegate(this); m_ui->packView->setItemDelegate(delegate); m_ui->packView->installEventFilter(this); + m_ui->packView->viewport()->installEventFilter(this); connect(m_ui->packDescription, &QTextBrowser::anchorClicked, this, &ResourcePage::openUrl); @@ -139,6 +140,13 @@ auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool return true; } } + } else if (watched == m_ui->packView->viewport() && event->type() == QEvent::MouseButtonPress) { + auto* mouseEvent = static_cast(event); + + if (mouseEvent->button() == Qt::MiddleButton) { + onResourceToggle(m_ui->packView->indexAt(mouseEvent->pos())); + return true; + } } return QWidget::eventFilter(watched, event); diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 1224b853b..fee743c23 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -142,6 +142,9 @@ bool ProjectItemDelegate::editorEvent(QEvent* event, auto mouseEvent = (QMouseEvent*)event; + if (mouseEvent->button() != Qt::LeftButton) + return false; + QStyleOptionViewItem opt(option); initStyleOption(&opt, index); From e89f96e9e9087c6b49cceb8e7e710d85ca46d68d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 11:48:31 +0000 Subject: [PATCH 028/171] Hack for broken windowsvista Signed-off-by: TheKodeToad --- launcher/ui/widgets/ProjectItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index fee743c23..c11939b00 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -22,7 +22,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); - if (option.state & QStyle::State_Selected) + if (option.state & QStyle::State_Selected && style->objectName() != "windowsvista") painter->setPen(opt.palette.highlightedText().color()); if (opt.features & QStyleOptionViewItem::HasCheckIndicator) { From a108b5e9eb05ee0072fc02d2ab0ef482025a2820 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 12:35:24 +0000 Subject: [PATCH 029/171] Correctly set objectName for HintOverrideProxyStyle Signed-off-by: TheKodeToad --- launcher/ui/themes/HintOverrideProxyStyle.cpp | 4 ++++ launcher/ui/themes/HintOverrideProxyStyle.h | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/launcher/ui/themes/HintOverrideProxyStyle.cpp b/launcher/ui/themes/HintOverrideProxyStyle.cpp index 80e821349..f31969fce 100644 --- a/launcher/ui/themes/HintOverrideProxyStyle.cpp +++ b/launcher/ui/themes/HintOverrideProxyStyle.cpp @@ -18,6 +18,10 @@ #include "HintOverrideProxyStyle.h" +HintOverrideProxyStyle::HintOverrideProxyStyle(QStyle* style) : QProxyStyle(style) { + setObjectName(style->objectName()); +} + int HintOverrideProxyStyle::styleHint(QStyle::StyleHint hint, const QStyleOption* option, const QWidget* widget, diff --git a/launcher/ui/themes/HintOverrideProxyStyle.h b/launcher/ui/themes/HintOverrideProxyStyle.h index 09b296018..e9c489d09 100644 --- a/launcher/ui/themes/HintOverrideProxyStyle.h +++ b/launcher/ui/themes/HintOverrideProxyStyle.h @@ -25,7 +25,7 @@ class HintOverrideProxyStyle : public QProxyStyle { Q_OBJECT public: - HintOverrideProxyStyle(QStyle* style) : QProxyStyle(style) {} + explicit HintOverrideProxyStyle(QStyle* style); int styleHint(QStyle::StyleHint hint, const QStyleOption* option = nullptr, From 947ca679520d4656a9002aaed1ac63acfbc59f42 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 19 Mar 2025 13:32:14 +0000 Subject: [PATCH 030/171] Fix windows 9x and possibly other styles Signed-off-by: TheKodeToad --- launcher/ui/widgets/ProjectItem.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index c11939b00..950c5fe0a 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -172,6 +172,8 @@ QStyleOptionViewItem ProjectItemDelegate::makeCheckboxStyleOption(const QStyleOp if (checkboxOpt.checkState == Qt::Checked) checkboxOpt.state |= QStyle::State_On; + else + checkboxOpt.state |= QStyle::State_Off; QRect checkboxRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &checkboxOpt, opt.widget); // 5px is the typical top margin for image From b9a1fa36459c91d7aa4d6155f1ccced83214453f Mon Sep 17 00:00:00 2001 From: Soup <43444191+Soup-64@users.noreply.github.com> Date: Sat, 5 Apr 2025 22:26:58 -0400 Subject: [PATCH 031/171] Implement popup for metacache someone in the Discord ran into an issue somewhat related to the metacache button not working (folder in use err), so this warning makes it more obvious when this happens, though it would be better to find out why it ran into a process conflict Signed-off-by: Soup <43444191+Soup-64@users.noreply.github.com> --- launcher/FileSystem.cpp | 1 + launcher/net/HttpMetaCache.cpp | 6 ++++-- launcher/net/HttpMetaCache.h | 2 +- launcher/ui/MainWindow.cpp | 5 ++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 954e7936e..14b50768a 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -55,6 +55,7 @@ #include "DesktopServices.h" #include "PSaveFile.h" #include "StringUtils.h" +#include "ui/dialogs/CustomMessageBox.h" #if defined Q_OS_WIN32 #define NOMINMAX diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 4985ad080..f57aad1ba 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -166,8 +166,9 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool return true; } -void HttpMetaCache::evictAll() +bool HttpMetaCache::evictAll() { + bool ret; for (QString& base : m_entries.keys()) { EntryMap& map = m_entries[base]; qCDebug(taskHttpMetaCacheLogC) << "Evicting base" << base; @@ -176,8 +177,9 @@ void HttpMetaCache::evictAll() qCWarning(taskHttpMetaCacheLogC) << "Unexpected missing cache entry" << entry->m_basePath; } map.entry_list.clear(); - FS::deletePath(map.base_path); + ret = FS::deletePath(map.base_path); } + return ret; } auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h index 036a8dd94..144012ae5 100644 --- a/launcher/net/HttpMetaCache.h +++ b/launcher/net/HttpMetaCache.h @@ -113,7 +113,7 @@ class HttpMetaCache : public QObject { // evict selected entry from cache auto evictEntry(MetaEntryPtr entry) -> bool; - void evictAll(); + bool evictAll(); void addBase(QString base, QString base_root); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index ddf726373..2b43af2b8 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1318,7 +1318,10 @@ void MainWindow::on_actionReportBug_triggered() void MainWindow::on_actionClearMetadata_triggered() { - APPLICATION->metacache()->evictAll(); + if(!APPLICATION->metacache()->evictAll()){ + CustomMessageBox::selectable(this, tr("Error"), tr("Metadata cache clear Failed!\n To clear the metadata cache manually, press Folders -> View Launcher Root Folder, and after closing the launcher delete the folder named \"meta\"\n"), QMessageBox::Warning)->show(); + } + APPLICATION->metacache()->SaveNow(); } From 0c90530f8859125ea762b6460e180c32096e8987 Mon Sep 17 00:00:00 2001 From: Soup <43444191+Soup-64@users.noreply.github.com> Date: Sun, 6 Apr 2025 15:27:49 -0400 Subject: [PATCH 032/171] cleanup Fix formatting and fix a typo in the return code check Signed-off-by: Soup <43444191+Soup-64@users.noreply.github.com> --- launcher/net/HttpMetaCache.cpp | 8 +++++--- launcher/net/HttpMetaCache.h | 2 +- launcher/ui/MainWindow.cpp | 9 +++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index f57aad1ba..5a3a451b7 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -166,9 +166,10 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool return true; } -bool HttpMetaCache::evictAll() +//returns true on success, false otherwise +auto HttpMetaCache::evictAll() -> bool { - bool ret; + bool ret = true; for (QString& base : m_entries.keys()) { EntryMap& map = m_entries[base]; qCDebug(taskHttpMetaCacheLogC) << "Evicting base" << base; @@ -177,7 +178,8 @@ bool HttpMetaCache::evictAll() qCWarning(taskHttpMetaCacheLogC) << "Unexpected missing cache entry" << entry->m_basePath; } map.entry_list.clear(); - ret = FS::deletePath(map.base_path); + //AND all return codes together so the result is true iff all runs of deletePath() are true + ret &= FS::deletePath(map.base_path); } return ret; } diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h index 144012ae5..5db41259f 100644 --- a/launcher/net/HttpMetaCache.h +++ b/launcher/net/HttpMetaCache.h @@ -113,7 +113,7 @@ class HttpMetaCache : public QObject { // evict selected entry from cache auto evictEntry(MetaEntryPtr entry) -> bool; - bool evictAll(); + auto evictAll() -> bool; void addBase(QString base, QString base_root); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 2b43af2b8..887d89006 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1318,8 +1318,13 @@ void MainWindow::on_actionReportBug_triggered() void MainWindow::on_actionClearMetadata_triggered() { - if(!APPLICATION->metacache()->evictAll()){ - CustomMessageBox::selectable(this, tr("Error"), tr("Metadata cache clear Failed!\n To clear the metadata cache manually, press Folders -> View Launcher Root Folder, and after closing the launcher delete the folder named \"meta\"\n"), QMessageBox::Warning)->show(); + //This if contains side effects! + if (!APPLICATION->metacache()->evictAll()) { + CustomMessageBox::selectable(this, tr("Error"), + tr("Metadata cache clear Failed!\nTo clear the metadata cache manually, press Folders -> View " + "Launcher Root Folder, and after closing the launcher delete the folder named \"meta\"\n"), + QMessageBox::Warning) + ->show(); } APPLICATION->metacache()->SaveNow(); From 25d7db207d3ddaca7d812754453c1e5385a3f80e Mon Sep 17 00:00:00 2001 From: Soup of the tomato kind <43444191+Soup-64@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:15:02 -0400 Subject: [PATCH 033/171] Update FileSystem.cpp fix oopsie Signed-off-by: Soup of the tomato kind <43444191+Soup-64@users.noreply.github.com> --- launcher/FileSystem.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 14b50768a..954e7936e 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -55,7 +55,6 @@ #include "DesktopServices.h" #include "PSaveFile.h" #include "StringUtils.h" -#include "ui/dialogs/CustomMessageBox.h" #if defined Q_OS_WIN32 #define NOMINMAX From 9b3fa591d3d6d0d09a5a164bfcc6356ae74932a7 Mon Sep 17 00:00:00 2001 From: Soup of the tomato kind <43444191+Soup-64@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:24:09 -0400 Subject: [PATCH 034/171] Update launcher/net/HttpMetaCache.h Co-authored-by: Alexandru Ionut Tripon Signed-off-by: Soup of the tomato kind <43444191+Soup-64@users.noreply.github.com> --- launcher/net/HttpMetaCache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h index 5db41259f..144012ae5 100644 --- a/launcher/net/HttpMetaCache.h +++ b/launcher/net/HttpMetaCache.h @@ -113,7 +113,7 @@ class HttpMetaCache : public QObject { // evict selected entry from cache auto evictEntry(MetaEntryPtr entry) -> bool; - auto evictAll() -> bool; + bool evictAll(); void addBase(QString base, QString base_root); From cc69a59f368e3db90a1e63ff9025abcf4d3aaf90 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 11 Apr 2025 08:38:05 +0300 Subject: [PATCH 035/171] fix: crash when the instance window is closed before download dialog is open Signed-off-by: Trial97 --- launcher/ui/pages/instance/ModFolderPage.cpp | 19 +++++++++++-------- .../ui/pages/instance/ResourcePackPage.cpp | 18 +++++++++++------- launcher/ui/pages/instance/ShaderPackPage.cpp | 18 +++++++++++------- .../ui/pages/instance/TexturePackPage.cpp | 18 +++++++++++------- launcher/ui/widgets/ModFilterWidget.cpp | 1 - 5 files changed, 44 insertions(+), 30 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 95507ac22..026f0c140 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -145,9 +145,10 @@ void ModFolderPage::downloadMods() QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); return; } - - ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + if (mdownload->exec()) { auto tasks = new ConcurrentTask(tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -165,7 +166,7 @@ void ModFolderPage::downloadMods() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } @@ -300,9 +301,11 @@ void ModFolderPage::changeModVersion() if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr) return; - ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance); - mdownload.setResourceMetadata((*mods_list.begin())->metadata()); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + mdownload->setResourceMetadata((*mods_list.begin())->metadata()); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -320,7 +323,7 @@ void ModFolderPage::changeModVersion() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index 79e677765..ae5eb8fac 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -84,8 +84,10 @@ void ResourcePackPage::downloadResourcePacks() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - ResourceDownload::ResourcePackDownloadDialog mdownload(this, m_model, m_instance); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -103,7 +105,7 @@ void ResourcePackPage::downloadResourcePacks() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } @@ -235,9 +237,11 @@ void ResourcePackPage::changeResourcePackVersion() if (resource.metadata() == nullptr) return; - ResourceDownload::ResourcePackDownloadDialog mdownload(this, m_model, m_instance); - mdownload.setResourceMetadata(resource.metadata()); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + mdownload->setResourceMetadata(resource.metadata()); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -255,7 +259,7 @@ void ResourcePackPage::changeResourcePackVersion() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index a287d3edf..45bb02030 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -81,8 +81,10 @@ void ShaderPackPage::downloadShaderPack() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - ResourceDownload::ShaderPackDownloadDialog mdownload(this, m_model, m_instance); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -100,7 +102,7 @@ void ShaderPackPage::downloadShaderPack() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } @@ -232,9 +234,11 @@ void ShaderPackPage::changeShaderPackVersion() if (resource.metadata() == nullptr) return; - ResourceDownload::ShaderPackDownloadDialog mdownload(this, m_model, m_instance); - mdownload.setResourceMetadata(resource.metadata()); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + mdownload->setResourceMetadata(resource.metadata()); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -252,7 +256,7 @@ void ShaderPackPage::changeShaderPackVersion() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index fd1e0a2fc..6d000a486 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -90,8 +90,10 @@ void TexturePackPage::downloadTexturePacks() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - ResourceDownload::TexturePackDownloadDialog mdownload(this, m_model, m_instance); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -109,7 +111,7 @@ void TexturePackPage::downloadTexturePacks() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } @@ -241,9 +243,11 @@ void TexturePackPage::changeTexturePackVersion() if (resource.metadata() == nullptr) return; - ResourceDownload::TexturePackDownloadDialog mdownload(this, m_model, m_instance); - mdownload.setResourceMetadata(resource.metadata()); - if (mdownload.exec()) { + auto mdownload = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); + mdownload->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, mdownload, &QDialog::close); + mdownload->setResourceMetadata(resource.metadata()); + if (mdownload->exec()) { auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -261,7 +265,7 @@ void TexturePackPage::changeTexturePackVersion() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { + for (auto& task : mdownload->getTasks()) { tasks->addTask(task); } diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 0fda7933e..03522bc19 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -291,7 +291,6 @@ void ModFilterWidget::onSideFilterChanged() side = ""; } - m_filter_changed = side != m_filter->side; m_filter->side = side; if (m_filter_changed) From bd304eee947847b1d8630568933e5d95046d349c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 13 Apr 2025 12:16:07 -0700 Subject: [PATCH 036/171] chore: use nix-shell over nix develop in .envrc (brakes less things) Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .envrc | 2 +- flake.nix | 2 ++ shell.nix | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 shell.nix diff --git a/.envrc b/.envrc index 190b5b2b3..1d11c5354 100644 --- a/.envrc +++ b/.envrc @@ -1,2 +1,2 @@ -use flake +use nix watch_file nix/*.nix diff --git a/flake.nix b/flake.nix index fd3003bc4..594a82d91 100644 --- a/flake.nix +++ b/flake.nix @@ -132,6 +132,8 @@ { default = pkgs.mkShell { + name = "prism-launcher"; + inputsFrom = [ packages'.prismlauncher-unwrapped ]; packages = with pkgs; [ diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..21bab1009 --- /dev/null +++ b/shell.nix @@ -0,0 +1,4 @@ +(import (fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec.tar.gz"; + sha256 = "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU="; +}) { src = ./.; }).shellNix From 4ac6a0629b7c52aca9272cfbff8908d28b2b1763 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 15 Apr 2025 03:42:28 +0800 Subject: [PATCH 037/171] Use LogView to implement level highlighting for other logs Signed-off-by: Yihe Li --- launcher/InstancePageProvider.h | 2 +- launcher/ui/pages/instance/LogPage.cpp | 135 +++++++++---------- launcher/ui/pages/instance/LogPage.h | 14 +- launcher/ui/pages/instance/OtherLogsPage.cpp | 77 ++++++++--- launcher/ui/pages/instance/OtherLogsPage.h | 7 +- launcher/ui/pages/instance/OtherLogsPage.ui | 18 ++- 6 files changed, 158 insertions(+), 95 deletions(-) diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h index 1d7c193f8..acc7fce58 100644 --- a/launcher/InstancePageProvider.h +++ b/launcher/InstancePageProvider.h @@ -46,7 +46,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider { values.append(new InstanceSettingsPage(onesix)); auto logMatcher = inst->getLogFileMatcher(); if (logMatcher) { - values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher)); + values.append(new OtherLogsPage(inst, logMatcher)); } return values; } diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index 4962f90ce..f050212b0 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -52,90 +52,81 @@ #include -class LogFormatProxyModel : public QIdentityProxyModel { - public: - LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {} - QVariant data(const QModelIndex& index, int role) const override - { - const LogColors& colors = APPLICATION->themeManager()->getLogColors(); +QVariant LogFormatProxyModel::data(const QModelIndex& index, int role) const +{ + const LogColors& colors = APPLICATION->themeManager()->getLogColors(); - switch (role) { - case Qt::FontRole: - return m_font; - case Qt::ForegroundRole: { - auto level = static_cast(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt()); - QColor result = colors.foreground.value(level); + switch (role) { + case Qt::FontRole: + return m_font; + case Qt::ForegroundRole: { + auto level = static_cast(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt()); + QColor result = colors.foreground.value(level); - if (result.isValid()) - return result; + if (result.isValid()) + return result; - break; - } - case Qt::BackgroundRole: { - auto level = static_cast(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt()); - QColor result = colors.background.value(level); - - if (result.isValid()) - return result; - - break; - } + break; } + case Qt::BackgroundRole: { + auto level = static_cast(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt()); + QColor result = colors.background.value(level); - return QIdentityProxyModel::data(index, role); + if (result.isValid()) + return result; + + break; + } } - void setFont(QFont font) { m_font = font; } + return QIdentityProxyModel::data(index, role); +} - QModelIndex find(const QModelIndex& start, const QString& value, bool reverse) const - { - QModelIndex parentIndex = parent(start); - auto compare = [this, start, parentIndex, value](int r) -> QModelIndex { - QModelIndex idx = index(r, start.column(), parentIndex); - if (!idx.isValid() || idx == start) { - return QModelIndex(); - } - QVariant v = data(idx, Qt::DisplayRole); - QString t = v.toString(); - if (t.contains(value, Qt::CaseInsensitive)) - return idx; +QModelIndex LogFormatProxyModel::find(const QModelIndex& start, const QString& value, bool reverse) const +{ + QModelIndex parentIndex = parent(start); + auto compare = [this, start, parentIndex, value](int r) -> QModelIndex { + QModelIndex idx = index(r, start.column(), parentIndex); + if (!idx.isValid() || idx == start) { return QModelIndex(); - }; - if (reverse) { - int from = start.row(); - int to = 0; - - for (int i = 0; i < 2; ++i) { - for (int r = from; (r >= to); --r) { - auto idx = compare(r); - if (idx.isValid()) - return idx; - } - // prepare for the next iteration - from = rowCount() - 1; - to = start.row(); - } - } else { - int from = start.row(); - int to = rowCount(parentIndex); - - for (int i = 0; i < 2; ++i) { - for (int r = from; (r < to); ++r) { - auto idx = compare(r); - if (idx.isValid()) - return idx; - } - // prepare for the next iteration - from = 0; - to = start.row(); - } } + QVariant v = data(idx, Qt::DisplayRole); + QString t = v.toString(); + if (t.contains(value, Qt::CaseInsensitive)) + return idx; return QModelIndex(); - } + }; + if (reverse) { + int from = start.row(); + int to = 0; - private: - QFont m_font; -}; + for (int i = 0; i < 2; ++i) { + for (int r = from; (r >= to); --r) { + auto idx = compare(r); + if (idx.isValid()) + return idx; + } + // prepare for the next iteration + from = rowCount() - 1; + to = start.row(); + } + } else { + int from = start.row(); + int to = rowCount(parentIndex); + + for (int i = 0; i < 2; ++i) { + for (int r = from; (r < to); ++r) { + auto idx = compare(r); + if (idx.isValid()) + return idx; + } + // prepare for the next iteration + from = 0; + to = start.row(); + } + } + return QModelIndex(); +} LogPage::LogPage(InstancePtr instance, QWidget* parent) : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) { diff --git a/launcher/ui/pages/instance/LogPage.h b/launcher/ui/pages/instance/LogPage.h index 6c259891d..1295410ea 100644 --- a/launcher/ui/pages/instance/LogPage.h +++ b/launcher/ui/pages/instance/LogPage.h @@ -35,6 +35,7 @@ #pragma once +#include #include #include @@ -46,7 +47,18 @@ namespace Ui { class LogPage; } class QTextCharFormat; -class LogFormatProxyModel; + +class LogFormatProxyModel : public QIdentityProxyModel { + public: + LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {} + QVariant data(const QModelIndex& index, int role) const override; + QFont getFont() const { return m_font; } + void setFont(QFont font) { m_font = font; } + QModelIndex find(const QModelIndex& start, const QString& value, bool reverse) const; + + private: + QFont m_font; +}; class LogPage : public QWidget, public BasePage { Q_OBJECT diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index ed8ef68d9..6fa7cb4c1 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -46,12 +46,38 @@ #include #include "RecursiveFileSystemWatcher.h" -OtherLogsPage::OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget* parent) - : QWidget(parent), ui(new Ui::OtherLogsPage), m_path(path), m_fileFilter(fileFilter), m_watcher(new RecursiveFileSystemWatcher(this)) +OtherLogsPage::OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, QWidget* parent) + : QWidget(parent) + , ui(new Ui::OtherLogsPage) + , m_instance(instance) + , m_path(instance->getLogFileRoot()) + , m_fileFilter(fileFilter) + , m_watcher(new RecursiveFileSystemWatcher(this)) + , m_model(new LogModel()) { ui->setupUi(this); ui->tabWidget->tabBar()->hide(); + m_proxy = new LogFormatProxyModel(this); + + // set up fonts in the log proxy + { + QString fontFamily = APPLICATION->settings()->get("ConsoleFont").toString(); + bool conversionOk = false; + int fontSize = APPLICATION->settings()->get("ConsoleFontSize").toInt(&conversionOk); + if (!conversionOk) { + fontSize = 11; + } + m_proxy->setFont(QFont(fontFamily, fontSize)); + } + + ui->text->setModel(m_proxy); + + m_model->setMaxLines(m_instance->getConsoleMaxLines()); + m_model->setStopOnOverflow(m_instance->shouldStopOnConsoleOverflow()); + m_model->setOverflowMessage(tr("Cannot display this log since the log length surpassed %1 lines.").arg(m_model->getMaxLines())); + m_proxy->setSourceModel(m_model.get()); + m_watcher->setMatcher(fileFilter); m_watcher->setRootDir(QDir::current().absoluteFilePath(m_path)); @@ -139,14 +165,8 @@ void OtherLogsPage::on_btnReload_clicked() QMessageBox::critical(this, tr("Error"), tr("Unable to open %1 for reading: %2").arg(m_currentFile, file.errorString())); } else { auto setPlainText = [this](const QString& text) { - QString fontFamily = APPLICATION->settings()->get("ConsoleFont").toString(); - bool conversionOk = false; - int fontSize = APPLICATION->settings()->get("ConsoleFontSize").toInt(&conversionOk); - if (!conversionOk) { - fontSize = 11; - } QTextDocument* doc = ui->text->document(); - doc->setDefaultFont(QFont(fontFamily, fontSize)); + doc->setDefaultFont(m_proxy->getFont()); ui->text->setPlainText(text); }; auto showTooBig = [setPlainText, &file]() { @@ -173,7 +193,32 @@ void OtherLogsPage::on_btnReload_clicked() showTooBig(); return; } - setPlainText(content); + + // If the file is not too big for display, but too slow for syntax highlighting, just show content as plain text + if (content.size() >= 10000000ll || content.isEmpty()) { + setPlainText(content); + return; + } + + // Try to determine a level for each line + if (content.back() == '\n') + content = content.removeLast(); + for (auto& line : content.split('\n')) { + MessageLevel::Enum level = MessageLevel::Unknown; + + // if the launcher part set a log level, use it + auto innerLevel = MessageLevel::fromLine(line); + if (innerLevel != MessageLevel::Unknown) { + level = innerLevel; + } + + // If the level is still undetermined, guess level + if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) { + level = m_instance->guessLevel(line, level); + } + + m_model->append(level, line); + } } } @@ -273,27 +318,21 @@ void OtherLogsPage::setControlsEnabled(const bool enabled) ui->btnClean->setEnabled(enabled); } -// FIXME: HACK, use LogView instead? -static void findNext(QPlainTextEdit* _this, const QString& what, bool reverse) -{ - _this->find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); -} - void OtherLogsPage::on_findButton_clicked() { auto modifiers = QApplication::keyboardModifiers(); bool reverse = modifiers & Qt::ShiftModifier; - findNext(ui->text, ui->searchBar->text(), reverse); + ui->text->findNext(ui->searchBar->text(), reverse); } void OtherLogsPage::findNextActivated() { - findNext(ui->text, ui->searchBar->text(), false); + ui->text->findNext(ui->searchBar->text(), false); } void OtherLogsPage::findPreviousActivated() { - findNext(ui->text, ui->searchBar->text(), true); + ui->text->findNext(ui->searchBar->text(), true); } void OtherLogsPage::findActivated() diff --git a/launcher/ui/pages/instance/OtherLogsPage.h b/launcher/ui/pages/instance/OtherLogsPage.h index 85a3a2dbc..d65ed6456 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.h +++ b/launcher/ui/pages/instance/OtherLogsPage.h @@ -39,6 +39,7 @@ #include #include +#include "LogPage.h" #include "ui/pages/BasePage.h" namespace Ui { @@ -51,7 +52,7 @@ class OtherLogsPage : public QWidget, public BasePage { Q_OBJECT public: - explicit OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget* parent = 0); + explicit OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, QWidget* parent = 0); ~OtherLogsPage(); QString id() const override { return "logs"; } @@ -82,8 +83,12 @@ class OtherLogsPage : public QWidget, public BasePage { private: Ui::OtherLogsPage* ui; + InstancePtr m_instance; QString m_path; QString m_currentFile; IPathMatcher::Ptr m_fileFilter; RecursiveFileSystemWatcher* m_watcher; + + LogFormatProxyModel* m_proxy; + shared_qobject_ptr m_model; }; diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index 3fdb023fe..de3091917 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -44,16 +44,25 @@ - + false + + false + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + false + @@ -130,6 +139,13 @@ + + + LogView + QPlainTextEdit +
ui/widgets/LogView.h
+
+
tabWidget selectLogBox From 1ee1bab067d94035b01c76c8728b3d4e09b702ef Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 15 Apr 2025 05:07:56 +0800 Subject: [PATCH 038/171] Add color lines button Signed-off-by: Yihe Li --- launcher/launch/LogModel.cpp | 12 ++++++++++++ launcher/launch/LogModel.h | 3 +++ launcher/ui/pages/instance/LogPage.cpp | 16 ++++++++++++++++ launcher/ui/pages/instance/LogPage.h | 1 + launcher/ui/pages/instance/LogPage.ui | 11 +++++++++++ launcher/ui/widgets/LogView.cpp | 12 ++++++++++-- launcher/ui/widgets/LogView.h | 2 ++ 7 files changed, 55 insertions(+), 2 deletions(-) diff --git a/launcher/launch/LogModel.cpp b/launcher/launch/LogModel.cpp index 23a33ae18..dd32d46a2 100644 --- a/launcher/launch/LogModel.cpp +++ b/launcher/launch/LogModel.cpp @@ -149,3 +149,15 @@ bool LogModel::wrapLines() const { return m_lineWrap; } + +void LogModel::setColorLines(bool state) +{ + if (m_colorLines != state) { + m_colorLines = state; + } +} + +bool LogModel::colorLines() const +{ + return m_colorLines; +} diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h index 167f74190..6c2a8cff3 100644 --- a/launcher/launch/LogModel.h +++ b/launcher/launch/LogModel.h @@ -27,6 +27,8 @@ class LogModel : public QAbstractListModel { void setLineWrap(bool state); bool wrapLines() const; + void setColorLines(bool state); + bool colorLines() const; enum Roles { LevelRole = Qt::UserRole }; @@ -47,6 +49,7 @@ class LogModel : public QAbstractListModel { QString m_overflowMessage = "OVERFLOW"; bool m_suspended = false; bool m_lineWrap = true; + bool m_colorLines = true; private: Q_DISABLE_COPY(LogModel) diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index f050212b0..7897a2932 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -180,6 +180,13 @@ void LogPage::modelStateToUI() ui->text->setWordWrap(false); ui->wrapCheckbox->setCheckState(Qt::Unchecked); } + if (m_model->colorLines()) { + ui->text->setColorLines(true); + ui->colorCheckbox->setCheckState(Qt::Checked); + } else { + ui->text->setColorLines(false); + ui->colorCheckbox->setCheckState(Qt::Unchecked); + } if (m_model->suspended()) { ui->trackLogCheckbox->setCheckState(Qt::Unchecked); } else { @@ -193,6 +200,7 @@ void LogPage::UIToModelState() return; } m_model->setLineWrap(ui->wrapCheckbox->checkState() == Qt::Checked); + m_model->setColorLines(ui->colorCheckbox->checkState() == Qt::Checked); m_model->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked); } @@ -282,6 +290,14 @@ void LogPage::on_wrapCheckbox_clicked(bool checked) m_model->setLineWrap(checked); } +void LogPage::on_colorCheckbox_clicked(bool checked) +{ + ui->text->setColorLines(checked); + if (!m_model) + return; + m_model->setColorLines(checked); +} + void LogPage::on_findButton_clicked() { auto modifiers = QApplication::keyboardModifiers(); diff --git a/launcher/ui/pages/instance/LogPage.h b/launcher/ui/pages/instance/LogPage.h index 1295410ea..b4d74fb9c 100644 --- a/launcher/ui/pages/instance/LogPage.h +++ b/launcher/ui/pages/instance/LogPage.h @@ -82,6 +82,7 @@ class LogPage : public QWidget, public BasePage { void on_trackLogCheckbox_clicked(bool checked); void on_wrapCheckbox_clicked(bool checked); + void on_colorCheckbox_clicked(bool checked); void on_findButton_clicked(); void findActivated(); diff --git a/launcher/ui/pages/instance/LogPage.ui b/launcher/ui/pages/instance/LogPage.ui index 31bb368c8..fb8690581 100644 --- a/launcher/ui/pages/instance/LogPage.ui +++ b/launcher/ui/pages/instance/LogPage.ui @@ -74,6 +74,16 @@ + + + + Color lines + + + true + + + @@ -170,6 +180,7 @@ tabWidget trackLogCheckbox wrapCheckbox + colorCheckbox btnCopy btnPaste btnClear diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp index 6578b1f12..181893af4 100644 --- a/launcher/ui/widgets/LogView.cpp +++ b/launcher/ui/widgets/LogView.cpp @@ -60,6 +60,14 @@ void LogView::setWordWrap(bool wrapping) } } +void LogView::setColorLines(bool colorLines) +{ + if (m_colorLines == colorLines) + return; + m_colorLines = colorLines; + repopulate(); +} + void LogView::setModel(QAbstractItemModel* model) { if (m_model) { @@ -130,11 +138,11 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last) format.setFont(font.value()); } auto fg = m_model->data(idx, Qt::ForegroundRole); - if (fg.isValid()) { + if (fg.isValid() && m_colorLines) { format.setForeground(fg.value()); } auto bg = m_model->data(idx, Qt::BackgroundRole); - if (bg.isValid()) { + if (bg.isValid() && m_colorLines) { format.setBackground(bg.value()); } cursor.movePosition(QTextCursor::End); diff --git a/launcher/ui/widgets/LogView.h b/launcher/ui/widgets/LogView.h index dde5f8f76..69ca332bb 100644 --- a/launcher/ui/widgets/LogView.h +++ b/launcher/ui/widgets/LogView.h @@ -15,6 +15,7 @@ class LogView : public QPlainTextEdit { public slots: void setWordWrap(bool wrapping); + void setColorLines(bool colorLines); void findNext(const QString& what, bool reverse); void scrollToBottom(); @@ -32,4 +33,5 @@ class LogView : public QPlainTextEdit { QTextCharFormat* m_defaultFormat = nullptr; bool m_scroll = false; bool m_scrolling = false; + bool m_colorLines = true; }; From 5634723ecd7e966b4722b7f62692de18cbde631b Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 15 Apr 2025 06:00:49 +0800 Subject: [PATCH 039/171] Harmonizing other log controls with minecraft log Signed-off-by: Yihe Li --- launcher/ui/pages/instance/OtherLogsPage.cpp | 25 +++ launcher/ui/pages/instance/OtherLogsPage.h | 4 + launcher/ui/pages/instance/OtherLogsPage.ui | 173 +++++++++++++------ 3 files changed, 147 insertions(+), 55 deletions(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 6fa7cb4c1..40a5d9d94 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -203,6 +203,8 @@ void OtherLogsPage::on_btnReload_clicked() // Try to determine a level for each line if (content.back() == '\n') content = content.removeLast(); + ui->text->clear(); + m_model->clear(); for (auto& line : content.split('\n')) { MessageLevel::Enum level = MessageLevel::Unknown; @@ -232,6 +234,11 @@ void OtherLogsPage::on_btnCopy_clicked() GuiUtil::setClipboardText(ui->text->toPlainText()); } +void OtherLogsPage::on_btnBottom_clicked() +{ + ui->text->scrollToBottom(); +} + void OtherLogsPage::on_btnDelete_clicked() { if (m_currentFile.isEmpty()) { @@ -308,6 +315,24 @@ void OtherLogsPage::on_btnClean_clicked() } } +void OtherLogsPage::on_wrapCheckbox_clicked(bool checked) +{ + ui->text->setWordWrap(checked); + if (!m_model) + return; + m_model->setLineWrap(checked); + ui->text->scrollToBottom(); +} + +void OtherLogsPage::on_colorCheckbox_clicked(bool checked) +{ + ui->text->setColorLines(checked); + if (!m_model) + return; + m_model->setColorLines(checked); + ui->text->scrollToBottom(); +} + void OtherLogsPage::setControlsEnabled(const bool enabled) { ui->btnReload->setEnabled(enabled); diff --git a/launcher/ui/pages/instance/OtherLogsPage.h b/launcher/ui/pages/instance/OtherLogsPage.h index d65ed6456..9394ab9b8 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.h +++ b/launcher/ui/pages/instance/OtherLogsPage.h @@ -72,6 +72,10 @@ class OtherLogsPage : public QWidget, public BasePage { void on_btnCopy_clicked(); void on_btnDelete_clicked(); void on_btnClean_clicked(); + void on_btnBottom_clicked(); + + void on_wrapCheckbox_clicked(bool checked); + void on_colorCheckbox_clicked(bool checked); void on_findButton_clicked(); void findActivated(); diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index de3091917..ca700e103 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -33,17 +33,41 @@ Tab 1 + + + + Search: + + + - Find + &Find - + + + + Qt::Vertical + + + + + + + Scroll all the way to bottom + + + &Bottom + + + + false @@ -65,54 +89,98 @@ - + - - - - Copy the whole log into the clipboard - - - &Copy - - + + + + + + Delete the selected log + + + &Delete This + + + + + + + Delete all the logs + + + Delete &All + + + + - - - - Clear the log - - - Delete - - - - - - - Upload the log to the paste service configured in preferences. - - - Upload - - - - - - - Clear the log - - - Clean - - - - - - - Reload - - + + + + + + Wrap lines + + + true + + + + + + + Color lines + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Copy the whole log into the clipboard + + + &Copy + + + + + + + Upload the log to the paste service configured in preferences + + + Upload + + + + + + + Reload the contents of the log from the disk + + + &Reload + + + + @@ -126,13 +194,6 @@ - - - - Search: - - - @@ -154,6 +215,8 @@ btnPaste btnDelete btnClean + wrapCheckbox + colorCheckbox text searchBar findButton From de66fe4eda954165a9d00f4fb94aad562c70a21a Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 15 Apr 2025 06:16:47 +0800 Subject: [PATCH 040/171] Apparently removeLast() only comes in Qt 6.5+ Signed-off-by: Yihe Li --- launcher/ui/pages/instance/OtherLogsPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 40a5d9d94..2b4bcb59b 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -202,7 +202,7 @@ void OtherLogsPage::on_btnReload_clicked() // Try to determine a level for each line if (content.back() == '\n') - content = content.removeLast(); + content = content.remove(content.size() - 1, 1); ui->text->clear(); m_model->clear(); for (auto& line : content.split('\n')) { From 521302a96251b2f55de2f15736c5ff3093fa4e22 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 15 Apr 2025 20:22:21 +0800 Subject: [PATCH 041/171] Move delete buttons to the same line & set model to nullptr before adding lines Signed-off-by: Yihe Li --- launcher/ui/pages/instance/OtherLogsPage.cpp | 3 +++ launcher/ui/pages/instance/OtherLogsPage.ui | 28 ++++++++++---------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 2b4bcb59b..974118626 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -204,6 +204,7 @@ void OtherLogsPage::on_btnReload_clicked() if (content.back() == '\n') content = content.remove(content.size() - 1, 1); ui->text->clear(); + ui->text->setModel(nullptr); m_model->clear(); for (auto& line : content.split('\n')) { MessageLevel::Enum level = MessageLevel::Unknown; @@ -221,6 +222,8 @@ void OtherLogsPage::on_btnReload_clicked() m_model->append(level, line); } + ui->text->setModel(m_proxy); + ui->text->scrollToBottom(); } } diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index ca700e103..b4bb25b08 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -91,15 +91,25 @@ - + + + + + + 0 + 0 + + + + Delete the selected log - &Delete This + &Delete Selected @@ -115,7 +125,7 @@ - + @@ -166,7 +176,7 @@ Upload the log to the paste service configured in preferences - Upload + &Upload @@ -182,16 +192,6 @@ - - - - - 0 - 0 - - - - From be803b32791e4ed2b34867354df3f19293cff604 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 15 Apr 2025 15:27:47 +0100 Subject: [PATCH 042/171] Optimise guessLevel Signed-off-by: TheKodeToad --- launcher/minecraft/MinecraftInstance.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index d1780d497..b155e535a 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -36,6 +36,7 @@ */ #include "MinecraftInstance.h" +#include #include "Application.h" #include "BuildConfig.h" #include "QObjectPtr.h" @@ -1014,8 +1015,18 @@ QMap MinecraftInstance::createCensorFilterFromSession(AuthSess MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLevel::Enum level) { - QRegularExpression re("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); - auto match = re.match(line); + if (line.contains("overwriting existing")) + return MessageLevel::Fatal; + + // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * + static const QRegularExpression JAVA_EXCEPTION( + R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\\d_$]*\.)+[a-zA-Z_$][a-zA-Z\\d_$]*)"); + + if (line.contains(JAVA_EXCEPTION)) + return MessageLevel::Error; + + static const QRegularExpression LINE_WITH_LEVEL("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); + auto match = LINE_WITH_LEVEL.match(line); if (match.hasMatch()) { // New style logs from log4j QString timestamp = match.captured("timestamp"); @@ -1042,15 +1053,6 @@ MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLev if (line.contains("[DEBUG]")) level = MessageLevel::Debug; } - if (line.contains("overwriting existing")) - return MessageLevel::Fatal; - // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * - static const QString javaSymbol = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*"; - if (line.contains("Exception in thread") || line.contains(QRegularExpression("\\s+at " + javaSymbol)) || - line.contains(QRegularExpression("Caused by: " + javaSymbol)) || - line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)")) || - line.contains(QRegularExpression("... \\d+ more$"))) - return MessageLevel::Error; return level; } From ce76320b23282c678f64d92312af450d8635faa7 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 15 Apr 2025 15:58:11 +0100 Subject: [PATCH 043/171] Remove unnecessary import Signed-off-by: TheKodeToad --- launcher/minecraft/MinecraftInstance.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index b155e535a..7b02e93ba 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -36,7 +36,6 @@ */ #include "MinecraftInstance.h" -#include #include "Application.h" #include "BuildConfig.h" #include "QObjectPtr.h" From 29b81e7163019f45c77d668afe17794286011e24 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 15 Apr 2025 23:11:54 +0800 Subject: [PATCH 044/171] Set parent for LogModel Signed-off-by: Yihe Li --- launcher/ui/pages/instance/OtherLogsPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 974118626..0d94843c0 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -53,7 +53,7 @@ OtherLogsPage::OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, , m_path(instance->getLogFileRoot()) , m_fileFilter(fileFilter) , m_watcher(new RecursiveFileSystemWatcher(this)) - , m_model(new LogModel()) + , m_model(new LogModel(this)) { ui->setupUi(this); ui->tabWidget->tabBar()->hide(); From 5ce6ad604b06c3274f24bb49984f904c1fa5bdb2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 16 Apr 2025 16:52:45 +0300 Subject: [PATCH 045/171] chore: sync cmake version with the one used in the launcher Signed-off-by: Trial97 --- libraries/LocalPeer/CMakeLists.txt | 2 +- libraries/gamemode/CMakeLists.txt | 2 +- libraries/javacheck/CMakeLists.txt | 2 +- libraries/launcher/CMakeLists.txt | 2 +- libraries/murmur2/CMakeLists.txt | 2 +- libraries/qdcss/CMakeLists.txt | 2 +- libraries/rainbow/CMakeLists.txt | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/LocalPeer/CMakeLists.txt b/libraries/LocalPeer/CMakeLists.txt index b736cefcb..f6de6581c 100644 --- a/libraries/LocalPeer/CMakeLists.txt +++ b/libraries/LocalPeer/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(LocalPeer) if(QT_VERSION_MAJOR EQUAL 5) diff --git a/libraries/gamemode/CMakeLists.txt b/libraries/gamemode/CMakeLists.txt index 9e07f34ac..61195ac21 100644 --- a/libraries/gamemode/CMakeLists.txt +++ b/libraries/gamemode/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(gamemode VERSION 1.6.1) diff --git a/libraries/javacheck/CMakeLists.txt b/libraries/javacheck/CMakeLists.txt index fd545d2bc..b9bcb121a 100644 --- a/libraries/javacheck/CMakeLists.txt +++ b/libraries/javacheck/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(launcher Java) find_package(Java 1.7 REQUIRED COMPONENTS Development) diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt index 4cd1ba58b..dfc4ebb32 100644 --- a/libraries/launcher/CMakeLists.txt +++ b/libraries/launcher/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(launcher Java) find_package(Java 1.7 REQUIRED COMPONENTS Development) diff --git a/libraries/murmur2/CMakeLists.txt b/libraries/murmur2/CMakeLists.txt index f3068201d..be989ee36 100644 --- a/libraries/murmur2/CMakeLists.txt +++ b/libraries/murmur2/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(murmur2) set(MURMUR_SOURCES diff --git a/libraries/qdcss/CMakeLists.txt b/libraries/qdcss/CMakeLists.txt index 0afdef321..ab8aaef94 100644 --- a/libraries/qdcss/CMakeLists.txt +++ b/libraries/qdcss/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(qdcss) if(QT_VERSION_MAJOR EQUAL 5) diff --git a/libraries/rainbow/CMakeLists.txt b/libraries/rainbow/CMakeLists.txt index b6bbe7101..0867b2d27 100644 --- a/libraries/rainbow/CMakeLists.txt +++ b/libraries/rainbow/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.9.4) +cmake_minimum_required(VERSION 3.15) project(rainbow) if(QT_VERSION_MAJOR EQUAL 5) From c3f4735808c28e1db66b1dbacdad731fdf9cb465 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 16 Apr 2025 17:02:22 +0300 Subject: [PATCH 046/171] fix: compile warning regarding duplicate object name Signed-off-by: Trial97 --- launcher/ui/pages/instance/OtherLogsPage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index b4bb25b08..6d1a46139 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -126,7 +126,7 @@ - + From b70d9d6537bf530594a3c69644283b231bb2d5e2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 16 Apr 2025 17:56:01 +0300 Subject: [PATCH 047/171] chore: update submodules Signed-off-by: Trial97 --- flatpak/shared-modules | 2 +- libraries/extra-cmake-modules | 2 +- libraries/libnbtplusplus | 2 +- libraries/quazip | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flatpak/shared-modules b/flatpak/shared-modules index f5d368a31..1f8e591b2 160000 --- a/flatpak/shared-modules +++ b/flatpak/shared-modules @@ -1 +1 @@ -Subproject commit f5d368a31d6ef046eb2955c74ec6f54f32ed5c4e +Subproject commit 1f8e591b263eef8a0dc04929f2da135af59fac3c diff --git a/libraries/extra-cmake-modules b/libraries/extra-cmake-modules index a3d9394ab..1f820dc98 160000 --- a/libraries/extra-cmake-modules +++ b/libraries/extra-cmake-modules @@ -1 +1 @@ -Subproject commit a3d9394aba4b35789293378e04fb7473d65edf97 +Subproject commit 1f820dc98d0a520c175433bcbb0098327d82aac6 diff --git a/libraries/libnbtplusplus b/libraries/libnbtplusplus index 23b955121..531449ba1 160000 --- a/libraries/libnbtplusplus +++ b/libraries/libnbtplusplus @@ -1 +1 @@ -Subproject commit 23b955121b8217c1c348a9ed2483167a6f3ff4ad +Subproject commit 531449ba1c930c98e0bcf5d332b237a8566f9d78 diff --git a/libraries/quazip b/libraries/quazip index 8aeb3f7d8..3fd3b299b 160000 --- a/libraries/quazip +++ b/libraries/quazip @@ -1 +1 @@ -Subproject commit 8aeb3f7d8254f4bf1f7c6cf2a8f59c2ca141a552 +Subproject commit 3fd3b299b875fbd2beac4894b8a870d80022cad7 From 6e00f94a578838337f93d39c80d7f6edc0f031ea Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 17 Apr 2025 22:43:32 +0300 Subject: [PATCH 048/171] chore: rename varibales to match code standards Signed-off-by: Trial97 --- launcher/minecraft/World.cpp | 36 +++++++++++++-------------- launcher/minecraft/World.h | 6 ++--- launcher/minecraft/WorldList.cpp | 42 ++++++++++++++++---------------- launcher/minecraft/WorldList.h | 10 ++++---- 4 files changed, 47 insertions(+), 47 deletions(-) diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index bd28f9e9a..fc67fbb13 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -252,41 +252,41 @@ void World::readFromFS(const QFileInfo& file) { auto bytes = getLevelDatDataFromFS(file); if (bytes.isEmpty()) { - is_valid = false; + m_isValid = false; return; } loadFromLevelDat(bytes); - levelDatTime = file.lastModified(); + m_levelDatTime = file.lastModified(); } void World::readFromZip(const QFileInfo& file) { QuaZip zip(file.absoluteFilePath()); - is_valid = zip.open(QuaZip::mdUnzip); - if (!is_valid) { + m_isValid = zip.open(QuaZip::mdUnzip); + if (!m_isValid) { return; } auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); - is_valid = !location.isEmpty(); - if (!is_valid) { + m_isValid = !location.isEmpty(); + if (!m_isValid) { return; } m_containerOffsetPath = location; QuaZipFile zippedFile(&zip); // read the install profile - is_valid = zip.setCurrentFile(location + "level.dat"); - if (!is_valid) { + m_isValid = zip.setCurrentFile(location + "level.dat"); + if (!m_isValid) { return; } - is_valid = zippedFile.open(QIODevice::ReadOnly); + m_isValid = zippedFile.open(QIODevice::ReadOnly); QuaZipFileInfo64 levelDatInfo; zippedFile.getFileInfo(&levelDatInfo); auto modTime = levelDatInfo.getNTFSmTime(); if (!modTime.isValid()) { modTime = levelDatInfo.dateTime; } - levelDatTime = modTime; - if (!is_valid) { + m_levelDatTime = modTime; + if (!m_isValid) { return; } loadFromLevelDat(zippedFile.readAll()); @@ -430,7 +430,7 @@ void World::loadFromLevelDat(QByteArray data) { auto levelData = parseLevelDat(data); if (!levelData) { - is_valid = false; + m_isValid = false; return; } @@ -439,20 +439,20 @@ void World::loadFromLevelDat(QByteArray data) valPtr = &levelData->at("Data"); } catch (const std::out_of_range& e) { qWarning() << "Unable to read NBT tags from " << m_folderName << ":" << e.what(); - is_valid = false; + m_isValid = false; return; } nbt::value& val = *valPtr; - is_valid = val.get_type() == nbt::tag_type::Compound; - if (!is_valid) + m_isValid = val.get_type() == nbt::tag_type::Compound; + if (!m_isValid) return; auto name = read_string(val, "LevelName"); m_actualName = name ? *name : m_folderName; auto timestamp = read_long(val, "LastPlayed"); - m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : levelDatTime; + m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : m_levelDatTime; m_gameType = read_gametype(val, "GameType"); @@ -490,7 +490,7 @@ bool World::replace(World& with) bool World::destroy() { - if (!is_valid) + if (!m_isValid) return false; if (FS::trash(m_containerFile.filePath())) @@ -508,7 +508,7 @@ bool World::destroy() bool World::operator==(const World& other) const { - return is_valid == other.is_valid && folderName() == other.folderName(); + return m_isValid == other.m_isValid && folderName() == other.folderName(); } bool World::isSymLinkUnder(const QString& instPath) const diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h index 4303dc553..6a4e1f092 100644 --- a/launcher/minecraft/World.h +++ b/launcher/minecraft/World.h @@ -39,7 +39,7 @@ class World { QDateTime lastPlayed() const { return m_lastPlayed; } GameType gameType() const { return m_gameType; } int64_t seed() const { return m_randomSeed; } - bool isValid() const { return is_valid; } + bool isValid() const { return m_isValid; } bool isOnFS() const { return m_containerFile.isDir(); } QFileInfo container() const { return m_containerFile; } // delete all the files of this world @@ -83,10 +83,10 @@ class World { QString m_folderName; QString m_actualName; QString m_iconFile; - QDateTime levelDatTime; + QDateTime m_levelDatTime; QDateTime m_lastPlayed; int64_t m_size; int64_t m_randomSeed = 0; GameType m_gameType; - bool is_valid = false; + bool m_isValid = false; }; diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index cf27be676..f06eddb66 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -51,18 +51,18 @@ WorldList::WorldList(const QString& dir, BaseInstance* instance) : QAbstractList m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); m_watcher = new QFileSystemWatcher(this); - is_watching = false; + m_isWatching = false; connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &WorldList::directoryChanged); } void WorldList::startWatching() { - if (is_watching) { + if (m_isWatching) { return; } update(); - is_watching = m_watcher->addPath(m_dir.absolutePath()); - if (is_watching) { + m_isWatching = m_watcher->addPath(m_dir.absolutePath()); + if (m_isWatching) { qDebug() << "Started watching " << m_dir.absolutePath(); } else { qDebug() << "Failed to start watching " << m_dir.absolutePath(); @@ -71,11 +71,11 @@ void WorldList::startWatching() void WorldList::stopWatching() { - if (!is_watching) { + if (!m_isWatching) { return; } - is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if (!is_watching) { + m_isWatching = !m_watcher->removePath(m_dir.absolutePath()); + if (!m_isWatching) { qDebug() << "Stopped watching " << m_dir.absolutePath(); } else { qDebug() << "Failed to stop watching " << m_dir.absolutePath(); @@ -101,12 +101,12 @@ bool WorldList::update() } } beginResetModel(); - worlds.swap(newWorlds); + m_worlds.swap(newWorlds); endResetModel(); return true; } -void WorldList::directoryChanged(QString path) +void WorldList::directoryChanged(QString) { update(); } @@ -123,12 +123,12 @@ QString WorldList::instDirPath() const bool WorldList::deleteWorld(int index) { - if (index >= worlds.size() || index < 0) + if (index >= m_worlds.size() || index < 0) return false; - World& m = worlds[index]; + World& m = m_worlds[index]; if (m.destroy()) { beginRemoveRows(QModelIndex(), index, index); - worlds.removeAt(index); + m_worlds.removeAt(index); endRemoveRows(); emit changed(); return true; @@ -139,11 +139,11 @@ bool WorldList::deleteWorld(int index) bool WorldList::deleteWorlds(int first, int last) { for (int i = first; i <= last; i++) { - World& m = worlds[i]; + World& m = m_worlds[i]; m.destroy(); } beginRemoveRows(QModelIndex(), first, last); - worlds.erase(worlds.begin() + first, worlds.begin() + last + 1); + m_worlds.erase(m_worlds.begin() + first, m_worlds.begin() + last + 1); endRemoveRows(); emit changed(); return true; @@ -151,9 +151,9 @@ bool WorldList::deleteWorlds(int first, int last) bool WorldList::resetIcon(int row) { - if (row >= worlds.size() || row < 0) + if (row >= m_worlds.size() || row < 0) return false; - World& m = worlds[row]; + World& m = m_worlds[row]; if (m.resetIcon()) { emit dataChanged(index(row), index(row), { WorldList::IconFileRole }); return true; @@ -174,12 +174,12 @@ QVariant WorldList::data(const QModelIndex& index, int role) const int row = index.row(); int column = index.column(); - if (row < 0 || row >= worlds.size()) + if (row < 0 || row >= m_worlds.size()) return QVariant(); QLocale locale; - auto& world = worlds[row]; + auto& world = m_worlds[row]; switch (role) { case Qt::DisplayRole: switch (column) { @@ -339,9 +339,9 @@ QMimeData* WorldList::mimeData(const QModelIndexList& indexes) const if (idx.column() != 0) continue; int row = idx.row(); - if (row < 0 || row >= this->worlds.size()) + if (row < 0 || row >= this->m_worlds.size()) continue; - worlds_.append(this->worlds[row]); + worlds_.append(this->m_worlds[row]); } if (!worlds_.size()) { return new QMimeData(); @@ -393,7 +393,7 @@ bool WorldList::dropMimeData(const QMimeData* data, return false; // files dropped from outside? if (data->hasUrls()) { - bool was_watching = is_watching; + bool was_watching = m_isWatching; if (was_watching) stopWatching(); auto urls = data->urls(); diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h index bea24bb9a..45e2eecac 100644 --- a/launcher/minecraft/WorldList.h +++ b/launcher/minecraft/WorldList.h @@ -40,9 +40,9 @@ class WorldList : public QAbstractListModel { virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; virtual int columnCount(const QModelIndex& parent) const; - size_t size() const { return worlds.size(); }; + size_t size() const { return m_worlds.size(); }; bool empty() const { return size() == 0; } - World& operator[](size_t index) { return worlds[index]; } + World& operator[](size_t index) { return m_worlds[index]; } /// Reloads the mod list and returns true if the list changed. virtual bool update(); @@ -82,7 +82,7 @@ class WorldList : public QAbstractListModel { QString instDirPath() const; - const QList& allWorlds() const { return worlds; } + const QList& allWorlds() const { return m_worlds; } private slots: void directoryChanged(QString path); @@ -93,7 +93,7 @@ class WorldList : public QAbstractListModel { protected: BaseInstance* m_instance; QFileSystemWatcher* m_watcher; - bool is_watching; + bool m_isWatching; QDir m_dir; - QList worlds; + QList m_worlds; }; From efeaefbf2e50b06d7c09a20e384d63b94b1f0699 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 17 Apr 2025 23:31:25 +0300 Subject: [PATCH 049/171] fix: load world size async Signed-off-by: Trial97 --- launcher/minecraft/World.cpp | 22 ++++------------ launcher/minecraft/World.h | 2 ++ launcher/minecraft/WorldList.cpp | 44 +++++++++++++++++++++++++++++++- launcher/minecraft/WorldList.h | 1 + 4 files changed, 51 insertions(+), 18 deletions(-) diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index fc67fbb13..8ae097bad 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -198,22 +198,6 @@ bool putLevelDatDataToFS(const QFileInfo& file, QByteArray& data) return f.commit(); } -int64_t calculateWorldSize(const QFileInfo& file) -{ - if (file.isFile() && file.suffix() == "zip") { - return file.size(); - } else if (file.isDir()) { - QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories); - int64_t total = 0; - while (it.hasNext()) { - it.next(); - total += it.fileInfo().size(); - } - return total; - } - return -1; -} - World::World(const QFileInfo& file) { repath(file); @@ -223,7 +207,6 @@ void World::repath(const QFileInfo& file) { m_containerFile = file; m_folderName = file.fileName(); - m_size = calculateWorldSize(file); if (file.isFile() && file.suffix() == "zip") { m_iconFile = QString(); readFromZip(file); @@ -531,3 +514,8 @@ bool World::isMoreThanOneHardLink() const } return FS::hardLinkCount(m_containerFile.absoluteFilePath()) > 1; } + +void World::setSize(int64_t size) +{ + m_size = size; +} diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h index 6a4e1f092..34d418e79 100644 --- a/launcher/minecraft/World.h +++ b/launcher/minecraft/World.h @@ -54,6 +54,8 @@ class World { bool rename(const QString& to); bool install(const QString& to, const QString& name = QString()); + void setSize(int64_t size); + // WEAK compare operator - used for replacing worlds bool operator==(const World& other) const; diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index f06eddb66..5f192740e 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -37,13 +37,14 @@ #include #include +#include #include #include #include +#include #include #include #include -#include "Application.h" WorldList::WorldList(const QString& dir, BaseInstance* instance) : QAbstractListModel(), m_instance(instance), m_dir(dir) { @@ -103,6 +104,7 @@ bool WorldList::update() beginResetModel(); m_worlds.swap(newWorlds); endResetModel(); + loadWorldsAsync(); return true; } @@ -416,4 +418,44 @@ bool WorldList::dropMimeData(const QMimeData* data, return false; } +int64_t calculateWorldSize(const QFileInfo& file) +{ + if (file.isFile() && file.suffix() == "zip") { + return file.size(); + } else if (file.isDir()) { + QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories); + int64_t total = 0; + while (it.hasNext()) { + it.next(); + total += it.fileInfo().size(); + } + return total; + } + return -1; +} + +void WorldList::loadWorldsAsync() +{ + for (int i = 0; i < m_worlds.size(); ++i) { + auto file = m_worlds.at(i).container(); + int row = i; + QThreadPool::globalInstance()->start([this, file, row]() mutable { + auto size = calculateWorldSize(file); + + QMetaObject::invokeMethod( + this, + [this, size, row, file]() { + if (row < m_worlds.size() && m_worlds[row].container() == file) { + m_worlds[row].setSize(size); + + // Notify views + QModelIndex modelIndex = index(row); + emit dataChanged(modelIndex, modelIndex, { SizeRole }); + } + }, + Qt::QueuedConnection); + }); + } +} + #include "WorldList.moc" diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h index 45e2eecac..93fecf1f5 100644 --- a/launcher/minecraft/WorldList.h +++ b/launcher/minecraft/WorldList.h @@ -86,6 +86,7 @@ class WorldList : public QAbstractListModel { private slots: void directoryChanged(QString path); + void loadWorldsAsync(); signals: void changed(); From 8a3aafc2743a2137480a6adf7fc02b9d14eb16eb Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 17 Apr 2025 22:17:17 +0300 Subject: [PATCH 050/171] chore: remove qt5 from github actions Signed-off-by: Trial97 --- .github/workflows/build.yml | 28 +++++++--------------------- .github/workflows/codeql.yml | 25 ++++++++++++++++++------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 11ef262ea..5d5cbc893 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,13 +52,6 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-22.04 - qt_ver: 5 - qt_host: linux - qt_arch: "" - qt_version: "5.15.2" - qt_modules: "qtnetworkauth" - - os: ubuntu-22.04 qt_ver: 6 qt_host: linux @@ -254,7 +247,7 @@ jobs: arch: ${{ matrix.vcvars_arch }} - name: Prepare AppImage (Linux) - if: runner.os == 'Linux' && matrix.qt_ver != 5 + if: runner.os == 'Linux' env: APPIMAGEUPDATE_HASH: ${{ matrix.appimageupdate_hash }} LINUXDEPLOY_HASH: ${{ matrix.linuxdeploy_hash }} @@ -287,7 +280,7 @@ jobs: ## - name: Configure CMake (macOS) - if: runner.os == 'macOS' && matrix.qt_ver == 6 + if: runner.os == 'macOS' run: | cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja @@ -502,7 +495,7 @@ jobs: } - name: Package AppImage (Linux) - if: runner.os == 'Linux' && matrix.qt_ver != 5 + if: runner.os == 'Linux' shell: bash env: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} @@ -598,29 +591,22 @@ jobs: name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }} path: PrismLauncher-Setup.exe - - name: Upload binary tarball (Linux, portable, Qt 5) - if: runner.os == 'Linux' && matrix.qt_ver != 6 - uses: actions/upload-artifact@v4 - with: - name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }} - path: PrismLauncher-portable.tar.gz - - - name: Upload binary tarball (Linux, portable, Qt 6) - if: runner.os == 'Linux' && matrix.qt_ver != 5 + - name: Upload binary tarball (Linux, portable) + if: runner.os == 'Linux' uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }} path: PrismLauncher-portable.tar.gz - name: Upload AppImage (Linux) - if: runner.os == 'Linux' && matrix.qt_ver != 5 + if: runner.os == 'Linux' uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage - name: Upload AppImage Zsync (Linux) - if: runner.os == 'Linux' && matrix.qt_ver != 5 + if: runner.os == 'Linux' uses: actions/upload-artifact@v4 with: name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d1d810374..e3243097d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,16 +1,16 @@ name: "CodeQL Code Scanning" -on: [ push, pull_request, workflow_dispatch ] +on: [push, pull_request, workflow_dispatch] jobs: CodeQL: runs-on: ubuntu-latest - + steps: - name: Checkout repository uses: actions/checkout@v4 with: - submodules: 'true' + submodules: "true" - name: Initialize CodeQL uses: github/codeql-action/init@v3 @@ -20,14 +20,25 @@ jobs: languages: cpp, java - name: Install Dependencies - run: - sudo apt-get -y update + run: sudo apt-get -y update - sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 libqt5networkauth5 libqt5networkauth5-dev libqt5opengl5 libqt5opengl5-dev + sudo apt-get -y install ninja-build extra-cmake-modules scdoc + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + aqtversion: "==3.1.*" + py7zrversion: ">=0.20.2" + version: "6.8.1" + host: "linux" + target: "desktop" + arch: "" + modules: "qt5compat qtimageformats qtnetworkauth" + tools: "" - name: Configure and Build run: | - cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -DLauncher_QT_VERSION_MAJOR=5 -G Ninja + cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -G Ninja cmake --build build From 59bd6a915bba933ea2e9a61827b65a2c2f0d9c3e Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 10 Apr 2025 10:54:28 +0300 Subject: [PATCH 051/171] chore: remove qt5 from cmake files Signed-off-by: Trial97 --- CMakeLists.txt | 25 ++----------------------- libraries/LocalPeer/CMakeLists.txt | 4 +--- libraries/qdcss/CMakeLists.txt | 4 +--- libraries/rainbow/CMakeLists.txt | 4 +--- libraries/systeminfo/CMakeLists.txt | 4 +--- 5 files changed, 6 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 10153c3ec..e2ea079bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -310,23 +310,7 @@ endif() # Find the required Qt parts include(QtVersionlessBackport) -if(Launcher_QT_VERSION_MAJOR EQUAL 5) - set(QT_VERSION_MAJOR 5) - find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth OpenGL) - find_package(Qt5 COMPONENTS DBus) - list(APPEND Launcher_QT_DBUS Qt5::DBus) - - if(NOT Launcher_FORCE_BUNDLED_LIBS) - find_package(QuaZip-Qt5 1.3 QUIET) - endif() - if (NOT QuaZip-Qt5_FOUND) - set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE) - set(FORCE_BUNDLED_QUAZIP 1) - endif() - - # Qt 6 sets these by default. Notably causes Windows APIs to use UNICODE strings. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE") -elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) +if(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_VERSION_MAJOR 6) find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth OpenGL) find_package(Qt6 COMPONENTS DBus) @@ -344,12 +328,7 @@ else() message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported") endif() -if(Launcher_QT_VERSION_MAJOR EQUAL 5) - include(ECMQueryQt) - ecm_query_qt(QT_PLUGINS_DIR QT_INSTALL_PLUGINS) - ecm_query_qt(QT_LIBS_DIR QT_INSTALL_LIBS) - ecm_query_qt(QT_LIBEXECS_DIR QT_INSTALL_LIBEXECS) -else() +if(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_PLUGINS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_PLUGINS}) set(QT_LIBS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBS}) set(QT_LIBEXECS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBEXECS}) diff --git a/libraries/LocalPeer/CMakeLists.txt b/libraries/LocalPeer/CMakeLists.txt index f6de6581c..dd78647c0 100644 --- a/libraries/LocalPeer/CMakeLists.txt +++ b/libraries/LocalPeer/CMakeLists.txt @@ -1,9 +1,7 @@ cmake_minimum_required(VERSION 3.15) project(LocalPeer) -if(QT_VERSION_MAJOR EQUAL 5) - find_package(Qt5 COMPONENTS Core Network REQUIRED) -elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) +if(Launcher_QT_VERSION_MAJOR EQUAL 6) find_package(Qt6 COMPONENTS Core Network Core5Compat REQUIRED) list(APPEND LocalPeer_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) endif() diff --git a/libraries/qdcss/CMakeLists.txt b/libraries/qdcss/CMakeLists.txt index ab8aaef94..7e497feca 100644 --- a/libraries/qdcss/CMakeLists.txt +++ b/libraries/qdcss/CMakeLists.txt @@ -1,9 +1,7 @@ cmake_minimum_required(VERSION 3.15) project(qdcss) -if(QT_VERSION_MAJOR EQUAL 5) - find_package(Qt5 COMPONENTS Core REQUIRED) -elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) +if(Launcher_QT_VERSION_MAJOR EQUAL 6) find_package(Qt6 COMPONENTS Core Core5Compat REQUIRED) list(APPEND qdcss_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) endif() diff --git a/libraries/rainbow/CMakeLists.txt b/libraries/rainbow/CMakeLists.txt index 0867b2d27..c971889d8 100644 --- a/libraries/rainbow/CMakeLists.txt +++ b/libraries/rainbow/CMakeLists.txt @@ -1,9 +1,7 @@ cmake_minimum_required(VERSION 3.15) project(rainbow) -if(QT_VERSION_MAJOR EQUAL 5) - find_package(Qt5 COMPONENTS Core Gui REQUIRED) -elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) +if(Launcher_QT_VERSION_MAJOR EQUAL 6) find_package(Qt6 COMPONENTS Core Gui REQUIRED) endif() diff --git a/libraries/systeminfo/CMakeLists.txt b/libraries/systeminfo/CMakeLists.txt index 33d246050..80b6b8094 100644 --- a/libraries/systeminfo/CMakeLists.txt +++ b/libraries/systeminfo/CMakeLists.txt @@ -1,8 +1,6 @@ project(systeminfo) -if(QT_VERSION_MAJOR EQUAL 5) - find_package(Qt5 COMPONENTS Core REQUIRED) -elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) +if(Launcher_QT_VERSION_MAJOR EQUAL 6) find_package(Qt6 COMPONENTS Core Core5Compat REQUIRED) list(APPEND systeminfo_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) endif() From 442aae88ce793ac1e0d686f0839ad1f7ff46666b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 10 Apr 2025 12:03:50 +0300 Subject: [PATCH 052/171] chore: remove qt version checks from code Signed-off-by: Trial97 --- launcher/DataMigrationTask.cpp | 8 -------- launcher/FileIgnoreProxy.cpp | 4 ---- launcher/FileSystem.cpp | 12 ----------- launcher/InstanceList.cpp | 4 ---- launcher/Version.h | 8 -------- launcher/icons/IconList.cpp | 6 +----- launcher/java/JavaChecker.cpp | 8 -------- launcher/launch/steps/PostLaunchCommand.cpp | 4 ---- launcher/launch/steps/PreLaunchCommand.cpp | 4 ---- launcher/minecraft/MinecraftInstance.cpp | 8 -------- launcher/minecraft/PackProfile.cpp | 4 ---- launcher/minecraft/WorldList.cpp | 4 ---- launcher/minecraft/auth/MinecraftAccount.cpp | 9 --------- launcher/minecraft/auth/Parsers.cpp | 4 ---- launcher/minecraft/auth/steps/MSAStep.cpp | 7 +------ .../minecraft/mod/ResourceFolderModel.cpp | 5 ----- .../atlauncher/ATLPackInstallTask.cpp | 10 ---------- .../legacy_ftb/PackInstallTask.cpp | 5 ----- .../legacy_ftb/PrivatePackManager.cpp | 4 ---- launcher/net/NetRequest.cpp | 10 ---------- launcher/net/PasteUpload.cpp | 4 ---- launcher/ui/MainWindow.cpp | 4 ---- launcher/ui/dialogs/NewInstanceDialog.cpp | 4 ---- launcher/ui/instanceview/InstanceView.cpp | 20 ------------------- launcher/ui/instanceview/VisualGroup.cpp | 4 ---- launcher/ui/pages/instance/ServersPage.cpp | 8 -------- libraries/systeminfo/src/distroutils.cpp | 12 ----------- 27 files changed, 2 insertions(+), 182 deletions(-) diff --git a/launcher/DataMigrationTask.cpp b/launcher/DataMigrationTask.cpp index 92e310a16..18decc7c3 100644 --- a/launcher/DataMigrationTask.cpp +++ b/launcher/DataMigrationTask.cpp @@ -37,11 +37,7 @@ void DataMigrationTask::dryRunFinished() disconnect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &DataMigrationTask::dryRunFinished); disconnect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &DataMigrationTask::dryRunAborted); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) if (!m_copyFuture.isValid() || !m_copyFuture.result()) { -#else - if (!m_copyFuture.result()) { -#endif emitFailed(tr("Failed to scan source path.")); return; } @@ -75,11 +71,7 @@ void DataMigrationTask::copyFinished() disconnect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &DataMigrationTask::copyFinished); disconnect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &DataMigrationTask::copyAborted); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) if (!m_copyFuture.isValid() || !m_copyFuture.result()) { -#else - if (!m_copyFuture.result()) { -#endif emitFailed(tr("Some paths could not be copied!")); return; } diff --git a/launcher/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp index 89c91ec1d..0314057d1 100644 --- a/launcher/FileIgnoreProxy.cpp +++ b/launcher/FileIgnoreProxy.cpp @@ -282,11 +282,7 @@ void FileIgnoreProxy::loadBlockedPathsFromFile(const QString& fileName) } auto ignoreData = ignoreFile.readAll(); auto string = QString::fromUtf8(ignoreData); -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) setBlockedPaths(string.split('\n', Qt::SkipEmptyParts)); -#else - setBlockedPaths(string.split('\n', QString::SkipEmptyParts)); -#endif } void FileIgnoreProxy::saveBlockedPathsToFile(const QString& fileName) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 7189ca841..08dc7d2cc 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -679,9 +679,6 @@ bool deletePath(QString path) bool trash(QString path, QString* pathInTrash) { -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - return false; -#else // FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal if (DesktopServices::isFlatpak()) return false; @@ -690,7 +687,6 @@ bool trash(QString path, QString* pathInTrash) return false; #endif return QFile::moveToTrash(path, pathInTrash); -#endif } QString PathCombine(const QString& path1, const QString& path2) @@ -724,11 +720,7 @@ int pathDepth(const QString& path) QFileInfo info(path); -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - auto parts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), QString::SkipEmptyParts); -#else auto parts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), Qt::SkipEmptyParts); -#endif int numParts = parts.length(); numParts -= parts.count("."); @@ -748,11 +740,7 @@ QString pathTruncate(const QString& path, int depth) return pathTruncate(trunc, depth); } -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - auto parts = QDir::toNativeSeparators(trunc).split(QDir::separator(), QString::SkipEmptyParts); -#else auto parts = QDir::toNativeSeparators(trunc).split(QDir::separator(), Qt::SkipEmptyParts); -#endif if (parts.startsWith(".") && !path.startsWith(".")) { parts.removeFirst(); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 918fa1073..f76f8599a 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -449,11 +449,7 @@ QList InstanceList::discoverInstances() out.append(id); qDebug() << "Found instance ID" << id; } -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) instanceSet = QSet(out.begin(), out.end()); -#else - instanceSet = out.toSet(); -#endif m_instancesProbed = true; return out; } diff --git a/launcher/Version.h b/launcher/Version.h index b06e256aa..12e7f0832 100644 --- a/launcher/Version.h +++ b/launcher/Version.h @@ -72,22 +72,14 @@ class Version { } } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) auto numPart = QStringView{ m_fullString }.left(cutoff); -#else - auto numPart = m_fullString.leftRef(cutoff); -#endif if (!numPart.isEmpty()) { m_isNull = false; m_numPart = numPart.toInt(); } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) auto stringPart = QStringView{ m_fullString }.mid(cutoff); -#else - auto stringPart = m_fullString.midRef(cutoff); -#endif if (!stringPart.isEmpty()) { m_isNull = false; diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index 7369d8b4b..8a2a482e1 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -137,11 +137,7 @@ QString formatName(const QDir& iconsDir, const QFileInfo& iconFile) /// Split into a separate function because the preprocessing impedes readability QSet toStringSet(const QList& list) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QSet set(list.begin(), list.end()); -#else - QSet set = list.toSet(); -#endif return set; } @@ -477,4 +473,4 @@ QString IconList::iconDirectory(const QString& key) const } } return getDirectory(); -} +} \ No newline at end of file diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 07b5d7b40..0aa725705 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -137,11 +137,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) QMap results; -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QStringList lines = m_stdout.split("\n", Qt::SkipEmptyParts); -#else - QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts); -#endif for (QString line : lines) { line = line.trimmed(); // NOTE: workaround for GH-4125, where garbage is getting printed into stdout on bedrock linux @@ -149,11 +145,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) continue; } -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) auto parts = line.split('=', Qt::SkipEmptyParts); -#else - auto parts = line.split('=', QString::SkipEmptyParts); -#endif if (parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) { continue; } else { diff --git a/launcher/launch/steps/PostLaunchCommand.cpp b/launcher/launch/steps/PostLaunchCommand.cpp index 5d893c71f..6b960974e 100644 --- a/launcher/launch/steps/PostLaunchCommand.cpp +++ b/launcher/launch/steps/PostLaunchCommand.cpp @@ -49,14 +49,10 @@ void PostLaunchCommand::executeTask() { auto cmd = m_parent->substituteVariables(m_command); emit logLine(tr("Running Post-Launch command: %1").arg(cmd), MessageLevel::Launcher); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) auto args = QProcess::splitCommand(cmd); const QString program = args.takeFirst(); m_process.start(program, args); -#else - m_process.start(cmd); -#endif } void PostLaunchCommand::on_state(LoggedProcess::State state) diff --git a/launcher/launch/steps/PreLaunchCommand.cpp b/launcher/launch/steps/PreLaunchCommand.cpp index 318237e99..7e843ca3f 100644 --- a/launcher/launch/steps/PreLaunchCommand.cpp +++ b/launcher/launch/steps/PreLaunchCommand.cpp @@ -49,13 +49,9 @@ void PreLaunchCommand::executeTask() { auto cmd = m_parent->substituteVariables(m_command); emit logLine(tr("Running Pre-Launch command: %1").arg(cmd), MessageLevel::Launcher); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) auto args = QProcess::splitCommand(cmd); const QString program = args.takeFirst(); m_process.start(program, args); -#else - m_process.start(cmd); -#endif } void PreLaunchCommand::on_state(LoggedProcess::State state) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index d1780d497..463523a72 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -757,11 +757,7 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine token_mapping["assets_root"] = absAssetsDir; token_mapping["assets_index_name"] = assets->id; -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QStringList parts = args_pattern.split(' ', Qt::SkipEmptyParts); -#else - QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); -#endif for (int i = 0; i < parts.length(); i++) { parts[i] = replaceTokensIn(parts[i], token_mapping); } @@ -816,11 +812,7 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftT auto mainWindow = qobject_cast(w); if (mainWindow) { auto m = mainWindow->windowHandle()->frameMargins(); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) screenGeometry = screenGeometry.shrunkBy(m); -#else - screenGeometry = { screenGeometry.width() - m.left() - m.right(), screenGeometry.height() - m.top() - m.bottom() }; -#endif break; } } diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 4deee9712..8475a1f32 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -645,11 +645,7 @@ void PackProfile::move(const int index, const MoveDirection direction) return; } beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); -#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) d->components.swapItemsAt(index, theirIndex); -#else - d->components.swap(index, theirIndex); -#endif endMoveRows(); invalidateLaunchProfile(); scheduleSave(); diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index cf27be676..ae6f918e5 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -307,11 +307,7 @@ class WorldMimeData : public QMimeData { QStringList formats() const { return QMimeData::formats() << "text/uri-list"; } protected: -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QVariant retrieveData(const QString& mimetype, QMetaType type) const -#else - QVariant retrieveData(const QString& mimetype, QVariant::Type type) const -#endif { QList urls; for (auto& world : m_worlds) { diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 1ed39b5ca..1613a42b1 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -106,11 +106,7 @@ QPixmap MinecraftAccount::getFace() const return QPixmap(); } QPixmap skin = QPixmap(8, 8); -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) skin.fill(QColorConstants::Transparent); -#else - skin.fill(QColor(0, 0, 0, 0)); -#endif QPainter painter(&skin); painter.drawPixmap(0, 0, skinTexture.copy(8, 8, 8, 8)); painter.drawPixmap(0, 0, skinTexture.copy(40, 8, 8, 8)); @@ -290,13 +286,8 @@ QUuid MinecraftAccount::uuidFromUsername(QString username) // basically a reimplementation of Java's UUID#nameUUIDFromBytes QByteArray digest = QCryptographicHash::hash(input, QCryptographicHash::Md5); -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - auto bOr = [](QByteArray& array, int index, char value) { array[index] = array.at(index) | value; }; - auto bAnd = [](QByteArray& array, int index, char value) { array[index] = array.at(index) & value; }; -#else auto bOr = [](QByteArray& array, qsizetype index, char value) { array[index] |= value; }; auto bAnd = [](QByteArray& array, qsizetype index, char value) { array[index] &= value; }; -#endif bAnd(digest, 6, (char)0x0f); // clear version bOr(digest, 6, (char)0x30); // set to version 3 bAnd(digest, 8, (char)0x3f); // clear variant diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp index f9d89baa2..de1ffda86 100644 --- a/launcher/minecraft/auth/Parsers.cpp +++ b/launcher/minecraft/auth/Parsers.cpp @@ -315,11 +315,7 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output) auto value = pObj.value("value"); if (value.isString()) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) texturePayload = QByteArray::fromBase64(value.toString().toUtf8(), QByteArray::AbortOnBase64DecodingErrors); -#else - texturePayload = QByteArray::fromBase64(value.toString().toUtf8()); -#endif } if (!texturePayload.isEmpty()) { diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp index 87a0f8f08..aa972be71 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -168,13 +168,8 @@ void MSAStep::perform() m_oauth2.setRefreshToken(m_data->msaToken.refresh_token); m_oauth2.refreshAccessToken(); } else { -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0 m_oauth2.setModifyParametersFunction( [](QAbstractOAuth::Stage stage, QMultiMap* map) { map->insert("prompt", "select_account"); }); -#else - m_oauth2.setModifyParametersFunction( - [](QAbstractOAuth::Stage stage, QMap* map) { map->insert("prompt", "select_account"); }); -#endif *m_data = AccountData(); m_data->msaClientID = m_clientId; @@ -182,4 +177,4 @@ void MSAStep::perform() } } -#include "MSAStep.moc" \ No newline at end of file +#include "MSAStep.moc" diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index d4900616b..601df84bd 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -363,16 +363,11 @@ void ResourceFolderModel::onUpdateSucceeded() auto& new_resources = update_results->resources; -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) auto current_list = m_resources_index.keys(); QSet current_set(current_list.begin(), current_list.end()); auto new_list = new_resources.keys(); QSet new_set(new_list.begin(), new_list.end()); -#else - QSet current_set(m_resources_index.keys().toSet()); - QSet new_set(new_resources.keys().toSet()); -#endif applyUpdates(current_set, new_set, new_resources); } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index a0898edbd..a9706a768 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -678,13 +678,8 @@ void PackInstallTask::extractConfigs() return; } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/minecraft"); -#else - m_extractFuture = - QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/minecraft"); -#endif connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, [this]() { downloadMods(); }); connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, [this]() { emitAborted(); }); m_extractFutureWatcher.setFuture(m_extractFuture); @@ -897,13 +892,8 @@ void PackInstallTask::onModsDownloaded() jobPtr.reset(); if (!modsToExtract.empty() || !modsToDecomp.empty() || !modsToCopy.empty()) { -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), &PackInstallTask::extractMods, this, modsToExtract, modsToDecomp, modsToCopy); -#else - m_modExtractFuture = - QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy); -#endif connect(&m_modExtractFutureWatcher, &QFutureWatcher::finished, this, &PackInstallTask::onModsExtracted); connect(&m_modExtractFutureWatcher, &QFutureWatcher::canceled, this, &PackInstallTask::emitAborted); m_modExtractFutureWatcher.setFuture(m_modExtractFuture); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index c04c0b2f3..c8d04828c 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -108,13 +108,8 @@ void PackInstallTask::unzip() return; } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/unzip"); -#else - m_extractFuture = - QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip"); -#endif connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &PackInstallTask::onUnzipFinished); connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, &PackInstallTask::onUnzipCanceled); m_extractFutureWatcher.setFuture(m_extractFuture); diff --git a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp index 2ae351329..17e9f7d76 100644 --- a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp +++ b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp @@ -44,12 +44,8 @@ namespace LegacyFTB { void PrivatePackManager::load() { try { -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) auto foo = QString::fromUtf8(FS::read(m_filename)).split('\n', Qt::SkipEmptyParts); currentPacks = QSet(foo.begin(), foo.end()); -#else - currentPacks = QString::fromUtf8(FS::read(m_filename)).split('\n', QString::SkipEmptyParts).toSet(); -#endif dirty = false; } catch (...) { diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index 310653508..ef533f599 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -104,12 +104,10 @@ void NetRequest::executeTask() header_proxy->writeHeaders(request); } -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) #if defined(LAUNCHER_APPLICATION) request.setTransferTimeout(APPLICATION->settings()->get("RequestTimeout").toInt() * 1000); #else request.setTransferTimeout(); -#endif #endif m_last_progress_time = m_clock.now(); @@ -122,11 +120,7 @@ void NetRequest::executeTask() connect(rep, &QNetworkReply::uploadProgress, this, &NetRequest::onProgress); connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::onProgress); connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 connect(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &NetRequest::downloadError); -#endif connect(rep, &QNetworkReply::sslErrors, this, &NetRequest::sslErrors); connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead); } @@ -323,11 +317,7 @@ auto NetRequest::abort() -> bool { m_state = State::AbortedByUser; if (m_reply) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 disconnect(m_reply.get(), &QNetworkReply::errorOccurred, nullptr, nullptr); -#else - disconnect(m_reply.get(), QOverload::of(&QNetworkReply::error), nullptr, nullptr); -#endif m_reply->abort(); } return true; diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index c67d3b23c..86a44669e 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -130,11 +130,7 @@ void PasteUpload::executeTask() connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); connect(rep, &QNetworkReply::finished, this, &PasteUpload::downloadFinished); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) connect(rep, &QNetworkReply::errorOccurred, this, &PasteUpload::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &PasteUpload::downloadError); -#endif m_reply = std::shared_ptr(rep); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index ddf726373..53e87b57e 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -814,11 +814,7 @@ void MainWindow::updateNewsLabel() QList stringToIntList(const QString& string) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QStringList split = string.split(',', Qt::SkipEmptyParts); -#else - QStringList split = string.split(',', QString::SkipEmptyParts); -#endif QList out; for (int i = 0; i < split.size(); ++i) { out.append(split.at(i).toInt()); diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index d9ea0aafb..6036663ba 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -136,11 +136,7 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, if (APPLICATION->settings()->get("NewInstanceGeometry").isValid()) { restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("NewInstanceGeometry").toByteArray())); } else { -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) auto screen = parent->screen(); -#else - auto screen = QGuiApplication::primaryScreen(); -#endif auto geometry = screen->availableSize(); resize(width(), qMin(geometry.height() - 50, 710)); } diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index c677f3951..fa1af4266 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -400,12 +400,8 @@ void InstanceView::mouseReleaseEvent(QMouseEvent* event) if (event->button() == Qt::LeftButton) { emit clicked(index); } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QStyleOptionViewItem option; initViewItemOption(&option); -#else - QStyleOptionViewItem option = viewOptions(); -#endif if (m_pressedAlreadySelected) { option.state |= QStyle::State_Selected; } @@ -431,12 +427,8 @@ void InstanceView::mouseDoubleClickEvent(QMouseEvent* event) QPersistentModelIndex persistent = index; emit doubleClicked(persistent); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QStyleOptionViewItem option; initViewItemOption(&option); -#else - QStyleOptionViewItem option = viewOptions(); -#endif if ((model()->flags(index) & Qt::ItemIsEnabled) && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) { emit activated(index); } @@ -472,12 +464,8 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event) painter.setOpacity(1.0); } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QStyleOptionViewItem option; initViewItemOption(&option); -#else - QStyleOptionViewItem option = viewOptions(); -#endif option.widget = this; if (model()->rowCount() == 0) { @@ -732,12 +720,8 @@ QRect InstanceView::geometryRect(const QModelIndex& index) const int x = pos.first; // int y = pos.second; -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QStyleOptionViewItem option; initViewItemOption(&option); -#else - QStyleOptionViewItem option = viewOptions(); -#endif QRect out; out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + cat->rowTopOf(index)); @@ -784,12 +768,8 @@ QPixmap InstanceView::renderToPixmap(const QModelIndexList& indices, QRect* r) c QPixmap pixmap(r->size()); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QStyleOptionViewItem option; initViewItemOption(&option); -#else - QStyleOptionViewItem option = viewOptions(); -#endif option.state |= QStyle::State_Selected; for (int j = 0; j < paintPairs.count(); ++j) { option.rect = paintPairs.at(j).first.translated(-r->topLeft()); diff --git a/launcher/ui/instanceview/VisualGroup.cpp b/launcher/ui/instanceview/VisualGroup.cpp index 83103c502..089db8ad7 100644 --- a/launcher/ui/instanceview/VisualGroup.cpp +++ b/launcher/ui/instanceview/VisualGroup.cpp @@ -73,12 +73,8 @@ void VisualGroup::update() positionInRow = 0; maxRowHeight = 0; } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QStyleOptionViewItem viewItemOption; view->initViewItemOption(&viewItemOption); -#else - QStyleOptionViewItem viewItemOption = view->viewOptions(); -#endif auto itemHeight = view->itemDelegate()->sizeHint(viewItemOption, item).height(); if (itemHeight > maxRowHeight) { diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 136fb47c7..a93966ae9 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -255,11 +255,7 @@ class ServersModel : public QAbstractListModel { return false; } beginMoveRows(QModelIndex(), row, row, QModelIndex(), row - 1); -#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) m_servers.swapItemsAt(row - 1, row); -#else - m_servers.swap(row - 1, row); -#endif endMoveRows(); scheduleSave(); return true; @@ -275,11 +271,7 @@ class ServersModel : public QAbstractListModel { return false; } beginMoveRows(QModelIndex(), row, row, QModelIndex(), row + 2); -#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) m_servers.swapItemsAt(row + 1, row); -#else - m_servers.swap(row + 1, row); -#endif endMoveRows(); scheduleSave(); return true; diff --git a/libraries/systeminfo/src/distroutils.cpp b/libraries/systeminfo/src/distroutils.cpp index 57e6c8320..5891282fe 100644 --- a/libraries/systeminfo/src/distroutils.cpp +++ b/libraries/systeminfo/src/distroutils.cpp @@ -145,11 +145,7 @@ void Sys::lsb_postprocess(Sys::LsbInfo& lsb, Sys::DistributionInfo& out) vers = lsb.codename; } else { // ubuntu, debian, gentoo, scientific, slackware, ... ? -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) auto parts = dist.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); -#else - auto parts = dist.split(QRegularExpression("\\s+"), QString::SkipEmptyParts); -#endif if (parts.size()) { dist = parts[0]; } @@ -182,11 +178,7 @@ QString Sys::_extract_distribution(const QString& x) if (release.startsWith("suse linux enterprise")) { return "sles"; } -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QStringList list = release.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); -#else - QStringList list = release.split(QRegularExpression("\\s+"), QString::SkipEmptyParts); -#endif if (list.size()) { return list[0]; } @@ -196,11 +188,7 @@ QString Sys::_extract_distribution(const QString& x) QString Sys::_extract_version(const QString& x) { QRegularExpression versionish_string(QRegularExpression::anchoredPattern("\\d+(?:\\.\\d+)*$")); -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QStringList list = x.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); -#else - QStringList list = x.split(QRegularExpression("\\s+"), QString::SkipEmptyParts); -#endif for (int i = list.size() - 1; i >= 0; --i) { QString chunk = list[i]; if (versionish_string.match(chunk).hasMatch()) { From 5fee4e3f8bbab3063e15fcc4f1563433d7001c1b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Apr 2025 00:17:29 +0300 Subject: [PATCH 053/171] chore: remove qt5 from release and copyright Signed-off-by: Trial97 --- .github/workflows/trigger_release.yml | 2 -- CMakeLists.txt | 10 ---------- COPYING.md | 2 +- nix/unwrapped.nix | 3 --- 4 files changed, 1 insertion(+), 16 deletions(-) diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 411a5bbeb..96f616a43 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -46,7 +46,6 @@ jobs: run: | mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }} mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz - mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip @@ -89,7 +88,6 @@ jobs: draft: true prerelease: false files: | - PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-x86_64.AppImage PrismLauncher-Linux-x86_64.AppImage.zsync PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz diff --git a/CMakeLists.txt b/CMakeLists.txt index e2ea079bf..321232378 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,11 +88,6 @@ else() endif() endif() -# Fix build with Qt 5.13 -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y") - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00") - # Fix aarch64 build for toml++ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0") @@ -334,11 +329,6 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_LIBEXECS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBEXECS}) endif() -# NOTE: Qt 6 already sets this by default -if (Qt5_POSITION_INDEPENDENT_CODE) - SET(CMAKE_POSITION_INDEPENDENT_CODE ON) -endif() - if(NOT Launcher_FORCE_BUNDLED_LIBS) # Find toml++ find_package(tomlplusplus 3.2.0 QUIET) diff --git a/COPYING.md b/COPYING.md index f1f0b3a70..818c13c78 100644 --- a/COPYING.md +++ b/COPYING.md @@ -108,7 +108,7 @@ Information on third party licenses used in MinGW-w64 can be found in its COPYING.MinGW-w64-runtime.txt. -## Qt 5/6 +## Qt 6 Copyright (C) 2022 The Qt Company Ltd and other contributors. Contact: https://www.qt.io/licensing diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 93cda8e1a..14882e893 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -96,9 +96,6 @@ stdenv.mkDerivation { ++ lib.optionals (msaClientID != null) [ (lib.cmakeFeature "Launcher_MSA_CLIENT_ID" (toString msaClientID)) ] - ++ lib.optionals (lib.versionOlder kdePackages.qtbase.version "6") [ - (lib.cmakeFeature "Launcher_QT_VERSION_MAJOR" "5") - ] ++ lib.optionals stdenv.hostPlatform.isDarwin [ # we wrap our binary manually (lib.cmakeFeature "INSTALL_BUNDLE" "nodeps") From 4a2b5c72dc75e03695566af399c504c19170d042 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 17 Apr 2025 14:54:44 -0700 Subject: [PATCH 054/171] feat(color-console): support ansi colors in console Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 2 +- launcher/CMakeLists.txt | 8 ++++---- launcher/{ => console}/WindowsConsole.cpp | 3 +++ launcher/{ => console}/WindowsConsole.h | 3 +++ 4 files changed, 11 insertions(+), 5 deletions(-) rename launcher/{ => console}/WindowsConsole.cpp (99%) rename launcher/{ => console}/WindowsConsole.h (93%) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index d773d9a1c..f98112641 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -155,7 +155,7 @@ #if defined Q_OS_WIN32 #include #include -#include "WindowsConsole.h" +#include "console/WindowsConsole.h" #endif #define STRINGIFY(x) #x diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 30d657f9e..5d757a130 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -589,8 +589,8 @@ set(ATLAUNCHER_SOURCES ) set(LINKEXE_SOURCES - WindowsConsole.cpp - WindowsConsole.h + console/WindowsConsole.cpp + console/WindowsConsole.h filelink/FileLink.h filelink/FileLink.cpp @@ -1164,8 +1164,8 @@ endif() if(WIN32) set(LAUNCHER_SOURCES - WindowsConsole.cpp - WindowsConsole.h + console/WindowsConsole.cpp + console/WindowsConsole.h ${LAUNCHER_SOURCES} ) endif() diff --git a/launcher/WindowsConsole.cpp b/launcher/console/WindowsConsole.cpp similarity index 99% rename from launcher/WindowsConsole.cpp rename to launcher/console/WindowsConsole.cpp index 83cad5afa..f388bd3b1 100644 --- a/launcher/WindowsConsole.cpp +++ b/launcher/console/WindowsConsole.cpp @@ -19,6 +19,7 @@ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif +#include "WindowsConsole.h" #include #include #include @@ -126,3 +127,5 @@ bool AttachWindowsConsole() return false; } + +std::error_code diff --git a/launcher/WindowsConsole.h b/launcher/console/WindowsConsole.h similarity index 93% rename from launcher/WindowsConsole.h rename to launcher/console/WindowsConsole.h index ab53864b4..4c1f3ee28 100644 --- a/launcher/WindowsConsole.h +++ b/launcher/console/WindowsConsole.h @@ -21,5 +21,8 @@ #pragma once +#include + void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr); bool AttachWindowsConsole(); +std::error_code EnableAnsiSupport(); From 45b6454222a83b932e0505cbd815d697226080cd Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Thu, 17 Apr 2025 23:18:57 -0700 Subject: [PATCH 055/171] feat(ansi-console): Format console with ansi excapes Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 181 +++++++++++++----- launcher/CMakeLists.txt | 31 ++- launcher/InstanceList.cpp | 6 +- launcher/console/Console.h | 33 ++++ launcher/console/WindowsConsole.cpp | 34 +++- launcher/filelink/FileLink.cpp | 5 +- .../updater/prismupdater/PrismUpdater.cpp | 109 +---------- 7 files changed, 225 insertions(+), 174 deletions(-) create mode 100644 launcher/console/Console.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index f98112641..70ffce12f 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -125,6 +125,7 @@ #include #include +#include #include #include #include "SysInfo.h" @@ -153,11 +154,16 @@ #endif #if defined Q_OS_WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif #include #include #include "console/WindowsConsole.h" #endif +#include "console/Console.h" + #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) @@ -165,6 +171,69 @@ static const QLatin1String liveCheckFile("live.check"); PixmapCache* PixmapCache::s_instance = nullptr; +static bool isANSIColorConsole; + +static QString defaultLogFormat = QStringLiteral( + "%{time process}" + " " + "%{if-debug}D%{endif}" + "%{if-info}I%{endif}" + "%{if-warning}W%{endif}" + "%{if-critical}C%{endif}" + "%{if-fatal}F%{endif}" + " " + "|" + " " + "%{function}:%{line}" + " " + "|" + " " + "%{if-category}[%{category}]: %{endif}" + "%{message}"); + +#define ansi_reset "\x1b[0m" +#define ansi_bold "\x1b[1m" +#define ansi_faint "\x1b[2m" +#define ansi_italic "\x1b[3m" +#define ansi_red_fg "\x1b[31m" +#define ansi_green_fg "\x1b[32m" +#define ansi_yellow_fg "\x1b[33m" +#define ansi_blue_fg "\x1b[34m" +#define ansi_purple_fg "\x1b[35m" + +static QString ansiLogFormat = QStringLiteral( + "%{time process}" + " " + "%{if-debug}" ansi_bold ansi_blue_fg "D" ansi_reset + "%{endif}" + "%{if-info}" ansi_bold ansi_green_fg "I" ansi_reset + "%{endif}" + "%{if-warning}" ansi_bold ansi_yellow_fg "W" ansi_reset + "%{endif}" + "%{if-critical}" ansi_bold ansi_red_fg "C" ansi_reset + "%{endif}" + "%{if-fatal}" ansi_bold ansi_red_fg "F" ansi_reset + "%{endif}" + " " + "|" + " " ansi_faint ansi_italic "%{function}:%{line}" ansi_reset + " " + "|" + " " + "%{if-category}[" ansi_bold ansi_purple_fg "%{category}" ansi_reset + "]: %{endif}" + "%{message}"); + +#undef ansi_purple_fg +#undef ansi_blue_fg +#undef ansi_yellow_fg +#undef ansi_green_fg +#undef ansi_red_fg +#undef ansi_italic +#undef ansi_faint +#undef ansi_bold +#undef ansi_reset + namespace { /** This is used so that we can output to the log file in addition to the CLI. */ @@ -173,11 +242,24 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt static std::mutex loggerMutex; const std::lock_guard lock(loggerMutex); // synchronized, QFile logFile is not thread-safe + if (isANSIColorConsole) { + // ensure default is set for log file + qSetMessagePattern(defaultLogFormat); + } + QString out = qFormatLogMessage(type, context, msg); out += QChar::LineFeed; APPLICATION->logFile->write(out.toUtf8()); APPLICATION->logFile->flush(); + + if (isANSIColorConsole) { + // format ansi for console; + qSetMessagePattern(ansiLogFormat); + out = qFormatLogMessage(type, context, msg); + out += QChar::LineFeed; + } + QTextStream(stderr) << out.toLocal8Bit(); fflush(stderr); } @@ -218,8 +300,18 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // attach the parent console if stdout not already captured if (AttachWindowsConsole()) { consoleAttached = true; + if (auto err = EnableAnsiSupport(); !err) { + isANSIColorConsole = true; + } else { + std::cout << "Error setting up ansi console" << err.message() << std::endl; + } + } +#else + if (console::isConsole()) { + isANSIColorConsole = true; } #endif + setOrganizationName(BuildConfig.LAUNCHER_NAME); setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN); setApplicationName(BuildConfig.LAUNCHER_NAME); @@ -448,27 +540,14 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) return; } qInstallMessageHandler(appDebugOutput); - - qSetMessagePattern( - "%{time process}" - " " - "%{if-debug}D%{endif}" - "%{if-info}I%{endif}" - "%{if-warning}W%{endif}" - "%{if-critical}C%{endif}" - "%{if-fatal}F%{endif}" - " " - "|" - " " - "%{if-category}[%{category}]: %{endif}" - "%{message}"); + qSetMessagePattern(defaultLogFormat); bool foundLoggingRules = false; auto logRulesFile = QStringLiteral("qtlogging.ini"); auto logRulesPath = FS::PathCombine(dataPath, logRulesFile); - qDebug() << "Testing" << logRulesPath << "..."; + qInfo() << "Testing" << logRulesPath << "..."; foundLoggingRules = QFile::exists(logRulesPath); // search the dataPath() @@ -476,7 +555,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) if (!foundLoggingRules && !isPortable() && dirParam.isEmpty() && dataDirEnv.isEmpty()) { logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, FS::PathCombine("..", logRulesFile)); if (!logRulesPath.isEmpty()) { - qDebug() << "Found" << logRulesPath << "..."; + qInfo() << "Found" << logRulesPath << "..."; foundLoggingRules = true; } } @@ -487,28 +566,28 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) #else logRulesPath = FS::PathCombine(m_rootPath, logRulesFile); #endif - qDebug() << "Testing" << logRulesPath << "..."; + qInfo() << "Testing" << logRulesPath << "..."; foundLoggingRules = QFile::exists(logRulesPath); } if (foundLoggingRules) { // load and set logging rules - qDebug() << "Loading logging rules from:" << logRulesPath; + qInfo() << "Loading logging rules from:" << logRulesPath; QSettings loggingRules(logRulesPath, QSettings::IniFormat); loggingRules.beginGroup("Rules"); QStringList rule_names = loggingRules.childKeys(); QStringList rules; - qDebug() << "Setting log rules:"; + qInfo() << "Setting log rules:"; for (auto rule_name : rule_names) { auto rule = QString("%1=%2").arg(rule_name).arg(loggingRules.value(rule_name).toString()); rules.append(rule); - qDebug() << " " << rule; + qInfo() << " " << rule; } auto rules_str = rules.join("\n"); QLoggingCategory::setFilterRules(rules_str); } - qDebug() << "<> Log initialized."; + qInfo() << "<> Log initialized."; } { @@ -525,33 +604,33 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } { - qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", ")); - qDebug() << "Version : " << BuildConfig.printableVersionString(); - qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM; - qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; - qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; - qDebug() << "Compiled for : " << BuildConfig.systemID(); - qDebug() << "Compiled by : " << BuildConfig.compilerID(); - qDebug() << "Build Artifact : " << BuildConfig.BUILD_ARTIFACT; - qDebug() << "Updates Enabled : " << (updaterEnabled() ? "Yes" : "No"); + qInfo() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", ")); + qInfo() << "Version : " << BuildConfig.printableVersionString(); + qInfo() << "Platform : " << BuildConfig.BUILD_PLATFORM; + qInfo() << "Git commit : " << BuildConfig.GIT_COMMIT; + qInfo() << "Git refspec : " << BuildConfig.GIT_REFSPEC; + qInfo() << "Compiled for : " << BuildConfig.systemID(); + qInfo() << "Compiled by : " << BuildConfig.compilerID(); + qInfo() << "Build Artifact : " << BuildConfig.BUILD_ARTIFACT; + qInfo() << "Updates Enabled : " << (updaterEnabled() ? "Yes" : "No"); if (adjustedBy.size()) { - qDebug() << "Work dir before adjustment : " << origcwdPath; - qDebug() << "Work dir after adjustment : " << QDir::currentPath(); - qDebug() << "Adjusted by : " << adjustedBy; + qInfo() << "Work dir before adjustment : " << origcwdPath; + qInfo() << "Work dir after adjustment : " << QDir::currentPath(); + qInfo() << "Adjusted by : " << adjustedBy; } else { - qDebug() << "Work dir : " << QDir::currentPath(); + qInfo() << "Work dir : " << QDir::currentPath(); } - qDebug() << "Binary path : " << binPath; - qDebug() << "Application root path : " << m_rootPath; + qInfo() << "Binary path : " << binPath; + qInfo() << "Application root path : " << m_rootPath; if (!m_instanceIdToLaunch.isEmpty()) { - qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch; + qInfo() << "ID of instance to launch : " << m_instanceIdToLaunch; } if (!m_serverToJoin.isEmpty()) { - qDebug() << "Address of server to join :" << m_serverToJoin; + qInfo() << "Address of server to join :" << m_serverToJoin; } else if (!m_worldToJoin.isEmpty()) { - qDebug() << "Name of the world to join :" << m_worldToJoin; + qInfo() << "Name of the world to join :" << m_worldToJoin; } - qDebug() << "<> Paths set."; + qInfo() << "<> Paths set."; } if (m_liveCheck) { @@ -818,7 +897,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) PixmapCache::setInstance(new PixmapCache(this)); - qDebug() << "<> Settings loaded."; + qInfo() << "<> Settings loaded."; } #ifndef QT_NO_ACCESSIBILITY @@ -834,7 +913,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) QString user = settings()->get("ProxyUser").toString(); QString pass = settings()->get("ProxyPass").toString(); updateProxySettings(proxyTypeStr, addr, port, user, pass); - qDebug() << "<> Network done."; + qInfo() << "<> Network done."; } // load translations @@ -842,8 +921,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_translations.reset(new TranslationsModel("translations")); auto bcp47Name = m_settings->get("Language").toString(); m_translations->selectLanguage(bcp47Name); - qDebug() << "Your language is" << bcp47Name; - qDebug() << "<> Translations loaded."; + qInfo() << "Your language is" << bcp47Name; + qInfo() << "<> Translations loaded."; } // Instance icons @@ -854,7 +933,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_icons.reset(new IconList(instFolders, setting->get().toString())); connect(setting.get(), &Setting::SettingChanged, [this](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); }); - qDebug() << "<> Instance icons initialized."; + qInfo() << "<> Instance icons initialized."; } // Themes @@ -866,25 +945,25 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // instance path: check for problems with '!' in instance path and warn the user in the log // and remember that we have to show him a dialog when the gui starts (if it does so) QString instDir = InstDirSetting->get().toString(); - qDebug() << "Instance path : " << instDir; + qInfo() << "Instance path : " << instDir; if (FS::checkProblemticPathJava(QDir(instDir))) { qWarning() << "Your instance path contains \'!\' and this is known to cause java problems!"; } m_instances.reset(new InstanceList(m_settings, instDir, this)); connect(InstDirSetting.get(), &Setting::SettingChanged, m_instances.get(), &InstanceList::on_InstFolderChanged); - qDebug() << "Loading Instances..."; + qInfo() << "Loading Instances..."; m_instances->loadList(); - qDebug() << "<> Instances loaded."; + qInfo() << "<> Instances loaded."; } // and accounts { m_accounts.reset(new AccountList(this)); - qDebug() << "Loading accounts..."; + qInfo() << "Loading accounts..."; m_accounts->setListFilePath("accounts.json", true); m_accounts->loadList(); m_accounts->fillQueue(); - qDebug() << "<> Accounts loaded."; + qInfo() << "<> Accounts loaded."; } // init the http meta cache @@ -905,7 +984,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_metacache->addBase("meta", QDir("meta").absolutePath()); m_metacache->addBase("java", QDir("cache/java").absolutePath()); m_metacache->Load(); - qDebug() << "<> Cache initialized."; + qInfo() << "<> Cache initialized."; } // now we have network, download translation updates diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 5d757a130..f60d7960e 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -99,7 +99,7 @@ set(CORE_SOURCES MTPixmapCache.h ) if (UNIX AND NOT CYGWIN AND NOT APPLE) -set(CORE_SOURCES + set(CORE_SOURCES ${CORE_SOURCES} # MangoHud @@ -589,8 +589,8 @@ set(ATLAUNCHER_SOURCES ) set(LINKEXE_SOURCES - console/WindowsConsole.cpp console/WindowsConsole.h + console/WindowsConsole.cpp filelink/FileLink.h filelink/FileLink.cpp @@ -659,6 +659,14 @@ set(PRISMUPDATER_SOURCES ) +if(WIN32) + set(PRISMUPDATER_SOURCES + console/WindowsConsole.h + console/WindowsConsole.cpp + ${PRISMUPDATER_SOURCES} + ) +endif() + ######## Logging categories ######## ecm_qt_declare_logging_category(CORE_SOURCES @@ -786,6 +794,9 @@ SET(LAUNCHER_SOURCES SysInfo.h SysInfo.cpp + # console utils + console/Console.h + # GUI - general utilities DesktopServices.h DesktopServices.cpp @@ -926,7 +937,7 @@ SET(LAUNCHER_SOURCES ui/pages/instance/McResolver.h ui/pages/instance/ServerPingTask.cpp ui/pages/instance/ServerPingTask.h - + # GUI - global settings pages ui/pages/global/AccountListPage.cpp ui/pages/global/AccountListPage.h @@ -1154,7 +1165,7 @@ SET(LAUNCHER_SOURCES ) if (NOT Apple) -set(LAUNCHER_SOURCES + set(LAUNCHER_SOURCES ${LAUNCHER_SOURCES} ui/dialogs/UpdateAvailableDialog.h @@ -1164,8 +1175,8 @@ endif() if(WIN32) set(LAUNCHER_SOURCES - console/WindowsConsole.cpp console/WindowsConsole.h + console/WindowsConsole.cpp ${LAUNCHER_SOURCES} ) endif() @@ -1324,11 +1335,11 @@ if(APPLE) set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/") if(Launcher_ENABLE_UPDATER) - file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256}) - file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle) + file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256}) + file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle) - find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle") - add_compile_definitions(SPARKLE_ENABLED) + find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle") + add_compile_definitions(SPARKLE_ENABLED) endif() target_link_libraries(Launcher_logic @@ -1338,7 +1349,7 @@ if(APPLE) "-framework ApplicationServices" ) if(Launcher_ENABLE_UPDATER) - target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK}) + target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK}) endif() endif() diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 918fa1073..3df44f408 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -428,7 +428,7 @@ static QMap getIdMapping(const QList& QList InstanceList::discoverInstances() { - qDebug() << "Discovering instances in" << m_instDir; + qInfo() << "Discovering instances in" << m_instDir; QList out; QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); while (iter.hasNext()) { @@ -447,7 +447,7 @@ QList InstanceList::discoverInstances() } auto id = dirInfo.fileName(); out.append(id); - qDebug() << "Found instance ID" << id; + qInfo() << "Found instance ID" << id; } #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) instanceSet = QSet(out.begin(), out.end()); @@ -468,7 +468,7 @@ InstanceList::InstListError InstanceList::loadList() if (existingIds.contains(id)) { auto instPair = existingIds[id]; existingIds.remove(id); - qDebug() << "Should keep and soft-reload" << id; + qInfo() << "Should keep and soft-reload" << id; } else { InstancePtr instPtr = loadInstance(id); if (instPtr) { diff --git a/launcher/console/Console.h b/launcher/console/Console.h new file mode 100644 index 000000000..7aaf83dcc --- /dev/null +++ b/launcher/console/Console.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include +#if defined Q_OS_WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#include +#endif + +namespace console { + +inline bool isConsole() +{ +#if defined Q_OS_WIN32 + DWORD procIDs[2]; + DWORD maxCount = 2; + DWORD result = GetConsoleProcessList((LPDWORD)procIDs, maxCount); + return result > 1; +#else + if (isatty(fileno(stdout))) { + return true; + } + return false; +#endif +} + +} // namespace console diff --git a/launcher/console/WindowsConsole.cpp b/launcher/console/WindowsConsole.cpp index f388bd3b1..4a0eb3d3d 100644 --- a/launcher/console/WindowsConsole.cpp +++ b/launcher/console/WindowsConsole.cpp @@ -16,14 +16,18 @@ * */ +#include "WindowsConsole.h" +#include + #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif -#include "WindowsConsole.h" +#include + #include #include #include -#include +#include #include void RedirectHandle(DWORD handle, FILE* stream, const char* mode) @@ -128,4 +132,28 @@ bool AttachWindowsConsole() return false; } -std::error_code +std::error_code EnableAnsiSupport() +{ + // ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew + // Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected + HANDLE console_handle = CreateFileW(L"CONOUT$", FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); + if (console_handle == INVALID_HANDLE_VALUE) { + return std::error_code(GetLastError(), std::system_category()); + } + + // ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode + DWORD console_mode; + if (0 == GetConsoleMode(console_handle, &console_mode)) { + return std::error_code(GetLastError(), std::system_category()); + } + + // VT processing not already enabled? + if ((console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0) { + // https://docs.microsoft.com/en-us/windows/console/setconsolemode + if (0 == SetConsoleMode(console_handle, console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) { + return std::error_code(GetLastError(), std::system_category()); + } + } + + return {}; +} diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index a082b4b5b..1494fa8cc 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -37,7 +37,10 @@ #include #if defined Q_OS_WIN32 -#include "WindowsConsole.h" +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include "console/WindowsConsole.h" #endif #include diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index f9ffeb658..365647db9 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -46,11 +46,8 @@ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif -#include -#include -#include #include -#include +#include "console/WindowsConsole.h" #endif #include @@ -87,112 +84,12 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt } } -#if defined Q_OS_WIN32 - -// taken from https://stackoverflow.com/a/25927081 -// getting a proper output to console with redirection support on windows is apparently hell -void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr) -{ - // Re-initialize the C runtime "FILE" handles with clean handles bound to "nul". We do this because it has been - // observed that the file number of our standard handle file objects can be assigned internally to a value of -2 - // when not bound to a valid target, which represents some kind of unknown internal invalid state. In this state our - // call to "_dup2" fails, as it specifically tests to ensure that the target file number isn't equal to this value - // before allowing the operation to continue. We can resolve this issue by first "re-opening" the target files to - // use the "nul" device, which will place them into a valid state, after which we can redirect them to our target - // using the "_dup2" function. - if (bindStdIn) { - FILE* dummyFile; - freopen_s(&dummyFile, "nul", "r", stdin); - } - if (bindStdOut) { - FILE* dummyFile; - freopen_s(&dummyFile, "nul", "w", stdout); - } - if (bindStdErr) { - FILE* dummyFile; - freopen_s(&dummyFile, "nul", "w", stderr); - } - - // Redirect unbuffered stdin from the current standard input handle - if (bindStdIn) { - HANDLE stdHandle = GetStdHandle(STD_INPUT_HANDLE); - if (stdHandle != INVALID_HANDLE_VALUE) { - int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT); - if (fileDescriptor != -1) { - FILE* file = _fdopen(fileDescriptor, "r"); - if (file != NULL) { - int dup2Result = _dup2(_fileno(file), _fileno(stdin)); - if (dup2Result == 0) { - setvbuf(stdin, NULL, _IONBF, 0); - } - } - } - } - } - - // Redirect unbuffered stdout to the current standard output handle - if (bindStdOut) { - HANDLE stdHandle = GetStdHandle(STD_OUTPUT_HANDLE); - if (stdHandle != INVALID_HANDLE_VALUE) { - int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT); - if (fileDescriptor != -1) { - FILE* file = _fdopen(fileDescriptor, "w"); - if (file != NULL) { - int dup2Result = _dup2(_fileno(file), _fileno(stdout)); - if (dup2Result == 0) { - setvbuf(stdout, NULL, _IONBF, 0); - } - } - } - } - } - - // Redirect unbuffered stderr to the current standard error handle - if (bindStdErr) { - HANDLE stdHandle = GetStdHandle(STD_ERROR_HANDLE); - if (stdHandle != INVALID_HANDLE_VALUE) { - int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT); - if (fileDescriptor != -1) { - FILE* file = _fdopen(fileDescriptor, "w"); - if (file != NULL) { - int dup2Result = _dup2(_fileno(file), _fileno(stderr)); - if (dup2Result == 0) { - setvbuf(stderr, NULL, _IONBF, 0); - } - } - } - } - } - - // Clear the error state for each of the C++ standard stream objects. We need to do this, as attempts to access the - // standard streams before they refer to a valid target will cause the iostream objects to enter an error state. In - // versions of Visual Studio after 2005, this seems to always occur during startup regardless of whether anything - // has been read from or written to the targets or not. - if (bindStdIn) { - std::wcin.clear(); - std::cin.clear(); - } - if (bindStdOut) { - std::wcout.clear(); - std::cout.clear(); - } - if (bindStdErr) { - std::wcerr.clear(); - std::cerr.clear(); - } -} -#endif - PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, argv) { #if defined Q_OS_WIN32 // attach the parent console if stdout not already captured - auto stdout_type = GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)); - if (stdout_type == FILE_TYPE_CHAR || stdout_type == FILE_TYPE_UNKNOWN) { - if (AttachConsole(ATTACH_PARENT_PROCESS)) { - BindCrtHandlesToStdHandles(true, true, true); - consoleAttached = true; - } + if (AttachWindowsConsole()) { + consoleAttached = true; } #endif setOrganizationName(BuildConfig.LAUNCHER_NAME); From 2271a05b19e259f9e1e727c09e1c68542dcc02e2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Apr 2025 12:37:54 +0300 Subject: [PATCH 056/171] chore: add back deprecation warnings and disable all API deprecated before 6.0 Signed-off-by: Trial97 --- CMakeLists.txt | 3 +++ launcher/StringUtils.cpp | 2 +- launcher/modplatform/legacy_ftb/PackFetchTask.cpp | 2 +- launcher/modplatform/legacy_ftb/PackInstallTask.cpp | 2 +- launcher/translations/TranslationsModel.cpp | 2 +- launcher/ui/MainWindow.cpp | 2 +- launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp | 4 ++-- launcher/ui/instanceview/InstanceView.cpp | 8 ++++---- libraries/LocalPeer/src/LocalPeer.cpp | 2 +- 9 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 321232378..22e3978d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,9 @@ else() endif() endif() +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_WARN_DEPRECATED_UP_TO=0x060200") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_UP_TO=0x060000") + # Fix aarch64 build for toml++ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0") diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index edda9f247..2ea67762e 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -53,7 +53,7 @@ static inline QChar getNextChar(const QString& s, int location) int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs) { int l1 = 0, l2 = 0; - while (l1 <= s1.count() && l2 <= s2.count()) { + while (l1 <= s1.size() && l2 <= s2.size()) { // skip spaces, tabs and 0's QChar c1 = getNextChar(s1, l1); while (c1.isSpace()) diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp index a0beeddcc..aea9cefad 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp @@ -79,7 +79,7 @@ void PackFetchTask::fetchPrivate(const QStringList& toFetch) QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] { ModpackList packs; parseAndAddPacks(*data, PackType::Private, packs); - foreach (Modpack currentPack, packs) { + for (auto& currentPack : packs) { currentPack.packCode = packCode; emit privateFileDownloadFinished(currentPack); } diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index c8d04828c..2b9bd127a 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -160,7 +160,7 @@ void PackInstallTask::install() // we only care about the libs QJsonArray libs = doc.object().value("libraries").toArray(); - foreach (const QJsonValue& value, libs) { + for (const auto& value : libs) { QString nameValue = value.toObject().value("name").toString(); if (!nameValue.startsWith("net.minecraftforge")) { continue; diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 429ead47d..e863dfef4 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -480,7 +480,7 @@ bool TranslationsModel::selectLanguage(QString key) bool successful = false; // FIXME: this is likely never present. FIX IT. d->m_qt_translator.reset(new QTranslator()); - if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { + if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "..."; if (!QCoreApplication::installTranslator(d->m_qt_translator.get())) { qCritical() << "Loading Qt Language File failed."; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 53e87b57e..2d8e4ebf5 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -724,7 +724,7 @@ void MainWindow::changeActiveAccount() QAction* sAction = (QAction*)sender(); // Profile's associated Mojang username - if (sAction->data().type() != QVariant::Type::Int) + if (sAction->data().typeId() != QMetaType::Int) return; QVariant action_data = sAction->data(); diff --git a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp index 97fe44175..e1e539050 100644 --- a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp +++ b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp @@ -76,8 +76,8 @@ void SkinOpenGLWindow::mousePressEvent(QMouseEvent* e) void SkinOpenGLWindow::mouseMoveEvent(QMouseEvent* event) { if (m_isMousePressed) { - int dx = event->x() - m_mousePosition.x(); - int dy = event->y() - m_mousePosition.y(); + int dx = event->position().x() - m_mousePosition.x(); + int dy = event->position().y() - m_mousePosition.y(); m_yaw += dx * 0.5f; m_pitch += dy * 0.5f; diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index fa1af4266..f52c994d3 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -418,7 +418,7 @@ void InstanceView::mouseDoubleClickEvent(QMouseEvent* event) QModelIndex index = indexAt(event->pos()); if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index)) { - QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(), event->screenPos(), event->button(), + QMouseEvent me(QEvent::MouseButtonPress, event->position(), event->scenePosition(), event->globalPosition(), event->button(), event->buttons(), event->modifiers()); mousePressEvent(&me); return; @@ -598,7 +598,7 @@ void InstanceView::dragEnterEvent(QDragEnterEvent* event) if (!isDragEventAccepted(event)) { return; } - m_lastDragPosition = event->pos() + offset(); + m_lastDragPosition = event->position().toPoint() + offset(); viewport()->update(); event->accept(); } @@ -610,7 +610,7 @@ void InstanceView::dragMoveEvent(QDragMoveEvent* event) if (!isDragEventAccepted(event)) { return; } - m_lastDragPosition = event->pos() + offset(); + m_lastDragPosition = event->position().toPoint() + offset(); viewport()->update(); event->accept(); } @@ -636,7 +636,7 @@ void InstanceView::dropEvent(QDropEvent* event) if (event->source() == this) { if (event->possibleActions() & Qt::MoveAction) { - std::pair dropPos = rowDropPos(event->pos()); + std::pair dropPos = rowDropPos(event->position().toPoint()); const VisualGroup* group = dropPos.first; auto hitResult = dropPos.second; diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp index c1875bf98..3761c109e 100644 --- a/libraries/LocalPeer/src/LocalPeer.cpp +++ b/libraries/LocalPeer/src/LocalPeer.cpp @@ -75,7 +75,7 @@ ApplicationId ApplicationId::fromTraditionalApp() prefix.remove(QRegularExpression("[^a-zA-Z]")); prefix.truncate(6); QByteArray idc = protoId.toUtf8(); - quint16 idNum = qChecksum(idc.constData(), idc.size()); + quint16 idNum = qChecksum(idc); auto socketName = QLatin1String("pl") + prefix + QLatin1Char('-') + QString::number(idNum, 16).left(12); #if defined(Q_OS_WIN) if (!pProcessIdToSessionId) { From cddf00c61bae89885ac802f048fdcf36aa77baf5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Apr 2025 14:10:00 +0300 Subject: [PATCH 057/171] fix: beginResetModel called before endResetModel Signed-off-by: Trial97 --- launcher/VersionProxyModel.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index 3d9d95eb6..165dd4cb7 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -295,13 +295,11 @@ void VersionProxyModel::sourceDataChanged(const QModelIndex& source_top_left, co void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw) { auto replacing = dynamic_cast(replacingRaw); - beginResetModel(); m_columns.clear(); if (!replacing) { roles.clear(); filterModel->setSourceModel(replacing); - endResetModel(); return; } @@ -343,8 +341,6 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw) hasLatest = true; } filterModel->setSourceModel(replacing); - - endResetModel(); } QModelIndex VersionProxyModel::getRecommended() const From cb591ba52ece20b79db70ee7e6d5919aab5c9a41 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 31 Dec 2024 17:25:44 +0200 Subject: [PATCH 058/171] chore:add qr code support for login Signed-off-by: Trial97 --- .gitmodules | 3 ++ CMakeLists.txt | 1 + COPYING.md | 9 ++++++ flake.lock | 19 ++++++++++- flake.nix | 7 ++++ launcher/CMakeLists.txt | 1 + launcher/resources/documents/documents.qrc | 1 - launcher/resources/documents/login-qr.svg | 8 ----- launcher/ui/dialogs/MSALoginDialog.cpp | 26 +++++++++------ libraries/README.md | 8 +++++ libraries/qt-qrcodegenerator/CMakeLists.txt | 32 +++++++++++++++++++ .../qt-qrcodegenerator/QR-Code-generator | 1 + libraries/qt-qrcodegenerator/qr.cpp | 29 +++++++++++++++++ libraries/qt-qrcodegenerator/qr.h | 8 +++++ nix/unwrapped.nix | 6 ++-- 15 files changed, 138 insertions(+), 21 deletions(-) delete mode 100644 launcher/resources/documents/login-qr.svg create mode 100644 libraries/qt-qrcodegenerator/CMakeLists.txt create mode 160000 libraries/qt-qrcodegenerator/QR-Code-generator create mode 100644 libraries/qt-qrcodegenerator/qr.cpp create mode 100644 libraries/qt-qrcodegenerator/qr.h diff --git a/.gitmodules b/.gitmodules index 7ad40becb..0c56d8768 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "flatpak/shared-modules"] path = flatpak/shared-modules url = https://github.com/flathub/shared-modules.git +[submodule "libraries/qt-qrcodegenerator/QR-Code-generator"] + path = libraries/qt-qrcodegenerator/QR-Code-generator + url = https://github.com/nayuki/QR-Code-generator diff --git a/CMakeLists.txt b/CMakeLists.txt index 10153c3ec..91e10daf7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -503,6 +503,7 @@ add_subdirectory(libraries/libnbtplusplus) add_subdirectory(libraries/systeminfo) # system information library add_subdirectory(libraries/launcher) # java based launcher part for Minecraft add_subdirectory(libraries/javacheck) # java compatibility checker +add_subdirectory(libraries/qt-qrcodegenerator) # qr code generator if(FORCE_BUNDLED_ZLIB) message(STATUS "Using bundled zlib") diff --git a/COPYING.md b/COPYING.md index f1f0b3a70..f742c0132 100644 --- a/COPYING.md +++ b/COPYING.md @@ -403,3 +403,12 @@ You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . + +## qt-qrcodegenerator (`libraries/qt-qrcodegenerator`) + + Copyright © 2024 Project Nayuki. (MIT License) + https://www.nayuki.io/page/qr-code-generator-library + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + - The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software. diff --git a/flake.lock b/flake.lock index 2d79b8335..070d069e5 100644 --- a/flake.lock +++ b/flake.lock @@ -32,10 +32,27 @@ "type": "github" } }, + "qt-qrcodegenerator": { + "flake": false, + "locked": { + "lastModified": 1731907326, + "narHash": "sha256-5+iYwsbX8wjKZPCy7ENj5HCYgOqzeSNLs/YrX2Vc7CQ=", + "owner": "nayuki", + "repo": "QR-Code-generator", + "rev": "f40366c40d8d1956081f7ec643d240c02a81df52", + "type": "github" + }, + "original": { + "owner": "nayuki", + "repo": "QR-Code-generator", + "type": "github" + } + }, "root": { "inputs": { "libnbtplusplus": "libnbtplusplus", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "qt-qrcodegenerator": "qt-qrcodegenerator" } } }, diff --git a/flake.nix b/flake.nix index fd3003bc4..2c7c69b3d 100644 --- a/flake.nix +++ b/flake.nix @@ -15,6 +15,11 @@ url = "github:PrismLauncher/libnbtplusplus"; flake = false; }; + + qt-qrcodegenerator = { + url = "github:nayuki/QR-Code-generator"; + flake = false; + }; }; outputs = @@ -22,6 +27,7 @@ self, nixpkgs, libnbtplusplus, + qt-qrcodegenerator, }: let @@ -167,6 +173,7 @@ prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix { inherit libnbtplusplus + qt-qrcodegenerator self ; }; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 30d657f9e..e10921d3e 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1289,6 +1289,7 @@ target_link_libraries(Launcher_logic qdcss BuildConfig Qt${QT_VERSION_MAJOR}::Widgets + qrcode ) if (UNIX AND NOT CYGWIN AND NOT APPLE) diff --git a/launcher/resources/documents/documents.qrc b/launcher/resources/documents/documents.qrc index 489d1d5a2..007efcde3 100644 --- a/launcher/resources/documents/documents.qrc +++ b/launcher/resources/documents/documents.qrc @@ -2,7 +2,6 @@ ../../../COPYING.md - login-qr.svg diff --git a/launcher/resources/documents/login-qr.svg b/launcher/resources/documents/login-qr.svg deleted file mode 100644 index 1b88e3c83..000000000 --- a/launcher/resources/documents/login-qr.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index 40d1eff1e..83f46294d 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -36,6 +36,7 @@ #include "MSALoginDialog.h" #include "Application.h" +#include "qr.h" #include "ui_MSALoginDialog.h" #include "DesktopServices.h" @@ -60,7 +61,6 @@ MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MS ui->code->setFont(font); connect(ui->copyCode, &QPushButton::clicked, this, [this] { QApplication::clipboard()->setText(ui->code->text()); }); - ui->qr->setPixmap(QIcon((":/documents/login-qr.svg")).pixmap(QSize(150, 150))); connect(ui->loginButton, &QPushButton::clicked, this, [this] { if (m_url.isValid()) { if (!DesktopServices::openUrl(m_url)) { @@ -139,19 +139,27 @@ void MSALoginDialog::authorizeWithBrowser(const QUrl& url) m_url = url; } -void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn) +void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, [[maybe_unused]] int expiresIn) { ui->stackedWidget->setCurrentIndex(1); const auto linkString = QString("%2").arg(url, url); - ui->code->setText(code); - auto isDefaultUrl = url == "https://www.microsoft.com/link"; - ui->qr->setVisible(isDefaultUrl); - if (isDefaultUrl) { - ui->qrMessage->setText(tr("Open %1 or scan the QR and enter the above code.").arg(linkString)); - } else { - ui->qrMessage->setText(tr("Open %1 and enter the above code.").arg(linkString)); + if (url == "https://www.microsoft.com/link" && !code.isEmpty()) { + url += QString("?otc=%1").arg(code); } + ui->code->setText(code); + + auto size = QSize(150, 150); + QPixmap pixmap(size); + pixmap.fill(Qt::white); + + QPainter painter(&pixmap); + paintQR(painter, size, url, Qt::black); + + // Set the generated pixmap to the label + ui->qr->setPixmap(pixmap); + + ui->qrMessage->setText(tr("Open %1 or scan the QR and enter the above code if needed.").arg(linkString)); } void MSALoginDialog::onDeviceFlowStatus(QString status) diff --git a/libraries/README.md b/libraries/README.md index 3c5f5a4ab..5f7b685e5 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -99,6 +99,14 @@ Canonical implementation of the murmur2 hash, taken from [SMHasher](https://gith Public domain (the author disclaimed the copyright). +## qt-qrcodegenerator + +A simple library for generating QR codes + +See [github repo](https://github.com/nayuki/QR-Code-generator). + +MIT + ## quazip A zip manipulation library. diff --git a/libraries/qt-qrcodegenerator/CMakeLists.txt b/libraries/qt-qrcodegenerator/CMakeLists.txt new file mode 100644 index 000000000..e18da0e71 --- /dev/null +++ b/libraries/qt-qrcodegenerator/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.6) + +project(qrcode) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_CXX_STANDARD_REQUIRED true) +set(CMAKE_C_STANDARD_REQUIRED true) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_C_STANDARD 11) + + +if(QT_VERSION_MAJOR EQUAL 5) + find_package(Qt5 COMPONENTS Core Gui REQUIRED) +elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 COMPONENTS Core Gui Core5Compat REQUIRED) + list(APPEND systeminfo_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) +endif() + +add_library(qrcode STATIC qr.h qr.cpp QR-Code-generator/cpp/qrcodegen.cpp QR-Code-generator/cpp/qrcodegen.hpp ) + +target_link_libraries(qrcode Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui ${systeminfo_LIBS}) + + +# needed for statically linked qrcode in shared libs on x86_64 +set_target_properties(qrcode + PROPERTIES POSITION_INDEPENDENT_CODE TRUE +) + +target_include_directories(qrcode PUBLIC ./ PRIVATE QR-Code-generator/cpp/) + diff --git a/libraries/qt-qrcodegenerator/QR-Code-generator b/libraries/qt-qrcodegenerator/QR-Code-generator new file mode 160000 index 000000000..f40366c40 --- /dev/null +++ b/libraries/qt-qrcodegenerator/QR-Code-generator @@ -0,0 +1 @@ +Subproject commit f40366c40d8d1956081f7ec643d240c02a81df52 diff --git a/libraries/qt-qrcodegenerator/qr.cpp b/libraries/qt-qrcodegenerator/qr.cpp new file mode 100644 index 000000000..69bfb6da5 --- /dev/null +++ b/libraries/qt-qrcodegenerator/qr.cpp @@ -0,0 +1,29 @@ + +#include "qr.h" +#include "qrcodegen.hpp" + +void paintQR(QPainter& painter, const QSize sz, const QString& data, QColor fg) +{ + // NOTE: At this point you will use the API to get the encoding and format you want, instead of my hardcoded stuff: + qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(data.toUtf8().constData(), qrcodegen::QrCode::Ecc::LOW); + const int s = qr.getSize() > 0 ? qr.getSize() : 1; + const double w = sz.width(); + const double h = sz.height(); + const double aspect = w / h; + const double size = ((aspect > 1.0) ? h : w); + const double scale = size / (s + 2); + // NOTE: For performance reasons my implementation only draws the foreground parts in supplied color. + // It expects background to be prepared already (in white or whatever is preferred). + painter.setPen(Qt::NoPen); + painter.setBrush(fg); + for (int y = 0; y < s; y++) { + for (int x = 0; x < s; x++) { + const int color = qr.getModule(x, y); // 0 for white, 1 for black + if (0 != color) { + const double rx1 = (x + 1) * scale, ry1 = (y + 1) * scale; + QRectF r(rx1, ry1, scale, scale); + painter.drawRects(&r, 1); + } + } + } +} \ No newline at end of file diff --git a/libraries/qt-qrcodegenerator/qr.h b/libraries/qt-qrcodegenerator/qr.h new file mode 100644 index 000000000..290d49001 --- /dev/null +++ b/libraries/qt-qrcodegenerator/qr.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include +#include + +// https://stackoverflow.com/questions/21400254/how-to-draw-a-qr-code-with-qt-in-native-c-c +void paintQR(QPainter& painter, const QSize sz, const QString& data, QColor fg); diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 93cda8e1a..060518242 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -9,16 +9,15 @@ jdk17, kdePackages, libnbtplusplus, + qt-qrcodegenerator, ninja, self, stripJavaArchivesHook, tomlplusplus, zlib, - msaClientID ? null, gamemodeSupport ? stdenv.hostPlatform.isLinux, }: - assert lib.assertMsg ( gamemodeSupport -> stdenv.hostPlatform.isLinux ) "gamemodeSupport is only available on Linux."; @@ -64,6 +63,9 @@ stdenv.mkDerivation { postUnpack = '' rm -rf source/libraries/libnbtplusplus ln -s ${libnbtplusplus} source/libraries/libnbtplusplus + + rm -rf source/libraries/qt-qrcodegenerator/QR-Code-generator + ln -s ${qt-qrcodegenerator} source/libraries/qt-qrcodegenerator/QR-Code-generator ''; nativeBuildInputs = [ From 5af06dec8522ea12719600deaa014137509ee8e5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 10 Apr 2025 22:49:29 +0300 Subject: [PATCH 059/171] chore: add getOrRegisterSetting function Signed-off-by: Trial97 --- launcher/minecraft/mod/ResourceFolderModel.cpp | 6 ++---- launcher/settings/SettingsObject.cpp | 5 +++++ launcher/settings/SettingsObject.h | 10 ++++++++++ launcher/ui/MainWindow.cpp | 5 +---- launcher/ui/pages/instance/ExternalResourcesPage.cpp | 5 +---- launcher/ui/pages/instance/ScreenshotsPage.cpp | 5 +---- launcher/ui/pages/instance/ServersPage.cpp | 5 +---- launcher/ui/pages/instance/VersionPage.cpp | 5 +---- launcher/ui/pages/instance/WorldListPage.cpp | 5 +---- 9 files changed, 23 insertions(+), 28 deletions(-) diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index d4900616b..5f4748b92 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -593,8 +593,7 @@ void ResourceFolderModel::setupHeaderAction(QAction* act, int column) void ResourceFolderModel::saveColumns(QTreeView* tree) { auto const setting_name = QString("UI/%1_Page/Columns").arg(id()); - auto setting = (m_instance->settings()->contains(setting_name)) ? m_instance->settings()->getSetting(setting_name) - : m_instance->settings()->registerSetting(setting_name); + auto setting = m_instance->settings()->getOrRegisterSetting(setting_name); setting->set(tree->header()->saveState()); } @@ -606,8 +605,7 @@ void ResourceFolderModel::loadColumns(QTreeView* tree) } auto const setting_name = QString("UI/%1_Page/Columns").arg(id()); - auto setting = (m_instance->settings()->contains(setting_name)) ? m_instance->settings()->getSetting(setting_name) - : m_instance->settings()->registerSetting(setting_name); + auto setting = m_instance->settings()->getOrRegisterSetting(setting_name); tree->header()->restoreState(setting->get().toByteArray()); } diff --git a/launcher/settings/SettingsObject.cpp b/launcher/settings/SettingsObject.cpp index 1e5dce251..0df22b42d 100644 --- a/launcher/settings/SettingsObject.cpp +++ b/launcher/settings/SettingsObject.cpp @@ -124,3 +124,8 @@ void SettingsObject::connectSignals(const Setting& setting) connect(&setting, &Setting::settingReset, this, &SettingsObject::resetSetting); connect(&setting, SIGNAL(settingReset(Setting)), this, SIGNAL(settingReset(const Setting&))); } + +std::shared_ptr SettingsObject::getOrRegisterSetting(const QString& id, QVariant defVal) +{ + return contains(id) ? getSetting(id) : registerSetting(id, defVal); +} diff --git a/launcher/settings/SettingsObject.h b/launcher/settings/SettingsObject.h index f133f2f7f..bd3f71b36 100644 --- a/launcher/settings/SettingsObject.h +++ b/launcher/settings/SettingsObject.h @@ -103,6 +103,16 @@ class SettingsObject : public QObject { */ std::shared_ptr getSetting(const QString& id) const; + /*! + * \brief Gets the setting with the given ID. + * \brief if is not registered yet it does that + * \param id The ID of the setting to get. + * \return A pointer to the setting with the given ID. + * Returns null if there is no setting with the given ID. + * \sa operator []() + */ + std::shared_ptr getOrRegisterSetting(const QString& id, QVariant defVal = QVariant()); + /*! * \brief Gets the value of the setting with the given ID. * \param id The ID of the setting to get. diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index ddf726373..0111d1253 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -176,10 +176,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi // restore the instance toolbar settings auto const setting_name = QString("WideBarVisibility_%1").arg(ui->instanceToolBar->objectName()); - if (!APPLICATION->settings()->contains(setting_name)) - instanceToolbarSetting = APPLICATION->settings()->registerSetting(setting_name); - else - instanceToolbarSetting = APPLICATION->settings()->getSetting(setting_name); + instanceToolbarSetting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->instanceToolBar->setVisibilityState(instanceToolbarSetting->get().toByteArray()); diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 50217f982..be65e6948 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -146,10 +146,7 @@ void ExternalResourcesPage::openedImpl() m_model->startWatching(); auto const setting_name = QString("WideBarVisibility_%1").arg(id()); - if (!APPLICATION->settings()->contains(setting_name)) - m_wide_bar_setting = APPLICATION->settings()->registerSetting(setting_name); - else - m_wide_bar_setting = APPLICATION->settings()->getSetting(setting_name); + m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->actionsToolbar->setVisibilityState(m_wide_bar_setting->get().toByteArray()); } diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index b619a07b8..fa568c794 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -559,10 +559,7 @@ void ScreenshotsPage::openedImpl() } auto const setting_name = QString("WideBarVisibility_%1").arg(id()); - if (!APPLICATION->settings()->contains(setting_name)) - m_wide_bar_setting = APPLICATION->settings()->registerSetting(setting_name); - else - m_wide_bar_setting = APPLICATION->settings()->getSetting(setting_name); + m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray()); } diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 136fb47c7..f8cd8304f 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -711,10 +711,7 @@ void ServersPage::openedImpl() m_model->observe(); auto const setting_name = QString("WideBarVisibility_%1").arg(id()); - if (!APPLICATION->settings()->contains(setting_name)) - m_wide_bar_setting = APPLICATION->settings()->registerSetting(setting_name); - else - m_wide_bar_setting = APPLICATION->settings()->getSetting(setting_name); + m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray()); diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index 975c44de2..a1eeb3d25 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -124,10 +124,7 @@ void VersionPage::retranslate() void VersionPage::openedImpl() { auto const setting_name = QString("WideBarVisibility_%1").arg(id()); - if (!APPLICATION->settings()->contains(setting_name)) - m_wide_bar_setting = APPLICATION->settings()->registerSetting(setting_name); - else - m_wide_bar_setting = APPLICATION->settings()->getSetting(setting_name); + m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray()); } diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index dd7486a6c..9e1a0fb55 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -119,10 +119,7 @@ void WorldListPage::openedImpl() } auto const setting_name = QString("WideBarVisibility_%1").arg(id()); - if (!APPLICATION->settings()->contains(setting_name)) - m_wide_bar_setting = APPLICATION->settings()->registerSetting(setting_name); - else - m_wide_bar_setting = APPLICATION->settings()->getSetting(setting_name); + m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray()); } From 0948d3598b9dff7b7ad0979f42f65c3302d037ad Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Apr 2025 16:39:36 +0300 Subject: [PATCH 060/171] chore: improve log display Signed-off-by: Trial97 --- launcher/GZip.cpp | 64 +++++++++++++++++ launcher/GZip.h | 20 ++++++ launcher/launch/LogModel.cpp | 5 ++ launcher/launch/LogModel.h | 1 + launcher/ui/pages/instance/OtherLogsPage.cpp | 74 ++++++++++++++------ launcher/ui/widgets/LogView.cpp | 5 +- 6 files changed, 145 insertions(+), 24 deletions(-) diff --git a/launcher/GZip.cpp b/launcher/GZip.cpp index 1c2539e08..eaf1c9035 100644 --- a/launcher/GZip.cpp +++ b/launcher/GZip.cpp @@ -36,6 +36,8 @@ #include "GZip.h" #include #include +#include +#include bool GZip::unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes) { @@ -136,3 +138,65 @@ bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes) } return true; } + +GZipStream::GZipStream(const QString& filePath) : GZipStream(new QFile(filePath)) {} + +GZipStream::GZipStream(QFile* file) : m_file(file) {} + +bool GZipStream::initStream() +{ + memset(&m_strm, 0, sizeof(m_strm)); + return (inflateInit2(&m_strm, 16 + MAX_WBITS) == Z_OK); +} + +bool GZipStream::unzipBlockByBlock(QByteArray& uncompressedBytes) +{ + uncompressedBytes.clear(); + if (!m_file->isOpen()) { + if (!m_file->open(QIODevice::ReadOnly)) { + qWarning() << "Failed to open file:" << (m_file->fileName()); + return false; + } + } + + if (!m_strm.state && !initStream()) { + return false; + } + + QByteArray compressedBlock; + unsigned int blockSize = 4096; + + compressedBlock = m_file->read(blockSize); + if (compressedBlock.isEmpty()) { + return true; // End of file reached + } + + bool done = processBlock(compressedBlock, uncompressedBytes); + if (inflateEnd(&m_strm) != Z_OK || !done) { + return false; + } + return done; +} + +bool GZipStream::processBlock(const QByteArray& compressedBlock, QByteArray& uncompressedBytes) +{ + m_strm.next_in = (Bytef*)compressedBlock.data(); + m_strm.avail_in = compressedBlock.size(); + + unsigned int uncompLength = uncompressedBytes.size(); + if (m_strm.total_out >= uncompLength) { + uncompressedBytes.resize(uncompLength * 2); + uncompLength *= 2; + } + + m_strm.next_out = reinterpret_cast(uncompressedBytes.data() + m_strm.total_out); + m_strm.avail_out = uncompLength - m_strm.total_out; + + int err = inflate(&m_strm, Z_NO_FLUSH); + if (err != Z_OK && err != Z_STREAM_END) { + qWarning() << "Decompression failed with error code" << err; + return false; + } + + return true; +} diff --git a/launcher/GZip.h b/launcher/GZip.h index 0bdb70407..dd4162839 100644 --- a/launcher/GZip.h +++ b/launcher/GZip.h @@ -1,8 +1,28 @@ #pragma once +#include #include +#include class GZip { public: static bool unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes); static bool zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes); }; + +class GZipStream { + public: + explicit GZipStream(const QString& filePath); + explicit GZipStream(QFile* file); + + // Decompress the next block and return the decompressed data + bool unzipBlockByBlock(QByteArray& uncompressedBytes); + + private: + bool initStream(); + + bool processBlock(const QByteArray& compressedBlock, QByteArray& uncompressedBytes); + + private: + QFile* m_file; + z_stream m_strm; +}; diff --git a/launcher/launch/LogModel.cpp b/launcher/launch/LogModel.cpp index dd32d46a2..45aac6099 100644 --- a/launcher/launch/LogModel.cpp +++ b/launcher/launch/LogModel.cpp @@ -161,3 +161,8 @@ bool LogModel::colorLines() const { return m_colorLines; } + +bool LogModel::isOverFlow() +{ + return m_numLines >= m_maxLines && m_stopOnOverflow; +} diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h index 6c2a8cff3..ba7b14487 100644 --- a/launcher/launch/LogModel.h +++ b/launcher/launch/LogModel.h @@ -24,6 +24,7 @@ class LogModel : public QAbstractListModel { void setMaxLines(int maxLines); void setStopOnOverflow(bool stop); void setOverflowMessage(const QString& overflowMessage); + bool isOverFlow(); void setLineWrap(bool state); bool wrapLines() const; diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 0d94843c0..b7d10a41a 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -151,6 +151,48 @@ void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) } } +class ReadLineAbstract { + public: + ReadLineAbstract(QFile* file) : m_file(file) + { + if (file->fileName().endsWith(".gz")) + m_gz = new GZipStream(file); + } + ~ReadLineAbstract() { delete m_gz; } + + QString readLine() + { + if (!m_gz) + return QString::fromUtf8(m_file->readLine()); + QString line; + for (;;) { + if (!m_decodedData.isEmpty()) { + int newlineIndex = m_decodedData.indexOf('\n'); + if (newlineIndex != -1) { + line += QString::fromUtf8(m_decodedData).left(newlineIndex); + m_decodedData.remove(0, newlineIndex + 1); + return line; + } + + line += QString::fromUtf8(m_decodedData); + m_decodedData.clear(); + } + + if (!m_gz->unzipBlockByBlock(m_decodedData)) { // If error occurs during unzipping + m_decodedData.clear(); + return QObject::tr("The content of the file(%1) could not be decoded.").arg(m_file->fileName()); + } + } + } + + bool done() { return m_gz ? m_decodedData.isEmpty() : m_file->atEnd(); } + + private: + QFile* m_file; + GZipStream* m_gz = nullptr; + QByteArray m_decodedData; +}; + void OtherLogsPage::on_btnReload_clicked() { if (m_currentFile.isEmpty()) { @@ -178,35 +220,17 @@ void OtherLogsPage::on_btnReload_clicked() showTooBig(); return; } - QString content; - if (file.fileName().endsWith(".gz")) { - QByteArray temp; - if (!GZip::unzip(file.readAll(), temp)) { - setPlainText(tr("The file (%1) is not readable.").arg(file.fileName())); - return; - } - content = QString::fromUtf8(temp); - } else { - content = QString::fromUtf8(file.readAll()); - } - if (content.size() >= 50000000ll) { - showTooBig(); - return; - } - // If the file is not too big for display, but too slow for syntax highlighting, just show content as plain text - if (content.size() >= 10000000ll || content.isEmpty()) { - setPlainText(content); - return; - } + ReadLineAbstract stream(&file); // Try to determine a level for each line - if (content.back() == '\n') - content = content.remove(content.size() - 1, 1); ui->text->clear(); ui->text->setModel(nullptr); m_model->clear(); - for (auto& line : content.split('\n')) { + auto line = stream.readLine(); + while (!stream.done()) { // just read until the model is full or the file ended + if (line.back() == '\n') + line = line.remove(line.size() - 1, 1); MessageLevel::Enum level = MessageLevel::Unknown; // if the launcher part set a log level, use it @@ -221,6 +245,10 @@ void OtherLogsPage::on_btnReload_clicked() } m_model->append(level, line); + if (m_model->isOverFlow()) + break; + + line = stream.readLine(); } ui->text->setModel(m_proxy); ui->text->scrollToBottom(); diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp index 181893af4..df25a2434 100644 --- a/launcher/ui/widgets/LogView.cpp +++ b/launcher/ui/widgets/LogView.cpp @@ -42,6 +42,7 @@ LogView::LogView(QWidget* parent) : QPlainTextEdit(parent) { setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); m_defaultFormat = new QTextCharFormat(currentCharFormat()); + setUndoRedoEnabled(false); } LogView::~LogView() @@ -129,6 +130,8 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last) QTextDocument document; QTextCursor cursor(&document); + cursor.movePosition(QTextCursor::End); + cursor.beginEditBlock(); for (int i = first; i <= last; i++) { auto idx = m_model->index(i, 0, parent); auto text = m_model->data(idx, Qt::DisplayRole).toString(); @@ -145,10 +148,10 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last) if (bg.isValid() && m_colorLines) { format.setBackground(bg.value()); } - cursor.movePosition(QTextCursor::End); cursor.insertText(text, format); cursor.insertBlock(); } + cursor.endEditBlock(); QTextDocumentFragment fragment(&document); QTextCursor workCursor = textCursor(); From 166b2cb286e1af10ca38df6e7b398216fc39ad6a Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 12:26:47 +0100 Subject: [PATCH 061/171] Tweak log formatting Signed-off-by: TheKodeToad --- launcher/Application.cpp | 56 ++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 70ffce12f..018309e63 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -125,9 +125,9 @@ #include #include -#include #include #include +#include #include "SysInfo.h" #ifdef Q_OS_LINUX @@ -176,23 +176,20 @@ static bool isANSIColorConsole; static QString defaultLogFormat = QStringLiteral( "%{time process}" " " - "%{if-debug}D%{endif}" - "%{if-info}I%{endif}" - "%{if-warning}W%{endif}" - "%{if-critical}C%{endif}" - "%{if-fatal}F%{endif}" + "%{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}" - " " - "|" - " " - "%{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" @@ -200,30 +197,26 @@ static QString defaultLogFormat = QStringLiteral( #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( - "%{time process}" + ansi_faint "%{time process}" ansi_reset " " - "%{if-debug}" ansi_bold ansi_blue_fg "D" ansi_reset - "%{endif}" - "%{if-info}" ansi_bold ansi_green_fg "I" ansi_reset - "%{endif}" - "%{if-warning}" ansi_bold ansi_yellow_fg "W" ansi_reset - "%{endif}" - "%{if-critical}" ansi_bold ansi_red_fg "C" ansi_reset - "%{endif}" - "%{if-fatal}" ansi_bold ansi_red_fg "F" ansi_reset - "%{endif}" + "%{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}" " " - "|" - " " ansi_faint ansi_italic "%{function}:%{line}" ansi_reset + "%{if-category}" ansi_bold "[%{category}]" ansi_reset_bold " %{endif}" + "%{message}" " " - "|" - " " - "%{if-category}[" ansi_bold ansi_purple_fg "%{category}" ansi_reset - "]: %{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 @@ -232,6 +225,7 @@ static QString ansiLogFormat = QStringLiteral( #undef ansi_italic #undef ansi_faint #undef ansi_bold +#undef ansi_reset_bold #undef ansi_reset namespace { From f3073406908cd318751fd393c78b265c8b3a8085 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 15:56:20 +0100 Subject: [PATCH 062/171] Fix formatting Signed-off-by: TheKodeToad --- launcher/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 018309e63..cfe028279 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -125,9 +125,9 @@ #include #include +#include #include #include -#include #include "SysInfo.h" #ifdef Q_OS_LINUX From 0f847d66820c73701e336c59f7740d90e8b4cae8 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 19:40:19 +0100 Subject: [PATCH 063/171] Fix mistakes Signed-off-by: TheKodeToad --- launcher/minecraft/MinecraftInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 7b02e93ba..6f367a1bd 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1019,7 +1019,7 @@ MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLev // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * static const QRegularExpression JAVA_EXCEPTION( - R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\\d_$]*\.)+[a-zA-Z_$][a-zA-Z\\d_$]*)"); + R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); if (line.contains(JAVA_EXCEPTION)) return MessageLevel::Error; From d5db974008f6991c1d7aaec4fb47cfddbdebf04f Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 19:52:30 +0100 Subject: [PATCH 064/171] Shallow search and lazy loading for Other Logs page Signed-off-by: TheKodeToad --- launcher/BaseInstance.h | 7 +- launcher/InstancePageProvider.h | 5 +- launcher/NullInstance.h | 4 +- launcher/minecraft/MinecraftInstance.cpp | 14 +--- launcher/minecraft/MinecraftInstance.h | 4 +- launcher/ui/pages/instance/OtherLogsPage.cpp | 85 ++++++++++++++------ launcher/ui/pages/instance/OtherLogsPage.h | 13 ++- 7 files changed, 78 insertions(+), 54 deletions(-) diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 2a2b4dc4a..f93321922 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -198,15 +198,10 @@ class BaseInstance : public QObject, public std::enable_shared_from_thisgameRoot(), "screenshots"))); values.append(new InstanceSettingsPage(onesix)); - auto logMatcher = inst->getLogFileMatcher(); - if (logMatcher) { - values.append(new OtherLogsPage(inst, logMatcher)); - } + values.append(new OtherLogsPage(inst)); return values; } diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h index 3d01c9d33..93bab6c8b 100644 --- a/launcher/NullInstance.h +++ b/launcher/NullInstance.h @@ -35,6 +35,7 @@ */ #pragma once +#include #include "BaseInstance.h" #include "launch/LaunchTask.h" @@ -57,8 +58,7 @@ class NullInstance : public BaseInstance { QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); } QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); } QMap getVariables() override { return QMap(); } - IPathMatcher::Ptr getLogFileMatcher() override { return nullptr; } - QString getLogFileRoot() override { return instanceRoot(); } + QStringList getLogFileSearchPaths() override { return {}; } QString typeName() const override { return "Null"; } bool canExport() const override { return false; } bool canEdit() const override { return false; } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 6f367a1bd..121b8035c 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1055,19 +1055,9 @@ MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLev return level; } -IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher() +QStringList MinecraftInstance::getLogFileSearchPaths() { - auto combined = std::make_shared(); - combined->add(std::make_shared(".*\\.log(\\.[0-9]*)?(\\.gz)?$")); - combined->add(std::make_shared("crash-.*\\.txt")); - combined->add(std::make_shared("IDMap dump.*\\.txt$")); - combined->add(std::make_shared("ModLoader\\.txt(\\..*)?$")); - return combined; -} - -QString MinecraftInstance::getLogFileRoot() -{ - return gameRoot(); + return { FS::PathCombine(gameRoot(), "logs"), FS::PathCombine(gameRoot(), "crash-reports"), gameRoot() }; } QString MinecraftInstance::getStatusbarDescription() diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 5d9bb45ef..677ea5b84 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -142,9 +142,7 @@ class MinecraftInstance : public BaseInstance { /// guess log level from a line of minecraft log MessageLevel::Enum guessLevel(const QString& line, MessageLevel::Enum level) override; - IPathMatcher::Ptr getLogFileMatcher() override; - - QString getLogFileRoot() override; + QStringList getLogFileSearchPaths() override; QString getStatusbarDescription() override; diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index b7d10a41a..671e0ab7f 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -43,16 +43,18 @@ #include #include +#include +#include +#include +#include #include -#include "RecursiveFileSystemWatcher.h" -OtherLogsPage::OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, QWidget* parent) +OtherLogsPage::OtherLogsPage(InstancePtr instance, QWidget* parent) : QWidget(parent) , ui(new Ui::OtherLogsPage) , m_instance(instance) - , m_path(instance->getLogFileRoot()) - , m_fileFilter(fileFilter) - , m_watcher(new RecursiveFileSystemWatcher(this)) + , m_basePath(instance->gameRoot()) + , m_logSearchPaths(instance->getLogFileSearchPaths()) , m_model(new LogModel(this)) { ui->setupUi(this); @@ -78,11 +80,7 @@ OtherLogsPage::OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, m_model->setOverflowMessage(tr("Cannot display this log since the log length surpassed %1 lines.").arg(m_model->getMaxLines())); m_proxy->setSourceModel(m_model.get()); - m_watcher->setMatcher(fileFilter); - m_watcher->setRootDir(QDir::current().absoluteFilePath(m_path)); - - connect(m_watcher, &RecursiveFileSystemWatcher::filesChanged, this, &OtherLogsPage::populateSelectLogBox); - populateSelectLogBox(); + connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &OtherLogsPage::populateSelectLogBox); auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); connect(findShortcut, &QShortcut::activated, this, &OtherLogsPage::findActivated); @@ -108,22 +106,39 @@ void OtherLogsPage::retranslate() void OtherLogsPage::openedImpl() { - m_watcher->enable(); + const QStringList failedPaths = m_watcher.addPaths(m_logSearchPaths); + + for (const QString& path : m_logSearchPaths) { + if (failedPaths.contains(path)) + qDebug() << "Failed to start watching" << path; + else + qDebug() << "Started watching" << path; + } + + populateSelectLogBox(); } + void OtherLogsPage::closedImpl() { - m_watcher->disable(); + const QStringList failedPaths = m_watcher.removePaths(m_logSearchPaths); + + for (const QString& path : m_logSearchPaths) { + if (failedPaths.contains(path)) + qDebug() << "Failed to stop watching" << path; + else + qDebug() << "Stopped watching" << path; + } } void OtherLogsPage::populateSelectLogBox() { + QString prevCurrentFile = m_currentFile; + ui->selectLogBox->clear(); - ui->selectLogBox->addItems(m_watcher->files()); - if (m_currentFile.isEmpty()) { - setControlsEnabled(false); - ui->selectLogBox->setCurrentIndex(-1); - } else { - const int index = ui->selectLogBox->findText(m_currentFile); + ui->selectLogBox->addItems(getPaths()); + + if (!prevCurrentFile.isEmpty()) { + const int index = ui->selectLogBox->findText(prevCurrentFile); if (index != -1) { ui->selectLogBox->setCurrentIndex(index); setControlsEnabled(true); @@ -140,7 +155,7 @@ void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) file = ui->selectLogBox->itemText(index); } - if (file.isEmpty() || !QFile::exists(FS::PathCombine(m_path, file))) { + if (file.isEmpty() || !QFile::exists(FS::PathCombine(m_basePath, file))) { m_currentFile = QString(); ui->text->clear(); setControlsEnabled(false); @@ -199,7 +214,7 @@ void OtherLogsPage::on_btnReload_clicked() setControlsEnabled(false); return; } - QFile file(FS::PathCombine(m_path, m_currentFile)); + QFile file(FS::PathCombine(m_basePath, m_currentFile)); if (!file.open(QFile::ReadOnly)) { setControlsEnabled(false); ui->btnReload->setEnabled(true); // allow reload @@ -284,7 +299,7 @@ void OtherLogsPage::on_btnDelete_clicked() QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { return; } - QFile file(FS::PathCombine(m_path, m_currentFile)); + QFile file(FS::PathCombine(m_basePath, m_currentFile)); if (FS::trash(file.fileName())) { return; @@ -297,7 +312,7 @@ void OtherLogsPage::on_btnDelete_clicked() void OtherLogsPage::on_btnClean_clicked() { - auto toDelete = m_watcher->files(); + auto toDelete = getPaths(); if (toDelete.isEmpty()) { return; } @@ -320,7 +335,9 @@ void OtherLogsPage::on_btnClean_clicked() } QStringList failed; for (auto item : toDelete) { - QFile file(FS::PathCombine(m_path, item)); + QString absolutePath = FS::PathCombine(m_basePath, item); + QFile file(absolutePath); + qDebug() << "Deleting log" << absolutePath; if (FS::trash(file.fileName())) { continue; } @@ -374,6 +391,28 @@ void OtherLogsPage::setControlsEnabled(const bool enabled) ui->btnClean->setEnabled(enabled); } +QStringList OtherLogsPage::getPaths() +{ + QDir baseDir(m_basePath); + + QStringList result; + + for (QString searchPath : m_logSearchPaths) { + QDirIterator iterator(searchPath, QDir::Files | QDir::Readable); + + while (iterator.hasNext()) { + const QString name = iterator.next(); + + if (!name.endsWith(".log") && !name.endsWith(".log.gz")) + continue; + + result.append(baseDir.relativeFilePath(name)); + } + } + + return result; +} + void OtherLogsPage::on_findButton_clicked() { auto modifiers = QApplication::keyboardModifiers(); diff --git a/launcher/ui/pages/instance/OtherLogsPage.h b/launcher/ui/pages/instance/OtherLogsPage.h index 9394ab9b8..fedb2506c 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.h +++ b/launcher/ui/pages/instance/OtherLogsPage.h @@ -39,6 +39,8 @@ #include #include +#include +#include #include "LogPage.h" #include "ui/pages/BasePage.h" @@ -52,7 +54,7 @@ class OtherLogsPage : public QWidget, public BasePage { Q_OBJECT public: - explicit OtherLogsPage(InstancePtr instance, IPathMatcher::Ptr fileFilter, QWidget* parent = 0); + explicit OtherLogsPage(InstancePtr instance, QWidget* parent = 0); ~OtherLogsPage(); QString id() const override { return "logs"; } @@ -85,13 +87,16 @@ class OtherLogsPage : public QWidget, public BasePage { private: void setControlsEnabled(bool enabled); + QStringList getPaths(); + private: Ui::OtherLogsPage* ui; InstancePtr m_instance; - QString m_path; + /** Path to display log paths relative to. */ + QString m_basePath; + QStringList m_logSearchPaths; QString m_currentFile; - IPathMatcher::Ptr m_fileFilter; - RecursiveFileSystemWatcher* m_watcher; + QFileSystemWatcher m_watcher; LogFormatProxyModel* m_proxy; shared_qobject_ptr m_model; From 01efd5b5d8b7f37b2e227cc8c448a78cccd84a24 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 21:24:25 +0100 Subject: [PATCH 065/171] Fix double load (again lol) Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/OtherLogsPage.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 671e0ab7f..ec597497d 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -132,20 +132,25 @@ void OtherLogsPage::closedImpl() void OtherLogsPage::populateSelectLogBox() { - QString prevCurrentFile = m_currentFile; + const QString prevCurrentFile = m_currentFile; + ui->selectLogBox->blockSignals(true); ui->selectLogBox->clear(); ui->selectLogBox->addItems(getPaths()); + ui->selectLogBox->blockSignals(false); if (!prevCurrentFile.isEmpty()) { const int index = ui->selectLogBox->findText(prevCurrentFile); if (index != -1) { ui->selectLogBox->setCurrentIndex(index); setControlsEnabled(true); + return; } else { setControlsEnabled(false); } } + + on_selectLogBox_currentIndexChanged(ui->selectLogBox->currentIndex()); } void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) From 7f2f62afa876d5fe40bdf5a4d08bf52a7d56624c Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Fri, 18 Apr 2025 16:50:25 -0400 Subject: [PATCH 066/171] ci(blocked-prs): use pull_request_target This needs to run in the context of our repo to have access to it's secrets Signed-off-by: Seth Flynn --- .github/workflows/merge-blocking-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index d6410f997..b77313b09 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -1,7 +1,7 @@ name: Merged Blocking Pull Request Automation on: - pull_request: + pull_request_target: types: - closed From e9f7ba188bfb40785de59dee269dbe7b8f9e0dd0 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Fri, 18 Apr 2025 16:53:16 -0400 Subject: [PATCH 067/171] ci(blocked-prs): use object filter to check pr label https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#example-using-an-object-filter Signed-off-by: Seth Flynn --- .github/workflows/merge-blocking-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index b77313b09..fe99e1c14 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -12,7 +12,7 @@ jobs: # a pr that was a `blocking:` label was merged. # find the open pr's it was blocked by and trigger a refresh of their state - if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), 'blocking' ) + if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'blocking') steps: - name: Generate token From b6e48ac641b684c9f977454c8c4c9d90c5aea408 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Fri, 18 Apr 2025 16:56:19 -0400 Subject: [PATCH 068/171] ci(blocked-prs): allow workflow_dispatch on blocking prs Signed-off-by: Seth Flynn --- .github/workflows/merge-blocking-pr.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index fe99e1c14..d37c33761 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -4,6 +4,12 @@ on: pull_request_target: types: - closed + workflow_dispatch: + inputs: + pr_id: + description: Local Pull Request number to work on + required: true + type: number jobs: update-blocked-status: @@ -12,7 +18,7 @@ jobs: # a pr that was a `blocking:` label was merged. # find the open pr's it was blocked by and trigger a refresh of their state - if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'blocking') + if: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'blocking') }} steps: - name: Generate token @@ -26,11 +32,11 @@ jobs: id: gather_deps env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} - PR_NUMBER: ${{ github.event.pull_request.number }} + PR_NUMBER: ${{ inputs.pr_id || github.event.pull_request.number }} run: | blocked_prs=$( gh -R ${{ github.repository }} pr list --label 'blocked' --json 'number,body' \ - | jq -c --argjson pr "${{ github.event.pull_request.number }}" ' + | jq -c --argjson pr "$PR_NUMBER" ' reduce ( .[] | select( .body | scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") | From 95492cdeef86ec92da141a951c29037132715191 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:57:31 +0000 Subject: [PATCH 069/171] chore(deps): update cachix/install-nix-action digest to 754537a --- .github/workflows/update-flake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index cdd360589..f4b1c4f5d 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@d1ca217b388ee87b2507a9a93bf01368bde7cec2 # v31 + - uses: cachix/install-nix-action@754537aaedb35f72ab11a60cc162c49ef3016495 # v31 - uses: DeterminateSystems/update-flake-lock@v24 with: From d1c71075756845efe3c1730874f20f71ca52408d Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 19 Apr 2025 00:41:24 +0300 Subject: [PATCH 070/171] fix: gzip file parsing as a stream Signed-off-by: Trial97 --- launcher/GZip.cpp | 114 +++++++++++-------- launcher/GZip.h | 27 +---- launcher/ui/pages/instance/OtherLogsPage.cpp | 91 ++++++--------- 3 files changed, 107 insertions(+), 125 deletions(-) diff --git a/launcher/GZip.cpp b/launcher/GZip.cpp index eaf1c9035..29c71c012 100644 --- a/launcher/GZip.cpp +++ b/launcher/GZip.cpp @@ -139,64 +139,80 @@ bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes) return true; } -GZipStream::GZipStream(const QString& filePath) : GZipStream(new QFile(filePath)) {} - -GZipStream::GZipStream(QFile* file) : m_file(file) {} - -bool GZipStream::initStream() +int inf(QFile* source, std::function handleBlock) { - memset(&m_strm, 0, sizeof(m_strm)); - return (inflateInit2(&m_strm, 16 + MAX_WBITS) == Z_OK); -} + constexpr auto CHUNK = 16384; + int ret; + unsigned have; + z_stream strm; + memset(&strm, 0, sizeof(strm)); + char in[CHUNK]; + unsigned char out[CHUNK]; -bool GZipStream::unzipBlockByBlock(QByteArray& uncompressedBytes) -{ - uncompressedBytes.clear(); - if (!m_file->isOpen()) { - if (!m_file->open(QIODevice::ReadOnly)) { - qWarning() << "Failed to open file:" << (m_file->fileName()); - return false; + ret = inflateInit2(&strm, (16 + MAX_WBITS)); + if (ret != Z_OK) + return ret; + + /* decompress until deflate stream ends or end of file */ + do { + strm.avail_in = source->read(in, CHUNK); + if (source->error()) { + (void)inflateEnd(&strm); + return Z_ERRNO; } - } + if (strm.avail_in == 0) + break; + strm.next_in = reinterpret_cast(in); - if (!m_strm.state && !initStream()) { - return false; - } + /* run inflate() on input until output buffer not full */ + do { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR); /* state not clobbered */ + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + return ret; + } + have = CHUNK - strm.avail_out; + if (!handleBlock(QByteArray(reinterpret_cast(out), have))) { + (void)inflateEnd(&strm); + return Z_OK; + } - QByteArray compressedBlock; - unsigned int blockSize = 4096; + } while (strm.avail_out == 0); - compressedBlock = m_file->read(blockSize); - if (compressedBlock.isEmpty()) { - return true; // End of file reached - } + /* done when inflate() says it's done */ + } while (ret != Z_STREAM_END); - bool done = processBlock(compressedBlock, uncompressedBytes); - if (inflateEnd(&m_strm) != Z_OK || !done) { - return false; - } - return done; + /* clean up and return */ + (void)inflateEnd(&strm); + return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; } -bool GZipStream::processBlock(const QByteArray& compressedBlock, QByteArray& uncompressedBytes) +QString zerr(int ret) { - m_strm.next_in = (Bytef*)compressedBlock.data(); - m_strm.avail_in = compressedBlock.size(); - - unsigned int uncompLength = uncompressedBytes.size(); - if (m_strm.total_out >= uncompLength) { - uncompressedBytes.resize(uncompLength * 2); - uncompLength *= 2; + switch (ret) { + case Z_ERRNO: + return QObject::tr("error handling file"); + case Z_STREAM_ERROR: + return QObject::tr("invalid compression level"); + case Z_DATA_ERROR: + return QObject::tr("invalid or incomplete deflate data"); + case Z_MEM_ERROR: + return QObject::tr("out of memory"); + case Z_VERSION_ERROR: + return QObject::tr("zlib version mismatch!"); } - - m_strm.next_out = reinterpret_cast(uncompressedBytes.data() + m_strm.total_out); - m_strm.avail_out = uncompLength - m_strm.total_out; - - int err = inflate(&m_strm, Z_NO_FLUSH); - if (err != Z_OK && err != Z_STREAM_END) { - qWarning() << "Decompression failed with error code" << err; - return false; - } - - return true; + return {}; } + +QString GZip::readGzFileByBlocks(QFile* source, std::function handleBlock) +{ + auto ret = inf(source, handleBlock); + return zerr(ret); +} \ No newline at end of file diff --git a/launcher/GZip.h b/launcher/GZip.h index dd4162839..b736ca93f 100644 --- a/launcher/GZip.h +++ b/launcher/GZip.h @@ -1,28 +1,11 @@ #pragma once -#include #include #include -class GZip { - public: - static bool unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes); - static bool zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes); -}; +namespace GZip { -class GZipStream { - public: - explicit GZipStream(const QString& filePath); - explicit GZipStream(QFile* file); +bool unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes); +bool zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes); +QString readGzFileByBlocks(QFile* source, std::function handleBlock); - // Decompress the next block and return the decompressed data - bool unzipBlockByBlock(QByteArray& uncompressedBytes); - - private: - bool initStream(); - - bool processBlock(const QByteArray& compressedBlock, QByteArray& uncompressedBytes); - - private: - QFile* m_file; - z_stream m_strm; -}; +} // namespace GZip diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index b7d10a41a..0aeb942a8 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -151,54 +151,13 @@ void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index) } } -class ReadLineAbstract { - public: - ReadLineAbstract(QFile* file) : m_file(file) - { - if (file->fileName().endsWith(".gz")) - m_gz = new GZipStream(file); - } - ~ReadLineAbstract() { delete m_gz; } - - QString readLine() - { - if (!m_gz) - return QString::fromUtf8(m_file->readLine()); - QString line; - for (;;) { - if (!m_decodedData.isEmpty()) { - int newlineIndex = m_decodedData.indexOf('\n'); - if (newlineIndex != -1) { - line += QString::fromUtf8(m_decodedData).left(newlineIndex); - m_decodedData.remove(0, newlineIndex + 1); - return line; - } - - line += QString::fromUtf8(m_decodedData); - m_decodedData.clear(); - } - - if (!m_gz->unzipBlockByBlock(m_decodedData)) { // If error occurs during unzipping - m_decodedData.clear(); - return QObject::tr("The content of the file(%1) could not be decoded.").arg(m_file->fileName()); - } - } - } - - bool done() { return m_gz ? m_decodedData.isEmpty() : m_file->atEnd(); } - - private: - QFile* m_file; - GZipStream* m_gz = nullptr; - QByteArray m_decodedData; -}; - void OtherLogsPage::on_btnReload_clicked() { if (m_currentFile.isEmpty()) { setControlsEnabled(false); return; } + QFile file(FS::PathCombine(m_path, m_currentFile)); if (!file.open(QFile::ReadOnly)) { setControlsEnabled(false); @@ -220,15 +179,9 @@ void OtherLogsPage::on_btnReload_clicked() showTooBig(); return; } - - ReadLineAbstract stream(&file); - - // Try to determine a level for each line - ui->text->clear(); - ui->text->setModel(nullptr); - m_model->clear(); - auto line = stream.readLine(); - while (!stream.done()) { // just read until the model is full or the file ended + auto handleLine = [this](QString line) { + if (line.isEmpty()) + return false; if (line.back() == '\n') line = line.remove(line.size() - 1, 1); MessageLevel::Enum level = MessageLevel::Unknown; @@ -245,10 +198,40 @@ void OtherLogsPage::on_btnReload_clicked() } m_model->append(level, line); - if (m_model->isOverFlow()) - break; + return m_model->isOverFlow(); + }; - line = stream.readLine(); + // Try to determine a level for each line + ui->text->clear(); + ui->text->setModel(nullptr); + m_model->clear(); + if (file.fileName().endsWith(".gz")) { + QString line; + auto error = GZip::readGzFileByBlocks(&file, [&line, handleLine](const QByteArray& d) { + auto block = d; + int newlineIndex = block.indexOf('\n'); + while (newlineIndex != -1) { + line += QString::fromUtf8(block).left(newlineIndex); + block.remove(0, newlineIndex + 1); + if (handleLine(line)) { + line.clear(); + return false; + } + line.clear(); + newlineIndex = block.indexOf('\n'); + } + line += QString::fromUtf8(block); + return true; + }); + if (!error.isEmpty()) { + setPlainText(tr("The file (%1) encountered an error when reading: %2.").arg(file.fileName(), error)); + return; + } else if (!line.isEmpty()) { + handleLine(line); + } + } else { + while (!file.atEnd() && !handleLine(QString::fromUtf8(file.readLine()))) { + } } ui->text->setModel(m_proxy); ui->text->scrollToBottom(); From e3ff9630e984ee3284639cef6d1dcb2356dcd93d Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Apr 2025 13:15:00 +0300 Subject: [PATCH 071/171] fix: 6.2 deprecation warning regard the QScopedPointer::swap function Signed-off-by: Trial97 --- launcher/ui/pages/modplatform/ModPage.cpp | 16 +++++++++------- launcher/ui/pages/modplatform/ModPage.h | 6 +++--- .../ui/pages/modplatform/flame/FlamePage.cpp | 13 ++++++++----- launcher/ui/pages/modplatform/flame/FlamePage.h | 2 +- .../modplatform/flame/FlameResourcePages.cpp | 4 ++-- .../pages/modplatform/flame/FlameResourcePages.h | 2 +- .../pages/modplatform/modrinth/ModrinthPage.cpp | 12 +++++++----- .../ui/pages/modplatform/modrinth/ModrinthPage.h | 2 +- .../modrinth/ModrinthResourcePages.cpp | 4 ++-- .../modplatform/modrinth/ModrinthResourcePages.h | 2 +- launcher/ui/widgets/ModFilterWidget.cpp | 5 ----- launcher/ui/widgets/ModFilterWidget.h | 4 +--- 12 files changed, 36 insertions(+), 36 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index f0cc2df54..bfd160235 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -62,22 +62,24 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePa connect(m_ui->packView, &QListView::doubleClicked, this, &ModPage::onResourceSelected); } -void ModPage::setFilterWidget(unique_qobject_ptr& widget) +void ModPage::setFilterWidget(ModFilterWidget* widget) { if (m_filter_widget) - disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr); + disconnect(m_filter_widget, nullptr, nullptr, nullptr); - auto old = m_ui->splitter->replaceWidget(0, widget.get()); + auto old = m_ui->splitter->replaceWidget(0, widget); // because we replaced the widget we also need to delete it if (old) { - delete old; + old->deleteLater(); } - m_filter_widget.swap(widget); - + m_filter_widget = widget; + if (m_filter_widget) { + m_filter_widget->deleteLater(); + } m_filter = m_filter_widget->getFilter(); - connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, &ModPage::triggerSearch); + connect(m_filter_widget, &ModFilterWidget::filterChanged, this, &ModPage::triggerSearch); prepareProviderCategories(); } diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 5c9a82303..25d9a4e90 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -50,11 +50,11 @@ class ModPage : public ResourcePage { void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, std::shared_ptr) override; - virtual unique_qobject_ptr createFilterWidget() = 0; + virtual ModFilterWidget* createFilterWidget() = 0; [[nodiscard]] bool supportsFiltering() const override { return true; }; auto getFilter() const -> const std::shared_ptr { return m_filter; } - void setFilterWidget(unique_qobject_ptr&); + void setFilterWidget(ModFilterWidget*); protected: ModPage(ModDownloadDialog* dialog, BaseInstance& instance); @@ -66,7 +66,7 @@ class ModPage : public ResourcePage { void triggerSearch() override; protected: - unique_qobject_ptr m_filter_widget; + ModFilterWidget* m_filter_widget = nullptr; std::shared_ptr m_filter; }; diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index de6b3d633..bcbae0d76 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -341,17 +341,20 @@ void FlamePage::setSearchTerm(QString term) void FlamePage::createFilterWidget() { - auto widget = ModFilterWidget::create(nullptr, false, this); - m_filterWidget.swap(widget); - auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); + auto widget = new ModFilterWidget(nullptr, false, this); + if (m_filterWidget) { + m_filterWidget->deleteLater(); + } + m_filterWidget = (widget); + auto old = ui->splitter->replaceWidget(0, m_filterWidget); // because we replaced the widget we also need to delete it if (old) { - delete old; + old->deleteLater(); } connect(ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); }); - connect(m_filterWidget.get(), &ModFilterWidget::filterChanged, this, &FlamePage::triggerSearch); + connect(m_filterWidget, &ModFilterWidget::filterChanged, this, &FlamePage::triggerSearch); auto response = std::make_shared(); m_categoriesTask = FlameAPI::getCategories(response, ModPlatform::ResourceType::MODPACK); QObject::connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() { diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h index 27c96d2f1..a828a2a29 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.h +++ b/launcher/ui/pages/modplatform/flame/FlamePage.h @@ -100,6 +100,6 @@ class FlamePage : public QWidget, public ModpackProviderBasePage { // Used to do instant searching with a delay to cache quick changes QTimer m_search_timer; - unique_qobject_ptr m_filterWidget; + ModFilterWidget* m_filterWidget; Task::Ptr m_categoriesTask; }; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 4e01f3a65..bfe873ac8 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -207,9 +207,9 @@ auto FlameShaderPackPage::shouldDisplay() const -> bool return true; } -unique_qobject_ptr FlameModPage::createFilterWidget() +ModFilterWidget* FlameModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_baseInstance), false, this); + return new ModFilterWidget(&static_cast(m_baseInstance), false, this); } void FlameModPage::prepareProviderCategories() diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 052706549..3117851a5 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -96,7 +96,7 @@ class FlameModPage : public ModPage { [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } void openUrl(const QUrl& url) override; - unique_qobject_ptr createFilterWidget() override; + ModFilterWidget* createFilterWidget() override; protected: virtual void prepareProviderCategories() override; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 7d70abec4..784b656a1 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -391,17 +391,19 @@ QString ModrinthPage::getSerachTerm() const void ModrinthPage::createFilterWidget() { - auto widget = ModFilterWidget::create(nullptr, true, this); - m_filterWidget.swap(widget); - auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); + auto widget = new ModFilterWidget(nullptr, true, this); + if (m_filterWidget) + m_filterWidget->deleteLater(); + m_filterWidget = widget; + auto old = ui->splitter->replaceWidget(0, m_filterWidget); // because we replaced the widget we also need to delete it if (old) { - delete old; + old->deleteLater(); } connect(ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); }); - connect(m_filterWidget.get(), &ModFilterWidget::filterChanged, this, &ModrinthPage::triggerSearch); + connect(m_filterWidget, &ModFilterWidget::filterChanged, this, &ModrinthPage::triggerSearch); auto response = std::make_shared(); m_categoriesTask = ModrinthAPI::getModCategories(response); QObject::connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index 7f504cdbd..c90402f52 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -103,6 +103,6 @@ class ModrinthPage : public QWidget, public ModpackProviderBasePage { // Used to do instant searching with a delay to cache quick changes QTimer m_search_timer; - unique_qobject_ptr m_filterWidget; + ModFilterWidget* m_filterWidget; Task::Ptr m_categoriesTask; }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 4ee620677..38a750622 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -142,9 +142,9 @@ auto ModrinthShaderPackPage::shouldDisplay() const -> bool return true; } -unique_qobject_ptr ModrinthModPage::createFilterWidget() +ModFilterWidget* ModrinthModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_baseInstance), true, this); + return new ModFilterWidget(&static_cast(m_baseInstance), true, this); } void ModrinthModPage::prepareProviderCategories() diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index eaf6129a5..e2ad60b51 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -94,7 +94,7 @@ class ModrinthModPage : public ModPage { [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } - unique_qobject_ptr createFilterWidget() override; + ModFilterWidget* createFilterWidget() override; protected: virtual void prepareProviderCategories() override; diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 03522bc19..8be3ce8d3 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -49,11 +49,6 @@ #include "Application.h" #include "minecraft/PackProfile.h" -unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, bool extended, QWidget* parent) -{ - return unique_qobject_ptr(new ModFilterWidget(instance, extended, parent)); -} - class VersionBasicModel : public QIdentityProxyModel { Q_OBJECT diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 41a2f1bbd..c7192a0d6 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -83,7 +83,7 @@ class ModFilterWidget : public QTabWidget { } }; - static unique_qobject_ptr create(MinecraftInstance* instance, bool extended, QWidget* parent = nullptr); + ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); virtual ~ModFilterWidget(); auto getFilter() -> std::shared_ptr; @@ -96,8 +96,6 @@ class ModFilterWidget : public QTabWidget { void setCategories(const QList&); private: - ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); - void loadVersionList(); void prepareBasicFilter(); From 49f0e8ef6b8fe1f18380b5ffe1d7c565c0d20b85 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 26 Mar 2025 08:29:40 +0200 Subject: [PATCH 072/171] replace qvector with qlist Signed-off-by: Trial97 --- launcher/Json.h | 20 ++++++++--------- launcher/icons/IconList.h | 2 +- launcher/launch/LogModel.cpp | 2 +- launcher/launch/LogModel.h | 2 +- launcher/meta/Index.cpp | 4 ++-- launcher/meta/Index.h | 6 ++--- launcher/meta/JsonFormat.cpp | 8 +++---- launcher/meta/Version.h | 2 +- launcher/meta/VersionList.cpp | 4 ++-- launcher/meta/VersionList.h | 6 ++--- launcher/minecraft/auth/AccountData.h | 2 +- launcher/minecraft/auth/AuthFlow.h | 1 - launcher/minecraft/skins/SkinList.cpp | 2 +- launcher/minecraft/skins/SkinList.h | 2 +- launcher/modplatform/ModIndex.h | 3 +-- .../modplatform/atlauncher/ATLPackIndex.h | 4 ++-- .../atlauncher/ATLPackInstallTask.cpp | 4 ++-- .../atlauncher/ATLPackInstallTask.h | 2 +- .../modplatform/atlauncher/ATLPackManifest.h | 16 +++++++------- .../modplatform/atlauncher/ATLShareCode.h | 4 ++-- launcher/modplatform/flame/FlameAPI.cpp | 2 +- launcher/modplatform/flame/FlameAPI.h | 2 +- launcher/modplatform/flame/FlameModIndex.cpp | 4 ++-- launcher/modplatform/flame/FlamePackIndex.cpp | 2 +- launcher/modplatform/flame/FlamePackIndex.h | 3 +-- launcher/modplatform/flame/PackManifest.h | 4 ++-- .../modrinth/ModrinthPackIndex.cpp | 4 ++-- .../modrinth/ModrinthPackManifest.cpp | 2 +- .../modrinth/ModrinthPackManifest.h | 4 ++-- .../modplatform/technic/SolderPackManifest.h | 6 ++--- launcher/translations/TranslationsModel.cpp | 4 ++-- launcher/translations/TranslationsModel.h | 2 +- .../ui/dialogs/skins/draw/BoxGeometry.cpp | 22 +++++++++---------- launcher/ui/dialogs/skins/draw/Scene.h | 6 ++--- launcher/ui/instanceview/InstanceView.cpp | 2 +- launcher/ui/instanceview/InstanceView.h | 2 +- launcher/ui/instanceview/VisualGroup.cpp | 2 +- launcher/ui/instanceview/VisualGroup.h | 4 ++-- .../atlauncher/AtlOptionalModDialog.cpp | 8 +++---- .../atlauncher/AtlOptionalModDialog.h | 12 +++++----- .../AtlUserInteractionSupportImpl.cpp | 4 ++-- .../AtlUserInteractionSupportImpl.h | 3 +-- .../modplatform/flame/FlameResourceModels.cpp | 2 +- .../pages/modplatform/technic/TechnicData.h | 3 +-- libraries/LocalPeer/src/LockedFile.h | 4 ++-- 45 files changed, 102 insertions(+), 107 deletions(-) diff --git a/launcher/Json.h b/launcher/Json.h index 28891f398..c13be6470 100644 --- a/launcher/Json.h +++ b/launcher/Json.h @@ -188,10 +188,10 @@ T ensureIsType(const QJsonObject& parent, const QString& key, const T default_ = } template -QVector requireIsArrayOf(const QJsonDocument& doc) +QList requireIsArrayOf(const QJsonDocument& doc) { const QJsonArray array = requireArray(doc); - QVector out; + QList out; for (const QJsonValue val : array) { out.append(requireIsType(val, "Document")); } @@ -199,10 +199,10 @@ QVector requireIsArrayOf(const QJsonDocument& doc) } template -QVector ensureIsArrayOf(const QJsonValue& value, const QString& what = "Value") +QList ensureIsArrayOf(const QJsonValue& value, const QString& what = "Value") { const QJsonArray array = ensureIsType(value, QJsonArray(), what); - QVector out; + QList out; for (const QJsonValue val : array) { out.append(requireIsType(val, what)); } @@ -210,7 +210,7 @@ QVector ensureIsArrayOf(const QJsonValue& value, const QString& what = "Value } template -QVector ensureIsArrayOf(const QJsonValue& value, const QVector default_, const QString& what = "Value") +QList ensureIsArrayOf(const QJsonValue& value, const QList default_, const QString& what = "Value") { if (value.isUndefined()) { return default_; @@ -220,7 +220,7 @@ QVector ensureIsArrayOf(const QJsonValue& value, const QVector default_, c /// @throw JsonException template -QVector requireIsArrayOf(const QJsonObject& parent, const QString& key, const QString& what = "__placeholder__") +QList requireIsArrayOf(const QJsonObject& parent, const QString& key, const QString& what = "__placeholder__") { const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); if (!parent.contains(key)) { @@ -230,10 +230,10 @@ QVector requireIsArrayOf(const QJsonObject& parent, const QString& key, const } template -QVector ensureIsArrayOf(const QJsonObject& parent, - const QString& key, - const QVector& default_ = QVector(), - const QString& what = "__placeholder__") +QList ensureIsArrayOf(const QJsonObject& parent, + const QString& key, + const QList& default_ = QList(), + const QString& what = "__placeholder__") { const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); if (!parent.contains(key)) { diff --git a/launcher/icons/IconList.h b/launcher/icons/IconList.h index 8936195c3..d2f904448 100644 --- a/launcher/icons/IconList.h +++ b/launcher/icons/IconList.h @@ -106,6 +106,6 @@ class IconList : public QAbstractListModel { shared_qobject_ptr m_watcher; bool m_isWatching; QMap m_nameIndex; - QVector m_icons; + QList m_icons; QDir m_dir; }; diff --git a/launcher/launch/LogModel.cpp b/launcher/launch/LogModel.cpp index 45aac6099..32a266428 100644 --- a/launcher/launch/LogModel.cpp +++ b/launcher/launch/LogModel.cpp @@ -100,7 +100,7 @@ void LogModel::setMaxLines(int maxLines) return; } // otherwise, we need to reorganize the data because it crosses the wrap boundary - QVector newContent; + QList newContent; newContent.resize(maxLines); if (m_numLines <= maxLines) { // if it all fits in the new buffer, just copy it over diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h index ba7b14487..bf178e35f 100644 --- a/launcher/launch/LogModel.h +++ b/launcher/launch/LogModel.h @@ -40,7 +40,7 @@ class LogModel : public QAbstractListModel { }; private: /* data */ - QVector m_content; + QList m_content; int m_maxLines = 1000; // first line in the circular buffer int m_firstLine = 0; diff --git a/launcher/meta/Index.cpp b/launcher/meta/Index.cpp index 1707854be..25a4cd146 100644 --- a/launcher/meta/Index.cpp +++ b/launcher/meta/Index.cpp @@ -23,7 +23,7 @@ namespace Meta { Index::Index(QObject* parent) : QAbstractListModel(parent) {} -Index::Index(const QVector& lists, QObject* parent) : QAbstractListModel(parent), m_lists(lists) +Index::Index(const QList& lists, QObject* parent) : QAbstractListModel(parent), m_lists(lists) { for (int i = 0; i < m_lists.size(); ++i) { m_uids.insert(m_lists.at(i)->uid(), m_lists.at(i)); @@ -103,7 +103,7 @@ void Index::parse(const QJsonObject& obj) void Index::merge(const std::shared_ptr& other) { - const QVector lists = other->m_lists; + const QList lists = other->m_lists; // initial load, no need to merge if (m_lists.isEmpty()) { beginResetModel(); diff --git a/launcher/meta/Index.h b/launcher/meta/Index.h index 026a00c07..fe5bf2170 100644 --- a/launcher/meta/Index.h +++ b/launcher/meta/Index.h @@ -29,7 +29,7 @@ class Index : public QAbstractListModel, public BaseEntity { Q_OBJECT public: explicit Index(QObject* parent = nullptr); - explicit Index(const QVector& lists, QObject* parent = nullptr); + explicit Index(const QList& lists, QObject* parent = nullptr); virtual ~Index() = default; enum { UidRole = Qt::UserRole, NameRole, ListPtrRole }; @@ -46,7 +46,7 @@ class Index : public QAbstractListModel, public BaseEntity { Version::Ptr get(const QString& uid, const QString& version); bool hasUid(const QString& uid) const; - QVector lists() const { return m_lists; } + QList lists() const { return m_lists; } Task::Ptr loadVersion(const QString& uid, const QString& version = {}, Net::Mode mode = Net::Mode::Online, bool force = false); @@ -60,7 +60,7 @@ class Index : public QAbstractListModel, public BaseEntity { void parse(const QJsonObject& obj) override; private: - QVector m_lists; + QList m_lists; QHash m_uids; void connectVersionList(int row, const VersionList::Ptr& list); diff --git a/launcher/meta/JsonFormat.cpp b/launcher/meta/JsonFormat.cpp index 86af7277e..8d8466c87 100644 --- a/launcher/meta/JsonFormat.cpp +++ b/launcher/meta/JsonFormat.cpp @@ -35,8 +35,8 @@ MetadataVersion currentFormatVersion() // Index static std::shared_ptr parseIndexInternal(const QJsonObject& obj) { - const QVector objects = requireIsArrayOf(obj, "packages"); - QVector lists; + const QList objects = requireIsArrayOf(obj, "packages"); + QList lists; lists.reserve(objects.size()); std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject& obj) { VersionList::Ptr list = std::make_shared(requireString(obj, "uid")); @@ -79,8 +79,8 @@ static VersionList::Ptr parseVersionListInternal(const QJsonObject& obj) { const QString uid = requireString(obj, "uid"); - const QVector versionsRaw = requireIsArrayOf(obj, "versions"); - QVector versions; + const QList versionsRaw = requireIsArrayOf(obj, "versions"); + QList versions; versions.reserve(versionsRaw.size()); std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject& vObj) { auto version = parseCommonVersion(uid, vObj); diff --git a/launcher/meta/Version.h b/launcher/meta/Version.h index 46dc740da..2327879a1 100644 --- a/launcher/meta/Version.h +++ b/launcher/meta/Version.h @@ -19,8 +19,8 @@ #include "BaseVersion.h" #include +#include #include -#include #include #include "minecraft/VersionFile.h" diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index 1de4e7f36..1f4a969fa 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -169,7 +169,7 @@ void VersionList::setName(const QString& name) emit nameChanged(name); } -void VersionList::setVersions(const QVector& versions) +void VersionList::setVersions(const QList& versions) { beginResetModel(); m_versions = versions; @@ -265,7 +265,7 @@ void VersionList::setupAddedVersion(const int row, const Version::Ptr& version) disconnect(version.get(), &Version::typeChanged, this, nullptr); connect(version.get(), &Version::requiresChanged, this, - [this, row]() { emit dataChanged(index(row), index(row), QVector() << RequiresRole); }); + [this, row]() { emit dataChanged(index(row), index(row), QList() << RequiresRole); }); connect(version.get(), &Version::timeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), { TimeRole, SortRole }); }); connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), { TypeRole }); }); diff --git a/launcher/meta/VersionList.h b/launcher/meta/VersionList.h index 4215439db..21c86b751 100644 --- a/launcher/meta/VersionList.h +++ b/launcher/meta/VersionList.h @@ -61,14 +61,14 @@ class VersionList : public BaseVersionList, public BaseEntity { Version::Ptr getVersion(const QString& version); bool hasVersion(QString version) const; - QVector versions() const { return m_versions; } + QList versions() const { return m_versions; } // this blocks until the version list is loaded void waitToLoad(); public: // for usage only by parsers void setName(const QString& name); - void setVersions(const QVector& versions); + void setVersions(const QList& versions); void merge(const VersionList::Ptr& other); void mergeFromIndex(const VersionList::Ptr& other); void parse(const QJsonObject& obj) override; @@ -82,7 +82,7 @@ class VersionList : public BaseVersionList, public BaseEntity { void updateListData(QList) override {} private: - QVector m_versions; + QList m_versions; QStringList m_externalRecommendsVersions; QHash m_lookup; QString m_uid; diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index 1ada4e38a..df7d569da 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -36,8 +36,8 @@ #pragma once #include #include +#include #include -#include #include #include diff --git a/launcher/minecraft/auth/AuthFlow.h b/launcher/minecraft/auth/AuthFlow.h index bff4c04e4..710509d8e 100644 --- a/launcher/minecraft/auth/AuthFlow.h +++ b/launcher/minecraft/auth/AuthFlow.h @@ -5,7 +5,6 @@ #include #include #include -#include #include "minecraft/auth/AccountData.h" #include "minecraft/auth/AuthStep.h" diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index 124b69c85..56379aaab 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -67,7 +67,7 @@ void SkinList::stopWatching() bool SkinList::update() { - QVector newSkins; + QList newSkins; m_dir.refresh(); auto manifestInfo = QFileInfo(m_dir.absoluteFilePath("index.json")); diff --git a/launcher/minecraft/skins/SkinList.h b/launcher/minecraft/skins/SkinList.h index e77269d57..5a160909a 100644 --- a/launcher/minecraft/skins/SkinList.h +++ b/launcher/minecraft/skins/SkinList.h @@ -74,7 +74,7 @@ class SkinList : public QAbstractListModel { private: shared_qobject_ptr m_watcher; bool m_isWatching; - QVector m_skinList; + QList m_skinList; QDir m_dir; MinecraftAccountPtr m_acct; }; \ No newline at end of file diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 8fae1bf6c..523358e4e 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -23,7 +23,6 @@ #include #include #include -#include #include class QIODevice; @@ -141,7 +140,7 @@ struct IndexedPack { QString side; bool versionsLoaded = false; - QVector versions; + QList versions; // Don't load by default, since some modplatform don't have that info bool extraDataLoaded = true; diff --git a/launcher/modplatform/atlauncher/ATLPackIndex.h b/launcher/modplatform/atlauncher/ATLPackIndex.h index 8d18c671d..187bc05ec 100644 --- a/launcher/modplatform/atlauncher/ATLPackIndex.h +++ b/launcher/modplatform/atlauncher/ATLPackIndex.h @@ -18,9 +18,9 @@ #include "ATLPackManifest.h" +#include #include #include -#include namespace ATLauncher { @@ -34,7 +34,7 @@ struct IndexedPack { int position; QString name; PackType type; - QVector versions; + QList versions; bool system; QString description; diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index a9706a768..fa3b1be3a 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -689,7 +689,7 @@ void PackInstallTask::downloadMods() { qDebug() << "PackInstallTask::installMods: " << QThread::currentThreadId(); - QVector optionalMods; + QList optionalMods; for (const auto& mod : m_version.mods) { if (mod.optional) { optionalMods.push_back(mod); @@ -697,7 +697,7 @@ void PackInstallTask::downloadMods() } // Select optional mods, if pack contains any - QVector selectedMods; + QList selectedMods; if (!optionalMods.isEmpty()) { setStatus(tr("Selecting optional mods...")); auto mods = m_support->chooseOptionalMods(m_version, optionalMods); diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index ee5960e30..ce8bb636d 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -62,7 +62,7 @@ class UserInteractionSupport { /** * Requests a user interaction to select which optional mods should be installed. */ - virtual std::optional> chooseOptionalMods(const PackVersion& version, QVector mods) = 0; + virtual std::optional> chooseOptionalMods(const PackVersion& version, QList mods) = 0; /** * Requests a user interaction to select a component version from a given version list diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index 8db91087d..b6c3b7a84 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -36,9 +36,9 @@ #pragma once #include +#include #include #include -#include namespace ATLauncher { @@ -113,7 +113,7 @@ struct VersionMod { bool hidden; bool library; QString group; - QVector depends; + QStringList depends; QString colour; QString warning; @@ -139,8 +139,8 @@ struct VersionKeep { }; struct VersionKeeps { - QVector files; - QVector folders; + QList files; + QList folders; }; struct VersionDelete { @@ -149,8 +149,8 @@ struct VersionDelete { }; struct VersionDeletes { - QVector files; - QVector folders; + QList files; + QList folders; }; struct PackVersionMainClass { @@ -171,8 +171,8 @@ struct PackVersion { PackVersionExtraArguments extraArguments; VersionLoader loader; - QVector libraries; - QVector mods; + QList libraries; + QList mods; VersionConfigs configs; QMap colours; diff --git a/launcher/modplatform/atlauncher/ATLShareCode.h b/launcher/modplatform/atlauncher/ATLShareCode.h index 531945bce..9b56c6d7c 100644 --- a/launcher/modplatform/atlauncher/ATLShareCode.h +++ b/launcher/modplatform/atlauncher/ATLShareCode.h @@ -19,8 +19,8 @@ #pragma once #include +#include #include -#include namespace ATLauncher { @@ -32,7 +32,7 @@ struct ShareCodeMod { struct ShareCode { QString pack; QString version; - QVector mods; + QList mods; }; struct ShareCodeResponse { diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index a06793de0..15eb7a696 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -215,7 +215,7 @@ QList FlameAPI::loadModCategories(std::shared_ptr FlameAPI::getLatestVersion(QVector versions, +std::optional FlameAPI::getLatestVersion(QList versions, QList instanceLoaders, ModPlatform::ModLoaderTypes modLoaders) { diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 509e1abcd..f85a08eb1 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -15,7 +15,7 @@ class FlameAPI : public NetworkResourceAPI { QString getModFileChangelog(int modId, int fileId); QString getModDescription(int modId); - std::optional getLatestVersion(QVector versions, + std::optional getLatestVersion(QList versions, QList instanceLoaders, ModPlatform::ModLoaderTypes fallback); diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index ff9d2d9ce..c1b9e67af 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -78,7 +78,7 @@ static QString enumToString(int hash_algorithm) void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr) { - QVector unsortedVersions; + QList unsortedVersions; for (auto versionIter : arr) { auto obj = versionIter.toObject(); @@ -208,7 +208,7 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform:: auto profile = (dynamic_cast(inst))->getPackProfile(); QString mcVersion = profile->getComponentVersion("net.minecraft"); auto loaders = profile->getSupportedModLoaders(); - QVector versions; + QList versions; for (auto versionIter : arr) { auto obj = versionIter.toObject(); diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index 8c25b0482..8a7734be5 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -77,7 +77,7 @@ void Flame::loadIndexedInfo(IndexedPack& pack, QJsonObject& obj) void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr) { - QVector unsortedVersions; + QList unsortedVersions; for (auto versionIter : arr) { auto version = Json::requireObject(versionIter); Flame::IndexedVersion file; diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h index 11633deee..30391288b 100644 --- a/launcher/modplatform/flame/FlamePackIndex.h +++ b/launcher/modplatform/flame/FlamePackIndex.h @@ -3,7 +3,6 @@ #include #include #include -#include #include "modplatform/ModIndex.h" namespace Flame { @@ -39,7 +38,7 @@ struct IndexedPack { QString logoUrl; bool versionsLoaded = false; - QVector versions; + QList versions; bool extraInfoLoaded = false; ModpackExtra extra; diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index 7af3b9d6b..ebb3ed5cc 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -36,10 +36,10 @@ #pragma once #include +#include #include #include #include -#include #include "minecraft/mod/tasks/LocalResourceParse.h" #include "modplatform/ModIndex.h" @@ -66,7 +66,7 @@ struct Modloader { struct Minecraft { QString version; QString libraries; - QVector modLoaders; + QList modLoaders; }; struct Manifest { diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 16b300b02..744b058c0 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -114,7 +114,7 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr) { - QVector unsortedVersions; + QList unsortedVersions; for (auto versionIter : arr) { auto obj = versionIter.toObject(); auto file = loadIndexedPackVersion(obj); @@ -253,7 +253,7 @@ ModPlatform::IndexedVersion Modrinth::loadDependencyVersions([[maybe_unused]] co QString mcVersion = profile->getComponentVersion("net.minecraft"); auto loaders = profile->getSupportedModLoaders(); - QVector versions; + QList versions; for (auto versionIter : arr) { auto obj = versionIter.toObject(); auto file = loadIndexedPackVersion(obj); diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index 89ef6e4c4..be565bf11 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -99,7 +99,7 @@ void loadIndexedInfo(Modpack& pack, QJsonObject& obj) void loadIndexedVersions(Modpack& pack, QJsonDocument& doc) { - QVector unsortedVersions; + QList unsortedVersions; auto arr = Json::requireArray(doc); diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h index 2e5e2da84..97b8ab712 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.h +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h @@ -40,10 +40,10 @@ #include #include +#include #include #include #include -#include #include "modplatform/ModIndex.h" @@ -110,7 +110,7 @@ struct Modpack { bool extraInfoLoaded = false; ModpackExtra extra; - QVector versions; + QList versions; }; void loadIndexedPack(Modpack&, QJsonObject&); diff --git a/launcher/modplatform/technic/SolderPackManifest.h b/launcher/modplatform/technic/SolderPackManifest.h index 1a06d7037..3a5947515 100644 --- a/launcher/modplatform/technic/SolderPackManifest.h +++ b/launcher/modplatform/technic/SolderPackManifest.h @@ -19,15 +19,15 @@ #pragma once #include +#include #include -#include namespace TechnicSolder { struct Pack { QString recommended; QString latest; - QVector builds; + QList builds; }; void loadPack(Pack& v, QJsonObject& obj); @@ -41,7 +41,7 @@ struct PackBuildMod { struct PackBuild { QString minecraft; - QVector mods; + QList mods; }; void loadPackBuild(PackBuild& v, QJsonObject& obj); diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index e863dfef4..75fc93b3b 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -153,7 +153,7 @@ struct TranslationsModel::Private { QDir m_dir; // initial state is just english - QVector m_languages = { Language(defaultLangCode) }; + QList m_languages = { Language(defaultLangCode) }; QString m_selectedLanguage = defaultLangCode; std::unique_ptr m_qt_translator; @@ -417,7 +417,7 @@ int TranslationsModel::columnCount([[maybe_unused]] const QModelIndex& parent) c return 2; } -QVector::Iterator TranslationsModel::findLanguage(const QString& key) +QList::Iterator TranslationsModel::findLanguage(const QString& key) { return std::find_if(d->m_languages.begin(), d->m_languages.end(), [key](Language& lang) { return lang.key == key; }); } diff --git a/launcher/translations/TranslationsModel.h b/launcher/translations/TranslationsModel.h index 96a0e9f8b..945e689fc 100644 --- a/launcher/translations/TranslationsModel.h +++ b/launcher/translations/TranslationsModel.h @@ -41,7 +41,7 @@ class TranslationsModel : public QAbstractListModel { void setUseSystemLocale(bool useSystemLocale); private: - QVector::Iterator findLanguage(const QString& key); + QList::Iterator findLanguage(const QString& key); std::optional findLanguageAsOptional(const QString& key); void reloadLocalFiles(); void downloadTranslation(QString key); diff --git a/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp b/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp index 9a5ad1ce2..b4ab8d4cc 100644 --- a/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp +++ b/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp @@ -18,10 +18,10 @@ #include "BoxGeometry.h" +#include #include #include #include -#include struct VertexData { QVector4D position; @@ -32,7 +32,7 @@ struct VertexData { // For cube we would need only 8 vertices but we have to // duplicate vertex for each face because texture coordinate // is different. -static const QVector vertices = { +static const QList vertices = { // Vertex data for face 0 QVector4D(-0.5f, -0.5f, 0.5f, 1.0f), // v0 QVector4D(0.5f, -0.5f, 0.5f, 1.0f), // v1 @@ -76,7 +76,7 @@ static const QVector vertices = { // index of the second strip needs to be duplicated. If // connecting strips have same vertex order then only last // index of the first strip needs to be duplicated. -static const QVector indices = { +static const QList indices = { 0, 1, 2, 3, 3, // Face 0 - triangle strip ( v0, v1, v2, v3) 4, 4, 5, 6, 7, 7, // Face 1 - triangle strip ( v4, v5, v6, v7) 8, 8, 9, 10, 11, 11, // Face 2 - triangle strip ( v8, v9, v10, v11) @@ -85,19 +85,19 @@ static const QVector indices = { 20, 20, 21, 22, 23 // Face 5 - triangle strip (v20, v21, v22, v23) }; -static const QVector planeVertices = { +static const QList planeVertices = { { QVector4D(-1.0f, -1.0f, -0.5f, 1.0f), QVector2D(0.0f, 0.0f) }, // Bottom-left { QVector4D(1.0f, -1.0f, -0.5f, 1.0f), QVector2D(1.0f, 0.0f) }, // Bottom-right { QVector4D(-1.0f, 1.0f, -0.5f, 1.0f), QVector2D(0.0f, 1.0f) }, // Top-left { QVector4D(1.0f, 1.0f, -0.5f, 1.0f), QVector2D(1.0f, 1.0f) }, // Top-right }; -static const QVector planeIndices = { +static const QList planeIndices = { 0, 1, 2, 3, 3 // Face 0 - triangle strip ( v0, v1, v2, v3) }; -QVector transformVectors(const QMatrix4x4& matrix, const QVector& vectors) +QList transformVectors(const QMatrix4x4& matrix, const QList& vectors) { - QVector transformedVectors; + QList transformedVectors; transformedVectors.reserve(vectors.size()); for (const QVector4D& vec : vectors) { @@ -113,9 +113,9 @@ QVector transformVectors(const QMatrix4x4& matrix, const QVector getCubeUVs(float u, float v, float width, float height, float depth, float textureWidth, float textureHeight) +QList getCubeUVs(float u, float v, float width, float height, float depth, float textureWidth, float textureHeight) { - auto toFaceVertices = [textureHeight, textureWidth](float x1, float y1, float x2, float y2) -> QVector { + auto toFaceVertices = [textureHeight, textureWidth](float x1, float y1, float x2, float y2) -> QList { return { QVector2D(x1 / textureWidth, 1.0 - y2 / textureHeight), QVector2D(x2 / textureWidth, 1.0 - y2 / textureHeight), @@ -168,7 +168,7 @@ QVector getCubeUVs(float u, float v, float width, float height, float back[2], }; // Create a new array to hold the modified UV data - QVector uvData; + QList uvData; uvData.reserve(24); // Iterate over the arrays and copy the data to newUVData @@ -237,7 +237,7 @@ void BoxGeometry::initGeometry(float u, float v, float width, float height, floa transformation.scale(m_size); auto positions = transformVectors(transformation, vertices); - QVector verticesData; + QList verticesData; verticesData.reserve(positions.size()); // Reserve space for efficiency for (int i = 0; i < positions.size(); ++i) { diff --git a/launcher/ui/dialogs/skins/draw/Scene.h b/launcher/ui/dialogs/skins/draw/Scene.h index de683a659..3560d1d74 100644 --- a/launcher/ui/dialogs/skins/draw/Scene.h +++ b/launcher/ui/dialogs/skins/draw/Scene.h @@ -34,9 +34,9 @@ class Scene { void setCapeVisible(bool visible); private: - QVector m_staticComponents; - QVector m_normalArms; - QVector m_slimArms; + QList m_staticComponents; + QList m_normalArms; + QList m_slimArms; BoxGeometry* m_cape = nullptr; QOpenGLTexture* m_skinTexture = nullptr; QOpenGLTexture* m_capeTexture = nullptr; diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index f52c994d3..2349c684d 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -89,7 +89,7 @@ void InstanceView::setModel(QAbstractItemModel* model) void InstanceView::dataChanged([[maybe_unused]] const QModelIndex& topLeft, [[maybe_unused]] const QModelIndex& bottomRight, - [[maybe_unused]] const QVector& roles) + [[maybe_unused]] const QList& roles) { scheduleDelayedItemsLayout(); } diff --git a/launcher/ui/instanceview/InstanceView.h b/launcher/ui/instanceview/InstanceView.h index 30be411a8..dea8b1212 100644 --- a/launcher/ui/instanceview/InstanceView.h +++ b/launcher/ui/instanceview/InstanceView.h @@ -83,7 +83,7 @@ class InstanceView : public QAbstractItemView { virtual void updateGeometries() override; protected slots: - virtual void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) override; + virtual void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList& roles) override; virtual void rowsInserted(const QModelIndex& parent, int start, int end) override; virtual void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) override; void modelReset(); diff --git a/launcher/ui/instanceview/VisualGroup.cpp b/launcher/ui/instanceview/VisualGroup.cpp index 089db8ad7..4f7a61eb5 100644 --- a/launcher/ui/instanceview/VisualGroup.cpp +++ b/launcher/ui/instanceview/VisualGroup.cpp @@ -55,7 +55,7 @@ void VisualGroup::update() auto itemsPerRow = view->itemsPerRow(); int numRows = qMax(1, qCeil((qreal)temp_items.size() / (qreal)itemsPerRow)); - rows = QVector(numRows); + rows = QList(numRows); int maxRowHeight = 0; int positionInRow = 0; diff --git a/launcher/ui/instanceview/VisualGroup.h b/launcher/ui/instanceview/VisualGroup.h index 8c6f06bcc..7210e0dfc 100644 --- a/launcher/ui/instanceview/VisualGroup.h +++ b/launcher/ui/instanceview/VisualGroup.h @@ -35,10 +35,10 @@ #pragma once +#include #include #include #include -#include class InstanceView; class QPainter; @@ -61,7 +61,7 @@ struct VisualGroup { InstanceView* view = nullptr; QString text; bool collapsed = false; - QVector rows; + QList rows; int firstItemIndex = 0; int m_verticalPosition = 0; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index d84737bf5..5ee8d2c04 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -47,7 +47,7 @@ AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, const ATLauncher::PackVersion& version, - QVector mods) + QList mods) : QAbstractListModel(parent), m_version(version), m_mods(mods) { // fill mod index @@ -64,9 +64,9 @@ AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, } } -QVector AtlOptionalModListModel::getResult() +QList AtlOptionalModListModel::getResult() { - QVector result; + QList result; for (const auto& mod : m_mods) { if (m_selection[mod.name]) { @@ -315,7 +315,7 @@ void AtlOptionalModListModel::setMod(const ATLauncher::VersionMod& mod, int inde } } -AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods) +AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QList mods) : QDialog(parent), ui(new Ui::AtlOptionalModDialog) { ui->setupUi(this); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h index 0636715cc..fa39e997c 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h @@ -55,9 +55,9 @@ class AtlOptionalModListModel : public QAbstractListModel { DescriptionColumn, }; - AtlOptionalModListModel(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods); + AtlOptionalModListModel(QWidget* parent, const ATLauncher::PackVersion& version, QList mods); - QVector getResult(); + QList getResult(); int rowCount(const QModelIndex& parent) const override; int columnCount(const QModelIndex& parent) const override; @@ -86,21 +86,21 @@ class AtlOptionalModListModel : public QAbstractListModel { std::shared_ptr m_response = std::make_shared(); ATLauncher::PackVersion m_version; - QVector m_mods; + QList m_mods; QMap m_selection; QMap m_index; - QMap> m_dependents; + QMap> m_dependents; }; class AtlOptionalModDialog : public QDialog { Q_OBJECT public: - AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods); + AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QList mods); ~AtlOptionalModDialog() override; - QVector getResult() { return listModel->getResult(); } + QList getResult() { return listModel->getResult(); } void useShareCode(); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp index 7550ff758..dc9a4758f 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp @@ -41,8 +41,8 @@ AtlUserInteractionSupportImpl::AtlUserInteractionSupportImpl(QWidget* parent) : m_parent(parent) {} -std::optional> AtlUserInteractionSupportImpl::chooseOptionalMods(const ATLauncher::PackVersion& version, - QVector mods) +std::optional> AtlUserInteractionSupportImpl::chooseOptionalMods(const ATLauncher::PackVersion& version, + QList mods) { AtlOptionalModDialog optionalModDialog(m_parent, version, mods); auto result = optionalModDialog.exec(); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h index 7ff021105..99f907a19 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h @@ -48,8 +48,7 @@ class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInt private: QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) override; - std::optional> chooseOptionalMods(const ATLauncher::PackVersion& version, - QVector mods) override; + std::optional> chooseOptionalMods(const ATLauncher::PackVersion& version, QList mods) override; void displayMessage(QString message) override; private: diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index fea1fc27a..a2a36141b 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -95,7 +95,7 @@ void FlameTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, { FlameMod::loadIndexedPackVersions(m, arr); - QVector filtered_versions(m.versions.size()); + QList filtered_versions(m.versions.size()); // FIXME: Client-side version filtering. This won't take into account any user-selected filtering. for (auto const& version : m.versions) { diff --git a/launcher/ui/pages/modplatform/technic/TechnicData.h b/launcher/ui/pages/modplatform/technic/TechnicData.h index fc7fa4d39..11d57f071 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicData.h +++ b/launcher/ui/pages/modplatform/technic/TechnicData.h @@ -37,7 +37,6 @@ #include #include -#include namespace Technic { struct Modpack { @@ -61,7 +60,7 @@ struct Modpack { bool versionsLoaded = false; QString recommended; - QVector versions; + QList versions; }; } // namespace Technic diff --git a/libraries/LocalPeer/src/LockedFile.h b/libraries/LocalPeer/src/LockedFile.h index e8023251c..0d3539708 100644 --- a/libraries/LocalPeer/src/LockedFile.h +++ b/libraries/LocalPeer/src/LockedFile.h @@ -42,7 +42,7 @@ #include #ifdef Q_OS_WIN -#include +#include #endif class LockedFile : public QFile { @@ -64,7 +64,7 @@ class LockedFile : public QFile { #ifdef Q_OS_WIN Qt::HANDLE wmutex; Qt::HANDLE rmutex; - QVector rmutexes; + QList rmutexes; QString mutexname; Qt::HANDLE getMutexHandle(int idx, bool doCreate); From 96a4b78e2edcc5225d4f831e6ef5c534e2c14d46 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 23:45:46 +0100 Subject: [PATCH 073/171] Remove accidental return Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/OtherLogsPage.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index ec597497d..63dcf5cff 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -43,11 +43,11 @@ #include #include -#include #include -#include -#include +#include +#include #include +#include OtherLogsPage::OtherLogsPage(InstancePtr instance, QWidget* parent) : QWidget(parent) @@ -144,7 +144,6 @@ void OtherLogsPage::populateSelectLogBox() if (index != -1) { ui->selectLogBox->setCurrentIndex(index); setControlsEnabled(true); - return; } else { setControlsEnabled(false); } From 8ea5eac29cd9340d9c2d1da6dd8e35355fdbce59 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 23:48:56 +0100 Subject: [PATCH 074/171] Make requested changes Signed-off-by: TheKodeToad --- launcher/NullInstance.h | 1 - launcher/ui/pages/instance/OtherLogsPage.h | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h index 93bab6c8b..e603b1634 100644 --- a/launcher/NullInstance.h +++ b/launcher/NullInstance.h @@ -35,7 +35,6 @@ */ #pragma once -#include #include "BaseInstance.h" #include "launch/LaunchTask.h" diff --git a/launcher/ui/pages/instance/OtherLogsPage.h b/launcher/ui/pages/instance/OtherLogsPage.h index fedb2506c..70eb145fb 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.h +++ b/launcher/ui/pages/instance/OtherLogsPage.h @@ -39,8 +39,7 @@ #include #include -#include -#include +#include #include "LogPage.h" #include "ui/pages/BasePage.h" From 19b241fd31d89cd159ab075475ab1270eb8b2799 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 18 Apr 2025 23:59:35 +0100 Subject: [PATCH 075/171] Include txt too Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/OtherLogsPage.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 63dcf5cff..4e401aa9c 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -404,13 +404,17 @@ QStringList OtherLogsPage::getPaths() for (QString searchPath : m_logSearchPaths) { QDirIterator iterator(searchPath, QDir::Files | QDir::Readable); + const bool isRoot = searchPath == m_basePath; + while (iterator.hasNext()) { const QString name = iterator.next(); - if (!name.endsWith(".log") && !name.endsWith(".log.gz")) + QString relativePath = baseDir.relativeFilePath(name); + + if (!(name.endsWith(".log") || name.endsWith(".log.gz") || (!isRoot && name.endsWith(".txt")))) continue; - result.append(baseDir.relativeFilePath(name)); + result.append(relativePath); } } From 0aa3341d5827c4a1529323cb682592891641488d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 19 Apr 2025 00:04:30 +0100 Subject: [PATCH 076/171] Fix other weird import Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/OtherLogsPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 4e401aa9c..caacea83e 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -43,8 +43,8 @@ #include #include -#include #include +#include #include #include #include From 92ba13cfdb839ece93cea2429193a8c6c3982cd8 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 19 Apr 2025 00:12:38 +0100 Subject: [PATCH 077/171] Fix catastrophic regex mistake Signed-off-by: TheKodeToad --- launcher/minecraft/MinecraftInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index ec136ede0..638979578 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1011,7 +1011,7 @@ MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLev // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * static const QRegularExpression JAVA_EXCEPTION( - R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); + R"((Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable))"); if (line.contains(JAVA_EXCEPTION)) return MessageLevel::Error; From 111cdc240ef0232c155a9dbdd0bd87a077fcdf86 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 19 Apr 2025 09:34:12 +0100 Subject: [PATCH 078/171] Disable auto-reload of files Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/OtherLogsPage.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index caacea83e..f457195d8 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -142,8 +142,11 @@ void OtherLogsPage::populateSelectLogBox() if (!prevCurrentFile.isEmpty()) { const int index = ui->selectLogBox->findText(prevCurrentFile); if (index != -1) { + ui->selectLogBox->blockSignals(true); ui->selectLogBox->setCurrentIndex(index); + ui->selectLogBox->blockSignals(false); setControlsEnabled(true); + return; } else { setControlsEnabled(false); } From 47295da3907a5be82f93ffc49165d0ce6ba452e0 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 16 Apr 2025 00:37:35 -0700 Subject: [PATCH 079/171] feat(logs): parse log4j xml events in logs Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/CMakeLists.txt | 2 + launcher/MessageLevel.cpp | 2 + launcher/MessageLevel.h | 1 + launcher/launch/LaunchTask.cpp | 55 +++++- launcher/launch/LaunchTask.h | 6 + launcher/logs/LogParser.cpp | 322 +++++++++++++++++++++++++++++++++ launcher/logs/LogParser.h | 74 ++++++++ 7 files changed, 461 insertions(+), 1 deletion(-) create mode 100644 launcher/logs/LogParser.cpp create mode 100644 launcher/logs/LogParser.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index ca5a5bea9..4f8b9018a 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -175,6 +175,8 @@ set(LAUNCH_SOURCES launch/LogModel.h launch/TaskStepWrapper.cpp launch/TaskStepWrapper.h + logs/LogParser.cpp + logs/LogParser.h ) # Old update system diff --git a/launcher/MessageLevel.cpp b/launcher/MessageLevel.cpp index 116e70c4b..d0e8809ec 100644 --- a/launcher/MessageLevel.cpp +++ b/launcher/MessageLevel.cpp @@ -4,6 +4,8 @@ MessageLevel::Enum MessageLevel::getLevel(const QString& levelName) { if (levelName == "Launcher") return MessageLevel::Launcher; + else if (levelName == "Trace") + return MessageLevel::Trace; else if (levelName == "Debug") return MessageLevel::Debug; else if (levelName == "Info") diff --git a/launcher/MessageLevel.h b/launcher/MessageLevel.h index fd12583f2..321af9d92 100644 --- a/launcher/MessageLevel.h +++ b/launcher/MessageLevel.h @@ -12,6 +12,7 @@ enum Enum { StdOut, /**< Undetermined stderr messages */ StdErr, /**< Undetermined stdout messages */ Launcher, /**< Launcher Messages */ + Trace, /**< Trace Messages */ Debug, /**< Debug Messages */ Info, /**< Info Messages */ Message, /**< Standard Messages */ diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp index 9ec746641..e1eaf8b1f 100644 --- a/launcher/launch/LaunchTask.cpp +++ b/launcher/launch/LaunchTask.cpp @@ -37,11 +37,14 @@ #include "launch/LaunchTask.h" #include +#include +#include #include #include #include #include #include +#include #include "MessageLevel.h" #include "tasks/Task.h" @@ -213,6 +216,52 @@ shared_qobject_ptr LaunchTask::getLogModel() return m_logModel; } +bool LaunchTask::parseXmlLogs(QString const& line, MessageLevel::Enum level) +{ + LogParser* parser; + switch (level) { + case MessageLevel::StdErr: + parser = &m_stderrParser; + break; + case MessageLevel::StdOut: + parser = &m_stdoutParser; + break; + default: + return false; + } + + parser->appendLine(line); + auto items = parser->parseAvailable(); + if (auto err = parser->getError(); err.has_value()) { + auto& model = *getLogModel(); + model.append(MessageLevel::Error, tr("[Log4j Parse Error] Failed to parse log4j log event: %1").arg(err.value().errMessage)); + return false; + } else { + if (items.has_value()) { + auto& model = *getLogModel(); + for (auto const& item : items.value()) { + if (std::holds_alternative(item)) { + auto entry = std::get(item); + auto msg = QString("[%1] [%2/%3] [%4]: %5") + .arg(entry.timestamp.toString("HH:mm:ss")) + .arg(entry.thread) + .arg(entry.levelText) + .arg(entry.logger) + .arg(entry.message); + msg = censorPrivateInfo(msg); + model.append(entry.level, msg); + } else if (std::holds_alternative(item)) { + auto msg = std::get(item).message; + level = m_instance->guessLevel(msg, level); + msg = censorPrivateInfo(msg); + model.append(level, msg); + } + } + } + } + return true; +} + void LaunchTask::onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel) { for (auto& line : lines) { @@ -222,6 +271,10 @@ void LaunchTask::onLogLines(const QStringList& lines, MessageLevel::Enum default void LaunchTask::onLogLine(QString line, MessageLevel::Enum level) { + if (parseXmlLogs(line, level)) { + return; + } + // if the launcher part set a log level, use it auto innerLevel = MessageLevel::fromLine(line); if (innerLevel != MessageLevel::Unknown) { @@ -229,7 +282,7 @@ void LaunchTask::onLogLine(QString line, MessageLevel::Enum level) } // If the level is still undetermined, guess level - if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) { + if (level == MessageLevel::Unknown) { level = m_instance->guessLevel(line, level); } diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h index 2e87ece95..5effab980 100644 --- a/launcher/launch/LaunchTask.h +++ b/launcher/launch/LaunchTask.h @@ -43,6 +43,7 @@ #include "LaunchStep.h" #include "LogModel.h" #include "MessageLevel.h" +#include "logs/LogParser.h" class LaunchTask : public Task { Q_OBJECT @@ -114,6 +115,9 @@ class LaunchTask : public Task { private: /*methods */ void finalizeSteps(bool successful, const QString& error); + protected: + bool parseXmlLogs(QString const& line, MessageLevel::Enum level); + protected: /* data */ MinecraftInstancePtr m_instance; shared_qobject_ptr m_logModel; @@ -122,4 +126,6 @@ class LaunchTask : public Task { int currentStep = -1; State state = NotStarted; qint64 m_pid = -1; + LogParser m_stdoutParser; + LogParser m_stderrParser; }; diff --git a/launcher/logs/LogParser.cpp b/launcher/logs/LogParser.cpp new file mode 100644 index 000000000..294036134 --- /dev/null +++ b/launcher/logs/LogParser.cpp @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2025 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "LogParser.h" + +void LogParser::appendLine(QAnyStringView data) +{ + if (!m_partialData.isEmpty()) { + m_buffer = QString(m_partialData); + m_buffer.append("\n"); + m_buffer.append(data.toString()); + m_partialData.clear(); + } else { + m_buffer.append(data.toString()); + } +} + +std::optional LogParser::getError() +{ + return m_error; +} + +MessageLevel::Enum LogParser::parseLogLevel(const QString& level) +{ + auto test = level.trimmed().toUpper(); + if (test == "TRACE") { + return MessageLevel::Trace; + } else if (test == "DEBUG") { + return MessageLevel::Debug; + } else if (test == "INFO") { + return MessageLevel::Info; + } else if (test == "WARN") { + return MessageLevel::Warning; + } else if (test == "ERROR") { + return MessageLevel::Error; + } else if (test == "FATAL") { + return MessageLevel::Fatal; + } else { + return MessageLevel::Unknown; + } +} + +std::optional LogParser::parseAttributes() +{ + LogParser::LogEntry entry{ + "", + MessageLevel::Info, + }; + auto attributes = m_parser.attributes(); + + for (const auto& attr : attributes) { + auto name = attr.name(); + auto value = attr.value(); + if (name == "logger") { + entry.logger = value.trimmed().toString(); + } else if (name == "timestamp") { + if (value.trimmed().isEmpty()) { + m_parser.raiseError("log4j:Event Missing required attribute: timestamp"); + return {}; + } + entry.timestamp = QDateTime::fromSecsSinceEpoch(value.trimmed().toLongLong()); + } else if (name == "level") { + entry.levelText = value.trimmed().toString(); + entry.level = parseLogLevel(entry.levelText); + } else if (name == "thread") { + entry.thread = value.trimmed().toString(); + } + } + if (entry.logger.isEmpty()) { + m_parser.raiseError("log4j:Event Missing required attribute: logger"); + return {}; + } + + return entry; +} + +void LogParser::setError() +{ + m_error = { + m_parser.errorString(), + m_parser.error(), + }; +} + +void LogParser::clearError() +{ + m_error = {}; // clear previous error +} + +bool isPotentialLog4JStart(QStringView buffer) +{ + static QString target = QStringLiteral(" LogParser::parseNext() +{ + clearError(); + + if (m_buffer.isEmpty()) { + return {}; + } + + if (m_buffer.trimmed().isEmpty()) { + m_buffer.clear(); + return {}; + } + + // check if we have a full xml log4j event + bool isCompleteLog4j = false; + m_parser.clear(); + m_parser.setNamespaceProcessing(false); + m_parser.addData(m_buffer); + if (m_parser.readNextStartElement()) { + if (m_parser.qualifiedName() == "log4j:Event") { + int depth = 1; + bool eod = false; + while (depth > 0 && !eod) { + auto tok = m_parser.readNext(); + switch (tok) { + case QXmlStreamReader::TokenType::StartElement: { + depth += 1; + } break; + case QXmlStreamReader::TokenType::EndElement: { + depth -= 1; + } break; + case QXmlStreamReader::TokenType::EndDocument: { + eod = true; // break outer while loop + } break; + default: { + // no op + } + } + if (m_parser.hasError()) { + break; + } + } + + isCompleteLog4j = depth == 0; + } + } + + if (isCompleteLog4j) { + return parseLog4J(); + } else { + if (isPotentialLog4JStart(m_buffer)) { + m_partialData = QString(m_buffer); + return LogParser::Partial{ QString(m_buffer) }; + } + + int start = 0; + auto bufView = QStringView(m_buffer); + while (start < bufView.length()) { + if (qsizetype pos = bufView.right(bufView.length() - start).indexOf('<'); pos != -1) { + auto slicestart = start + pos; + auto slice = bufView.right(bufView.length() - slicestart); + if (isPotentialLog4JStart(slice)) { + if (slicestart > 0) { + auto text = m_buffer.left(slicestart); + m_buffer = m_buffer.right(m_buffer.length() - slicestart); + if (!text.trimmed().isEmpty()) { + return LogParser::PlainText{ text }; + } + } + m_partialData = QString(m_buffer); + return LogParser::Partial{ QString(m_buffer) }; + } + start = slicestart + 1; + } else { + break; + } + } + + // no log4j found, all plain text + auto text = QString(m_buffer); + m_buffer.clear(); + if (text.trimmed().isEmpty()) { + return {}; + } else { + return LogParser::PlainText{ text }; + } + } +} + +std::optional> LogParser::parseAvailable() +{ + QList items; + bool doNext = true; + while (doNext) { + auto item_ = parseNext(); + if (m_error.has_value()) { + return {}; + } + if (item_.has_value()) { + auto item = item_.value(); + if (std::holds_alternative(item)) { + break; + } else { + items.push_back(item); + } + } else { + doNext = false; + } + } + return items; +} + +std::optional LogParser::parseLog4J() +{ + m_parser.clear(); + m_parser.setNamespaceProcessing(false); + m_parser.addData(m_buffer); + + m_parser.readNextStartElement(); + if (m_parser.qualifiedName() == "log4j:Event") { + auto entry_ = parseAttributes(); + if (!entry_.has_value()) { + setError(); + return {}; + } + auto entry = entry_.value(); + + bool foundMessage = false; + int depth = 1; + + while (!m_parser.atEnd()) { + auto tok = m_parser.readNext(); + switch (tok) { + case QXmlStreamReader::TokenType::StartElement: { + depth += 1; + if (m_parser.qualifiedName() == "log4j:Message") { + QString message; + bool messageComplete = false; + + while (!messageComplete) { + auto tok = m_parser.readNext(); + + switch (tok) { + case QXmlStreamReader::TokenType::Characters: { + message.append(m_parser.text()); + } break; + case QXmlStreamReader::TokenType::EndElement: { + if (m_parser.qualifiedName() == "log4j:Message") { + messageComplete = true; + } + } break; + case QXmlStreamReader::TokenType::EndDocument: { + return {}; // parse fail + } break; + default: { + // no op + } + } + + if (m_parser.hasError()) { + return {}; + } + } + + entry.message = message; + foundMessage = true; + depth -= 1; + } + break; + case QXmlStreamReader::TokenType::EndElement: { + depth -= 1; + if (depth == 0 && m_parser.qualifiedName() == "log4j:Event") { + if (foundMessage) { + auto consumed = m_parser.characterOffset(); + if (consumed > 0 && consumed <= m_buffer.length()) { + m_buffer = m_buffer.right(m_buffer.length() - consumed); + + if (!m_buffer.isEmpty() && m_buffer.trimmed().isEmpty()) { + // only whitespace, dump it + m_buffer.clear(); + } + } + clearError(); + return entry; + } + m_parser.raiseError("log4j:Event Missing required attribute: message"); + setError(); + return {}; + } + } break; + case QXmlStreamReader::TokenType::EndDocument: { + return {}; + } break; + default: { + // no op + } + } + } + + if (m_parser.hasError()) { + return {}; + } + } + } + + throw std::runtime_error("unreachable: already verified this was a complete log4j:Event"); +} diff --git a/launcher/logs/LogParser.h b/launcher/logs/LogParser.h new file mode 100644 index 000000000..462ea43cf --- /dev/null +++ b/launcher/logs/LogParser.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2025 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "MessageLevel.h" + +class LogParser { + public: + struct LogEntry { + QString logger; + MessageLevel::Enum level; + QString levelText; + QDateTime timestamp; + QString thread; + QString message; + }; + struct Partial { + QString data; + }; + struct PlainText { + QString message; + }; + struct Error { + QString errMessage; + QXmlStreamReader::Error error; + }; + + using ParsedItem = std::variant; + + public: + LogParser() = default; + + void appendLine(QAnyStringView data); + std::optional parseNext(); + std::optional> parseAvailable(); + std::optional getError(); + + protected: + MessageLevel::Enum parseLogLevel(const QString& level); + std::optional parseAttributes(); + void setError(); + void clearError(); + + std::optional parseLog4J(); + + private: + QString m_buffer; + QString m_partialData; + QXmlStreamReader m_parser; + std::optional m_error; +}; From bfdc77665d8b38db22dc1bf4f49b4772993a8766 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 18 Apr 2025 15:21:14 -0700 Subject: [PATCH 080/171] feat(xml-logs): add tests Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- tests/XmlLogs_test.cpp | 85 + .../TerraFirmaGreg-Modern-forge.text.log | 947 ++++++ .../TerraFirmaGreg-Modern-forge.xml.log | 2854 +++++++++++++++++ .../testdata/TestLogs/vanilla-1.21.5.text.log | 25 + .../testdata/TestLogs/vanilla-1.21.5.xml.log | 75 + 5 files changed, 3986 insertions(+) create mode 100644 tests/XmlLogs_test.cpp create mode 100644 tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.text.log create mode 100644 tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.xml.log create mode 100644 tests/testdata/TestLogs/vanilla-1.21.5.text.log create mode 100644 tests/testdata/TestLogs/vanilla-1.21.5.xml.log diff --git a/tests/XmlLogs_test.cpp b/tests/XmlLogs_test.cpp new file mode 100644 index 000000000..072448c4e --- /dev/null +++ b/tests/XmlLogs_test.cpp @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2025 Rachel Powers <508861+Ryex@users.noreply.github.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2025 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +class XmlLogParseTest : public QObject { + Q_OBJECT + + private slots: + + void parseXml_data() + { + QString source = QFINDTESTDATA("testdata/TestLogs"); + + QString shortXml = QString::fromUtf8(FS::read(FS::PathCombine(source, "vanilla-1.21.5.xml.log"))); + QString shortText = QString::fromUtf8(FS::read(FS::PathCombine(source, "vanilla-1.21.5.text.log"))); + + QString longXml = QString::fromUtf8(FS::read(FS::PathCombine(source, "TerraFirmaGreg-Modern-forge.xml.log"))); + QString longText = QString::fromUtf8(FS::read(FS::PathCombine(source, "TerraFirmaGreg-Modern-forge.text.log"))); + QTest::addColumn("text"); + QTest::addColumn("xml"); + QTest::newRow("short-vanilla") << shortText << shortXml; + QTest::newRow("long-forge") << longText << longXml; + } + + void parseXml() { QFETCH(QString, ) } + + private: + LogParser m_parser; + + QList> parseLines(const QStringList& lines) + { + QList> out; + for (const auto& line : lines) + m_parser.appendLine(line); + auto items = m_parser.parseAvailable(); + for (const auto& item : items) { + if (std::holds_alternative(item)) { + auto entry = std::get(item); + auto msg = QString("[%1] [%2/%3] [%4]: %5") + .arg(entry.timestamp.toString("HH:mm:ss")) + .arg(entry.thread) + .arg(entry.levelText) + .arg(entry.logger) + .arg(entry.message); + msg = censorPrivateInfo(msg); + out.append(std::make_pair(entry.level, msg)); + } else if (std::holds_alternative(item)) { + auto msg = std::get(item).message; + level = m_instance->guessLevel(msg, level); + msg = censorPrivateInfo(msg); + out.append(std::make_pair(entry.level, msg)); + } + } + return out; + } +}; diff --git a/tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.text.log b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.text.log new file mode 100644 index 000000000..c0775ebb7 --- /dev/null +++ b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.text.log @@ -0,0 +1,947 @@ +Checking: MC_SLIM +Checking: MERGED_MAPPINGS +Checking: MAPPINGS +Checking: MC_EXTRA +Checking: MOJMAPS +Checking: PATCHED +Checking: MC_SRG +2025-04-18 12:47:23,932 main WARN Advanced terminal features are not available in this environment +[12:47:24] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher running: args [--username, Ryexandrite, --version, 1.20.1, --gameDir, /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft, --assetsDir, /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/assets, --assetIndex, 5, --uuid, , --accessToken, ❄❄❄❄❄❄❄❄, --userType, msa, --versionType, release, --launchTarget, forgeclient, --fml.forgeVersion, 47.2.6, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412, --width, 854, --height, 480] +[12:47:24] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher 10.0.9+10.0.9+main.dcd20f30 starting: java version 17.0.8 by Microsoft; OS Linux arch amd64 version 6.6.85 +[12:47:24] [main/INFO] [ne.mi.fm.lo.ImmediateWindowHandler/]: Loading ImmediateWindowProvider fmlearlywindow +[12:47:24] [main/INFO] [EARLYDISPLAY/]: Trying GL version 4.6 +[12:47:24] [main/INFO] [EARLYDISPLAY/]: Requested GL version 4.6 got version 4.6 +[12:47:24] [main/INFO] [mixin/]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%23140!/ Service=ModLauncher Env=CLIENT +[12:47:24] [pool-2-thread-1/INFO] [EARLYDISPLAY/]: GL info: AMD Radeon RX 5700 XT (radeonsi, navi10, LLVM 19.1.7, DRM 3.54, 6.6.85) GL version 4.6 (Core Profile) Mesa 25.0.3 (git-c3afa2a74f), AMD +[12:47:25] [main/WARN] [ne.mi.fm.lo.mo.ModFileParser/LOADING]: Mod file /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/net/minecraftforge/fmlcore/1.20.1-47.2.6/fmlcore-1.20.1-47.2.6.jar is missing mods.toml file +[12:47:25] [main/WARN] [ne.mi.fm.lo.mo.ModFileParser/LOADING]: Mod file /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/net/minecraftforge/javafmllanguage/1.20.1-47.2.6/javafmllanguage-1.20.1-47.2.6.jar is missing mods.toml file +[12:47:25] [main/WARN] [ne.mi.fm.lo.mo.ModFileParser/LOADING]: Mod file /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/net/minecraftforge/lowcodelanguage/1.20.1-47.2.6/lowcodelanguage-1.20.1-47.2.6.jar is missing mods.toml file +[12:47:25] [main/WARN] [ne.mi.fm.lo.mo.ModFileParser/LOADING]: Mod file /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/net/minecraftforge/mclanguage/1.20.1-47.2.6/mclanguage-1.20.1-47.2.6.jar is missing mods.toml file +[12:47:25] [main/WARN] [ne.mi.ja.se.JarSelector/]: Attempted to select two dependency jars from JarJar which have the same identification: Mod File: and Mod File: . Using Mod File: +[12:47:25] [main/INFO] [ne.mi.fm.lo.mo.JarInJarDependencyLocator/]: Found 28 dependencies adding them to mods collection +[12:47:28] [main/ERROR] [mixin/]: Mixin config dynamiclightsreforged.mixins.json does not specify "minVersion" property +[12:47:28] [main/INFO] [mixin/]: Compatibility level set to JAVA_17 +[12:47:28] [main/ERROR] [mixin/]: Mixin config mixins.satin.client.json does not specify "minVersion" property +[12:47:28] [main/ERROR] [mixin/]: Mixin config firstperson.mixins.json does not specify "minVersion" property +[12:47:28] [main/ERROR] [mixin/]: Mixin config yacl.mixins.json does not specify "minVersion" property +[12:47:28] [main/INFO] [cp.mo.mo.LaunchServiceHandler/MODLAUNCHER]: Launching target 'forgeclient' with arguments [--version, 1.20.1, --gameDir, /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft, --assetsDir, /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/assets, --uuid, , --username, Ryexandrite, --assetIndex, 5, --accessToken, ❄❄❄❄❄❄❄❄, --userType, msa, --versionType, release, --width, 854, --height, 480] +[12:47:28] [main/INFO] [co.ab.sa.co.Saturn/]: Loaded Saturn config file with 4 configurable options +[12:47:28] [main/INFO] [ModernFix/]: Loaded configuration file for ModernFix 5.18.1+mc1.20.1: 83 options available, 1 override(s) found +[12:47:28] [main/WARN] [ModernFix/]: Option 'mixin.perf.thread_priorities' overriden (by mods [smoothboot]) to 'false' +[12:47:28] [main/INFO] [ModernFix/]: Applying Nashorn fix +[12:47:28] [main/INFO] [ModernFix/]: Applied Forge config corruption patch +[12:47:28] [main/INFO] [fpsreducer/]: OptiFine was NOT detected. +[12:47:28] [main/INFO] [fpsreducer/]: OptiFabric was NOT detected. +[12:47:28] [main/WARN] [EmbeddiumConfig/]: Mod 'tfc' attempted to override option 'mixin.features.fast_biome_colors', which doesn't exist, ignoring +[12:47:28] [main/INFO] [Embeddium/]: Loaded configuration file for Embeddium: 205 options available, 3 override(s) found +[12:47:28] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Searching for graphics cards... +[12:47:28] [main/INFO] [Embeddium-GraphicsAdapterProbe/]: Found graphics card: GraphicsAdapterInfo[vendor=AMD, name=Navi 10 [Radeon RX 5600 OEM/5600 XT / 5700/5700 XT], version=unknown] +[12:47:28] [main/WARN] [Embeddium-Workarounds/]: Sodium has applied one or more workarounds to prevent crashes or other issues on your system: [NO_ERROR_CONTEXT_UNSUPPORTED] +[12:47:28] [main/WARN] [Embeddium-Workarounds/]: This is not necessarily an issue, but it may result in certain features or optimizations being disabled. You can sometimes fix these issues by upgrading your graphics driver. +[12:47:28] [main/INFO] [Radium Config/]: Loaded configuration file for Radium: 125 options available, 7 override(s) found +[12:47:28] [main/WARN] [mixin/]: Reference map 'carpeted-common-refmap.json' for carpeted-common.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:28] [main/WARN] [mixin/]: Reference map 'carpeted-forge-refmap.json' for carpeted.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:28] [main/WARN] [mixin/]: Reference map 'emi-forge-refmap.json' for emi-forge.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:28] [main/WARN] [mixin/]: Reference map 'ftb-filter-system-common-refmap.json' for ftbfiltersystem-common.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:28] [main/WARN] [mixin/]: Reference map 'ftb-filter-system-forge-refmap.json' for ftbfiltersystem.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:28] [main/INFO] [Puzzles Lib/]: Loading 160 mods: + - additionalplacements 1.8.0 + - ae2 15.2.13 + - ae2insertexportcard 1.20.1-1.3.0 + - ae2netanalyser 1.20-1.0.6-forge + - ae2wtlib 15.2.3-forge + - aiimprovements 0.5.2 + - ambientsounds 6.1.1 + - architectury 9.2.14 + - astikorcarts 1.1.8 + - attributefix 21.0.4 + - balm 7.3.9 + \-- kuma_api 20.1.8 + - barrels_2012 2.1 + - betterf3 7.0.2 + - betterfoliage 5.0.2 + - betterpingdisplay 1.1 + - betterthirdperson 1.9.0 + - blur 3.1.1 + \-- satin 1.20.1+1.15.0-SNAPSHOT + - carpeted 1.20-1.4 + - carryon 2.1.2.7 + \-- mixinextras 0.2.0-beta.6 + - catalogue 1.8.0 + - chat_heads 0.13.9 + - cherishedworlds 6.1.7+1.20.1 + - clienttweaks 11.1.0 + - cloth_config 11.1.136 + - clumps 12.0.0.4 + - computercraft 1.113.1 + - controlling 12.0.2 + - coralstfc 1.0.0 + - corpse 1.20.1-1.0.19 + - cosmeticarmorreworked 1.20.1-v1a + - craftingtweaks 18.2.5 + - craftpresence 2.5.0 + - create 0.5.1.f + \-- flywheel 0.6.10-7 + - create_connected 0.8.2-mc1.20.1 + - createaddition 1.20.1-1.2.4c + - creativecore 2.12.15 + - cucumber 7.0.12 + - cupboard 1.20.1-2.7 + - curios 5.10.0+1.20.1 + - defaultoptions 18.0.1 + - do_a_barrel_roll 3.5.6+1.20.1 + - drippyloadingscreen 3.0.1 + - dynamiclightsreforged 1.20.1_v1.6.0 + - embeddium 0.3.19+mc1.20.1 + \-- rubidium 0.7.1 + - embeddiumplus 1.2.12 + - emi 1.1.7+1.20.1+forge + - enhancedvisuals 1.8.1 + - etched 3.0.2 + - everycomp 1.20-2.7.12 + - expatternprovider 1.20-1.1.14-forge + - exposure 1.7.7 + - fallingtrees 0.12.7 + - fancymenu 3.2.3 + - ferritecore 6.0.1 + - firmaciv 0.2.10-alpha-1.20.1 + - firmalife 2.1.15 + - firstperson 2.4.5 + - flickerfix 4.0.1 + - forge 47.2.6 + - fpsreducer 1.20-2.5 + - framedblocks 9.3.1 + - ftbbackups2 1.0.23 + - ftbessentials 2001.2.2 + - ftbfiltersystem 1.0.2 + - ftblibrary 2001.2.4 + - ftbquests 2001.4.8 + - ftbranks 2001.1.3 + - ftbteams 2001.3.0 + - ftbxmodcompat 2.1.1 + - gcyr 0.1.8 + - getittogetherdrops 1.3 + - glodium 1.20-1.5-forge + - gtceu 1.2.3.a + |-- configuration 2.2.0 + \-- ldlib 1.0.25.j + - hangglider 8.0.1 + - immediatelyfast 1.2.18+1.20.4 + - inventoryhud 3.4.26 + - invtweaks 1.1.0 + - itemphysiclite 1.6.5 + - jade 11.9.4+forge + - jadeaddons 5.2.2 + - jei 15.3.0.8 + - konkrete 1.8.0 + - ksyxis 1.3.2 + - kubejs 2001.6.5-build.14 + - kubejs_create 2001.2.5-build.2 + - kubejs_tfc 1.20.1-1.1.3 + - letmedespawn 1.3.2b + - lootjs 1.20.1-2.12.0 + - megacells 2.4.4-1.20.1 + - melody 1.0.2 + - memoryleakfix 1.1.5 + - merequester 1.20.1-1.1.5 + - minecraft 1.20.1 + - modelfix 1.15 + - modernfix 5.18.1+mc1.20.1 + - moonlight 1.20-2.13.51 + - morered 4.0.0.4 + |-- jumbofurnace 4.0.0.5 + \-- useitemonblockevent 1.0.0.2 + - mousetweaks 2.25.1 + - myserveriscompatible 1.0 + - nanhealthfixer 1.20.1-0.0.1 + - nerb 0.4.1 + - noisium 2.3.0+mc1.20-1.20.1 + - noreportbutton 1.5.0 + - notenoughanimations 1.7.6 + - octolib 0.4.2 + - oculus 1.7.0 + - openpartiesandclaims 0.23.2 + - packetfixer 1.4.2 + - pandalib 0.4.2 + - patchouli 1.20.1-84-FORGE + - pickupnotifier 8.0.0 + - placebo 8.6.2 + - playerrevive 2.0.27 + - polylib 2000.0.3-build.143 + - puzzleslib 8.1.23 + \-- puzzlesaccessapi 8.0.7 + - radium 0.12.3+git.50c5c33 + - railways 1.6.4+forge-mc1.20.1 + - recipeessentials 1.20.1-3.6 + - rhino 2001.2.2-build.18 + - saturn 0.1.3 + - searchables 1.0.3 + - shimmer 1.20.1-0.2.4 + - showcaseitem 1.20.1-1.2 + - simplylight 1.20.1-1.4.6-build.50 + - smoothboot 0.0.4 + - sophisticatedbackpacks 3.20.5.1044 + - sophisticatedcore 0.6.22.611 + - supermartijn642configlib 1.1.8 + - supermartijn642corelib 1.1.17 + - tfc 3.2.12 + - tfc_tumbleweed 1.2.2 + - tfcagedalcohol 2.1 + - tfcambiental 1.20.1-3.3.0 + - tfcastikorcarts 1.1.8.2 + - tfcchannelcasting 0.2.3-beta + - tfcea 0.0.2 + - tfcgroomer 1.20.1-0.1.2 + - tfchotornot 1.0.4 + - tfcvesseltooltip 1.1 + - tfg 0.5.9 + - toofast 0.4.3.5 + - toolbelt 1.20.01 + - treetap 1.20.1-0.4.0 + - tumbleweed 0.5.5 + - unilib 1.0.2 + - uteamcore 5.1.4.312 + - waterflasks 3.0.3 + - xaerominimap 24.4.0 + - xaeroworldmap 1.39.0 + - yeetusexperimentus 2.3.1-build.6+mc1.20.1 + - yet_another_config_lib_v3 3.5.0+1.20.1-forge +[12:47:28] [main/WARN] [mixin/]: Reference map 'packetfixer-forge-forge-refmap.json' for packetfixer-forge.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:28] [main/WARN] [mixin/]: Reference map 'tfchotornot.refmap.json' for tfchotornot.mixins.json could not be read. If this is a development environment you can ignore this message +[12:47:29] [main/INFO] [ne.mi.co.Co.placebo/COREMODLOG]: Patching IForgeItemStack#getEnchantmentLevel +[12:47:29] [main/INFO] [ne.mi.co.Co.placebo/COREMODLOG]: Patching IForgeItemStack#getEnchantmentLevel +[12:47:29] [main/WARN] [mixin/]: Error loading class: mezz/modnametooltip/TooltipEventHandler (java.lang.ClassNotFoundException: mezz.modnametooltip.TooltipEventHandler) +[12:47:29] [main/WARN] [mixin/]: Error loading class: me/shedaniel/rei/impl/client/ClientHelperImpl (java.lang.ClassNotFoundException: me.shedaniel.rei.impl.client.ClientHelperImpl) +[12:47:29] [main/WARN] [mixin/]: Error loading class: me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl (java.lang.ClassNotFoundException: me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl) +[12:47:29] [main/INFO] [co.cu.Cupboard/]: Loaded config for: recipeessentials.json +[12:47:30] [main/WARN] [mixin/]: Error loading class: loaderCommon/forge/com/seibel/distanthorizons/common/wrappers/worldGeneration/mimicObject/ChunkLoader (java.lang.ClassNotFoundException: loaderCommon.forge.com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.ChunkLoader) +[12:47:30] [main/INFO] [fpsreducer/]: bre2el.fpsreducer.mixin.RenderSystemMixin will be applied. +[12:47:30] [main/INFO] [fpsreducer/]: bre2el.fpsreducer.mixin.WindowMixin will NOT be applied because OptiFine was NOT detected. +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.ChatComponentMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.ChatComponentMixin2 false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.ChatListenerMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.ClientPacketListenerMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.CommandSuggestionSuggestionsListMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.ConnectionMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.DownloadedPackSourceMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.FontStringRenderOutputMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.GuiMessageLineMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.GuiMessageMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.HttpTextureMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.PlayerChatMessageMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.SkinManagerMixin false false +[12:47:30] [main/WARN] [debug/]: dzwdz.chat_heads.mixin.compat.EmojifulMixin true false +[12:47:30] [main/WARN] [mixin/]: Error loading class: dan200/computercraft/shared/integration/jei/JEIComputerCraft (java.lang.ClassNotFoundException: dan200.computercraft.shared.integration.jei.JEIComputerCraft) +[12:47:30] [main/WARN] [mixin/]: @Mixin target dan200.computercraft.shared.integration.jei.JEIComputerCraft was not found tfg.mixins.json:common.cc.JEIComputerCraftMixin +[12:47:30] [main/WARN] [mixin/]: Error loading class: com/copycatsplus/copycats/content/copycat/slab/CopycatSlabBlock (java.lang.ClassNotFoundException: com.copycatsplus.copycats.content.copycat.slab.CopycatSlabBlock) +[12:47:30] [main/WARN] [mixin/]: @Mixin target com.copycatsplus.copycats.content.copycat.slab.CopycatSlabBlock was not found create_connected.mixins.json:compat.CopycatBlockMixin +[12:47:30] [main/WARN] [mixin/]: Error loading class: com/copycatsplus/copycats/content/copycat/board/CopycatBoardBlock (java.lang.ClassNotFoundException: com.copycatsplus.copycats.content.copycat.board.CopycatBoardBlock) +[12:47:30] [main/WARN] [mixin/]: @Mixin target com.copycatsplus.copycats.content.copycat.board.CopycatBoardBlock was not found create_connected.mixins.json:compat.CopycatBlockMixin +[12:47:30] [main/WARN] [mixin/]: Error loading class: me/jellysquid/mods/lithium/common/ai/pathing/PathNodeDefaults (java.lang.ClassNotFoundException: me.jellysquid.mods.lithium.common.ai.pathing.PathNodeDefaults) +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'alloc.blockstate.StateMixin' as option 'mixin.alloc.blockstate' (added by mods [ferritecore]) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.fluid.EntityMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.intersection.WorldMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.movement.EntityMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.unpushable_cramming.AbstractMinecartEntityMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.unpushable_cramming.BoatEntityMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.unpushable_cramming.EntityMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.unpushable_cramming.EntityPredicatesMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.unpushable_cramming.EntityTrackingSectionMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'entity.collisions.unpushable_cramming.LivingEntityMixin' as option 'mixin.entity.collisions' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [Radium Config/]: Force-disabling mixin 'world.player_chunk_tick.ThreadedAnvilChunkStorageMixin' as option 'mixin.world.player_chunk_tick' (added by user configuration) disables it and children +[12:47:30] [main/WARN] [mixin/]: Error loading class: org/cyclops/integrateddynamics/block/BlockCable (java.lang.ClassNotFoundException: org.cyclops.integrateddynamics.block.BlockCable) +[12:47:30] [main/WARN] [mixin/]: @Mixin target org.cyclops.integrateddynamics.block.BlockCable was not found mixins.epp.json:MixinBlockCable +[12:47:30] [main/WARN] [mixin/]: Error loading class: blusunrize/immersiveengineering/api/wires/GlobalWireNetwork (java.lang.ClassNotFoundException: blusunrize.immersiveengineering.api.wires.GlobalWireNetwork) +[12:47:30] [main/WARN] [mixin/]: @Mixin target blusunrize.immersiveengineering.api.wires.GlobalWireNetwork was not found mixins.epp.json:MixinGlobalWireNetwork +[12:47:30] [main/WARN] [mixin/]: Error loading class: weather2/weathersystem/storm/TornadoHelper (java.lang.ClassNotFoundException: weather2.weathersystem.storm.TornadoHelper) +[12:47:30] [main/WARN] [mixin/]: @Mixin target weather2.weathersystem.storm.TornadoHelper was not found tfc_tumbleweed.mixins.json:TornadoHelperMixin +[12:47:30] [main/WARN] [mixin/]: Error loading class: weather2/weathersystem/storm/TornadoHelper (java.lang.ClassNotFoundException: weather2.weathersystem.storm.TornadoHelper) +[12:47:30] [main/WARN] [mixin/]: @Mixin target weather2.weathersystem.storm.TornadoHelper was not found tfc_tumbleweed.mixins.json:client.TornadoHelperMixin +[12:47:30] [main/INFO] [memoryleakfix/]: [MemoryLeakFix] Will be applying 3 memory leak fixes! +[12:47:30] [main/INFO] [memoryleakfix/]: [MemoryLeakFix] Currently enabled memory leak fixes: [targetEntityLeak, biomeTemperatureLeak, hugeScreenshotLeak] +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.world.sky.WorldRendererMixin' as rule 'mixin.features.render.world.sky' (added by mods [oculus, tfc]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.world.sky.ClientWorldMixin' as rule 'mixin.features.render.world.sky' (added by mods [oculus, tfc]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.world.sky.BackgroundRendererMixin' as rule 'mixin.features.render.world.sky' (added by mods [oculus, tfc]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.gui.font.GlyphRendererMixin' as rule 'mixin.features.render.gui.font' (added by mods [oculus]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.gui.font.FontSetMixin' as rule 'mixin.features.render.gui.font' (added by mods [oculus]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.entity.shadows.EntityRenderDispatcherMixin' as rule 'mixin.features.render.entity' (added by mods [oculus]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.entity.fast_render.ModelPartMixin' as rule 'mixin.features.render.entity' (added by mods [oculus]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.entity.fast_render.CuboidMixin' as rule 'mixin.features.render.entity' (added by mods [oculus]) disables it and children +[12:47:31] [main/WARN] [Embeddium/]: Force-disabling mixin 'features.render.entity.cull.EntityRendererMixin' as rule 'mixin.features.render.entity' (added by mods [oculus]) disables it and children +[12:47:31] [main/WARN] [mixin/]: Error loading class: org/jetbrains/annotations/ApiStatus$Internal (java.lang.ClassNotFoundException: org.jetbrains.annotations.ApiStatus$Internal) +[12:47:31] [main/INFO] [MixinExtras|Service/]: Initializing MixinExtras via com.llamalad7.mixinextras.service.MixinExtrasServiceImpl(version=0.4.1). +[12:47:31] [main/INFO] [Smooth Boot (Reloaded)/]: Smooth Boot (Reloaded) config initialized +[12:47:31] [main/WARN] [mixin/]: Static binding violation: PRIVATE @Overwrite method m_216202_ in modernfix-forge.mixins.json:perf.tag_id_caching.TagOrElementLocationMixin cannot reduce visibiliy of PUBLIC target method, visibility will be upgraded. +[12:47:32] [pool-4-thread-1/INFO] [minecraft/Bootstrap]: ModernFix reached bootstrap stage (9.773 s after launch) +[12:47:32] [pool-4-thread-1/WARN] [mixin/]: @Final field delegatesByName:Ljava/util/Map; in modernfix-forge.mixins.json:perf.forge_registry_alloc.ForgeRegistryMixin should be final +[12:47:32] [pool-4-thread-1/WARN] [mixin/]: @Final field delegatesByValue:Ljava/util/Map; in modernfix-forge.mixins.json:perf.forge_registry_alloc.ForgeRegistryMixin should be final +[12:47:32] [pool-4-thread-1/INFO] [ne.mi.co.Co.placebo/COREMODLOG]: Patching IForgeItemStack#getEnchantmentLevel +[12:47:32] [pool-4-thread-1/INFO] [ne.mi.co.Co.placebo/COREMODLOG]: Patching IForgeItemStack#getEnchantmentLevel +[12:47:32] [pool-4-thread-1/INFO] [ModernFix/]: Injecting BlockStateBase cache population hook into getNeighborPathNodeType from me.jellysquid.mods.lithium.mixin.ai.pathing.AbstractBlockStateMixin +[12:47:32] [pool-4-thread-1/INFO] [ModernFix/]: Injecting BlockStateBase cache population hook into getPathNodeType from me.jellysquid.mods.lithium.mixin.ai.pathing.AbstractBlockStateMixin +[12:47:32] [pool-4-thread-1/INFO] [ModernFix/]: Injecting BlockStateBase cache population hook into getAllFlags from me.jellysquid.mods.lithium.mixin.util.block_tracking.AbstractBlockStateMixin +[12:47:32] [pool-4-thread-1/WARN] [mixin/]: Method overwrite conflict for m_6104_ in embeddium.mixins.json:features.options.render_layers.LeavesBlockMixin, previously written by me.srrapero720.embeddiumplus.mixins.impl.leaves_culling.LeavesBlockMixin. Skipping method. +[12:47:32] [pool-4-thread-1/INFO] [ne.mi.co.Co.placebo/COREMODLOG]: Patching IForgeItemStack#getEnchantmentLevel +[12:47:32] [pool-4-thread-1/INFO] [ne.mi.co.Co.placebo/COREMODLOG]: Patching IForgeItemStack#getEnchantmentLevel +[12:47:33] [pool-4-thread-1/INFO] [minecraft/Bootstrap]: Vanilla bootstrap took 779 milliseconds +[12:47:34] [pool-4-thread-1/WARN] [mixin/]: Method overwrite conflict for m_47505_ in lithium.mixins.json:world.temperature_cache.BiomeMixin, previously written by org.embeddedt.modernfix.common.mixin.perf.remove_biome_temperature_cache.BiomeMixin. Skipping method. +[12:47:34] [pool-4-thread-1/INFO] [co.al.me.MERequester/]: Registering content +[12:47:35] [Render thread/WARN] [minecraft/VanillaPackResourcesBuilder]: Assets URL 'union:/home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/net/minecraft/client/1.20.1-20230612.114412/client-1.20.1-20230612.114412-srg.jar%23444!/assets/.mcassetsroot' uses unexpected schema +[12:47:35] [Render thread/WARN] [minecraft/VanillaPackResourcesBuilder]: Assets URL 'union:/home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/libraries/net/minecraft/client/1.20.1-20230612.114412/client-1.20.1-20230612.114412-srg.jar%23444!/data/.mcassetsroot' uses unexpected schema +[12:47:35] [Render thread/INFO] [mojang/YggdrasilAuthenticationService]: Environment: authHost='https://authserver.mojang.com', accountsHost='https://api.mojang.com', sessionHost='https://sessionserver.mojang.com', servicesHost='https://api.minecraftservices.com', name='PROD' +[12:47:35] [Render thread/INFO] [minecraft/Minecraft]: Setting user: Ryexandrite +[12:47:35] [Render thread/INFO] [ModernFix/]: Bypassed Mojang DFU +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant is searching for constants in method with descriptor (Lnet/minecraft/network/chat/Component;Lnet/minecraft/client/GuiMessageTag;)V +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found STRING constant: value = , stringValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found a matching constant TYPE at ordinal 0 +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found LdcInsn +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found STRING constant: value = \\r, stringValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found a matching constant TYPE at ordinal 1 +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found LdcInsn \\r +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found STRING constant: value = +, stringValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found a matching constant TYPE at ordinal 2 +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found LdcInsn + +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found STRING constant: value = \\n, stringValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found a matching constant TYPE at ordinal 3 +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found LdcInsn \\n +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found CLASS constant: value = Ljava/lang/String;, typeValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found STRING constant: value = [{}] [CHAT] {}, stringValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found a matching constant TYPE at ordinal 4 +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found LdcInsn [{}] [CHAT] {} +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found STRING constant: value = [CHAT] {}, stringValue = null +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found a matching constant TYPE at ordinal 5 +[12:47:35] [Render thread/INFO] [mixin/]: BeforeConstant found LdcInsn [CHAT] {} +[12:47:35] [Render thread/INFO] [defaultoptions/]: Loaded default options for extra-folder +[12:47:35] [Render thread/INFO] [ModernFix/]: Instantiating Mojang DFU +[12:47:36] [Render thread/INFO] [minecraft/Minecraft]: Backend library: LWJGL version 3.3.1 build 7 +[12:47:36] [Render thread/INFO] [KubeJS/]: Loaded client.properties +[12:47:36] [Render thread/INFO] [Embeddium-PostlaunchChecks/]: OpenGL Vendor: AMD +[12:47:36] [Render thread/INFO] [Embeddium-PostlaunchChecks/]: OpenGL Renderer: AMD Radeon RX 5700 XT (radeonsi, navi10, LLVM 19.1.7, DRM 3.54, 6.6.85) +[12:47:36] [Render thread/INFO] [Embeddium-PostlaunchChecks/]: OpenGL Version: 4.6 (Core Profile) Mesa 25.0.3 (git-c3afa2a74f) +[12:47:36] [Render thread/WARN] [Embeddium++/Config]: Loading Embeddium++Config +[12:47:36] [Render thread/INFO] [Embeddium++/Config]: Updating config cache +[12:47:36] [Render thread/INFO] [Embeddium++/Config]: Cache updated successfully +[12:47:36] [Render thread/INFO] [ImmediatelyFast/]: Initializing ImmediatelyFast 1.2.18+1.20.4 on AMD Radeon RX 5700 XT (radeonsi, navi10, LLVM 19.1.7, DRM 3.54, 6.6.85) (AMD) with OpenGL 4.6 (Core Profile) Mesa 25.0.3 (git-c3afa2a74f) +[12:47:36] [Render thread/INFO] [ImmediatelyFast/]: AMD GPU detected. Enabling coherent buffer mapping +[12:47:36] [Datafixer Bootstrap/INFO] [mojang/DataFixerBuilder]: 188 Datafixer optimizations took 85 milliseconds +[12:47:36] [Render thread/INFO] [ImmediatelyFast/]: Found Iris/Oculus 1.7.0. Enabling compatibility. +[12:47:36] [Render thread/INFO] [Oculus/]: Debug functionality is disabled. +[12:47:36] [Render thread/INFO] [Oculus/]: OpenGL 4.5 detected, enabling DSA. +[12:47:36] [Render thread/INFO] [Oculus/]: Shaders are disabled because no valid shaderpack is selected +[12:47:36] [Render thread/INFO] [Oculus/]: Shaders are disabled +[12:47:36] [modloading-worker-0/INFO] [dynamiclightsreforged/]: [LambDynLights] Initializing Dynamic Lights Reforged... +[12:47:36] [modloading-worker-0/INFO] [LowDragLib/]: LowDragLib is initializing on platform: Forge +[12:47:36] [modloading-worker-0/INFO] [in.u_.u_.ut.ve.JarSignVerifier/]: Mod uteamcore is signed with a valid certificate. +[12:47:36] [modloading-worker-0/INFO] [de.ke.me.Melody/]: [MELODY] Loading Melody background audio library.. +[12:47:36] [modloading-worker-0/INFO] [Puzzles Lib/]: Constructing common components for pickupnotifier:main +[12:47:36] [modloading-worker-0/WARN] [mixin/]: Static binding violation: PRIVATE @Overwrite method m_109501_ in embeddium.mixins.json:core.render.world.WorldRendererMixin cannot reduce visibiliy of PUBLIC target method, visibility will be upgraded. +[12:47:36] [modloading-worker-0/INFO] [de.ke.ko.Konkrete/]: [KONKRETE] Successfully initialized! +[12:47:36] [modloading-worker-0/INFO] [de.ke.ko.Konkrete/]: [KONKRETE] Server-side libs ready to use! +[12:47:36] [modloading-worker-0/INFO] [Puzzles Lib/]: Constructing client components for pickupnotifier:main +[12:47:36] [modloading-worker-0/WARN] [mixin/]: Static binding violation: PRIVATE @Overwrite method m_215924_ in modernfix-forge.mixins.json:perf.tag_id_caching.TagEntryMixin cannot reduce visibiliy of PUBLIC target method, visibility will be upgraded. +[12:47:36] [modloading-worker-0/INFO] [Additional Placements/]: Attempting to manually load Additional Placements config early. +[12:47:36] [modloading-worker-0/INFO] [Additional Placements/]: manual config load successful. +[12:47:36] [modloading-worker-0/WARN] [Additional Placements/]: During block registration you may recieve several reports of "Potentially Dangerous alternative prefix `additionalplacements`". Ignore these, they are intended. +[12:47:36] [modloading-worker-0/INFO] [Puzzles Lib/]: Constructing common components for hangglider:main +[12:47:36] [modloading-worker-0/INFO] [noisium/]: Loading Noisium. +[12:47:36] [modloading-worker-0/INFO] [co.cu.Cupboard/]: Loaded config for: cupboard.json +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id architectury:sync_ids +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id architectury:sync_ids +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id pandalib:config_sync +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id pandalib:config_sync +[12:47:36] [modloading-worker-0/INFO] [fallingtrees | Config/]: Successfully loaded config 'fallingtrees_client' +[12:47:36] [modloading-worker-0/INFO] [fallingtrees | Config/]: Successfully saved config 'fallingtrees_client' +[12:47:36] [modloading-worker-0/INFO] [Puzzles Lib/]: Constructing client components for hangglider:main +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id polylib:container_to_client +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id polylib:tile_to_client +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id polylib:container_packet_server +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id polylib:tile_data_server +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id polylib:tile_packet_server +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftblibrary:edit_nbt +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftblibrary:edit_nbt_response +[12:47:36] [modloading-worker-0/INFO] [fallingtrees | Config/]: Successfully loaded config 'fallingtrees_common' +[12:47:36] [modloading-worker-0/INFO] [fallingtrees | Config/]: Successfully saved config 'fallingtrees_common' +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftblibrary:sync_known_server_registries +[12:47:36] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftblibrary:edit_config +[12:47:37] [UniLib/INFO] [unilib/]: Starting version check for "craftpresence" (MC 1.20.1) at "https://raw.githubusercontent.com/CDAGaming/VersionLibrary/master/CraftPresence/update.json" +[12:47:37] [UniLib/INFO] [unilib/]: Starting version check for "unilib" (MC 1.20.1) at "https://raw.githubusercontent.com/CDAGaming/VersionLibrary/master/UniLib/update.json" +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbessentials:update_tab_name +[12:47:37] [modloading-worker-0/INFO] [invtweaks/]: Registered 2 network packets +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Loaded common.properties +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Loaded dev.properties +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Looking for KubeJS plugins... +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbteams:sync_teams +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbteams:sync_message_history +[12:47:37] [modloading-worker-0/INFO] [GregTechCEu/]: GregTechCEu is initializing on platform: Forge +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source kubejs +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbteams:open_gui +[12:47:37] [CraftPresence/INFO] [craftpresence/]: Configuration settings have been saved and reloaded successfully! +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbteams:open_my_team_gui +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbteams:update_settings +[12:47:37] [modloading-worker-0/WARN] [KubeJS/]: Plugin dev.latvian.mods.kubejs.integration.forge.gamestages.GameStagesIntegration does not have required mod gamestages loaded, skipping +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source ldlib +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source exposure +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source tfg +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbteams:update_settings_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbteams:send_message +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source ftbxmodcompat +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbteams:send_message_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbteams:update_presence +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbteams:create_party +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbteams:player_gui_operation +[12:47:37] [modloading-worker-0/WARN] [KubeJS/]: Plugin dev.ftb.mods.ftbxmodcompat.ftbchunks.kubejs.FTBChunksKubeJSPlugin does not have required mod ftbchunks loaded, skipping +[12:47:37] [modloading-worker-0/INFO] [de.ke.dr.DrippyLoadingScreen/]: [DRIPPY LOADING SCREEN] Loading v3.0.1 in client-side mode on FORGE! +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source lootjs +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source cucumber +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source gtceu +[12:47:37] [modloading-worker-0/INFO] [ne.dr.tf.TerraFirmaCraft/]: Initializing TerraFirmaCraft +[12:47:37] [modloading-worker-0/INFO] [ne.dr.tf.TerraFirmaCraft/]: Options: Assertions Enabled = false, Boostrap = false, Test = false, Debug Logging = true +[12:47:37] [CraftPresence/INFO] [craftpresence/]: Checking Discord for available assets with Client Id: 1182610212121743470 +[12:47:37] [CraftPresence/INFO] [craftpresence/]: Originally coded by paulhobbel - https://github.com/paulhobbel +[12:47:37] [modloading-worker-0/INFO] [ne.mi.co.ForgeMod/FORGEMOD]: Forge mod loading, version 47.2.6, for MC 1.20.1 with MCP 20230612.114412 +[12:47:37] [modloading-worker-0/INFO] [ne.mi.co.MinecraftForge/FORGE]: MinecraftForge v47.2.6 Initialized +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source gcyr +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source kubejs_tfc +[12:47:37] [modloading-worker-0/WARN] [KubeJS/]: Plugin com.notenoughmail.kubejs_tfc.addons.precpros.PrecProsPlugin does not have required mod precisionprospecting loaded, skipping +[12:47:37] [modloading-worker-0/WARN] [KubeJS/]: Plugin com.notenoughmail.kubejs_tfc.addons.afc.AFCPlugin does not have required mod afc loaded, skipping +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Found plugin source kubejs_create +[Mouse Tweaks] Main.initialize() +[Mouse Tweaks] Initialized. +[12:47:37] [modloading-worker-0/INFO] [Every Compat/]: Loaded EveryCompat Create Module +[12:47:37] [modloading-worker-0/INFO] [Configuration/FileWatching]: Registered gtceu config for auto-sync function +[12:47:37] [modloading-worker-0/INFO] [Configuration/FileWatching]: Registered gtceu config for auto-sync function +[12:47:37] [modloading-worker-0/INFO] [Configuration/FileWatching]: Registered gcyr config for auto-sync function +[12:47:37] [modloading-worker-0/INFO] [GregTechCEu/]: High-Tier is Disabled. +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.im.StdSchedulerFactory/]: Using default implementation for ThreadExecutor +[12:47:37] [modloading-worker-0/INFO] [KubeJS/]: Done in 309.0 ms +[12:47:37] [CraftPresence/INFO] [craftpresence/]: 3 total assets detected! +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_quests +[12:47:37] [modloading-worker-0/INFO] [Ksyxis/]: Ksyxis: Booting... (platform: Forge, manual: false) +[12:47:37] [modloading-worker-0/INFO] [Ksyxis/]: Ksyxis: Found Mixin library. (version: 0.8.5) +[12:47:37] [modloading-worker-0/INFO] [Ksyxis/]: Ksyxis: Ready. As always, this mod will speed up your world loading and might or might not break it. +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_team_data +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.co.SchedulerSignalerImpl/]: Initialized Scheduler Signaller of type: class net.creeperhost.ftbbackups.repack.org.quartz.core.SchedulerSignalerImpl +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.co.QuartzScheduler/]: Quartz Scheduler v.2.0.2 created. +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:update_task_progress +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.si.RAMJobStore/]: RAMJobStore initialized. +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:submit_task +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.co.QuartzScheduler/]: Scheduler meta-data: Quartz Scheduler (v2.0.2) 'ftbbackups2' with instanceId 'NON_CLUSTERED' + Scheduler class: 'net.creeperhost.ftbbackups.repack.org.quartz.core.QuartzScheduler' - running locally. + NOT STARTED. + Currently in standby mode. + Number of jobs executed: 0 + Using thread pool 'net.creeperhost.ftbbackups.repack.org.quartz.simpl.SimpleThreadPool' - with 1 threads. + Using job-store 'net.creeperhost.ftbbackups.repack.org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered. + +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.im.StdSchedulerFactory/]: Quartz scheduler 'ftbbackups2' initialized from an externally provided properties instance. +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.im.StdSchedulerFactory/]: Quartz scheduler version: 2.0.2 +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:claim_reward +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:claim_reward_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_editing_mode +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:get_emergency_items +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:create_other_team_data +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:claim_all_rewards +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:claim_choice_reward +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:display_completion_toast +[12:47:37] [modloading-worker-0/INFO] [ne.cr.ft.re.or.qu.co.QuartzScheduler/]: Scheduler ftbbackups2_$_NON_CLUSTERED started. +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:display_reward_toast +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:display_item_reward_toast +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:toggle_pinned +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:toggle_pinned_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:toggle_chapter_pinned +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:toggle_chapter_pinned_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:toggle_editing_mode +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:force_save +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:update_team_data +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:set_custom_image +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:object_started +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:object_completed +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:object_started_reset +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:object_completed_reset +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_lock +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:reset_reward +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:team_data_changed +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:task_screen_config_req +[12:47:37] [modloading-worker-0/INFO] [co.jo.fl.ba.Backend/]: Oculus detected. +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:task_screen_config_resp +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:change_progress +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:create_object +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:create_object_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:create_task_at +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:delete_object +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:delete_object_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:edit_object +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:edit_object_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:move_chapter +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:move_chapter_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:move_quest +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:move_quest_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:change_chapter_group +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:change_chapter_group_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:move_chapter_group +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:move_chapter_group_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_reward_blocking +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:copy_quest +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:copy_chapter_image +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:sync_structures_request +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_structures_response +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbquests:request_team_data +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:sync_editor_permission +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:open_quest_book +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ftbquests:clear_display_cache +[12:47:37] [modloading-worker-0/INFO] [me.je.li.lo.PluginCaller/]: Sending ConfigManager... +[12:47:37] [modloading-worker-0/INFO] [me.je.li.lo.PluginCaller/]: Sending ConfigManager took 11.32 ms +[12:47:37] [modloading-worker-0/INFO] [MEGA Cells/]: Initialised items. +[12:47:37] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ftbfiltersystem:sync_filter +[12:47:37] [modloading-worker-0/INFO] [MEGA Cells/]: Initialised blocks. +[12:47:37] [modloading-worker-0/INFO] [MEGA Cells/]: Initialised block entities. +[12:47:37] [modloading-worker-0/INFO] [de.ke.fa.FancyMenu/]: [FANCYMENU] Loading v3.2.3 in client-side mode on FORGE! +[12:47:37] [modloading-worker-0/INFO] [showcaseitem/]: Loading config: /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft/config/showcaseitem-common.toml +[12:47:37] [modloading-worker-0/INFO] [showcaseitem/]: Built config: /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft/config/showcaseitem-common.toml +[12:47:37] [modloading-worker-0/INFO] [showcaseitem/]: Loaded config: /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft/config/showcaseitem-common.toml +[12:47:37] [modloading-worker-0/INFO] [FTB XMod Compat/]: [FTB Quests] Enabled KubeJS integration +[12:47:37] [modloading-worker-0/INFO] [me.tr.be.BetterF3Forge/]: [BetterF3] Starting... +[12:47:37] [modloading-worker-0/INFO] [me.tr.be.BetterF3Forge/]: [BetterF3] Loading... +[12:47:37] [modloading-worker-0/INFO] [de.to.pa.PacketFixer/]: Packet Fixer has been initialized successfully +[12:47:37] [modloading-worker-0/INFO] [YetAnotherConfigLib/]: Deserializing YACLConfig from '/home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft/config/yacl.json5' +[12:47:37] [modloading-worker-0/INFO] [Puzzles Lib/]: Constructing common components for puzzleslib:main +[12:47:37] [modloading-worker-0/INFO] [me.tr.be.BetterF3Forge/]: [BetterF3] All done! +[12:47:37] [modloading-worker-0/INFO] [Puzzles Lib/]: Constructing client components for puzzleslib:main +[12:47:37] [modloading-worker-0/INFO] [Rhino Script Remapper/]: Loading Rhino Minecraft remapper... +[12:47:37] [modloading-worker-0/INFO] [de.la.mo.rh.mo.ut.RhinoProperties/]: Rhino properties loaded. +[12:47:37] [modloading-worker-0/INFO] [Rhino Script Remapper/]: Loading mappings for 1.20.1 +[12:47:37] [modloading-worker-0/WARN] [mixin/]: @Inject(@At("INVOKE")) Shift.BY=2 on create_connected.mixins.json:sequencedgearshift.SequencedGearshiftScreenMixin::handler$cfa000$updateParamsOfRow exceeds the maximum allowed value: 0. Increase the value of maxShiftBy to suppress this warning. +[12:47:37] [modloading-worker-0/INFO] [Rhino Script Remapper/]: Done in 0.090 s +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registered bogey styles from railways +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering data fixers +[12:47:38] [modloading-worker-0/WARN] [Railways/]: Skipping Datafixer Registration due to it being disabled in the config. +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Hex Casting +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Oh The Biomes You'll Go +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Blue Skies +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Twilight Forest +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Biomes O' Plenty +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Nature's Spirit +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Dreams and Desires +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for Quark +[12:47:38] [modloading-worker-0/INFO] [Railways/]: Registering tracks for TerraFirmaCraft +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:main_startup_script.js in 0.058 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:tfc/constants.js in 0.024 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:horornot/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:minecraft/constants.js in 0.004 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:railways/constants.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:gtceu/machines.js in 0.008 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:gtceu/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:gtceu/material_info.js in 0.002 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:gtceu/recipe_types.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:gtceu/blocks.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:gtceu/items.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:framedblocks/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:firmalife/constants.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:sophisticated_backpacks/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:more_red/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:mega_cells/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:create/constants.js in 0.002 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:ae2/constants.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:create_additions/constants.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:chisel_and_bits/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:extended_ae2/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:asticor_carts/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:firmaciv/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:firmaciv/blocks.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:ftb_quests/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:tfg/fluids.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:tfg/materials.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:tfg/blocks.js in 0.001 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:tfg/items.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded script startup_scripts:computer_craft/constants.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: Loaded 30/30 KubeJS startup scripts in 0.721 s with 0 errors and 0 warnings +[12:47:38] [modloading-worker-0/INFO] [KubeJS Client/]: example.js#3: TerraFirmaGreg the best modpack in the world :) +[12:47:38] [modloading-worker-0/INFO] [KubeJS Client/]: Loaded script client_scripts:example.js in 0.0 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Client/]: Loaded script client_scripts:tooltips.js in 0.003 s +[12:47:38] [modloading-worker-0/INFO] [KubeJS Client/]: Loaded 2/2 KubeJS client scripts in 0.022 s with 0 errors and 0 warnings +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: tfg/blocks.js#48: Loaded Java class 'net.minecraft.world.level.block.AmethystClusterBlock' +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: tfg/blocks.js#49: Loaded Java class 'net.minecraft.world.level.block.Blocks' +[12:47:38] [modloading-worker-0/INFO] [KubeJS Startup/]: tfg/blocks.js#50: Loaded Java class 'net.minecraft.world.level.block.state.BlockBehaviour$Properties' +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id kubejs:send_data_from_client +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:send_data_from_server +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:paint +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:add_stage +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:remove_stage +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:sync_stages +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id kubejs:first_click +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:toast +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:reload_startup_scripts +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:display_server_errors +[12:47:38] [modloading-worker-0/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id kubejs:display_client_errors +[12:47:38] [Render thread/INFO] [GregTechCEu/]: GTCEu common proxy init! +[12:47:38] [Render thread/INFO] [GregTechCEu/]: Registering material registries +[12:47:38] [Render thread/INFO] [GregTechCEu/]: Registering GTCEu Materials +[12:47:38] [CraftPresence/INFO] [craftpresence/]: Attempting to connect to Discord (1/10)... +[12:47:39] [Render thread/INFO] [GregTechCEu/]: Registering addon Materials +[12:47:39] [Render thread/WARN] [GregTechCEu/]: FluidStorageKey{gtceu:liquid} already has an associated fluid for material gtceu:water +[12:47:39] [Render thread/WARN] [GregTechCEu/]: FluidStorageKey{gtceu:liquid} already has an associated fluid for material gtceu:lava +[12:47:39] [CraftPresence/INFO] [craftpresence/]: Loaded display data with Client Id: 1182610212121743470 (Logged in as RyRy) +[12:47:39] [Render thread/INFO] [GregTechCEu/]: Registering KeyBinds +[12:47:39] [Render thread/WARN] [ne.mi.fm.DeferredWorkQueue/LOADING]: Mod 'gtceu' took 1.043 s to run a deferred task. +[12:47:42] [Render thread/WARN] [ne.mi.re.ForgeRegistry/REGISTRIES]: Registry minecraft:menu: The object net.minecraft.world.inventory.MenuType@67141ef8 has been registered twice for the same name ae2:export_card. +[12:47:42] [Render thread/WARN] [ne.mi.re.ForgeRegistry/REGISTRIES]: Registry minecraft:menu: The object net.minecraft.world.inventory.MenuType@f4864e9 has been registered twice for the same name ae2:insert_card. +[12:47:42] [Render thread/INFO] [Moonlight/]: Initialized block sets in 21ms +[12:47:42] [Render thread/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering C2S receiver with id ae2wtlib:cycle_terminal +[12:47:43] [Render thread/INFO] [Every Compat/]: Registering Compat WoodType Blocks +[12:47:43] [Render thread/INFO] [Every Compat/]: EveryCompat Create Module: registered 42 WoodType blocks +[12:47:43] [Render thread/INFO] [tf.TFCTumbleweed/]: Injecting TFC Tumbleweed override pack +[12:47:43] [Render thread/INFO] [co.ee.fi.FirmaLife/]: Injecting firmalife override pack +[ALSOFT] (EE) Failed to set real-time priority for thread: Operation not permitted (1) +[12:47:43] [Render thread/INFO] [Oculus/]: Hardware information: +[12:47:43] [Render thread/INFO] [Oculus/]: CPU: 16x AMD Ryzen 7 3700X 8-Core Processor +[12:47:43] [Render thread/INFO] [Oculus/]: GPU: AMD Radeon RX 5700 XT (radeonsi, navi10, LLVM 19.1.7, DRM 3.54, 6.6.85) (Supports OpenGL 4.6 (Core Profile) Mesa 25.0.3 (git-c3afa2a74f)) +[12:47:43] [Render thread/INFO] [Oculus/]: OS: Linux (6.6.85) +[12:47:44] [Render thread/WARN] [mixin/]: Method overwrite conflict for isHidden in mixins.oculus.compat.sodium.json:copyEntity.ModelPartMixin, previously written by dev.tr7zw.firstperson.mixins.ModelPartMixin. Skipping method. +[12:47:44] [Render thread/INFO] [minecraft/Minecraft]: [FANCYMENU] Registering resource reload listener.. +[12:47:44] [Render thread/INFO] [de.ke.fa.cu.ScreenCustomization/]: [FANCYMENU] Initializing screen customization engine! Addons should NOT REGISTER TO REGISTRIES anymore now! +[12:47:44] [Render thread/INFO] [de.ke.fa.cu.la.ScreenCustomizationLayerHandler/]: [FANCYMENU] Minecraft resource reload: STARTING +[12:47:44] [Render thread/INFO] [ModernFix/]: Invalidating pack caches +[12:47:44] [Render thread/INFO] [minecraft/ReloadableResourceManager]: Reloading ResourceManager: Additional Placements blockstate redirection pack, vanilla, mod_resources, gtceu:dynamic_assets, Moonlight Mods Dynamic Assets, Firmalife-1.20.1-2.1.15.jar:overload, TFCTumbleweed-1.20.1-1.2.2.jar:overload, KubeJS Resource Pack [assets], ldlib +[12:47:44] [Finalizer/WARN] [ModernFix/]: One or more BufferBuilders have been leaked, ModernFix will attempt to correct this. +[12:47:45] [Render thread/INFO] [Every Compat/]: Generated runtime CLIENT_RESOURCES for pack Moonlight Mods Dynamic Assets (everycomp) in: 597 ms +[12:47:45] [Render thread/INFO] [Moonlight/]: Generated runtime CLIENT_RESOURCES for pack Moonlight Mods Dynamic Assets (moonlight) in: 0 ms +[12:47:45] [modloading-worker-0/INFO] [Puzzles Lib/]: Loading client config for pickupnotifier +[12:47:45] [modloading-worker-0/INFO] [Puzzles Lib/]: Loading client config for hangglider +[12:47:45] [Worker-ResourceReload-4/INFO] [minecraft/UnihexProvider]: Found unifont_all_no_pua-15.0.06.hex, loading +[12:47:45] [Worker-ResourceReload-3/INFO] [xa.pa.OpenPartiesAndClaims/]: Loading Open Parties and Claims! +[12:47:45] [Worker-ResourceReload-1/INFO] [co.re.RecipeEssentials/]: recipeessentials mod initialized +[12:47:45] [Worker-ResourceReload-10/INFO] [ne.dr.tf.TerraFirmaCraft/]: TFC Common Setup +[12:47:45] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [saturn] Starting version check at https://github.com/AbdElAziz333/Saturn/raw/mc1.20.1/dev/updates.json +[12:47:45] [Worker-ResourceReload-1/INFO] [FTB Library/]: Setting game stages provider implementation to: KubeJS Stages +[12:47:45] [Worker-ResourceReload-1/INFO] [FTB XMod Compat/]: Chose [KubeJS Stages] as the active game stages implementation +[12:47:45] [Worker-ResourceReload-1/INFO] [FTB Library/]: Setting permissions provider implementation to: FTB Ranks +[12:47:45] [Worker-ResourceReload-1/INFO] [FTB XMod Compat/]: Chose [FTB Ranks] as the active permissions implementation +[12:47:45] [Worker-ResourceReload-1/INFO] [FTB XMod Compat/]: [FTB Quests] recipe helper provider is [JEI] +[12:47:45] [Worker-ResourceReload-1/INFO] [FTB XMod Compat/]: [FTB Quests] Enabled FTB Filter System integration +[12:47:45] [Render thread/INFO] [GregTechCEu/]: GregTech Model loading took 520ms +[12:47:46] [Render thread/INFO] [minecraft/LoadingOverlay]: [DRIPPY LOADING SCREEN] Initializing fonts for text rendering.. +[12:47:46] [Render thread/INFO] [minecraft/LoadingOverlay]: [DRIPPY LOADING SCREEN] Calculating animation sizes for FancyMenu.. +[12:47:46] [Render thread/INFO] [de.ke.fa.cu.la.ScreenCustomizationLayerHandler/]: [FANCYMENU] ScreenCustomizationLayer registered: drippy_loading_overlay +[12:47:46] [Render thread/INFO] [de.ke.fa.cu.an.AnimationHandler/]: [FANCYMENU] Preloading animations! This could cause the loading screen to freeze for a while.. +[12:47:46] [Render thread/INFO] [de.ke.fa.cu.an.AnimationHandler/]: [FANCYMENU] Finished preloading animations! +[12:47:46] [Render thread/INFO] [de.ke.fa.FancyMenu/]: [FANCYMENU] Starting late client initialization phase.. +[12:47:46] [Forge Version Check/WARN] [ne.mi.fm.VersionChecker/]: Failed to process update information +com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 9 column 1 path $ + at com.google.gson.Gson.fromJson(Gson.java:1226) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:1124) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:1034) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:969) ~[gson-2.10.jar%2388!/:?] {} + at net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:183) ~[fmlcore-1.20.1-47.2.6.jar%23445!/:?] {} + at java.lang.Iterable.forEach(Iterable.java:75) ~[?:?] {re:mixin} + at net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) ~[fmlcore-1.20.1-47.2.6.jar%23445!/:?] {} +Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 9 column 1 path $ + at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:393) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:182) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:144) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:1214) ~[gson-2.10.jar%2388!/:?] {} + ... 6 more +[12:47:46] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [controlling] Starting version check at https://updates.blamejared.com/get?n=controlling&gv=1.20.1 +[12:47:46] [Worker-ResourceReload-6/ERROR] [minecraft/SimpleJsonResourceReloadListener]: Couldn't parse data file tfc:field_guide/ru_ru/entries/tfg_ores/surface_copper from tfc:patchouli_books/field_guide/ru_ru/entries/tfg_ores/surface_copper.json +com.google.gson.JsonParseException: com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 55 column 6 path $.pages[5] + at net.minecraft.util.GsonHelper.m_13780_(GsonHelper.java:526) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:classloading,re:mixin} + at net.minecraft.util.GsonHelper.m_263475_(GsonHelper.java:531) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:classloading,re:mixin} + at net.minecraft.util.GsonHelper.m_13776_(GsonHelper.java:581) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:classloading,re:mixin} + at net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener.m_278771_(SimpleJsonResourceReloadListener.java:41) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,re:classloading} + at net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener.m_5944_(SimpleJsonResourceReloadListener.java:29) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,re:classloading} + at net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener.m_5944_(SimpleJsonResourceReloadListener.java:17) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,re:classloading} + at net.minecraft.server.packs.resources.SimplePreparableReloadListener.m_10786_(SimplePreparableReloadListener.java:11) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,re:classloading,pl:accesstransformer:B,pl:mixin:APP:moonlight.mixins.json:ConditionHackMixin,pl:mixin:A} + at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768) ~[?:?] {} + at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1760) ~[?:?] {} + at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) ~[?:?] {} + at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) ~[?:?] {} + at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) ~[?:?] {re:mixin,re:computing_frames} + at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) ~[?:?] {re:mixin,re:computing_frames} + at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) ~[?:?] {re:mixin} +Caused by: com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 55 column 6 path $.pages[5] + at com.google.gson.stream.JsonReader.syntaxError(JsonReader.java:1657) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.stream.JsonReader.checkLenient(JsonReader.java:1463) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:569) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.stream.JsonReader.hasNext(JsonReader.java:422) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.TypeAdapters$28.read(TypeAdapters.java:779) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.TypeAdapters$28.read(TypeAdapters.java:725) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.TypeAdapters$34$1.read(TypeAdapters.java:1007) ~[gson-2.10.jar%2388!/:?] {} + at net.minecraft.util.GsonHelper.m_13780_(GsonHelper.java:524) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:classloading,re:mixin} + ... 13 more +[12:47:46] [Render thread/INFO] [de.ke.fa.ut.wi.WindowHandler/]: [FANCYMENU] Custom window icon successfully updated! +[12:47:46] [Render thread/INFO] [KubeJS Client/]: Client resource reload complete! +[12:47:46] [Render thread/INFO] [defaultoptions/]: Loaded default options for keymappings +[12:47:46] [Render thread/INFO] [de.ke.fa.ut.wi.WindowHandler/]: [FANCYMENU] Custom window icon successfully updated! +[12:47:46] [Worker-Main-6/INFO] [minecraft/UnihexProvider]: Found unifont_all_no_pua-15.0.06.hex, loading +[12:47:47] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [controlling] Found status: BETA Current: 12.0.2 Target: 12.0.2 +[12:47:47] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [uteamcore] Starting version check at https://api.u-team.info/update/uteamcore.json +[12:47:47] [FTB Backups Config Watcher 0/INFO] [ne.cr.ft.FTBBackups/]: Config at /home/ryex/.var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances/TerraFirmaGreg-Modern/minecraft/config/ftbbackups2.json has changed, reloaded! +[12:47:47] [Worker-ResourceReload-14/WARN] [minecraft/SpriteLoader]: Texture create_connected:block/fluid_container_window_debug with size 40x32 limits mip level from 4 to 3 +[12:47:47] [UniLib/INFO] [unilib/]: Received update status for "unilib" -> Outdated (Target version: "v1.0.5") +[12:47:47] [UniLib/INFO] [unilib/]: Received update status for "craftpresence" -> Outdated (Target version: "v2.5.4") +[12:47:47] [Render thread/INFO] [Every Compat/]: Registered 42 compat blocks making up 0.31% of total blocks registered +[12:47:47] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [uteamcore] Found status: OUTDATED Current: 5.1.4.312 Target: 5.1.4.346 +[12:47:47] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [pickupnotifier] Starting version check at https://raw.githubusercontent.com/Fuzss/modresources/main/update/pickupnotifier.json +[12:47:47] [Render thread/INFO] [Moonlight/]: Initialized color sets in 104ms +[12:47:47] [Render thread/INFO] [co.no.ku.KubeJSTFC/]: KubeJS TFC configuration: +[12:47:47] [Render thread/INFO] [co.no.ku.KubeJSTFC/]: Debug mode enabled: false +[12:47:47] [Render thread/INFO] [MEGA Cells/]: Initialised AE2WT integration. +[12:47:47] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [pickupnotifier] Found status: UP_TO_DATE Current: 8.0.0 Target: null +[12:47:47] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [corpse] Starting version check at https://update.maxhenkel.de/forge/corpse +[12:47:47] [Worker-ResourceReload-0/INFO] [FirstPersonModel/]: Loading FirstPerson Mod +[12:47:47] [Worker-ResourceReload-4/INFO] [xa.ma.WorldMap/]: Loading Xaero's World Map - Stage 1/2 +[12:47:47] [Placebo Patreon Trail Loader/INFO] [placebo/]: Loading patreon trails data... +[12:47:47] [Placebo Patreon Wing Loader/INFO] [placebo/]: Loading patreon wing data... +[12:47:47] [Worker-ResourceReload-13/INFO] [de.ke.ko.Konkrete/]: [KONKRETE] Client-side libs ready to use! +[12:47:47] [Placebo Patreon Trail Loader/INFO] [placebo/]: Loaded 45 patreon trails. +[12:47:47] [Placebo Patreon Wing Loader/INFO] [placebo/]: Loaded 21 patreon wings. +[12:47:47] [Worker-ResourceReload-7/INFO] [EMI/]: [EMI] Discovered Sodium +[12:47:47] [Worker-ResourceReload-14/INFO] [xa.mi.XaeroMinimap/]: Loading Xaero's Minimap - Stage 1/2 +[12:47:47] [Worker-ResourceReload-13/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ae2wtlib:update_wut +[12:47:47] [Worker-ResourceReload-13/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ae2wtlib:update_restock +[12:47:47] [Worker-ResourceReload-13/INFO] [de.ar.ne.fo.NetworkManagerImpl/]: Registering S2C receiver with id ae2wtlib:restock_amounts +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [corpse] Found status: OUTDATED Current: 1.20.1-1.0.19 Target: 1.20.1-1.0.20 +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [create_connected] Starting version check at https://raw.githubusercontent.com/hlysine/create_connected/main/update.json +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [create_connected] Found status: OUTDATED Current: 0.8.2-mc1.20.1 Target: 1.0.1-mc1.20.1 +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [blur] Starting version check at https://api.modrinth.com/updates/rubidium-extra/forge_updates.json +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [blur] Found status: AHEAD Current: 3.1.1 Target: null +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [hangglider] Starting version check at https://raw.githubusercontent.com/Fuzss/modresources/main/update/hangglider.json +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [hangglider] Found status: UP_TO_DATE Current: 8.0.1 Target: null +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [searchables] Starting version check at https://updates.blamejared.com/get?n=searchables&gv=1.20.1 +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [searchables] Found status: BETA Current: 1.0.3 Target: 1.0.3 +[12:47:48] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [computercraft] Starting version check at https://api.modrinth.com/updates/cc-tweaked/forge_updates.json +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [computercraft] Found status: OUTDATED Current: 1.113.1 Target: 1.115.1 +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [unilib] Starting version check at https://raw.githubusercontent.com/CDAGaming/VersionLibrary/master/UniLib/update.json +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [unilib] Found status: AHEAD Current: 1.0.2 Target: null +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [craftpresence] Starting version check at https://raw.githubusercontent.com/CDAGaming/VersionLibrary/master/CraftPresence/update.json +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [craftpresence] Found status: AHEAD Current: 2.5.0 Target: null +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [radium] Starting version check at https://api.modrinth.com/updates/radium/forge_updates.json +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [radium] Found status: OUTDATED Current: 0.12.3+git.50c5c33 Target: 0.12.4 +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [attributefix] Starting version check at https://updates.blamejared.com/get?n=attributefix&gv=1.20.1 +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [attributefix] Found status: BETA Current: 21.0.4 Target: 21.0.4 +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [clumps] Starting version check at https://updates.blamejared.com/get?n=clumps&gv=1.20.1 +[12:47:49] [Worker-ResourceReload-4/WARN] [xa.hu.mi.MinimapLogs/]: io exception while checking patreon: Online mod data expired! Date: Thu Apr 17 01:03:51 MST 2025 +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [clumps] Found status: BETA Current: 12.0.0.4 Target: 12.0.0.4 +[12:47:49] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [catalogue] Starting version check at https://mrcrayfish.com/modupdatejson?id=catalogue +[12:47:50] [Forge Version Check/WARN] [ne.mi.fm.VersionChecker/]: Failed to process update information +com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $ + at com.google.gson.Gson.fromJson(Gson.java:1226) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:1124) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:1034) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:969) ~[gson-2.10.jar%2388!/:?] {} + at net.minecraftforge.fml.VersionChecker$1.process(VersionChecker.java:183) ~[fmlcore-1.20.1-47.2.6.jar%23445!/:?] {} + at java.lang.Iterable.forEach(Iterable.java:75) ~[?:?] {re:mixin} + at net.minecraftforge.fml.VersionChecker$1.run(VersionChecker.java:114) ~[fmlcore-1.20.1-47.2.6.jar%23445!/:?] {} +Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $ + at com.google.gson.stream.JsonReader.beginObject(JsonReader.java:393) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:182) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:144) ~[gson-2.10.jar%2388!/:?] {} + at com.google.gson.Gson.fromJson(Gson.java:1214) ~[gson-2.10.jar%2388!/:?] {} + ... 6 more +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [puzzlesaccessapi] Starting version check at https://raw.githubusercontent.com/Fuzss/modresources/main/update/puzzlesaccessapi.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [puzzlesaccessapi] Found status: BETA Current: 8.0.7 Target: null +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [forge] Starting version check at https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json +[12:47:50] [Worker-ResourceReload-5/WARN] [minecraft/ModelBakery]: tfcambiental:snowshoes#inventory +java.io.FileNotFoundException: tfcambiental:models/item/snowshoes.json + at net.minecraft.client.resources.model.ModelBakery.m_119364_(ModelBakery.java:417) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.m_119362_(ModelBakery.java:266) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.m_119341_(ModelBakery.java:243) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.m_119306_(ModelBakery.java:384) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.(ModelBakery.java:150) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelManager.m_246505_(ModelManager.java:83) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at java.util.concurrent.CompletableFuture.biApply(CompletableFuture.java:1311) ~[?:?] {re:mixin} + at java.util.concurrent.CompletableFuture$BiApply.tryFire(CompletableFuture.java:1280) ~[?:?] {} + at java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:483) ~[?:?] {} + at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) ~[?:?] {} + at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) ~[?:?] {} + at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) ~[?:?] {re:mixin,re:computing_frames} + at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) ~[?:?] {re:mixin,re:computing_frames} + at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) ~[?:?] {re:mixin} +[12:47:50] [Worker-ResourceReload-5/WARN] [minecraft/ModelBakery]: carpeted:block/label +java.io.FileNotFoundException: carpeted:models/block/label.json + at net.minecraft.client.resources.model.ModelBakery.m_119364_(ModelBakery.java:417) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.m_119362_(ModelBakery.java:262) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.m_119341_(ModelBakery.java:243) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelBakery.(ModelBakery.java:159) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at net.minecraft.client.resources.model.ModelManager.m_246505_(ModelManager.java:83) ~[client-1.20.1-20230612.114412-srg.jar%23444!/:?] {re:mixin,pl:accesstransformer:B,pl:runtimedistcleaner:A,re:classloading,pl:accesstransformer:B,pl:mixin:A,pl:runtimedistcleaner:A} + at java.util.concurrent.CompletableFuture.biApply(CompletableFuture.java:1311) ~[?:?] {re:mixin} + at java.util.concurrent.CompletableFuture$BiApply.tryFire(CompletableFuture.java:1280) ~[?:?] {} + at java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:483) ~[?:?] {} + at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) ~[?:?] {} + at java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) ~[?:?] {} + at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) ~[?:?] {re:mixin,re:computing_frames} + at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) ~[?:?] {re:mixin,re:computing_frames} + at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) ~[?:?] {re:mixin} +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [forge] Found status: OUTDATED Current: 47.2.6 Target: 47.4.0 +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [moonlight] Starting version check at https://raw.githubusercontent.com/MehVahdJukaar/Moonlight/multi-loader/forge/update.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [moonlight] Found status: BETA Current: 1.20-2.13.51 Target: null +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [configuration] Starting version check at https://raw.githubusercontent.com/Toma1O6/UpdateSchemas/master/configuration-forge.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [configuration] Found status: OUTDATED Current: 2.2.0 Target: 2.2.1 +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [smoothboot] Starting version check at https://github.com/AbdElAziz333/SmoothBoot-Reloaded/raw/mc1.20.1/dev/updates.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [smoothboot] Found status: UP_TO_DATE Current: 0.0.4 Target: null +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [ksyxis] Starting version check at https://raw.githubusercontent.com/VidTu/Ksyxis/main/updater_ksyxis_forge.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [ksyxis] Found status: OUTDATED Current: 1.3.2 Target: 1.3.3 +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [flywheel] Starting version check at https://api.modrinth.com/updates/flywheel/forge_updates.json +[12:47:50] [Worker-ResourceReload-4/ERROR] [xa.ma.WorldMap/]: io exception while checking versions: Online mod data expired! Date: Thu Apr 17 01:03:51 MST 2025 +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [flywheel] Found status: BETA Current: 0.6.10-7 Target: null +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [inventoryhud] Starting version check at https://raw.githubusercontent.com/DmitryLovin/pluginUpdate/master/invupdate.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [inventoryhud] Found status: UP_TO_DATE Current: 3.4.26 Target: null +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [puzzleslib] Starting version check at https://raw.githubusercontent.com/Fuzss/modresources/main/update/puzzleslib.json +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [puzzleslib] Found status: OUTDATED Current: 8.1.23 Target: 8.1.32 +[12:47:50] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [betterf3] Starting version check at https://api.modrinth.com/updates/betterf3/forge_updates.json +[12:47:50] [Render thread/INFO] [xa.mi.XaeroMinimap/]: Loading Xaero's Minimap - Stage 2/2 +[12:47:51] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [betterf3] Found status: UP_TO_DATE Current: 7.0.2 Target: null +[12:47:51] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [packetfixer] Starting version check at https://api.modrinth.com/updates/packet-fixer/forge_updates.json +[12:47:51] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [packetfixer] Found status: OUTDATED Current: 1.4.2 Target: 2.0.0 +[12:47:51] [Render thread/WARN] [xa.hu.mi.MinimapLogs/]: io exception while checking versions: Online mod data expired! Date: Thu Apr 17 01:03:51 MST 2025 +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Registered player tracker system: minimap_synced +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Xaero's Minimap: World Map found! +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Registered player tracker system: openpartiesandclaims +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Xaero's Minimap: Open Parties And Claims found! +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: No Optifine! +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Xaero's Minimap: No Vivecraft! +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Xaero's Minimap: Framed Blocks found! +[12:47:51] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Xaero's Minimap: Iris found! +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Loading Xaero's World Map - Stage 2/2 +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: New world map region cache hash code: -815523079 +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Registered player tracker system: map_synced +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Xaero's WorldMap Mod: Xaero's minimap found! +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Registered player tracker system: minimap_synced +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Registered player tracker system: openpartiesandclaims +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Xaero's WorldMap Mod: Open Parties And Claims found! +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: No Optifine! +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Xaero's World Map: No Vivecraft! +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Xaero's World Map: Framed Blocks found! +[12:47:51] [Render thread/INFO] [xa.ma.WorldMap/]: Xaero's World Map: Iris found! +[12:47:56] [Worker-ResourceReload-5/WARN] [minecraft/ModelManager]: Missing textures in model firmaciv:firmaciv_compass#inventory: + minecraft:textures/atlas/blocks.png:minecraft:item/compass +[12:47:56] [Worker-ResourceReload-5/WARN] [minecraft/ModelManager]: Missing textures in model gtceu:tin_double_ingot#inventory: + minecraft:textures/atlas/blocks.png:gtceu:item/material_sets/dull/ingot_double_overlay +[12:47:56] [Worker-ResourceReload-5/WARN] [minecraft/ModelManager]: Missing textures in model createaddition:small_light_connector#facing=west,mode=push,powered=true,rotation=x_clockwise_180,variant=default: + minecraft:textures/atlas/blocks.png:create:block/chute_block +[12:47:56] [Worker-ResourceReload-5/WARN] [minecraft/ModelManager]: Missing textures in model gtceu:copper_double_ingot#inventory: + minecraft:textures/atlas/blocks.png:gtceu:item/material_sets/dull/ingot_double_overlay +Reloading Dynamic Lights +[12:47:57] [Render thread/INFO] [co.jo.fl.ba.Backend/]: Loaded all shader sources. +Create Crafts & Additions Initialized! +[12:47:57] [Worker-ResourceReload-2/INFO] [AttributeFix/]: Loaded values for 19 compatible attributes. +[12:47:57] [Worker-ResourceReload-2/ERROR] [AttributeFix/]: Attribute ID 'minecolonies:mc_mob_damage' does not belong to a known attribute. This entry will be ignored. +[12:47:57] [Worker-ResourceReload-2/INFO] [AttributeFix/]: Loaded 20 values from config. +[12:47:57] [Worker-ResourceReload-2/INFO] [AttributeFix/]: Saving config file. 20 entries. +[12:47:57] [Worker-ResourceReload-2/INFO] [AttributeFix/]: Applying changes for 20 attributes. +[12:47:57] [Worker-ResourceReload-11/INFO] [de.me.as.AstikorCarts/]: Automatic pull animal configuration: +pull_animals = [ + "minecraft:camel", + "minecraft:donkey", + "minecraft:horse", + "minecraft:mule", + "minecraft:skeleton_horse", + "minecraft:zombie_horse", + "minecraft:player", + "tfc:donkey", + "tfc:mule", + "tfc:horse" + ] +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at io.github.mortuusars.exposure.integration.jade.ExposureJadePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at de.maxhenkel.corpse.integration.waila.PluginCorpse +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at xfacthd.framedblocks.common.compat.jade.FramedJadePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at snownee.jade.addon.general.GeneralPlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at snownee.jade.addon.create.CreatePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at me.pandamods.fallingtrees.compat.JadePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at com.gregtechceu.gtceu.integration.jade.GTJadePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at net.dries007.tfc.compat.jade.JadeIntegration +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at snownee.jade.addon.vanilla.VanillaPlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at snownee.jade.addon.universal.UniversalPlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at snownee.jade.addon.core.CorePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at appeng.integration.modules.jade.JadeModule +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at com.glodblock.github.extendedae.xmod.jade.JadePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at cy.jdkdigital.treetap.compat.jade.JadePlugin +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at com.eerussianguy.firmalife.compat.tooltip.JadeIntegration +[12:47:57] [Worker-ResourceReload-9/INFO] [Jade/]: Start loading plugin at com.ljuangbminecraft.tfcchannelcasting.compat.JadeIntegration +[12:47:59] [Render thread/WARN] [ne.mi.fm.DeferredWorkQueue/LOADING]: Mod 'create_connected' took 1.342 s to run a deferred task. +[12:47:59] [Render thread/INFO] [defaultoptions/]: Loaded default options for keymappings +[ALSOFT] (EE) Failed to set real-time priority for thread: Operation not permitted (1) +[12:47:59] [Render thread/INFO] [mojang/Library]: OpenAL initialized on device Starship/Matisse HD Audio Controller Analog Stereo +[12:47:59] [Render thread/INFO] [minecraft/SoundEngine]: Sound engine started +[12:47:59] [Render thread/INFO] [minecraft/SoundEngine]: [FANCYMENU] Reloading AudioResourceHandler after Minecraft SoundEngine reload.. +[12:47:59] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 4096x2048x4 minecraft:textures/atlas/blocks.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 1024x512x4 minecraft:textures/atlas/signs.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 512x512x4 minecraft:textures/atlas/banner_patterns.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 512x512x4 minecraft:textures/atlas/shield_patterns.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 2048x1024x4 minecraft:textures/atlas/armor_trims.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 1024x1024x4 minecraft:textures/atlas/chest.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 128x64x4 minecraft:textures/atlas/decorated_pot.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 512x256x4 minecraft:textures/atlas/shulker_boxes.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 512x256x4 minecraft:textures/atlas/beds.png-atlas +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh particle. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_solid. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader fsh rendertype_solid. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_cutout_mipped. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader fsh rendertype_cutout_mipped. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_cutout. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader fsh rendertype_cutout. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_translucent. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_armor_cutout_no_cull. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_solid. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_cutout. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_cutout_no_cull. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_cutout_no_cull_z_offset. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_translucent_cull. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_translucent. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader rendertype_entity_translucent_emissive could not find sampler named Sampler2 in the specified shader program. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_smooth_cutout. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_decal. +[12:48:00] [Render thread/INFO] [Shimmer/]: inject shader vsh rendertype_entity_no_outline. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader moonlight:text_alpha_color could not find sampler named Sampler2 in the specified shader program. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader moonlight:text_alpha_color could not find uniform named IViewRotMat in the specified shader program. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader moonlight:text_alpha_color could not find uniform named FogShape in the specified shader program. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader shimmer:rendertype_armor_cutout_no_cull could not find sampler named Sampler2 in the specified shader program. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader shimmer:rendertype_armor_cutout_no_cull could not find uniform named Light0_Direction in the specified shader program. +[12:48:00] [Render thread/WARN] [minecraft/ShaderInstance]: Shader shimmer:rendertype_armor_cutout_no_cull could not find uniform named Light1_Direction in the specified shader program. +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 2048x1024x0 minecraft:textures/atlas/particles.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 256x256x0 minecraft:textures/atlas/paintings.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 256x128x0 minecraft:textures/atlas/mob_effects.png-atlas +[12:48:00] [Render thread/INFO] [xa.ma.WorldMap/]: Successfully reloaded the world map shaders! +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Loading exposure filters: +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:light_blue_pane, exposure:shaders/light_blue_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:orange_pane, exposure:shaders/orange_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:red_pane, exposure:shaders/red_filter.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:purple_pane, exposure:shaders/purple_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:blue_pane, exposure:shaders/blue_filter.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:light_gray_pane, exposure:shaders/light_gray_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:magenta_pane, exposure:shaders/magenta_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:gray_pane, exposure:shaders/gray_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:lime_pane, exposure:shaders/lime_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:green_pane, exposure:shaders/green_filter.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:pink_pane, exposure:shaders/pink_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:yellow_pane, exposure:shaders/yellow_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:white_pane, exposure:shaders/white_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:brown_pane, exposure:shaders/brown_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:glass_pane, exposure:shaders/crisp.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:interplanar_projector, exposure:shaders/invert.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:black_pane, exposure:shaders/black_tint.json] added. +[12:48:00] [Render thread/INFO] [io.gi.mo.ex.Exposure/]: Filter [exposure:cyan_pane, exposure:shaders/cyan_tint.json] added. +[12:48:00] [Render thread/INFO] [patchouli/]: BookContentResourceListenerLoader preloaded 1073 jsons +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 128x128x0 computercraft:textures/atlas/gui.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 512x256x0 polylib:textures/atlas/gui.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 256x128x0 jei:textures/atlas/gui.png-atlas +[12:48:00] [Render thread/INFO] [minecraft/TextureAtlas]: Created: 256x256x0 moonlight:textures/atlas/map_markers.png-atlas +[12:48:00] [Render thread/INFO] [xa.hu.mi.MinimapLogs/]: Successfully reloaded the minimap shaders! +[12:48:00] [Render thread/INFO] [Shimmer/]: buildIn shimmer configuration is enabled, this can be disabled by config file +[12:48:00] [Render thread/INFO] [Shimmer/]: mod jar and resource pack discovery: file managed my minecraft located in [sourceName:KubeJS Resource Pack [assets],location:KubeJS Resource Pack [assets]] +[12:48:00] [Render thread/ERROR] [Shimmer/]: can't find block framedblocks:framed_gate_door from file managed my minecraft located in [sourceName:KubeJS Resource Pack [assets],location:KubeJS Resource Pack [assets]] +[12:48:00] [Render thread/ERROR] [Shimmer/]: can't find block framedblocks:framed_iron_gate_door from file managed my minecraft located in [sourceName:KubeJS Resource Pack [assets],location:KubeJS Resource Pack [assets]] +[12:48:00] [Render thread/ERROR] [Shimmer/]: can't find block framedblocks:framed_gate_door from file managed my minecraft located in [sourceName:KubeJS Resource Pack [assets],location:KubeJS Resource Pack [assets]] +[12:48:00] [Render thread/ERROR] [Shimmer/]: can't find block framedblocks:framed_iron_gate_door from file managed my minecraft located in [sourceName:KubeJS Resource Pack [assets],location:KubeJS Resource Pack [assets]] +[12:48:00] [Render thread/INFO] [de.ke.fa.ut.re.ResourceHandlers/]: [FANCYMENU] Reloading resources.. +[12:48:00] [Render thread/INFO] [de.ke.fa.ut.re.pr.ResourcePreLoader/]: [FANCYMENU] Pre-loading resources.. +[12:48:00] [Render thread/INFO] [de.ke.fa.cu.la.ScreenCustomizationLayerHandler/]: [FANCYMENU] Updating animation sizes.. +[12:48:00] [Render thread/INFO] [de.ke.fa.cu.la.ScreenCustomizationLayerHandler/]: [FANCYMENU] Minecraft resource reload: FINISHED +[12:48:00] [Render thread/INFO] [de.ke.fa.cu.la.ScreenCustomizationLayerHandler/]: [FANCYMENU] ScreenCustomizationLayer registered: title_screen +[12:48:00] [Render thread/INFO] [Oculus/]: Creating pipeline for dimension NamespacedId{namespace='minecraft', name='overworld'} +[12:48:01] [Render thread/INFO] [ambientsounds/]: Loaded AmbientEngine 'basic' v3.1.0. 11 dimension(s), 11 features, 11 blockgroups, 2 sound collections, 37 regions, 58 sounds, 11 sound categories, 5 solids and 2 biome types +[12:48:01] [Render thread/INFO] [FirstPersonModel/]: PlayerAnimator not found! +[12:48:01] [Render thread/INFO] [FirstPersonModel/]: Loaded Vanilla Hands items: [] +[12:48:01] [Render thread/INFO] [FirstPersonModel/]: Loaded Auto Disable items: [camera] +[12:48:02] [Render thread/WARN] [ModernFix/]: Game took 40.304 seconds to start diff --git a/tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.xml.log b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.xml.log new file mode 100644 index 000000000..51e5ec546 --- /dev/null +++ b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-forge.xml.log @@ -0,0 +1,2854 @@ +Checking: MC_SLIM +Checking: MERGED_MAPPINGS +Checking: MAPPINGS +Checking: MC_EXTRA +Checking: MOJMAPS +Checking: PATCHED +Checking: MC_SRG + + , --accessToken, ❄❄❄❄❄❄❄❄, --userType, msa, --versionType, release, --launchTarget, forgeclient, --fml.forgeVersion, 47.2.6, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412, --width, 854, --height, 480]]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , --username, Ryexandrite, --assetIndex, 5, --accessToken, ❄❄❄❄❄❄❄❄, --userType, msa, --versionType, release, --width, 854, --height, 480]]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Outdated (Target version: "v2.5.4")]]> + +[Mouse Tweaks] Main.initialize() +[Mouse Tweaks] Initialized. + + + + + Outdated (Target version: "v1.0.5")]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[ALSOFT] (EE) Failed to set real-time priority for thread: Operation not permitted (1) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (ModelBakery.java:150) + at TRANSFORMER/minecraft@1.20.1/net.minecraft.client.resources.model.ModelManager.m_246505_(ModelManager.java:83) + at java.base/java.util.concurrent.CompletableFuture.biApply(CompletableFuture.java:1311) + at java.base/java.util.concurrent.CompletableFuture$BiApply.tryFire(CompletableFuture.java:1280) + at java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:483) + at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) + at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) + at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) + at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) + at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) +]]> + + + + (ModelBakery.java:159) + at TRANSFORMER/minecraft@1.20.1/net.minecraft.client.resources.model.ModelManager.m_246505_(ModelManager.java:83) + at java.base/java.util.concurrent.CompletableFuture.biApply(CompletableFuture.java:1311) + at java.base/java.util.concurrent.CompletableFuture$BiApply.tryFire(CompletableFuture.java:1280) + at java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:483) + at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) + at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) + at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) + at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) + at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Reloading Dynamic Lights + + + +Create Crafts & Additions Initialized! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[ALSOFT] (EE) Failed to set real-time priority for thread: Operation not permitted (1) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/testdata/TestLogs/vanilla-1.21.5.text.log b/tests/testdata/TestLogs/vanilla-1.21.5.text.log new file mode 100644 index 000000000..e78702858 --- /dev/null +++ b/tests/testdata/TestLogs/vanilla-1.21.5.text.log @@ -0,0 +1,25 @@ +[12:50:56] [Datafixer Bootstrap/INFO]: 263 Datafixer optimizations took 908 milliseconds +[12:50:58] [Render thread/INFO]: Environment: Environment[sessionHost=https://sessionserver.mojang.com, servicesHost=https://api.minecraftservices.com, name=PROD] +[12:50:58] [Render thread/INFO]: Setting user: Ryexandrite +[12:50:58] [Render thread/INFO]: Backend library: LWJGL version 3.3.3+5 +[12:50:58] [Render thread/INFO]: Using optional rendering extensions: GL_KHR_debug, GL_ARB_vertex_attrib_binding, GL_ARB_direct_state_access +[12:50:58] [Render thread/INFO]: Reloading ResourceManager: vanilla +[12:50:59] [Worker-Main-6/INFO]: Found unifont_all_no_pua-16.0.01.hex, loading +[12:50:59] [Worker-Main-7/INFO]: Found unifont_jp_patch-16.0.01.hex, loading +[12:50:59] [Render thread/WARN]: minecraft:pipeline/entity_translucent_emissive shader program does not use sampler Sampler2 defined in the pipeline. This might be a bug. +[12:50:59] [Render thread/INFO]: OpenAL initialized on device Starship/Matisse HD Audio Controller Analog Stereo +[12:50:59] [Render thread/INFO]: Sound engine started +[12:50:59] [Render thread/INFO]: Created: 1024x512x4 minecraft:textures/atlas/blocks.png-atlas +[12:50:59] [Render thread/INFO]: Created: 256x256x4 minecraft:textures/atlas/signs.png-atlas +[12:50:59] [Render thread/INFO]: Created: 512x512x4 minecraft:textures/atlas/banner_patterns.png-atlas +[12:50:59] [Render thread/INFO]: Created: 512x512x4 minecraft:textures/atlas/shield_patterns.png-atlas +[12:50:59] [Render thread/INFO]: Created: 2048x1024x4 minecraft:textures/atlas/armor_trims.png-atlas +[12:50:59] [Render thread/INFO]: Created: 256x256x4 minecraft:textures/atlas/chest.png-atlas +[12:50:59] [Render thread/INFO]: Created: 128x64x4 minecraft:textures/atlas/decorated_pot.png-atlas +[12:50:59] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/beds.png-atlas +[12:50:59] [Render thread/INFO]: Created: 512x256x4 minecraft:textures/atlas/shulker_boxes.png-atlas +[12:50:59] [Render thread/INFO]: Created: 64x64x0 minecraft:textures/atlas/map_decorations.png-atlas +[12:50:59] [Render thread/INFO]: Created: 512x256x0 minecraft:textures/atlas/particles.png-atlas +[12:51:00] [Render thread/INFO]: Created: 512x256x0 minecraft:textures/atlas/paintings.png-atlas +[12:51:00] [Render thread/INFO]: Created: 256x128x0 minecraft:textures/atlas/mob_effects.png-atlas +[12:51:00] [Render thread/INFO]: Created: 1024x512x0 minecraft:textures/atlas/gui.png-atlas diff --git a/tests/testdata/TestLogs/vanilla-1.21.5.xml.log b/tests/testdata/TestLogs/vanilla-1.21.5.xml.log new file mode 100644 index 000000000..24bfe0b7f --- /dev/null +++ b/tests/testdata/TestLogs/vanilla-1.21.5.xml.log @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 21570a03fbb2973712f1ffe5f1bb4d6095e53e4c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 18 Apr 2025 15:22:39 -0700 Subject: [PATCH 081/171] feat(xml-logs): finish tests Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/BaseInstance.h | 3 - launcher/MessageLevel.cpp | 17 +- launcher/launch/LaunchTask.cpp | 12 +- launcher/launch/LogModel.cpp | 8 + launcher/launch/LogModel.h | 2 + launcher/logs/LogParser.cpp | 50 +- launcher/logs/LogParser.h | 5 +- launcher/minecraft/MinecraftInstance.cpp | 43 - launcher/minecraft/MinecraftInstance.h | 3 - launcher/ui/pages/instance/OtherLogsPage.cpp | 7 +- tests/CMakeLists.txt | 4 + tests/XmlLogs_test.cpp | 104 +- .../TestLogs/TerraFirmaGreg-Modern-levels.txt | 945 ++++++++++++++++++ .../TerraFirmaGreg-Modern-xml-levels.txt | 869 ++++++++++++++++ .../TestLogs/vanilla-1.21.5-levels.txt | 25 + 15 files changed, 2006 insertions(+), 91 deletions(-) create mode 100644 tests/testdata/TestLogs/TerraFirmaGreg-Modern-levels.txt create mode 100644 tests/testdata/TestLogs/TerraFirmaGreg-Modern-xml-levels.txt create mode 100644 tests/testdata/TestLogs/vanilla-1.21.5-levels.txt diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 2a2b4dc4a..99ce49a62 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -151,9 +151,6 @@ class BaseInstance : public QObject, public std::enable_shared_from_this -#include #include #include #include @@ -237,9 +236,9 @@ bool LaunchTask::parseXmlLogs(QString const& line, MessageLevel::Enum level) model.append(MessageLevel::Error, tr("[Log4j Parse Error] Failed to parse log4j log event: %1").arg(err.value().errMessage)); return false; } else { - if (items.has_value()) { + if (!items.isEmpty()) { auto& model = *getLogModel(); - for (auto const& item : items.value()) { + for (auto const& item : items) { if (std::holds_alternative(item)) { auto entry = std::get(item); auto msg = QString("[%1] [%2/%3] [%4]: %5") @@ -252,7 +251,7 @@ bool LaunchTask::parseXmlLogs(QString const& line, MessageLevel::Enum level) model.append(entry.level, msg); } else if (std::holds_alternative(item)) { auto msg = std::get(item).message; - level = m_instance->guessLevel(msg, level); + level = LogParser::guessLevel(msg, model.previousLevel()); msg = censorPrivateInfo(msg); model.append(level, msg); } @@ -281,15 +280,16 @@ void LaunchTask::onLogLine(QString line, MessageLevel::Enum level) level = innerLevel; } + auto& model = *getLogModel(); + // If the level is still undetermined, guess level if (level == MessageLevel::Unknown) { - level = m_instance->guessLevel(line, level); + level = LogParser::guessLevel(line, model.previousLevel()); } // censor private user info line = censorPrivateInfo(line); - auto& model = *getLogModel(); model.append(level, line); } diff --git a/launcher/launch/LogModel.cpp b/launcher/launch/LogModel.cpp index 45aac6099..5d32be9a2 100644 --- a/launcher/launch/LogModel.cpp +++ b/launcher/launch/LogModel.cpp @@ -166,3 +166,11 @@ bool LogModel::isOverFlow() { return m_numLines >= m_maxLines && m_stopOnOverflow; } + + +MessageLevel::Enum LogModel::previousLevel() { + if (!m_content.isEmpty()) { + return m_content.last().level; + } + return MessageLevel::Unknown; +} diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h index ba7b14487..f4664c47c 100644 --- a/launcher/launch/LogModel.h +++ b/launcher/launch/LogModel.h @@ -31,6 +31,8 @@ class LogModel : public QAbstractListModel { void setColorLines(bool state); bool colorLines() const; + MessageLevel::Enum previousLevel(); + enum Roles { LevelRole = Qt::UserRole }; private /* types */: diff --git a/launcher/logs/LogParser.cpp b/launcher/logs/LogParser.cpp index 294036134..c5d2a8647 100644 --- a/launcher/logs/LogParser.cpp +++ b/launcher/logs/LogParser.cpp @@ -19,6 +19,9 @@ #include "LogParser.h" +#include +#include "MessageLevel.h" + void LogParser::appendLine(QAnyStringView data) { if (!m_partialData.isEmpty()) { @@ -202,7 +205,7 @@ std::optional LogParser::parseNext() } } -std::optional> LogParser::parseAvailable() +QList LogParser::parseAvailable() { QList items; bool doNext = true; @@ -320,3 +323,48 @@ std::optional LogParser::parseLog4J() throw std::runtime_error("unreachable: already verified this was a complete log4j:Event"); } + +MessageLevel::Enum LogParser::guessLevel(const QString& line, MessageLevel::Enum level) +{ + static const QRegularExpression LINE_WITH_LEVEL("^\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); + auto match = LINE_WITH_LEVEL.match(line); + if (match.hasMatch()) { + // New style logs from log4j + QString timestamp = match.captured("timestamp"); + QString levelStr = match.captured("level"); + if (levelStr == "INFO") + level = MessageLevel::Info; + if (levelStr == "WARN") + level = MessageLevel::Warning; + if (levelStr == "ERROR") + level = MessageLevel::Error; + if (levelStr == "FATAL") + level = MessageLevel::Fatal; + if (levelStr == "TRACE" || levelStr == "DEBUG") + level = MessageLevel::Debug; + } else { + // Old style forge logs + if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") || + line.contains("[FINEST]")) + level = MessageLevel::Info; + if (line.contains("[SEVERE]") || line.contains("[STDERR]")) + level = MessageLevel::Error; + if (line.contains("[WARNING]")) + level = MessageLevel::Warning; + if (line.contains("[DEBUG]")) + level = MessageLevel::Debug; + } + if (level != MessageLevel::Unknown) + return level; + + if (line.contains("overwriting existing")) + return MessageLevel::Fatal; + + // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * + // static const QRegularExpression JAVA_EXCEPTION( + // R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); + // + // if (line.contains(JAVA_EXCEPTION)) + // return MessageLevel::Error; + return MessageLevel::Info; +} diff --git a/launcher/logs/LogParser.h b/launcher/logs/LogParser.h index 462ea43cf..8b23754ac 100644 --- a/launcher/logs/LogParser.h +++ b/launcher/logs/LogParser.h @@ -55,9 +55,12 @@ class LogParser { void appendLine(QAnyStringView data); std::optional parseNext(); - std::optional> parseAvailable(); + QList parseAvailable(); std::optional getError(); + /// guess log level from a line of game log + static MessageLevel::Enum guessLevel(const QString& line, MessageLevel::Enum level); + protected: MessageLevel::Enum parseLogLevel(const QString& level); std::optional parseAttributes(); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index ec136ede0..e8db24b10 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1004,49 +1004,6 @@ QMap MinecraftInstance::createCensorFilterFromSession(AuthSess return filter; } -MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLevel::Enum level) -{ - if (line.contains("overwriting existing")) - return MessageLevel::Fatal; - - // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * - static const QRegularExpression JAVA_EXCEPTION( - R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); - - if (line.contains(JAVA_EXCEPTION)) - return MessageLevel::Error; - - static const QRegularExpression LINE_WITH_LEVEL("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); - auto match = LINE_WITH_LEVEL.match(line); - if (match.hasMatch()) { - // New style logs from log4j - QString timestamp = match.captured("timestamp"); - QString levelStr = match.captured("level"); - if (levelStr == "INFO") - level = MessageLevel::Message; - if (levelStr == "WARN") - level = MessageLevel::Warning; - if (levelStr == "ERROR") - level = MessageLevel::Error; - if (levelStr == "FATAL") - level = MessageLevel::Fatal; - if (levelStr == "TRACE" || levelStr == "DEBUG") - level = MessageLevel::Debug; - } else { - // Old style forge logs - if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") || - line.contains("[FINEST]")) - level = MessageLevel::Message; - if (line.contains("[SEVERE]") || line.contains("[STDERR]")) - level = MessageLevel::Error; - if (line.contains("[WARNING]")) - level = MessageLevel::Warning; - if (line.contains("[DEBUG]")) - level = MessageLevel::Debug; - } - return level; -} - IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher() { auto combined = std::make_shared(); diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 5d9bb45ef..cd5cd1ddc 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -139,9 +139,6 @@ class MinecraftInstance : public BaseInstance { QProcessEnvironment createEnvironment() override; QProcessEnvironment createLaunchEnvironment() override; - /// guess log level from a line of minecraft log - MessageLevel::Enum guessLevel(const QString& line, MessageLevel::Enum level) override; - IPathMatcher::Ptr getLogFileMatcher() override; QString getLogFileRoot() override; diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 0aeb942a8..8b8c64c6d 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -179,7 +179,9 @@ void OtherLogsPage::on_btnReload_clicked() showTooBig(); return; } - auto handleLine = [this](QString line) { + MessageLevel::Enum last = MessageLevel::Unknown; + + auto handleLine = [this, &last](QString line) { if (line.isEmpty()) return false; if (line.back() == '\n') @@ -194,9 +196,10 @@ void OtherLogsPage::on_btnReload_clicked() // If the level is still undetermined, guess level if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) { - level = m_instance->guessLevel(line, level); + level = LogParser::guessLevel(line, last); } + last = level; m_model->append(level, line); return m_model->isOverFlow(); }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2dedb47cc..31b887ff1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -62,3 +62,7 @@ ecm_add_test(MetaComponentParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VE ecm_add_test(CatPack_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME CatPack) + + +ecm_add_test(XmlLogs_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME XmlLogs) diff --git a/tests/XmlLogs_test.cpp b/tests/XmlLogs_test.cpp index 072448c4e..e01238570 100644 --- a/tests/XmlLogs_test.cpp +++ b/tests/XmlLogs_test.cpp @@ -23,9 +23,11 @@ #include #include +#include #include -#include +#include +#include #include #include @@ -42,16 +44,62 @@ class XmlLogParseTest : public QObject { QString shortXml = QString::fromUtf8(FS::read(FS::PathCombine(source, "vanilla-1.21.5.xml.log"))); QString shortText = QString::fromUtf8(FS::read(FS::PathCombine(source, "vanilla-1.21.5.text.log"))); + QStringList shortTextLevels_s = QString::fromUtf8(FS::read(FS::PathCombine(source, "vanilla-1.21.5-levels.txt"))) + .split(QRegularExpression("\n|\r\n|\r"), Qt::SkipEmptyParts); + + QList shortTextLevels; + shortTextLevels.reserve(24); + std::transform(shortTextLevels_s.cbegin(), shortTextLevels_s.cend(), std::back_inserter(shortTextLevels), + [](const QString& line) { return MessageLevel::getLevel(line.trimmed()); }); QString longXml = QString::fromUtf8(FS::read(FS::PathCombine(source, "TerraFirmaGreg-Modern-forge.xml.log"))); QString longText = QString::fromUtf8(FS::read(FS::PathCombine(source, "TerraFirmaGreg-Modern-forge.text.log"))); - QTest::addColumn("text"); - QTest::addColumn("xml"); - QTest::newRow("short-vanilla") << shortText << shortXml; - QTest::newRow("long-forge") << longText << longXml; + QStringList longTextLevels_s = QString::fromUtf8(FS::read(FS::PathCombine(source, "TerraFirmaGreg-Modern-levels.txt"))) + .split(QRegularExpression("\n|\r\n|\r"), Qt::SkipEmptyParts); + QStringList longTextLevelsXml_s = QString::fromUtf8(FS::read(FS::PathCombine(source, "TerraFirmaGreg-Modern-xml-levels.txt"))) + .split(QRegularExpression("\n|\r\n|\r"), Qt::SkipEmptyParts); + + QList longTextLevelsPlain; + longTextLevelsPlain.reserve(974); + std::transform(longTextLevels_s.cbegin(), longTextLevels_s.cend(), std::back_inserter(longTextLevelsPlain), + [](const QString& line) { return MessageLevel::getLevel(line.trimmed()); }); + QList longTextLevelsXml; + longTextLevelsXml.reserve(896); + std::transform(longTextLevelsXml_s.cbegin(), longTextLevelsXml_s.cend(), std::back_inserter(longTextLevelsXml), + [](const QString& line) { return MessageLevel::getLevel(line.trimmed()); }); + + QTest::addColumn("log"); + QTest::addColumn("num_entries"); + QTest::addColumn>("entry_levels"); + + QTest::newRow("short-vanilla-plain") << shortText << 25 << shortTextLevels; + QTest::newRow("short-vanilla-xml") << shortXml << 25 << shortTextLevels; + QTest::newRow("long-forge-plain") << longText << 945 << longTextLevelsPlain; + QTest::newRow("long-forge-xml") << longXml << 869 << longTextLevelsXml; } - void parseXml() { QFETCH(QString, ) } + void parseXml() + { + QFETCH(QString, log); + QFETCH(int, num_entries); + QFETCH(QList, entry_levels); + + QList> entries = {}; + + QBENCHMARK + { + entries = parseLines(log.split(QRegularExpression("\n|\r\n|\r"))); + } + + QCOMPARE(entries.length(), num_entries); + + QList levels = {}; + + std::transform(entries.cbegin(), entries.cend(), std::back_inserter(levels), + [](std::pair entry) { return entry.first; }); + + QCOMPARE(levels, entry_levels); + } private: LogParser m_parser; @@ -59,27 +107,35 @@ class XmlLogParseTest : public QObject { QList> parseLines(const QStringList& lines) { QList> out; - for (const auto& line : lines) + MessageLevel::Enum last = MessageLevel::Unknown; + + for (const auto& line : lines) { m_parser.appendLine(line); - auto items = m_parser.parseAvailable(); - for (const auto& item : items) { - if (std::holds_alternative(item)) { - auto entry = std::get(item); - auto msg = QString("[%1] [%2/%3] [%4]: %5") - .arg(entry.timestamp.toString("HH:mm:ss")) - .arg(entry.thread) - .arg(entry.levelText) - .arg(entry.logger) - .arg(entry.message); - msg = censorPrivateInfo(msg); - out.append(std::make_pair(entry.level, msg)); - } else if (std::holds_alternative(item)) { - auto msg = std::get(item).message; - level = m_instance->guessLevel(msg, level); - msg = censorPrivateInfo(msg); - out.append(std::make_pair(entry.level, msg)); + + auto items = m_parser.parseAvailable(); + for (const auto& item : items) { + if (std::holds_alternative(item)) { + auto entry = std::get(item); + auto msg = QString("[%1] [%2/%3] [%4]: %5") + .arg(entry.timestamp.toString("HH:mm:ss")) + .arg(entry.thread) + .arg(entry.levelText) + .arg(entry.logger) + .arg(entry.message); + out.append(std::make_pair(entry.level, msg)); + last = entry.level; + } else if (std::holds_alternative(item)) { + auto msg = std::get(item).message; + auto level = LogParser::guessLevel(msg, last); + out.append(std::make_pair(level, msg)); + last = level; + } } } return out; } }; + +QTEST_GUILESS_MAIN(XmlLogParseTest) + +#include "XmlLogs_test.moc" diff --git a/tests/testdata/TestLogs/TerraFirmaGreg-Modern-levels.txt b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-levels.txt new file mode 100644 index 000000000..1b3002117 --- /dev/null +++ b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-levels.txt @@ -0,0 +1,945 @@ +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +INFO +ERROR +INFO +ERROR +ERROR +ERROR +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +WARN +INFO +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +INFO +INFO +WARN +WARN +WARN +INFO +WARN +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +WARN +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +INFO +INFO +WARN +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +ERROR +ERROR +ERROR +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN diff --git a/tests/testdata/TestLogs/TerraFirmaGreg-Modern-xml-levels.txt b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-xml-levels.txt new file mode 100644 index 000000000..941e5e3fe --- /dev/null +++ b/tests/testdata/TestLogs/TerraFirmaGreg-Modern-xml-levels.txt @@ -0,0 +1,869 @@ +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +ERROR +INFO +ERROR +ERROR +ERROR +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +WARN +INFO +WARN +WARN +WARN +WARN +WARN +INFO +WARN +WARN +INFO +INFO +WARN +WARN +WARN +INFO +WARN +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +WARN +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +WARN +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +INFO +INFO +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +ERROR +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +WARN +WARN +INFO +INFO +INFO +INFO +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +WARN +WARN +WARN +WARN +WARN +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +ERROR +ERROR +ERROR +ERROR +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO diff --git a/tests/testdata/TestLogs/vanilla-1.21.5-levels.txt b/tests/testdata/TestLogs/vanilla-1.21.5-levels.txt new file mode 100644 index 000000000..02734e56f --- /dev/null +++ b/tests/testdata/TestLogs/vanilla-1.21.5-levels.txt @@ -0,0 +1,25 @@ +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +WARN +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO +INFO From 266031df816b432589bdb9e2905ce143cc0d9715 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 19 Apr 2025 10:45:12 +0100 Subject: [PATCH 082/171] Fix compilation on Qt 6.4.2 Is this an EOL version lol Signed-off-by: TheKodeToad --- launcher/logs/LogParser.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/launcher/logs/LogParser.cpp b/launcher/logs/LogParser.cpp index c5d2a8647..59a1ff3c3 100644 --- a/launcher/logs/LogParser.cpp +++ b/launcher/logs/LogParser.cpp @@ -22,6 +22,8 @@ #include #include "MessageLevel.h" +using namespace Qt::Literals::StringLiterals; + void LogParser::appendLine(QAnyStringView data) { if (!m_partialData.isEmpty()) { @@ -70,18 +72,18 @@ std::optional LogParser::parseAttributes() for (const auto& attr : attributes) { auto name = attr.name(); auto value = attr.value(); - if (name == "logger") { + if (name == "logger"_L1) { entry.logger = value.trimmed().toString(); - } else if (name == "timestamp") { + } else if (name == "timestamp"_L1) { if (value.trimmed().isEmpty()) { m_parser.raiseError("log4j:Event Missing required attribute: timestamp"); return {}; } entry.timestamp = QDateTime::fromSecsSinceEpoch(value.trimmed().toLongLong()); - } else if (name == "level") { + } else if (name == "level"_L1) { entry.levelText = value.trimmed().toString(); entry.level = parseLogLevel(entry.levelText); - } else if (name == "thread") { + } else if (name == "thread"_L1) { entry.thread = value.trimmed().toString(); } } @@ -135,7 +137,7 @@ std::optional LogParser::parseNext() m_parser.setNamespaceProcessing(false); m_parser.addData(m_buffer); if (m_parser.readNextStartElement()) { - if (m_parser.qualifiedName() == "log4j:Event") { + if (m_parser.qualifiedName() == "log4j:Event"_L1) { int depth = 1; bool eod = false; while (depth > 0 && !eod) { @@ -235,7 +237,7 @@ std::optional LogParser::parseLog4J() m_parser.addData(m_buffer); m_parser.readNextStartElement(); - if (m_parser.qualifiedName() == "log4j:Event") { + if (m_parser.qualifiedName() == "log4j:Event"_L1) { auto entry_ = parseAttributes(); if (!entry_.has_value()) { setError(); @@ -251,7 +253,7 @@ std::optional LogParser::parseLog4J() switch (tok) { case QXmlStreamReader::TokenType::StartElement: { depth += 1; - if (m_parser.qualifiedName() == "log4j:Message") { + if (m_parser.qualifiedName() == "log4j:Message"_L1) { QString message; bool messageComplete = false; @@ -263,7 +265,7 @@ std::optional LogParser::parseLog4J() message.append(m_parser.text()); } break; case QXmlStreamReader::TokenType::EndElement: { - if (m_parser.qualifiedName() == "log4j:Message") { + if (m_parser.qualifiedName() == "log4j:Message"_L1) { messageComplete = true; } } break; @@ -287,7 +289,7 @@ std::optional LogParser::parseLog4J() break; case QXmlStreamReader::TokenType::EndElement: { depth -= 1; - if (depth == 0 && m_parser.qualifiedName() == "log4j:Event") { + if (depth == 0 && m_parser.qualifiedName() == "log4j:Event"_L1) { if (foundMessage) { auto consumed = m_parser.characterOffset(); if (consumed > 0 && consumed <= m_buffer.length()) { @@ -362,7 +364,8 @@ MessageLevel::Enum LogParser::guessLevel(const QString& line, MessageLevel::Enum // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * // static const QRegularExpression JAVA_EXCEPTION( - // R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); + // R"(Exception in thread|...\d more$|(\s+at |Caused by: + // )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); // // if (line.contains(JAVA_EXCEPTION)) // return MessageLevel::Error; From 11015a22d253d76f2d40838073c1b0a9c20831d4 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 19 Apr 2025 10:47:32 +0100 Subject: [PATCH 083/171] Remove commented out code Signed-off-by: TheKodeToad --- launcher/logs/LogParser.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/launcher/logs/LogParser.cpp b/launcher/logs/LogParser.cpp index 59a1ff3c3..4c4eae5aa 100644 --- a/launcher/logs/LogParser.cpp +++ b/launcher/logs/LogParser.cpp @@ -362,12 +362,5 @@ MessageLevel::Enum LogParser::guessLevel(const QString& line, MessageLevel::Enum if (line.contains("overwriting existing")) return MessageLevel::Fatal; - // NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * - // static const QRegularExpression JAVA_EXCEPTION( - // R"(Exception in thread|...\d more$|(\s+at |Caused by: - // )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)"); - // - // if (line.contains(JAVA_EXCEPTION)) - // return MessageLevel::Error; return MessageLevel::Info; } From 1bd1245d860e3547085533860c6c6e0725a09528 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 19 Apr 2025 13:31:33 -0700 Subject: [PATCH 084/171] feat(xml-logs): Case insisitive xml parseing + cleaner switch Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/logs/LogParser.cpp | 186 +++++++++++++++++------------------- launcher/logs/LogParser.h | 1 - 2 files changed, 89 insertions(+), 98 deletions(-) diff --git a/launcher/logs/LogParser.cpp b/launcher/logs/LogParser.cpp index 4c4eae5aa..7e7d30ffc 100644 --- a/launcher/logs/LogParser.cpp +++ b/launcher/logs/LogParser.cpp @@ -29,11 +29,9 @@ void LogParser::appendLine(QAnyStringView data) if (!m_partialData.isEmpty()) { m_buffer = QString(m_partialData); m_buffer.append("\n"); - m_buffer.append(data.toString()); m_partialData.clear(); - } else { - m_buffer.append(data.toString()); } + m_buffer.append(data.toString()); } std::optional LogParser::getError() @@ -41,26 +39,6 @@ std::optional LogParser::getError() return m_error; } -MessageLevel::Enum LogParser::parseLogLevel(const QString& level) -{ - auto test = level.trimmed().toUpper(); - if (test == "TRACE") { - return MessageLevel::Trace; - } else if (test == "DEBUG") { - return MessageLevel::Debug; - } else if (test == "INFO") { - return MessageLevel::Info; - } else if (test == "WARN") { - return MessageLevel::Warning; - } else if (test == "ERROR") { - return MessageLevel::Error; - } else if (test == "FATAL") { - return MessageLevel::Fatal; - } else { - return MessageLevel::Unknown; - } -} - std::optional LogParser::parseAttributes() { LogParser::LogEntry entry{ @@ -82,7 +60,7 @@ std::optional LogParser::parseAttributes() entry.timestamp = QDateTime::fromSecsSinceEpoch(value.trimmed().toLongLong()); } else if (name == "level"_L1) { entry.levelText = value.trimmed().toString(); - entry.level = parseLogLevel(entry.levelText); + entry.level = MessageLevel::getLevel(entry.levelText); } else if (name == "thread"_L1) { entry.thread = value.trimmed().toString(); } @@ -137,7 +115,7 @@ std::optional LogParser::parseNext() m_parser.setNamespaceProcessing(false); m_parser.addData(m_buffer); if (m_parser.readNextStartElement()) { - if (m_parser.qualifiedName() == "log4j:Event"_L1) { + if (m_parser.qualifiedName().compare("log4j:Event"_L1, Qt::CaseInsensitive) == 0) { int depth = 1; bool eod = false; while (depth > 0 && !eod) { @@ -237,7 +215,7 @@ std::optional LogParser::parseLog4J() m_parser.addData(m_buffer); m_parser.readNextStartElement(); - if (m_parser.qualifiedName() == "log4j:Event"_L1) { + if (m_parser.qualifiedName().compare("log4j:Event"_L1, Qt::CaseInsensitive) == 0) { auto entry_ = parseAttributes(); if (!entry_.has_value()) { setError(); @@ -248,72 +226,95 @@ std::optional LogParser::parseLog4J() bool foundMessage = false; int depth = 1; + enum parseOp { noOp, entryReady, parseError }; + + auto foundStart = [&]() -> parseOp { + depth += 1; + if (m_parser.qualifiedName().compare("log4j:Message"_L1, Qt::CaseInsensitive) == 0) { + QString message; + bool messageComplete = false; + + while (!messageComplete) { + auto tok = m_parser.readNext(); + + switch (tok) { + case QXmlStreamReader::TokenType::Characters: { + message.append(m_parser.text()); + } break; + case QXmlStreamReader::TokenType::EndElement: { + if (m_parser.qualifiedName().compare("log4j:Message"_L1, Qt::CaseInsensitive) == 0) { + messageComplete = true; + } + } break; + case QXmlStreamReader::TokenType::EndDocument: { + return parseError; // parse fail + } break; + default: { + // no op + } + } + + if (m_parser.hasError()) { + return parseError; + } + } + + entry.message = message; + foundMessage = true; + depth -= 1; + } + return noOp; + }; + + auto foundEnd = [&]() -> parseOp { + depth -= 1; + if (depth == 0 && m_parser.qualifiedName().compare("log4j:Event"_L1, Qt::CaseInsensitive) == 0) { + if (foundMessage) { + auto consumed = m_parser.characterOffset(); + if (consumed > 0 && consumed <= m_buffer.length()) { + m_buffer = m_buffer.right(m_buffer.length() - consumed); + + if (!m_buffer.isEmpty() && m_buffer.trimmed().isEmpty()) { + // only whitespace, dump it + m_buffer.clear(); + } + } + clearError(); + return entryReady; + } + m_parser.raiseError("log4j:Event Missing required attribute: message"); + setError(); + return parseError; + } + return noOp; + }; + while (!m_parser.atEnd()) { auto tok = m_parser.readNext(); + parseOp op = noOp; switch (tok) { case QXmlStreamReader::TokenType::StartElement: { - depth += 1; - if (m_parser.qualifiedName() == "log4j:Message"_L1) { - QString message; - bool messageComplete = false; + op = foundStart(); + } break; + case QXmlStreamReader::TokenType::EndElement: { + op = foundEnd(); + } break; + case QXmlStreamReader::TokenType::EndDocument: { + return {}; + } break; + default: { + // no op + } + } - while (!messageComplete) { - auto tok = m_parser.readNext(); - - switch (tok) { - case QXmlStreamReader::TokenType::Characters: { - message.append(m_parser.text()); - } break; - case QXmlStreamReader::TokenType::EndElement: { - if (m_parser.qualifiedName() == "log4j:Message"_L1) { - messageComplete = true; - } - } break; - case QXmlStreamReader::TokenType::EndDocument: { - return {}; // parse fail - } break; - default: { - // no op - } - } - - if (m_parser.hasError()) { - return {}; - } - } - - entry.message = message; - foundMessage = true; - depth -= 1; - } - break; - case QXmlStreamReader::TokenType::EndElement: { - depth -= 1; - if (depth == 0 && m_parser.qualifiedName() == "log4j:Event"_L1) { - if (foundMessage) { - auto consumed = m_parser.characterOffset(); - if (consumed > 0 && consumed <= m_buffer.length()) { - m_buffer = m_buffer.right(m_buffer.length() - consumed); - - if (!m_buffer.isEmpty() && m_buffer.trimmed().isEmpty()) { - // only whitespace, dump it - m_buffer.clear(); - } - } - clearError(); - return entry; - } - m_parser.raiseError("log4j:Event Missing required attribute: message"); - setError(); - return {}; - } - } break; - case QXmlStreamReader::TokenType::EndDocument: { - return {}; - } break; - default: { - // no op - } + switch (op) { + case parseError: + return {}; // parse fail or error + case entryReady: + return entry; + case noOp: + default: { + // no op } } @@ -334,16 +335,7 @@ MessageLevel::Enum LogParser::guessLevel(const QString& line, MessageLevel::Enum // New style logs from log4j QString timestamp = match.captured("timestamp"); QString levelStr = match.captured("level"); - if (levelStr == "INFO") - level = MessageLevel::Info; - if (levelStr == "WARN") - level = MessageLevel::Warning; - if (levelStr == "ERROR") - level = MessageLevel::Error; - if (levelStr == "FATAL") - level = MessageLevel::Fatal; - if (levelStr == "TRACE" || levelStr == "DEBUG") - level = MessageLevel::Debug; + level = MessageLevel::getLevel(levelStr); } else { // Old style forge logs if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") || diff --git a/launcher/logs/LogParser.h b/launcher/logs/LogParser.h index 8b23754ac..1a1d86dd1 100644 --- a/launcher/logs/LogParser.h +++ b/launcher/logs/LogParser.h @@ -62,7 +62,6 @@ class LogParser { static MessageLevel::Enum guessLevel(const QString& line, MessageLevel::Enum level); protected: - MessageLevel::Enum parseLogLevel(const QString& level); std::optional parseAttributes(); void setError(); void clearError(); From 3fd5557f89de1269f024b9bd146ff019170795ee Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 20 Apr 2025 00:28:02 +0000 Subject: [PATCH 085/171] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'libnbtplusplus': 'github:PrismLauncher/libnbtplusplus/23b955121b8217c1c348a9ed2483167a6f3ff4ad?narHash=sha256-yy0q%2Bbky80LtK1GWzz7qpM%2BaAGrOqLuewbid8WT1ilk%3D' (2023-11-06) → 'github:PrismLauncher/libnbtplusplus/531449ba1c930c98e0bcf5d332b237a8566f9d78?narHash=sha256-qhmjaRkt%2BO7A%2Bgu6HjUkl7QzOEb4r8y8vWZMG2R/C6o%3D' (2025-04-16) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/2631b0b7abcea6e640ce31cd78ea58910d31e650?narHash=sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR%2BXhw3kr/3Xd0GPTM%3D' (2025-04-12) → 'github:NixOS/nixpkgs/b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef?narHash=sha256-fusHbZCyv126cyArUwwKrLdCkgVAIaa/fQJYFlCEqiU%3D' (2025-04-17) • Updated input 'qt-qrcodegenerator': 'github:nayuki/QR-Code-generator/f40366c40d8d1956081f7ec643d240c02a81df52?narHash=sha256-5%2BiYwsbX8wjKZPCy7ENj5HCYgOqzeSNLs/YrX2Vc7CQ%3D' (2024-11-18) → 'github:nayuki/QR-Code-generator/2c9044de6b049ca25cb3cd1649ed7e27aa055138?narHash=sha256-6SugPt0lp1Gz7nV23FLmsmpfzgFItkSw7jpGftsDPWc%3D' (2025-01-23) --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index 070d069e5..07fa5117a 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "libnbtplusplus": { "flake": false, "locked": { - "lastModified": 1699286814, - "narHash": "sha256-yy0q+bky80LtK1GWzz7qpM+aAGrOqLuewbid8WT1ilk=", + "lastModified": 1744811532, + "narHash": "sha256-qhmjaRkt+O7A+gu6HjUkl7QzOEb4r8y8vWZMG2R/C6o=", "owner": "PrismLauncher", "repo": "libnbtplusplus", - "rev": "23b955121b8217c1c348a9ed2483167a6f3ff4ad", + "rev": "531449ba1c930c98e0bcf5d332b237a8566f9d78", "type": "github" }, "original": { @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1744463964, - "narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=", + "lastModified": 1744932701, + "narHash": "sha256-fusHbZCyv126cyArUwwKrLdCkgVAIaa/fQJYFlCEqiU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650", + "rev": "b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef", "type": "github" }, "original": { @@ -35,11 +35,11 @@ "qt-qrcodegenerator": { "flake": false, "locked": { - "lastModified": 1731907326, - "narHash": "sha256-5+iYwsbX8wjKZPCy7ENj5HCYgOqzeSNLs/YrX2Vc7CQ=", + "lastModified": 1737616857, + "narHash": "sha256-6SugPt0lp1Gz7nV23FLmsmpfzgFItkSw7jpGftsDPWc=", "owner": "nayuki", "repo": "QR-Code-generator", - "rev": "f40366c40d8d1956081f7ec643d240c02a81df52", + "rev": "2c9044de6b049ca25cb3cd1649ed7e27aa055138", "type": "github" }, "original": { From e03870d3f2455d5db7ba701187af41abcda66d73 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 20 Apr 2025 16:44:47 -0400 Subject: [PATCH 086/171] ci(get-merge-commit): init Signed-off-by: Seth Flynn --- .github/actions/get-merge-commit.yml | 103 +++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 .github/actions/get-merge-commit.yml diff --git a/.github/actions/get-merge-commit.yml b/.github/actions/get-merge-commit.yml new file mode 100644 index 000000000..8c67fdfc9 --- /dev/null +++ b/.github/actions/get-merge-commit.yml @@ -0,0 +1,103 @@ +# This file incorporates work covered by the following copyright and +# permission notice +# +# Copyright (c) 2003-2025 Eelco Dolstra and the Nixpkgs/NixOS contributors +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +name: Get merge commit +description: Get a merge commit of a given pull request + +inputs: + repository: + description: Repository containing the pull request + required: false + pull-request-id: + description: ID of a pull request + required: true + +outputs: + merge-commit-sha: + description: Git SHA of a merge commit + value: ${{ steps.query.outputs.merge-commit-sha }} + +runs: + using: composite + + steps: + - name: Wait for GitHub to report merge commit + id: query + shell: bash + env: + GITHUB_REPO: ${{ inputs.repository || github.repository }} + PR_ID: ${{ inputs.pull-request-id }} + # https://github.com/NixOS/nixpkgs/blob/8f77f3600f1ee775b85dc2c72fd842768e486ec9/ci/get-merge-commit.sh + run: | + set -euo pipefail + + log() { + echo "$@" >&2 + } + + # Retry the API query this many times + retryCount=5 + # Start with 5 seconds, but double every retry + retryInterval=5 + + while true; do + log "Checking whether the pull request can be merged" + prInfo=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$GITHUB_REPO/pulls/$PR_ID") + + # Non-open PRs won't have their mergeability computed no matter what + state=$(jq -r .state <<<"$prInfo") + if [[ "$state" != open ]]; then + log "PR is not open anymore" + exit 1 + fi + + mergeable=$(jq -r .mergeable <<<"$prInfo") + if [[ "$mergeable" == "null" ]]; then + if ((retryCount == 0)); then + log "Not retrying anymore. It's likely that GitHub is having internal issues: check https://www.githubstatus.com/" + exit 3 + else + ((retryCount -= 1)) || true + + # null indicates that GitHub is still computing whether it's mergeable + # Wait a couple seconds before trying again + log "GitHub is still computing whether this PR can be merged, waiting $retryInterval seconds before trying again ($retryCount retries left)" + sleep "$retryInterval" + + ((retryInterval *= 2)) || true + fi + else + break + fi + done + + if [[ "$mergeable" == "true" ]]; then + echo "merge-commit-sha=$(jq -r .merge_commit_sha <<<"$prInfo")" >> "$GITHUB_OUTPUT" + else + echo "# 🚨 The PR has a merge conflict!" >>> "$GITHUB_STEP_SUMMARY" + exit 2 + fi From f2a601f8153f974c73ad547198650de83889bd9d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 16:58:13 +0000 Subject: [PATCH 087/171] chore(deps): update determinatesystems/nix-installer-action action to v17 --- .github/workflows/nix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index fea0df6ce..765aa4ca8 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -65,7 +65,7 @@ jobs: uses: actions/checkout@v4 - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v16 + uses: DeterminateSystems/nix-installer-action@v17 with: determinate: ${{ env.USE_DETERMINATE }} From 83ebb5984b47dbf393d4e2ad63bb6d9024c13f26 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 25 Apr 2025 19:18:28 -0700 Subject: [PATCH 088/171] fix: nullptr access style can't always be created Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/themes/SystemTheme.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index 7fba08026..c9b2e5cfd 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -54,7 +54,7 @@ SystemTheme::SystemTheme(const QString& styleName, const QPalette& defaultPalett m_colorPalette = defaultPalette; } else { auto style = QStyleFactory::create(styleName); - m_colorPalette = style->standardPalette(); + m_colorPalette = style != nullptr ? style->standardPalette() : defaultPalette; delete style; } } From abe18fb144f707b57ee56dda996ca034c47a3bd6 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 20 Apr 2025 16:54:44 -0400 Subject: [PATCH 089/171] ci(nix): checkout merge commit of pull request Signed-off-by: Seth Flynn --- .github/workflows/nix.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index fea0df6ce..816e2a7aa 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -61,8 +61,17 @@ jobs: id-token: write steps: + - name: Get merge commit + if: ${{ github.event_name == 'pull_request_target' }} + id: merge-commit + uses: ./.github/actions/get-merge-commit.yml + with: + pull-request-id: ${{ github.event.pull_request.id }} + - name: Checkout repository uses: actions/checkout@v4 + with: + ref: ${{ steps.merge-commit.outputs.merge-commit-sha || github.sha }} - name: Install Nix uses: DeterminateSystems/nix-installer-action@v16 From a702d06cd85737728a1ec15538127e4c418bde8e Mon Sep 17 00:00:00 2001 From: Xander Date: Sat, 26 Apr 2025 21:41:14 +0100 Subject: [PATCH 090/171] Pass mainclass and gameargs to the main game via system properties Signed-off-by: Xander --- .../launcher/impl/StandardLauncher.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java index 084fbc849..af9b41533 100644 --- a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java +++ b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java @@ -97,6 +97,18 @@ public final class StandardLauncher extends AbstractLauncher { gameArgs.add(worldName); } + StringBuilder joinedGameArgs = new StringBuilder(); + for (String gameArg : gameArgs) { + if (joinedGameArgs.length() > 0) { + joinedGameArgs.append(" "); + } + joinedGameArgs.append(gameArg); + } + + // pass the real main class and game arguments in so mods can access them + System.setProperty("org.prismlauncher.launch.mainclass", mainClassName); + System.setProperty("org.prismlauncher.launch.gameargs", joinedGameArgs.toString()); + // find and invoke the main method MethodHandle method = ReflectionUtils.findMainMethod(mainClassName); method.invokeExact(gameArgs.toArray(new String[0])); From a92e114236b54a23561fa4071cc1ec8486b43119 Mon Sep 17 00:00:00 2001 From: Xander Date: Sat, 26 Apr 2025 21:57:28 +0100 Subject: [PATCH 091/171] Use \u001F instead of a space as a delimeter for game args Signed-off-by: Xander --- .../org/prismlauncher/launcher/impl/StandardLauncher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java index af9b41533..96a809dfe 100644 --- a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java +++ b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java @@ -100,7 +100,7 @@ public final class StandardLauncher extends AbstractLauncher { StringBuilder joinedGameArgs = new StringBuilder(); for (String gameArg : gameArgs) { if (joinedGameArgs.length() > 0) { - joinedGameArgs.append(" "); + joinedGameArgs.append('\u001F'); // unit separator, designed for this purpose } joinedGameArgs.append(gameArg); } From 02106ab29a8761e44aab5c8e73fd8842c4802b32 Mon Sep 17 00:00:00 2001 From: Xander Date: Sat, 26 Apr 2025 23:42:22 +0100 Subject: [PATCH 092/171] comment on property about delimeter Signed-off-by: Xander --- .../org/prismlauncher/launcher/impl/StandardLauncher.java | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java index 96a809dfe..968499ff6 100644 --- a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java +++ b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java @@ -107,6 +107,7 @@ public final class StandardLauncher extends AbstractLauncher { // pass the real main class and game arguments in so mods can access them System.setProperty("org.prismlauncher.launch.mainclass", mainClassName); + // unit separator ('\u001F') delimited list of game args System.setProperty("org.prismlauncher.launch.gameargs", joinedGameArgs.toString()); // find and invoke the main method From facc48d0f8e58db46f67c444a59904391cad7ce3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 27 Apr 2025 00:28:07 +0000 Subject: [PATCH 093/171] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef?narHash=sha256-fusHbZCyv126cyArUwwKrLdCkgVAIaa/fQJYFlCEqiU%3D' (2025-04-17) → 'github:NixOS/nixpkgs/f771eb401a46846c1aebd20552521b233dd7e18b?narHash=sha256-ITSpPDwvLBZBnPRS2bUcHY3gZSwis/uTe255QgMtTLA%3D' (2025-04-24) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 07fa5117a..5418557a3 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1744932701, - "narHash": "sha256-fusHbZCyv126cyArUwwKrLdCkgVAIaa/fQJYFlCEqiU=", + "lastModified": 1745526057, + "narHash": "sha256-ITSpPDwvLBZBnPRS2bUcHY3gZSwis/uTe255QgMtTLA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef", + "rev": "f771eb401a46846c1aebd20552521b233dd7e18b", "type": "github" }, "original": { From 47cb58d326f5177c7337b531a8aec75b07edf1d7 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 27 Apr 2025 07:16:20 -0400 Subject: [PATCH 094/171] ci(nix): fix get-merge-commit action call Signed-off-by: Seth Flynn --- .../{get-merge-commit.yml => get-merge-commit/action.yml} | 0 .github/workflows/nix.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename .github/actions/{get-merge-commit.yml => get-merge-commit/action.yml} (100%) diff --git a/.github/actions/get-merge-commit.yml b/.github/actions/get-merge-commit/action.yml similarity index 100% rename from .github/actions/get-merge-commit.yml rename to .github/actions/get-merge-commit/action.yml diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 816e2a7aa..03d5f2089 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -64,7 +64,7 @@ jobs: - name: Get merge commit if: ${{ github.event_name == 'pull_request_target' }} id: merge-commit - uses: ./.github/actions/get-merge-commit.yml + uses: PrismLauncher/PrismLauncher/.github/actions/get-merge-commit@develop with: pull-request-id: ${{ github.event.pull_request.id }} From 3b7b9fa03c330aa8844b380be614dce9fde90097 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 27 Apr 2025 06:59:19 -0400 Subject: [PATCH 095/171] ci: better filter workflow runs Signed-off-by: Seth Flynn --- .github/workflows/codeql.yml | 35 ++++++++++++++++++++- .github/workflows/trigger_builds.yml | 46 +++++++++++++++++++--------- 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e3243097d..a5ac537f1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,6 +1,39 @@ name: "CodeQL Code Scanning" -on: [push, pull_request, workflow_dispatch] +on: + push: + # NOTE: `!` doesn't work with `paths-ignore` :( + # So we a catch-all glob instead + # https://github.com/orgs/community/discussions/25369#discussioncomment-3247674 + paths: + - "**" + - "!.github/**" + - ".github/workflows/codeql.yml" + - "!flatpak/" + - "!nix/" + - "!scripts/" + + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" + pull_request: + # See above + paths: + - "**" + - "!.github/**" + - ".github/workflows/codeql.yml" + - "!flatpak/" + - "!nix/" + - "!scripts/" + + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" + workflow_dispatch: jobs: CodeQL: diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml index 9efafc8cc..e4c90ef0b 100644 --- a/.github/workflows/trigger_builds.yml +++ b/.github/workflows/trigger_builds.yml @@ -4,21 +4,39 @@ on: push: branches-ignore: - "renovate/**" - paths-ignore: - - "**.md" - - "**/LICENSE" - - "flake.lock" - - "packages/**" - - ".github/ISSUE_TEMPLATE/**" - - ".markdownlint**" + # NOTE: `!` doesn't work with `paths-ignore` :( + # So we a catch-all glob instead + # https://github.com/orgs/community/discussions/25369#discussioncomment-3247674 + paths: + - "**" + - "!.github/**" + - ".github/workflows/build.yml" + - ".github/workflows/trigger_builds.yml" + - "!flatpak/" + - "!nix/" + - "!scripts/" + + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" pull_request: - paths-ignore: - - "**.md" - - "**/LICENSE" - - "flake.lock" - - "packages/**" - - ".github/ISSUE_TEMPLATE/**" - - ".markdownlint**" + # See above + paths: + - "**" + - "!.github/**" + - ".github/workflows/build.yml" + - ".github/workflows/trigger_builds.yml" + - "!flatpak/" + - "!nix/" + - "!scripts/" + + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" workflow_dispatch: jobs: From 57a2ef1aed5a673f7772005e0dc4c4aa100e3c4d Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sun, 27 Apr 2025 07:00:26 -0400 Subject: [PATCH 096/171] ci: fix improper paths-ignore usage Signed-off-by: Seth Flynn --- .github/workflows/flatpak.yml | 40 +++++++++++++++++++++---------- .github/workflows/nix.yml | 44 ++++++++++++++++++++--------------- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index 41cc2a51d..8caba46fa 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -2,22 +2,38 @@ name: Flatpak on: push: - paths-ignore: - - "**.md" - - "**/LICENSE" - - ".github/ISSUE_TEMPLATE/**" - - ".markdownlint**" - - "nix/**" # We don't do anything with these artifacts on releases. They go to Flathub tags-ignore: - "*" + # NOTE: `!` doesn't work with `paths-ignore` :( + # So we a catch-all glob instead + # https://github.com/orgs/community/discussions/25369#discussioncomment-3247674 + paths: + - "**" + - "!.github/**" + - ".github/workflows/flatpak.yml" + - "!nix/" + - "!scripts/" + + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" pull_request: - paths-ignore: - - "**.md" - - "**/LICENSE" - - ".github/ISSUE_TEMPLATE/**" - - ".markdownlint**" - - "nix/**" + # See above + paths: + - "**" + - "!.github/**" + - ".github/workflows/flatpak.yml" + - "!nix/" + - "!scripts/" + + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" workflow_dispatch: permissions: diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 816e2a7aa..b968062c9 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -4,28 +4,34 @@ on: push: tags: - "*" - paths-ignore: - - ".github/**" - - "!.github/workflows/nix.yml" - - "flatpak/" - - "scripts/" + # NOTE: `!` doesn't work with `paths-ignore` :( + # So we a catch-all glob instead + # https://github.com/orgs/community/discussions/25369#discussioncomment-3247674 + paths: + - "**" + - "!.github/**" + - ".github/workflows/nix.yml" + - "!flatpak/" + - "!scripts/" - - ".git*" - - ".envrc" - - "**.md" - - "!COPYING.md" - - "renovate.json" + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" pull_request_target: - paths-ignore: - - ".github/**" - - "flatpak/" - - "scripts/" + paths: + - "**" + - "!.github/**" + - ".github/workflows/nix.yml" + - "!flatpak/" + - "!scripts/" - - ".git*" - - ".envrc" - - "**.md" - - "!COPYING.md" - - "renovate.json" + - "!.git*" + - "!.envrc" + - "!**.md" + - "COPYING.md" + - "!renovate.json" workflow_dispatch: permissions: From 20626e6606ba07317fb4283f0814ecef4e1ea136 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 28 Apr 2025 10:28:57 +0100 Subject: [PATCH 097/171] Fix log sorting Signed-off-by: TheKodeToad --- launcher/minecraft/MinecraftInstance.cpp | 2 +- launcher/ui/pages/instance/OtherLogsPage.cpp | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 121b8035c..1009d7c42 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1057,7 +1057,7 @@ MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLev QStringList MinecraftInstance::getLogFileSearchPaths() { - return { FS::PathCombine(gameRoot(), "logs"), FS::PathCombine(gameRoot(), "crash-reports"), gameRoot() }; + return { FS::PathCombine(gameRoot(), "crash-reports"), FS::PathCombine(gameRoot(), "logs"), gameRoot() }; } QString MinecraftInstance::getStatusbarDescription() diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index f457195d8..80a9c0fdc 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -146,6 +146,7 @@ void OtherLogsPage::populateSelectLogBox() ui->selectLogBox->setCurrentIndex(index); ui->selectLogBox->blockSignals(false); setControlsEnabled(true); + // don't refresh file return; } else { setControlsEnabled(false); @@ -405,20 +406,17 @@ QStringList OtherLogsPage::getPaths() QStringList result; for (QString searchPath : m_logSearchPaths) { - QDirIterator iterator(searchPath, QDir::Files | QDir::Readable); + QDir searchDir(searchPath); - const bool isRoot = searchPath == m_basePath; + QStringList filters{ "*.log", "*.log.gz" }; - while (iterator.hasNext()) { - const QString name = iterator.next(); + if (searchPath != m_basePath) + filters.append("*.txt"); - QString relativePath = baseDir.relativeFilePath(name); + QStringList entries = searchDir.entryList(filters, QDir::Files | QDir::Readable, QDir::SortFlag::Time); - if (!(name.endsWith(".log") || name.endsWith(".log.gz") || (!isRoot && name.endsWith(".txt")))) - continue; - - result.append(relativePath); - } + for (const QString& name : entries) + result.append(baseDir.relativeFilePath(searchDir.filePath(name))); } return result; From 2c943a003d798aeaa76ab630979953ac7aff51eb Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 28 Apr 2025 03:08:33 -0700 Subject: [PATCH 098/171] feat(xml-logs): preserve whitespace lines in logs Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/logs/LogParser.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/launcher/logs/LogParser.cpp b/launcher/logs/LogParser.cpp index 7e7d30ffc..6e33b24dd 100644 --- a/launcher/logs/LogParser.cpp +++ b/launcher/logs/LogParser.cpp @@ -105,8 +105,9 @@ std::optional LogParser::parseNext() } if (m_buffer.trimmed().isEmpty()) { + auto text = QString(m_buffer); m_buffer.clear(); - return {}; + return LogParser::PlainText { text }; } // check if we have a full xml log4j event @@ -177,11 +178,7 @@ std::optional LogParser::parseNext() // no log4j found, all plain text auto text = QString(m_buffer); m_buffer.clear(); - if (text.trimmed().isEmpty()) { - return {}; - } else { - return LogParser::PlainText{ text }; - } + return LogParser::PlainText{ text }; } } @@ -273,11 +270,7 @@ std::optional LogParser::parseLog4J() auto consumed = m_parser.characterOffset(); if (consumed > 0 && consumed <= m_buffer.length()) { m_buffer = m_buffer.right(m_buffer.length() - consumed); - - if (!m_buffer.isEmpty() && m_buffer.trimmed().isEmpty()) { - // only whitespace, dump it - m_buffer.clear(); - } + // potential whitespace preserved for next item } clearError(); return entryReady; From d0ccd110a15c31e3bb03454518ea2c78fe38a07c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 19 Apr 2025 06:16:57 -0700 Subject: [PATCH 099/171] fix: use after free begone! Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/Application.cpp | 2 +- launcher/qtlogging.ini | 3 +++ launcher/ui/pages/instance/ModFolderPage.cpp | 24 ++++++++++++++----- launcher/ui/pages/instance/ModFolderPage.h | 4 ++++ .../ui/pages/instance/ResourcePackPage.cpp | 24 +++++++++++++------ launcher/ui/pages/instance/ResourcePackPage.h | 5 ++++ launcher/ui/pages/instance/ShaderPackPage.cpp | 21 +++++++++++----- launcher/ui/pages/instance/ShaderPackPage.h | 4 ++++ .../ui/pages/instance/TexturePackPage.cpp | 20 +++++++++++----- launcher/ui/pages/instance/TexturePackPage.h | 5 ++++ 10 files changed, 86 insertions(+), 26 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index cfe028279..33d700772 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -96,6 +96,7 @@ #include #include #include +#include #include #include #include @@ -125,7 +126,6 @@ #include #include -#include #include #include #include "SysInfo.h" diff --git a/launcher/qtlogging.ini b/launcher/qtlogging.ini index c12d1e109..10f724163 100644 --- a/launcher/qtlogging.ini +++ b/launcher/qtlogging.ini @@ -3,6 +3,9 @@ # prevent log spam and strange bugs # qt.qpa.drawing in particular causes theme artifacts on MacOS qt.*.debug=false +# supress image format noise +kf.imageformats.plugins.hdr=false +kf.imageformats.plugins.xcf=false # don't log credentials by default launcher.auth.credentials.debug=false # remove the debug lines, other log levels still get through diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 026f0c140..8508e7908 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include "Application.h" @@ -145,10 +146,17 @@ void ModFolderPage::downloadMods() QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); return; } - auto mdownload = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - if (mdownload->exec()) { + + m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); + m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &ModFolderPage::downloadDialogFinished); + m_downloadDialog->open(); +} + +void ModFolderPage::downloadDialogFinished(int result) +{ + if (result) { auto tasks = new ConcurrentTask(tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -166,8 +174,12 @@ void ModFolderPage::downloadMods() tasks->deleteLater(); }); - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); + if (m_downloadDialog) { + for (auto& task : m_downloadDialog->getTasks()) { + tasks->addTask(task); + } + } else { + qWarning() << "ResourceDownloadDialog vanished before we could collect tasks!"; } ProgressDialog loadDialog(this); diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index a7d078f50..8996b1615 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -38,7 +38,9 @@ #pragma once +#include #include "ExternalResourcesPage.h" +#include "ui/dialogs/ResourceDownloadDialog.h" class ModFolderPage : public ExternalResourcesPage { Q_OBJECT @@ -63,6 +65,7 @@ class ModFolderPage : public ExternalResourcesPage { void removeItems(const QItemSelection& selection) override; void downloadMods(); + void downloadDialogFinished(int result); void updateMods(bool includeDeps = false); void deleteModMetadata(); void exportModMetadata(); @@ -70,6 +73,7 @@ class ModFolderPage : public ExternalResourcesPage { protected: std::shared_ptr m_model; + QPointer m_downloadDialog; }; class CoreModFolderPage : public ModFolderPage { diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index ae5eb8fac..0d9e643b1 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -84,10 +84,16 @@ void ResourcePackPage::downloadResourcePacks() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - auto mdownload = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - if (mdownload->exec()) { + m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); + m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &ResourcePackPage::downloadDialogFinished); + m_downloadDialog->open(); +} + +void ResourcePackPage::downloadDialogFinished(int result) +{ + if (result) { auto tasks = new ConcurrentTask("Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -105,8 +111,12 @@ void ResourcePackPage::downloadResourcePacks() tasks->deleteLater(); }); - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); + if (m_downloadDialog) { + for (auto& task : m_downloadDialog->getTasks()) { + tasks->addTask(task); + } + } else { + qWarning() << "ResourceDownloadDialog vanished before we could collect tasks!"; } ProgressDialog loadDialog(this); @@ -269,4 +279,4 @@ void ResourcePackPage::changeResourcePackVersion() m_model->update(); } -} \ No newline at end of file +} diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index 55abe007c..e39d417c9 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -37,7 +37,10 @@ #pragma once +#include + #include "ExternalResourcesPage.h" +#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui_ExternalResourcesPage.h" #include "minecraft/mod/ResourcePackFolderModel.h" @@ -62,10 +65,12 @@ class ResourcePackPage : public ExternalResourcesPage { private slots: void downloadResourcePacks(); + void downloadDialogFinished(int result); void updateResourcePacks(); void deleteResourcePackMetadata(); void changeResourcePackVersion(); protected: std::shared_ptr m_model; + QPointer m_downloadDialog; }; diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index 45bb02030..829a75a72 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -81,10 +81,15 @@ void ShaderPackPage::downloadShaderPack() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - auto mdownload = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - if (mdownload->exec()) { + m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); + m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &ShaderPackPage::downloadDialogFinished); + m_downloadDialog->open(); +} + +void ShaderPackPage::downloadDialogFinished(int result) { + if (result) { auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -102,8 +107,12 @@ void ShaderPackPage::downloadShaderPack() tasks->deleteLater(); }); - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); + if (m_downloadDialog) { + for (auto& task : m_downloadDialog->getTasks()) { + tasks->addTask(task); + } + } else { + qWarning() << "ResourceDownloadDialog vanished before we could collect tasks!"; } ProgressDialog loadDialog(this); diff --git a/launcher/ui/pages/instance/ShaderPackPage.h b/launcher/ui/pages/instance/ShaderPackPage.h index ebf7f1d58..f2b141329 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.h +++ b/launcher/ui/pages/instance/ShaderPackPage.h @@ -37,7 +37,9 @@ #pragma once +#include #include "ExternalResourcesPage.h" +#include "ui/dialogs/ResourceDownloadDialog.h" class ShaderPackPage : public ExternalResourcesPage { Q_OBJECT @@ -54,10 +56,12 @@ class ShaderPackPage : public ExternalResourcesPage { public slots: void downloadShaderPack(); + void downloadDialogFinished(int result); void updateShaderPacks(); void deleteShaderPackMetadata(); void changeShaderPackVersion(); private: std::shared_ptr m_model; + QPointer m_downloadDialog; }; diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index 6d000a486..ada29d94b 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -90,10 +90,14 @@ void TexturePackPage::downloadTexturePacks() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - auto mdownload = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - if (mdownload->exec()) { + auto m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); + m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); +} + +void TexturePackPage::downloadDialogFinished(int result) +{ + if (result) { auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); @@ -111,8 +115,12 @@ void TexturePackPage::downloadTexturePacks() tasks->deleteLater(); }); - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); + if (m_downloadDialog) { + for (auto& task : m_downloadDialog->getTasks()) { + tasks->addTask(task); + } + } else { + qWarning() << "ResourceDownloadDialog vanished before we could collect tasks!"; } ProgressDialog loadDialog(this); diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h index 28d7ba209..3ebca3e87 100644 --- a/launcher/ui/pages/instance/TexturePackPage.h +++ b/launcher/ui/pages/instance/TexturePackPage.h @@ -37,7 +37,10 @@ #pragma once +#include + #include "ExternalResourcesPage.h" +#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui_ExternalResourcesPage.h" #include "minecraft/mod/TexturePackFolderModel.h" @@ -57,10 +60,12 @@ class TexturePackPage : public ExternalResourcesPage { public slots: void updateFrame(const QModelIndex& current, const QModelIndex& previous) override; void downloadTexturePacks(); + void downloadDialogFinished(int result); void updateTexturePacks(); void deleteTexturePackMetadata(); void changeTexturePackVersion(); private: std::shared_ptr m_model; + QPointer m_downloadDialog; }; From 07a6606c9c549b70ce91225303abe90982973a72 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sat, 19 Apr 2025 06:48:04 -0700 Subject: [PATCH 100/171] fix: cover both usages of the download dialog Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/pages/instance/ModFolderPage.cpp | 42 ++++-------------- .../ui/pages/instance/ResourcePackPage.cpp | 40 ++++------------- launcher/ui/pages/instance/ShaderPackPage.cpp | 43 +++++-------------- .../ui/pages/instance/TexturePackPage.cpp | 43 +++++-------------- 4 files changed, 37 insertions(+), 131 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 8508e7908..dad2da8a4 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -146,11 +146,11 @@ void ModFolderPage::downloadMods() QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); return; } - + m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); - m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ModFolderPage::downloadDialogFinished); + m_downloadDialog->open(); } @@ -188,6 +188,8 @@ void ModFolderPage::downloadDialogFinished(int result) m_model->update(); } + if (m_downloadDialog) + m_downloadDialog->deleteLater(); } void ModFolderPage::updateMods(bool includeDeps) @@ -313,38 +315,12 @@ void ModFolderPage::changeModVersion() if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr) return; - auto mdownload = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - mdownload->setResourceMetadata((*mods_list.begin())->metadata()); - if (mdownload->exec()) { - auto tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::aborted, [this, tasks]() { - CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::succeeded, [this, tasks]() { - QStringList warnings = tasks->warnings(); - if (warnings.count()) - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &ModFolderPage::downloadDialogFinished); - tasks->deleteLater(); - }); - - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); - } - - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(tasks); - - m_model->update(); - } + m_downloadDialog->setResourceMetadata((*mods_list.begin())->metadata()); + m_downloadDialog->open(); } void ModFolderPage::exportModMetadata() diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index 0d9e643b1..f37b3baf9 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -85,9 +85,9 @@ void ResourcePackPage::downloadResourcePacks() return; // this is a null instance or a legacy instance m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); - m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ResourcePackPage::downloadDialogFinished); + m_downloadDialog->open(); } @@ -125,6 +125,8 @@ void ResourcePackPage::downloadDialogFinished(int result) m_model->update(); } + if (m_downloadDialog) + m_downloadDialog->deleteLater(); } void ResourcePackPage::updateResourcePacks() @@ -247,36 +249,10 @@ void ResourcePackPage::changeResourcePackVersion() if (resource.metadata() == nullptr) return; - auto mdownload = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - mdownload->setResourceMetadata(resource.metadata()); - if (mdownload->exec()) { - auto tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::aborted, [this, tasks]() { - CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::succeeded, [this, tasks]() { - QStringList warnings = tasks->warnings(); - if (warnings.count()) - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &ResourcePackPage::downloadDialogFinished); - tasks->deleteLater(); - }); - - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); - } - - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(tasks); - - m_model->update(); - } + m_downloadDialog->setResourceMetadata(resource.metadata()); + m_downloadDialog->open(); } diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index 829a75a72..930b0b9da 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -82,13 +82,14 @@ void ShaderPackPage::downloadShaderPack() return; // this is a null instance or a legacy instance m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); - m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ShaderPackPage::downloadDialogFinished); + m_downloadDialog->open(); } -void ShaderPackPage::downloadDialogFinished(int result) { +void ShaderPackPage::downloadDialogFinished(int result) +{ if (result) { auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { @@ -121,6 +122,8 @@ void ShaderPackPage::downloadDialogFinished(int result) { m_model->update(); } + if (m_downloadDialog) + m_downloadDialog->deleteLater(); } void ShaderPackPage::updateShaderPacks() @@ -243,36 +246,10 @@ void ShaderPackPage::changeShaderPackVersion() if (resource.metadata() == nullptr) return; - auto mdownload = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - mdownload->setResourceMetadata(resource.metadata()); - if (mdownload->exec()) { - auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::aborted, [this, tasks]() { - CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::succeeded, [this, tasks]() { - QStringList warnings = tasks->warnings(); - if (warnings.count()) - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &ShaderPackPage::downloadDialogFinished); - tasks->deleteLater(); - }); - - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); - } - - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(tasks); - - m_model->update(); - } + m_downloadDialog->setResourceMetadata(resource.metadata()); + m_downloadDialog->open(); } diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index ada29d94b..2886decb4 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -90,9 +90,10 @@ void TexturePackPage::downloadTexturePacks() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - auto m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); - m_downloadDialog->setAttribute(Qt::WA_DeleteOnClose); + m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &TexturePackPage::downloadDialogFinished); + m_downloadDialog->open(); } void TexturePackPage::downloadDialogFinished(int result) @@ -129,6 +130,8 @@ void TexturePackPage::downloadDialogFinished(int result) m_model->update(); } + if (m_downloadDialog) + m_downloadDialog->deleteLater(); } void TexturePackPage::updateTexturePacks() @@ -251,36 +254,10 @@ void TexturePackPage::changeTexturePackVersion() if (resource.metadata() == nullptr) return; - auto mdownload = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); - mdownload->setAttribute(Qt::WA_DeleteOnClose); - connect(this, &QObject::destroyed, mdownload, &QDialog::close); - mdownload->setResourceMetadata(resource.metadata()); - if (mdownload->exec()) { - auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::aborted, [this, tasks]() { - CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::succeeded, [this, tasks]() { - QStringList warnings = tasks->warnings(); - if (warnings.count()) - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &TexturePackPage::downloadDialogFinished); - tasks->deleteLater(); - }); - - for (auto& task : mdownload->getTasks()) { - tasks->addTask(task); - } - - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(tasks); - - m_model->update(); - } + m_downloadDialog->setResourceMetadata(resource.metadata()); + m_downloadDialog->open(); } From bcdbe79c592894ab9e47908a5b06d7b3c5568430 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 28 Apr 2025 17:37:22 +0300 Subject: [PATCH 101/171] fix: add github token for gh cli Signed-off-by: Trial97 --- .github/workflows/nix.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index a11389e6c..a77b33521 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -73,7 +73,9 @@ jobs: uses: PrismLauncher/PrismLauncher/.github/actions/get-merge-commit@develop with: pull-request-id: ${{ github.event.pull_request.id }} - + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Checkout repository uses: actions/checkout@v4 with: From 0ccb4059a028615543bc232c3f87a7ba0b71e1ca Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 28 Apr 2025 17:44:23 +0300 Subject: [PATCH 102/171] chore: update submodules Signed-off-by: Trial97 --- flatpak/shared-modules | 2 +- libraries/extra-cmake-modules | 2 +- libraries/libnbtplusplus | 2 +- libraries/quazip | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flatpak/shared-modules b/flatpak/shared-modules index f5d368a31..73f08ed2c 160000 --- a/flatpak/shared-modules +++ b/flatpak/shared-modules @@ -1 +1 @@ -Subproject commit f5d368a31d6ef046eb2955c74ec6f54f32ed5c4e +Subproject commit 73f08ed2c3187f6648ca04ebef030930a6c9f0be diff --git a/libraries/extra-cmake-modules b/libraries/extra-cmake-modules index a3d9394ab..1f820dc98 160000 --- a/libraries/extra-cmake-modules +++ b/libraries/extra-cmake-modules @@ -1 +1 @@ -Subproject commit a3d9394aba4b35789293378e04fb7473d65edf97 +Subproject commit 1f820dc98d0a520c175433bcbb0098327d82aac6 diff --git a/libraries/libnbtplusplus b/libraries/libnbtplusplus index 23b955121..531449ba1 160000 --- a/libraries/libnbtplusplus +++ b/libraries/libnbtplusplus @@ -1 +1 @@ -Subproject commit 23b955121b8217c1c348a9ed2483167a6f3ff4ad +Subproject commit 531449ba1c930c98e0bcf5d332b237a8566f9d78 diff --git a/libraries/quazip b/libraries/quazip index 8aeb3f7d8..3fd3b299b 160000 --- a/libraries/quazip +++ b/libraries/quazip @@ -1 +1 @@ -Subproject commit 8aeb3f7d8254f4bf1f7c6cf2a8f59c2ca141a552 +Subproject commit 3fd3b299b875fbd2beac4894b8a870d80022cad7 From c5fd5e6ac1ad69ecc753495e5da0039d5ce1ba32 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 16 Apr 2025 18:55:26 +0300 Subject: [PATCH 103/171] chore: make all the regexes static const Signed-off-by: Trial97 --- launcher/BaseInstance.cpp | 1 - launcher/InstanceImportTask.cpp | 4 ++-- launcher/JavaCommon.cpp | 6 ++++-- launcher/LaunchController.cpp | 3 ++- launcher/RecursiveFileSystemWatcher.cpp | 1 - launcher/StringUtils.cpp | 7 +++---- launcher/Version.cpp | 1 - launcher/java/JavaVersion.cpp | 8 ++++++-- launcher/launch/LaunchTask.cpp | 1 - launcher/minecraft/GradleSpecifier.h | 4 ++-- launcher/minecraft/MinecraftInstance.cpp | 5 ++--- launcher/minecraft/OneSixVersionFormat.cpp | 4 ++-- launcher/minecraft/ProfileUtils.cpp | 1 - launcher/minecraft/auth/AccountData.cpp | 1 - launcher/minecraft/auth/MinecraftAccount.cpp | 11 +++++++---- .../minecraft/launch/LauncherPartLaunch.cpp | 17 +++++++++-------- launcher/minecraft/mod/Resource.cpp | 4 ++-- launcher/minecraft/mod/ResourcePack.cpp | 2 -- launcher/minecraft/mod/ShaderPack.cpp | 2 -- launcher/minecraft/mod/TexturePack.cpp | 2 -- .../minecraft/mod/tasks/LocalModParseTask.cpp | 4 ++-- .../modplatform/atlauncher/ATLPackIndex.cpp | 3 ++- .../atlauncher/ATLPackInstallTask.cpp | 6 ++++-- .../flame/FlameInstanceCreationTask.cpp | 3 ++- launcher/net/MetaCacheSink.cpp | 4 ++-- launcher/pathmatcher/FSTreeMatcher.h | 1 - launcher/pathmatcher/MultiMatcher.h | 1 - launcher/pathmatcher/RegexpMatcher.h | 2 ++ launcher/pathmatcher/SimplePrefixMatcher.h | 1 - launcher/ui/dialogs/NewComponentDialog.cpp | 3 ++- launcher/ui/dialogs/ProfileSetupDialog.cpp | 5 ++--- launcher/ui/pages/global/APIPage.cpp | 14 +++++++------- launcher/ui/pages/instance/ScreenshotsPage.cpp | 3 ++- launcher/updater/prismupdater/PrismUpdater.cpp | 13 ++++++------- libraries/LocalPeer/src/LocalPeer.cpp | 3 ++- libraries/qdcss/src/qdcss.cpp | 8 ++++---- libraries/systeminfo/src/distroutils.cpp | 12 +++++++----- 37 files changed, 87 insertions(+), 84 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index eab91a5eb..6fbe5eea6 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -42,7 +42,6 @@ #include #include #include -#include #include "settings/INISettingsObject.h" #include "settings/OverrideSetting.h" diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 71630656d..633382404 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -378,8 +378,8 @@ void InstanceImportTask::processModrinth() } else { QString pack_id; if (!m_sourceUrl.isEmpty()) { - QRegularExpression regex(R"(data\/([^\/]*)\/versions)"); - pack_id = regex.match(m_sourceUrl.toString()).captured(1); + static const QRegularExpression s_regex(R"(data\/([^\/]*)\/versions)"); + pack_id = s_regex.match(m_sourceUrl.toString()).captured(1); } // FIXME: Find a way to get the ID in directly imported ZIPs diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp index 188edb943..b71000054 100644 --- a/launcher/JavaCommon.cpp +++ b/launcher/JavaCommon.cpp @@ -41,7 +41,9 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent) { - if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegularExpression("-Xm[sx]")) || jvmargs.contains("-XX-MaxHeapSize") || + static const QRegularExpression s_memRegex("-Xm[sx]"); + static const QRegularExpression s_versionRegex("-version:.*"); + if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(s_memRegex) || jvmargs.contains("-XX-MaxHeapSize") || jvmargs.contains("-XX:InitialHeapSize")) { auto warnStr = QObject::tr( "You tried to manually set a JVM memory option (using \"-XX:PermSize\", \"-XX-MaxHeapSize\", \"-XX:InitialHeapSize\", \"-Xmx\" " @@ -52,7 +54,7 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent) return false; } // block lunacy with passing required version to the JVM - if (jvmargs.contains(QRegularExpression("-version:.*"))) { + if (jvmargs.contains(s_versionRegex)) { auto warnStr = QObject::tr( "You tried to pass required Java version argument to the JVM (using \"-version:xxx\"). This is not safe and will not be " "allowed.\n" diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 07047bf67..b1a956b49 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -182,7 +182,8 @@ void LaunchController::login() auto name = askOfflineName("Player", m_demo, ok); if (ok) { m_session = std::make_shared(); - m_session->MakeDemo(name, MinecraftAccount::uuidFromUsername(name).toString().remove(QRegularExpression("[{}-]"))); + static const QRegularExpression s_removeChars("[{}-]"); + m_session->MakeDemo(name, MinecraftAccount::uuidFromUsername(name).toString().remove(s_removeChars)); launchInstance(); return; } diff --git a/launcher/RecursiveFileSystemWatcher.cpp b/launcher/RecursiveFileSystemWatcher.cpp index 8b28a03f1..5cb3cd0be 100644 --- a/launcher/RecursiveFileSystemWatcher.cpp +++ b/launcher/RecursiveFileSystemWatcher.cpp @@ -1,7 +1,6 @@ #include "RecursiveFileSystemWatcher.h" #include -#include RecursiveFileSystemWatcher::RecursiveFileSystemWatcher(QObject* parent) : QObject(parent), m_watcher(new QFileSystemWatcher(this)) { diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 2ea67762e..b9e875482 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -213,11 +213,10 @@ QPair StringUtils::splitFirst(const QString& s, const QRegular return qMakePair(left, right); } -static const QRegularExpression ulMatcher("<\\s*/\\s*ul\\s*>"); - QString StringUtils::htmlListPatch(QString htmlStr) { - int pos = htmlStr.indexOf(ulMatcher); + static const QRegularExpression s_ulMatcher("<\\s*/\\s*ul\\s*>"); + int pos = htmlStr.indexOf(s_ulMatcher); int imgPos; while (pos != -1) { pos = htmlStr.indexOf(">", pos) + 1; // Get the size of the tag. Add one for zeroeth index @@ -230,7 +229,7 @@ QString StringUtils::htmlListPatch(QString htmlStr) if (textBetween.isEmpty()) htmlStr.insert(pos, "
"); - pos = htmlStr.indexOf(ulMatcher, pos); + pos = htmlStr.indexOf(s_ulMatcher, pos); } return htmlStr; } \ No newline at end of file diff --git a/launcher/Version.cpp b/launcher/Version.cpp index 03a16e8a0..bffe5d58a 100644 --- a/launcher/Version.cpp +++ b/launcher/Version.cpp @@ -1,7 +1,6 @@ #include "Version.h" #include -#include #include #include diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp index bca50f2c9..e9a160ea7 100644 --- a/launcher/java/JavaVersion.cpp +++ b/launcher/java/JavaVersion.cpp @@ -19,9 +19,13 @@ JavaVersion& JavaVersion::operator=(const QString& javaVersionString) QRegularExpression pattern; if (javaVersionString.startsWith("1.")) { - pattern = QRegularExpression("1[.](?[0-9]+)([.](?[0-9]+))?(_(?[0-9]+)?)?(-(?[a-zA-Z0-9]+))?"); + static const QRegularExpression s_withOne( + "1[.](?[0-9]+)([.](?[0-9]+))?(_(?[0-9]+)?)?(-(?[a-zA-Z0-9]+))?"); + pattern = s_withOne; } else { - pattern = QRegularExpression("(?[0-9]+)([.](?[0-9]+))?([.](?[0-9]+))?(-(?[a-zA-Z0-9]+))?"); + static const QRegularExpression s_withoutOne( + "(?[0-9]+)([.](?[0-9]+))?([.](?[0-9]+))?(-(?[a-zA-Z0-9]+))?"); + pattern = s_withoutOne; } auto match = pattern.match(m_string); diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp index e3e8eaec2..b67df7631 100644 --- a/launcher/launch/LaunchTask.cpp +++ b/launcher/launch/LaunchTask.cpp @@ -41,7 +41,6 @@ #include #include #include -#include #include #include #include "MessageLevel.h" diff --git a/launcher/minecraft/GradleSpecifier.h b/launcher/minecraft/GradleSpecifier.h index 22db7d641..a2588064f 100644 --- a/launcher/minecraft/GradleSpecifier.h +++ b/launcher/minecraft/GradleSpecifier.h @@ -54,11 +54,11 @@ struct GradleSpecifier { 4 "jdk15" 5 "jar" */ - QRegularExpression matcher( + static const QRegularExpression s_matcher( QRegularExpression::anchoredPattern("([^:@]+):([^:@]+):([^:@]+)" "(?::([^:@]+))?" "(?:@([^:@]+))?")); - QRegularExpressionMatch match = matcher.match(value); + QRegularExpressionMatch match = s_matcher.match(value); m_valid = match.hasMatch(); if (!m_valid) { m_invalidValue = value; diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 83141158e..b8ab93d5b 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -53,7 +53,6 @@ #include "MMCTime.h" #include "java/JavaVersion.h" #include "pathmatcher/MultiMatcher.h" -#include "pathmatcher/RegexpMatcher.h" #include "launch/LaunchTask.h" #include "launch/TaskStepWrapper.h" @@ -689,9 +688,9 @@ static QString replaceTokensIn(QString text, QMap with) { // TODO: does this still work?? QString result; - QRegularExpression token_regexp("\\$\\{(.+)\\}", QRegularExpression::InvertedGreedinessOption); + static const QRegularExpression s_token_regexp("\\$\\{(.+)\\}", QRegularExpression::InvertedGreedinessOption); QStringList list; - QRegularExpressionMatchIterator i = token_regexp.globalMatch(text); + QRegularExpressionMatchIterator i = s_token_regexp.globalMatch(text); int lastCapturedEnd = 0; while (i.hasNext()) { QRegularExpressionMatch match = i.next(); diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index 684869c8d..32dd1875c 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -114,9 +114,9 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc out->uid = root.value("fileId").toString(); } - const QRegularExpression valid_uid_regex{ QRegularExpression::anchoredPattern( + static const QRegularExpression s_validUidRegex{ QRegularExpression::anchoredPattern( QStringLiteral(R"([a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]+)*)")) }; - if (!valid_uid_regex.match(out->uid).hasMatch()) { + if (!s_validUidRegex.match(out->uid).hasMatch()) { qCritical() << "The component's 'uid' contains illegal characters! UID:" << out->uid; out->addProblem(ProblemSeverity::Error, QObject::tr("The component's 'uid' contains illegal characters! This can cause security issues.")); diff --git a/launcher/minecraft/ProfileUtils.cpp b/launcher/minecraft/ProfileUtils.cpp index 08ec0fac3..a79f89529 100644 --- a/launcher/minecraft/ProfileUtils.cpp +++ b/launcher/minecraft/ProfileUtils.cpp @@ -41,7 +41,6 @@ #include #include -#include #include namespace ProfileUtils { diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index fd2082035..9dbe7ffc0 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -38,7 +38,6 @@ #include #include #include -#include #include namespace { diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 1613a42b1..86e9cc511 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -55,7 +55,8 @@ MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { - data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); + static const QRegularExpression s_removeChars("[{}-]"); + data.internalId = QUuid::createUuid().toString().remove(s_removeChars); } MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) @@ -76,14 +77,15 @@ MinecraftAccountPtr MinecraftAccount::createBlankMSA() MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username) { + static const QRegularExpression s_removeChars("[{}-]"); auto account = makeShared(); account->data.type = AccountType::Offline; account->data.yggdrasilToken.token = "0"; account->data.yggdrasilToken.validity = Validity::Certain; account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); account->data.yggdrasilToken.extra["userName"] = username; - account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); - account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]")); + account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(s_removeChars); + account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(s_removeChars); account->data.minecraftProfile.name = username; account->data.minecraftProfile.validity = Validity::Certain; return account; @@ -231,6 +233,7 @@ bool MinecraftAccount::shouldRefresh() const void MinecraftAccount::fillSession(AuthSessionPtr session) { + static const QRegularExpression s_removeChars("[{}-]"); if (ownsMinecraft() && !hasProfile()) { session->status = AuthSession::RequiresProfileSetup; } else { @@ -248,7 +251,7 @@ void MinecraftAccount::fillSession(AuthSessionPtr session) // profile ID session->uuid = data.profileId(); if (session->uuid.isEmpty()) - session->uuid = uuidFromUsername(session->player_name).toString().remove(QRegularExpression("[{}-]")); + session->uuid = uuidFromUsername(session->player_name).toString().remove(s_removeChars); // 'legacy' or 'mojang', depending on account type session->user_type = typeString(); if (!session->access_token.isEmpty()) { diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 49d91e433..388d55628 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -53,15 +53,16 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) , m_process(parent->instance()->getJavaVersion().defaultsToUtf8() ? QTextCodec::codecForName("UTF-8") : QTextCodec::codecForLocale()) { if (parent->instance()->settings()->get("CloseAfterLaunch").toBool()) { + static const QRegularExpression s_settingUser(".*Setting user.+", QRegularExpression::CaseInsensitiveOption); std::shared_ptr connection{ new QMetaObject::Connection }; - *connection = connect( - &m_process, &LoggedProcess::log, this, [connection](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) { - qDebug() << lines; - if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) { - APPLICATION->closeAllWindows(); - disconnect(*connection); - } - }); + *connection = connect(&m_process, &LoggedProcess::log, this, + [connection](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) { + qDebug() << lines; + if (lines.filter(s_settingUser).length() != 0) { + APPLICATION->closeAllWindows(); + disconnect(*connection); + } + }); } connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines); diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index d1a7b8f9c..6f2efe740 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -82,8 +82,8 @@ auto Resource::name() const -> QString static void removeThePrefix(QString& string) { - QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); - string.remove(regex); + static const QRegularExpression s_regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); + string.remove(s_regex); string = string.trimmed(); } diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index 4ed3c67e3..cf8ec871c 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -3,8 +3,6 @@ #include #include #include -#include - #include "MTPixmapCache.h" #include "Version.h" diff --git a/launcher/minecraft/mod/ShaderPack.cpp b/launcher/minecraft/mod/ShaderPack.cpp index ccb344cb5..99e51fcae 100644 --- a/launcher/minecraft/mod/ShaderPack.cpp +++ b/launcher/minecraft/mod/ShaderPack.cpp @@ -22,8 +22,6 @@ #include "ShaderPack.h" -#include - void ShaderPack::setPackFormat(ShaderPackFormat new_format) { QMutexLocker locker(&m_data_lock); diff --git a/launcher/minecraft/mod/TexturePack.cpp b/launcher/minecraft/mod/TexturePack.cpp index 04cc36310..a1ef7f525 100644 --- a/launcher/minecraft/mod/TexturePack.cpp +++ b/launcher/minecraft/mod/TexturePack.cpp @@ -21,8 +21,6 @@ #include #include -#include - #include "MTPixmapCache.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h" diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index b0e8eb101..952115bed 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -16,7 +16,7 @@ #include "minecraft/mod/ModDetails.h" #include "settings/INIFile.h" -static QRegularExpression newlineRegex("\r\n|\n|\r"); +static const QRegularExpression s_newlineRegex("\r\n|\n|\r"); namespace ModUtils { @@ -494,7 +494,7 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level) } // quick and dirty line-by-line parser - auto manifestLines = QString(file.readAll()).split(newlineRegex); + auto manifestLines = QString(file.readAll()).split(s_newlineRegex); QString manifestVersion = ""; for (auto& line : manifestLines) { if (line.startsWith("Implementation-Version: ", Qt::CaseInsensitive)) { diff --git a/launcher/modplatform/atlauncher/ATLPackIndex.cpp b/launcher/modplatform/atlauncher/ATLPackIndex.cpp index 678db63cc..c35569d45 100644 --- a/launcher/modplatform/atlauncher/ATLPackIndex.cpp +++ b/launcher/modplatform/atlauncher/ATLPackIndex.cpp @@ -43,5 +43,6 @@ void ATLauncher::loadIndexedPack(ATLauncher::IndexedPack& m, QJsonObject& obj) m.system = Json::ensureBoolean(obj, QString("system"), false); m.description = Json::ensureString(obj, "description", ""); - m.safeName = Json::requireString(obj, "name").replace(QRegularExpression("[^A-Za-z0-9]"), "").toLower() + ".png"; + static const QRegularExpression s_regex("[^A-Za-z0-9]"); + m.safeName = Json::requireString(obj, "name").replace(s_regex, "").toLower() + ".png"; } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index a9706a768..e22e6135d 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -69,7 +69,8 @@ PackInstallTask::PackInstallTask(UserInteractionSupport* support, QString packNa { m_support = support; m_pack_name = packName; - m_pack_safe_name = packName.replace(QRegularExpression("[^A-Za-z0-9]"), ""); + static const QRegularExpression s_regex("[^A-Za-z0-9]"); + m_pack_safe_name = packName.replace(s_regex, ""); m_version_name = version; m_install_mode = installMode; } @@ -938,7 +939,8 @@ bool PackInstallTask::extractMods(const QMap& toExtract, QString folderToExtract = ""; if (mod.type == ModType::Extract) { folderToExtract = mod.extractFolder; - folderToExtract.remove(QRegularExpression("^/")); + static const QRegularExpression s_regex("^/"); + folderToExtract.remove(s_regex); } qDebug() << "Extracting " + mod.file + " to " + extractToDir; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 22c9e603b..c30ba5249 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -391,7 +391,8 @@ bool FlameCreationTask::createInstance() // Hack to correct some 'special sauce'... if (mcVersion.endsWith('.')) { - mcVersion.remove(QRegularExpression("[.]+$")); + static const QRegularExpression s_regex("[.]+$"); + mcVersion.remove(s_regex); logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); } diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp index 432c0c84b..8896f10e3 100644 --- a/launcher/net/MetaCacheSink.cpp +++ b/launcher/net/MetaCacheSink.cpp @@ -98,8 +98,8 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply& reply) auto cache_control_header = reply.rawHeader("Cache-Control"); qCDebug(taskMetaCacheLogC) << "Parsing 'Cache-Control' header with" << cache_control_header; - QRegularExpression max_age_expr("max-age=([0-9]+)"); - qint64 max_age = max_age_expr.match(cache_control_header).captured(1).toLongLong(); + static const QRegularExpression s_maxAgeExpr("max-age=([0-9]+)"); + qint64 max_age = s_maxAgeExpr.match(cache_control_header).captured(1).toLongLong(); m_entry->setMaximumAge(max_age); } else if (reply.hasRawHeader("Expires")) { diff --git a/launcher/pathmatcher/FSTreeMatcher.h b/launcher/pathmatcher/FSTreeMatcher.h index 689f11979..d8d36d2c3 100644 --- a/launcher/pathmatcher/FSTreeMatcher.h +++ b/launcher/pathmatcher/FSTreeMatcher.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include "IPathMatcher.h" class FSTreeMatcher : public IPathMatcher { diff --git a/launcher/pathmatcher/MultiMatcher.h b/launcher/pathmatcher/MultiMatcher.h index ccd5a9163..3ad07b643 100644 --- a/launcher/pathmatcher/MultiMatcher.h +++ b/launcher/pathmatcher/MultiMatcher.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include "IPathMatcher.h" class MultiMatcher : public IPathMatcher { diff --git a/launcher/pathmatcher/RegexpMatcher.h b/launcher/pathmatcher/RegexpMatcher.h index 18c42f887..e36516386 100644 --- a/launcher/pathmatcher/RegexpMatcher.h +++ b/launcher/pathmatcher/RegexpMatcher.h @@ -12,6 +12,8 @@ class RegexpMatcher : public IPathMatcher { m_onlyFilenamePart = !regexp.contains('/'); } + RegexpMatcher(const QRegularExpression& regex) : m_regexp(regex) { m_onlyFilenamePart = !regex.pattern().contains('/'); } + RegexpMatcher& caseSensitive(bool cs = true) { if (cs) { diff --git a/launcher/pathmatcher/SimplePrefixMatcher.h b/launcher/pathmatcher/SimplePrefixMatcher.h index ff3805179..57bf63a30 100644 --- a/launcher/pathmatcher/SimplePrefixMatcher.h +++ b/launcher/pathmatcher/SimplePrefixMatcher.h @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: GPL-3.0-only -#include #include "IPathMatcher.h" class SimplePrefixMatcher : public IPathMatcher { diff --git a/launcher/ui/dialogs/NewComponentDialog.cpp b/launcher/ui/dialogs/NewComponentDialog.cpp index b5f8ff889..d1e420864 100644 --- a/launcher/ui/dialogs/NewComponentDialog.cpp +++ b/launcher/ui/dialogs/NewComponentDialog.cpp @@ -83,7 +83,8 @@ NewComponentDialog::~NewComponentDialog() void NewComponentDialog::updateDialogState() { auto protoUid = ui->nameTextBox->text().toLower(); - protoUid.remove(QRegularExpression("[^a-z]")); + static const QRegularExpression s_removeChars("[^a-z]"); + protoUid.remove(s_removeChars); if (protoUid.isEmpty()) { ui->uidTextBox->setPlaceholderText(originalPlaceholderText); } else { diff --git a/launcher/ui/dialogs/ProfileSetupDialog.cpp b/launcher/ui/dialogs/ProfileSetupDialog.cpp index dd87b249c..0b5e1a784 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.cpp +++ b/launcher/ui/dialogs/ProfileSetupDialog.cpp @@ -59,9 +59,9 @@ ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidg yellowIcon = APPLICATION->getThemedIcon("status-yellow"); badIcon = APPLICATION->getThemedIcon("status-bad"); - QRegularExpression permittedNames("[a-zA-Z0-9_]{3,16}"); + static const QRegularExpression s_permittedNames("[a-zA-Z0-9_]{3,16}"); auto nameEdit = ui->nameEdit; - nameEdit->setValidator(new QRegularExpressionValidator(permittedNames)); + nameEdit->setValidator(new QRegularExpressionValidator(s_permittedNames)); nameEdit->setClearButtonEnabled(true); validityAction = nameEdit->addAction(yellowIcon, QLineEdit::LeadingPosition); connect(nameEdit, &QLineEdit::textEdited, this, &ProfileSetupDialog::nameEdited); @@ -268,7 +268,6 @@ void ProfileSetupDialog::setupProfileFinished() QString errorMessage = tr("Network Error: %1\nHTTP Status: %2").arg(m_profile_task->errorString(), QString::number(m_profile_task->replyStatusCode())); - if (parsedError.fullyParsed) { errorMessage += "Path: " + parsedError.path + "\n"; errorMessage += "Error: " + parsedError.error + "\n"; diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index a137c4cde..a030bf316 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -59,10 +59,10 @@ APIPage::APIPage(QWidget* parent) : QWidget(parent), ui(new Ui::APIPage) int comboBoxEntries[] = { PasteUpload::PasteType::Mclogs, PasteUpload::PasteType::NullPointer, PasteUpload::PasteType::PasteGG, PasteUpload::PasteType::Hastebin }; - static QRegularExpression validUrlRegExp("https?://.+"); - static QRegularExpression validMSAClientID( + static const QRegularExpression s_validUrlRegExp("https?://.+"); + static const QRegularExpression s_validMSAClientID( QRegularExpression::anchoredPattern("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}")); - static QRegularExpression validFlameKey(QRegularExpression::anchoredPattern("\\$2[ayb]\\$.{56}")); + static const QRegularExpression s_validFlameKey(QRegularExpression::anchoredPattern("\\$2[ayb]\\$.{56}")); ui->setupUi(this); @@ -75,10 +75,10 @@ APIPage::APIPage(QWidget* parent) : QWidget(parent), ui(new Ui::APIPage) // This function needs to be called even when the ComboBox's index is still in its default state. updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex()); // NOTE: this allows http://, but we replace that with https later anyway - ui->metaURL->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->metaURL)); - ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry)); - ui->msaClientID->setValidator(new QRegularExpressionValidator(validMSAClientID, ui->msaClientID)); - ui->flameKey->setValidator(new QRegularExpressionValidator(validFlameKey, ui->flameKey)); + ui->metaURL->setValidator(new QRegularExpressionValidator(s_validUrlRegExp, ui->metaURL)); + ui->baseURLEntry->setValidator(new QRegularExpressionValidator(s_validUrlRegExp, ui->baseURLEntry)); + ui->msaClientID->setValidator(new QRegularExpressionValidator(s_validMSAClientID, ui->msaClientID)); + ui->flameKey->setValidator(new QRegularExpressionValidator(s_validFlameKey, ui->flameKey)); ui->metaURL->setPlaceholderText(BuildConfig.META_URL); ui->userAgentLineEdit->setPlaceholderText(BuildConfig.USER_AGENT); diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index fa568c794..e59002a15 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -150,7 +150,8 @@ class FilterModel : public QIdentityProxyModel { return QVariant(); if (role == Qt::DisplayRole || role == Qt::EditRole) { QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); - return result.toString().remove(QRegularExpression("\\.png$")); + static const QRegularExpression s_removeChars("\\.png$"); + return result.toString().remove(s_removeChars); } if (role == Qt::DecorationRole) { QVariant result = sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 365647db9..815924d52 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -721,8 +721,8 @@ QList PrismUpdaterApp::validReleaseArtifacts(const GitHubRel for_platform = false; } - auto qt_pattern = QRegularExpression("-qt(\\d+)"); - auto qt_match = qt_pattern.match(asset_name); + static const QRegularExpression s_qtPattern("-qt(\\d+)"); + auto qt_match = s_qtPattern.match(asset_name); if (for_platform && qt_match.hasMatch()) { if (platform_qt_ver.isEmpty() || platform_qt_ver.toInt() != qt_match.captured(1).toInt()) { qDebug() << "Rejecting" << asset.name << "because it is not for the correct qt version" << platform_qt_ver.toInt() << "vs" @@ -1018,12 +1018,11 @@ void PrismUpdaterApp::backupAppDir() logUpdate("manifest.txt empty or missing. making best guess at files to back up."); } logUpdate(tr("Backing up:\n %1").arg(file_list.join(",\n "))); + static const QRegularExpression s_replaceRegex("[" + QRegularExpression::escape("\\/:*?\"<>|") + "]"); auto app_dir = QDir(m_rootPath); - auto backup_dir = FS::PathCombine( - app_dir.absolutePath(), - QStringLiteral("backup_") + - QString(m_prismVersion).replace(QRegularExpression("[" + QRegularExpression::escape("\\/:*?\"<>|") + "]"), QString("_")) + "-" + - m_prismGitCommit); + auto backup_dir = + FS::PathCombine(app_dir.absolutePath(), + QStringLiteral("backup_") + QString(m_prismVersion).replace(s_replaceRegex, QString("_")) + "-" + m_prismGitCommit); FS::ensureFolderPathExists(backup_dir); auto backup_marker_path = FS::PathCombine(m_dataPath, ".prism_launcher_update_backup_path.txt"); FS::write(backup_marker_path, backup_dir.toUtf8()); diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp index 3761c109e..cb74c031b 100644 --- a/libraries/LocalPeer/src/LocalPeer.cpp +++ b/libraries/LocalPeer/src/LocalPeer.cpp @@ -72,7 +72,8 @@ ApplicationId ApplicationId::fromTraditionalApp() protoId = protoId.toLower(); #endif auto prefix = protoId.section(QLatin1Char('/'), -1); - prefix.remove(QRegularExpression("[^a-zA-Z]")); + static const QRegularExpression s_removeChars("[^a-zA-Z]"); + prefix.remove(s_removeChars); prefix.truncate(6); QByteArray idc = protoId.toUtf8(); quint16 idNum = qChecksum(idc); diff --git a/libraries/qdcss/src/qdcss.cpp b/libraries/qdcss/src/qdcss.cpp index c531fb63d..bf0ef63cb 100644 --- a/libraries/qdcss/src/qdcss.cpp +++ b/libraries/qdcss/src/qdcss.cpp @@ -8,19 +8,19 @@ #include #include -QRegularExpression ruleset_re = QRegularExpression(R"([#.]?(@?\w+?)\s*\{(.*?)\})", QRegularExpression::DotMatchesEverythingOption); -QRegularExpression rule_re = QRegularExpression(R"((\S+?)\s*:\s*(?:\"(.*?)(? +static const QRegularExpression s_distoSplitRegex("\\s+"); + Sys::DistributionInfo Sys::read_os_release() { Sys::DistributionInfo out; @@ -145,7 +147,7 @@ void Sys::lsb_postprocess(Sys::LsbInfo& lsb, Sys::DistributionInfo& out) vers = lsb.codename; } else { // ubuntu, debian, gentoo, scientific, slackware, ... ? - auto parts = dist.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); + auto parts = dist.split(s_distoSplitRegex, Qt::SkipEmptyParts); if (parts.size()) { dist = parts[0]; } @@ -178,7 +180,7 @@ QString Sys::_extract_distribution(const QString& x) if (release.startsWith("suse linux enterprise")) { return "sles"; } - QStringList list = release.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); + QStringList list = release.split(s_distoSplitRegex, Qt::SkipEmptyParts); if (list.size()) { return list[0]; } @@ -187,11 +189,11 @@ QString Sys::_extract_distribution(const QString& x) QString Sys::_extract_version(const QString& x) { - QRegularExpression versionish_string(QRegularExpression::anchoredPattern("\\d+(?:\\.\\d+)*$")); - QStringList list = x.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); + static const QRegularExpression s_versionishString(QRegularExpression::anchoredPattern("\\d+(?:\\.\\d+)*$")); + QStringList list = x.split(s_distoSplitRegex, Qt::SkipEmptyParts); for (int i = list.size() - 1; i >= 0; --i) { QString chunk = list[i]; - if (versionish_string.match(chunk).hasMatch()) { + if (s_versionishString.match(chunk).hasMatch()) { return chunk; } } From 71da130fe4cfbc64ced56fe27a89af49b6715494 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 28 Apr 2025 23:11:52 +0300 Subject: [PATCH 104/171] ci(nix): fix the PR number Signed-off-by: Trial97 --- .github/workflows/nix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index a77b33521..75ef7c65a 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -72,7 +72,7 @@ jobs: id: merge-commit uses: PrismLauncher/PrismLauncher/.github/actions/get-merge-commit@develop with: - pull-request-id: ${{ github.event.pull_request.id }} + pull-request-id: ${{ github.event.number }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 1c223997db10c5459b58fa93be541edfac925ac5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 21:33:12 +0000 Subject: [PATCH 105/171] chore(deps): update hendrikmuhs/ccache-action action to v1.2.18 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5d5cbc893..952b7c515 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -152,7 +152,7 @@ jobs: - name: Setup ccache if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' - uses: hendrikmuhs/ccache-action@v1.2.17 + uses: hendrikmuhs/ccache-action@v1.2.18 with: key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} From 93552277fe52c31eb03f4eb8ae08969f61a01891 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 29 Apr 2025 09:02:58 +0300 Subject: [PATCH 106/171] fix: build error introduced in #3516 Signed-off-by: Trial97 --- launcher/ui/widgets/ProjectItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 950c5fe0a..03fa659c9 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -152,7 +152,7 @@ bool ProjectItemDelegate::editorEvent(QEvent* event, const QStyleOptionViewItem checkboxOpt = makeCheckboxStyleOption(opt, style); - if (!checkboxOpt.rect.contains(mouseEvent->x(), mouseEvent->y())) + if (!checkboxOpt.rect.contains(mouseEvent->pos().x(), mouseEvent->pos().y())) return false; // swallow other events From 7da32af1b26ff9695e52c19622989e1dc755b498 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 29 Apr 2025 10:42:42 +0300 Subject: [PATCH 107/171] ci(nix): remove addtional > Signed-off-by: Trial97 --- .github/actions/get-merge-commit/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/get-merge-commit/action.yml b/.github/actions/get-merge-commit/action.yml index 8c67fdfc9..534d138e1 100644 --- a/.github/actions/get-merge-commit/action.yml +++ b/.github/actions/get-merge-commit/action.yml @@ -98,6 +98,6 @@ runs: if [[ "$mergeable" == "true" ]]; then echo "merge-commit-sha=$(jq -r .merge_commit_sha <<<"$prInfo")" >> "$GITHUB_OUTPUT" else - echo "# 🚨 The PR has a merge conflict!" >>> "$GITHUB_STEP_SUMMARY" + echo "# 🚨 The PR has a merge conflict!" >> "$GITHUB_STEP_SUMMARY" exit 2 fi From 5c8481a118c8fefbfe901001d7828eaf6866eac4 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 29 Apr 2025 10:34:42 +0300 Subject: [PATCH 108/171] chore: reformat Signed-off-by: Trial97 --- launcher/Application.cpp | 1 + launcher/launch/LogModel.cpp | 4 +- launcher/logs/LogParser.cpp | 2 +- launcher/minecraft/MinecraftInstance.cpp | 1 - launcher/minecraft/mod/Mod.h | 2 +- .../minecraft/mod/TexturePackFolderModel.cpp | 3 +- launcher/net/HttpMetaCache.cpp | 4 +- launcher/tasks/Task.h | 5 +- launcher/ui/MainWindow.cpp | 2 +- launcher/ui/pages/global/JavaPage.cpp | 2 +- launcher/ui/pages/global/JavaPage.h | 2 +- .../ui/pages/instance/InstanceSettingsPage.h | 5 +- .../ui/pages/instance/ManagedPackPage.cpp | 15 ++- launcher/ui/pages/instance/McClient.cpp | 91 ++++++++++--------- launcher/ui/pages/instance/McClient.h | 27 +++--- launcher/ui/pages/instance/McResolver.cpp | 31 ++++--- launcher/ui/pages/instance/McResolver.h | 14 +-- launcher/ui/pages/instance/ServerPingTask.cpp | 32 +++---- launcher/ui/pages/instance/ServerPingTask.h | 7 +- launcher/ui/themes/HintOverrideProxyStyle.cpp | 3 +- 20 files changed, 134 insertions(+), 119 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 33d700772..0daab026c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -128,6 +128,7 @@ #include #include +#include #include "SysInfo.h" #ifdef Q_OS_LINUX diff --git a/launcher/launch/LogModel.cpp b/launcher/launch/LogModel.cpp index 53a450ff7..90af9787d 100644 --- a/launcher/launch/LogModel.cpp +++ b/launcher/launch/LogModel.cpp @@ -167,8 +167,8 @@ bool LogModel::isOverFlow() return m_numLines >= m_maxLines && m_stopOnOverflow; } - -MessageLevel::Enum LogModel::previousLevel() { +MessageLevel::Enum LogModel::previousLevel() +{ if (!m_content.isEmpty()) { return m_content.last().level; } diff --git a/launcher/logs/LogParser.cpp b/launcher/logs/LogParser.cpp index 6e33b24dd..0790dec4d 100644 --- a/launcher/logs/LogParser.cpp +++ b/launcher/logs/LogParser.cpp @@ -107,7 +107,7 @@ std::optional LogParser::parseNext() if (m_buffer.trimmed().isEmpty()) { auto text = QString(m_buffer); m_buffer.clear(); - return LogParser::PlainText { text }; + return LogParser::PlainText{ text }; } // check if we have a full xml log4j event diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index b8ab93d5b..991afce89 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1003,7 +1003,6 @@ QMap MinecraftInstance::createCensorFilterFromSession(AuthSess return filter; } - QStringList MinecraftInstance::getLogFileSearchPaths() { return { FS::PathCombine(gameRoot(), "crash-reports"), FS::PathCombine(gameRoot(), "logs"), gameRoot() }; diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index deb1859de..553af92f3 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -84,7 +84,7 @@ class Mod : public Resource { bool valid() const override; - [[nodiscard]] int compare(const Resource & other, SortType type) const override; + [[nodiscard]] int compare(const Resource& other, SortType type) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; // Delete all the files of this mod diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 073ea7ca7..4d7c71359 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -48,7 +48,8 @@ TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* in m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size" }); m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") }); m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE }; - m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; m_columnsHideable = { false, true, false, true, true, true }; m_columnsHiddenByDefault = { false, false, false, false, false, true }; } diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 5a3a451b7..77c1de47d 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -166,7 +166,7 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool return true; } -//returns true on success, false otherwise +// returns true on success, false otherwise auto HttpMetaCache::evictAll() -> bool { bool ret = true; @@ -178,7 +178,7 @@ auto HttpMetaCache::evictAll() -> bool qCWarning(taskHttpMetaCacheLogC) << "Unexpected missing cache entry" << entry->m_basePath; } map.entry_list.clear(); - //AND all return codes together so the result is true iff all runs of deletePath() are true + // AND all return codes together so the result is true iff all runs of deletePath() are true ret &= FS::deletePath(map.base_path); } return ret; diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 503d6a6b6..fcd075150 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -79,7 +79,6 @@ Q_DECLARE_METATYPE(TaskStepProgress) using TaskStepProgressList = QList>; - /*! * Represents a task that has to be done. * To create a task, you need to subclass this class, implement the executeTask() method and call @@ -177,9 +176,9 @@ class Task : public QObject, public QRunnable { virtual void executeTask() = 0; protected slots: - //! The Task subclass must call this method when the task has succeeded + //! The Task subclass must call this method when the task has succeeded virtual void emitSucceeded(); - //! **The Task subclass** must call this method when the task has aborted. External code should call abort() instead. + //! **The Task subclass** must call this method when the task has aborted. External code should call abort() instead. virtual void emitAborted(); //! The Task subclass must call this method when the task has failed virtual void emitFailed(QString reason = ""); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index d190c6a02..d9275a7ab 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1311,7 +1311,7 @@ void MainWindow::on_actionReportBug_triggered() void MainWindow::on_actionClearMetadata_triggered() { - //This if contains side effects! + // This if contains side effects! if (!APPLICATION->metacache()->evictAll()) { CustomMessageBox::selectable(this, tr("Error"), tr("Metadata cache clear Failed!\nTo clear the metadata cache manually, press Folders -> View " diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index b99d0c63e..6a44c9290 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -62,7 +62,7 @@ JavaPage::JavaPage(QWidget* parent) : QWidget(parent), ui(new Ui::JavaPage) { ui->setupUi(this); - + if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { ui->managedJavaList->initialize(new JavaInstallList(this, true)); ui->managedJavaList->setResizeOn(2); diff --git a/launcher/ui/pages/global/JavaPage.h b/launcher/ui/pages/global/JavaPage.h index ea7724c1d..b30fa22e3 100644 --- a/launcher/ui/pages/global/JavaPage.h +++ b/launcher/ui/pages/global/JavaPage.h @@ -37,11 +37,11 @@ #include #include -#include "ui/widgets/JavaSettingsWidget.h" #include #include #include "JavaCommon.h" #include "ui/pages/BasePage.h" +#include "ui/widgets/JavaSettingsWidget.h" class SettingsObject; diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h index fa1dce3dc..de173937b 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.h +++ b/launcher/ui/pages/instance/InstanceSettingsPage.h @@ -35,17 +35,18 @@ #pragma once +#include #include "Application.h" #include "BaseInstance.h" #include "ui/pages/BasePage.h" #include "ui/widgets/MinecraftSettingsWidget.h" -#include class InstanceSettingsPage : public MinecraftSettingsWidget, public BasePage { Q_OBJECT public: - explicit InstanceSettingsPage(MinecraftInstancePtr instance, QWidget* parent = nullptr) : MinecraftSettingsWidget(std::move(instance), parent) + explicit InstanceSettingsPage(MinecraftInstancePtr instance, QWidget* parent = nullptr) + : MinecraftSettingsWidget(std::move(instance), parent) { connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::saveSettings); connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings); diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index 0fccd1d33..1738c9cde 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -347,13 +347,18 @@ void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const if (m_instance_window != nullptr) m_instance_window->close(); - CustomMessageBox::selectable(nullptr, tr("Update Successful"), tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Information) - ->show(); + CustomMessageBox::selectable(nullptr, tr("Update Successful"), + tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()), + QMessageBox::Information) + ->show(); } else { - CustomMessageBox::selectable(nullptr, tr("Update Failed"), tr("The instance failed to update to pack version %1. Please check launcher logs for more information.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Critical) - ->show(); + CustomMessageBox::selectable( + nullptr, tr("Update Failed"), + tr("The instance failed to update to pack version %1. Please check launcher logs for more information.") + .arg(m_inst->getManagedPackVersionName()), + QMessageBox::Critical) + ->show(); } - } void ModrinthManagedPackPage::update() diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index 90813ac18..11b3a22d1 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -1,21 +1,22 @@ -#include -#include +#include #include #include -#include +#include +#include #include -#include "McClient.h" #include "Json.h" +#include "McClient.h" -// 7 first bits +// 7 first bits #define SEGMENT_BITS 0x7F // last bit #define CONTINUE_BIT 0x80 -McClient::McClient(QObject *parent, QString domain, QString ip, short port): QObject(parent), m_domain(domain), m_ip(ip), m_port(port) {} +McClient::McClient(QObject* parent, QString domain, QString ip, short port) : QObject(parent), m_domain(domain), m_ip(ip), m_port(port) {} -void McClient::getStatusData() { +void McClient::getStatusData() +{ qDebug() << "Connecting to socket.."; connect(&m_socket, &QTcpSocket::connected, this, [this]() { @@ -25,28 +26,28 @@ void McClient::getStatusData() { connect(&m_socket, &QTcpSocket::readyRead, this, &McClient::readRawResponse); }); - connect(&m_socket, &QTcpSocket::errorOccurred, this, [this]() { - emitFail("Socket disconnected: " + m_socket.errorString()); - }); + connect(&m_socket, &QTcpSocket::errorOccurred, this, [this]() { emitFail("Socket disconnected: " + m_socket.errorString()); }); m_socket.connectToHost(m_ip, m_port); } -void McClient::sendRequest() { +void McClient::sendRequest() +{ QByteArray data; - writeVarInt(data, 0x00); // packet ID - writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1) - writeVarInt(data, m_domain.size()); // server address length - writeString(data, m_domain.toStdString()); // server address - writeFixedInt(data, m_port, 2); // server port - writeVarInt(data, 0x01); // next state - writePacketToSocket(data); // send handshake packet + writeVarInt(data, 0x00); // packet ID + writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1) + writeVarInt(data, m_domain.size()); // server address length + writeString(data, m_domain.toStdString()); // server address + writeFixedInt(data, m_port, 2); // server port + writeVarInt(data, 0x01); // next state + writePacketToSocket(data); // send handshake packet - writeVarInt(data, 0x00); // packet ID - writePacketToSocket(data); // send status packet + writeVarInt(data, 0x00); // packet ID + writePacketToSocket(data); // send status packet } -void McClient::readRawResponse() { +void McClient::readRawResponse() +{ if (m_responseReadState == 2) { return; } @@ -56,28 +57,27 @@ void McClient::readRawResponse() { m_wantedRespLength = readVarInt(m_resp); m_responseReadState = 1; } - + if (m_responseReadState == 1 && m_resp.size() >= m_wantedRespLength) { if (m_resp.size() > m_wantedRespLength) { - qDebug() << "Warning: Packet length doesn't match actual packet size (" << m_wantedRespLength << " expected vs " << m_resp.size() << " received)"; + qDebug() << "Warning: Packet length doesn't match actual packet size (" << m_wantedRespLength << " expected vs " + << m_resp.size() << " received)"; } parseResponse(); m_responseReadState = 2; } } -void McClient::parseResponse() { +void McClient::parseResponse() +{ qDebug() << "Received response successfully"; int packetID = readVarInt(m_resp); if (packetID != 0x00) { - throw Exception( - QString("Packet ID doesn't match expected value (0x00 vs 0x%1)") - .arg(packetID, 0, 16) - ); + throw Exception(QString("Packet ID doesn't match expected value (0x00 vs 0x%1)").arg(packetID, 0, 16)); } - Q_UNUSED(readVarInt(m_resp)); // json length + Q_UNUSED(readVarInt(m_resp)); // json length // 'resp' should now be the JSON string QJsonDocument doc = QJsonDocument::fromJson(m_resp); @@ -85,8 +85,9 @@ void McClient::parseResponse() { } // From https://wiki.vg/Protocol#VarInt_and_VarLong -void McClient::writeVarInt(QByteArray &data, int value) { - while ((value & ~SEGMENT_BITS)) { // check if the value is too big to fit in 7 bits +void McClient::writeVarInt(QByteArray& data, int value) +{ + while ((value & ~SEGMENT_BITS)) { // check if the value is too big to fit in 7 bits // Write 7 bits data.append((value & SEGMENT_BITS) | CONTINUE_BIT); @@ -98,7 +99,8 @@ void McClient::writeVarInt(QByteArray &data, int value) { } // From https://wiki.vg/Protocol#VarInt_and_VarLong -int McClient::readVarInt(QByteArray &data) { +int McClient::readVarInt(QByteArray& data) +{ int value = 0; int position = 0; char currentByte; @@ -107,17 +109,20 @@ int McClient::readVarInt(QByteArray &data) { currentByte = readByte(data); value |= (currentByte & SEGMENT_BITS) << position; - if ((currentByte & CONTINUE_BIT) == 0) break; + if ((currentByte & CONTINUE_BIT) == 0) + break; position += 7; } - if (position >= 32) throw Exception("VarInt is too big"); + if (position >= 32) + throw Exception("VarInt is too big"); return value; } -char McClient::readByte(QByteArray &data) { +char McClient::readByte(QByteArray& data) +{ if (data.isEmpty()) { throw Exception("No more bytes to read"); } @@ -128,17 +133,20 @@ char McClient::readByte(QByteArray &data) { } // write number with specified size in big endian format -void McClient::writeFixedInt(QByteArray &data, int value, int size) { +void McClient::writeFixedInt(QByteArray& data, int value, int size) +{ for (int i = size - 1; i >= 0; i--) { data.append((value >> (i * 8)) & 0xFF); } } -void McClient::writeString(QByteArray &data, const std::string &value) { +void McClient::writeString(QByteArray& data, const std::string& value) +{ data.append(value.c_str()); } -void McClient::writePacketToSocket(QByteArray &data) { +void McClient::writePacketToSocket(QByteArray& data) +{ // we prefix the packet with its length QByteArray dataWithSize; writeVarInt(dataWithSize, data.size()); @@ -151,14 +159,15 @@ void McClient::writePacketToSocket(QByteArray &data) { data.clear(); } - -void McClient::emitFail(QString error) { +void McClient::emitFail(QString error) +{ qDebug() << "Minecraft server ping for status error:" << error; emit failed(error); emit finished(); } -void McClient::emitSucceed(QJsonObject data) { +void McClient::emitSucceed(QJsonObject data) +{ emit succeeded(data); emit finished(); } diff --git a/launcher/ui/pages/instance/McClient.h b/launcher/ui/pages/instance/McClient.h index 59834dfb7..832b70d40 100644 --- a/launcher/ui/pages/instance/McClient.h +++ b/launcher/ui/pages/instance/McClient.h @@ -1,8 +1,8 @@ -#include -#include +#include #include #include -#include +#include +#include #include @@ -22,29 +22,30 @@ class McClient : public QObject { unsigned m_wantedRespLength = 0; QByteArray m_resp; -public: - explicit McClient(QObject *parent, QString domain, QString ip, short port); + public: + explicit McClient(QObject* parent, QString domain, QString ip, short port); //! Read status data of the server, and calls the succeeded() signal with the parsed JSON data void getStatusData(); -private: + + private: void sendRequest(); //! Accumulate data until we have a full response, then call parseResponse() once void readRawResponse(); void parseResponse(); - void writeVarInt(QByteArray &data, int value); - int readVarInt(QByteArray &data); - char readByte(QByteArray &data); + void writeVarInt(QByteArray& data, int value); + int readVarInt(QByteArray& data); + char readByte(QByteArray& data); //! write number with specified size in big endian format - void writeFixedInt(QByteArray &data, int value, int size); - void writeString(QByteArray &data, const std::string &value); + void writeFixedInt(QByteArray& data, int value, int size); + void writeString(QByteArray& data, const std::string& value); - void writePacketToSocket(QByteArray &data); + void writePacketToSocket(QByteArray& data); void emitFail(QString error); void emitSucceed(QJsonObject data); -signals: + signals: void succeeded(QJsonObject data); void failed(QString error); void finished(); diff --git a/launcher/ui/pages/instance/McResolver.cpp b/launcher/ui/pages/instance/McResolver.cpp index 48c2a72fd..2a769762c 100644 --- a/launcher/ui/pages/instance/McResolver.cpp +++ b/launcher/ui/pages/instance/McResolver.cpp @@ -1,23 +1,25 @@ -#include -#include #include +#include #include +#include #include "McResolver.h" -McResolver::McResolver(QObject *parent, QString domain, int port): QObject(parent), m_constrDomain(domain), m_constrPort(port) {} +McResolver::McResolver(QObject* parent, QString domain, int port) : QObject(parent), m_constrDomain(domain), m_constrPort(port) {} -void McResolver::ping() { +void McResolver::ping() +{ pingWithDomainSRV(m_constrDomain, m_constrPort); } -void McResolver::pingWithDomainSRV(QString domain, int port) { - QDnsLookup *lookup = new QDnsLookup(this); +void McResolver::pingWithDomainSRV(QString domain, int port) +{ + QDnsLookup* lookup = new QDnsLookup(this); lookup->setName(QString("_minecraft._tcp.%1").arg(domain)); lookup->setType(QDnsLookup::SRV); connect(lookup, &QDnsLookup::finished, this, [this, domain, port]() { - QDnsLookup *lookup = qobject_cast(sender()); + QDnsLookup* lookup = qobject_cast(sender()); lookup->deleteLater(); @@ -43,8 +45,9 @@ void McResolver::pingWithDomainSRV(QString domain, int port) { lookup->lookup(); } -void McResolver::pingWithDomainA(QString domain, int port) { - QHostInfo::lookupHost(domain, this, [this, port](const QHostInfo &hostInfo){ +void McResolver::pingWithDomainA(QString domain, int port) +{ + QHostInfo::lookupHost(domain, this, [this, port](const QHostInfo& hostInfo) { if (hostInfo.error() != QHostInfo::NoError) { emitFail("A record lookup failed"); return; @@ -55,19 +58,21 @@ void McResolver::pingWithDomainA(QString domain, int port) { emitFail("No A entries found for domain"); return; } - + const auto& firstRecord = records.at(0); emitSucceed(firstRecord.toString(), port); - }); + }); } -void McResolver::emitFail(QString error) { +void McResolver::emitFail(QString error) +{ qDebug() << "DNS resolver error:" << error; emit failed(error); emit finished(); } -void McResolver::emitSucceed(QString ip, int port) { +void McResolver::emitSucceed(QString ip, int port) +{ emit succeeded(ip, port); emit finished(); } diff --git a/launcher/ui/pages/instance/McResolver.h b/launcher/ui/pages/instance/McResolver.h index 06b4b7b38..3dfeddc6a 100644 --- a/launcher/ui/pages/instance/McResolver.h +++ b/launcher/ui/pages/instance/McResolver.h @@ -1,8 +1,8 @@ +#include +#include +#include #include #include -#include -#include -#include // resolve the IP and port of a Minecraft server class McResolver : public QObject { @@ -11,17 +11,17 @@ class McResolver : public QObject { QString m_constrDomain; int m_constrPort; -public: - explicit McResolver(QObject *parent, QString domain, int port); + public: + explicit McResolver(QObject* parent, QString domain, int port); void ping(); -private: + private: void pingWithDomainSRV(QString domain, int port); void pingWithDomainA(QString domain, int port); void emitFail(QString error); void emitSucceed(QString ip, int port); -signals: + signals: void succeeded(QString ip, int port); void failed(QString error); void finished(); diff --git a/launcher/ui/pages/instance/ServerPingTask.cpp b/launcher/ui/pages/instance/ServerPingTask.cpp index 3ec9308ca..b39f3d117 100644 --- a/launcher/ui/pages/instance/ServerPingTask.cpp +++ b/launcher/ui/pages/instance/ServerPingTask.cpp @@ -1,47 +1,41 @@ #include -#include "ServerPingTask.h" -#include "McResolver.h" -#include "McClient.h" #include +#include "McClient.h" +#include "McResolver.h" +#include "ServerPingTask.h" -unsigned getOnlinePlayers(QJsonObject data) { +unsigned getOnlinePlayers(QJsonObject data) +{ return Json::requireInteger(Json::requireObject(data, "players"), "online"); } -void ServerPingTask::executeTask() { +void ServerPingTask::executeTask() +{ qDebug() << "Querying status of " << QString("%1:%2").arg(m_domain).arg(m_port); // Resolve the actual IP and port for the server - McResolver *resolver = new McResolver(nullptr, m_domain, m_port); + McResolver* resolver = new McResolver(nullptr, m_domain, m_port); QObject::connect(resolver, &McResolver::succeeded, this, [this, resolver](QString ip, int port) { qDebug() << "Resolved Address for" << m_domain << ": " << ip << ":" << port; // Now that we have the IP and port, query the server - McClient *client = new McClient(nullptr, m_domain, ip, port); + McClient* client = new McClient(nullptr, m_domain, ip, port); QObject::connect(client, &McClient::succeeded, this, [this](QJsonObject data) { m_outputOnlinePlayers = getOnlinePlayers(data); qDebug() << "Online players: " << m_outputOnlinePlayers; emitSucceeded(); }); - QObject::connect(client, &McClient::failed, this, [this](QString error) { - emitFailed(error); - }); + QObject::connect(client, &McClient::failed, this, [this](QString error) { emitFailed(error); }); // Delete McClient object when done - QObject::connect(client, &McClient::finished, this, [this, client]() { - client->deleteLater(); - }); + QObject::connect(client, &McClient::finished, this, [this, client]() { client->deleteLater(); }); client->getStatusData(); }); - QObject::connect(resolver, &McResolver::failed, this, [this](QString error) { - emitFailed(error); - }); + QObject::connect(resolver, &McResolver::failed, this, [this](QString error) { emitFailed(error); }); // Delete McResolver object when done - QObject::connect(resolver, &McResolver::finished, [resolver]() { - resolver->deleteLater(); - }); + QObject::connect(resolver, &McResolver::finished, [resolver]() { resolver->deleteLater(); }); resolver->ping(); } \ No newline at end of file diff --git a/launcher/ui/pages/instance/ServerPingTask.h b/launcher/ui/pages/instance/ServerPingTask.h index 0956a4f63..6f03b92ad 100644 --- a/launcher/ui/pages/instance/ServerPingTask.h +++ b/launcher/ui/pages/instance/ServerPingTask.h @@ -5,18 +5,17 @@ #include - class ServerPingTask : public Task { Q_OBJECT - public: + public: explicit ServerPingTask(QString domain, int port) : Task(), m_domain(domain), m_port(port) {} ~ServerPingTask() override = default; int m_outputOnlinePlayers = -1; - private: + private: QString m_domain; int m_port; - protected: + protected: virtual void executeTask() override; }; diff --git a/launcher/ui/themes/HintOverrideProxyStyle.cpp b/launcher/ui/themes/HintOverrideProxyStyle.cpp index f31969fce..f5b8232a8 100644 --- a/launcher/ui/themes/HintOverrideProxyStyle.cpp +++ b/launcher/ui/themes/HintOverrideProxyStyle.cpp @@ -18,7 +18,8 @@ #include "HintOverrideProxyStyle.h" -HintOverrideProxyStyle::HintOverrideProxyStyle(QStyle* style) : QProxyStyle(style) { +HintOverrideProxyStyle::HintOverrideProxyStyle(QStyle* style) : QProxyStyle(style) +{ setObjectName(style->objectName()); } From 8bb79cefacef4616304eae958dcef5b8281b0d23 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 29 Apr 2025 10:47:00 +0300 Subject: [PATCH 109/171] chore: add format commits to the git-blame-ignore Signed-off-by: Trial97 --- .git-blame-ignore-revs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 528b128b1..c7d36db27 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -5,3 +5,9 @@ bbb3b3e6f6e3c0f95873f22e6d0a4aaf350f49d9 # (nix) alejandra -> nixfmt 4c81d8c53d09196426568c4a31a4e752ed05397a + +# reformat codebase +1d468ac35ad88d8c77cc83f25e3704d9bd7df01b + +# format a part of codebase +5c8481a118c8fefbfe901001d7828eaf6866eac4 From 147159be2c98a334a88a8f3fcda42de1bdda7e7c Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 29 Apr 2025 19:02:35 +0300 Subject: [PATCH 110/171] fix: file filtering on modpack export Signed-off-by: Trial97 --- launcher/FileIgnoreProxy.cpp | 4 ++-- launcher/FileIgnoreProxy.h | 2 +- launcher/MMCZip.cpp | 6 +++--- launcher/MMCZip.h | 3 ++- launcher/modplatform/flame/FlamePackExportTask.cpp | 2 +- launcher/modplatform/flame/FlamePackExportTask.h | 4 ++-- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 2 +- launcher/modplatform/modrinth/ModrinthPackExportTask.h | 4 ++-- 8 files changed, 14 insertions(+), 13 deletions(-) diff --git a/launcher/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp index 0314057d1..cebe82eda 100644 --- a/launcher/FileIgnoreProxy.cpp +++ b/launcher/FileIgnoreProxy.cpp @@ -269,9 +269,9 @@ bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath())); } -bool FileIgnoreProxy::filterFile(const QString& fileName) const +bool FileIgnoreProxy::filterFile(const QFileInfo& file) const { - return m_blocked.covers(fileName) || ignoreFile(QFileInfo(QDir(m_root), fileName)); + return m_blocked.covers(relPath(file.absoluteFilePath())) || ignoreFile(file); } void FileIgnoreProxy::loadBlockedPathsFromFile(const QString& fileName) diff --git a/launcher/FileIgnoreProxy.h b/launcher/FileIgnoreProxy.h index 25d85ab60..5184fc354 100644 --- a/launcher/FileIgnoreProxy.h +++ b/launcher/FileIgnoreProxy.h @@ -69,7 +69,7 @@ class FileIgnoreProxy : public QSortFilterProxyModel { // list of relative paths that need to be removed completely from model inline SeparatorPrefixTree<'/'>& ignoreFilesWithPath() { return m_ignoreFilePaths; } - bool filterFile(const QString& fileName) const; + bool filterFile(const QFileInfo& fileName) const; void loadBlockedPathsFromFile(const QString& fileName); diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index b38aca17a..0b1a2b39e 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -418,7 +418,7 @@ bool extractFile(QString fileCompressed, QString file, QString target) return extractRelFile(&zip, file, target); } -bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter) +bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter) { QDir rootDirectory(rootDir); if (!rootDirectory.exists()) @@ -443,8 +443,8 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q // collect files entries = directory.entryInfoList(QDir::Files); for (const auto& e : entries) { - QString relativeFilePath = rootDirectory.relativeFilePath(e.absoluteFilePath()); - if (excludeFilter && excludeFilter(relativeFilePath)) { + if (excludeFilter && excludeFilter(e)) { + QString relativeFilePath = rootDirectory.relativeFilePath(e.absoluteFilePath()); qDebug() << "Skipping file " << relativeFilePath; continue; } diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index d81df9d81..fe0c79de2 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -56,6 +56,7 @@ namespace MMCZip { using FilterFunction = std::function; +using FilterFileFunction = std::function; /** * Merge two zip files, using a filter function @@ -149,7 +150,7 @@ bool extractFile(QString fileCompressed, QString file, QString dir); * \param excludeFilter function to excludeFilter which files shouldn't be included (returning true means to excude) * \return true for success or false for failure */ -bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter); +bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter); #if defined(LAUNCHER_APPLICATION) class ExportToZipTask : public Task { diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index 3405b702f..4e1a3722d 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -47,7 +47,7 @@ FlamePackExportTask::FlamePackExportTask(const QString& name, bool optionalFiles, InstancePtr instance, const QString& output, - MMCZip::FilterFunction filter) + MMCZip::FilterFileFunction filter) : name(name) , version(version) , author(author) diff --git a/launcher/modplatform/flame/FlamePackExportTask.h b/launcher/modplatform/flame/FlamePackExportTask.h index b11eb17fa..38acdf518 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.h +++ b/launcher/modplatform/flame/FlamePackExportTask.h @@ -34,7 +34,7 @@ class FlamePackExportTask : public Task { bool optionalFiles, InstancePtr instance, const QString& output, - MMCZip::FilterFunction filter); + MMCZip::FilterFileFunction filter); protected: void executeTask() override; @@ -51,7 +51,7 @@ class FlamePackExportTask : public Task { MinecraftInstance* mcInstance; const QDir gameRoot; const QString output; - const MMCZip::FilterFunction filter; + const MMCZip::FilterFileFunction filter; struct ResolvedFile { int addonId; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index d103170af..8e582e456 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -40,7 +40,7 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, bool optionalFiles, InstancePtr instance, const QString& output, - MMCZip::FilterFunction filter) + MMCZip::FilterFileFunction filter) : name(name) , version(version) , summary(summary) diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index ee740a456..ec4730de5 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -35,7 +35,7 @@ class ModrinthPackExportTask : public Task { bool optionalFiles, InstancePtr instance, const QString& output, - MMCZip::FilterFunction filter); + MMCZip::FilterFileFunction filter); protected: void executeTask() override; @@ -58,7 +58,7 @@ class ModrinthPackExportTask : public Task { MinecraftInstance* mcInstance; const QDir gameRoot; const QString output; - const MMCZip::FilterFunction filter; + const MMCZip::FilterFileFunction filter; ModrinthAPI api; QFileInfoList files; From 053b57c21ff813e70915668ed29d80c16f0a43c2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 29 Apr 2025 19:24:04 +0300 Subject: [PATCH 111/171] fix: crash when task was canceled and abort signal was fired early Signed-off-by: Trial97 --- launcher/InstanceImportTask.cpp | 5 ++--- launcher/java/download/ArchiveDownloadTask.cpp | 2 +- launcher/modplatform/flame/FlamePackExportTask.cpp | 4 +++- launcher/modplatform/modrinth/ModrinthPackExportTask.cpp | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 633382404..e2735385b 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -72,7 +72,6 @@ bool InstanceImportTask::abort() bool wasAborted = false; if (m_task) wasAborted = m_task->abort(); - Task::abort(); return wasAborted; } @@ -305,7 +304,7 @@ void InstanceImportTask::processFlame() connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus); connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails); - connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort); + connect(inst_creation_task.get(), &Task::aborted, this, &InstanceImportTask::emitAborted); connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable); m_task.reset(inst_creation_task); @@ -404,7 +403,7 @@ void InstanceImportTask::processModrinth() connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus); connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails); - connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort); + connect(inst_creation_task.get(), &Task::aborted, this, &InstanceImportTask::emitAborted); connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable); m_task.reset(inst_creation_task); diff --git a/launcher/java/download/ArchiveDownloadTask.cpp b/launcher/java/download/ArchiveDownloadTask.cpp index bb7cc568d..bb31ca1e2 100644 --- a/launcher/java/download/ArchiveDownloadTask.cpp +++ b/launcher/java/download/ArchiveDownloadTask.cpp @@ -55,6 +55,7 @@ void ArchiveDownloadTask::executeTask() connect(download.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress); connect(download.get(), &Task::status, this, &ArchiveDownloadTask::setStatus); connect(download.get(), &Task::details, this, &ArchiveDownloadTask::setDetails); + connect(download.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted); connect(download.get(), &Task::succeeded, [this, fullPath] { // This should do all of the extracting and creating folders extractJava(fullPath); @@ -135,7 +136,6 @@ bool ArchiveDownloadTask::abort() auto aborted = canAbort(); if (m_task) aborted = m_task->abort(); - emitAborted(); return aborted; }; } // namespace Java \ No newline at end of file diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index 3405b702f..08b01e1e9 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -70,7 +70,6 @@ bool FlamePackExportTask::abort() { if (task) { task->abort(); - emitAborted(); return true; } return false; @@ -171,6 +170,7 @@ void FlamePackExportTask::collectHashes() progressStep->status = status; stepProgress(*progressStep); }); + connect(hashingTask.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted); hashingTask->start(); } @@ -246,6 +246,7 @@ void FlamePackExportTask::makeApiRequest() getProjectsInfo(); }); connect(task.get(), &Task::failed, this, &FlamePackExportTask::getProjectsInfo); + connect(task.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted); task->start(); } @@ -324,6 +325,7 @@ void FlamePackExportTask::getProjectsInfo() buildZip(); }); connect(projTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed); + connect(task.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted); task.reset(projTask); task->start(); } diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index d103170af..a03ae2122 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -63,7 +63,6 @@ bool ModrinthPackExportTask::abort() { if (task) { task->abort(); - emitAborted(); return true; } return false; @@ -158,6 +157,7 @@ void ModrinthPackExportTask::makeApiRequest() task = api.currentVersions(pendingHashes.values(), "sha512", response); connect(task.get(), &Task::succeeded, [this, response]() { parseApiResponse(response); }); connect(task.get(), &Task::failed, this, &ModrinthPackExportTask::emitFailed); + connect(task.get(), &Task::aborted, this, &ModrinthPackExportTask::emitAborted); task->start(); } } From 9131f79cc0bb967ddca85564b74cabc6e074124b Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Tue, 8 Apr 2025 14:59:33 -0400 Subject: [PATCH 112/171] feat: add CMakePresets.json Signed-off-by: Seth Flynn --- CMakePresets.json | 14 +++ cmake/commonPresets.json | 70 +++++++++++++++ cmake/linuxPreset.json | 95 +++++++++++++++++++++ cmake/macosPreset.json | 152 +++++++++++++++++++++++++++++++++ cmake/windowsMSVCPreset.json | 155 ++++++++++++++++++++++++++++++++++ cmake/windowsMinGWPreset.json | 99 ++++++++++++++++++++++ 6 files changed, 585 insertions(+) create mode 100644 CMakePresets.json create mode 100644 cmake/commonPresets.json create mode 100644 cmake/linuxPreset.json create mode 100644 cmake/macosPreset.json create mode 100644 cmake/windowsMSVCPreset.json create mode 100644 cmake/windowsMinGWPreset.json diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 000000000..f8e688b89 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json", + "version": 8, + "cmakeMinimumRequired": { + "major": 3, + "minor": 28 + }, + "include": [ + "cmake/linuxPreset.json", + "cmake/macosPreset.json", + "cmake/windowsMinGWPreset.json", + "cmake/windowsMSVCPreset.json" + ] +} diff --git a/cmake/commonPresets.json b/cmake/commonPresets.json new file mode 100644 index 000000000..7acbf8a31 --- /dev/null +++ b/cmake/commonPresets.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json", + "version": 8, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "binaryDir": "build", + "installDir": "install", + "cacheVariables": { + "Launcher_BUILD_PLATFORM": "custom" + } + }, + { + "name": "base_debug", + "hidden": true, + "inherits": [ + "base" + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "base_release", + "hidden": true, + "inherits": [ + "base" + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "ENABLE_LTO": "ON" + } + } + ], + "testPresets": [ + { + "name": "base", + "hidden": true, + "output": { + "outputOnFailure": true + }, + "execution": { + "noTestsAction": "error" + }, + "filter": { + "exclude": { + "name": "^example64|example$" + } + } + }, + { + "name": "base_debug", + "hidden": true, + "inherits": [ + "base" + ], + "output": { + "debug": true + } + }, + { + "name": "base_release", + "hidden": true, + "inherits": [ + "base" + ] + } + ] +} diff --git a/cmake/linuxPreset.json b/cmake/linuxPreset.json new file mode 100644 index 000000000..81ae95c2c --- /dev/null +++ b/cmake/linuxPreset.json @@ -0,0 +1,95 @@ +{ + "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json", + "version": 8, + "include": [ + "commonPresets.json" + ], + "configurePresets": [ + { + "name": "linux_base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "generator": "Ninja", + "cacheVariables": { + "Launcher_BUILD_ARTIFACT": "Linux-Qt6", + "Launcher_ENABLE_JAVA_DOWNLOADER": "ON" + } + }, + { + "name": "linux_debug", + "inherits": [ + "base_debug", + "linux_base" + ], + "displayName": "Linux (Debug)" + }, + { + "name": "linux_release", + "inherits": [ + "base_release", + "linux_base" + ], + "displayName": "Linux (Release)" + } + ], + "buildPresets": [ + { + "name": "linux_base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "linux_debug", + "inherits": [ + "linux_base" + ], + "displayName": "Linux (Debug)", + "configurePreset": "linux_debug" + }, + { + "name": "linux_release", + "inherits": [ + "linux_base" + ], + "displayName": "Linux (Release)", + "configurePreset": "linux_release" + } + ], + "testPresets": [ + { + "name": "linux_base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "linux_debug", + "inherits": [ + "base_debug", + "linux_base" + ], + "displayName": "Linux (Debug)", + "configurePreset": "linux_debug" + }, + { + "name": "linux_release", + "inherits": [ + "base_release", + "linux_base" + ], + "displayName": "Linux (Release)", + "configurePreset": "linux_release" + } + ] +} diff --git a/cmake/macosPreset.json b/cmake/macosPreset.json new file mode 100644 index 000000000..8cecfbe71 --- /dev/null +++ b/cmake/macosPreset.json @@ -0,0 +1,152 @@ +{ + "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json", + "version": 8, + "include": [ + "commonPresets.json" + ], + "configurePresets": [ + { + "name": "macos_base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "generator": "Ninja" + }, + { + "name": "macos_universal_base", + "hidden": true, + "inherits": [ + "macos_base" + ], + "cacheVariables": { + "CMAKE_OSX_ARCHITECTURES": "x86_64;arm64", + "Launcher_BUILD_ARTIFACT": "macOS-Qt6" + } + }, + { + "name": "macos_debug", + "inherits": [ + "base_debug", + "macos_base" + ], + "displayName": "macOS (Debug)" + }, + { + "name": "macos_release", + "inherits": [ + "base_release", + "macos_base" + ], + "displayName": "macOS (Release)" + }, + { + "name": "macos_universal_debug", + "inherits": [ + "base_debug", + "macos_universal_base" + ], + "displayName": "macOS (Universal Binary, Debug)" + }, + { + "name": "macos_universal_release", + "inherits": [ + "base_release", + "macos_universal_base" + ], + "displayName": "macOS (Universal Binary, Release)" + } + ], + "buildPresets": [ + { + "name": "macos_base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + }, + { + "name": "macos_debug", + "inherits": [ + "macos_base" + ], + "displayName": "macOS (Debug)", + "configurePreset": "macos_debug" + }, + { + "name": "macos_release", + "inherits": [ + "macos_base" + ], + "displayName": "macOS (Release)", + "configurePreset": "macos_release" + }, + { + "name": "macos_universal_debug", + "inherits": [ + "macos_base" + ], + "displayName": "macOS (Universal Binary, Debug)", + "configurePreset": "macos_universal_debug" + }, + { + "name": "macos_universal_release", + "inherits": [ + "macos_base" + ], + "displayName": "macOS (Universal Binary, Release)", + "configurePreset": "macos_universal_release" + } + ], + "testPresets": [ + { + "name": "macos_base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + }, + { + "name": "macos_debug", + "inherits": [ + "base_debug", + "macos_base" + ], + "displayName": "MacOS (Debug)", + "configurePreset": "macos_debug" + }, + { + "name": "macos_release", + "inherits": [ + "base_release", + "macos_base" + ], + "displayName": "macOS (Release)", + "configurePreset": "macos_release" + }, + { + "name": "macos_universal_debug", + "inherits": [ + "base_debug", + "macos_base" + ], + "displayName": "MacOS (Universal Binary, Debug)", + "configurePreset": "macos_universal_debug" + }, + { + "name": "macos_universal_release", + "inherits": [ + "base_release", + "macos_base" + ], + "displayName": "macOS (Universal Binary, Release)", + "configurePreset": "macos_universal_release" + } + ] +} diff --git a/cmake/windowsMSVCPreset.json b/cmake/windowsMSVCPreset.json new file mode 100644 index 000000000..f7e9d09ec --- /dev/null +++ b/cmake/windowsMSVCPreset.json @@ -0,0 +1,155 @@ +{ + "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json", + "version": 8, + "include": [ + "commonPresets.json" + ], + "configurePresets": [ + { + "name": "windows_msvc_base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "cacheVariables": { + "Launcher_BUILD_ARTIFACT": "Windows-MSVC-Qt6" + } + }, + { + "name": "windows_msvc_arm64_cross_base", + "hidden": true, + "inherits": [ + "windows_msvc_base" + ], + "architecture": "arm64", + "cacheVariables": { + "Launcher_BUILD_ARTIFACT": "Windows-MSVC-arm64-Qt6" + } + }, + { + "name": "windows_msvc_debug", + "inherits": [ + "base_debug", + "windows_msvc_base" + ], + "displayName": "Windows MSVC (Debug)", + "generator": "Ninja" + }, + { + "name": "windows_msvc_release", + "inherits": [ + "base_release", + "windows_msvc_base" + ], + "displayName": "Windows MSVC (Release)" + }, + { + "name": "windows_msvc_arm64_cross_debug", + "inherits": [ + "base_debug", + "windows_msvc_arm64_cross_base" + ], + "displayName": "Windows MSVC (ARM64 cross, Debug)" + }, + { + "name": "windows_msvc_arm64_cross_release", + "inherits": [ + "base_release", + "windows_msvc_arm64_cross_base" + ], + "displayName": "Windows MSVC (ARM64 cross, Release)" + } + ], + "buildPresets": [ + { + "name": "windows_msvc_base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "windows_msvc_debug", + "inherits": [ + "windows_msvc_base" + ], + "displayName": "Windows MSVC (Debug)", + "configurePreset": "windows_msvc_debug", + "configuration": "Debug" + }, + { + "name": "windows_msvc_release", + "inherits": [ + "windows_msvc_base" + ], + "displayName": "Windows MSVC (Release)", + "configurePreset": "windows_msvc_release", + "configuration": "Release", + "nativeToolOptions": [ + "/p:UseMultiToolTask=true", + "/p:EnforceProcessCountAcrossBuilds=true" + ] + }, + { + "name": "windows_msvc_arm64_cross_debug", + "inherits": [ + "windows_msvc_base" + ], + "displayName": "Windows MSVC (ARM64 cross, Debug)", + "configurePreset": "windows_msvc_arm64_cross_debug", + "configuration": "Debug", + "nativeToolOptions": [ + "/p:UseMultiToolTask=true", + "/p:EnforceProcessCountAcrossBuilds=true" + ] + }, + { + "name": "windows_msvc_arm64_cross_release", + "inherits": [ + "windows_msvc_base" + ], + "displayName": "Windows MSVC (ARM64 cross, Release)", + "configurePreset": "windows_msvc_arm64_cross_release", + "configuration": "Release", + "nativeToolOptions": [ + "/p:UseMultiToolTask=true", + "/p:EnforceProcessCountAcrossBuilds=true" + ] + } + ], + "testPresets": [ + { + "name": "windows_msvc_base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "windows_msvc_debug", + "inherits": [ + "base_debug", + "windows_msvc_base" + ], + "displayName": "Windows MSVC (Debug)", + "configurePreset": "windows_msvc_debug", + "configuration": "Debug" + }, + { + "name": "windows_msvc_release", + "inherits": [ + "base_release", + "windows_msvc_base" + ], + "displayName": "Windows MSVC (Release)", + "configurePreset": "windows_msvc_release", + "configuration": "Release" + } + ] +} diff --git a/cmake/windowsMinGWPreset.json b/cmake/windowsMinGWPreset.json new file mode 100644 index 000000000..40273f81b --- /dev/null +++ b/cmake/windowsMinGWPreset.json @@ -0,0 +1,99 @@ +{ + "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json", + "version": 8, + "include": [ + "commonPresets.json" + ], + "configurePresets": [ + { + "name": "windows_mingw_base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "generator": "Ninja", + "cacheVariables": { + "Launcher_BUILD_ARTIFACT": "Windows-MinGW-w64-Qt6" + } + }, + { + "name": "windows_mingw_debug", + "inherits": [ + "base_debug", + "windows_mingw_base" + ], + "displayName": "Windows MinGW (Debug)" + }, + { + "name": "windows_mingw_release", + "inherits": [ + "base_release", + "windows_mingw_base" + ], + "displayName": "Windows MinGW (Release)" + } + ], + "buildPresets": [ + { + "name": "windows_mingw_base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "windows_mingw_debug", + "inherits": [ + "windows_mingw_base" + ], + "displayName": "Windows MinGW (Debug)", + "configurePreset": "windows_mingw_debug" + }, + { + "name": "windows_mingw_release", + "inherits": [ + "windows_mingw_base" + ], + "displayName": "Windows MinGW (Release)", + "configurePreset": "windows_mingw_release" + } + ], + "testPresets": [ + { + "name": "windows_mingw_base", + "hidden": true, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "filter": { + "exclude": { + "name": "^example64|example$" + } + } + }, + { + "name": "windows_mingw_debug", + "inherits": [ + "base_debug", + "windows_mingw_base" + ], + "displayName": "Windows MinGW (Debug)", + "configurePreset": "windows_mingw_debug" + }, + { + "name": "windows_mingw_release", + "inherits": [ + "base_release", + "windows_mingw_base" + ], + "displayName": "Windows MinGW (Release)", + "configurePreset": "windows_mingw_release" + } + ] +} From 70500af2a2029fd783a80131a47e000bf24dfbb4 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Tue, 8 Apr 2025 14:59:54 -0400 Subject: [PATCH 113/171] ci: use cmake workflow presets Signed-off-by: Seth Flynn --- .github/workflows/build.yml | 89 ++++++------------- .github/workflows/codeql.yml | 5 +- cmake/commonPresets.json | 11 +++ cmake/linuxPreset.json | 85 ++++++++++++++++++ cmake/macosPreset.json | 120 ++++++++++++++++++++++++++ cmake/windowsMSVCPreset.json | 156 ++++++++++++++++++++++++++++++++++ cmake/windowsMinGWPreset.json | 84 ++++++++++++++++++ 7 files changed, 484 insertions(+), 66 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 952b7c515..6172dc3ae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,6 +53,7 @@ jobs: matrix: include: - os: ubuntu-22.04 + cmake_preset: linux qt_ver: 6 qt_host: linux qt_arch: "" @@ -64,11 +65,13 @@ jobs: - os: windows-2022 name: "Windows-MinGW-w64" + cmake_preset: windows_mingw msystem: clang64 vcvars_arch: "amd64_x86" - os: windows-2022 name: "Windows-MSVC" + cmake_preset: windows_msvc msystem: "" architecture: "x64" vcvars_arch: "amd64" @@ -82,6 +85,7 @@ jobs: - os: windows-2022 name: "Windows-MSVC-arm64" + cmake_preset: windows_msvc_arm64_cross msystem: "" architecture: "arm64" vcvars_arch: "amd64_arm64" @@ -95,6 +99,7 @@ jobs: - os: macos-14 name: macOS + cmake_preset: ${{ inputs.build_type == 'Debug' && 'macos_universal' || 'macos' }} macosx_deployment_target: 11.0 qt_ver: 6 qt_host: mac @@ -275,75 +280,30 @@ jobs: with: distribution: "temurin" java-version: "17" + ## - # CONFIGURE + # SOURCE BUILD ## - - name: Configure CMake (macOS) - if: runner.os == 'macOS' + - name: Run CMake workflow + if: ${{ runner.os != 'Windows' || matrix.msystem == '' }} + shell: bash + env: + CMAKE_PRESET: ${{ matrix.cmake_preset }} + PRESET_TYPE: ${{ inputs.build_type == 'Debug' && 'debug' || 'ci' }} run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja + cmake --workflow --preset "$CMAKE_PRESET"_"$PRESET_TYPE" - - name: Configure CMake (Windows MinGW-w64) - if: runner.os == 'Windows' && matrix.msystem != '' + # NOTE: Split due to the `shell` requirement for msys2 + # TODO(@getchoo): Get ccache working! + - name: Run CMake workflow (Windows MinGW-w64) + if: ${{ runner.os == 'Windows' && matrix.msystem != '' }} shell: msys2 {0} + env: + CMAKE_PRESET: ${{ matrix.cmake_preset }} + PRESET_TYPE: ${{ inputs.build_type == 'Debug' && 'debug' || 'ci' }} run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja - - - name: Configure CMake (Windows MSVC) - if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64' - run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja - - - name: Configure CMake (Windows MSVC arm64) - if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture == 'arm64' - run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} - - - name: Configure CMake (Linux) - if: runner.os == 'Linux' - run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -G Ninja - - ## - # BUILD - ## - - - name: Build - if: runner.os != 'Windows' - run: | - cmake --build ${{ env.BUILD_DIR }} - - - name: Build (Windows MinGW-w64) - if: runner.os == 'Windows' && matrix.msystem != '' - shell: msys2 {0} - run: | - cmake --build ${{ env.BUILD_DIR }} - - - name: Build (Windows MSVC) - if: runner.os == 'Windows' && matrix.msystem == '' - run: | - cmake --build ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }} - - ## - # TEST - ## - - - name: Test - if: runner.os != 'Windows' - run: | - ctest -E "^example64|example$" --test-dir build --output-on-failure - - - name: Test (Windows MinGW-w64) - if: runner.os == 'Windows' && matrix.msystem != '' - shell: msys2 {0} - run: | - ctest -E "^example64|example$" --test-dir build --output-on-failure - - - name: Test (Windows MSVC) - if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64' - run: | - ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }} + cmake --workflow --preset "$CMAKE_PRESET"_"$PRESET_TYPE" ## # PACKAGE BUILDS @@ -542,8 +502,11 @@ jobs: - name: Package (Linux, portable) if: runner.os == 'Linux' + env: + CMAKE_PRESET: ${{ matrix.cmake_preset }} + PRESET_TYPE: ${{ inputs.build_type == 'Debug' && 'debug' || 'ci' }} run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_PORTABLE_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -DINSTALL_BUNDLE=full -G Ninja + cmake --preset "$CMAKE_PRESET"_"$PRESET_TYPE" -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_PORTABLE_DIR }} -DINSTALL_BUNDLE=full cmake --install ${{ env.BUILD_DIR }} cmake --install ${{ env.BUILD_DIR }} --component portable diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a5ac537f1..285125185 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -71,9 +71,8 @@ jobs: - name: Configure and Build run: | - cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -G Ninja - - cmake --build build + cmake --preset linux_debug + cmake --build --preset linux_debug - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 diff --git a/cmake/commonPresets.json b/cmake/commonPresets.json index 7acbf8a31..9cdf51649 100644 --- a/cmake/commonPresets.json +++ b/cmake/commonPresets.json @@ -31,6 +31,17 @@ "CMAKE_BUILD_TYPE": "Release", "ENABLE_LTO": "ON" } + }, + { + "name": "base_ci", + "hidden": true, + "inherits": [ + "base_release" + ], + "cacheVariables": { + "Launcher_BUILD_PLATFORM": "official", + "Launcher_FORCE_BUNDLED_LIBS": "ON" + } } ], "testPresets": [ diff --git a/cmake/linuxPreset.json b/cmake/linuxPreset.json index 81ae95c2c..b8bfe4ff0 100644 --- a/cmake/linuxPreset.json +++ b/cmake/linuxPreset.json @@ -34,6 +34,18 @@ "linux_base" ], "displayName": "Linux (Release)" + }, + { + "name": "linux_ci", + "inherits": [ + "base_ci", + "linux_base" + ], + "displayName": "Linux (CI)", + "cacheVariables": { + "Launcher_BUILD_ARTIFACT": "Linux-Qt6" + }, + "installDir": "/usr" } ], "buildPresets": [ @@ -61,6 +73,14 @@ ], "displayName": "Linux (Release)", "configurePreset": "linux_release" + }, + { + "name": "linux_ci", + "inherits": [ + "linux_base" + ], + "displayName": "Linux (CI)", + "configurePreset": "linux_ci" } ], "testPresets": [ @@ -90,6 +110,71 @@ ], "displayName": "Linux (Release)", "configurePreset": "linux_release" + }, + { + "name": "linux_ci", + "inherits": [ + "base_release", + "linux_base" + ], + "displayName": "Linux (CI)", + "configurePreset": "linux_ci" + } + ], + "workflowPresets": [ + { + "name": "linux_debug", + "displayName": "Linux (Debug)", + "steps": [ + { + "type": "configure", + "name": "linux_debug" + }, + { + "type": "build", + "name": "linux_debug" + }, + { + "type": "test", + "name": "linux_debug" + } + ] + }, + { + "name": "linux", + "displayName": "Linux (Release)", + "steps": [ + { + "type": "configure", + "name": "linux_release" + }, + { + "type": "build", + "name": "linux_release" + }, + { + "type": "test", + "name": "linux_release" + } + ] + }, + { + "name": "linux_ci", + "displayName": "Linux (CI)", + "steps": [ + { + "type": "configure", + "name": "linux_ci" + }, + { + "type": "build", + "name": "linux_ci" + }, + { + "type": "test", + "name": "linux_ci" + } + ] } ] } diff --git a/cmake/macosPreset.json b/cmake/macosPreset.json index 8cecfbe71..726949934 100644 --- a/cmake/macosPreset.json +++ b/cmake/macosPreset.json @@ -57,6 +57,17 @@ "macos_universal_base" ], "displayName": "macOS (Universal Binary, Release)" + }, + { + "name": "macos_ci", + "inherits": [ + "base_ci", + "macos_universal_base" + ], + "displayName": "macOS (CI)", + "cacheVariables": { + "Launcher_BUILD_ARTIFACT": "macOS-Qt6" + } } ], "buildPresets": [ @@ -100,6 +111,14 @@ ], "displayName": "macOS (Universal Binary, Release)", "configurePreset": "macos_universal_release" + }, + { + "name": "macos_ci", + "inherits": [ + "macos_base" + ], + "displayName": "macOS (CI)", + "configurePreset": "macos_ci" } ], "testPresets": [ @@ -147,6 +166,107 @@ ], "displayName": "macOS (Universal Binary, Release)", "configurePreset": "macos_universal_release" + }, + { + "name": "macos_ci", + "inherits": [ + "base_release", + "macos_base" + ], + "displayName": "macOS (CI)", + "configurePreset": "macos_ci" + } + ], + "workflowPresets": [ + { + "name": "macos_debug", + "displayName": "macOS (Debug)", + "steps": [ + { + "type": "configure", + "name": "macos_debug" + }, + { + "type": "build", + "name": "macos_debug" + }, + { + "type": "test", + "name": "macos_debug" + } + ] + }, + { + "name": "macos", + "displayName": "macOS (Release)", + "steps": [ + { + "type": "configure", + "name": "macos_release" + }, + { + "type": "build", + "name": "macos_release" + }, + { + "type": "test", + "name": "macos_release" + } + ] + }, + { + "name": "macos_universal_debug", + "displayName": "macOS (Universal Binary, Debug)", + "steps": [ + { + "type": "configure", + "name": "macos_universal_debug" + }, + { + "type": "build", + "name": "macos_universal_debug" + }, + { + "type": "test", + "name": "macos_universal_debug" + } + ] + }, + { + "name": "macos_universal", + "displayName": "macOS (Universal Binary, Release)", + "steps": [ + { + "type": "configure", + "name": "macos_universal_release" + }, + { + "type": "build", + "name": "macos_universal_release" + }, + { + "type": "test", + "name": "macos_universal_release" + } + ] + }, + { + "name": "macos_ci", + "displayName": "macOS (CI)", + "steps": [ + { + "type": "configure", + "name": "macos_ci" + }, + { + "type": "build", + "name": "macos_ci" + }, + { + "type": "test", + "name": "macos_ci" + } + ] } ] } diff --git a/cmake/windowsMSVCPreset.json b/cmake/windowsMSVCPreset.json index f7e9d09ec..eb6a38b19 100644 --- a/cmake/windowsMSVCPreset.json +++ b/cmake/windowsMSVCPreset.json @@ -60,6 +60,28 @@ "windows_msvc_arm64_cross_base" ], "displayName": "Windows MSVC (ARM64 cross, Release)" + }, + { + "name": "windows_msvc_ci", + "inherits": [ + "base_ci", + "windows_msvc_base" + ], + "displayName": "Windows MSVC (CI)", + "cacheVariables": { + "Launcher_BUILD_ARTIFACT": "Windows-MSVC-Qt6" + } + }, + { + "name": "windows_msvc_arm64_cross_ci", + "inherits": [ + "base_ci", + "windows_msvc_arm64_cross_base" + ], + "displayName": "Windows MSVC (ARM64 cross, CI)", + "cacheVariables": { + "Launcher_BUILD_ARTIFACT": "Windows-MSVC-arm64-Qt6" + } } ], "buildPresets": [ @@ -119,6 +141,32 @@ "/p:UseMultiToolTask=true", "/p:EnforceProcessCountAcrossBuilds=true" ] + }, + { + "name": "windows_msvc_ci", + "inherits": [ + "windows_msvc_base" + ], + "displayName": "Windows MSVC (CI)", + "configurePreset": "windows_msvc_ci", + "configuration": "Release", + "nativeToolOptions": [ + "/p:UseMultiToolTask=true", + "/p:EnforceProcessCountAcrossBuilds=true" + ] + }, + { + "name": "windows_msvc_arm64_cross_ci", + "inherits": [ + "windows_msvc_base" + ], + "displayName": "Windows MSVC (ARM64 cross, CI)", + "configurePreset": "windows_msvc_arm64_cross_ci", + "configuration": "Release", + "nativeToolOptions": [ + "/p:UseMultiToolTask=true", + "/p:EnforceProcessCountAcrossBuilds=true" + ] } ], "testPresets": [ @@ -150,6 +198,114 @@ "displayName": "Windows MSVC (Release)", "configurePreset": "windows_msvc_release", "configuration": "Release" + }, + { + "name": "windows_msvc_ci", + "inherits": [ + "base_release", + "windows_msvc_base" + ], + "displayName": "Windows MSVC (CI)", + "configurePreset": "windows_msvc_ci", + "configuration": "Release" + } + ], + "workflowPresets": [ + { + "name": "windows_msvc_debug", + "displayName": "Windows MSVC (Debug)", + "steps": [ + { + "type": "configure", + "name": "windows_msvc_debug" + }, + { + "type": "build", + "name": "windows_msvc_debug" + }, + { + "type": "test", + "name": "windows_msvc_debug" + } + ] + }, + { + "name": "windows_msvc", + "displayName": "Windows MSVC (Release)", + "steps": [ + { + "type": "configure", + "name": "windows_msvc_release" + }, + { + "type": "build", + "name": "windows_msvc_release" + }, + { + "type": "test", + "name": "windows_msvc_release" + } + ] + }, + { + "name": "windows_msvc_arm64_cross_debug", + "displayName": "Windows MSVC (ARM64 cross, Debug)", + "steps": [ + { + "type": "configure", + "name": "windows_msvc_arm64_cross_debug" + }, + { + "type": "build", + "name": "windows_msvc_arm64_cross_debug" + } + ] + }, + { + "name": "windows_msvc_arm64_cross", + "displayName": "Windows MSVC (ARM64 cross, Release)", + "steps": [ + { + "type": "configure", + "name": "windows_msvc_arm64_cross_release" + }, + { + "type": "build", + "name": "windows_msvc_arm64_cross_release" + } + ] + }, + { + "name": "windows_msvc_ci", + "displayName": "Windows MSVC (CI)", + "steps": [ + { + "type": "configure", + "name": "windows_msvc_ci" + }, + { + "type": "build", + "name": "windows_msvc_ci" + }, + { + "type": "test", + "name": "windows_msvc_ci" + } + ] + }, + { + "name": "windows_msvc_arm64_cross_ci", + "displayName": "Windows MSVC (ARM64 cross, CI)", + "steps": [ + { + "type": "configure", + "name": "windows_msvc_arm64_cross_ci" + }, + { + "type": "build", + "name": "windows_msvc_arm64_cross_ci" + } + ] } ] } diff --git a/cmake/windowsMinGWPreset.json b/cmake/windowsMinGWPreset.json index 40273f81b..984caadd6 100644 --- a/cmake/windowsMinGWPreset.json +++ b/cmake/windowsMinGWPreset.json @@ -33,6 +33,17 @@ "windows_mingw_base" ], "displayName": "Windows MinGW (Release)" + }, + { + "name": "windows_mingw_ci", + "inherits": [ + "base_ci", + "windows_mingw_base" + ], + "displayName": "Windows MinGW (CI)", + "cacheVariables": { + "Launcher_BUILD_ARTIFACT": "Windows-MinGW-w64-Qt6" + } } ], "buildPresets": [ @@ -60,6 +71,14 @@ ], "displayName": "Windows MinGW (Release)", "configurePreset": "windows_mingw_release" + }, + { + "name": "windows_mingw_ci", + "inherits": [ + "windows_mingw_base" + ], + "displayName": "Windows MinGW (CI)", + "configurePreset": "windows_mingw_ci" } ], "testPresets": [ @@ -94,6 +113,71 @@ ], "displayName": "Windows MinGW (Release)", "configurePreset": "windows_mingw_release" + }, + { + "name": "windows_mingw_ci", + "inherits": [ + "base_release", + "windows_mingw_base" + ], + "displayName": "Windows MinGW (CI)", + "configurePreset": "windows_mingw_ci" + } + ], + "workflowPresets": [ + { + "name": "windows_mingw_debug", + "displayName": "Windows MinGW (Debug)", + "steps": [ + { + "type": "configure", + "name": "windows_mingw_debug" + }, + { + "type": "build", + "name": "windows_mingw_debug" + }, + { + "type": "test", + "name": "windows_mingw_debug" + } + ] + }, + { + "name": "windows_mingw", + "displayName": "Windows MinGW (Release)", + "steps": [ + { + "type": "configure", + "name": "windows_mingw_release" + }, + { + "type": "build", + "name": "windows_mingw_release" + }, + { + "type": "test", + "name": "windows_mingw_release" + } + ] + }, + { + "name": "windows_mingw_ci", + "displayName": "Windows MinGW (CI)", + "steps": [ + { + "type": "configure", + "name": "windows_mingw_ci" + }, + { + "type": "build", + "name": "windows_mingw_ci" + }, + { + "type": "test", + "name": "windows_mingw_ci" + } + ] } ] } From 6c45ff915aa7a5b190d3f0e03bd40ed1c18d16c5 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 28 Apr 2025 13:22:54 -0400 Subject: [PATCH 114/171] chore(gitignore): add CMakeUserPresets.json Signed-off-by: Seth Flynn --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b563afbc7..00afabbfa 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ CMakeLists.txt.user.* CMakeSettings.json /CMakeFiles CMakeCache.txt +CMakeUserPresets.json /.project /.settings /.idea From b438236a6467605feabc10266a13602079a797e4 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Tue, 29 Apr 2025 09:15:07 -0400 Subject: [PATCH 115/171] ci: use symlink for ccache when possible Signed-off-by: Seth Flynn --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6172dc3ae..5de1c44fb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -159,6 +159,7 @@ jobs: if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' uses: hendrikmuhs/ccache-action@v1.2.18 with: + create-symlink: ${{ runner.os != 'Windows' }} key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} - name: Use ccache on Debug builds only From a465af45dc62904ea7b874aea2534b4c4ce4effd Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 30 Apr 2025 10:15:59 +0300 Subject: [PATCH 116/171] fix: task typo in flame export task Signed-off-by: Trial97 --- launcher/modplatform/flame/FlamePackExportTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index 98cdfffc9..c9a35f3cf 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -325,7 +325,7 @@ void FlamePackExportTask::getProjectsInfo() buildZip(); }); connect(projTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed); - connect(task.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted); + connect(projTask.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted); task.reset(projTask); task->start(); } From dc3a8dcfed67d3e3a7808fe06fe7e7b6bfff7300 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Tue, 29 Apr 2025 23:48:42 -0400 Subject: [PATCH 117/171] ci: only run on specific paths This avoids the previously applied paths-ignore exception workaround, and makes runs as strict as (reasonably) possible. Only directories known to affect builds will trigger builds, as well as any `.cpp` or `.h` files to account for any new folders created - though these should still be added to the workflow later Signed-off-by: Seth Flynn --- .github/workflows/codeql.yml | 59 ++++++++++++++----------- .github/workflows/flatpak.yml | 61 ++++++++++++++++---------- .github/workflows/nix.yml | 64 +++++++++++++++++++--------- .github/workflows/trigger_builds.yml | 61 +++++++++++++++----------- 4 files changed, 152 insertions(+), 93 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a5ac537f1..5ce0741dd 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,37 +2,48 @@ name: "CodeQL Code Scanning" on: push: - # NOTE: `!` doesn't work with `paths-ignore` :( - # So we a catch-all glob instead - # https://github.com/orgs/community/discussions/25369#discussioncomment-3247674 paths: - - "**" - - "!.github/**" - - ".github/workflows/codeql.yml" - - "!flatpak/" - - "!nix/" - - "!scripts/" + # File types + - "**.cpp" + - "**.h" + - "**.java" - - "!.git*" - - "!.envrc" - - "!**.md" + # Directories + - "buildconfig/" + - "cmake/" + - "launcher/" + - "libraries/" + - "program_info/" + - "tests/" + + # Files + - "CMakeLists.txt" - "COPYING.md" - - "!renovate.json" + + # Workflows + - ".github/codeql" + - ".github/workflows/codeql.yml" pull_request: - # See above paths: - - "**" - - "!.github/**" - - ".github/workflows/codeql.yml" - - "!flatpak/" - - "!nix/" - - "!scripts/" + # File types + - "**.cpp" + - "**.h" - - "!.git*" - - "!.envrc" - - "!**.md" + # Directories + - "buildconfig/" + - "cmake/" + - "launcher/" + - "libraries/" + - "program_info/" + - "tests/" + + # Files + - "CMakeLists.txt" - "COPYING.md" - - "!renovate.json" + + # Workflows + - ".github/codeql" + - ".github/workflows/codeql.yml" workflow_dispatch: jobs: diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index 8caba46fa..cab0edeb7 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -5,35 +5,52 @@ on: # We don't do anything with these artifacts on releases. They go to Flathub tags-ignore: - "*" - # NOTE: `!` doesn't work with `paths-ignore` :( - # So we a catch-all glob instead - # https://github.com/orgs/community/discussions/25369#discussioncomment-3247674 paths: - - "**" - - "!.github/**" - - ".github/workflows/flatpak.yml" - - "!nix/" - - "!scripts/" + # File types + - "**.cpp" + - "**.h" + - "**.java" - - "!.git*" - - "!.envrc" - - "!**.md" + # Build files + - "flatpak/" + + # Directories + - "buildconfig/" + - "cmake/" + - "launcher/" + - "libraries/" + - "program_info/" + - "tests/" + + # Files + - "CMakeLists.txt" - "COPYING.md" - - "!renovate.json" + + # Workflows + - ".github/workflows/flatpak.yml" pull_request: - # See above paths: - - "**" - - "!.github/**" - - ".github/workflows/flatpak.yml" - - "!nix/" - - "!scripts/" + # File types + - "**.cpp" + - "**.h" - - "!.git*" - - "!.envrc" - - "!**.md" + # Build files + - "flatpak/" + + # Directories + - "buildconfig/" + - "cmake/" + - "launcher/" + - "libraries/" + - "program_info/" + - "tests/" + + # Files + - "CMakeLists.txt" - "COPYING.md" - - "!renovate.json" + + # Workflows + - ".github/workflows/flatpak.yml" workflow_dispatch: permissions: diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 75ef7c65a..5a40ebb1f 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -4,34 +4,56 @@ on: push: tags: - "*" - # NOTE: `!` doesn't work with `paths-ignore` :( - # So we a catch-all glob instead - # https://github.com/orgs/community/discussions/25369#discussioncomment-3247674 paths: - - "**" - - "!.github/**" - - ".github/workflows/nix.yml" - - "!flatpak/" - - "!scripts/" + # File types + - "**.cpp" + - "**.h" + - "**.java" - - "!.git*" - - "!.envrc" - - "!**.md" + # Build files + - "**.nix" + - "nix/" + - "flake.lock" + + # Directories + - "buildconfig/" + - "cmake/" + - "launcher/" + - "libraries/" + - "program_info/" + - "tests/" + + # Files + - "CMakeLists.txt" - "COPYING.md" - - "!renovate.json" + + # Workflows + - ".github/workflows/nix.yml" pull_request_target: paths: - - "**" - - "!.github/**" - - ".github/workflows/nix.yml" - - "!flatpak/" - - "!scripts/" + # File types + - "**.cpp" + - "**.h" - - "!.git*" - - "!.envrc" - - "!**.md" + # Build files + - "**.nix" + - "nix/" + - "flake.lock" + + # Directories + - "buildconfig/" + - "cmake/" + - "launcher/" + - "libraries/" + - "program_info/" + - "tests/" + + # Files + - "CMakeLists.txt" - "COPYING.md" - - "!renovate.json" + + # Workflows + - ".github/workflows/nix.yml" workflow_dispatch: permissions: diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml index e4c90ef0b..4be03f46f 100644 --- a/.github/workflows/trigger_builds.yml +++ b/.github/workflows/trigger_builds.yml @@ -4,39 +4,48 @@ on: push: branches-ignore: - "renovate/**" - # NOTE: `!` doesn't work with `paths-ignore` :( - # So we a catch-all glob instead - # https://github.com/orgs/community/discussions/25369#discussioncomment-3247674 paths: - - "**" - - "!.github/**" + # File types + - "**.cpp" + - "**.h" + - "**.java" + + # Directories + - "buildconfig/" + - "cmake/" + - "launcher/" + - "libraries/" + - "program_info/" + - "tests/" + + # Files + - "CMakeLists.txt" + - "COPYING.md" + + # Workflows - ".github/workflows/build.yml" - ".github/workflows/trigger_builds.yml" - - "!flatpak/" - - "!nix/" - - "!scripts/" - - - "!.git*" - - "!.envrc" - - "!**.md" - - "COPYING.md" - - "!renovate.json" pull_request: - # See above paths: - - "**" - - "!.github/**" + # File types + - "**.cpp" + - "**.h" + + # Directories + - "buildconfig/" + - "cmake/" + - "launcher/" + - "libraries/" + - "program_info/" + - "tests/" + + # Files + - "CMakeLists.txt" + - "COPYING.md" + + # Workflows - ".github/workflows/build.yml" - ".github/workflows/trigger_builds.yml" - - "!flatpak/" - - "!nix/" - - "!scripts/" - - - "!.git*" - - "!.envrc" - - "!**.md" - - "COPYING.md" - - "!renovate.json" workflow_dispatch: jobs: From eb911389f855881fee9983d002fc434db7aa685a Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Wed, 30 Apr 2025 02:45:23 -0700 Subject: [PATCH 118/171] Distinguish between stacked and blocked pr distinguish between stacked and blocked pr stacks need merge blocks just need a close Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked-prs.yml | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index bd49b7230..a8b2a3fe6 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -125,6 +125,7 @@ jobs: "type": $type, "number": .number, "merged": .merged, + "state": if .state == "open" then "Open" elif .merged then "Merged" else "Closed" end, "labels": (reduce .labels[].name as $l ([]; . + [$l])), "basePrUrl": .html_url, "baseRepoName": .head.repo.name, @@ -138,11 +139,16 @@ jobs: ) { echo "data=$blocked_pr_data"; - echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")"; - echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )"; + echo "all_merged=$(jq -r 'all(.[] | (.type == "Stacked on" and .merged) or (.type == "Blocked on" and (.state != "Open")); .)' <<< "$blocked_pr_data")"; + echo "current_blocking=$(jq -c 'map( + select( + (.type == "Stacked on" and (.merged | not)) or + (.type == "Blocked on" and (.state == "Open")) + ) | .number + )' <<< "$blocked_pr_data" )"; } >> "$GITHUB_OUTPUT" - - name: Add 'blocked' Label is Missing + - name: Add 'blocked' Label if Missing id: label_blocked if: (fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0) && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true @@ -184,14 +190,18 @@ jobs: # create commit Status, overwrites previous identical context while read -r pr_data ; do DESC=$( - jq -r ' "Blocking PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"' <<< "$pr_data" + jq -r 'if .type == "Stacked on" then + "Stacked PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged" + else + "Blocking PR #" + (.number | tostring) + " is " + (if .state == "Open" then "" else "not yet " end) + "merged or closed" + end ' <<< "$pr_data" ) gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ "/repos/${OWNER}/${REPO}/statuses/${pr_head_sha}" \ - -f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$pr_data")" \ + -f "state=$(jq -r 'if (.type == "Stacked on" and .merged) or (.type == "Blocked on" and (.state != "Open")) then "success" else "failure" end' <<< "$pr_data")" \ -f "target_url=$(jq -r '.basePrUrl' <<< "$pr_data" )" \ -f "description=$DESC" \ -f "context=ci/blocking-pr-check:$(jq '.number' <<< "$pr_data")" @@ -214,7 +224,13 @@ jobs: base_repo_owner=$(jq -r '.baseRepoOwner' <<< "$pr_data") base_repo_name=$(jq -r '.baseRepoName' <<< "$pr_data") compare_url="https://github.com/$base_repo_owner/$base_repo_name/compare/$base_ref_name...$pr_head_label" - status=$(jq -r 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$pr_data") + status=$(jq -r ' + if .type == "Stacked on" then + if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged (" + .state + ")" end + else + if .state != "Open" then ":white_check_mark: " + .state else ":x: Open" end + end + ' <<< "$pr_data") type=$(jq -r '.type' <<< "$pr_data") echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH" done < <(jq -c '.[]' <<< "$BLOCKING_DATA") From 19d69994554edf7027a3a7fe591956a85b75480a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 16:35:22 +0000 Subject: [PATCH 119/171] chore(deps): update cachix/install-nix-action digest to 5261181 --- .github/workflows/update-flake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index f4b1c4f5d..62852171b 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@754537aaedb35f72ab11a60cc162c49ef3016495 # v31 + - uses: cachix/install-nix-action@526118121621777ccd86f79b04685a9319637641 # v31 - uses: DeterminateSystems/update-flake-lock@v24 with: From 2dfb674e443b5510e6a6c48a5ce9ec249d6b9bcd Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Tue, 29 Apr 2025 01:07:34 -0400 Subject: [PATCH 120/171] ci: split build workflow into composite actions Signed-off-by: Seth Flynn --- .github/actions/package/linux/action.yml | 124 ++++ .github/actions/package/macos/action.yml | 121 ++++ .github/actions/package/windows/action.yml | 143 ++++ .github/actions/setup-dependencies/action.yml | 67 ++ .../setup-dependencies/linux/action.yml | 26 + .../setup-dependencies/macos/action.yml | 16 + .../setup-dependencies/windows/action.yml | 85 +++ .github/workflows/build.yml | 615 +++--------------- .github/workflows/trigger_builds.yml | 16 +- .github/workflows/trigger_release.yml | 16 +- 10 files changed, 672 insertions(+), 557 deletions(-) create mode 100644 .github/actions/package/linux/action.yml create mode 100644 .github/actions/package/macos/action.yml create mode 100644 .github/actions/package/windows/action.yml create mode 100644 .github/actions/setup-dependencies/action.yml create mode 100644 .github/actions/setup-dependencies/linux/action.yml create mode 100644 .github/actions/setup-dependencies/macos/action.yml create mode 100644 .github/actions/setup-dependencies/windows/action.yml diff --git a/.github/actions/package/linux/action.yml b/.github/actions/package/linux/action.yml new file mode 100644 index 000000000..b71e62592 --- /dev/null +++ b/.github/actions/package/linux/action.yml @@ -0,0 +1,124 @@ +name: Package for Linux +description: Create Linux packages for Prism Launcher + +inputs: + version: + description: Launcher version + required: true + build-type: + description: Type for the build + required: true + default: Debug + artifact-name: + description: Name of the uploaded artifact + required: true + default: Linux + cmake-preset: + description: Base CMake preset previously used for the build + required: true + default: linux + qt-version: + description: Version of Qt to use + required: true + gpg-private-key: + description: Private key for AppImage signing + required: false + gpg-private-key-id: + description: ID for the gpg-private-key, to select the signing key + required: false + +runs: + using: composite + + steps: + - name: Package AppImage + shell: bash + env: + VERSION: ${{ inputs.version }} + BUILD_DIR: build + INSTALL_APPIMAGE_DIR: install-appdir + + GPG_PRIVATE_KEY: ${{ inputs.gpg-private-key }} + run: | + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr + + mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml + export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated + + export OUTPUT="PrismLauncher-Linux-x86_64.AppImage" + + chmod +x linuxdeploy-*.AppImage + + mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib + mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines + + cp -r ${{ runner.workspace }}/Qt/${{ inputs.qt-version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines + + cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ + cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ + cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ + + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" + export LD_LIBRARY_PATH + + chmod +x AppImageUpdate-x86_64.AppImage + cp AppImageUpdate-x86_64.AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin + + export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync" + + if [ '${{ inputs.gpg-private-key-id }}' != '' ]; then + export SIGN=1 + export SIGN_KEY=${{ inputs.gpg-private-key-id }} + mkdir -p ~/.gnupg/ + echo "$GPG_PRIVATE_KEY" > ~/.gnupg/private.key + gpg --import ~/.gnupg/private.key + else + echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY + fi + + ./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg + + mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build-type }}-x86_64.AppImage" + + - name: Package portable tarball + shell: bash + env: + BUILD_DIR: build + + CMAKE_PRESET: ${{ inputs.cmake-preset }} + + INSTALL_PORTABLE_DIR: install-portable + run: | + cmake --preset "$CMAKE_PRESET" -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_PORTABLE_DIR }} -DINSTALL_BUNDLE=full + cmake --install ${{ env.BUILD_DIR }} + cmake --install ${{ env.BUILD_DIR }} --component portable + + mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib + cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib + cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib + cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib + cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib + cp /usr/lib/x86_64-linux-gnu/libffi.so.*.* ${{ env.INSTALL_PORTABLE_DIR }}/lib + mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib + + for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt + cd ${{ env.INSTALL_PORTABLE_DIR }} + tar -czf ../PrismLauncher-portable.tar.gz * + + - name: Upload binary tarball + uses: actions/upload-artifact@v4 + with: + name: PrismLauncher-${{ inputs.artifact-name }}-Qt6-Portable-${{ inputs.version }}-${{ inputs.build-type }} + path: PrismLauncher-portable.tar.gz + + - name: Upload AppImage + uses: actions/upload-artifact@v4 + with: + name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }}-x86_64.AppImage + path: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-x86_64.AppImage + + - name: Upload AppImage Zsync + uses: actions/upload-artifact@v4 + with: + name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }}-x86_64.AppImage.zsync + path: PrismLauncher-Linux-x86_64.AppImage.zsync diff --git a/.github/actions/package/macos/action.yml b/.github/actions/package/macos/action.yml new file mode 100644 index 000000000..42181953c --- /dev/null +++ b/.github/actions/package/macos/action.yml @@ -0,0 +1,121 @@ +name: Package for macOS +description: Create a macOS package for Prism Launcher + +inputs: + version: + description: Launcher version + required: true + build-type: + description: Type for the build + required: true + default: Debug + artifact-name: + description: Name of the uploaded artifact + required: true + default: macOS + apple-codesign-cert: + description: Certificate for signing macOS builds + required: false + apple-codesign-password: + description: Password for signing macOS builds + required: false + apple-codesign-id: + description: Certificate ID for signing macOS builds + required: false + apple-notarize-apple-id: + description: Apple ID used for notarizing macOS builds + required: false + apple-notarize-team-id: + description: Team ID used for notarizing macOS builds + required: false + apple-notarize-password: + description: Password used for notarizing macOS builds + required: false + sparkle-ed25519-key: + description: Private key for signing Sparkle updates + required: false + +runs: + using: composite + + steps: + - name: Fetch codesign certificate + shell: bash + run: | + echo '${{ inputs.apple-codesign-cert }}' | base64 --decode > codesign.p12 + if [ -n '${{ inputs.apple-codesign-id }}' ]; then + security create-keychain -p '${{ inputs.apple-codesign-password }}' build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p '${{ inputs.apple-codesign-password }}' build.keychain + security import codesign.p12 -k build.keychain -P '${{ inputs.apple-codesign-password }}' -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k '${{ inputs.apple-codesign-password }}' build.keychain + else + echo ":warning: Using ad-hoc code signing for macOS, as certificate was not present." >> $GITHUB_STEP_SUMMARY + fi + + - name: Package + shell: bash + env: + BUILD_DIR: build + INSTALL_DIR: install + run: | + cmake --install ${{ env.BUILD_DIR }} + + cd ${{ env.INSTALL_DIR }} + chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher" + + if [ -n '${{ inputs.apple-codesign-id }}' ]; then + APPLE_CODESIGN_ID='${{ inputs.apple-codesign-id }}' + ENTITLEMENTS_FILE='../program_info/App.entitlements' + else + APPLE_CODESIGN_ID='-' + ENTITLEMENTS_FILE='../program_info/AdhocSignedApp.entitlements' + fi + + sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher" + mv "PrismLauncher.app" "Prism Launcher.app" + + - name: Notarize + shell: bash + env: + INSTALL_DIR: install + run: | + cd ${{ env.INSTALL_DIR }} + + if [ -n '${{ inputs.apple-notarize-password }}' ]; then + ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip + xcrun notarytool submit ../PrismLauncher.zip \ + --wait --progress \ + --apple-id '${{ inputs.apple-notarize-apple-id }}' \ + --team-id '${{ inputs.apple-notarize-team-id }}' \ + --password '${{ inputs.apple-notarize-password }}' + + xcrun stapler staple "Prism Launcher.app" + else + echo ":warning: Skipping notarization as credentials are not present." >> $GITHUB_STEP_SUMMARY + fi + ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip + + - name: Make Sparkle signature + shell: bash + run: | + if [ '${{ inputs.sparkle-ed25519-key }}' != '' ]; then + echo '${{ inputs.sparkle-ed25519-key }}' > ed25519-priv.pem + signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n) + rm ed25519-priv.pem + cat >> $GITHUB_STEP_SUMMARY << EOF + ### Artifact Information :information_source: + - :memo: Sparkle Signature (ed25519): \`$signature\` + EOF + else + cat >> $GITHUB_STEP_SUMMARY << EOF + ### Artifact Information :information_source: + - :warning: Sparkle Signature (ed25519): No private key available (likely a pull request or fork) + EOF + fi + + - name: Upload binary tarball + uses: actions/upload-artifact@v4 + with: + name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }} + path: PrismLauncher.zip diff --git a/.github/actions/package/windows/action.yml b/.github/actions/package/windows/action.yml new file mode 100644 index 000000000..60b2c75d1 --- /dev/null +++ b/.github/actions/package/windows/action.yml @@ -0,0 +1,143 @@ +name: Package for Windows +description: Create a Windows package for Prism Launcher + +inputs: + version: + description: Launcher version + required: true + build-type: + description: Type for the build + required: true + default: Debug + artifact-name: + description: Name of the uploaded artifact + required: true + msystem: + description: MSYS2 subsystem to use + required: true + default: false + windows-codesign-cert: + description: Certificate for signing Windows builds + required: false + windows-codesign-password: + description: Password for signing Windows builds + required: false + +runs: + using: composite + + steps: + - name: Package (MinGW) + if: ${{ inputs.msystem != '' }} + shell: msys2 {0} + env: + BUILD_DIR: build + INSTALL_DIR: install + run: | + cmake --install ${{ env.BUILD_DIR }} + touch ${{ env.INSTALL_DIR }}/manifest.txt + for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt + + - name: Package (MSVC) + if: ${{ inputs.msystem == '' }} + shell: pwsh + env: + BUILD_DIR: build + INSTALL_DIR: install + run: | + cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build-type }} + + cd ${{ github.workspace }} + + Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt + + - name: Fetch codesign certificate + shell: bash # yes, we are not using MSYS2 or PowerShell here + run: | + echo '${{ inputs.windows-codesign-cert }}' | base64 --decode > codesign.pfx + + - name: Sign executable + shell: pwsh + env: + INSTALL_DIR: install + run: | + if (Get-Content ./codesign.pfx){ + cd ${{ env.INSTALL_DIR }} + # We ship the exact same executable for portable and non-portable editions, so signing just once is fine + SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ inputs.windows-codesign-password }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_updater.exe prismlauncher_filelink.exe + } else { + ":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY + } + + - name: Package (MinGW, portable) + if: ${{ inputs.msystem != '' }} + shell: msys2 {0} + env: + BUILD_DIR: build + INSTALL_DIR: install + INSTALL_PORTABLE_DIR: install-portable + run: | + cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable + for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt + + - name: Package (MSVC, portable) + if: ${{ inputs.msystem == '' }} + shell: pwsh + env: + BUILD_DIR: build + INSTALL_DIR: install + INSTALL_PORTABLE_DIR: install-portable + run: | + cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable + + Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt + + - name: Package (installer) + shell: pwsh + env: + BUILD_DIR: build + INSTALL_DIR: install + + NSCURL_VERSION: "v24.9.26.122" + NSCURL_SHA256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0" + run: | + New-Item -Name NSISPlugins -ItemType Directory + Invoke-Webrequest https://github.com/negrutiu/nsis-nscurl/releases/download/"${{ env.NSCURL_VERSION }}"/NScurl.zip -OutFile NSISPlugins\NScurl.zip + $nscurl_hash = Get-FileHash NSISPlugins\NScurl.zip -Algorithm Sha256 | Select-Object -ExpandProperty Hash + if ( $nscurl_hash -ne "${{ env.nscurl_sha256 }}") { + echo "::error:: NSCurl.zip sha256 mismatch" + exit 1 + } + Expand-Archive -Path NSISPlugins\NScurl.zip -DestinationPath NSISPlugins\NScurl + + cd ${{ env.INSTALL_DIR }} + makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi" + + - name: Sign installer + shell: pwsh + run: | + if (Get-Content ./codesign.pfx){ + SignTool sign /fd sha256 /td sha256 /f codesign.pfx /p '${{ inputs.windows-codesign-password }}' /tr http://timestamp.digicert.com PrismLauncher-Setup.exe + } else { + ":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY + } + + - name: Upload binary zip + uses: actions/upload-artifact@v4 + with: + name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }} + path: install/** + + - name: Upload portable zip + uses: actions/upload-artifact@v4 + with: + name: PrismLauncher-${{ inputs.artifact-name }}-Portable-${{ inputs.version }}-${{ inputs.build-type }} + path: install-portable/** + + - name: Upload installer + uses: actions/upload-artifact@v4 + with: + name: PrismLauncher-${{ inputs.artifact-name }}-Setup-${{ inputs.version }}-${{ inputs.build-type }} + path: PrismLauncher-Setup.exe diff --git a/.github/actions/setup-dependencies/action.yml b/.github/actions/setup-dependencies/action.yml new file mode 100644 index 000000000..e583989b0 --- /dev/null +++ b/.github/actions/setup-dependencies/action.yml @@ -0,0 +1,67 @@ +name: Setup Dependencies +description: Install and setup dependencies for building Prism Launcher + +inputs: + build-type: + description: Type for the build + required: true + default: Debug + msystem: + description: MSYS2 subsystem to use + required: false + vcvars-arch: + description: Visual Studio architecture to use + required: false + qt-architecture: + description: Qt architecture + required: false + qt-version: + description: Version of Qt to use + required: true + default: 6.8.1 + +outputs: + build-type: + description: Type of build used + value: ${{ inputs.build-type }} + qt-version: + description: Version of Qt used + value: ${{ inputs.qt-version }} + +runs: + using: composite + + steps: + - name: Setup Linux dependencies + if: ${{ runner.os == 'Linux' }} + uses: ./.github/actions/setup-dependencies/linux + + - name: Setup macOS dependencies + if: ${{ runner.os == 'macOS' }} + uses: ./.github/actions/setup-dependencies/macos + + - name: Setup Windows dependencies + if: ${{ runner.os == 'Windows' }} + uses: ./.github/actions/setup-dependencies/windows + with: + build-type: ${{ inputs.build-type }} + msystem: ${{ inputs.msystem }} + vcvars-arch: ${{ inputs.vcvars-arch }} + + # TODO(@getchoo): Get this working on MSYS2! + - name: Setup ccache + if: ${{ (runner.os != 'Windows' || inputs.msystem == '') && inputs.build-type == 'Debug' }} + uses: hendrikmuhs/ccache-action@v1.2.17 + with: + create-symlink: ${{ runner.os != 'Windows' }} + key: ${{ runner.os }}-qt${{ inputs.qt_ver }}-${{ inputs.architecture }} + + - name: Install Qt + if: ${{ inputs.msystem == '' }} + uses: jurplel/install-qt-action@v4 + with: + aqtversion: "==3.1.*" + version: ${{ inputs.qt-version }} + arch: ${{ inputs.qt-architecture }} + modules: qt5compat qtimageformats qtnetworkauth + cache: ${{ inputs.build-type == 'Debug' }} diff --git a/.github/actions/setup-dependencies/linux/action.yml b/.github/actions/setup-dependencies/linux/action.yml new file mode 100644 index 000000000..dd0d28364 --- /dev/null +++ b/.github/actions/setup-dependencies/linux/action.yml @@ -0,0 +1,26 @@ +name: Setup Linux dependencies + +runs: + using: composite + + steps: + - name: Install host dependencies + shell: bash + run: | + sudo apt-get -y update + sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev + + - name: Setup AppImage tooling + shell: bash + run: | + declare -A appimage_deps + appimage_deps["https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage"]="4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage" + appimage_deps["https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage"]="15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage" + appimage_deps["https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage"]="f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage" + + for url in "${!appimage_deps[@]}"; do + curl -LO "$url" + sha256sum -c - <<< "${appimage_deps[$url]}" + done + + sudo apt -y install libopengl0 diff --git a/.github/actions/setup-dependencies/macos/action.yml b/.github/actions/setup-dependencies/macos/action.yml new file mode 100644 index 000000000..dcbb308c2 --- /dev/null +++ b/.github/actions/setup-dependencies/macos/action.yml @@ -0,0 +1,16 @@ +name: Setup macOS dependencies + +runs: + using: composite + + steps: + - name: Install dependencies + shell: bash + run: | + brew update + brew install ninja extra-cmake-modules temurin@17 + + - name: Set JAVA_HOME + shell: bash + run: | + echo "JAVA_HOME=$(/usr/libexec/java_home -v 17)" >> "$GITHUB_ENV" diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml new file mode 100644 index 000000000..782d02348 --- /dev/null +++ b/.github/actions/setup-dependencies/windows/action.yml @@ -0,0 +1,85 @@ +name: Setup Windows Dependencies + +inputs: + build-type: + description: Type for the build + required: true + default: Debug + msystem: + description: MSYS2 subsystem to use + required: false + vcvars-arch: + description: Visual Studio architecture to use + required: true + default: amd64 + +runs: + using: composite + + steps: + # NOTE: Installed on MinGW as well for SignTool + - name: Enter VS Developer shell + if: ${{ runner.os == 'Windows' }} + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ inputs.vcvars-arch }} + vsversion: 2022 + + - name: Setup MSYS2 (MinGW-64) + if: ${{ inputs.msystem != '' }} + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ inputs.msystem }} + update: true + install: >- + git + mingw-w64-x86_64-binutils + pacboy: >- + toolchain:p + cmake:p + extra-cmake-modules:p + ninja:p + qt6-base:p + qt6-svg:p + qt6-imageformats:p + quazip-qt6:p + ccache:p + qt6-5compat:p + qt6-networkauth:p + cmark:p + + - name: Force newer ccache (MSVC) + if: ${{ inputs.msystem == '' && inputs.build-type == 'Debug' }} + shell: bash + run: | + choco install ccache --version 4.7.1 + + - name: Configure ccache (MSVC) + if: ${{ inputs.msystem == '' && inputs.build-type == 'Debug' }} + shell: pwsh + run: | + # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) + Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe + echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV + echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV + echo "TrackFileAccess=false" >> $env:GITHUB_ENV + # Needed for ccache, but also speeds up compile + echo "UseMultiToolTask=true" >> $env:GITHUB_ENV + + - name: Retrieve ccache cache (MinGW) + if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }} + uses: actions/cache@v4.2.3 + with: + path: '${{ github.workspace }}\.ccache' + key: ${{ runner.os }}-mingw-w64-ccache-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-mingw-w64-ccache + + - name: Setup ccache (MinGW) + if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }} + shell: msys2 {0} + run: | + ccache --set-config=cache_dir='${{ github.workspace }}\.ccache' + ccache --set-config=max_size='500M' + ccache --set-config=compression=true + ccache -p # Show config diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5de1c44fb..3408cf624 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,581 +3,138 @@ name: Build on: workflow_call: inputs: - build_type: - description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel) + build-type: + description: Type of build (Debug or Release) type: string default: Debug - is_qt_cached: - description: Enable Qt caching or not - type: string - default: true - secrets: - SPARKLE_ED25519_KEY: - description: Private key for signing Sparkle updates - required: false - WINDOWS_CODESIGN_CERT: - description: Certificate for signing Windows builds - required: false - WINDOWS_CODESIGN_PASSWORD: - description: Password for signing Windows builds - required: false - APPLE_CODESIGN_CERT: - description: Certificate for signing macOS builds - required: false - APPLE_CODESIGN_PASSWORD: - description: Password for signing macOS builds - required: false - APPLE_CODESIGN_ID: - description: Certificate ID for signing macOS builds - required: false - APPLE_NOTARIZE_APPLE_ID: - description: Apple ID used for notarizing macOS builds - required: false - APPLE_NOTARIZE_TEAM_ID: - description: Team ID used for notarizing macOS builds - required: false - APPLE_NOTARIZE_PASSWORD: - description: Password used for notarizing macOS builds - required: false - GPG_PRIVATE_KEY: - description: Private key for AppImage signing - required: false - GPG_PRIVATE_KEY_ID: - description: ID for the GPG_PRIVATE_KEY, to select the signing key - required: false jobs: build: + name: Build (${{ matrix.artifact-name }}) + strategy: fail-fast: false matrix: include: - os: ubuntu-22.04 - cmake_preset: linux - qt_ver: 6 - qt_host: linux - qt_arch: "" - qt_version: "6.8.1" - qt_modules: "qt5compat qtimageformats qtnetworkauth" - linuxdeploy_hash: "4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage" - linuxdeploy_qt_hash: "15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage" - appimageupdate_hash: "f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage" + artifact-name: Linux + base-cmake-preset: linux - os: windows-2022 - name: "Windows-MinGW-w64" - cmake_preset: windows_mingw + artifact-name: Windows-MinGW-w64 + base-cmake-preset: windows_mingw msystem: clang64 - vcvars_arch: "amd64_x86" + vcvars-arch: amd64_x86 - os: windows-2022 - name: "Windows-MSVC" - cmake_preset: windows_msvc - msystem: "" - architecture: "x64" - vcvars_arch: "amd64" - qt_ver: 6 - qt_host: "windows" - qt_arch: "win64_msvc2022_64" - qt_version: "6.8.1" - qt_modules: "qt5compat qtimageformats qtnetworkauth" - nscurl_tag: "v24.9.26.122" - nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0" + artifact-name: Windows-MSVC + base-cmake-preset: windows_msvc + # TODO(@getchoo): This is the default in setup-dependencies/windows. Why isn't it working?!?! + vcvars-arch: amd64 - os: windows-2022 - name: "Windows-MSVC-arm64" - cmake_preset: windows_msvc_arm64_cross - msystem: "" - architecture: "arm64" - vcvars_arch: "amd64_arm64" - qt_ver: 6 - qt_host: "windows" - qt_arch: "win64_msvc2022_arm64_cross_compiled" - qt_version: "6.8.1" - qt_modules: "qt5compat qtimageformats qtnetworkauth" - nscurl_tag: "v24.9.26.122" - nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0" + artifact-name: Windows-MSVC-arm64 + base-cmake-preset: windows_msvc_arm64_cross + vcvars-arch: amd64_arm64 + qt-architecture: win64_msvc2022_arm64_cross_compiled - os: macos-14 - name: macOS - cmake_preset: ${{ inputs.build_type == 'Debug' && 'macos_universal' || 'macos' }} - macosx_deployment_target: 11.0 - qt_ver: 6 - qt_host: mac - qt_arch: "" - qt_version: "6.8.1" - qt_modules: "qt5compat qtimageformats qtnetworkauth" + artifact-name: macOS + base-cmake-preset: ${{ inputs.build-type == 'Debug' && 'macos_universal' || 'macos' }} + macosx-deployment-target: 11.0 runs-on: ${{ matrix.os }} + defaults: + run: + shell: ${{ matrix.msystem != '' && 'msys2 {0}' || 'bash' }} + env: - MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} - INSTALL_DIR: "install" - INSTALL_PORTABLE_DIR: "install-portable" - INSTALL_APPIMAGE_DIR: "install-appdir" - BUILD_DIR: "build" - CCACHE_VAR: "" - HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 + MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx-deployment-target }} steps: ## - # PREPARE + # SETUP ## + - name: Checkout uses: actions/checkout@v4 with: - submodules: "true" + submodules: true - - name: "Setup MSYS2" - if: runner.os == 'Windows' && matrix.msystem != '' - uses: msys2/setup-msys2@v2 + - name: Setup dependencies + id: setup-dependencies + uses: ./.github/actions/setup-dependencies with: + build-type: ${{ inputs.build-type || 'Debug' }} msystem: ${{ matrix.msystem }} - update: true - install: >- - git - mingw-w64-x86_64-binutils - pacboy: >- - toolchain:p - cmake:p - extra-cmake-modules:p - ninja:p - qt6-base:p - qt6-svg:p - qt6-imageformats:p - quazip-qt6:p - ccache:p - qt6-5compat:p - qt6-networkauth:p - cmark:p + vcvars-arch: ${{ matrix.vcvars-arch }} + qt-architecture: ${{ matrix.qt-architecture }} - - name: Force newer ccache - if: runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug' - run: | - choco install ccache --version 4.7.1 + ## + # BUILD + ## - - name: Setup ccache - if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' - uses: hendrikmuhs/ccache-action@v1.2.18 - with: - create-symlink: ${{ runner.os != 'Windows' }} - key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} - - - name: Use ccache on Debug builds only - if: inputs.build_type == 'Debug' - shell: bash - run: | - echo "CCACHE_VAR=ccache" >> $GITHUB_ENV - - - name: Retrieve ccache cache (Windows MinGW-w64) - if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' - uses: actions/cache@v4.2.3 - with: - path: '${{ github.workspace }}\.ccache' - key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} - restore-keys: | - ${{ matrix.os }}-mingw-w64-ccache - - - name: Setup ccache (Windows MinGW-w64) - if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' - shell: msys2 {0} - run: | - ccache --set-config=cache_dir='${{ github.workspace }}\.ccache' - ccache --set-config=max_size='500M' - ccache --set-config=compression=true - ccache -p # Show config - ccache -z # Zero stats - - - name: Configure ccache (Windows MSVC) - if: ${{ runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug' }} - run: | - # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) - Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe - echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV - echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV - echo "TrackFileAccess=false" >> $env:GITHUB_ENV - # Needed for ccache, but also speeds up compile - echo "UseMultiToolTask=true" >> $env:GITHUB_ENV - - - name: Set short version - shell: bash - run: | - ver_short=`git rev-parse --short HEAD` - echo "VERSION=$ver_short" >> $GITHUB_ENV - - - name: Install Dependencies (Linux) - if: runner.os == 'Linux' - run: | - sudo apt-get -y update - sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev - - - name: Install Dependencies (macOS) - if: runner.os == 'macOS' - run: | - brew update - brew install ninja extra-cmake-modules - - - name: Install host Qt (Windows MSVC arm64) - if: runner.os == 'Windows' && matrix.architecture == 'arm64' - uses: jurplel/install-qt-action@v4 - with: - aqtversion: "==3.1.*" - py7zrversion: ">=0.20.2" - version: ${{ matrix.qt_version }} - host: "windows" - target: "desktop" - arch: ${{ matrix.qt_arch }} - modules: ${{ matrix.qt_modules }} - cache: ${{ inputs.is_qt_cached }} - cache-key-prefix: host-qt-arm64-windows - dir: ${{ github.workspace }}\HostQt - set-env: false - - - name: Install Qt (macOS, Linux & Windows MSVC) - if: matrix.msystem == '' - uses: jurplel/install-qt-action@v4 - with: - aqtversion: "==3.1.*" - py7zrversion: ">=0.20.2" - version: ${{ matrix.qt_version }} - target: "desktop" - arch: ${{ matrix.qt_arch }} - modules: ${{ matrix.qt_modules }} - tools: ${{ matrix.qt_tools }} - cache: ${{ inputs.is_qt_cached }} - - - name: Install MSVC (Windows MSVC) - if: runner.os == 'Windows' # We want this for MinGW builds as well, as we need SignTool - uses: ilammy/msvc-dev-cmd@v1 - with: - vsversion: 2022 - arch: ${{ matrix.vcvars_arch }} - - - name: Prepare AppImage (Linux) - if: runner.os == 'Linux' + - name: Get CMake preset + id: cmake-preset env: - APPIMAGEUPDATE_HASH: ${{ matrix.appimageupdate_hash }} - LINUXDEPLOY_HASH: ${{ matrix.linuxdeploy_hash }} - LINUXDEPLOY_QT_HASH: ${{ matrix.linuxdeploy_qt_hash }} + BASE_CMAKE_PRESET: ${{ matrix.base-cmake-preset }} + PRESET_TYPE: ${{ inputs.build-type == 'Debug' && 'debug' || 'ci' }} run: | - wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage" - wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage" - - wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage" - - sha256sum -c - <<< "$LINUXDEPLOY_HASH" - sha256sum -c - <<< "$LINUXDEPLOY_QT_HASH" - sha256sum -c - <<< "$APPIMAGEUPDATE_HASH" - - sudo apt install libopengl0 - - - name: Add QT_HOST_PATH var (Windows MSVC arm64) - if: runner.os == 'Windows' && matrix.architecture == 'arm64' - run: | - echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2022_64" >> $env:GITHUB_ENV - - - name: Setup java (macOS) - if: runner.os == 'macOS' - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "17" - - ## - # SOURCE BUILD - ## + echo preset="$BASE_CMAKE_PRESET"_"$PRESET_TYPE" >> "$GITHUB_OUTPUT" - name: Run CMake workflow - if: ${{ runner.os != 'Windows' || matrix.msystem == '' }} + env: + CMAKE_PRESET: ${{ steps.cmake-preset.outputs.preset }} + run: | + cmake --workflow --preset "$CMAKE_PRESET" + + ## + # PACKAGE + ## + + - name: Get short version + id: short-version shell: bash - env: - CMAKE_PRESET: ${{ matrix.cmake_preset }} - PRESET_TYPE: ${{ inputs.build_type == 'Debug' && 'debug' || 'ci' }} run: | - cmake --workflow --preset "$CMAKE_PRESET"_"$PRESET_TYPE" + echo "version=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - # NOTE: Split due to the `shell` requirement for msys2 - # TODO(@getchoo): Get ccache working! - - name: Run CMake workflow (Windows MinGW-w64) - if: ${{ runner.os == 'Windows' && matrix.msystem != '' }} - shell: msys2 {0} - env: - CMAKE_PRESET: ${{ matrix.cmake_preset }} - PRESET_TYPE: ${{ inputs.build_type == 'Debug' && 'debug' || 'ci' }} - run: | - cmake --workflow --preset "$CMAKE_PRESET"_"$PRESET_TYPE" + - name: Package (Linux) + if: ${{ runner.os == 'Linux' }} + uses: ./.github/actions/package/linux + with: + version: ${{ steps.short-version.outputs.version }} + build-type: ${{ steps.setup-dependencies.outputs.build-type }} + cmake-preset: ${{ steps.cmake-preset.outputs.preset }} + qt-version: ${{ steps.setup-dependencies.outputs.qt-version }} - ## - # PACKAGE BUILDS - ## - - - name: Fetch codesign certificate (macOS) - if: runner.os == 'macOS' - run: | - echo '${{ secrets.APPLE_CODESIGN_CERT }}' | base64 --decode > codesign.p12 - if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then - security create-keychain -p '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain - security default-keychain -s build.keychain - security unlock-keychain -p '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain - security import codesign.p12 -k build.keychain -P '${{ secrets.APPLE_CODESIGN_PASSWORD }}' -T /usr/bin/codesign - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain - else - echo ":warning: Using ad-hoc code signing for macOS, as certificate was not present." >> $GITHUB_STEP_SUMMARY - fi + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-private-key-id: ${{ secrets.GPG_PRIVATE_KEY_ID }} - name: Package (macOS) - if: runner.os == 'macOS' - run: | - cmake --install ${{ env.BUILD_DIR }} - - cd ${{ env.INSTALL_DIR }} - chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher" - - if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then - APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}' - ENTITLEMENTS_FILE='../program_info/App.entitlements' - else - APPLE_CODESIGN_ID='-' - ENTITLEMENTS_FILE='../program_info/AdhocSignedApp.entitlements' - fi - - sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher" - mv "PrismLauncher.app" "Prism Launcher.app" - - - name: Notarize (macOS) - if: runner.os == 'macOS' - run: | - cd ${{ env.INSTALL_DIR }} - - if [ -n '${{ secrets.APPLE_NOTARIZE_PASSWORD }}' ]; then - ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip - xcrun notarytool submit ../PrismLauncher.zip \ - --wait --progress \ - --apple-id '${{ secrets.APPLE_NOTARIZE_APPLE_ID }}' \ - --team-id '${{ secrets.APPLE_NOTARIZE_TEAM_ID }}' \ - --password '${{ secrets.APPLE_NOTARIZE_PASSWORD }}' - - xcrun stapler staple "Prism Launcher.app" - else - echo ":warning: Skipping notarization as credentials are not present." >> $GITHUB_STEP_SUMMARY - fi - ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip - - - name: Make Sparkle signature (macOS) - if: matrix.name == 'macOS' - run: | - if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then - echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem - signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n) - rm ed25519-priv.pem - cat >> $GITHUB_STEP_SUMMARY << EOF - ### Artifact Information :information_source: - - :memo: Sparkle Signature (ed25519): \`$signature\` - EOF - else - cat >> $GITHUB_STEP_SUMMARY << EOF - ### Artifact Information :information_source: - - :warning: Sparkle Signature (ed25519): No private key available (likely a pull request or fork) - EOF - fi - - - name: Package (Windows MinGW-w64) - if: runner.os == 'Windows' && matrix.msystem != '' - shell: msys2 {0} - run: | - cmake --install ${{ env.BUILD_DIR }} - touch ${{ env.INSTALL_DIR }}/manifest.txt - for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt - - - name: Package (Windows MSVC) - if: runner.os == 'Windows' && matrix.msystem == '' - run: | - cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }} - - cd ${{ github.workspace }} - - Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt - - - name: Fetch codesign certificate (Windows) - if: runner.os == 'Windows' - shell: bash # yes, we are not using MSYS2 or PowerShell here - run: | - echo '${{ secrets.WINDOWS_CODESIGN_CERT }}' | base64 --decode > codesign.pfx - - - name: Sign executable (Windows) - if: runner.os == 'Windows' - run: | - if (Get-Content ./codesign.pfx){ - cd ${{ env.INSTALL_DIR }} - # We ship the exact same executable for portable and non-portable editions, so signing just once is fine - SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_updater.exe prismlauncher_filelink.exe - } else { - ":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY - } - - - name: Package (Windows MinGW-w64, portable) - if: runner.os == 'Windows' && matrix.msystem != '' - shell: msys2 {0} - run: | - cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead - cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable - for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt - - - name: Package (Windows MSVC, portable) - if: runner.os == 'Windows' && matrix.msystem == '' - run: | - cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead - cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable - - Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt - - - name: Package (Windows, installer) - if: runner.os == 'Windows' - run: | - if ('${{ matrix.nscurl_tag }}') { - New-Item -Name NSISPlugins -ItemType Directory - Invoke-Webrequest https://github.com/negrutiu/nsis-nscurl/releases/download/${{ matrix.nscurl_tag }}/NScurl.zip -OutFile NSISPlugins\NScurl.zip - $nscurl_hash = Get-FileHash NSISPlugins\NScurl.zip -Algorithm Sha256 | Select-Object -ExpandProperty Hash - if ( $nscurl_hash -ne "${{ matrix.nscurl_sha256 }}") { - echo "::error:: NSCurl.zip sha256 mismatch" - exit 1 - } - Expand-Archive -Path NSISPlugins\NScurl.zip -DestinationPath NSISPlugins\NScurl - } - cd ${{ env.INSTALL_DIR }} - makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi" - - - name: Sign installer (Windows) - if: runner.os == 'Windows' - run: | - if (Get-Content ./codesign.pfx){ - SignTool sign /fd sha256 /td sha256 /f codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com PrismLauncher-Setup.exe - } else { - ":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY - } - - - name: Package AppImage (Linux) - if: runner.os == 'Linux' - shell: bash - env: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - run: | - cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr - - mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml - export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated - - export OUTPUT="PrismLauncher-Linux-x86_64.AppImage" - - chmod +x linuxdeploy-*.AppImage - - mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib - mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines - - cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines - - cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ - cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ - cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ - - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" - export LD_LIBRARY_PATH - - chmod +x AppImageUpdate-x86_64.AppImage - cp AppImageUpdate-x86_64.AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin - - export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync" - - if [ '${{ secrets.GPG_PRIVATE_KEY_ID }}' != '' ]; then - export SIGN=1 - export SIGN_KEY=${{ secrets.GPG_PRIVATE_KEY_ID }} - mkdir -p ~/.gnupg/ - echo "$GPG_PRIVATE_KEY" > ~/.gnupg/private.key - gpg --import ~/.gnupg/private.key - else - echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY - fi - - ./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg - - mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage" - - - name: Package (Linux, portable) - if: runner.os == 'Linux' - env: - CMAKE_PRESET: ${{ matrix.cmake_preset }} - PRESET_TYPE: ${{ inputs.build_type == 'Debug' && 'debug' || 'ci' }} - run: | - cmake --preset "$CMAKE_PRESET"_"$PRESET_TYPE" -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_PORTABLE_DIR }} -DINSTALL_BUNDLE=full - cmake --install ${{ env.BUILD_DIR }} - cmake --install ${{ env.BUILD_DIR }} --component portable - - mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib - cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib - cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib - cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib - cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib - cp /usr/lib/x86_64-linux-gnu/libffi.so.*.* ${{ env.INSTALL_PORTABLE_DIR }}/lib - mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib - - for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt - cd ${{ env.INSTALL_PORTABLE_DIR }} - tar -czf ../PrismLauncher-portable.tar.gz * - - ## - # UPLOAD BUILDS - ## - - - name: Upload binary tarball (macOS) - if: runner.os == 'macOS' - uses: actions/upload-artifact@v4 + if: ${{ runner.os == 'macOS' }} + uses: ./.github/actions/package/macos with: - name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }} - path: PrismLauncher.zip + version: ${{ steps.short-version.outputs.version }} + build-type: ${{ steps.setup-dependencies.outputs.build-type }} + artifact-name: ${{ matrix.artifact-name }} - - name: Upload binary zip (Windows) - if: runner.os == 'Windows' - uses: actions/upload-artifact@v4 + apple-codesign-cert: ${{ secrets.APPLE-CODESIGN-CERT }} + apple-codesign-password: ${{ secrets.APPLE-CODESIGN_PASSWORD }} + apple-codesign-id: ${{ secrets.APPLE-CODESIGN_ID }} + apple-notarize-apple-id: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }} + apple-notarize-team-id: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }} + apple-notarize-password: ${{ secrets.APPLE-NOTARIZE_PASSWORD }} + sparkle-ed25519-key: ${{ secrets.SPARKLE-ED25519_KEY }} + + - name: Package (Windows) + if: ${{ runner.os == 'Windows' }} + uses: ./.github/actions/package/windows with: - name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }} - path: ${{ env.INSTALL_DIR }}/** + version: ${{ steps.short-version.outputs.version }} + build-type: ${{ steps.setup-dependencies.outputs.build-type }} + artifact-name: ${{ matrix.artifact-name }} + msystem: ${{ matrix.msystem }} - - name: Upload binary zip (Windows, portable) - if: runner.os == 'Windows' - uses: actions/upload-artifact@v4 - with: - name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }} - path: ${{ env.INSTALL_PORTABLE_DIR }}/** - - - name: Upload installer (Windows) - if: runner.os == 'Windows' - uses: actions/upload-artifact@v4 - with: - name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }} - path: PrismLauncher-Setup.exe - - - name: Upload binary tarball (Linux, portable) - if: runner.os == 'Linux' - uses: actions/upload-artifact@v4 - with: - name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }} - path: PrismLauncher-portable.tar.gz - - - name: Upload AppImage (Linux) - if: runner.os == 'Linux' - uses: actions/upload-artifact@v4 - with: - name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage - path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage - - - name: Upload AppImage Zsync (Linux) - if: runner.os == 'Linux' - uses: actions/upload-artifact@v4 - with: - name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync - path: PrismLauncher-Linux-x86_64.AppImage.zsync - - - name: ccache stats (Windows MinGW-w64) - if: runner.os == 'Windows' && matrix.msystem != '' - shell: msys2 {0} - run: | - ccache -s + windows-codesign-cert: ${{ secrets.WINDOWS_CODESIGN_CERT }} + windows-codesign-password: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }} diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml index 4be03f46f..e0d6f83ce 100644 --- a/.github/workflows/trigger_builds.yml +++ b/.github/workflows/trigger_builds.yml @@ -53,17 +53,5 @@ jobs: name: Build Debug uses: ./.github/workflows/build.yml with: - build_type: Debug - is_qt_cached: true - secrets: - SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }} - WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }} - WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }} - APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }} - APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }} - APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }} - APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }} - APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }} - APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }} - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }} + build-type: Debug + secrets: inherit diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 96f616a43..ae205895d 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -10,20 +10,8 @@ jobs: name: Build Release uses: ./.github/workflows/build.yml with: - build_type: Release - is_qt_cached: false - secrets: - SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }} - WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }} - WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }} - APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }} - APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }} - APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }} - APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }} - APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }} - APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }} - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }} + build-type: Release + secrets: inherit create_release: needs: build_release From 77b88fc7ec468d43d154e5fa464bada1774cbd19 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Tue, 29 Apr 2025 01:08:33 -0400 Subject: [PATCH 121/171] ci: run build workflow directly on push/prs Calling this from another workflow only for these events doesn't make much sense now Signed-off-by: Seth Flynn --- .github/workflows/build.yml | 53 +++++++++++++++++++++++++- .github/workflows/trigger_builds.yml | 57 ---------------------------- 2 files changed, 51 insertions(+), 59 deletions(-) delete mode 100644 .github/workflows/trigger_builds.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3408cf624..c393d4767 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,12 +1,61 @@ name: Build on: + push: + branches-ignore: + - "renovate/**" + paths: + # File types + - "**.cpp" + - "**.h" + - "**.java" + + # Directories + - "buildconfig/" + - "cmake/" + - "launcher/" + - "libraries/" + - "program_info/" + - "tests/" + + # Files + - "CMakeLists.txt" + - "COPYING.md" + + # Workflows + - ".github/workflows/build.yml" + pull_request: + paths: + # File types + - "**.cpp" + - "**.h" + + # Directories + - "buildconfig/" + - "cmake/" + - "launcher/" + - "libraries/" + - "program_info/" + - "tests/" + + # Files + - "CMakeLists.txt" + - "COPYING.md" + + # Workflows + - ".github/workflows/build.yml" workflow_call: inputs: build-type: description: Type of build (Debug or Release) type: string default: Debug + workflow_dispatch: + inputs: + build-type: + description: Type of build (Debug or Release) + type: string + default: Debug jobs: build: @@ -40,7 +89,7 @@ jobs: - os: macos-14 artifact-name: macOS - base-cmake-preset: ${{ inputs.build-type == 'Debug' && 'macos_universal' || 'macos' }} + base-cmake-preset: ${{ (inputs.build-type || 'Debug') == 'Debug' && 'macos_universal' || 'macos' }} macosx-deployment-target: 11.0 runs-on: ${{ matrix.os }} @@ -79,7 +128,7 @@ jobs: id: cmake-preset env: BASE_CMAKE_PRESET: ${{ matrix.base-cmake-preset }} - PRESET_TYPE: ${{ inputs.build-type == 'Debug' && 'debug' || 'ci' }} + PRESET_TYPE: ${{ (inputs.build-type || 'Debug') == 'Debug' && 'debug' || 'ci' }} run: | echo preset="$BASE_CMAKE_PRESET"_"$PRESET_TYPE" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml deleted file mode 100644 index e0d6f83ce..000000000 --- a/.github/workflows/trigger_builds.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Build Application - -on: - push: - branches-ignore: - - "renovate/**" - paths: - # File types - - "**.cpp" - - "**.h" - - "**.java" - - # Directories - - "buildconfig/" - - "cmake/" - - "launcher/" - - "libraries/" - - "program_info/" - - "tests/" - - # Files - - "CMakeLists.txt" - - "COPYING.md" - - # Workflows - - ".github/workflows/build.yml" - - ".github/workflows/trigger_builds.yml" - pull_request: - paths: - # File types - - "**.cpp" - - "**.h" - - # Directories - - "buildconfig/" - - "cmake/" - - "launcher/" - - "libraries/" - - "program_info/" - - "tests/" - - # Files - - "CMakeLists.txt" - - "COPYING.md" - - # Workflows - - ".github/workflows/build.yml" - - ".github/workflows/trigger_builds.yml" - workflow_dispatch: - -jobs: - build_debug: - name: Build Debug - uses: ./.github/workflows/build.yml - with: - build-type: Debug - secrets: inherit From efa3392632890ba3bff4abc5b95bc85ab591fad6 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Tue, 29 Apr 2025 01:09:19 -0400 Subject: [PATCH 122/171] ci: trigger_release -> release Signed-off-by: Seth Flynn --- .github/workflows/{trigger_release.yml => release.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{trigger_release.yml => release.yml} (100%) diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/release.yml similarity index 100% rename from .github/workflows/trigger_release.yml rename to .github/workflows/release.yml From 1e617392ad24a3283205e89a792309c4744fd45f Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Wed, 30 Apr 2025 02:22:03 -0400 Subject: [PATCH 123/171] ci(codeql): use setup-dependencies action Signed-off-by: Seth Flynn --- .github/workflows/codeql.yml | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a16738c9c..5a2ecbd6d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -63,22 +63,10 @@ jobs: queries: security-and-quality languages: cpp, java - - name: Install Dependencies - run: sudo apt-get -y update - - sudo apt-get -y install ninja-build extra-cmake-modules scdoc - - - name: Install Qt - uses: jurplel/install-qt-action@v3 + - name: Setup dependencies + uses: ./.github/actions/setup-dependencies with: - aqtversion: "==3.1.*" - py7zrversion: ">=0.20.2" - version: "6.8.1" - host: "linux" - target: "desktop" - arch: "" - modules: "qt5compat qtimageformats qtnetworkauth" - tools: "" + build-type: Debug - name: Configure and Build run: | From 8c5333a5da67795e5d53a320b0147ebb4470a2a4 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Wed, 30 Apr 2025 03:54:47 -0400 Subject: [PATCH 124/171] ci: use sccache on windows This apparently works with less hacks, and is actually suggested by the action we're using Signed-off-by: Seth Flynn --- .github/actions/setup-dependencies/action.yml | 11 +++++++++++ .../setup-dependencies/windows/action.yml | 18 ------------------ 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/.github/actions/setup-dependencies/action.yml b/.github/actions/setup-dependencies/action.yml index e583989b0..760ee3e87 100644 --- a/.github/actions/setup-dependencies/action.yml +++ b/.github/actions/setup-dependencies/action.yml @@ -53,9 +53,20 @@ runs: if: ${{ (runner.os != 'Windows' || inputs.msystem == '') && inputs.build-type == 'Debug' }} uses: hendrikmuhs/ccache-action@v1.2.17 with: + variant: ${{ runner.os == 'Windows' && 'sccache' || 'ccache' }} create-symlink: ${{ runner.os != 'Windows' }} key: ${{ runner.os }}-qt${{ inputs.qt_ver }}-${{ inputs.architecture }} + - name: Use ccache on debug builds + if: ${{ inputs.build-type == 'Debug' }} + shell: bash + env: + # Only use sccache on MSVC + CCACHE_VARIANT: ${{ (runner.os == 'Windows' && inputs.msystem == '') && 'sccache' || 'ccache' }} + run: | + echo "CMAKE_C_COMPILER_LAUNCHER=$CCACHE_VARIANT" >> "$GITHUB_ENV" + echo "CMAKE_CXX_COMPILER_LAUNCHER=$CCACHE_VARIANT" >> "$GITHUB_ENV" + - name: Install Qt if: ${{ inputs.msystem == '' }} uses: jurplel/install-qt-action@v4 diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml index 782d02348..cac1698cb 100644 --- a/.github/actions/setup-dependencies/windows/action.yml +++ b/.github/actions/setup-dependencies/windows/action.yml @@ -48,24 +48,6 @@ runs: qt6-networkauth:p cmark:p - - name: Force newer ccache (MSVC) - if: ${{ inputs.msystem == '' && inputs.build-type == 'Debug' }} - shell: bash - run: | - choco install ccache --version 4.7.1 - - - name: Configure ccache (MSVC) - if: ${{ inputs.msystem == '' && inputs.build-type == 'Debug' }} - shell: pwsh - run: | - # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) - Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe - echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV - echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV - echo "TrackFileAccess=false" >> $env:GITHUB_ENV - # Needed for ccache, but also speeds up compile - echo "UseMultiToolTask=true" >> $env:GITHUB_ENV - - name: Retrieve ccache cache (MinGW) if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }} uses: actions/cache@v4.2.3 From be33aa356708765b5210894f1d9674b4cb814955 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 14:19:24 +0000 Subject: [PATCH 125/171] chore(deps): update hendrikmuhs/ccache-action action to v1.2.18 --- .github/actions/setup-dependencies/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-dependencies/action.yml b/.github/actions/setup-dependencies/action.yml index 760ee3e87..e97abd1df 100644 --- a/.github/actions/setup-dependencies/action.yml +++ b/.github/actions/setup-dependencies/action.yml @@ -51,7 +51,7 @@ runs: # TODO(@getchoo): Get this working on MSYS2! - name: Setup ccache if: ${{ (runner.os != 'Windows' || inputs.msystem == '') && inputs.build-type == 'Debug' }} - uses: hendrikmuhs/ccache-action@v1.2.17 + uses: hendrikmuhs/ccache-action@v1.2.18 with: variant: ${{ runner.os == 'Windows' && 'sccache' || 'ccache' }} create-symlink: ${{ runner.os != 'Windows' }} From c7aef20b1e7a6d3ef3e88e87f6d59ffd255203b7 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 8 Apr 2025 20:46:18 +0300 Subject: [PATCH 126/171] deperecate macos 11 Signed-off-by: Trial97 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c393d4767..574db521f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,7 +90,7 @@ jobs: - os: macos-14 artifact-name: macOS base-cmake-preset: ${{ (inputs.build-type || 'Debug') == 'Debug' && 'macos_universal' || 'macos' }} - macosx-deployment-target: 11.0 + macosx-deployment-target: 12.0 runs-on: ${{ matrix.os }} From ee81c7a6f4f77c3983f8df10bae442b083b96c47 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Thu, 1 May 2025 09:17:09 -0400 Subject: [PATCH 127/171] feat: build mingw binaries for arm64 Signed-off-by: Seth Flynn --- .github/workflows/build.yml | 8 +++++++- .github/workflows/release.yml | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c393d4767..d7d2e97b8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,9 +72,15 @@ jobs: - os: windows-2022 artifact-name: Windows-MinGW-w64 base-cmake-preset: windows_mingw - msystem: clang64 + msystem: CLANG64 vcvars-arch: amd64_x86 + - os: windows-11-arm + artifact-name: Windows-MinGW-arm64 + base-cmake-preset: windows_mingw + msystem: CLANGARM64 + vcvars-arch: arm64 + - os: windows-2022 artifact-name: Windows-MSVC base-cmake-preset: windows_msvc diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ae205895d..a93233dab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,6 +82,9 @@ jobs: PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip PrismLauncher-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe + PrismLauncher-Windows-MinGW-arm64-${{ env.VERSION }}.zip + PrismLauncher-Windows-MinGW-arm64-Portable-${{ env.VERSION }}.zip + PrismLauncher-Windows-MinGW-arm64-Setup-${{ env.VERSION }}.exe PrismLauncher-Windows-MSVC-arm64-${{ env.VERSION }}.zip PrismLauncher-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip PrismLauncher-Windows-MSVC-arm64-Setup-${{ env.VERSION }}.exe From a7c5959b7e7dfbe58bca207fe73fd10e3570cd1e Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Thu, 1 May 2025 09:24:34 -0400 Subject: [PATCH 128/171] ci(setup-deps): dont force x64 binutils for msys2 Signed-off-by: Seth Flynn --- .github/actions/setup-dependencies/windows/action.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml index cac1698cb..78717ddf4 100644 --- a/.github/actions/setup-dependencies/windows/action.yml +++ b/.github/actions/setup-dependencies/windows/action.yml @@ -33,20 +33,19 @@ runs: update: true install: >- git - mingw-w64-x86_64-binutils pacboy: >- toolchain:p + ccache:p cmake:p extra-cmake-modules:p ninja:p qt6-base:p qt6-svg:p qt6-imageformats:p - quazip-qt6:p - ccache:p qt6-5compat:p qt6-networkauth:p cmark:p + quazip-qt6:p - name: Retrieve ccache cache (MinGW) if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }} From a55bffc963bc285936d0e5739c2cffa617581f69 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 5 May 2025 13:25:29 -0700 Subject: [PATCH 129/171] ci(blocked-prs): jq if statements need parens Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked-prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index a8b2a3fe6..e518c709d 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -125,7 +125,7 @@ jobs: "type": $type, "number": .number, "merged": .merged, - "state": if .state == "open" then "Open" elif .merged then "Merged" else "Closed" end, + "state": (if .state == "open" then "Open" elif .merged then "Merged" else "Closed" end), "labels": (reduce .labels[].name as $l ([]; . + [$l])), "basePrUrl": .html_url, "baseRepoName": .head.repo.name, From d1234198a1506564617fb8da8437e540d6131f67 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 5 May 2025 14:01:58 -0700 Subject: [PATCH 130/171] ci(blocked-pr): default pr body as empty string Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked-prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index e518c709d..3ebe9f5cf 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -64,7 +64,7 @@ jobs: "prNumber": .number, "prHeadSha": .head.sha, "prHeadLabel": .head.label, - "prBody": .body, + "prBody": .body // "", "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } ' <<< "$PR_JSON")" From f379c5ef34718f15a190614665f59250886ee19c Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 5 May 2025 15:40:21 -0700 Subject: [PATCH 131/171] ci(blocked-pr): another jq syntax fix Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .github/workflows/blocked-prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 3ebe9f5cf..ecbaf755d 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -64,7 +64,7 @@ jobs: "prNumber": .number, "prHeadSha": .head.sha, "prHeadLabel": .head.label, - "prBody": .body // "", + "prBody": (.body // ""), "prLabels": (reduce .labels[].name as $l ([]; . + [$l])) } ' <<< "$PR_JSON")" From 476f3edce0985ddebe04190a8d566d5abf60e5b2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 6 May 2025 00:06:56 +0300 Subject: [PATCH 132/171] Revert "fix: 6.2 deprecation warning regard the QScopedPointer::swap function (#3655)" This reverts commit ca258109c5d49f27f5de5cb5100aa8e74eaf71e2, reversing changes made to 693d9d02bca3348b9a59e013867c3f6c8c5a9f98. Signed-off-by: Trial97 --- launcher/ui/pages/modplatform/ModPage.cpp | 16 +++++++--------- launcher/ui/pages/modplatform/ModPage.h | 6 +++--- .../ui/pages/modplatform/flame/FlamePage.cpp | 13 +++++-------- launcher/ui/pages/modplatform/flame/FlamePage.h | 2 +- .../modplatform/flame/FlameResourcePages.cpp | 4 ++-- .../pages/modplatform/flame/FlameResourcePages.h | 2 +- .../pages/modplatform/modrinth/ModrinthPage.cpp | 12 +++++------- .../ui/pages/modplatform/modrinth/ModrinthPage.h | 2 +- .../modrinth/ModrinthResourcePages.cpp | 4 ++-- .../modplatform/modrinth/ModrinthResourcePages.h | 2 +- launcher/ui/widgets/ModFilterWidget.cpp | 5 +++++ launcher/ui/widgets/ModFilterWidget.h | 4 +++- 12 files changed, 36 insertions(+), 36 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 80d2bcb73..8b4919015 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -61,24 +61,22 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePa connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); } -void ModPage::setFilterWidget(ModFilterWidget* widget) +void ModPage::setFilterWidget(unique_qobject_ptr& widget) { if (m_filter_widget) - disconnect(m_filter_widget, nullptr, nullptr, nullptr); + disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr); - auto old = m_ui->splitter->replaceWidget(0, widget); + auto old = m_ui->splitter->replaceWidget(0, widget.get()); // because we replaced the widget we also need to delete it if (old) { - old->deleteLater(); + delete old; } - m_filter_widget = widget; - if (m_filter_widget) { - m_filter_widget->deleteLater(); - } + m_filter_widget.swap(widget); + m_filter = m_filter_widget->getFilter(); - connect(m_filter_widget, &ModFilterWidget::filterChanged, this, &ModPage::triggerSearch); + connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, &ModPage::triggerSearch); prepareProviderCategories(); } diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 87865dc83..47fe21e0f 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -51,11 +51,11 @@ class ModPage : public ResourcePage { void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, std::shared_ptr) override; - virtual ModFilterWidget* createFilterWidget() = 0; + virtual unique_qobject_ptr createFilterWidget() = 0; [[nodiscard]] bool supportsFiltering() const override { return true; }; auto getFilter() const -> const std::shared_ptr { return m_filter; } - void setFilterWidget(ModFilterWidget*); + void setFilterWidget(unique_qobject_ptr&); protected: ModPage(ModDownloadDialog* dialog, BaseInstance& instance); @@ -67,7 +67,7 @@ class ModPage : public ResourcePage { void triggerSearch() override; protected: - ModFilterWidget* m_filter_widget = nullptr; + unique_qobject_ptr m_filter_widget; std::shared_ptr m_filter; }; diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index bcbae0d76..de6b3d633 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -341,20 +341,17 @@ void FlamePage::setSearchTerm(QString term) void FlamePage::createFilterWidget() { - auto widget = new ModFilterWidget(nullptr, false, this); - if (m_filterWidget) { - m_filterWidget->deleteLater(); - } - m_filterWidget = (widget); - auto old = ui->splitter->replaceWidget(0, m_filterWidget); + auto widget = ModFilterWidget::create(nullptr, false, this); + m_filterWidget.swap(widget); + auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it if (old) { - old->deleteLater(); + delete old; } connect(ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); }); - connect(m_filterWidget, &ModFilterWidget::filterChanged, this, &FlamePage::triggerSearch); + connect(m_filterWidget.get(), &ModFilterWidget::filterChanged, this, &FlamePage::triggerSearch); auto response = std::make_shared(); m_categoriesTask = FlameAPI::getCategories(response, ModPlatform::ResourceType::MODPACK); QObject::connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() { diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h index a828a2a29..27c96d2f1 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.h +++ b/launcher/ui/pages/modplatform/flame/FlamePage.h @@ -100,6 +100,6 @@ class FlamePage : public QWidget, public ModpackProviderBasePage { // Used to do instant searching with a delay to cache quick changes QTimer m_search_timer; - ModFilterWidget* m_filterWidget; + unique_qobject_ptr m_filterWidget; Task::Ptr m_categoriesTask; }; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index bfe873ac8..4e01f3a65 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -207,9 +207,9 @@ auto FlameShaderPackPage::shouldDisplay() const -> bool return true; } -ModFilterWidget* FlameModPage::createFilterWidget() +unique_qobject_ptr FlameModPage::createFilterWidget() { - return new ModFilterWidget(&static_cast(m_baseInstance), false, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), false, this); } void FlameModPage::prepareProviderCategories() diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 3117851a5..052706549 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -96,7 +96,7 @@ class FlameModPage : public ModPage { [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } void openUrl(const QUrl& url) override; - ModFilterWidget* createFilterWidget() override; + unique_qobject_ptr createFilterWidget() override; protected: virtual void prepareProviderCategories() override; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 784b656a1..7d70abec4 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -391,19 +391,17 @@ QString ModrinthPage::getSerachTerm() const void ModrinthPage::createFilterWidget() { - auto widget = new ModFilterWidget(nullptr, true, this); - if (m_filterWidget) - m_filterWidget->deleteLater(); - m_filterWidget = widget; - auto old = ui->splitter->replaceWidget(0, m_filterWidget); + auto widget = ModFilterWidget::create(nullptr, true, this); + m_filterWidget.swap(widget); + auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it if (old) { - old->deleteLater(); + delete old; } connect(ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); }); - connect(m_filterWidget, &ModFilterWidget::filterChanged, this, &ModrinthPage::triggerSearch); + connect(m_filterWidget.get(), &ModFilterWidget::filterChanged, this, &ModrinthPage::triggerSearch); auto response = std::make_shared(); m_categoriesTask = ModrinthAPI::getModCategories(response); QObject::connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index c90402f52..7f504cdbd 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -103,6 +103,6 @@ class ModrinthPage : public QWidget, public ModpackProviderBasePage { // Used to do instant searching with a delay to cache quick changes QTimer m_search_timer; - ModFilterWidget* m_filterWidget; + unique_qobject_ptr m_filterWidget; Task::Ptr m_categoriesTask; }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 38a750622..4ee620677 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -142,9 +142,9 @@ auto ModrinthShaderPackPage::shouldDisplay() const -> bool return true; } -ModFilterWidget* ModrinthModPage::createFilterWidget() +unique_qobject_ptr ModrinthModPage::createFilterWidget() { - return new ModFilterWidget(&static_cast(m_baseInstance), true, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), true, this); } void ModrinthModPage::prepareProviderCategories() diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index e2ad60b51..eaf6129a5 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -94,7 +94,7 @@ class ModrinthModPage : public ModPage { [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } - ModFilterWidget* createFilterWidget() override; + unique_qobject_ptr createFilterWidget() override; protected: virtual void prepareProviderCategories() override; diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 8be3ce8d3..03522bc19 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -49,6 +49,11 @@ #include "Application.h" #include "minecraft/PackProfile.h" +unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, bool extended, QWidget* parent) +{ + return unique_qobject_ptr(new ModFilterWidget(instance, extended, parent)); +} + class VersionBasicModel : public QIdentityProxyModel { Q_OBJECT diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index c7192a0d6..41a2f1bbd 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -83,7 +83,7 @@ class ModFilterWidget : public QTabWidget { } }; - ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); + static unique_qobject_ptr create(MinecraftInstance* instance, bool extended, QWidget* parent = nullptr); virtual ~ModFilterWidget(); auto getFilter() -> std::shared_ptr; @@ -96,6 +96,8 @@ class ModFilterWidget : public QTabWidget { void setCategories(const QList&); private: + ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); + void loadVersionList(); void prepareBasicFilter(); From 7523bc192532c7e7c4078e9539864f80cb72af81 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 6 May 2025 00:17:38 +0300 Subject: [PATCH 133/171] fix: replaced deprecated unique_qobject_ptr::swap with unique_ptr Signed-off-by: Trial97 --- launcher/ui/pages/modplatform/ModPage.cpp | 2 +- launcher/ui/pages/modplatform/ModPage.h | 6 +++--- launcher/ui/pages/modplatform/flame/FlamePage.cpp | 2 +- launcher/ui/pages/modplatform/flame/FlamePage.h | 2 +- .../ui/pages/modplatform/flame/FlameResourcePages.cpp | 4 ++-- launcher/ui/pages/modplatform/flame/FlameResourcePages.h | 2 +- launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp | 2 +- launcher/ui/pages/modplatform/modrinth/ModrinthPage.h | 2 +- .../pages/modplatform/modrinth/ModrinthResourcePages.cpp | 4 ++-- .../ui/pages/modplatform/modrinth/ModrinthResourcePages.h | 2 +- launcher/ui/widgets/ModFilterWidget.cpp | 8 ++++---- launcher/ui/widgets/ModFilterWidget.h | 4 ++-- 12 files changed, 20 insertions(+), 20 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 8b4919015..803ba6d5c 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -61,7 +61,7 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePa connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); } -void ModPage::setFilterWidget(unique_qobject_ptr& widget) +void ModPage::setFilterWidget(std::unique_ptr& widget) { if (m_filter_widget) disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr); diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 47fe21e0f..fb9f3f9d3 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -51,11 +51,11 @@ class ModPage : public ResourcePage { void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, std::shared_ptr) override; - virtual unique_qobject_ptr createFilterWidget() = 0; + virtual std::unique_ptr createFilterWidget() = 0; [[nodiscard]] bool supportsFiltering() const override { return true; }; auto getFilter() const -> const std::shared_ptr { return m_filter; } - void setFilterWidget(unique_qobject_ptr&); + void setFilterWidget(std::unique_ptr&); protected: ModPage(ModDownloadDialog* dialog, BaseInstance& instance); @@ -67,7 +67,7 @@ class ModPage : public ResourcePage { void triggerSearch() override; protected: - unique_qobject_ptr m_filter_widget; + std::unique_ptr m_filter_widget; std::shared_ptr m_filter; }; diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index de6b3d633..bb91e5a64 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -341,7 +341,7 @@ void FlamePage::setSearchTerm(QString term) void FlamePage::createFilterWidget() { - auto widget = ModFilterWidget::create(nullptr, false, this); + auto widget = ModFilterWidget::create(nullptr, false); m_filterWidget.swap(widget); auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h index 27c96d2f1..32b752bbe 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.h +++ b/launcher/ui/pages/modplatform/flame/FlamePage.h @@ -100,6 +100,6 @@ class FlamePage : public QWidget, public ModpackProviderBasePage { // Used to do instant searching with a delay to cache quick changes QTimer m_search_timer; - unique_qobject_ptr m_filterWidget; + std::unique_ptr m_filterWidget; Task::Ptr m_categoriesTask; }; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 4e01f3a65..4bea52fc0 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -207,9 +207,9 @@ auto FlameShaderPackPage::shouldDisplay() const -> bool return true; } -unique_qobject_ptr FlameModPage::createFilterWidget() +std::unique_ptr FlameModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_baseInstance), false, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), false); } void FlameModPage::prepareProviderCategories() diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 052706549..3518e7c24 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -96,7 +96,7 @@ class FlameModPage : public ModPage { [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } void openUrl(const QUrl& url) override; - unique_qobject_ptr createFilterWidget() override; + std::unique_ptr createFilterWidget() override; protected: virtual void prepareProviderCategories() override; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 7d70abec4..701bb9f72 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -391,7 +391,7 @@ QString ModrinthPage::getSerachTerm() const void ModrinthPage::createFilterWidget() { - auto widget = ModFilterWidget::create(nullptr, true, this); + auto widget = ModFilterWidget::create(nullptr, true); m_filterWidget.swap(widget); auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index 7f504cdbd..d22a72e4e 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -103,6 +103,6 @@ class ModrinthPage : public QWidget, public ModpackProviderBasePage { // Used to do instant searching with a delay to cache quick changes QTimer m_search_timer; - unique_qobject_ptr m_filterWidget; + std::unique_ptr m_filterWidget; Task::Ptr m_categoriesTask; }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 4ee620677..064cb28bf 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -142,9 +142,9 @@ auto ModrinthShaderPackPage::shouldDisplay() const -> bool return true; } -unique_qobject_ptr ModrinthModPage::createFilterWidget() +std::unique_ptr ModrinthModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_baseInstance), true, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), true); } void ModrinthModPage::prepareProviderCategories() diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index eaf6129a5..f6a789cc3 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -94,7 +94,7 @@ class ModrinthModPage : public ModPage { [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } - unique_qobject_ptr createFilterWidget() override; + std::unique_ptr createFilterWidget() override; protected: virtual void prepareProviderCategories() override; diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 03522bc19..da41b990a 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -49,9 +49,9 @@ #include "Application.h" #include "minecraft/PackProfile.h" -unique_qobject_ptr ModFilterWidget::create(MinecraftInstance* instance, bool extended, QWidget* parent) +std::unique_ptr ModFilterWidget::create(MinecraftInstance* instance, bool extended) { - return unique_qobject_ptr(new ModFilterWidget(instance, extended, parent)); + return std::unique_ptr(new ModFilterWidget(instance, extended)); } class VersionBasicModel : public QIdentityProxyModel { @@ -107,8 +107,8 @@ class AllVersionProxyModel : public QSortFilterProxyModel { } }; -ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWidget* parent) - : QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter()) +ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended) + : QTabWidget(), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter()) { ui->setupUi(this); diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 41a2f1bbd..88f2593dd 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -83,7 +83,7 @@ class ModFilterWidget : public QTabWidget { } }; - static unique_qobject_ptr create(MinecraftInstance* instance, bool extended, QWidget* parent = nullptr); + static std::unique_ptr create(MinecraftInstance* instance, bool extended); virtual ~ModFilterWidget(); auto getFilter() -> std::shared_ptr; @@ -96,7 +96,7 @@ class ModFilterWidget : public QTabWidget { void setCategories(const QList&); private: - ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr); + ModFilterWidget(MinecraftInstance* instance, bool extendedSupport); void loadVersionList(); void prepareBasicFilter(); From 9b07c6948cbd02f79af69a7d92b3a215d61a6377 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 6 May 2025 23:27:29 +0300 Subject: [PATCH 134/171] fix: modrinth categories not loading Signed-off-by: Trial97 --- .../ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp | 6 +++--- .../ui/pages/modplatform/modrinth/ModrinthResourcePages.h | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 064cb28bf..398bf0455 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -150,11 +150,11 @@ std::unique_ptr ModrinthModPage::createFilterWidget() void ModrinthModPage::prepareProviderCategories() { auto response = std::make_shared(); - auto task = ModrinthAPI::getModCategories(response); - QObject::connect(task.get(), &Task::succeeded, [this, response]() { + m_categoriesTask = ModrinthAPI::getModCategories(response); + QObject::connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() { auto categories = ModrinthAPI::loadModCategories(response); m_filter_widget->setCategories(categories); }); - task->start(); + m_categoriesTask->start(); }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index f6a789cc3..7f8d9d571 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -98,6 +98,7 @@ class ModrinthModPage : public ModPage { protected: virtual void prepareProviderCategories() override; + Task::Ptr m_categoriesTask; }; class ModrinthResourcePackPage : public ResourcePackResourcePage { From cb01d5c46ef41998d6905d43850746e4a3190ae4 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 4 May 2025 09:11:43 +0300 Subject: [PATCH 135/171] feat: refactor logs upload to use the NetJob Signed-off-by: Trial97 --- buildconfig/BuildConfig.cpp.in | 1 - buildconfig/BuildConfig.h | 3 - launcher/Application.cpp | 11 -- launcher/Application.h | 1 - launcher/net/PasteUpload.cpp | 221 +++++++++++++++------------------ launcher/net/PasteUpload.h | 66 ++++++---- launcher/ui/GuiUtil.cpp | 120 +++++++++++------- launcher/ui/GuiUtil.h | 4 +- 8 files changed, 215 insertions(+), 212 deletions(-) diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index 6bebcb80e..3637e7369 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -53,7 +53,6 @@ Config::Config() LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@"; USER_AGENT = "@Launcher_UserAgent@"; - USER_AGENT_UNCACHED = USER_AGENT + " (Uncached)"; // Version information VERSION_MAJOR = @Launcher_VERSION_MAJOR@; diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index b59adcb57..10c38e3d6 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -107,9 +107,6 @@ class Config { /// User-Agent to use. QString USER_AGENT; - /// User-Agent to use for uncached requests. - QString USER_AGENT_UNCACHED; - /// The git commit hash of this build QString GIT_COMMIT; diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 0daab026c..772e685eb 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1883,17 +1883,6 @@ QString Application::getUserAgent() return BuildConfig.USER_AGENT; } -QString Application::getUserAgentUncached() -{ - QString uaOverride = m_settings->get("UserAgentOverride").toString(); - if (!uaOverride.isEmpty()) { - uaOverride += " (Uncached)"; - return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString()); - } - - return BuildConfig.USER_AGENT_UNCACHED; -} - bool Application::handleDataMigration(const QString& currentData, const QString& oldData, const QString& name, diff --git a/launcher/Application.h b/launcher/Application.h index 12f41509c..fefb32292 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -160,7 +160,6 @@ class Application : public QApplication { QString getFlameAPIKey(); QString getModrinthAPIToken(); QString getUserAgent(); - QString getUserAgentUncached(); /// this is the root of the 'installation'. Used for automatic updates const QString& root() { return m_rootPath; } diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 86a44669e..8df47d006 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -36,74 +36,42 @@ */ #include "PasteUpload.h" -#include "Application.h" -#include "BuildConfig.h" -#include -#include #include #include #include #include #include -#include "net/Logging.h" +const std::array PasteUpload::PasteTypes = { { { "0x0.st", "https://0x0.st", "" }, + { "hastebin", "https://hst.sh", "/documents" }, + { "paste.gg", "https://paste.gg", "/api/v1/pastes" }, + { "mclo.gs", "https://api.mclo.gs", "/1/log" } } }; -std::array PasteUpload::PasteTypes = { { { "0x0.st", "https://0x0.st", "" }, - { "hastebin", "https://hst.sh", "/documents" }, - { "paste.gg", "https://paste.gg", "/api/v1/pastes" }, - { "mclo.gs", "https://api.mclo.gs", "/1/log" } } }; - -PasteUpload::PasteUpload(QWidget* window, QString text, QString baseUrl, PasteType pasteType) - : m_window(window), m_baseUrl(baseUrl), m_pasteType(pasteType), m_text(text.toUtf8()) +QNetworkReply* PasteUpload::getReply(QNetworkRequest& request) { - if (m_baseUrl == "") - m_baseUrl = PasteTypes.at(pasteType).defaultBase; - - // HACK: Paste's docs say the standard API path is at /api/ but the official instance paste.gg doesn't follow that?? - if (pasteType == PasteGG && m_baseUrl == PasteTypes.at(pasteType).defaultBase) - m_uploadUrl = "https://api.paste.gg/v1/pastes"; - else - m_uploadUrl = m_baseUrl + PasteTypes.at(pasteType).endpointPath; -} - -PasteUpload::~PasteUpload() {} - -void PasteUpload::executeTask() -{ - QNetworkRequest request{ QUrl(m_uploadUrl) }; - QNetworkReply* rep{}; - - request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8()); - - switch (m_pasteType) { - case NullPointer: { - QHttpMultiPart* multiPart = new QHttpMultiPart{ QHttpMultiPart::FormDataType }; + switch (m_paste_type) { + case PasteUpload::NullPointer: { + QHttpMultiPart* multiPart = new QHttpMultiPart{ QHttpMultiPart::FormDataType, this }; QHttpPart filePart; - filePart.setBody(m_text); + filePart.setBody(m_log.toUtf8()); filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"file\"; filename=\"log.txt\""); multiPart->append(filePart); - rep = APPLICATION->network()->post(request, multiPart); - multiPart->setParent(rep); - - break; + return m_network->post(request, multiPart); } - case Hastebin: { - request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8()); - rep = APPLICATION->network()->post(request, m_text); - break; + case PasteUpload::Hastebin: { + return m_network->post(request, m_log.toUtf8()); } - case Mclogs: { + case PasteUpload::Mclogs: { QUrlQuery postData; - postData.addQueryItem("content", m_text); + postData.addQueryItem("content", m_log); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - rep = APPLICATION->network()->post(request, postData.toString().toUtf8()); - break; + return m_network->post(request, postData.toString().toUtf8()); } - case PasteGG: { + case PasteUpload::PasteGG: { QJsonObject obj; QJsonDocument doc; request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); @@ -114,7 +82,7 @@ void PasteUpload::executeTask() QJsonObject logFileInfo; QJsonObject logFileContentInfo; logFileContentInfo.insert("format", "text"); - logFileContentInfo.insert("value", QString::fromUtf8(m_text)); + logFileContentInfo.insert("value", m_log); logFileInfo.insert("name", "log.txt"); logFileInfo.insert("content", logFileContentInfo); files.append(logFileInfo); @@ -122,108 +90,115 @@ void PasteUpload::executeTask() obj.insert("files", files); doc.setObject(obj); - rep = APPLICATION->network()->post(request, doc.toJson()); - break; + return m_network->post(request, doc.toJson()); } } - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, &QNetworkReply::finished, this, &PasteUpload::downloadFinished); + return nullptr; +}; - connect(rep, &QNetworkReply::errorOccurred, this, &PasteUpload::downloadError); +auto PasteUpload::Sink::init(QNetworkRequest&) -> Task::State +{ + m_output.clear(); + return Task::State::Running; +}; - m_reply = std::shared_ptr(rep); - - setStatus(tr("Uploading to %1").arg(m_uploadUrl)); +auto PasteUpload::Sink::write(QByteArray& data) -> Task::State +{ + m_output.append(data); + return Task::State::Running; } -void PasteUpload::downloadError(QNetworkReply::NetworkError error) +auto PasteUpload::Sink::abort() -> Task::State { - // error happened during download. - qCCritical(taskUploadLogC) << getUid().toString() << "Network error: " << error; - emitFailed(m_reply->errorString()); + m_output.clear(); + return Task::State::Failed; } -void PasteUpload::downloadFinished() +auto PasteUpload::Sink::finalize(QNetworkReply&) -> Task::State { - QByteArray data = m_reply->readAll(); - int statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (m_reply->error() != QNetworkReply::NetworkError::NoError) { - emitFailed(tr("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } else if (statusCode != 200 && statusCode != 201) { - QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); - emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase)); - qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned unexpected status code " << statusCode - << " with body: " << data; - m_reply.reset(); - return; - } - - switch (m_pasteType) { - case NullPointer: - m_pasteLink = QString::fromUtf8(data).trimmed(); + switch (m_paste_type) { + case PasteUpload::NullPointer: + m_result->link = QString::fromUtf8(m_output).trimmed(); break; - case Hastebin: { - QJsonDocument jsonDoc{ QJsonDocument::fromJson(data) }; - QJsonObject jsonObj{ jsonDoc.object() }; - if (jsonObj.contains("key") && jsonObj["key"].isString()) { - QString key = jsonDoc.object()["key"].toString(); - m_pasteLink = m_baseUrl + "/" + key; + case PasteUpload::Hastebin: { + QJsonParseError jsonError; + auto doc = QJsonDocument::fromJson(m_output, &jsonError); + if (jsonError.error != QJsonParseError::NoError) { + qDebug() << "hastebin server did not reply with JSON" << jsonError.errorString(); + return Task::State::Failed; + } + auto obj = doc.object(); + if (obj.contains("key") && obj["key"].isString()) { + QString key = doc.object()["key"].toString(); + m_result->link = m_base_url + "/" + key; } else { - emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); - qCCritical(taskUploadLogC) << getUid().toString() << getUid().toString() << m_uploadUrl - << " returned malformed response body: " << data; - return; + qDebug() << "Log upload failed:" << doc.toJson(); + return Task::State::Failed; } break; } - case Mclogs: { - QJsonDocument jsonDoc{ QJsonDocument::fromJson(data) }; - QJsonObject jsonObj{ jsonDoc.object() }; - if (jsonObj.contains("success") && jsonObj["success"].isBool()) { - bool success = jsonObj["success"].toBool(); + case PasteUpload::Mclogs: { + QJsonParseError jsonError; + auto doc = QJsonDocument::fromJson(m_output, &jsonError); + if (jsonError.error != QJsonParseError::NoError) { + qDebug() << "mclogs server did not reply with JSON" << jsonError.errorString(); + return Task::State::Failed; + } + auto obj = doc.object(); + if (obj.contains("success") && obj["success"].isBool()) { + bool success = obj["success"].toBool(); if (success) { - m_pasteLink = jsonObj["url"].toString(); + m_result->link = obj["url"].toString(); } else { - QString error = jsonObj["error"].toString(); - emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error)); - qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error; - qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data; - return; + m_result->error = obj["error"].toString(); } } else { - emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); - qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data; - return; + qDebug() << "Log upload failed:" << doc.toJson(); + return Task::State::Failed; } break; } - case PasteGG: - QJsonDocument jsonDoc{ QJsonDocument::fromJson(data) }; - QJsonObject jsonObj{ jsonDoc.object() }; - if (jsonObj.contains("status") && jsonObj["status"].isString()) { - QString status = jsonObj["status"].toString(); + case PasteUpload::PasteGG: + QJsonParseError jsonError; + auto doc = QJsonDocument::fromJson(m_output, &jsonError); + if (jsonError.error != QJsonParseError::NoError) { + qDebug() << "pastegg server did not reply with JSON" << jsonError.errorString(); + return Task::State::Failed; + } + auto obj = doc.object(); + if (obj.contains("status") && obj["status"].isString()) { + QString status = obj["status"].toString(); if (status == "success") { - m_pasteLink = m_baseUrl + "/p/anonymous/" + jsonObj["result"].toObject()["id"].toString(); + m_result->link = m_base_url + "/p/anonymous/" + obj["result"].toObject()["id"].toString(); } else { - QString error = jsonObj["error"].toString(); - QString message = - (jsonObj.contains("message") && jsonObj["message"].isString()) ? jsonObj["message"].toString() : "none"; - emitFailed(tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_uploadUrl, error, message)); - qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error; - qCCritical(taskUploadLogC) << getUid().toString() << "Error message: " << message; - qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data; - return; + m_result->error = obj["error"].toString(); + m_result->extra_message = (obj.contains("message") && obj["message"].isString()) ? obj["message"].toString() : "none"; } } else { - emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); - qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data; - return; + qDebug() << "Log upload failed:" << doc.toJson(); + return Task::State::Failed; } break; } - emitSucceeded(); + return Task::State::Succeeded; +} + +Net::NetRequest::Ptr PasteUpload::make(const QString& log, + const PasteUpload::PasteType pasteType, + const QString customBaseURL, + ResultPtr result) +{ + auto base = PasteUpload::PasteTypes.at(pasteType); + QString baseUrl = customBaseURL.isEmpty() ? base.defaultBase : customBaseURL; + auto up = makeShared(log, pasteType); + + // HACK: Paste's docs say the standard API path is at /api/ but the official instance paste.gg doesn't follow that?? + if (pasteType == PasteUpload::PasteGG && baseUrl == base.defaultBase) + up->m_url = "https://api.paste.gg/v1/pastes"; + else + up->m_url = baseUrl + base.endpointPath; + + up->m_sink.reset(new Sink(pasteType, baseUrl, result)); + return up; } diff --git a/launcher/net/PasteUpload.h b/launcher/net/PasteUpload.h index 2ba6067c3..b1247a16b 100644 --- a/launcher/net/PasteUpload.h +++ b/launcher/net/PasteUpload.h @@ -35,15 +35,16 @@ #pragma once -#include -#include -#include -#include -#include +#include "net/NetRequest.h" #include "tasks/Task.h" -class PasteUpload : public Task { - Q_OBJECT +#include +#include + +#include +#include + +class PasteUpload : public Net::NetRequest { public: enum PasteType : int { // 0x0.st @@ -58,32 +59,47 @@ class PasteUpload : public Task { First = NullPointer, Last = Mclogs }; - struct PasteTypeInfo { const QString name; const QString defaultBase; const QString endpointPath; }; - static std::array PasteTypes; + static const std::array PasteTypes; + struct Result { + QString link; + QString error; + QString extra_message; + }; - PasteUpload(QWidget* window, QString text, QString url, PasteType pasteType); - virtual ~PasteUpload(); + using ResultPtr = std::shared_ptr; - QString pasteLink() { return m_pasteLink; } + class Sink : public Net::Sink { + public: + Sink(const PasteType pasteType, const QString base_url, ResultPtr result) + : m_paste_type(pasteType), m_base_url(base_url), m_result(result) {}; + virtual ~Sink() = default; - protected: - virtual void executeTask(); + public: + auto init(QNetworkRequest& request) -> Task::State override; + auto write(QByteArray& data) -> Task::State override; + auto abort() -> Task::State override; + auto finalize(QNetworkReply& reply) -> Task::State override; + auto hasLocalData() -> bool override { return false; } + + private: + const PasteType m_paste_type; + const QString m_base_url; + ResultPtr m_result; + QByteArray m_output; + }; + PasteUpload(const QString& log, const PasteType pasteType) : m_log(log), m_paste_type(pasteType) {} + virtual ~PasteUpload() = default; + + static NetRequest::Ptr make(const QString& log, const PasteType pasteType, const QString baseURL, ResultPtr result); private: - QWidget* m_window; - QString m_pasteLink; - QString m_baseUrl; - QString m_uploadUrl; - PasteType m_pasteType; - QByteArray m_text; - std::shared_ptr m_reply; - public slots: - void downloadError(QNetworkReply::NetworkError); - void downloadFinished(); -}; + virtual QNetworkReply* getReply(QNetworkRequest&) override; + QString m_log; + const PasteType m_paste_type; +}; \ No newline at end of file diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index d53ade86d..de11d66e0 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -38,10 +38,15 @@ #include "GuiUtil.h" #include +#include #include #include #include +#include + +#include "FileSystem.h" +#include "net/NetJob.h" #include "net/PasteUpload.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" @@ -74,52 +79,52 @@ QString truncateLogForMclogs(const QString& logContent) return logContent; } +std::optional GuiUtil::uploadPaste(const QString& name, const QFileInfo& filePath, QWidget* parentWidget) +{ + return uploadPaste(name, FS::read(filePath.absoluteFilePath()), parentWidget); +}; + std::optional GuiUtil::uploadPaste(const QString& name, const QString& text, QWidget* parentWidget) { ProgressDialog dialog(parentWidget); - auto pasteTypeSetting = static_cast(APPLICATION->settings()->get("PastebinType").toInt()); - auto pasteCustomAPIBaseSetting = APPLICATION->settings()->get("PastebinCustomAPIBase").toString(); + auto pasteType = static_cast(APPLICATION->settings()->get("PastebinType").toInt()); + auto baseURL = APPLICATION->settings()->get("PastebinCustomAPIBase").toString(); bool shouldTruncate = false; - { - QUrl baseUrl; - if (pasteCustomAPIBaseSetting.isEmpty()) - baseUrl = PasteUpload::PasteTypes[pasteTypeSetting].defaultBase; - else - baseUrl = pasteCustomAPIBaseSetting; + if (baseURL.isEmpty()) + baseURL = PasteUpload::PasteTypes[pasteType].defaultBase; - if (baseUrl.isValid()) { - auto response = CustomMessageBox::selectable(parentWidget, QObject::tr("Confirm Upload"), - QObject::tr("You are about to upload \"%1\" to %2.\n" - "You should double-check for personal information.\n\n" - "Are you sure?") - .arg(name, baseUrl.host()), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) - ->exec(); + if (auto url = QUrl(baseURL); url.isValid()) { + auto response = CustomMessageBox::selectable(parentWidget, QObject::tr("Confirm Upload"), + QObject::tr("You are about to upload \"%1\" to %2.\n" + "You should double-check for personal information.\n\n" + "Are you sure?") + .arg(name, url.host()), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) + return {}; + + if (baseURL == "https://api.mclo.gs" && text.count("\n") > MaxMclogsLines) { + auto truncateResponse = CustomMessageBox::selectable( + parentWidget, QObject::tr("Confirm Truncation"), + QObject::tr("The log has %1 lines, exceeding mclo.gs' limit of %2.\n" + "The launcher can keep the first %3 and last %4 lines, trimming the middle.\n\n" + "If you choose 'No', mclo.gs will only keep the first %2 lines, cutting off " + "potentially useful info like crashes at the end.\n\n" + "Proceed with truncation?") + .arg(text.count("\n")) + .arg(MaxMclogsLines) + .arg(InitialMclogsLines) + .arg(FinalMclogsLines), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No) + ->exec(); + + if (truncateResponse == QMessageBox::Cancel) { return {}; - - if (baseUrl.toString() == "https://api.mclo.gs" && text.count("\n") > MaxMclogsLines) { - auto truncateResponse = CustomMessageBox::selectable( - parentWidget, QObject::tr("Confirm Truncation"), - QObject::tr("The log has %1 lines, exceeding mclo.gs' limit of %2.\n" - "The launcher can keep the first %3 and last %4 lines, trimming the middle.\n\n" - "If you choose 'No', mclo.gs will only keep the first %2 lines, cutting off " - "potentially useful info like crashes at the end.\n\n" - "Proceed with truncation?") - .arg(text.count("\n")) - .arg(MaxMclogsLines) - .arg(InitialMclogsLines) - .arg(FinalMclogsLines), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No) - ->exec(); - - if (truncateResponse == QMessageBox::Cancel) { - return {}; - } - shouldTruncate = truncateResponse == QMessageBox::Yes; } + shouldTruncate = truncateResponse == QMessageBox::Yes; } } @@ -128,22 +133,43 @@ std::optional GuiUtil::uploadPaste(const QString& name, const QString& textToUpload = truncateLogForMclogs(text); } - std::unique_ptr paste(new PasteUpload(parentWidget, textToUpload, pasteCustomAPIBaseSetting, pasteTypeSetting)); + auto result = std::make_shared(); + auto job = NetJob::Ptr(new NetJob("Log Upload", APPLICATION->network())); - dialog.execWithTask(paste.get()); - if (!paste->wasSuccessful()) { - CustomMessageBox::selectable(parentWidget, QObject::tr("Upload failed"), paste->failReason(), QMessageBox::Critical)->exec(); - return QString(); - } else { - const QString link = paste->pasteLink(); - setClipboardText(link); + job->addNetAction(PasteUpload::make(textToUpload, pasteType, baseURL, result)); + QObject::connect(job.get(), &Task::failed, [parentWidget](QString reason) { + CustomMessageBox::selectable(parentWidget, QObject::tr("Failed to upload logs!"), reason, QMessageBox::Critical)->show(); + }); + QObject::connect(job.get(), &Task::aborted, [parentWidget] { + CustomMessageBox::selectable(parentWidget, QObject::tr("Logs upload aborted"), + QObject::tr("The task has been aborted by the user."), QMessageBox::Information) + ->show(); + }); + + if (dialog.execWithTask(job.get()) == QDialog::Accepted) { + if (!result->error.isEmpty() || !result->extra_message.isEmpty()) { + QString message = QObject::tr("Error: %1").arg(result->error); + if (!result->extra_message.isEmpty()) { + message += QObject::tr("\nError message: %1").arg(result->extra_message); + } + CustomMessageBox::selectable(parentWidget, QObject::tr("Failed to upload logs!"), message, QMessageBox::Critical)->show(); + return {}; + } + if (result->link.isEmpty()) { + CustomMessageBox::selectable(parentWidget, QObject::tr("Failed to upload logs!"), "The upload link is empty", + QMessageBox::Critical) + ->show(); + return {}; + } + setClipboardText(result->link); CustomMessageBox::selectable( parentWidget, QObject::tr("Upload finished"), - QObject::tr("The link to the uploaded log has been placed in your clipboard.").arg(link), + QObject::tr("The link to the uploaded log has been placed in your clipboard.").arg(result->link), QMessageBox::Information) ->exec(); - return link; + return result->link; } + return {}; } void GuiUtil::setClipboardText(const QString& text) diff --git a/launcher/ui/GuiUtil.h b/launcher/ui/GuiUtil.h index 8d384d3f6..d380ccfe7 100644 --- a/launcher/ui/GuiUtil.h +++ b/launcher/ui/GuiUtil.h @@ -1,10 +1,12 @@ #pragma once +#include #include #include namespace GuiUtil { -std::optional uploadPaste(const QString& name, const QString& text, QWidget* parentWidget); +std::optional uploadPaste(const QString& name, const QFileInfo& filePath, QWidget* parentWidget); +std::optional uploadPaste(const QString& name, const QString& data, QWidget* parentWidget); void setClipboardText(const QString& text); QStringList BrowseForFiles(QString context, QString caption, QString filter, QString defaultPath, QWidget* parentWidget); QString BrowseForFile(QString context, QString caption, QString filter, QString defaultPath, QWidget* parentWidget); From 63d40ecda429b64bd80df2b8f023d9429e0acf4e Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 19 Nov 2023 11:33:55 +0200 Subject: [PATCH 136/171] feat: add upload action for launcher logs Signed-off-by: Trial97 --- launcher/ui/MainWindow.cpp | 11 +++++++++++ launcher/ui/MainWindow.ui | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index d9275a7ab..920db8ca7 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -90,6 +90,7 @@ #include #include "InstanceWindow.h" +#include "ui/GuiUtil.h" #include "ui/dialogs/AboutDialog.h" #include "ui/dialogs/CopyInstanceDialog.h" #include "ui/dialogs/CustomMessageBox.h" @@ -235,6 +236,16 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi ui->actionViewJavaFolder->setEnabled(BuildConfig.JAVA_DOWNLOADER_ENABLED); } + { // logs upload + + auto menu = new QMenu(this); + for (auto file : QDir("logs").entryInfoList(QDir::Files)) { + auto action = menu->addAction(file.fileName()); + connect(action, &QAction::triggered, this, [this, file] { GuiUtil::uploadPaste(file.fileName(), file, this); }); + } + ui->actionUploadLog->setMenu(menu); + } + // add the toolbar toggles to the view menu ui->viewMenu->addAction(ui->instanceToolBar->toggleViewAction()); ui->viewMenu->addAction(ui->newsToolBar->toggleViewAction()); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index f20c34206..e01d0fa74 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -215,6 +215,7 @@
+ @@ -696,6 +697,18 @@ Clear cached metadata + + + + .. + + + Upload logs + + + Upload launcher logs to the selected log provider + + From e3258061737ba18bdf328d270f7dbdc8886a1ae4 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 4 May 2025 09:12:58 +0300 Subject: [PATCH 137/171] feat: add regex removal for log sesnitive data Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 4 ++ launcher/logs/AnonymizeLog.cpp | 68 ++++++++++++++++++++++++++++++++++ launcher/logs/AnonymizeLog.h | 40 ++++++++++++++++++++ launcher/net/PasteUpload.cpp | 12 ++++-- launcher/net/PasteUpload.h | 5 ++- launcher/ui/GuiUtil.cpp | 4 +- launcher/ui/GuiUtil.h | 2 +- 7 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 launcher/logs/AnonymizeLog.cpp create mode 100644 launcher/logs/AnonymizeLog.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 4f8b9018a..dbd9dc0bb 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -834,6 +834,10 @@ SET(LAUNCHER_SOURCES icons/IconList.h icons/IconList.cpp + # log utils + logs/AnonymizeLog.cpp + logs/AnonymizeLog.h + # GUI - windows ui/GuiUtil.h ui/GuiUtil.cpp diff --git a/launcher/logs/AnonymizeLog.cpp b/launcher/logs/AnonymizeLog.cpp new file mode 100644 index 000000000..e5021a616 --- /dev/null +++ b/launcher/logs/AnonymizeLog.cpp @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "AnonymizeLog.h" + +#include + +struct RegReplace { + RegReplace(QRegularExpression r, QString w) : reg(r), with(w) { reg.optimize(); } + QRegularExpression reg; + QString with; +}; + +static const QVector anonymizeRules = { + RegReplace(QRegularExpression("C:\\\\Users\\\\([^\\\\]+)\\\\", QRegularExpression::CaseInsensitiveOption), + "C:\\Users\\********\\"), // windows + RegReplace(QRegularExpression("C:\\/Users\\/([^\\/]+)\\/", QRegularExpression::CaseInsensitiveOption), + "C:/Users/********/"), // windows with forward slashes + RegReplace(QRegularExpression("(?)"), // SESSION_TOKEN + RegReplace(QRegularExpression("new refresh token: \"[^\"]+\"", QRegularExpression::CaseInsensitiveOption), + "new refresh token: \"\""), // refresh token + RegReplace(QRegularExpression("\"device_code\" : \"[^\"]+\"", QRegularExpression::CaseInsensitiveOption), + "\"device_code\" : \"\""), // device code +}; + +void anonymizeLog(QString& log) +{ + for (auto rule : anonymizeRules) { + log.replace(rule.reg, rule.with); + } +} \ No newline at end of file diff --git a/launcher/logs/AnonymizeLog.h b/launcher/logs/AnonymizeLog.h new file mode 100644 index 000000000..2409ecee7 --- /dev/null +++ b/launcher/logs/AnonymizeLog.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +void anonymizeLog(QString& log); \ No newline at end of file diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 8df47d006..2333a2256 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -41,7 +41,9 @@ #include #include #include +#include #include +#include "logs/AnonymizeLog.h" const std::array PasteUpload::PasteTypes = { { { "0x0.st", "https://0x0.st", "" }, { "hastebin", "https://hst.sh", "/documents" }, @@ -184,10 +186,7 @@ auto PasteUpload::Sink::finalize(QNetworkReply&) -> Task::State return Task::State::Succeeded; } -Net::NetRequest::Ptr PasteUpload::make(const QString& log, - const PasteUpload::PasteType pasteType, - const QString customBaseURL, - ResultPtr result) +Net::NetRequest::Ptr PasteUpload::make(const QString& log, PasteUpload::PasteType pasteType, QString customBaseURL, ResultPtr result) { auto base = PasteUpload::PasteTypes.at(pasteType); QString baseUrl = customBaseURL.isEmpty() ? base.defaultBase : customBaseURL; @@ -202,3 +201,8 @@ Net::NetRequest::Ptr PasteUpload::make(const QString& log, up->m_sink.reset(new Sink(pasteType, baseUrl, result)); return up; } + +PasteUpload::PasteUpload(const QString& log, PasteType pasteType) : m_log(log), m_paste_type(pasteType) +{ + anonymizeLog(m_log); +} diff --git a/launcher/net/PasteUpload.h b/launcher/net/PasteUpload.h index b1247a16b..55fb2231c 100644 --- a/launcher/net/PasteUpload.h +++ b/launcher/net/PasteUpload.h @@ -39,6 +39,7 @@ #include "tasks/Task.h" #include +#include #include #include @@ -93,10 +94,10 @@ class PasteUpload : public Net::NetRequest { ResultPtr m_result; QByteArray m_output; }; - PasteUpload(const QString& log, const PasteType pasteType) : m_log(log), m_paste_type(pasteType) {} + PasteUpload(const QString& log, PasteType pasteType); virtual ~PasteUpload() = default; - static NetRequest::Ptr make(const QString& log, const PasteType pasteType, const QString baseURL, ResultPtr result); + static NetRequest::Ptr make(const QString& log, PasteType pasteType, QString baseURL, ResultPtr result); private: virtual QNetworkReply* getReply(QNetworkRequest&) override; diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index de11d66e0..adb6e8bf2 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -46,6 +46,7 @@ #include #include "FileSystem.h" +#include "logs/AnonymizeLog.h" #include "net/NetJob.h" #include "net/PasteUpload.h" #include "ui/dialogs/CustomMessageBox.h" @@ -172,8 +173,9 @@ std::optional GuiUtil::uploadPaste(const QString& name, const QString& return {}; } -void GuiUtil::setClipboardText(const QString& text) +void GuiUtil::setClipboardText(QString text) { + anonymizeLog(text); QApplication::clipboard()->setText(text); } diff --git a/launcher/ui/GuiUtil.h b/launcher/ui/GuiUtil.h index d380ccfe7..c3ba01f5b 100644 --- a/launcher/ui/GuiUtil.h +++ b/launcher/ui/GuiUtil.h @@ -7,7 +7,7 @@ namespace GuiUtil { std::optional uploadPaste(const QString& name, const QFileInfo& filePath, QWidget* parentWidget); std::optional uploadPaste(const QString& name, const QString& data, QWidget* parentWidget); -void setClipboardText(const QString& text); +void setClipboardText(QString text); QStringList BrowseForFiles(QString context, QString caption, QString filter, QString defaultPath, QWidget* parentWidget); QString BrowseForFile(QString context, QString caption, QString filter, QString defaultPath, QWidget* parentWidget); } // namespace GuiUtil From fa189572dbd0d5ed1b4a09f315efc3d0d2f80bf7 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 7 May 2025 22:52:22 +0300 Subject: [PATCH 138/171] feat: search for pack icon in the actual file Signed-off-by: Trial97 --- launcher/InstanceImportTask.cpp | 47 +++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index e2735385b..b0314743d 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -262,6 +262,25 @@ void InstanceImportTask::extractFinished() } } +bool installIcon(QString root, QString instIcon) +{ + auto importIconPath = IconUtils::findBestIconIn(root, instIcon); + if (importIconPath.isNull() || !QFile::exists(importIconPath)) + importIconPath = IconUtils::findBestIconIn(root, "icon.png"); + if (importIconPath.isNull() || !QFile::exists(importIconPath)) + importIconPath = IconUtils::findBestIconIn(FS::PathCombine(root, "overrides"), "icon.png"); + if (!importIconPath.isNull() && QFile::exists(importIconPath)) { + // import icon + auto iconList = APPLICATION->icons(); + if (iconList->iconFileExists(instIcon)) { + iconList->deleteIcon(instIcon); + } + iconList->installIcon(importIconPath, instIcon); + return true; + } + return false; +} + void InstanceImportTask::processFlame() { shared_qobject_ptr inst_creation_task = nullptr; @@ -287,6 +306,14 @@ void InstanceImportTask::processFlame() } inst_creation_task->setName(*this); + // if the icon was specified by user, use that. otherwise pull icon from the pack + if (m_instIcon == "default") { + auto iconKey = QString("Flame_%1_Icon").arg(name()); + + if (installIcon(m_stagingPath, iconKey)) { + m_instIcon = iconKey; + } + } inst_creation_task->setIcon(m_instIcon); inst_creation_task->setGroup(m_instGroup); inst_creation_task->setConfirmUpdate(shouldConfirmUpdate()); @@ -339,17 +366,7 @@ void InstanceImportTask::processMultiMC() } else { m_instIcon = instance.iconKey(); - auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon); - if (importIconPath.isNull() || !QFile::exists(importIconPath)) - importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), "icon.png"); - if (!importIconPath.isNull() && QFile::exists(importIconPath)) { - // import icon - auto iconList = APPLICATION->icons(); - if (iconList->iconFileExists(m_instIcon)) { - iconList->deleteIcon(m_instIcon); - } - iconList->installIcon(importIconPath, m_instIcon); - } + installIcon(instance.instanceRoot(), m_instIcon); } emitSucceeded(); } @@ -386,6 +403,14 @@ void InstanceImportTask::processModrinth() } inst_creation_task->setName(*this); + // if the icon was specified by user, use that. otherwise pull icon from the pack + if (m_instIcon == "default") { + auto iconKey = QString("Modrinth_%1_Icon").arg(name()); + + if (installIcon(m_stagingPath, iconKey)) { + m_instIcon = iconKey; + } + } inst_creation_task->setIcon(m_instIcon); inst_creation_task->setGroup(m_instGroup); inst_creation_task->setConfirmUpdate(shouldConfirmUpdate()); From 4bad7e48c30d3aa3b29967493ca5286270f1ce46 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 7 May 2025 23:21:35 +0300 Subject: [PATCH 139/171] feat: prevent deletion of running instances Signed-off-by: Trial97 --- launcher/ui/MainWindow.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index d9275a7ab..4043a4285 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1383,6 +1383,14 @@ void MainWindow::on_actionDeleteInstance_triggered() return; } + if (m_selectedInstance->isRunning()) { + CustomMessageBox::selectable(this, tr("Cannot Delete Running Instance"), + tr("The selected instance is currently running and cannot be deleted. Please stop the instance before " + "attempting to delete it."), + QMessageBox::Warning, QMessageBox::Ok) + ->exec(); + return; + } auto id = m_selectedInstance->id(); auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"), From cf7061b9a82db766c67df402f9976e59368dd85b Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 7 May 2025 17:42:30 -0400 Subject: [PATCH 140/171] fix(FileSystem): dont re-define structs for newer mingw versions Signed-off-by: seth --- launcher/FileSystem.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 08dc7d2cc..ecb1e42d2 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -107,6 +107,10 @@ namespace fs = std::filesystem; #if defined(__MINGW32__) +// Avoid re-defining structs retroactively added to MinGW +// https://github.com/mingw-w64/mingw-w64/issues/90#issuecomment-2829284729 +#if __MINGW64_VERSION_MAJOR < 13 + struct _DUPLICATE_EXTENTS_DATA { HANDLE FileHandle; LARGE_INTEGER SourceFileOffset; @@ -116,6 +120,7 @@ struct _DUPLICATE_EXTENTS_DATA { using DUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA; using PDUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA*; +#endif struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER { WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32 From 0711890d1870db899222ffc18189850af5402f61 Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 7 May 2025 17:55:05 -0400 Subject: [PATCH 141/171] ci(mingw): use tomlplusplus from msys2 Signed-off-by: seth --- .github/actions/setup-dependencies/windows/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml index 78717ddf4..b7ade26e0 100644 --- a/.github/actions/setup-dependencies/windows/action.yml +++ b/.github/actions/setup-dependencies/windows/action.yml @@ -45,6 +45,7 @@ runs: qt6-5compat:p qt6-networkauth:p cmark:p + tomlplusplus:p quazip-qt6:p - name: Retrieve ccache cache (MinGW) From 9d79695512acbfd487760400f11c68fd2dfa5a38 Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 7 May 2025 17:56:33 -0400 Subject: [PATCH 142/171] ci(mingw): print msys2 packages Also lists theirs versions, which is useful for debugging issues like those fixed in https://github.com/PrismLauncher/PrismLauncher/pull/3756 Signed-off-by: seth --- .github/actions/setup-dependencies/windows/action.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml index b7ade26e0..0a643f583 100644 --- a/.github/actions/setup-dependencies/windows/action.yml +++ b/.github/actions/setup-dependencies/windows/action.yml @@ -25,7 +25,7 @@ runs: arch: ${{ inputs.vcvars-arch }} vsversion: 2022 - - name: Setup MSYS2 (MinGW-64) + - name: Setup MSYS2 (MinGW) if: ${{ inputs.msystem != '' }} uses: msys2/setup-msys2@v2 with: @@ -48,6 +48,12 @@ runs: tomlplusplus:p quazip-qt6:p + - name: List pacman packages (MinGW) + if: ${{ inputs.msystem != '' }} + shell: msys2 {0} + run: | + pacman -Qe + - name: Retrieve ccache cache (MinGW) if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }} uses: actions/cache@v4.2.3 From 3dcac0de502de5c7425bb8e66e0d0ad761418c88 Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 7 May 2025 18:03:37 -0400 Subject: [PATCH 143/171] ci: run workflows on local action changes Helps ensure they still actually work when changes are made Signed-off-by: seth --- .github/workflows/build.yml | 4 ++++ .github/workflows/codeql.yml | 2 ++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac33aba27..f4cdae97c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,8 @@ on: # Workflows - ".github/workflows/build.yml" + - ".github/actions/package/" + - ".github/actions/setup-dependencies/" pull_request: paths: # File types @@ -44,6 +46,8 @@ on: # Workflows - ".github/workflows/build.yml" + - ".github/actions/package/" + - ".github/actions/setup-dependencies/" workflow_call: inputs: build-type: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5a2ecbd6d..f8fae8ecf 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -23,6 +23,7 @@ on: # Workflows - ".github/codeql" - ".github/workflows/codeql.yml" + - ".github/actions/setup-dependencies/" pull_request: paths: # File types @@ -44,6 +45,7 @@ on: # Workflows - ".github/codeql" - ".github/workflows/codeql.yml" + - ".github/actions/setup-dependencies/" workflow_dispatch: jobs: From b9a97c8647767d85c2a96545007f33fb557656cb Mon Sep 17 00:00:00 2001 From: seth Date: Wed, 7 May 2025 21:01:00 -0400 Subject: [PATCH 144/171] ci(release): upload mingw-arm64 artifacts Signed-off-by: seth --- .github/workflows/release.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a93233dab..6e879cfd7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,6 +66,17 @@ jobs: cd .. done + for d in PrismLauncher-Windows-MinGW-arm64*; do + cd "${d}" || continue + INST="$(echo -n ${d} | grep -o Setup || true)" + PORT="$(echo -n ${d} | grep -o Portable || true)" + NAME="PrismLauncher-Windows-MinGW-arm64" + test -z "${PORT}" || NAME="${NAME}-Portable" + test -z "${INST}" || mv PrismLauncher-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe + test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" * + cd .. + done + - name: Create release id: create_release uses: softprops/action-gh-release@v2 From 69469b4484b88459a2fa74358197ac1dba4896ff Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 11 May 2025 03:33:03 +0800 Subject: [PATCH 145/171] Refactor shortcut creation logic into its own file Signed-off-by: Yihe Li --- launcher/CMakeLists.txt | 2 + launcher/minecraft/ShortcutUtils.cpp | 264 +++++++++++++++++++++++++++ launcher/minecraft/ShortcutUtils.h | 71 +++++++ launcher/ui/MainWindow.cpp | 191 +------------------ launcher/ui/MainWindow.h | 2 - launcher/ui/MainWindow.ui | 2 +- 6 files changed, 345 insertions(+), 187 deletions(-) create mode 100644 launcher/minecraft/ShortcutUtils.cpp create mode 100644 launcher/minecraft/ShortcutUtils.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index dbd9dc0bb..918e38df0 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -310,6 +310,8 @@ set(MINECRAFT_SOURCES minecraft/ParseUtils.h minecraft/ProfileUtils.cpp minecraft/ProfileUtils.h + minecraft/ShortcutUtils.cpp + minecraft/ShortcutUtils.h minecraft/Library.cpp minecraft/Library.h minecraft/MojangDownloadInfo.h diff --git a/launcher/minecraft/ShortcutUtils.cpp b/launcher/minecraft/ShortcutUtils.cpp new file mode 100644 index 000000000..7d4faf231 --- /dev/null +++ b/launcher/minecraft/ShortcutUtils.cpp @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * parent program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * parent program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with parent program. If not, see . + * + * parent file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use parent file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ShortcutUtils.h" + +#include "FileSystem.h" + +#include +#include + +#include +#include +#include + +namespace ShortcutUtils { + +void createInstanceShortcut(BaseInstance* instance, + QString shortcutName, + QString shortcutFilePath, + QString targetString, + QWidget* parent, + const QStringList& extraArgs) +{ + if (!instance) + return; + + QString appPath = QApplication::applicationFilePath(); + QString iconPath; + QStringList args; +#if defined(Q_OS_MACOS) + appPath = QApplication::applicationFilePath(); + if (appPath.startsWith("/private/var/")) { + QMessageBox::critical(parent, QObject::tr("Create Shortcut"), + QObject::tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts.")); + return; + } + + auto pIcon = APPLICATION->icons()->icon(instance->iconKey()); + if (pIcon == nullptr) { + pIcon = APPLICATION->icons()->icon("grass"); + } + + iconPath = FS::PathCombine(instance->instanceRoot(), "Icon.icns"); + + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) { + QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application.")); + return; + } + + QIcon icon = pIcon->icon(); + + bool success = icon.pixmap(1024, 1024).save(iconPath, "ICNS"); + iconFile.close(); + + if (!success) { + iconFile.remove(); + QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application.")); + return; + } +#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + if (appPath.startsWith("/tmp/.mount_")) { + // AppImage! + appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); + if (appPath.isEmpty()) { + QMessageBox::critical( + parent, QObject::tr("Create Shortcut"), + QObject::tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); + } else if (appPath.endsWith("/")) { + appPath.chop(1); + } + } + + auto icon = APPLICATION->icons()->icon(instance->iconKey()); + if (icon == nullptr) { + icon = APPLICATION->icons()->icon("grass"); + } + + iconPath = FS::PathCombine(instance->instanceRoot(), "icon.png"); + + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) { + QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); + return; + } + bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG"); + iconFile.close(); + + if (!success) { + iconFile.remove(); + QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); + return; + } + + if (DesktopServices::isFlatpak()) { + appPath = "flatpak"; + args.append({ "run", BuildConfig.LAUNCHER_APPID }); + } + +#elif defined(Q_OS_WIN) + auto icon = APPLICATION->icons()->icon(instance->iconKey()); + if (icon == nullptr) { + icon = APPLICATION->icons()->icon("grass"); + } + + iconPath = FS::PathCombine(instance->instanceRoot(), "icon.ico"); + + // part of fix for weird bug involving the window icon being replaced + // dunno why it happens, but parent 2-line fix seems to be enough, so w/e + auto appIcon = APPLICATION->getThemedIcon("logo"); + + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) { + QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); + return; + } + bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO"); + iconFile.close(); + + // restore original window icon + QGuiApplication::setWindowIcon(appIcon); + + if (!success) { + iconFile.remove(); + QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); + return; + } + +#else + QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Not supported on your platform!")); + return; +#endif + args.append({ "--launch", instance->id() }); + args.append(extraArgs); + + if (!FS::createShortcut(std::move(shortcutFilePath), appPath, args, shortcutName, iconPath)) { +#if not defined(Q_OS_MACOS) + iconFile.remove(); +#endif + QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create %1 shortcut!").arg(targetString)); + return; + } +} + +void createInstanceShortcutOnDesktop(BaseInstance* instance, + QString shortcutName, + QString targetString, + QWidget* parent, + const QStringList& extraArgs) +{ + if (!instance) + return; + + QString desktopDir = FS::getDesktopDir(); + if (desktopDir.isEmpty()) { + QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Couldn't find desktop?!")); + return; + } + + QString shortcutFilePath = FS::PathCombine(FS::getDesktopDir(), FS::RemoveInvalidFilenameChars(shortcutName)); + createInstanceShortcut(instance, shortcutName, shortcutFilePath, targetString, parent, extraArgs); + QMessageBox::information(parent, QObject::tr("Create Shortcut"), + QObject::tr("Created a shortcut to this %1 on your desktop!").arg(targetString)); +} + +void createInstanceShortcutInApplications(BaseInstance* instance, + QString shortcutName, + QString targetString, + QWidget* parent, + const QStringList& extraArgs) +{ + if (!instance) + return; + + QString applicationsDir = FS::getApplicationsDir(); + if (applicationsDir.isEmpty()) { + QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Couldn't find applications folder?!")); + return; + } + +#if defined(Q_OS_MACOS) || defined(Q_OS_WIN) + applicationsDir = FS::PathCombine(applicationsDir, BuildConfig.LAUNCHER_DISPLAYNAME + " Instances"); + + QDir applicationsDirQ(applicationsDir); + if (!applicationsDirQ.mkpath(".")) { + QMessageBox::critical(parent, QObject::tr("Create Shortcut"), + QObject::tr("Failed to create instances folder in applications folder!")); + return; + } +#endif + + QString shortcutFilePath = FS::PathCombine(applicationsDir, FS::RemoveInvalidFilenameChars(shortcutName)); + createInstanceShortcut(instance, shortcutName, shortcutFilePath, targetString, parent, extraArgs); + QMessageBox::information(parent, QObject::tr("Create Shortcut"), + QObject::tr("Created a shortcut to this %1 in your applications folder!").arg(targetString)); +} + +void createInstanceShortcutInOther(BaseInstance* instance, + QString shortcutName, + QString targetString, + QWidget* parent, + const QStringList& extraArgs) +{ + if (!instance) + return; + + QString defaultedDir = FS::getDesktopDir(); +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + QString extension = ".desktop"; +#elif defined(Q_OS_WINDOWS) + QString extension = ".lnk"; +#else + QString extension = ""; +#endif + + QString shortcutFilePath = FS::PathCombine(defaultedDir, FS::RemoveInvalidFilenameChars(shortcutName) + extension); + QFileDialog fileDialog; + // workaround to make sure the portal file dialog opens in the desktop directory + fileDialog.setDirectoryUrl(defaultedDir); + + shortcutFilePath = fileDialog.getSaveFileName(parent, QObject::tr("Create Shortcut"), shortcutFilePath, + QObject::tr("Desktop Entries") + " (*" + extension + ")"); + if (shortcutFilePath.isEmpty()) + return; // file dialog canceled by user + + if (shortcutFilePath.endsWith(extension)) + shortcutFilePath = shortcutFilePath.mid(0, shortcutFilePath.length() - extension.length()); + createInstanceShortcut(instance, shortcutName, shortcutFilePath, targetString, parent, extraArgs); + QMessageBox::information(parent, QObject::tr("Create Shortcut"), QObject::tr("Created a shortcut to this %1!").arg(targetString)); +} + +} // namespace ShortcutUtils diff --git a/launcher/minecraft/ShortcutUtils.h b/launcher/minecraft/ShortcutUtils.h new file mode 100644 index 000000000..88800f5e6 --- /dev/null +++ b/launcher/minecraft/ShortcutUtils.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include "Application.h" + +#include + +namespace ShortcutUtils { +/// Create an instance shortcut on the specified file path +void createInstanceShortcut(BaseInstance* instance, + QString shortcutName, + QString shortcutFilePath, + QString targetString, + QWidget* parent = nullptr, + const QStringList& extraArgs = {}); + +/// Create an instance shortcut on the desktop +void createInstanceShortcutOnDesktop(BaseInstance* instance, + QString shortcutName, + QString targetString, + QWidget* parent = nullptr, + const QStringList& extraArgs = {}); + +/// Create an instance shortcut in the Applications directory +void createInstanceShortcutInApplications(BaseInstance* instance, + QString shortcutName, + QString targetString, + QWidget* parent = nullptr, + const QStringList& extraArgs = {}); + +/// Create an instance shortcut in other directories +void createInstanceShortcutInOther(BaseInstance* instance, + QString shortcutName, + QString targetString, + QWidget* parent = nullptr, + const QStringList& extraArgs = {}); + +} // namespace ShortcutUtils diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 232ba45cd..ed9961975 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -109,6 +109,7 @@ #include "ui/widgets/LabeledToolButton.h" #include "minecraft/PackProfile.h" +#include "minecraft/ShortcutUtils.h" #include "minecraft/VersionFile.h" #include "minecraft/WorldList.h" #include "minecraft/mod/ModFolderModel.h" @@ -1545,157 +1546,6 @@ void MainWindow::on_actionKillInstance_triggered() } } -void MainWindow::createInstanceShortcut(QString shortcutFilePath) -{ - if (!m_selectedInstance) - return; - - QString appPath = QApplication::applicationFilePath(); - QString iconPath; - QStringList args; -#if defined(Q_OS_MACOS) - appPath = QApplication::applicationFilePath(); - if (appPath.startsWith("/private/var/")) { - QMessageBox::critical(this, tr("Create instance shortcut"), - tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts.")); - return; - } - - auto pIcon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); - if (pIcon == nullptr) { - pIcon = APPLICATION->icons()->icon("grass"); - } - - iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "Icon.icns"); - - QFile iconFile(iconPath); - if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); - return; - } - - QIcon icon = pIcon->icon(); - - bool success = icon.pixmap(1024, 1024).save(iconPath, "ICNS"); - iconFile.close(); - - if (!success) { - iconFile.remove(); - QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); - return; - } -#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - if (appPath.startsWith("/tmp/.mount_")) { - // AppImage! - appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); - if (appPath.isEmpty()) { - QMessageBox::critical(this, tr("Create instance shortcut"), - tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); - } else if (appPath.endsWith("/")) { - appPath.chop(1); - } - } - - auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); - if (icon == nullptr) { - icon = APPLICATION->icons()->icon("grass"); - } - - iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png"); - - QFile iconFile(iconPath); - if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } - bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG"); - iconFile.close(); - - if (!success) { - iconFile.remove(); - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } - - if (DesktopServices::isFlatpak()) { - appPath = "flatpak"; - args.append({ "run", BuildConfig.LAUNCHER_APPID }); - } - -#elif defined(Q_OS_WIN) - auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); - if (icon == nullptr) { - icon = APPLICATION->icons()->icon("grass"); - } - - iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico"); - - // part of fix for weird bug involving the window icon being replaced - // dunno why it happens, but this 2-line fix seems to be enough, so w/e - auto appIcon = APPLICATION->getThemedIcon("logo"); - - QFile iconFile(iconPath); - if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } - bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO"); - iconFile.close(); - - // restore original window icon - QGuiApplication::setWindowIcon(appIcon); - - if (!success) { - iconFile.remove(); - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } - -#else - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!")); - return; -#endif - args.append({ "--launch", m_selectedInstance->id() }); - - if (!FS::createShortcut(std::move(shortcutFilePath), appPath, args, m_selectedInstance->name(), iconPath)) { -#if not defined(Q_OS_MACOS) - iconFile.remove(); -#endif - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!")); - return; - } -} - -void MainWindow::on_actionCreateInstanceShortcutOther_triggered() -{ - if (!m_selectedInstance) - return; - - QString defaultedDir = FS::getDesktopDir(); -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - QString extension = ".desktop"; -#elif defined(Q_OS_WINDOWS) - QString extension = ".lnk"; -#else - QString extension = ""; -#endif - - QString shortcutFilePath = FS::PathCombine(defaultedDir, FS::RemoveInvalidFilenameChars(m_selectedInstance->name()) + extension); - QFileDialog fileDialog; - // workaround to make sure the portal file dialog opens in the desktop directory - fileDialog.setDirectoryUrl(defaultedDir); - - shortcutFilePath = - fileDialog.getSaveFileName(this, tr("Create Shortcut"), shortcutFilePath, tr("Desktop Entries") + " (*" + extension + ")"); - if (shortcutFilePath.isEmpty()) - return; // file dialog canceled by user - - if(shortcutFilePath.endsWith(extension)) - shortcutFilePath = shortcutFilePath.mid(0, shortcutFilePath.length() - extension.length()); - createInstanceShortcut(shortcutFilePath); - QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!")); -} - void MainWindow::on_actionCreateInstanceShortcut_triggered() { if (!m_selectedInstance) @@ -1709,44 +1559,17 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered() void MainWindow::on_actionCreateInstanceShortcutDesktop_triggered() { - if (!m_selectedInstance) - return; - - QString desktopDir = FS::getDesktopDir(); - if (desktopDir.isEmpty()) { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!")); - return; - } - - QString shortcutFilePath = FS::PathCombine(FS::getDesktopDir(), FS::RemoveInvalidFilenameChars(m_selectedInstance->name())); - createInstanceShortcut(shortcutFilePath); - QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!")); + ShortcutUtils::createInstanceShortcutOnDesktop(m_selectedInstance.get(), m_selectedInstance->name(), tr("instance"), this); } void MainWindow::on_actionCreateInstanceShortcutApplications_triggered() { - if (!m_selectedInstance) - return; + ShortcutUtils::createInstanceShortcutInApplications(m_selectedInstance.get(), m_selectedInstance->name(), tr("instance"), this); +} - QString applicationsDir = FS::getApplicationsDir(); - if (applicationsDir.isEmpty()) { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find applications folder?!")); - return; - } - -#if defined(Q_OS_MACOS) || defined(Q_OS_WIN) - applicationsDir = FS::PathCombine(applicationsDir, BuildConfig.LAUNCHER_DISPLAYNAME + " Instances"); - - QDir applicationsDirQ(applicationsDir); - if (!applicationsDirQ.mkpath(".")) { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instances folder in applications folder!")); - return; - } -#endif - - QString shortcutFilePath = FS::PathCombine(applicationsDir, FS::RemoveInvalidFilenameChars(m_selectedInstance->name())); - createInstanceShortcut(shortcutFilePath); - QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance in your applications folder!")); +void MainWindow::on_actionCreateInstanceShortcutOther_triggered() +{ + ShortcutUtils::createInstanceShortcutInOther(m_selectedInstance.get(), m_selectedInstance->name(), tr("instance"), this); } void MainWindow::taskEnd() diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index f3f2de730..20ab21e67 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -166,7 +166,6 @@ class MainWindow : public QMainWindow { void on_actionEditInstance_triggered(); void on_actionCreateInstanceShortcut_triggered(); - void on_actionCreateInstanceShortcutDesktop_triggered(); void on_actionCreateInstanceShortcutApplications_triggered(); void on_actionCreateInstanceShortcutOther_triggered(); @@ -230,7 +229,6 @@ class MainWindow : public QMainWindow { void setSelectedInstanceById(const QString& id); void updateStatusCenter(); void setInstanceActionsEnabled(bool enabled); - void createInstanceShortcut(QString shortcutDirPath); void runModalTask(Task* task); void instanceFromInstanceTask(InstanceTask* task); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 69d31589b..6530e2c5a 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -776,7 +776,7 @@ Desktop - Creates an shortcut to this instance on your desktop + Creates a shortcut to this instance on your desktop QAction::TextHeuristicRole From 9f1ee3594e557e830fa689f1e86a8ec815bf39df Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 11 May 2025 00:28:34 +0000 Subject: [PATCH 146/171] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/f771eb401a46846c1aebd20552521b233dd7e18b?narHash=sha256-ITSpPDwvLBZBnPRS2bUcHY3gZSwis/uTe255QgMtTLA%3D' (2025-04-24) → 'github:NixOS/nixpkgs/dda3dcd3fe03e991015e9a74b22d35950f264a54?narHash=sha256-Ua0drDHawlzNqJnclTJGf87dBmaO/tn7iZ%2BTCkTRpRc%3D' (2025-05-08) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 5418557a3..d0cc6f54c 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1745526057, - "narHash": "sha256-ITSpPDwvLBZBnPRS2bUcHY3gZSwis/uTe255QgMtTLA=", + "lastModified": 1746663147, + "narHash": "sha256-Ua0drDHawlzNqJnclTJGf87dBmaO/tn7iZ+TCkTRpRc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f771eb401a46846c1aebd20552521b233dd7e18b", + "rev": "dda3dcd3fe03e991015e9a74b22d35950f264a54", "type": "github" }, "original": { From 37213ecc34797dd4388e1c8e97f22e407d8dfef5 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 11 May 2025 19:01:37 +0800 Subject: [PATCH 147/171] Add create shortcut button for worlds Signed-off-by: Yihe Li --- launcher/minecraft/WorldList.cpp | 38 +++++++++++++ launcher/minecraft/WorldList.h | 5 ++ launcher/ui/MainWindow.cpp | 8 +-- launcher/ui/pages/instance/WorldListPage.cpp | 58 ++++++++++++++++++++ launcher/ui/pages/instance/WorldListPage.h | 4 ++ launcher/ui/pages/instance/WorldListPage.ui | 44 ++++++++++++++- 6 files changed, 152 insertions(+), 5 deletions(-) diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 6a821ba60..9a5cf042c 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -46,6 +46,9 @@ #include #include +#include +#include "minecraft/ShortcutUtils.h" + WorldList::WorldList(const QString& dir, BaseInstance* instance) : QAbstractListModel(), m_instance(instance), m_dir(dir) { FS::ensureFolderPathExists(m_dir.absolutePath()); @@ -454,4 +457,39 @@ void WorldList::loadWorldsAsync() } } +void WorldList::createWorldShortcut(const QModelIndex& index, QWidget* parent) const +{ + if (!m_instance) + return; + + if (DesktopServices::isFlatpak()) + createWorldShortcutInOther(index, parent); + else + createWorldShortcutOnDesktop(index, parent); +} + +void WorldList::createWorldShortcutOnDesktop(const QModelIndex& index, QWidget* parent) const +{ + auto world = static_cast(data(index, ObjectRole).value()); + QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world->name()); + QStringList extraArgs{ "--world", world->name() }; + ShortcutUtils::createInstanceShortcutOnDesktop(m_instance, name, tr("world"), parent, extraArgs); +} + +void WorldList::createWorldShortcutInApplications(const QModelIndex& index, QWidget* parent) const +{ + auto world = static_cast(data(index, ObjectRole).value()); + QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world->name()); + QStringList extraArgs{ "--world", world->name() }; + ShortcutUtils::createInstanceShortcutInApplications(m_instance, name, tr("world"), parent, extraArgs); +} + +void WorldList::createWorldShortcutInOther(const QModelIndex& index, QWidget* parent) const +{ + auto world = static_cast(data(index, ObjectRole).value()); + QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world->name()); + QStringList extraArgs{ "--world", world->name() }; + ShortcutUtils::createInstanceShortcutInOther(m_instance, name, tr("world"), parent, extraArgs); +} + #include "WorldList.moc" diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h index 93fecf1f5..4f54e0737 100644 --- a/launcher/minecraft/WorldList.h +++ b/launcher/minecraft/WorldList.h @@ -84,6 +84,11 @@ class WorldList : public QAbstractListModel { const QList& allWorlds() const { return m_worlds; } + void createWorldShortcut(const QModelIndex& index, QWidget* parent = nullptr) const; + void createWorldShortcutOnDesktop(const QModelIndex& index, QWidget* parent = nullptr) const; + void createWorldShortcutInApplications(const QModelIndex& index, QWidget* parent = nullptr) const; + void createWorldShortcutInOther(const QModelIndex& index, QWidget* parent = nullptr) const; + private slots: void directoryChanged(QString path); void loadWorldsAsync(); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index ed9961975..4f03d14da 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -214,17 +214,17 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi QString desktopDir = FS::getDesktopDir(); QString applicationDir = FS::getApplicationsDir(); - if(!applicationDir.isEmpty()) + if (!applicationDir.isEmpty()) shortcutActions.push_front(ui->actionCreateInstanceShortcutApplications); - if(!desktopDir.isEmpty()) + if (!desktopDir.isEmpty()) shortcutActions.push_front(ui->actionCreateInstanceShortcutDesktop); } - if(shortcutActions.length() > 1) { + if (shortcutActions.length() > 1) { auto shortcutInstanceMenu = new QMenu(this); - for(auto action : shortcutActions) + for (auto action : shortcutActions) shortcutInstanceMenu->addAction(action); ui->actionCreateInstanceShortcut->setMenu(shortcutInstanceMenu); } diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 9e1a0fb55..f6a1e0e5f 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -345,6 +345,28 @@ void WorldListPage::worldChanged([[maybe_unused]] const QModelIndex& current, [[ if (!supportsJoin) { ui->toolBar->removeAction(ui->actionJoin); + ui->toolBar->removeAction(ui->actionCreateWorldShortcut); + } else { + QList shortcutActions = { ui->actionCreateWorldShortcutOther }; + if (!DesktopServices::isFlatpak()) { + QString desktopDir = FS::getDesktopDir(); + QString applicationDir = FS::getApplicationsDir(); + + if (!applicationDir.isEmpty()) + shortcutActions.push_front(ui->actionCreateWorldShortcutApplications); + + if (!desktopDir.isEmpty()) + shortcutActions.push_front(ui->actionCreateWorldShortcutDesktop); + } + + if (shortcutActions.length() > 1) { + auto shortcutInstanceMenu = new QMenu(this); + + for (auto action : shortcutActions) + shortcutInstanceMenu->addAction(action); + ui->actionCreateWorldShortcut->setMenu(shortcutInstanceMenu); + } + ui->actionCreateWorldShortcut->setEnabled(enable); } } @@ -420,6 +442,42 @@ void WorldListPage::on_actionRename_triggered() } } +void WorldListPage::on_actionCreateWorldShortcut_triggered() +{ + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) { + return; + } + m_worlds->createWorldShortcut(index, this); +} + +void WorldListPage::on_actionCreateWorldShortcutDesktop_triggered() +{ + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) { + return; + } + m_worlds->createWorldShortcutOnDesktop(index, this); +} + +void WorldListPage::on_actionCreateWorldShortcutApplications_triggered() +{ + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) { + return; + } + m_worlds->createWorldShortcutInApplications(index, this); +} + +void WorldListPage::on_actionCreateWorldShortcutOther_triggered() +{ + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) { + return; + } + m_worlds->createWorldShortcutInOther(index, this); +} + void WorldListPage::on_actionRefresh_triggered() { m_worlds->update(); diff --git a/launcher/ui/pages/instance/WorldListPage.h b/launcher/ui/pages/instance/WorldListPage.h index 84d9cd075..f2c081bc5 100644 --- a/launcher/ui/pages/instance/WorldListPage.h +++ b/launcher/ui/pages/instance/WorldListPage.h @@ -95,6 +95,10 @@ class WorldListPage : public QMainWindow, public BasePage { void on_actionAdd_triggered(); void on_actionCopy_triggered(); void on_actionRename_triggered(); + void on_actionCreateWorldShortcut_triggered(); + void on_actionCreateWorldShortcutDesktop_triggered(); + void on_actionCreateWorldShortcutApplications_triggered(); + void on_actionCreateWorldShortcutOther_triggered(); void on_actionRefresh_triggered(); void on_actionView_Folder_triggered(); void on_actionDatapacks_triggered(); diff --git a/launcher/ui/pages/instance/WorldListPage.ui b/launcher/ui/pages/instance/WorldListPage.ui index 04344b453..f4664d503 100644 --- a/launcher/ui/pages/instance/WorldListPage.ui +++ b/launcher/ui/pages/instance/WorldListPage.ui @@ -85,10 +85,11 @@ + + - @@ -118,6 +119,14 @@ Delete + + + Create Shortcut + + + Creates a shortcut on a selected folder to join the selected world. + + MCEdit @@ -154,6 +163,39 @@ Manage datapacks inside the world. + + + Desktop + + + Creates a shortcut to this world on your desktop + + + QAction::TextHeuristicRole + + + + + Applications + + + Create a shortcut of this world on your start menu + + + QAction::TextHeuristicRole + + + + + Other... + + + Creates a shortcut in a folder selected by you + + + QAction::TextHeuristicRole + +
From dbdc9bea7a53ac0e26e69476d7a016f92afb8dff Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 11 May 2025 19:16:37 +0800 Subject: [PATCH 148/171] Add create shortcut button for servers Signed-off-by: Yihe Li --- launcher/minecraft/ShortcutUtils.cpp | 1 - launcher/ui/pages/instance/ServersPage.cpp | 75 ++++++++++++++++++++++ launcher/ui/pages/instance/ServersPage.h | 4 ++ launcher/ui/pages/instance/ServersPage.ui | 45 ++++++++++++- 4 files changed, 123 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/ShortcutUtils.cpp b/launcher/minecraft/ShortcutUtils.cpp index 7d4faf231..c579368c2 100644 --- a/launcher/minecraft/ShortcutUtils.cpp +++ b/launcher/minecraft/ShortcutUtils.cpp @@ -170,7 +170,6 @@ void createInstanceShortcut(BaseInstance* instance, iconFile.remove(); #endif QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create %1 shortcut!").arg(targetString)); - return; } } diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 245bbffe2..88b21a787 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -40,8 +40,10 @@ #include "ui/dialogs/CustomMessageBox.h" #include "ui_ServersPage.h" +#include #include #include +#include #include #include #include @@ -102,6 +104,38 @@ struct Server { } } + void createServerShortcut(BaseInstance* instance, QWidget* parent = nullptr) const + { + if (!instance) + return; + + if (DesktopServices::isFlatpak()) + createServerShortcutInOther(instance, parent); + else + createServerShortcutOnDesktop(instance, parent); + } + + void createServerShortcutOnDesktop(BaseInstance* instance, QWidget* parent = nullptr) const + { + QString name = QString(QObject::tr("%1 - Server %2")).arg(instance->name(), m_name); + QStringList extraArgs{ "--server", m_address }; + ShortcutUtils::createInstanceShortcutOnDesktop(instance, name, QObject::tr("server"), parent, extraArgs); + } + + void createServerShortcutInApplications(BaseInstance* instance, QWidget* parent = nullptr) const + { + QString name = QString(QObject::tr("%1 - Server %2")).arg(instance->name(), m_name); + QStringList extraArgs{ "--server", m_address }; + ShortcutUtils::createInstanceShortcutInApplications(instance, name, QObject::tr("server"), parent, extraArgs); + } + + void createServerShortcutInOther(BaseInstance* instance, QWidget* parent = nullptr) const + { + QString name = QString(QObject::tr("%1 - Server %2")).arg(instance->name(), m_name); + QStringList extraArgs{ "--server", m_address }; + ShortcutUtils::createInstanceShortcutInOther(instance, name, QObject::tr("server"), parent, extraArgs); + } + // Data - persistent and user changeable QString m_name; QString m_address; @@ -696,6 +730,27 @@ void ServersPage::updateState() } ui->actionAdd->setDisabled(m_locked); + + QList shortcutActions = { ui->actionCreateServerShortcutOther }; + if (!DesktopServices::isFlatpak()) { + QString desktopDir = FS::getDesktopDir(); + QString applicationDir = FS::getApplicationsDir(); + + if (!applicationDir.isEmpty()) + shortcutActions.push_front(ui->actionCreateServerShortcutApplications); + + if (!desktopDir.isEmpty()) + shortcutActions.push_front(ui->actionCreateServerShortcutDesktop); + } + + if (shortcutActions.length() > 1) { + auto shortcutInstanceMenu = new QMenu(this); + + for (auto action : shortcutActions) + shortcutInstanceMenu->addAction(action); + ui->actionCreateServerShortcut->setMenu(shortcutInstanceMenu); + } + ui->actionCreateServerShortcut->setEnabled(serverEditEnabled); } void ServersPage::openedImpl() @@ -767,6 +822,26 @@ void ServersPage::on_actionJoin_triggered() APPLICATION->launch(m_inst, true, false, std::make_shared(MinecraftTarget::parse(address, false))); } +void ServersPage::on_actionCreateServerShortcut_triggered() +{ + m_model->at(currentServer)->createServerShortcut(m_inst.get(), this); +} + +void ServersPage::on_actionCreateServerShortcutDesktop_triggered() +{ + m_model->at(currentServer)->createServerShortcutOnDesktop(m_inst.get(), this); +} + +void ServersPage::on_actionCreateServerShortcutApplications_triggered() +{ + m_model->at(currentServer)->createServerShortcutInApplications(m_inst.get(), this); +} + +void ServersPage::on_actionCreateServerShortcutOther_triggered() +{ + m_model->at(currentServer)->createServerShortcutInOther(m_inst.get(), this); +} + void ServersPage::on_actionRefresh_triggered() { m_model->queryServersStatus(); diff --git a/launcher/ui/pages/instance/ServersPage.h b/launcher/ui/pages/instance/ServersPage.h index 77710d6cc..94baaa004 100644 --- a/launcher/ui/pages/instance/ServersPage.h +++ b/launcher/ui/pages/instance/ServersPage.h @@ -85,6 +85,10 @@ class ServersPage : public QMainWindow, public BasePage { void on_actionMove_Up_triggered(); void on_actionMove_Down_triggered(); void on_actionJoin_triggered(); + void on_actionCreateServerShortcut_triggered(); + void on_actionCreateServerShortcutDesktop_triggered(); + void on_actionCreateServerShortcutApplications_triggered(); + void on_actionCreateServerShortcutOther_triggered(); void on_actionRefresh_triggered(); void runningStateChanged(bool running); diff --git a/launcher/ui/pages/instance/ServersPage.ui b/launcher/ui/pages/instance/ServersPage.ui index d330835c8..e26152242 100644 --- a/launcher/ui/pages/instance/ServersPage.ui +++ b/launcher/ui/pages/instance/ServersPage.ui @@ -145,10 +145,12 @@ false + + - + @@ -177,6 +179,47 @@ Join + + + Create Shortcut + + + Creates a shortcut on a selected folder to join the selected server. + + + + + Desktop + + + Creates a shortcut to this server on your desktop + + + QAction::TextHeuristicRole + + + + + Applications + + + Create a shortcut of this server on your start menu + + + QAction::TextHeuristicRole + + + + + Other... + + + Creates a shortcut in a folder selected by you + + + QAction::TextHeuristicRole + + Refresh From 039682b7dc70b7d5d9c6d5b7694444010e1b0574 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 13 May 2025 03:09:49 +0800 Subject: [PATCH 149/171] Remove inappropriate comments Signed-off-by: Yihe Li --- launcher/minecraft/ShortcutUtils.cpp | 1 - launcher/minecraft/ShortcutUtils.h | 1 - 2 files changed, 2 deletions(-) diff --git a/launcher/minecraft/ShortcutUtils.cpp b/launcher/minecraft/ShortcutUtils.cpp index c579368c2..de8bd3cb3 100644 --- a/launcher/minecraft/ShortcutUtils.cpp +++ b/launcher/minecraft/ShortcutUtils.cpp @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu * * parent program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/minecraft/ShortcutUtils.h b/launcher/minecraft/ShortcutUtils.h index 88800f5e6..0bc18b61e 100644 --- a/launcher/minecraft/ShortcutUtils.h +++ b/launcher/minecraft/ShortcutUtils.h @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From bae0ac7ad6bdcd1299c8fdacd91796176308e0ac Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 13 May 2025 03:12:20 +0800 Subject: [PATCH 150/171] Use index.row() directly Signed-off-by: Yihe Li --- launcher/minecraft/WorldList.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 9a5cf042c..ec6328018 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -470,25 +470,25 @@ void WorldList::createWorldShortcut(const QModelIndex& index, QWidget* parent) c void WorldList::createWorldShortcutOnDesktop(const QModelIndex& index, QWidget* parent) const { - auto world = static_cast(data(index, ObjectRole).value()); - QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world->name()); - QStringList extraArgs{ "--world", world->name() }; + const auto& world = allWorlds().at(index.row()); + QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world.name()); + QStringList extraArgs{ "--world", world.name() }; ShortcutUtils::createInstanceShortcutOnDesktop(m_instance, name, tr("world"), parent, extraArgs); } void WorldList::createWorldShortcutInApplications(const QModelIndex& index, QWidget* parent) const { - auto world = static_cast(data(index, ObjectRole).value()); - QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world->name()); - QStringList extraArgs{ "--world", world->name() }; + const auto& world = allWorlds().at(index.row()); + QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world.name()); + QStringList extraArgs{ "--world", world.name() }; ShortcutUtils::createInstanceShortcutInApplications(m_instance, name, tr("world"), parent, extraArgs); } void WorldList::createWorldShortcutInOther(const QModelIndex& index, QWidget* parent) const { - auto world = static_cast(data(index, ObjectRole).value()); - QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world->name()); - QStringList extraArgs{ "--world", world->name() }; + const auto& world = allWorlds().at(index.row()); + QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world.name()); + QStringList extraArgs{ "--world", world.name() }; ShortcutUtils::createInstanceShortcutInOther(m_instance, name, tr("world"), parent, extraArgs); } From db82988943a0f98dda5adbd2463239623226a5b0 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 13 May 2025 03:23:28 +0800 Subject: [PATCH 151/171] Re-add an appropriate copyright comment Signed-off-by: Yihe Li --- launcher/minecraft/ShortcutUtils.cpp | 3 +++ launcher/minecraft/ShortcutUtils.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/launcher/minecraft/ShortcutUtils.cpp b/launcher/minecraft/ShortcutUtils.cpp index de8bd3cb3..ac3a60614 100644 --- a/launcher/minecraft/ShortcutUtils.cpp +++ b/launcher/minecraft/ShortcutUtils.cpp @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 TheKodeToad + * Copyright (C) 2025 Yihe Li * * parent program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/launcher/minecraft/ShortcutUtils.h b/launcher/minecraft/ShortcutUtils.h index 0bc18b61e..7c0eeea5d 100644 --- a/launcher/minecraft/ShortcutUtils.h +++ b/launcher/minecraft/ShortcutUtils.h @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 TheKodeToad + * Copyright (C) 2025 Yihe Li * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From 31dc84653d454e8b913d7bf06609096e335c8318 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Tue, 13 May 2025 05:14:45 +0800 Subject: [PATCH 152/171] Refactor shortcut parameter into its own struct Signed-off-by: Yihe Li --- launcher/minecraft/ShortcutUtils.cpp | 103 +++++++++------------ launcher/minecraft/ShortcutUtils.h | 34 +++---- launcher/minecraft/WorldList.cpp | 6 +- launcher/ui/MainWindow.cpp | 6 +- launcher/ui/pages/instance/ServersPage.cpp | 6 +- 5 files changed, 66 insertions(+), 89 deletions(-) diff --git a/launcher/minecraft/ShortcutUtils.cpp b/launcher/minecraft/ShortcutUtils.cpp index ac3a60614..2bbeacb08 100644 --- a/launcher/minecraft/ShortcutUtils.cpp +++ b/launcher/minecraft/ShortcutUtils.cpp @@ -48,14 +48,9 @@ namespace ShortcutUtils { -void createInstanceShortcut(BaseInstance* instance, - QString shortcutName, - QString shortcutFilePath, - QString targetString, - QWidget* parent, - const QStringList& extraArgs) +void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) { - if (!instance) + if (!shortcut.instance) return; QString appPath = QApplication::applicationFilePath(); @@ -64,21 +59,21 @@ void createInstanceShortcut(BaseInstance* instance, #if defined(Q_OS_MACOS) appPath = QApplication::applicationFilePath(); if (appPath.startsWith("/private/var/")) { - QMessageBox::critical(parent, QObject::tr("Create Shortcut"), + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts.")); return; } - auto pIcon = APPLICATION->icons()->icon(instance->iconKey()); + auto pIcon = APPLICATION->icons()->icon(shortcut.instance->iconKey()); if (pIcon == nullptr) { pIcon = APPLICATION->icons()->icon("grass"); } - iconPath = FS::PathCombine(instance->instanceRoot(), "Icon.icns"); + iconPath = FS::PathCombine(shortcut.instance->instanceRoot(), "Icon.icns"); QFile iconFile(iconPath); if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application.")); + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application.")); return; } @@ -89,7 +84,7 @@ void createInstanceShortcut(BaseInstance* instance, if (!success) { iconFile.remove(); - QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application.")); + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application.")); return; } #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) @@ -98,23 +93,23 @@ void createInstanceShortcut(BaseInstance* instance, appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); if (appPath.isEmpty()) { QMessageBox::critical( - parent, QObject::tr("Create Shortcut"), + shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); } else if (appPath.endsWith("/")) { appPath.chop(1); } } - auto icon = APPLICATION->icons()->icon(instance->iconKey()); + auto icon = APPLICATION->icons()->icon(shortcut.instance->iconKey()); if (icon == nullptr) { icon = APPLICATION->icons()->icon("grass"); } - iconPath = FS::PathCombine(instance->instanceRoot(), "icon.png"); + iconPath = FS::PathCombine(shortcut.instance->instanceRoot(), "icon.png"); QFile iconFile(iconPath); if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); return; } bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG"); @@ -122,7 +117,7 @@ void createInstanceShortcut(BaseInstance* instance, if (!success) { iconFile.remove(); - QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); return; } @@ -132,12 +127,12 @@ void createInstanceShortcut(BaseInstance* instance, } #elif defined(Q_OS_WIN) - auto icon = APPLICATION->icons()->icon(instance->iconKey()); + auto icon = APPLICATION->icons()->icon(shortcut.instance->iconKey()); if (icon == nullptr) { icon = APPLICATION->icons()->icon("grass"); } - iconPath = FS::PathCombine(instance->instanceRoot(), "icon.ico"); + iconPath = FS::PathCombine(shortcut.instance->instanceRoot(), "icon.ico"); // part of fix for weird bug involving the window icon being replaced // dunno why it happens, but parent 2-line fix seems to be enough, so w/e @@ -145,7 +140,7 @@ void createInstanceShortcut(BaseInstance* instance, QFile iconFile(iconPath); if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); return; } bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO"); @@ -156,58 +151,51 @@ void createInstanceShortcut(BaseInstance* instance, if (!success) { iconFile.remove(); - QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); return; } #else - QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Not supported on your platform!")); + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Not supported on your platform!")); return; #endif - args.append({ "--launch", instance->id() }); - args.append(extraArgs); + args.append({ "--launch", shortcut.instance->id() }); + args.append(shortcut.extraArgs); - if (!FS::createShortcut(std::move(shortcutFilePath), appPath, args, shortcutName, iconPath)) { + if (!FS::createShortcut(std::move(filePath), appPath, args, shortcut.name, iconPath)) { #if not defined(Q_OS_MACOS) iconFile.remove(); #endif - QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create %1 shortcut!").arg(targetString)); + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), + QObject::tr("Failed to create %1 shortcut!").arg(shortcut.targetString)); } } -void createInstanceShortcutOnDesktop(BaseInstance* instance, - QString shortcutName, - QString targetString, - QWidget* parent, - const QStringList& extraArgs) +void createInstanceShortcutOnDesktop(const Shortcut& shortcut) { - if (!instance) + if (!shortcut.instance) return; QString desktopDir = FS::getDesktopDir(); if (desktopDir.isEmpty()) { - QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Couldn't find desktop?!")); + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Couldn't find desktop?!")); return; } - QString shortcutFilePath = FS::PathCombine(FS::getDesktopDir(), FS::RemoveInvalidFilenameChars(shortcutName)); - createInstanceShortcut(instance, shortcutName, shortcutFilePath, targetString, parent, extraArgs); - QMessageBox::information(parent, QObject::tr("Create Shortcut"), - QObject::tr("Created a shortcut to this %1 on your desktop!").arg(targetString)); + QString shortcutFilePath = FS::PathCombine(desktopDir, FS::RemoveInvalidFilenameChars(shortcut.name)); + createInstanceShortcut(shortcut, shortcutFilePath); + QMessageBox::information(shortcut.parent, QObject::tr("Create Shortcut"), + QObject::tr("Created a shortcut to this %1 on your desktop!").arg(shortcut.targetString)); } -void createInstanceShortcutInApplications(BaseInstance* instance, - QString shortcutName, - QString targetString, - QWidget* parent, - const QStringList& extraArgs) +void createInstanceShortcutInApplications(const Shortcut& shortcut) { - if (!instance) + if (!shortcut.instance) return; QString applicationsDir = FS::getApplicationsDir(); if (applicationsDir.isEmpty()) { - QMessageBox::critical(parent, QObject::tr("Create Shortcut"), QObject::tr("Couldn't find applications folder?!")); + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Couldn't find applications folder?!")); return; } @@ -216,25 +204,21 @@ void createInstanceShortcutInApplications(BaseInstance* instance, QDir applicationsDirQ(applicationsDir); if (!applicationsDirQ.mkpath(".")) { - QMessageBox::critical(parent, QObject::tr("Create Shortcut"), + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create instances folder in applications folder!")); return; } #endif - QString shortcutFilePath = FS::PathCombine(applicationsDir, FS::RemoveInvalidFilenameChars(shortcutName)); - createInstanceShortcut(instance, shortcutName, shortcutFilePath, targetString, parent, extraArgs); - QMessageBox::information(parent, QObject::tr("Create Shortcut"), - QObject::tr("Created a shortcut to this %1 in your applications folder!").arg(targetString)); + QString shortcutFilePath = FS::PathCombine(applicationsDir, FS::RemoveInvalidFilenameChars(shortcut.name)); + createInstanceShortcut(shortcut, shortcutFilePath); + QMessageBox::information(shortcut.parent, QObject::tr("Create Shortcut"), + QObject::tr("Created a shortcut to this %1 in your applications folder!").arg(shortcut.targetString)); } -void createInstanceShortcutInOther(BaseInstance* instance, - QString shortcutName, - QString targetString, - QWidget* parent, - const QStringList& extraArgs) +void createInstanceShortcutInOther(const Shortcut& shortcut) { - if (!instance) + if (!shortcut.instance) return; QString defaultedDir = FS::getDesktopDir(); @@ -246,20 +230,21 @@ void createInstanceShortcutInOther(BaseInstance* instance, QString extension = ""; #endif - QString shortcutFilePath = FS::PathCombine(defaultedDir, FS::RemoveInvalidFilenameChars(shortcutName) + extension); + QString shortcutFilePath = FS::PathCombine(defaultedDir, FS::RemoveInvalidFilenameChars(shortcut.name) + extension); QFileDialog fileDialog; // workaround to make sure the portal file dialog opens in the desktop directory fileDialog.setDirectoryUrl(defaultedDir); - shortcutFilePath = fileDialog.getSaveFileName(parent, QObject::tr("Create Shortcut"), shortcutFilePath, + shortcutFilePath = fileDialog.getSaveFileName(shortcut.parent, QObject::tr("Create Shortcut"), shortcutFilePath, QObject::tr("Desktop Entries") + " (*" + extension + ")"); if (shortcutFilePath.isEmpty()) return; // file dialog canceled by user if (shortcutFilePath.endsWith(extension)) shortcutFilePath = shortcutFilePath.mid(0, shortcutFilePath.length() - extension.length()); - createInstanceShortcut(instance, shortcutName, shortcutFilePath, targetString, parent, extraArgs); - QMessageBox::information(parent, QObject::tr("Create Shortcut"), QObject::tr("Created a shortcut to this %1!").arg(targetString)); + createInstanceShortcut(shortcut, shortcutFilePath); + QMessageBox::information(shortcut.parent, QObject::tr("Create Shortcut"), + QObject::tr("Created a shortcut to this %1!").arg(shortcut.targetString)); } } // namespace ShortcutUtils diff --git a/launcher/minecraft/ShortcutUtils.h b/launcher/minecraft/ShortcutUtils.h index 7c0eeea5d..a21ccf06a 100644 --- a/launcher/minecraft/ShortcutUtils.h +++ b/launcher/minecraft/ShortcutUtils.h @@ -41,33 +41,25 @@ #include namespace ShortcutUtils { +/// A struct to hold parameters for creating a shortcut +struct Shortcut { + BaseInstance* instance; + QString name; + QString targetString; + QWidget* parent = nullptr; + QStringList extraArgs = {}; +}; + /// Create an instance shortcut on the specified file path -void createInstanceShortcut(BaseInstance* instance, - QString shortcutName, - QString shortcutFilePath, - QString targetString, - QWidget* parent = nullptr, - const QStringList& extraArgs = {}); +void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath); /// Create an instance shortcut on the desktop -void createInstanceShortcutOnDesktop(BaseInstance* instance, - QString shortcutName, - QString targetString, - QWidget* parent = nullptr, - const QStringList& extraArgs = {}); +void createInstanceShortcutOnDesktop(const Shortcut& shortcut); /// Create an instance shortcut in the Applications directory -void createInstanceShortcutInApplications(BaseInstance* instance, - QString shortcutName, - QString targetString, - QWidget* parent = nullptr, - const QStringList& extraArgs = {}); +void createInstanceShortcutInApplications(const Shortcut& shortcut); /// Create an instance shortcut in other directories -void createInstanceShortcutInOther(BaseInstance* instance, - QString shortcutName, - QString targetString, - QWidget* parent = nullptr, - const QStringList& extraArgs = {}); +void createInstanceShortcutInOther(const Shortcut& shortcut); } // namespace ShortcutUtils diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index ec6328018..582531577 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -473,7 +473,7 @@ void WorldList::createWorldShortcutOnDesktop(const QModelIndex& index, QWidget* const auto& world = allWorlds().at(index.row()); QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world.name()); QStringList extraArgs{ "--world", world.name() }; - ShortcutUtils::createInstanceShortcutOnDesktop(m_instance, name, tr("world"), parent, extraArgs); + ShortcutUtils::createInstanceShortcutOnDesktop({ m_instance, name, tr("world"), parent, extraArgs }); } void WorldList::createWorldShortcutInApplications(const QModelIndex& index, QWidget* parent) const @@ -481,7 +481,7 @@ void WorldList::createWorldShortcutInApplications(const QModelIndex& index, QWid const auto& world = allWorlds().at(index.row()); QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world.name()); QStringList extraArgs{ "--world", world.name() }; - ShortcutUtils::createInstanceShortcutInApplications(m_instance, name, tr("world"), parent, extraArgs); + ShortcutUtils::createInstanceShortcutInApplications({ m_instance, name, tr("world"), parent, extraArgs }); } void WorldList::createWorldShortcutInOther(const QModelIndex& index, QWidget* parent) const @@ -489,7 +489,7 @@ void WorldList::createWorldShortcutInOther(const QModelIndex& index, QWidget* pa const auto& world = allWorlds().at(index.row()); QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world.name()); QStringList extraArgs{ "--world", world.name() }; - ShortcutUtils::createInstanceShortcutInOther(m_instance, name, tr("world"), parent, extraArgs); + ShortcutUtils::createInstanceShortcutInOther({ m_instance, name, tr("world"), parent, extraArgs }); } #include "WorldList.moc" diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 4f03d14da..d64e92124 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1559,17 +1559,17 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered() void MainWindow::on_actionCreateInstanceShortcutDesktop_triggered() { - ShortcutUtils::createInstanceShortcutOnDesktop(m_selectedInstance.get(), m_selectedInstance->name(), tr("instance"), this); + ShortcutUtils::createInstanceShortcutOnDesktop({ m_selectedInstance.get(), m_selectedInstance->name(), tr("instance"), this }); } void MainWindow::on_actionCreateInstanceShortcutApplications_triggered() { - ShortcutUtils::createInstanceShortcutInApplications(m_selectedInstance.get(), m_selectedInstance->name(), tr("instance"), this); + ShortcutUtils::createInstanceShortcutInApplications({ m_selectedInstance.get(), m_selectedInstance->name(), tr("instance"), this }); } void MainWindow::on_actionCreateInstanceShortcutOther_triggered() { - ShortcutUtils::createInstanceShortcutInOther(m_selectedInstance.get(), m_selectedInstance->name(), tr("instance"), this); + ShortcutUtils::createInstanceShortcutInOther({ m_selectedInstance.get(), m_selectedInstance->name(), tr("instance"), this }); } void MainWindow::taskEnd() diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 88b21a787..b29cc1137 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -119,21 +119,21 @@ struct Server { { QString name = QString(QObject::tr("%1 - Server %2")).arg(instance->name(), m_name); QStringList extraArgs{ "--server", m_address }; - ShortcutUtils::createInstanceShortcutOnDesktop(instance, name, QObject::tr("server"), parent, extraArgs); + ShortcutUtils::createInstanceShortcutOnDesktop({ instance, name, QObject::tr("server"), parent, extraArgs }); } void createServerShortcutInApplications(BaseInstance* instance, QWidget* parent = nullptr) const { QString name = QString(QObject::tr("%1 - Server %2")).arg(instance->name(), m_name); QStringList extraArgs{ "--server", m_address }; - ShortcutUtils::createInstanceShortcutInApplications(instance, name, QObject::tr("server"), parent, extraArgs); + ShortcutUtils::createInstanceShortcutInApplications({ instance, name, QObject::tr("server"), parent, extraArgs }); } void createServerShortcutInOther(BaseInstance* instance, QWidget* parent = nullptr) const { QString name = QString(QObject::tr("%1 - Server %2")).arg(instance->name(), m_name); QStringList extraArgs{ "--server", m_address }; - ShortcutUtils::createInstanceShortcutInOther(instance, name, QObject::tr("server"), parent, extraArgs); + ShortcutUtils::createInstanceShortcutInOther({ instance, name, QObject::tr("server"), parent, extraArgs }); } // Data - persistent and user changeable From 003d4226262d32c1369910f1f2d63eff33b0a6a6 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 13 May 2025 15:51:52 +0200 Subject: [PATCH 153/171] chore(readme): update Jetbrains logo Signed-off-by: Sefa Eyeoglu --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c4909509..361864dfe 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,13 @@ We thank all the wonderful backers over at Open Collective! Support Prism Launch Thanks to JetBrains for providing us a few licenses for all their products, as part of their [Open Source program](https://www.jetbrains.com/opensource/). -[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/opensource/) + + + + + JetBrains logo + + Thanks to Weblate for hosting our translation efforts. From bbfaaef31d991643ffb43a1173b383db06bea000 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 14 May 2025 09:55:55 +0300 Subject: [PATCH 154/171] chore: replace foreach macro Signed-off-by: Trial97 --- launcher/java/JavaUtils.cpp | 8 ++++---- launcher/ui/widgets/CheckComboBox.cpp | 2 +- launcher/ui/widgets/InfoFrame.cpp | 4 ++-- launcher/updater/MacSparkleUpdater.mm | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 072cb1d16..2d0560049 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -365,13 +365,13 @@ QList JavaUtils::FindJavaPaths() javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java"); QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/"); QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString& java, libraryJVMJavas) { + for (const QString& java : libraryJVMJavas) { javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java"); } QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/"); QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString& java, systemLibraryJVMJavas) { + for (const QString& java : systemLibraryJVMJavas) { javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); } @@ -381,14 +381,14 @@ QList JavaUtils::FindJavaPaths() // javas downloaded by sdkman QDir sdkmanDir(FS::PathCombine(home, ".sdkman/candidates/java")); QStringList sdkmanJavas = sdkmanDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString& java, sdkmanJavas) { + for (const QString& java : sdkmanJavas) { javas.append(sdkmanDir.absolutePath() + "/" + java + "/bin/java"); } // java in user library folder (like from intellij downloads) QDir userLibraryJVMDir(FS::PathCombine(home, "Library/Java/JavaVirtualMachines/")); QStringList userLibraryJVMJavas = userLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString& java, userLibraryJVMJavas) { + for (const QString& java : userLibraryJVMJavas) { javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); } diff --git a/launcher/ui/widgets/CheckComboBox.cpp b/launcher/ui/widgets/CheckComboBox.cpp index 02b629162..57d98ea7f 100644 --- a/launcher/ui/widgets/CheckComboBox.cpp +++ b/launcher/ui/widgets/CheckComboBox.cpp @@ -178,7 +178,7 @@ QStringList CheckComboBox::checkedItems() const void CheckComboBox::setCheckedItems(const QStringList& items) { - foreach (auto text, items) { + for (auto text : items) { auto index = findText(text); setItemCheckState(index, index != -1 ? Qt::Checked : Qt::Unchecked); } diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 3ef5dcb88..93520f611 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -287,7 +287,7 @@ void InfoFrame::setDescription(QString text) QChar rem('\n'); QString finaltext; finaltext.reserve(intermediatetext.size()); - foreach (const QChar& c, intermediatetext) { + for (const QChar& c : intermediatetext) { if (c == rem && prev) { continue; } @@ -341,7 +341,7 @@ void InfoFrame::setLicense(QString text) QChar rem('\n'); QString finaltext; finaltext.reserve(intermediatetext.size()); - foreach (const QChar& c, intermediatetext) { + for (const QChar& c : intermediatetext) { if (c == rem && prev) { continue; } diff --git a/launcher/updater/MacSparkleUpdater.mm b/launcher/updater/MacSparkleUpdater.mm index b2b631593..07862c9a3 100644 --- a/launcher/updater/MacSparkleUpdater.mm +++ b/launcher/updater/MacSparkleUpdater.mm @@ -166,7 +166,7 @@ void MacSparkleUpdater::setAllowedChannels(const QSet& channels) { QString channelsConfig = ""; // Convert QSet -> NSSet NSMutableSet* nsChannels = [NSMutableSet setWithCapacity:channels.count()]; - foreach (const QString channel, channels) { + for (const QString channel : channels) { [nsChannels addObject:channel.toNSString()]; channelsConfig += channel + " "; } From f3c253d7086e669aa7d56488cf0ad3227cc28368 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 15 May 2025 19:05:40 +0800 Subject: [PATCH 155/171] Fix menu issues Signed-off-by: Yihe Li --- launcher/ui/pages/instance/ServersPage.ui | 3 +++ launcher/ui/pages/instance/WorldListPage.ui | 3 +++ 2 files changed, 6 insertions(+) diff --git a/launcher/ui/pages/instance/ServersPage.ui b/launcher/ui/pages/instance/ServersPage.ui index e26152242..bb8bff5aa 100644 --- a/launcher/ui/pages/instance/ServersPage.ui +++ b/launcher/ui/pages/instance/ServersPage.ui @@ -135,6 +135,9 @@ Qt::ToolButtonTextOnly + + true + false diff --git a/launcher/ui/pages/instance/WorldListPage.ui b/launcher/ui/pages/instance/WorldListPage.ui index f4664d503..6d951cbdd 100644 --- a/launcher/ui/pages/instance/WorldListPage.ui +++ b/launcher/ui/pages/instance/WorldListPage.ui @@ -70,6 +70,9 @@ Qt::ToolButtonTextOnly + + true + false From bc1d1b41c0c8c8718fb175790d458744a5c9fe42 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 15 May 2025 19:12:15 +0800 Subject: [PATCH 156/171] Move menu creation to constructors to avoid performance issues Signed-off-by: Yihe Li --- launcher/ui/pages/instance/ServersPage.cpp | 42 ++++++++++---------- launcher/ui/pages/instance/WorldListPage.cpp | 39 +++++++++--------- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index b29cc1137..36844d92a 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -615,6 +615,26 @@ ServersPage::ServersPage(InstancePtr inst, QWidget* parent) : QMainWindow(parent connect(ui->resourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resourceIndexChanged(int))); connect(m_model, &QAbstractItemModel::rowsRemoved, this, &ServersPage::rowsRemoved); + QList shortcutActions = { ui->actionCreateServerShortcutOther }; + if (!DesktopServices::isFlatpak()) { + QString desktopDir = FS::getDesktopDir(); + QString applicationDir = FS::getApplicationsDir(); + + if (!applicationDir.isEmpty()) + shortcutActions.push_front(ui->actionCreateServerShortcutApplications); + + if (!desktopDir.isEmpty()) + shortcutActions.push_front(ui->actionCreateServerShortcutDesktop); + } + + if (shortcutActions.length() > 1) { + auto shortcutInstanceMenu = new QMenu(this); + + for (auto action : shortcutActions) + shortcutInstanceMenu->addAction(action); + ui->actionCreateServerShortcut->setMenu(shortcutInstanceMenu); + } + m_locked = m_inst->isRunning(); if (m_locked) { m_model->lock(); @@ -718,6 +738,7 @@ void ServersPage::updateState() ui->actionMove_Up->setEnabled(serverEditEnabled); ui->actionRemove->setEnabled(serverEditEnabled); ui->actionJoin->setEnabled(serverEditEnabled); + ui->actionCreateServerShortcut->setEnabled(serverEditEnabled); if (server) { ui->addressLine->setText(server->m_address); @@ -730,27 +751,6 @@ void ServersPage::updateState() } ui->actionAdd->setDisabled(m_locked); - - QList shortcutActions = { ui->actionCreateServerShortcutOther }; - if (!DesktopServices::isFlatpak()) { - QString desktopDir = FS::getDesktopDir(); - QString applicationDir = FS::getApplicationsDir(); - - if (!applicationDir.isEmpty()) - shortcutActions.push_front(ui->actionCreateServerShortcutApplications); - - if (!desktopDir.isEmpty()) - shortcutActions.push_front(ui->actionCreateServerShortcutDesktop); - } - - if (shortcutActions.length() > 1) { - auto shortcutInstanceMenu = new QMenu(this); - - for (auto action : shortcutActions) - shortcutInstanceMenu->addAction(action); - ui->actionCreateServerShortcut->setMenu(shortcutInstanceMenu); - } - ui->actionCreateServerShortcut->setEnabled(serverEditEnabled); } void ServersPage::openedImpl() diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index f6a1e0e5f..c770f9f23 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -89,6 +89,26 @@ WorldListPage::WorldListPage(InstancePtr inst, std::shared_ptr worlds ui->toolBar->insertSpacer(ui->actionRefresh); + QList shortcutActions = { ui->actionCreateWorldShortcutOther }; + if (!DesktopServices::isFlatpak()) { + QString desktopDir = FS::getDesktopDir(); + QString applicationDir = FS::getApplicationsDir(); + + if (!applicationDir.isEmpty()) + shortcutActions.push_front(ui->actionCreateWorldShortcutApplications); + + if (!desktopDir.isEmpty()) + shortcutActions.push_front(ui->actionCreateWorldShortcutDesktop); + } + + if (shortcutActions.length() > 1) { + auto shortcutInstanceMenu = new QMenu(this); + + for (auto action : shortcutActions) + shortcutInstanceMenu->addAction(action); + ui->actionCreateWorldShortcut->setMenu(shortcutInstanceMenu); + } + WorldListProxyModel* proxy = new WorldListProxyModel(this); proxy->setSortCaseSensitivity(Qt::CaseInsensitive); proxy->setSourceModel(m_worlds.get()); @@ -347,25 +367,6 @@ void WorldListPage::worldChanged([[maybe_unused]] const QModelIndex& current, [[ ui->toolBar->removeAction(ui->actionJoin); ui->toolBar->removeAction(ui->actionCreateWorldShortcut); } else { - QList shortcutActions = { ui->actionCreateWorldShortcutOther }; - if (!DesktopServices::isFlatpak()) { - QString desktopDir = FS::getDesktopDir(); - QString applicationDir = FS::getApplicationsDir(); - - if (!applicationDir.isEmpty()) - shortcutActions.push_front(ui->actionCreateWorldShortcutApplications); - - if (!desktopDir.isEmpty()) - shortcutActions.push_front(ui->actionCreateWorldShortcutDesktop); - } - - if (shortcutActions.length() > 1) { - auto shortcutInstanceMenu = new QMenu(this); - - for (auto action : shortcutActions) - shortcutInstanceMenu->addAction(action); - ui->actionCreateWorldShortcut->setMenu(shortcutInstanceMenu); - } ui->actionCreateWorldShortcut->setEnabled(enable); } } From d9c9eb6521fceb8a9555758633a4b4a5633ebc07 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 18 May 2025 20:41:11 +0800 Subject: [PATCH 157/171] Remove redundant assignment Signed-off-by: Yihe Li --- launcher/minecraft/ShortcutUtils.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/ShortcutUtils.cpp b/launcher/minecraft/ShortcutUtils.cpp index 2bbeacb08..cbf4f00e0 100644 --- a/launcher/minecraft/ShortcutUtils.cpp +++ b/launcher/minecraft/ShortcutUtils.cpp @@ -57,7 +57,6 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) QString iconPath; QStringList args; #if defined(Q_OS_MACOS) - appPath = QApplication::applicationFilePath(); if (appPath.startsWith("/private/var/")) { QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts.")); From 183f3d4e9a21ca0491dc0aa474020376dcb297cb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 11 May 2025 00:28:34 +0000 Subject: [PATCH 158/171] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/f771eb401a46846c1aebd20552521b233dd7e18b?narHash=sha256-ITSpPDwvLBZBnPRS2bUcHY3gZSwis/uTe255QgMtTLA%3D' (2025-04-24) → 'github:NixOS/nixpkgs/dda3dcd3fe03e991015e9a74b22d35950f264a54?narHash=sha256-Ua0drDHawlzNqJnclTJGf87dBmaO/tn7iZ%2BTCkTRpRc%3D' (2025-05-08) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 5418557a3..d0cc6f54c 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1745526057, - "narHash": "sha256-ITSpPDwvLBZBnPRS2bUcHY3gZSwis/uTe255QgMtTLA=", + "lastModified": 1746663147, + "narHash": "sha256-Ua0drDHawlzNqJnclTJGf87dBmaO/tn7iZ+TCkTRpRc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f771eb401a46846c1aebd20552521b233dd7e18b", + "rev": "dda3dcd3fe03e991015e9a74b22d35950f264a54", "type": "github" }, "original": { From 91e9e49d2ce637f8bca4f4cfd9570cff6d3d4b07 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 13 May 2025 15:51:52 +0200 Subject: [PATCH 159/171] chore(readme): update Jetbrains logo Signed-off-by: Sefa Eyeoglu --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c4909509..361864dfe 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,13 @@ We thank all the wonderful backers over at Open Collective! Support Prism Launch Thanks to JetBrains for providing us a few licenses for all their products, as part of their [Open Source program](https://www.jetbrains.com/opensource/). -[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/opensource/) + + + + + JetBrains logo + + Thanks to Weblate for hosting our translation efforts. From 8a60ec1c4a7779a78521a438b0e607959bba8fcb Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 14 May 2025 09:55:55 +0300 Subject: [PATCH 160/171] chore: replace foreach macro Signed-off-by: Trial97 --- launcher/java/JavaUtils.cpp | 8 ++++---- launcher/ui/widgets/CheckComboBox.cpp | 2 +- launcher/ui/widgets/InfoFrame.cpp | 4 ++-- launcher/updater/MacSparkleUpdater.mm | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 072cb1d16..2d0560049 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -365,13 +365,13 @@ QList JavaUtils::FindJavaPaths() javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java"); QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/"); QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString& java, libraryJVMJavas) { + for (const QString& java : libraryJVMJavas) { javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java"); } QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/"); QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString& java, systemLibraryJVMJavas) { + for (const QString& java : systemLibraryJVMJavas) { javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); } @@ -381,14 +381,14 @@ QList JavaUtils::FindJavaPaths() // javas downloaded by sdkman QDir sdkmanDir(FS::PathCombine(home, ".sdkman/candidates/java")); QStringList sdkmanJavas = sdkmanDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString& java, sdkmanJavas) { + for (const QString& java : sdkmanJavas) { javas.append(sdkmanDir.absolutePath() + "/" + java + "/bin/java"); } // java in user library folder (like from intellij downloads) QDir userLibraryJVMDir(FS::PathCombine(home, "Library/Java/JavaVirtualMachines/")); QStringList userLibraryJVMJavas = userLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString& java, userLibraryJVMJavas) { + for (const QString& java : userLibraryJVMJavas) { javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); } diff --git a/launcher/ui/widgets/CheckComboBox.cpp b/launcher/ui/widgets/CheckComboBox.cpp index 02b629162..57d98ea7f 100644 --- a/launcher/ui/widgets/CheckComboBox.cpp +++ b/launcher/ui/widgets/CheckComboBox.cpp @@ -178,7 +178,7 @@ QStringList CheckComboBox::checkedItems() const void CheckComboBox::setCheckedItems(const QStringList& items) { - foreach (auto text, items) { + for (auto text : items) { auto index = findText(text); setItemCheckState(index, index != -1 ? Qt::Checked : Qt::Unchecked); } diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 3ef5dcb88..93520f611 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -287,7 +287,7 @@ void InfoFrame::setDescription(QString text) QChar rem('\n'); QString finaltext; finaltext.reserve(intermediatetext.size()); - foreach (const QChar& c, intermediatetext) { + for (const QChar& c : intermediatetext) { if (c == rem && prev) { continue; } @@ -341,7 +341,7 @@ void InfoFrame::setLicense(QString text) QChar rem('\n'); QString finaltext; finaltext.reserve(intermediatetext.size()); - foreach (const QChar& c, intermediatetext) { + for (const QChar& c : intermediatetext) { if (c == rem && prev) { continue; } diff --git a/launcher/updater/MacSparkleUpdater.mm b/launcher/updater/MacSparkleUpdater.mm index b2b631593..07862c9a3 100644 --- a/launcher/updater/MacSparkleUpdater.mm +++ b/launcher/updater/MacSparkleUpdater.mm @@ -166,7 +166,7 @@ void MacSparkleUpdater::setAllowedChannels(const QSet& channels) { QString channelsConfig = ""; // Convert QSet -> NSSet NSMutableSet* nsChannels = [NSMutableSet setWithCapacity:channels.count()]; - foreach (const QString channel, channels) { + for (const QString channel : channels) { [nsChannels addObject:channel.toNSString()]; channelsConfig += channel + " "; } From 1c288543f2c05f0a9969da2e17c957566bf28584 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 18 May 2025 22:32:30 +0800 Subject: [PATCH 161/171] Initial UI for shortcut dialog Signed-off-by: Yihe Li --- launcher/ui/dialogs/CreateShortcutDialog.ui | 217 ++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 launcher/ui/dialogs/CreateShortcutDialog.ui diff --git a/launcher/ui/dialogs/CreateShortcutDialog.ui b/launcher/ui/dialogs/CreateShortcutDialog.ui new file mode 100644 index 000000000..26e34ecde --- /dev/null +++ b/launcher/ui/dialogs/CreateShortcutDialog.ui @@ -0,0 +1,217 @@ + + + CreateShortcutDialog + + + Qt::WindowModality::ApplicationModal + + + + 0 + 0 + 450 + 365 + + + + Create Instance Shortcut + + + true + + + + + + + + + :/icons/instances/grass:/icons/instances/grass + + + + 80 + 80 + + + + + + + + + + Save To: + + + + + + + + 0 + 0 + + + + + + + + Name: + + + + + + + Name + + + + + + + + + + + Use a different account than the default specified. + + + Override the default account + + + + + + + false + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + + + + + + + + Specify a world or server to automatically join on launch. + + + Select a target to join on launch + + + + + + + false + + + + 0 + 0 + + + + + + + World: + + + + + + + + 0 + 0 + + + + + + + + Server Address: + + + + + + + Server Address + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + + iconButton + + + + + buttonBox + accepted() + CreateShortcutDialog + accept() + + + 20 + 20 + + + 20 + 20 + + + + + buttonBox + rejected() + CreateShortcutDialog + reject() + + + 20 + 20 + + + 20 + 20 + + + + + From 0a5013ff9f795c74f293d43c092fceb88bb378aa Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 18 May 2025 22:54:57 +0800 Subject: [PATCH 162/171] Add source files for UI Signed-off-by: Yihe Li --- launcher/CMakeLists.txt | 3 + launcher/ui/MainWindow.cpp | 1 + launcher/ui/dialogs/CreateShortcutDialog.cpp | 70 ++++++++++++++++++++ launcher/ui/dialogs/CreateShortcutDialog.h | 56 ++++++++++++++++ launcher/ui/dialogs/CreateShortcutDialog.ui | 9 +++ 5 files changed, 139 insertions(+) create mode 100644 launcher/ui/dialogs/CreateShortcutDialog.cpp create mode 100644 launcher/ui/dialogs/CreateShortcutDialog.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 918e38df0..90a2093b6 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1050,6 +1050,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/ProfileSetupDialog.h ui/dialogs/CopyInstanceDialog.cpp ui/dialogs/CopyInstanceDialog.h + ui/dialogs/CreateShortcutDialog.cpp + ui/dialogs/CreateShortcutDialog.h ui/dialogs/CustomMessageBox.cpp ui/dialogs/CustomMessageBox.h ui/dialogs/ExportInstanceDialog.cpp @@ -1232,6 +1234,7 @@ qt_wrap_ui(LAUNCHER_UI ui/widgets/MinecraftSettingsWidget.ui ui/widgets/JavaSettingsWidget.ui ui/dialogs/CopyInstanceDialog.ui + ui/dialogs/CreateShortcutDialog.ui ui/dialogs/ProfileSetupDialog.ui ui/dialogs/ProgressDialog.ui ui/dialogs/NewInstanceDialog.ui diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index d64e92124..7cc28917a 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -93,6 +93,7 @@ #include "ui/GuiUtil.h" #include "ui/dialogs/AboutDialog.h" #include "ui/dialogs/CopyInstanceDialog.h" +#include "ui/dialogs/CreateShortcutDialog.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ExportInstanceDialog.h" #include "ui/dialogs/ExportPackDialog.h" diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp new file mode 100644 index 000000000..eda8eb214 --- /dev/null +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 TheKodeToad + * Copyright (C) 2025 Yihe Li + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "Application.h" +#include "BuildConfig.h" +#include "CreateShortcutDialog.h" +#include "ui_CreateShortcutDialog.h" + +#include "ui/dialogs/IconPickerDialog.h" + +#include "BaseInstance.h" +#include "DesktopServices.h" +#include "FileSystem.h" +#include "InstanceList.h" +#include "icons/IconList.h" + +CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent) + : QDialog(parent), ui(new Ui::CreateShortcutDialog), m_instance(instance) +{ + ui->setupUi(this); + resize(minimumSizeHint()); + layout()->setSizeConstraint(QLayout::SetFixedSize); + + InstIconKey = instance->iconKey(); + ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); + ui->instNameTextBox->setText(instance->name()); + ui->instNameTextBox->setFocus(); +} + +CreateShortcutDialog::~CreateShortcutDialog() +{ + delete ui; +} diff --git a/launcher/ui/dialogs/CreateShortcutDialog.h b/launcher/ui/dialogs/CreateShortcutDialog.h new file mode 100644 index 000000000..5fba78931 --- /dev/null +++ b/launcher/ui/dialogs/CreateShortcutDialog.h @@ -0,0 +1,56 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "BaseInstance.h" + +class BaseInstance; + +namespace Ui { +class CreateShortcutDialog; +} + +class CreateShortcutDialog : public QDialog { + Q_OBJECT + + public: + explicit CreateShortcutDialog(InstancePtr instance, QWidget* parent = nullptr); + ~CreateShortcutDialog(); + + private slots: + // Icon, target and name + void on_iconButton_clicked(); + void on_saveTargetSelectionBox_currentIndexChanged(int index); + void on_instNameTextBox_textChanged(const QString& arg1); + + // Override account + void on_overrideAccountCheckbox_stateChanged(int state); + void on_accountSelectionBox_currentIndexChanged(int index); + + // Override target (world, server) + void on_targetCheckbox_stateChanged(int state); + void on_worldSelectionBox_currentIndexChanged(int index); + void on_serverAddressTextBox_textChanged(const QString& arg1); + void targetChanged(); + + private: + /* data */ + Ui::CreateShortcutDialog* ui; + QString InstIconKey; + InstancePtr m_instance; + bool m_QuickJoinSupported = false; +}; diff --git a/launcher/ui/dialogs/CreateShortcutDialog.ui b/launcher/ui/dialogs/CreateShortcutDialog.ui index 26e34ecde..364c42b15 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.ui +++ b/launcher/ui/dialogs/CreateShortcutDialog.ui @@ -135,6 +135,9 @@ World: + + targetBtnGroup +
@@ -152,6 +155,9 @@ Server Address: + + targetBtnGroup + @@ -214,4 +220,7 @@ + + + From ea8f105292058289ec7350d61ab5fec1c3e94306 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 18 May 2025 23:03:14 +0800 Subject: [PATCH 163/171] Add stubs and b asic integration with MainWindow Signed-off-by: Yihe Li --- launcher/ui/MainWindow.cpp | 44 ++------------------ launcher/ui/MainWindow.h | 3 -- launcher/ui/MainWindow.ui | 33 --------------- launcher/ui/dialogs/CreateShortcutDialog.cpp | 44 ++++++++++++++++++-- launcher/ui/dialogs/CreateShortcutDialog.h | 2 + 5 files changed, 47 insertions(+), 79 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 7cc28917a..062175ece 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -110,7 +110,6 @@ #include "ui/widgets/LabeledToolButton.h" #include "minecraft/PackProfile.h" -#include "minecraft/ShortcutUtils.h" #include "minecraft/VersionFile.h" #include "minecraft/WorldList.h" #include "minecraft/mod/ModFolderModel.h" @@ -209,26 +208,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi exportInstanceMenu->addAction(ui->actionExportInstanceMrPack); exportInstanceMenu->addAction(ui->actionExportInstanceFlamePack); ui->actionExportInstance->setMenu(exportInstanceMenu); - - QList shortcutActions = { ui->actionCreateInstanceShortcutOther }; - if (!DesktopServices::isFlatpak()) { - QString desktopDir = FS::getDesktopDir(); - QString applicationDir = FS::getApplicationsDir(); - - if (!applicationDir.isEmpty()) - shortcutActions.push_front(ui->actionCreateInstanceShortcutApplications); - - if (!desktopDir.isEmpty()) - shortcutActions.push_front(ui->actionCreateInstanceShortcutDesktop); - } - - if (shortcutActions.length() > 1) { - auto shortcutInstanceMenu = new QMenu(this); - - for (auto action : shortcutActions) - shortcutInstanceMenu->addAction(action); - ui->actionCreateInstanceShortcut->setMenu(shortcutInstanceMenu); - } } // hide, disable and show stuff @@ -1552,25 +1531,10 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered() if (!m_selectedInstance) return; - if (DesktopServices::isFlatpak()) - on_actionCreateInstanceShortcutOther_triggered(); - else - on_actionCreateInstanceShortcutDesktop_triggered(); -} - -void MainWindow::on_actionCreateInstanceShortcutDesktop_triggered() -{ - ShortcutUtils::createInstanceShortcutOnDesktop({ m_selectedInstance.get(), m_selectedInstance->name(), tr("instance"), this }); -} - -void MainWindow::on_actionCreateInstanceShortcutApplications_triggered() -{ - ShortcutUtils::createInstanceShortcutInApplications({ m_selectedInstance.get(), m_selectedInstance->name(), tr("instance"), this }); -} - -void MainWindow::on_actionCreateInstanceShortcutOther_triggered() -{ - ShortcutUtils::createInstanceShortcutInOther({ m_selectedInstance.get(), m_selectedInstance->name(), tr("instance"), this }); + CreateShortcutDialog shortcutDlg(m_selectedInstance, this); + if (!shortcutDlg.exec()) + return; + shortcutDlg.createShortcut(); } void MainWindow::taskEnd() diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 20ab21e67..0e692eda7 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -166,9 +166,6 @@ class MainWindow : public QMainWindow { void on_actionEditInstance_triggered(); void on_actionCreateInstanceShortcut_triggered(); - void on_actionCreateInstanceShortcutDesktop_triggered(); - void on_actionCreateInstanceShortcutApplications_triggered(); - void on_actionCreateInstanceShortcutOther_triggered(); void taskEnd(); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 6530e2c5a..1499ec872 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -771,39 +771,6 @@ Open the Java folder in a file browser. Only available if the built-in Java downloader is used. - - - Desktop - - - Creates a shortcut to this instance on your desktop - - - QAction::TextHeuristicRole - - - - - Applications - - - Create a shortcut of this instance on your start menu - - - QAction::TextHeuristicRole - - - - - Other... - - - Creates a shortcut in a folder selected by you - - - QAction::TextHeuristicRole - - diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index eda8eb214..8b6ce8a04 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -50,21 +50,59 @@ #include "FileSystem.h" #include "InstanceList.h" #include "icons/IconList.h" +#include "minecraft/ShortcutUtils.h" CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent) : QDialog(parent), ui(new Ui::CreateShortcutDialog), m_instance(instance) { ui->setupUi(this); - resize(minimumSizeHint()); - layout()->setSizeConstraint(QLayout::SetFixedSize); InstIconKey = instance->iconKey(); ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); ui->instNameTextBox->setText(instance->name()); - ui->instNameTextBox->setFocus(); } CreateShortcutDialog::~CreateShortcutDialog() { delete ui; } + +void CreateShortcutDialog::on_iconButton_clicked() +{ + IconPickerDialog dlg(this); + dlg.execWithSelection(InstIconKey); + + if (dlg.result() == QDialog::Accepted) { + InstIconKey = dlg.selectedIconKey; + ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); + } +} + +void CreateShortcutDialog::on_saveTargetSelectionBox_currentIndexChanged(int index) +{} + +void CreateShortcutDialog::on_instNameTextBox_textChanged(const QString& arg1) +{} + +void CreateShortcutDialog::on_overrideAccountCheckbox_stateChanged(int state) +{} + +void CreateShortcutDialog::on_accountSelectionBox_currentIndexChanged(int index) +{} + +void CreateShortcutDialog::on_targetCheckbox_stateChanged(int state) +{} + +void CreateShortcutDialog::on_worldSelectionBox_currentIndexChanged(int index) +{} + +void CreateShortcutDialog::on_serverAddressTextBox_textChanged(const QString& arg1) +{} + +void CreateShortcutDialog::targetChanged() +{} + +// Real work +void CreateShortcutDialog::createShortcut() const +{} + diff --git a/launcher/ui/dialogs/CreateShortcutDialog.h b/launcher/ui/dialogs/CreateShortcutDialog.h index 5fba78931..be0a5e792 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.h +++ b/launcher/ui/dialogs/CreateShortcutDialog.h @@ -31,6 +31,8 @@ class CreateShortcutDialog : public QDialog { explicit CreateShortcutDialog(InstancePtr instance, QWidget* parent = nullptr); ~CreateShortcutDialog(); + void createShortcut() const; + private slots: // Icon, target and name void on_iconButton_clicked(); From b296085ea064f22cc21c32ff9d90e3c850dc56de Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 18 May 2025 23:32:23 +0800 Subject: [PATCH 164/171] Small adjustments Signed-off-by: Yihe Li --- launcher/ui/dialogs/CreateShortcutDialog.cpp | 47 ++++++++++++-------- launcher/ui/dialogs/CreateShortcutDialog.h | 8 ++-- launcher/ui/dialogs/CreateShortcutDialog.ui | 14 ++++-- 3 files changed, 43 insertions(+), 26 deletions(-) diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index 8b6ce8a04..38c22d861 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -60,6 +60,11 @@ CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent InstIconKey = instance->iconKey(); ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); ui->instNameTextBox->setText(instance->name()); + + m_QuickJoinSupported = instance->traits().contains("feature:is_quick_play_singleplayer"); + if (!m_QuickJoinSupported) { + // TODO: Remove radio box and add a single server address textbox instead + } } CreateShortcutDialog::~CreateShortcutDialog() @@ -78,31 +83,35 @@ void CreateShortcutDialog::on_iconButton_clicked() } } -void CreateShortcutDialog::on_saveTargetSelectionBox_currentIndexChanged(int index) -{} +void CreateShortcutDialog::on_saveTargetSelectionBox_currentIndexChanged(int index) {} -void CreateShortcutDialog::on_instNameTextBox_textChanged(const QString& arg1) -{} +void CreateShortcutDialog::on_instNameTextBox_textChanged(const QString& arg1) {} -void CreateShortcutDialog::on_overrideAccountCheckbox_stateChanged(int state) -{} +void CreateShortcutDialog::on_overrideAccountCheckbox_stateChanged(int state) {} -void CreateShortcutDialog::on_accountSelectionBox_currentIndexChanged(int index) -{} +void CreateShortcutDialog::on_accountSelectionBox_currentIndexChanged(int index) {} -void CreateShortcutDialog::on_targetCheckbox_stateChanged(int state) -{} +void CreateShortcutDialog::on_targetCheckbox_stateChanged(int state) {} -void CreateShortcutDialog::on_worldSelectionBox_currentIndexChanged(int index) -{} +void CreateShortcutDialog::on_worldSelectionBox_currentIndexChanged(int index) {} -void CreateShortcutDialog::on_serverAddressTextBox_textChanged(const QString& arg1) -{} +void CreateShortcutDialog::on_serverAddressBox_textChanged(const QString& arg1) {} -void CreateShortcutDialog::targetChanged() -{} +void CreateShortcutDialog::targetChanged() {} // Real work -void CreateShortcutDialog::createShortcut() const -{} - +void CreateShortcutDialog::createShortcut() +{ + QString targetString = tr("instance"); + QStringList extraArgs; + if (ui->targetCheckbox->isChecked()) { + if (ui->worldTarget->isChecked()) { + targetString = tr("world"); + extraArgs = { "--world", /* world ID */ }; + } else if (ui->serverTarget->isChecked()) { + targetString = tr("server"); + extraArgs = { "--server", /* server address */ }; + } + } + ShortcutUtils::createInstanceShortcutOnDesktop({ m_instance.get(), m_instance->name(), targetString, this, extraArgs }); +} diff --git a/launcher/ui/dialogs/CreateShortcutDialog.h b/launcher/ui/dialogs/CreateShortcutDialog.h index be0a5e792..7be6b339c 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.h +++ b/launcher/ui/dialogs/CreateShortcutDialog.h @@ -31,7 +31,7 @@ class CreateShortcutDialog : public QDialog { explicit CreateShortcutDialog(InstancePtr instance, QWidget* parent = nullptr); ~CreateShortcutDialog(); - void createShortcut() const; + void createShortcut(); private slots: // Icon, target and name @@ -46,13 +46,15 @@ class CreateShortcutDialog : public QDialog { // Override target (world, server) void on_targetCheckbox_stateChanged(int state); void on_worldSelectionBox_currentIndexChanged(int index); - void on_serverAddressTextBox_textChanged(const QString& arg1); + void on_serverAddressBox_textChanged(const QString& arg1); void targetChanged(); private: - /* data */ + // Data Ui::CreateShortcutDialog* ui; QString InstIconKey; InstancePtr m_instance; bool m_QuickJoinSupported = false; + + // Index representations }; diff --git a/launcher/ui/dialogs/CreateShortcutDialog.ui b/launcher/ui/dialogs/CreateShortcutDialog.ui index 364c42b15..e45a8428a 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.ui +++ b/launcher/ui/dialogs/CreateShortcutDialog.ui @@ -10,7 +10,7 @@ 0 0 450 - 365 + 370 @@ -161,9 +161,15 @@ - - - Server Address + + + + 0 + 0 + + + + true From 2e6981977b3b457b1e25b1d6d55fb8ad89d72632 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Mon, 19 May 2025 00:00:39 +0800 Subject: [PATCH 165/171] Add basic shortcut creation integration Signed-off-by: Yihe Li --- launcher/minecraft/ShortcutUtils.cpp | 24 +++--------- launcher/minecraft/ShortcutUtils.h | 1 + launcher/ui/dialogs/CreateShortcutDialog.cpp | 40 ++++++++++++++++---- launcher/ui/dialogs/CreateShortcutDialog.h | 3 +- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/launcher/minecraft/ShortcutUtils.cpp b/launcher/minecraft/ShortcutUtils.cpp index cbf4f00e0..ea2a988be 100644 --- a/launcher/minecraft/ShortcutUtils.cpp +++ b/launcher/minecraft/ShortcutUtils.cpp @@ -54,6 +54,10 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) return; QString appPath = QApplication::applicationFilePath(); + auto icon = APPLICATION->icons()->icon(shortcut.iconKey.isEmpty() ? shortcut.instance->iconKey() : shortcut.iconKey); + if (icon == nullptr) { + icon = APPLICATION->icons()->icon("grass"); + } QString iconPath; QStringList args; #if defined(Q_OS_MACOS) @@ -63,11 +67,6 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) return; } - auto pIcon = APPLICATION->icons()->icon(shortcut.instance->iconKey()); - if (pIcon == nullptr) { - pIcon = APPLICATION->icons()->icon("grass"); - } - iconPath = FS::PathCombine(shortcut.instance->instanceRoot(), "Icon.icns"); QFile iconFile(iconPath); @@ -76,9 +75,8 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) return; } - QIcon icon = pIcon->icon(); - - bool success = icon.pixmap(1024, 1024).save(iconPath, "ICNS"); + QIcon iconObj = icon->icon(); + bool success = iconObj.pixmap(1024, 1024).save(iconPath, "ICNS"); iconFile.close(); if (!success) { @@ -99,11 +97,6 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) } } - auto icon = APPLICATION->icons()->icon(shortcut.instance->iconKey()); - if (icon == nullptr) { - icon = APPLICATION->icons()->icon("grass"); - } - iconPath = FS::PathCombine(shortcut.instance->instanceRoot(), "icon.png"); QFile iconFile(iconPath); @@ -126,11 +119,6 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) } #elif defined(Q_OS_WIN) - auto icon = APPLICATION->icons()->icon(shortcut.instance->iconKey()); - if (icon == nullptr) { - icon = APPLICATION->icons()->icon("grass"); - } - iconPath = FS::PathCombine(shortcut.instance->instanceRoot(), "icon.ico"); // part of fix for weird bug involving the window icon being replaced diff --git a/launcher/minecraft/ShortcutUtils.h b/launcher/minecraft/ShortcutUtils.h index a21ccf06a..e3d2e283a 100644 --- a/launcher/minecraft/ShortcutUtils.h +++ b/launcher/minecraft/ShortcutUtils.h @@ -48,6 +48,7 @@ struct Shortcut { QString targetString; QWidget* parent = nullptr; QStringList extraArgs = {}; + QString iconKey = ""; }; /// Create an instance shortcut on the specified file path diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index 38c22d861..83057619e 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -63,8 +63,22 @@ CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent m_QuickJoinSupported = instance->traits().contains("feature:is_quick_play_singleplayer"); if (!m_QuickJoinSupported) { - // TODO: Remove radio box and add a single server address textbox instead + ui->worldTarget->hide(); + ui->worldSelectionBox->hide(); } + + // Populate save targets + if (!DesktopServices::isFlatpak()) { + QString desktopDir = FS::getDesktopDir(); + QString applicationDir = FS::getApplicationsDir(); + + if (!desktopDir.isEmpty()) + ui->saveTargetSelectionBox->addItem("Desktop", QVariant::fromValue(SaveTarget::Desktop)); + + if (!applicationDir.isEmpty()) + ui->saveTargetSelectionBox->addItem("Applications", QVariant::fromValue(SaveTarget::Applications)); + } + ui->saveTargetSelectionBox->addItem("Other...", QVariant::fromValue(SaveTarget::Other)); } CreateShortcutDialog::~CreateShortcutDialog() @@ -83,15 +97,17 @@ void CreateShortcutDialog::on_iconButton_clicked() } } -void CreateShortcutDialog::on_saveTargetSelectionBox_currentIndexChanged(int index) {} - -void CreateShortcutDialog::on_instNameTextBox_textChanged(const QString& arg1) {} - -void CreateShortcutDialog::on_overrideAccountCheckbox_stateChanged(int state) {} +void CreateShortcutDialog::on_overrideAccountCheckbox_stateChanged(int state) +{ + ui->accountOptionsGroup->setEnabled(state == Qt::Checked); +} void CreateShortcutDialog::on_accountSelectionBox_currentIndexChanged(int index) {} -void CreateShortcutDialog::on_targetCheckbox_stateChanged(int state) {} +void CreateShortcutDialog::on_targetCheckbox_stateChanged(int state) +{ + ui->targetOptionsGroup->setEnabled(state == Qt::Checked); +} void CreateShortcutDialog::on_worldSelectionBox_currentIndexChanged(int index) {} @@ -113,5 +129,13 @@ void CreateShortcutDialog::createShortcut() extraArgs = { "--server", /* server address */ }; } } - ShortcutUtils::createInstanceShortcutOnDesktop({ m_instance.get(), m_instance->name(), targetString, this, extraArgs }); + + auto target = ui->saveTargetSelectionBox->currentData().value(); + auto name = ui->instNameTextBox->text(); + if (target == SaveTarget::Desktop) + ShortcutUtils::createInstanceShortcutOnDesktop({ m_instance.get(), name, targetString, this, extraArgs, InstIconKey }); + else if (target == SaveTarget::Applications) + ShortcutUtils::createInstanceShortcutInApplications({ m_instance.get(), name, targetString, this, extraArgs, InstIconKey }); + else + ShortcutUtils::createInstanceShortcutInOther({ m_instance.get(), m_instance->name(), targetString, this, extraArgs, InstIconKey }); } diff --git a/launcher/ui/dialogs/CreateShortcutDialog.h b/launcher/ui/dialogs/CreateShortcutDialog.h index 7be6b339c..04849ebfa 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.h +++ b/launcher/ui/dialogs/CreateShortcutDialog.h @@ -36,8 +36,6 @@ class CreateShortcutDialog : public QDialog { private slots: // Icon, target and name void on_iconButton_clicked(); - void on_saveTargetSelectionBox_currentIndexChanged(int index); - void on_instNameTextBox_textChanged(const QString& arg1); // Override account void on_overrideAccountCheckbox_stateChanged(int state); @@ -57,4 +55,5 @@ class CreateShortcutDialog : public QDialog { bool m_QuickJoinSupported = false; // Index representations + enum class SaveTarget { Desktop, Applications, Other }; }; From 3529d295843812796d191c79bbbc3d3e08df4ed9 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Mon, 19 May 2025 01:05:08 +0800 Subject: [PATCH 166/171] Implement world and server selection Signed-off-by: Yihe Li --- launcher/ui/dialogs/CreateShortcutDialog.cpp | 70 +++++++++++++++++--- launcher/ui/dialogs/CreateShortcutDialog.h | 8 ++- launcher/ui/dialogs/CreateShortcutDialog.ui | 12 +--- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index 83057619e..9a41a7961 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -50,7 +50,9 @@ #include "FileSystem.h" #include "InstanceList.h" #include "icons/IconList.h" +#include "minecraft/MinecraftInstance.h" #include "minecraft/ShortcutUtils.h" +#include "minecraft/WorldList.h" CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent) : QDialog(parent), ui(new Ui::CreateShortcutDialog), m_instance(instance) @@ -59,12 +61,14 @@ CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent InstIconKey = instance->iconKey(); ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); - ui->instNameTextBox->setText(instance->name()); + ui->instNameTextBox->setPlaceholderText(instance->name()); - m_QuickJoinSupported = instance->traits().contains("feature:is_quick_play_singleplayer"); + auto mInst = std::dynamic_pointer_cast(instance); + m_QuickJoinSupported = mInst && mInst->traits().contains("feature:is_quick_play_singleplayer"); if (!m_QuickJoinSupported) { ui->worldTarget->hide(); ui->worldSelectionBox->hide(); + ui->serverTarget->setChecked(true); } // Populate save targets @@ -79,6 +83,18 @@ CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent ui->saveTargetSelectionBox->addItem("Applications", QVariant::fromValue(SaveTarget::Applications)); } ui->saveTargetSelectionBox->addItem("Other...", QVariant::fromValue(SaveTarget::Other)); + + // Populate worlds + if (m_QuickJoinSupported) { + auto worldList = mInst->worldList(); + worldList->update(); + for (const auto& world : worldList->allWorlds()) { + // Entry name: World Name [Game Mode] - Last Played: DateTime + QString entry_name = tr("%1 [%2] - Last Played: %3") + .arg(world.name(), world.gameType().toTranslatedString(), world.lastPlayed().toString(Qt::ISODate)); + ui->worldSelectionBox->addItem(entry_name, world.name()); + } + } } CreateShortcutDialog::~CreateShortcutDialog() @@ -107,13 +123,49 @@ void CreateShortcutDialog::on_accountSelectionBox_currentIndexChanged(int index) void CreateShortcutDialog::on_targetCheckbox_stateChanged(int state) { ui->targetOptionsGroup->setEnabled(state == Qt::Checked); + ui->worldSelectionBox->setEnabled(ui->worldTarget->isChecked()); + ui->serverAddressBox->setEnabled(ui->serverTarget->isChecked()); + stateChanged(); } -void CreateShortcutDialog::on_worldSelectionBox_currentIndexChanged(int index) {} +void CreateShortcutDialog::on_worldTarget_toggled(bool checked) +{ + ui->worldSelectionBox->setEnabled(checked); + stateChanged(); +} -void CreateShortcutDialog::on_serverAddressBox_textChanged(const QString& arg1) {} +void CreateShortcutDialog::on_serverTarget_toggled(bool checked) +{ + ui->serverAddressBox->setEnabled(checked); + stateChanged(); +} -void CreateShortcutDialog::targetChanged() {} +void CreateShortcutDialog::on_worldSelectionBox_currentIndexChanged(int index) +{ + stateChanged(); +} + +void CreateShortcutDialog::on_serverAddressBox_textChanged(const QString& text) +{ + stateChanged(); +} + +void CreateShortcutDialog::stateChanged() +{ + QString result = m_instance->name(); + if (ui->targetCheckbox->isChecked()) { + if (ui->worldTarget->isChecked()) + result = tr("%1 - %2").arg(result, ui->worldSelectionBox->currentData().toString()); + else if (ui->serverTarget->isChecked()) + result = tr("%1 - Server %2").arg(result, ui->serverAddressBox->text()); + } + ui->instNameTextBox->setPlaceholderText(result); + if (!ui->targetCheckbox->isChecked()) + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + else + ui->buttonBox->button(QDialogButtonBox::Ok) + ->setEnabled(ui->worldTarget->isChecked() || (ui->serverTarget->isChecked() && !ui->serverAddressBox->text().isEmpty())); +} // Real work void CreateShortcutDialog::createShortcut() @@ -123,19 +175,21 @@ void CreateShortcutDialog::createShortcut() if (ui->targetCheckbox->isChecked()) { if (ui->worldTarget->isChecked()) { targetString = tr("world"); - extraArgs = { "--world", /* world ID */ }; + extraArgs = { "--world", ui->worldSelectionBox->currentData().toString() }; } else if (ui->serverTarget->isChecked()) { targetString = tr("server"); - extraArgs = { "--server", /* server address */ }; + extraArgs = { "--server", ui->serverAddressBox->text() }; } } auto target = ui->saveTargetSelectionBox->currentData().value(); auto name = ui->instNameTextBox->text(); + if (name.isEmpty()) + name = ui->instNameTextBox->placeholderText(); if (target == SaveTarget::Desktop) ShortcutUtils::createInstanceShortcutOnDesktop({ m_instance.get(), name, targetString, this, extraArgs, InstIconKey }); else if (target == SaveTarget::Applications) ShortcutUtils::createInstanceShortcutInApplications({ m_instance.get(), name, targetString, this, extraArgs, InstIconKey }); else - ShortcutUtils::createInstanceShortcutInOther({ m_instance.get(), m_instance->name(), targetString, this, extraArgs, InstIconKey }); + ShortcutUtils::createInstanceShortcutInOther({ m_instance.get(), name, targetString, this, extraArgs, InstIconKey }); } diff --git a/launcher/ui/dialogs/CreateShortcutDialog.h b/launcher/ui/dialogs/CreateShortcutDialog.h index 04849ebfa..c26005304 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.h +++ b/launcher/ui/dialogs/CreateShortcutDialog.h @@ -43,9 +43,10 @@ class CreateShortcutDialog : public QDialog { // Override target (world, server) void on_targetCheckbox_stateChanged(int state); + void on_worldTarget_toggled(bool checked); + void on_serverTarget_toggled(bool checked); void on_worldSelectionBox_currentIndexChanged(int index); - void on_serverAddressBox_textChanged(const QString& arg1); - void targetChanged(); + void on_serverAddressBox_textChanged(const QString& text); private: // Data @@ -56,4 +57,7 @@ class CreateShortcutDialog : public QDialog { // Index representations enum class SaveTarget { Desktop, Applications, Other }; + + // Functions + void stateChanged(); }; diff --git a/launcher/ui/dialogs/CreateShortcutDialog.ui b/launcher/ui/dialogs/CreateShortcutDialog.ui index e45a8428a..2a90f43ab 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.ui +++ b/launcher/ui/dialogs/CreateShortcutDialog.ui @@ -161,15 +161,9 @@
- - - - 0 - 0 - - - - true + + + Server Address From 4839595a117ee65e952f7c18ffb4231136b7a084 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Mon, 19 May 2025 01:29:23 +0800 Subject: [PATCH 167/171] Implement account override Signed-off-by: Yihe Li --- launcher/ui/dialogs/CreateShortcutDialog.cpp | 33 +++++++++++++++++--- launcher/ui/dialogs/CreateShortcutDialog.h | 1 - 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index 9a41a7961..6bc2b80cf 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -50,9 +50,11 @@ #include "FileSystem.h" #include "InstanceList.h" #include "icons/IconList.h" + #include "minecraft/MinecraftInstance.h" #include "minecraft/ShortcutUtils.h" #include "minecraft/WorldList.h" +#include "minecraft/auth/AccountList.h" CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent) : QDialog(parent), ui(new Ui::CreateShortcutDialog), m_instance(instance) @@ -95,6 +97,25 @@ CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent ui->worldSelectionBox->addItem(entry_name, world.name()); } } + + // Populate accounts + auto accounts = APPLICATION->accounts(); + MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); + if (accounts->count() <= 0) { + ui->overrideAccountCheckbox->setEnabled(false); + } else + for (int i = 0; i < accounts->count(); i++) { + MinecraftAccountPtr account = accounts->at(i); + auto profileLabel = account->profileName(); + if (account->isInUse()) + profileLabel = tr("%1 (in use)").arg(profileLabel); + auto face = account->getFace(); + QIcon icon = face.isNull() ? APPLICATION->getThemedIcon("noaccount") : face; + ui->accountSelectionBox->addItem(profileLabel, account->profileName()); + ui->accountSelectionBox->setItemIcon(i, icon); + if (defaultAccount == account) + ui->accountSelectionBox->setCurrentIndex(i); + } } CreateShortcutDialog::~CreateShortcutDialog() @@ -118,8 +139,6 @@ void CreateShortcutDialog::on_overrideAccountCheckbox_stateChanged(int state) ui->accountOptionsGroup->setEnabled(state == Qt::Checked); } -void CreateShortcutDialog::on_accountSelectionBox_currentIndexChanged(int index) {} - void CreateShortcutDialog::on_targetCheckbox_stateChanged(int state) { ui->targetOptionsGroup->setEnabled(state == Qt::Checked); @@ -186,10 +205,14 @@ void CreateShortcutDialog::createShortcut() auto name = ui->instNameTextBox->text(); if (name.isEmpty()) name = ui->instNameTextBox->placeholderText(); + if (ui->overrideAccountCheckbox->isChecked()) + extraArgs.append({ "--profile", ui->accountSelectionBox->currentData().toString() }); + + ShortcutUtils::Shortcut args{ m_instance.get(), name, targetString, this, extraArgs, InstIconKey }; if (target == SaveTarget::Desktop) - ShortcutUtils::createInstanceShortcutOnDesktop({ m_instance.get(), name, targetString, this, extraArgs, InstIconKey }); + ShortcutUtils::createInstanceShortcutOnDesktop(args); else if (target == SaveTarget::Applications) - ShortcutUtils::createInstanceShortcutInApplications({ m_instance.get(), name, targetString, this, extraArgs, InstIconKey }); + ShortcutUtils::createInstanceShortcutInApplications(args); else - ShortcutUtils::createInstanceShortcutInOther({ m_instance.get(), name, targetString, this, extraArgs, InstIconKey }); + ShortcutUtils::createInstanceShortcutInOther(args); } diff --git a/launcher/ui/dialogs/CreateShortcutDialog.h b/launcher/ui/dialogs/CreateShortcutDialog.h index c26005304..cfedbf017 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.h +++ b/launcher/ui/dialogs/CreateShortcutDialog.h @@ -39,7 +39,6 @@ class CreateShortcutDialog : public QDialog { // Override account void on_overrideAccountCheckbox_stateChanged(int state); - void on_accountSelectionBox_currentIndexChanged(int index); // Override target (world, server) void on_targetCheckbox_stateChanged(int state); From 46c9eb1d5fb2df8ebe10f4a57d41e9172b3aca84 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Mon, 19 May 2025 01:33:15 +0800 Subject: [PATCH 168/171] Remove button additions Signed-off-by: Yihe Li --- launcher/minecraft/WorldList.cpp | 38 ---------- launcher/minecraft/WorldList.h | 5 -- launcher/ui/pages/instance/ServersPage.cpp | 75 -------------------- launcher/ui/pages/instance/ServersPage.h | 4 -- launcher/ui/pages/instance/ServersPage.ui | 48 +------------ launcher/ui/pages/instance/WorldListPage.cpp | 59 --------------- launcher/ui/pages/instance/WorldListPage.h | 4 -- launcher/ui/pages/instance/WorldListPage.ui | 47 +----------- 8 files changed, 2 insertions(+), 278 deletions(-) diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 582531577..6a821ba60 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -46,9 +46,6 @@ #include #include -#include -#include "minecraft/ShortcutUtils.h" - WorldList::WorldList(const QString& dir, BaseInstance* instance) : QAbstractListModel(), m_instance(instance), m_dir(dir) { FS::ensureFolderPathExists(m_dir.absolutePath()); @@ -457,39 +454,4 @@ void WorldList::loadWorldsAsync() } } -void WorldList::createWorldShortcut(const QModelIndex& index, QWidget* parent) const -{ - if (!m_instance) - return; - - if (DesktopServices::isFlatpak()) - createWorldShortcutInOther(index, parent); - else - createWorldShortcutOnDesktop(index, parent); -} - -void WorldList::createWorldShortcutOnDesktop(const QModelIndex& index, QWidget* parent) const -{ - const auto& world = allWorlds().at(index.row()); - QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world.name()); - QStringList extraArgs{ "--world", world.name() }; - ShortcutUtils::createInstanceShortcutOnDesktop({ m_instance, name, tr("world"), parent, extraArgs }); -} - -void WorldList::createWorldShortcutInApplications(const QModelIndex& index, QWidget* parent) const -{ - const auto& world = allWorlds().at(index.row()); - QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world.name()); - QStringList extraArgs{ "--world", world.name() }; - ShortcutUtils::createInstanceShortcutInApplications({ m_instance, name, tr("world"), parent, extraArgs }); -} - -void WorldList::createWorldShortcutInOther(const QModelIndex& index, QWidget* parent) const -{ - const auto& world = allWorlds().at(index.row()); - QString name = QString(tr("%1 - %2")).arg(m_instance->name(), world.name()); - QStringList extraArgs{ "--world", world.name() }; - ShortcutUtils::createInstanceShortcutInOther({ m_instance, name, tr("world"), parent, extraArgs }); -} - #include "WorldList.moc" diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h index 4f54e0737..93fecf1f5 100644 --- a/launcher/minecraft/WorldList.h +++ b/launcher/minecraft/WorldList.h @@ -84,11 +84,6 @@ class WorldList : public QAbstractListModel { const QList& allWorlds() const { return m_worlds; } - void createWorldShortcut(const QModelIndex& index, QWidget* parent = nullptr) const; - void createWorldShortcutOnDesktop(const QModelIndex& index, QWidget* parent = nullptr) const; - void createWorldShortcutInApplications(const QModelIndex& index, QWidget* parent = nullptr) const; - void createWorldShortcutInOther(const QModelIndex& index, QWidget* parent = nullptr) const; - private slots: void directoryChanged(QString path); void loadWorldsAsync(); diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 36844d92a..245bbffe2 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -40,10 +40,8 @@ #include "ui/dialogs/CustomMessageBox.h" #include "ui_ServersPage.h" -#include #include #include -#include #include #include #include @@ -104,38 +102,6 @@ struct Server { } } - void createServerShortcut(BaseInstance* instance, QWidget* parent = nullptr) const - { - if (!instance) - return; - - if (DesktopServices::isFlatpak()) - createServerShortcutInOther(instance, parent); - else - createServerShortcutOnDesktop(instance, parent); - } - - void createServerShortcutOnDesktop(BaseInstance* instance, QWidget* parent = nullptr) const - { - QString name = QString(QObject::tr("%1 - Server %2")).arg(instance->name(), m_name); - QStringList extraArgs{ "--server", m_address }; - ShortcutUtils::createInstanceShortcutOnDesktop({ instance, name, QObject::tr("server"), parent, extraArgs }); - } - - void createServerShortcutInApplications(BaseInstance* instance, QWidget* parent = nullptr) const - { - QString name = QString(QObject::tr("%1 - Server %2")).arg(instance->name(), m_name); - QStringList extraArgs{ "--server", m_address }; - ShortcutUtils::createInstanceShortcutInApplications({ instance, name, QObject::tr("server"), parent, extraArgs }); - } - - void createServerShortcutInOther(BaseInstance* instance, QWidget* parent = nullptr) const - { - QString name = QString(QObject::tr("%1 - Server %2")).arg(instance->name(), m_name); - QStringList extraArgs{ "--server", m_address }; - ShortcutUtils::createInstanceShortcutInOther({ instance, name, QObject::tr("server"), parent, extraArgs }); - } - // Data - persistent and user changeable QString m_name; QString m_address; @@ -615,26 +581,6 @@ ServersPage::ServersPage(InstancePtr inst, QWidget* parent) : QMainWindow(parent connect(ui->resourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resourceIndexChanged(int))); connect(m_model, &QAbstractItemModel::rowsRemoved, this, &ServersPage::rowsRemoved); - QList shortcutActions = { ui->actionCreateServerShortcutOther }; - if (!DesktopServices::isFlatpak()) { - QString desktopDir = FS::getDesktopDir(); - QString applicationDir = FS::getApplicationsDir(); - - if (!applicationDir.isEmpty()) - shortcutActions.push_front(ui->actionCreateServerShortcutApplications); - - if (!desktopDir.isEmpty()) - shortcutActions.push_front(ui->actionCreateServerShortcutDesktop); - } - - if (shortcutActions.length() > 1) { - auto shortcutInstanceMenu = new QMenu(this); - - for (auto action : shortcutActions) - shortcutInstanceMenu->addAction(action); - ui->actionCreateServerShortcut->setMenu(shortcutInstanceMenu); - } - m_locked = m_inst->isRunning(); if (m_locked) { m_model->lock(); @@ -738,7 +684,6 @@ void ServersPage::updateState() ui->actionMove_Up->setEnabled(serverEditEnabled); ui->actionRemove->setEnabled(serverEditEnabled); ui->actionJoin->setEnabled(serverEditEnabled); - ui->actionCreateServerShortcut->setEnabled(serverEditEnabled); if (server) { ui->addressLine->setText(server->m_address); @@ -822,26 +767,6 @@ void ServersPage::on_actionJoin_triggered() APPLICATION->launch(m_inst, true, false, std::make_shared(MinecraftTarget::parse(address, false))); } -void ServersPage::on_actionCreateServerShortcut_triggered() -{ - m_model->at(currentServer)->createServerShortcut(m_inst.get(), this); -} - -void ServersPage::on_actionCreateServerShortcutDesktop_triggered() -{ - m_model->at(currentServer)->createServerShortcutOnDesktop(m_inst.get(), this); -} - -void ServersPage::on_actionCreateServerShortcutApplications_triggered() -{ - m_model->at(currentServer)->createServerShortcutInApplications(m_inst.get(), this); -} - -void ServersPage::on_actionCreateServerShortcutOther_triggered() -{ - m_model->at(currentServer)->createServerShortcutInOther(m_inst.get(), this); -} - void ServersPage::on_actionRefresh_triggered() { m_model->queryServersStatus(); diff --git a/launcher/ui/pages/instance/ServersPage.h b/launcher/ui/pages/instance/ServersPage.h index 94baaa004..77710d6cc 100644 --- a/launcher/ui/pages/instance/ServersPage.h +++ b/launcher/ui/pages/instance/ServersPage.h @@ -85,10 +85,6 @@ class ServersPage : public QMainWindow, public BasePage { void on_actionMove_Up_triggered(); void on_actionMove_Down_triggered(); void on_actionJoin_triggered(); - void on_actionCreateServerShortcut_triggered(); - void on_actionCreateServerShortcutDesktop_triggered(); - void on_actionCreateServerShortcutApplications_triggered(); - void on_actionCreateServerShortcutOther_triggered(); void on_actionRefresh_triggered(); void runningStateChanged(bool running); diff --git a/launcher/ui/pages/instance/ServersPage.ui b/launcher/ui/pages/instance/ServersPage.ui index bb8bff5aa..d330835c8 100644 --- a/launcher/ui/pages/instance/ServersPage.ui +++ b/launcher/ui/pages/instance/ServersPage.ui @@ -135,9 +135,6 @@ Qt::ToolButtonTextOnly - - true - false @@ -148,12 +145,10 @@ false - - - + @@ -182,47 +177,6 @@ Join - - - Create Shortcut - - - Creates a shortcut on a selected folder to join the selected server. - - - - - Desktop - - - Creates a shortcut to this server on your desktop - - - QAction::TextHeuristicRole - - - - - Applications - - - Create a shortcut of this server on your start menu - - - QAction::TextHeuristicRole - - - - - Other... - - - Creates a shortcut in a folder selected by you - - - QAction::TextHeuristicRole - - Refresh diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index c770f9f23..9e1a0fb55 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -89,26 +89,6 @@ WorldListPage::WorldListPage(InstancePtr inst, std::shared_ptr worlds ui->toolBar->insertSpacer(ui->actionRefresh); - QList shortcutActions = { ui->actionCreateWorldShortcutOther }; - if (!DesktopServices::isFlatpak()) { - QString desktopDir = FS::getDesktopDir(); - QString applicationDir = FS::getApplicationsDir(); - - if (!applicationDir.isEmpty()) - shortcutActions.push_front(ui->actionCreateWorldShortcutApplications); - - if (!desktopDir.isEmpty()) - shortcutActions.push_front(ui->actionCreateWorldShortcutDesktop); - } - - if (shortcutActions.length() > 1) { - auto shortcutInstanceMenu = new QMenu(this); - - for (auto action : shortcutActions) - shortcutInstanceMenu->addAction(action); - ui->actionCreateWorldShortcut->setMenu(shortcutInstanceMenu); - } - WorldListProxyModel* proxy = new WorldListProxyModel(this); proxy->setSortCaseSensitivity(Qt::CaseInsensitive); proxy->setSourceModel(m_worlds.get()); @@ -365,9 +345,6 @@ void WorldListPage::worldChanged([[maybe_unused]] const QModelIndex& current, [[ if (!supportsJoin) { ui->toolBar->removeAction(ui->actionJoin); - ui->toolBar->removeAction(ui->actionCreateWorldShortcut); - } else { - ui->actionCreateWorldShortcut->setEnabled(enable); } } @@ -443,42 +420,6 @@ void WorldListPage::on_actionRename_triggered() } } -void WorldListPage::on_actionCreateWorldShortcut_triggered() -{ - QModelIndex index = getSelectedWorld(); - if (!index.isValid()) { - return; - } - m_worlds->createWorldShortcut(index, this); -} - -void WorldListPage::on_actionCreateWorldShortcutDesktop_triggered() -{ - QModelIndex index = getSelectedWorld(); - if (!index.isValid()) { - return; - } - m_worlds->createWorldShortcutOnDesktop(index, this); -} - -void WorldListPage::on_actionCreateWorldShortcutApplications_triggered() -{ - QModelIndex index = getSelectedWorld(); - if (!index.isValid()) { - return; - } - m_worlds->createWorldShortcutInApplications(index, this); -} - -void WorldListPage::on_actionCreateWorldShortcutOther_triggered() -{ - QModelIndex index = getSelectedWorld(); - if (!index.isValid()) { - return; - } - m_worlds->createWorldShortcutInOther(index, this); -} - void WorldListPage::on_actionRefresh_triggered() { m_worlds->update(); diff --git a/launcher/ui/pages/instance/WorldListPage.h b/launcher/ui/pages/instance/WorldListPage.h index f2c081bc5..84d9cd075 100644 --- a/launcher/ui/pages/instance/WorldListPage.h +++ b/launcher/ui/pages/instance/WorldListPage.h @@ -95,10 +95,6 @@ class WorldListPage : public QMainWindow, public BasePage { void on_actionAdd_triggered(); void on_actionCopy_triggered(); void on_actionRename_triggered(); - void on_actionCreateWorldShortcut_triggered(); - void on_actionCreateWorldShortcutDesktop_triggered(); - void on_actionCreateWorldShortcutApplications_triggered(); - void on_actionCreateWorldShortcutOther_triggered(); void on_actionRefresh_triggered(); void on_actionView_Folder_triggered(); void on_actionDatapacks_triggered(); diff --git a/launcher/ui/pages/instance/WorldListPage.ui b/launcher/ui/pages/instance/WorldListPage.ui index 6d951cbdd..04344b453 100644 --- a/launcher/ui/pages/instance/WorldListPage.ui +++ b/launcher/ui/pages/instance/WorldListPage.ui @@ -70,9 +70,6 @@ Qt::ToolButtonTextOnly - - true - false @@ -88,11 +85,10 @@ - - + @@ -122,14 +118,6 @@ Delete - - - Create Shortcut - - - Creates a shortcut on a selected folder to join the selected world. - - MCEdit @@ -166,39 +154,6 @@ Manage datapacks inside the world. - - - Desktop - - - Creates a shortcut to this world on your desktop - - - QAction::TextHeuristicRole - - - - - Applications - - - Create a shortcut of this world on your start menu - - - QAction::TextHeuristicRole - - - - - Other... - - - Creates a shortcut in a folder selected by you - - - QAction::TextHeuristicRole - - From 3745bdb6f2725d51e24692f25fc4965a325ecde9 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Mon, 19 May 2025 01:46:23 +0800 Subject: [PATCH 169/171] Special treatment of non-Quick Join worlds Signed-off-by: Yihe Li --- launcher/ui/dialogs/CreateShortcutDialog.cpp | 2 + launcher/ui/dialogs/CreateShortcutDialog.ui | 52 ++++++++++++++------ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index 6bc2b80cf..6fb6e7050 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -71,6 +71,8 @@ CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent ui->worldTarget->hide(); ui->worldSelectionBox->hide(); ui->serverTarget->setChecked(true); + ui->serverTarget->hide(); + ui->serverLabel->show(); } // Populate save targets diff --git a/launcher/ui/dialogs/CreateShortcutDialog.ui b/launcher/ui/dialogs/CreateShortcutDialog.ui index 2a90f43ab..9e2bdd747 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.ui +++ b/launcher/ui/dialogs/CreateShortcutDialog.ui @@ -131,14 +131,21 @@ - - - World: + + + 0 - - targetBtnGroup - - + + + + World: + + + targetBtnGroup + + + + @@ -151,14 +158,31 @@ - - - Server Address: + + + 0 - - targetBtnGroup - - + + + + Server Address: + + + targetBtnGroup + + + + + + + false + + + Server Address: + + + + From a89caf7362d595a66c37d06a77f56270042784f4 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 22 May 2025 23:09:29 +0800 Subject: [PATCH 170/171] Apply suggestions from review Signed-off-by: Yihe Li --- launcher/minecraft/ShortcutUtils.cpp | 2 +- launcher/ui/dialogs/CreateShortcutDialog.cpp | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/launcher/minecraft/ShortcutUtils.cpp b/launcher/minecraft/ShortcutUtils.cpp index ea2a988be..43954aa6a 100644 --- a/launcher/minecraft/ShortcutUtils.cpp +++ b/launcher/minecraft/ShortcutUtils.cpp @@ -149,7 +149,7 @@ void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) args.append({ "--launch", shortcut.instance->id() }); args.append(shortcut.extraArgs); - if (!FS::createShortcut(std::move(filePath), appPath, args, shortcut.name, iconPath)) { + if (!FS::createShortcut(filePath, appPath, args, shortcut.name, iconPath)) { #if not defined(Q_OS_MACOS) iconFile.remove(); #endif diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index 6fb6e7050..581ac29e3 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -81,12 +81,12 @@ CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent QString applicationDir = FS::getApplicationsDir(); if (!desktopDir.isEmpty()) - ui->saveTargetSelectionBox->addItem("Desktop", QVariant::fromValue(SaveTarget::Desktop)); + ui->saveTargetSelectionBox->addItem(tr("Desktop"), QVariant::fromValue(SaveTarget::Desktop)); if (!applicationDir.isEmpty()) - ui->saveTargetSelectionBox->addItem("Applications", QVariant::fromValue(SaveTarget::Applications)); + ui->saveTargetSelectionBox->addItem(tr("Applications"), QVariant::fromValue(SaveTarget::Applications)); } - ui->saveTargetSelectionBox->addItem("Other...", QVariant::fromValue(SaveTarget::Other)); + ui->saveTargetSelectionBox->addItem(tr("Other..."), QVariant::fromValue(SaveTarget::Other)); // Populate worlds if (m_QuickJoinSupported) { @@ -105,7 +105,7 @@ CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); if (accounts->count() <= 0) { ui->overrideAccountCheckbox->setEnabled(false); - } else + } else { for (int i = 0; i < accounts->count(); i++) { MinecraftAccountPtr account = accounts->at(i); auto profileLabel = account->profileName(); @@ -118,6 +118,7 @@ CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent if (defaultAccount == account) ui->accountSelectionBox->setCurrentIndex(i); } + } } CreateShortcutDialog::~CreateShortcutDialog() @@ -183,9 +184,11 @@ void CreateShortcutDialog::stateChanged() ui->instNameTextBox->setPlaceholderText(result); if (!ui->targetCheckbox->isChecked()) ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - else + else { ui->buttonBox->button(QDialogButtonBox::Ok) - ->setEnabled(ui->worldTarget->isChecked() || (ui->serverTarget->isChecked() && !ui->serverAddressBox->text().isEmpty())); + ->setEnabled((ui->worldTarget->isChecked() && ui->worldSelectionBox->currentIndex() != -1) || + (ui->serverTarget->isChecked() && !ui->serverAddressBox->text().isEmpty())); + } } // Real work From 8425861fb14f1c47ea60a9d99d79ee9c857c3f26 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Fri, 23 May 2025 00:53:03 +0800 Subject: [PATCH 171/171] Just disable world selection when there is no world Signed-off-by: Yihe Li --- launcher/ui/dialogs/CreateShortcutDialog.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index 581ac29e3..278573a22 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -67,7 +67,9 @@ CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent auto mInst = std::dynamic_pointer_cast(instance); m_QuickJoinSupported = mInst && mInst->traits().contains("feature:is_quick_play_singleplayer"); - if (!m_QuickJoinSupported) { + auto worldList = mInst->worldList(); + worldList->update(); + if (!m_QuickJoinSupported || worldList->empty()) { ui->worldTarget->hide(); ui->worldSelectionBox->hide(); ui->serverTarget->setChecked(true); @@ -90,8 +92,6 @@ CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent // Populate worlds if (m_QuickJoinSupported) { - auto worldList = mInst->worldList(); - worldList->update(); for (const auto& world : worldList->allWorlds()) { // Entry name: World Name [Game Mode] - Last Played: DateTime QString entry_name = tr("%1 [%2] - Last Played: %3")