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 01/97] 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 02/97] 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 03/97] 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 04/97] 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 05/97] 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 06/97] 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 07/97] 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 08/97] 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 09/97] 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 10/97] 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 11/97] 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 12/97] 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 13/97] 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 e3ff9630e984ee3284639cef6d1dcb2356dcd93d Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Apr 2025 13:15:00 +0300 Subject: [PATCH 14/97] 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 facc48d0f8e58db46f67c444a59904391cad7ce3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 27 Apr 2025 00:28:07 +0000 Subject: [PATCH 15/97] 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 5c8481a118c8fefbfe901001d7828eaf6866eac4 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 29 Apr 2025 10:34:42 +0300 Subject: [PATCH 16/97] 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 17/97] 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 476054ba19f1858b1ac1effb84f0e7415e7de0c3 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 20 Apr 2025 22:17:41 +0300 Subject: [PATCH 18/97] feat: move qr code glue in MSALoginDialog Signed-off-by: Trial97 --- .gitmodules | 4 +-- CMakeLists.txt | 10 +++++- COPYING.md | 2 +- flake.lock | 4 +-- flake.nix | 6 ++-- launcher/CMakeLists.txt | 2 +- launcher/ui/dialogs/MSALoginDialog.cpp | 33 ++++++++++++++++++- libraries/README.md | 2 +- libraries/qrcodegenerator | 1 + 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 ++-- 14 files changed, 55 insertions(+), 85 deletions(-) create mode 160000 libraries/qrcodegenerator delete mode 100644 libraries/qt-qrcodegenerator/CMakeLists.txt delete mode 160000 libraries/qt-qrcodegenerator/QR-Code-generator delete mode 100644 libraries/qt-qrcodegenerator/qr.cpp delete mode 100644 libraries/qt-qrcodegenerator/qr.h diff --git a/.gitmodules b/.gitmodules index 0c56d8768..0a0a50bee 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,6 +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 +[submodule "libraries/qrcodegenerator"] + path = libraries/qrcodegenerator url = https://github.com/nayuki/QR-Code-generator diff --git a/CMakeLists.txt b/CMakeLists.txt index 68d900c27..ce3d433fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -475,7 +475,6 @@ 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") @@ -533,6 +532,15 @@ add_subdirectory(libraries/gamemode) add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API add_subdirectory(libraries/qdcss) # css parser +# qr code generator +set(QRCODE_SOURCES + libraries/qrcodegenerator/cpp/qrcodegen.cpp + libraries/qrcodegenerator/cpp/qrcodegen.hpp +) +add_library(qrcodegenerator STATIC ${QRCODE_SOURCES}) +target_include_directories(qrcodegenerator PUBLIC "libraries/qrcodegenerator/cpp/" ) +generate_export_header(qrcodegenerator) + ############################### Built Artifacts ############################### add_subdirectory(buildconfig) diff --git a/COPYING.md b/COPYING.md index 1ebde116f..f9b905351 100644 --- a/COPYING.md +++ b/COPYING.md @@ -404,7 +404,7 @@ 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`) +## QR-Code-generator (`libraries/qrcodegenerator`) Copyright © 2024 Project Nayuki. (MIT License) https://www.nayuki.io/page/qr-code-generator-library diff --git a/flake.lock b/flake.lock index 5418557a3..a0057327e 100644 --- a/flake.lock +++ b/flake.lock @@ -32,7 +32,7 @@ "type": "github" } }, - "qt-qrcodegenerator": { + "qrcodegenerator": { "flake": false, "locked": { "lastModified": 1737616857, @@ -52,7 +52,7 @@ "inputs": { "libnbtplusplus": "libnbtplusplus", "nixpkgs": "nixpkgs", - "qt-qrcodegenerator": "qt-qrcodegenerator" + "qrcodegenerator": "qrcodegenerator" } } }, diff --git a/flake.nix b/flake.nix index 69abd78dd..751ef2eeb 100644 --- a/flake.nix +++ b/flake.nix @@ -16,7 +16,7 @@ flake = false; }; - qt-qrcodegenerator = { + qrcodegenerator = { url = "github:nayuki/QR-Code-generator"; flake = false; }; @@ -27,7 +27,7 @@ self, nixpkgs, libnbtplusplus, - qt-qrcodegenerator, + qrcodegenerator, }: let @@ -175,7 +175,7 @@ prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix { inherit libnbtplusplus - qt-qrcodegenerator + qrcodegenerator self ; }; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 4f8b9018a..aa26b3544 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1302,7 +1302,7 @@ target_link_libraries(Launcher_logic qdcss BuildConfig Qt${QT_VERSION_MAJOR}::Widgets - qrcode + qrcodegenerator ) if (UNIX AND NOT CYGWIN AND NOT APPLE) diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index 83f46294d..14ec672e0 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -36,7 +36,6 @@ #include "MSALoginDialog.h" #include "Application.h" -#include "qr.h" #include "ui_MSALoginDialog.h" #include "DesktopServices.h" @@ -44,10 +43,15 @@ #include #include +#include +#include #include +#include #include #include +#include "qrcodegen.hpp" + MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MSALoginDialog) { ui->setupUi(this); @@ -139,6 +143,33 @@ void MSALoginDialog::authorizeWithBrowser(const QUrl& url) m_url = url; } +// 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) +{ + // 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); + } + } + } +} + void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, [[maybe_unused]] int expiresIn) { ui->stackedWidget->setCurrentIndex(1); diff --git a/libraries/README.md b/libraries/README.md index 5f7b685e5..be41e549f 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -99,7 +99,7 @@ Canonical implementation of the murmur2 hash, taken from [SMHasher](https://gith Public domain (the author disclaimed the copyright). -## qt-qrcodegenerator +## QR-Code-generator A simple library for generating QR codes diff --git a/libraries/qrcodegenerator b/libraries/qrcodegenerator new file mode 160000 index 000000000..2c9044de6 --- /dev/null +++ b/libraries/qrcodegenerator @@ -0,0 +1 @@ +Subproject commit 2c9044de6b049ca25cb3cd1649ed7e27aa055138 diff --git a/libraries/qt-qrcodegenerator/CMakeLists.txt b/libraries/qt-qrcodegenerator/CMakeLists.txt deleted file mode 100644 index e18da0e71..000000000 --- a/libraries/qt-qrcodegenerator/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -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 deleted file mode 160000 index f40366c40..000000000 --- a/libraries/qt-qrcodegenerator/QR-Code-generator +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f40366c40d8d1956081f7ec643d240c02a81df52 diff --git a/libraries/qt-qrcodegenerator/qr.cpp b/libraries/qt-qrcodegenerator/qr.cpp deleted file mode 100644 index 69bfb6da5..000000000 --- a/libraries/qt-qrcodegenerator/qr.cpp +++ /dev/null @@ -1,29 +0,0 @@ - -#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 deleted file mode 100644 index 290d49001..000000000 --- a/libraries/qt-qrcodegenerator/qr.h +++ /dev/null @@ -1,8 +0,0 @@ -#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 b5b02b101..d9144410f 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -9,7 +9,7 @@ jdk17, kdePackages, libnbtplusplus, - qt-qrcodegenerator, + qrcodegenerator, ninja, self, stripJavaArchivesHook, @@ -64,8 +64,8 @@ stdenv.mkDerivation { 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 + rm -rf source/libraries/qrcodegenerator + ln -s ${qrcodegenerator} source/libraries/qrcodegenerator ''; nativeBuildInputs = [ From 497e0cbfdc68a8ffd386e0fd5ab9c11eb8eb2903 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 29 Apr 2025 14:42:31 +0100 Subject: [PATCH 19/97] =?UTF-8?q?Tweak=20pack=20export=20UI=20=E2=80=93=20?= =?UTF-8?q?add=20recommended=20RAM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ExportPackDialog.ui | 176 +++++++++++++++++------- 1 file changed, 130 insertions(+), 46 deletions(-) diff --git a/launcher/ui/dialogs/ExportPackDialog.ui b/launcher/ui/dialogs/ExportPackDialog.ui index a4a174212..bda8b8dd0 100644 --- a/launcher/ui/dialogs/ExportPackDialog.ui +++ b/launcher/ui/dialogs/ExportPackDialog.ui @@ -19,36 +19,56 @@ &Description - + - - - &Name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - name - - - - - - - - - - &Version - - - version - - - - - - - 1.0.0 - - + + + + &Name: + + + name + + + + + + + + + + &Version: + + + version + + + + + + + 1.0.0 + + + + + + + &Author: + + + author + + + + + + + @@ -62,24 +82,29 @@ + + + 0 + 0 + + + + + 0 + 100 + + + + + 16777215 + 100 + + true - - - - &Author - - - author - - - - - - @@ -88,7 +113,70 @@ &Options - + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + &Recommended Memory: + + + + + + + false + + + + 0 + 0 + + + + MiB + + + 8 + + + 32768 + + + 128 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + @@ -138,10 +226,6 @@ - name - version - summary - author files optionalFiles From 75d4ef18288700992eb3c08b3e2120bea26427b0 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 29 Apr 2025 14:42:40 +0100 Subject: [PATCH 20/97] Implement UI for setting recommended RAM Signed-off-by: TheKodeToad --- launcher/minecraft/MinecraftInstance.cpp | 1 + launcher/ui/dialogs/ExportPackDialog.cpp | 25 +++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 991afce89..635cecfac 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -248,6 +248,7 @@ void MinecraftInstance::loadSpecificSettings() m_settings->registerSetting("ExportSummary", ""); m_settings->registerSetting("ExportAuthor", ""); m_settings->registerSetting("ExportOptionalFiles", true); + m_settings->registerSetting("ExportRecommendedRAM"); qDebug() << "Instance-type specific settings were loaded!"; diff --git a/launcher/ui/dialogs/ExportPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp index 303df94a1..e5edaf8a7 100644 --- a/launcher/ui/dialogs/ExportPackDialog.cpp +++ b/launcher/ui/dialogs/ExportPackDialog.cpp @@ -44,12 +44,16 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla m_ui->version->setText(instance->settings()->get("ExportVersion").toString()); m_ui->optionalFiles->setChecked(instance->settings()->get("ExportOptionalFiles").toBool()); + connect(m_ui->recommendedMemoryCheckBox, &QCheckBox::toggled, m_ui->recommendedMemory, &QWidget::setEnabled); + if (m_provider == ModPlatform::ResourceProvider::MODRINTH) { setWindowTitle(tr("Export Modrinth Pack")); m_ui->authorLabel->hide(); m_ui->author->hide(); + m_ui->recommendedMemoryWidget->hide(); + m_ui->summary->setPlainText(instance->settings()->get("ExportSummary").toString()); } else { setWindowTitle(tr("Export CurseForge Pack")); @@ -57,6 +61,19 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla m_ui->summaryLabel->hide(); m_ui->summary->hide(); + const int recommendedRAM = instance->settings()->get("ExportRecommendedRAM").toInt(); + + if (recommendedRAM > 0) { + m_ui->recommendedMemoryCheckBox->setChecked(true); + m_ui->recommendedMemory->setValue(recommendedRAM); + } else { + m_ui->recommendedMemoryCheckBox->setChecked(false); + + // recommend based on setting - limited to 12 GiB (CurseForge warns above this amount) + const int defaultRecommendation = qMin(m_instance->settings()->get("MaxMemAlloc").toInt(), 1024 * 12); + m_ui->recommendedMemory->setValue(defaultRecommendation); + } + m_ui->author->setText(instance->settings()->get("ExportAuthor").toString()); } @@ -120,9 +137,15 @@ void ExportPackDialog::done(int result) if (m_provider == ModPlatform::ResourceProvider::MODRINTH) settings->set("ExportSummary", m_ui->summary->toPlainText()); - else + else { settings->set("ExportAuthor", m_ui->author->text()); + if (m_ui->recommendedMemoryCheckBox->isChecked()) + settings->set("ExportRecommendedRAM", m_ui->recommendedMemory->value()); + else + settings->reset("ExportRecommendedRAM"); + } + if (result == Accepted) { const QString name = m_ui->name->text().isEmpty() ? m_instance->name() : m_ui->name->text(); const QString filename = FS::RemoveInvalidFilenameChars(name); From ee52127044a170437c8d6161b26fcf8f8a4c82e5 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 29 Apr 2025 14:40:20 +0100 Subject: [PATCH 21/97] Implement recommendedRam in CurseForge export Signed-off-by: TheKodeToad --- launcher/modplatform/flame/FlamePackExportTask.cpp | 8 +++++++- launcher/modplatform/flame/FlamePackExportTask.h | 4 +++- launcher/ui/dialogs/ExportPackDialog.cpp | 4 +++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index 3405b702f..952f30c11 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -47,7 +47,8 @@ FlamePackExportTask::FlamePackExportTask(const QString& name, bool optionalFiles, InstancePtr instance, const QString& output, - MMCZip::FilterFunction filter) + MMCZip::FilterFunction filter, + int recommendedRAM) : name(name) , version(version) , author(author) @@ -57,6 +58,7 @@ FlamePackExportTask::FlamePackExportTask(const QString& name, , gameRoot(instance->gameRoot()) , output(output) , filter(filter) + , m_recommendedRAM(recommendedRAM) {} void FlamePackExportTask::executeTask() @@ -411,6 +413,10 @@ QByteArray FlamePackExportTask::generateIndex() loader["primary"] = true; version["modLoaders"] = QJsonArray({ loader }); } + + if (m_recommendedRAM > 0) + version["recommendedRam"] = m_recommendedRAM; + obj["minecraft"] = version; } diff --git a/launcher/modplatform/flame/FlamePackExportTask.h b/launcher/modplatform/flame/FlamePackExportTask.h index b11eb17fa..dfd2ea941 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.h +++ b/launcher/modplatform/flame/FlamePackExportTask.h @@ -34,7 +34,8 @@ class FlamePackExportTask : public Task { bool optionalFiles, InstancePtr instance, const QString& output, - MMCZip::FilterFunction filter); + MMCZip::FilterFunction filter, + int recommendedRAM); protected: void executeTask() override; @@ -52,6 +53,7 @@ class FlamePackExportTask : public Task { const QDir gameRoot; const QString output; const MMCZip::FilterFunction filter; + const int m_recommendedRAM; struct ResolvedFile { int addonId; diff --git a/launcher/ui/dialogs/ExportPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp index e5edaf8a7..675f0d158 100644 --- a/launcher/ui/dialogs/ExportPackDialog.cpp +++ b/launcher/ui/dialogs/ExportPackDialog.cpp @@ -172,8 +172,10 @@ void ExportPackDialog::done(int result) task = new ModrinthPackExportTask(name, m_ui->version->text(), m_ui->summary->toPlainText(), m_ui->optionalFiles->isChecked(), m_instance, output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1)); } else { + int recommendedRAM = m_ui->recommendedMemoryCheckBox->isChecked() ? m_ui->recommendedMemory->value() : 0; + task = new FlamePackExportTask(name, m_ui->version->text(), m_ui->author->text(), m_ui->optionalFiles->isChecked(), m_instance, - output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1)); + output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1), recommendedRAM); } connect(task, &Task::failed, From 147159be2c98a334a88a8f3fcda42de1bdda7e7c Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 29 Apr 2025 19:02:35 +0300 Subject: [PATCH 22/97] 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 acdb8c5578374652ce8a505051e1c2e6fbe06673 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 29 Apr 2025 16:25:19 +0100 Subject: [PATCH 23/97] Implement recommendedRam in CurseForge import Signed-off-by: TheKodeToad --- .../flame/FlameInstanceCreationTask.cpp | 19 +++++++++++++++++++ launcher/modplatform/flame/PackManifest.cpp | 1 + launcher/modplatform/flame/PackManifest.h | 1 + 3 files changed, 21 insertions(+) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index c30ba5249..5d9c74ccf 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -54,6 +54,7 @@ #include "settings/INISettingsObject.h" +#include "sys.h" #include "tasks/ConcurrentTask.h" #include "ui/dialogs/BlockedModsDialog.h" #include "ui/dialogs/CustomMessageBox.h" @@ -418,6 +419,24 @@ bool FlameCreationTask::createInstance() } } + int recommendedRAM = m_pack.minecraft.recommendedRAM; + + // only set memory if this is a fresh instance + if (m_instance == nullptr && recommendedRAM > 0) { + const uint64_t sysMiB = Sys::getSystemRam() / Sys::mebibyte; + const uint64_t max = sysMiB * 0.9; + + if (recommendedRAM > max) { + logWarning(tr("The recommended memory of the modpack exceeds 90% of your system RAM—reducing it from %1 MiB to %2 MiB!") + .arg(recommendedRAM) + .arg(max)); + recommendedRAM = max; + } + + instance.settings()->set("OverrideMemory", true); + instance.settings()->set("MaxMemAlloc", recommendedRAM); + } + QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods"); QFileInfo jarmodsInfo(jarmodsPath); if (jarmodsInfo.isDir()) { diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index 278105f4a..641fb5d9a 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -27,6 +27,7 @@ static void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft) loadModloaderV1(loader, obj); m.modLoaders.append(loader); } + m.recommendedRAM = Json::ensureInteger(minecraft, "recommendedRam", 0); } static void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest) diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index ebb3ed5cc..6b911ffb4 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -67,6 +67,7 @@ struct Minecraft { QString version; QString libraries; QList modLoaders; + int recommendedRAM; }; struct Manifest { From 24036021bbd3833b8dfc66a8a3162a087ea16b37 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 29 Apr 2025 16:46:11 +0100 Subject: [PATCH 24/97] Propagate task warnings Signed-off-by: TheKodeToad --- launcher/InstanceImportTask.cpp | 5 +++++ launcher/tasks/Task.cpp | 2 ++ launcher/tasks/Task.h | 1 + 3 files changed, 8 insertions(+) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 633382404..b5a41b590 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -212,6 +212,7 @@ void InstanceImportTask::processZipPack() progressStep->status = status; stepProgress(*progressStep); }); + connect(zipTask.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); }); m_task.reset(zipTask); zipTask->start(); } @@ -308,6 +309,8 @@ void InstanceImportTask::processFlame() connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort); connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable); + connect(inst_creation_task.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); }); + m_task.reset(inst_creation_task); setAbortable(true); m_task->start(); @@ -407,6 +410,8 @@ void InstanceImportTask::processModrinth() connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort); connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable); + connect(inst_creation_task.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); }); + m_task.reset(inst_creation_task); setAbortable(true); m_task->start(); diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index 1871c5df8..92b345c8d 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -196,6 +196,8 @@ void Task::logWarning(const QString& line) { qWarning() << line; m_Warnings.append(line); + + emit warningLogged(line); } QStringList Task::warnings() const diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index fcd075150..43e71c8ab 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -145,6 +145,7 @@ class Task : public QObject, public QRunnable { void failed(QString reason); void status(QString status); void details(QString details); + void warningLogged(const QString& warning); void stepProgress(TaskStepProgress const& task_progress); //! Emitted when the canAbort() status has changed. */ From 053b57c21ff813e70915668ed29d80c16f0a43c2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 29 Apr 2025 19:24:04 +0300 Subject: [PATCH 25/97] 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 26/97] 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 27/97] 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 28/97] 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 29/97] 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 30/97] 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 31/97] 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 32/97] 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 33/97] 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 34/97] 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 35/97] 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 36/97] 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 37/97] 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 38/97] 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 39/97] 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 40/97] 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 41/97] 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 42/97] 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 13f533801b5ae19923c58bb6723431569398d271 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sat, 3 May 2025 13:32:11 +0100 Subject: [PATCH 43/97] Use options struct for FlamePackExportTask Signed-off-by: TheKodeToad --- .../modplatform/flame/FlamePackExportTask.cpp | 115 ++++++++---------- .../modplatform/flame/FlamePackExportTask.h | 32 +++-- launcher/ui/MainWindow.cpp | 12 +- launcher/ui/dialogs/ExportPackDialog.cpp | 18 ++- launcher/ui/dialogs/ExportPackDialog.h | 5 +- 5 files changed, 87 insertions(+), 95 deletions(-) diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index 701c75308..b74aa08ac 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -41,24 +41,8 @@ const QString FlamePackExportTask::TEMPLATE = "
  • {name}{authors}
  • \n"; const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" }); -FlamePackExportTask::FlamePackExportTask(const QString& name, - const QString& version, - const QString& author, - bool optionalFiles, - InstancePtr instance, - const QString& output, - MMCZip::FilterFileFunction filter, - int recommendedRAM) - : name(name) - , version(version) - , author(author) - , optionalFiles(optionalFiles) - , instance(instance) - , mcInstance(dynamic_cast(instance.get())) - , gameRoot(instance->gameRoot()) - , output(output) - , filter(filter) - , m_recommendedRAM(recommendedRAM) +FlamePackExportTask::FlamePackExportTask(FlamePackExportOptions&& options) + : m_options(std::move(options)), m_gameRoot(m_options.instance->gameRoot()) {} void FlamePackExportTask::executeTask() @@ -83,7 +67,7 @@ void FlamePackExportTask::collectFiles() QCoreApplication::processEvents(); files.clear(); - if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { + if (!MMCZip::collectFileListRecursively(m_options.instance->gameRoot(), nullptr, &files, m_options.filter)) { emitFailed(tr("Could not search for files")); return; } @@ -91,11 +75,8 @@ void FlamePackExportTask::collectFiles() pendingHashes.clear(); resolvedFiles.clear(); - if (mcInstance != nullptr) { - mcInstance->loaderModList()->update(); - connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, &FlamePackExportTask::collectHashes); - } else - collectHashes(); + m_options.instance->loaderModList()->update(); + connect(m_options.instance->loaderModList().get(), &ModFolderModel::updateFinished, this, &FlamePackExportTask::collectHashes); } void FlamePackExportTask::collectHashes() @@ -103,11 +84,11 @@ void FlamePackExportTask::collectHashes() setAbortable(true); setStatus(tr("Finding file hashes...")); setProgress(1, 5); - auto allMods = mcInstance->loaderModList()->allMods(); + auto allMods = m_options.instance->loaderModList()->allMods(); ConcurrentTask::Ptr hashingTask(new ConcurrentTask("MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); task.reset(hashingTask); for (const QFileInfo& file : files) { - const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); + const QString relative = m_gameRoot.relativeFilePath(file.absoluteFilePath()); // require sensible file types if (!std::any_of(FILE_EXTENSIONS.begin(), FILE_EXTENSIONS.end(), [&relative](const QString& extension) { return relative.endsWith('.' + extension) || relative.endsWith('.' + extension + ".disabled"); @@ -337,13 +318,13 @@ void FlamePackExportTask::buildZip() setStatus(tr("Adding files...")); setProgress(4, 5); - auto zipTask = makeShared(output, gameRoot, files, "overrides/", true, false); + auto zipTask = makeShared(m_options.output, m_gameRoot, files, "overrides/", true, false); zipTask->addExtraFile("manifest.json", generateIndex()); zipTask->addExtraFile("modlist.html", generateHTML()); QStringList exclude; std::transform(resolvedFiles.keyBegin(), resolvedFiles.keyEnd(), std::back_insert_iterator(exclude), - [this](QString file) { return gameRoot.relativeFilePath(file); }); + [this](QString file) { return m_gameRoot.relativeFilePath(file); }); zipTask->setExcludeFiles(exclude); auto progressStep = std::make_shared(); @@ -378,56 +359,56 @@ QByteArray FlamePackExportTask::generateIndex() QJsonObject obj; obj["manifestType"] = "minecraftModpack"; obj["manifestVersion"] = 1; - obj["name"] = name; - obj["version"] = version; - obj["author"] = author; + obj["name"] = m_options.name; + obj["version"] = m_options.version; + obj["author"] = m_options.author; obj["overrides"] = "overrides"; - if (mcInstance) { - QJsonObject version; - auto profile = mcInstance->getPackProfile(); - // collect all supported components - const ComponentPtr minecraft = profile->getComponent("net.minecraft"); - const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); - const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); - const ComponentPtr forge = profile->getComponent("net.minecraftforge"); - const ComponentPtr neoforge = profile->getComponent("net.neoforged"); - // convert all available components to mrpack dependencies - if (minecraft != nullptr) - version["version"] = minecraft->m_version; - QString id; - if (quilt != nullptr) - id = "quilt-" + quilt->m_version; - else if (fabric != nullptr) - id = "fabric-" + fabric->m_version; - else if (forge != nullptr) - id = "forge-" + forge->m_version; - else if (neoforge != nullptr) { - id = "neoforge-"; - if (minecraft->m_version == "1.20.1") - id += "1.20.1-"; - id += neoforge->m_version; - } - version["modLoaders"] = QJsonArray(); - if (!id.isEmpty()) { - QJsonObject loader; - loader["id"] = id; - loader["primary"] = true; - version["modLoaders"] = QJsonArray({ loader }); - } + QJsonObject version; - if (m_recommendedRAM > 0) - version["recommendedRam"] = m_recommendedRAM; + auto profile = m_options.instance->getPackProfile(); + // collect all supported components + const ComponentPtr minecraft = profile->getComponent("net.minecraft"); + const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); + const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); + const ComponentPtr forge = profile->getComponent("net.minecraftforge"); + const ComponentPtr neoforge = profile->getComponent("net.neoforged"); - obj["minecraft"] = version; + // convert all available components to mrpack dependencies + if (minecraft != nullptr) + version["version"] = minecraft->m_version; + QString id; + if (quilt != nullptr) + id = "quilt-" + quilt->m_version; + else if (fabric != nullptr) + id = "fabric-" + fabric->m_version; + else if (forge != nullptr) + id = "forge-" + forge->m_version; + else if (neoforge != nullptr) { + id = "neoforge-"; + if (minecraft->m_version == "1.20.1") + id += "1.20.1-"; + id += neoforge->m_version; } + version["modLoaders"] = QJsonArray(); + if (!id.isEmpty()) { + QJsonObject loader; + loader["id"] = id; + loader["primary"] = true; + version["modLoaders"] = QJsonArray({ loader }); + } + + if (m_options.recommendedRAM > 0) + version["recommendedRam"] = m_options.recommendedRAM; + + obj["minecraft"] = version; QJsonArray files; for (auto mod : resolvedFiles) { QJsonObject file; file["projectID"] = mod.addonId; file["fileID"] = mod.version; - file["required"] = mod.enabled || !optionalFiles; + file["required"] = mod.enabled || !m_options.optionalFiles; files << file; } obj["files"] = files; diff --git a/launcher/modplatform/flame/FlamePackExportTask.h b/launcher/modplatform/flame/FlamePackExportTask.h index d3c35de77..e3d4c74a7 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.h +++ b/launcher/modplatform/flame/FlamePackExportTask.h @@ -19,23 +19,26 @@ #pragma once -#include "BaseInstance.h" #include "MMCZip.h" #include "minecraft/MinecraftInstance.h" #include "modplatform/flame/FlameAPI.h" #include "tasks/Task.h" +struct FlamePackExportOptions { + QString name; + QString version; + QString author; + bool optionalFiles; + MinecraftInstancePtr instance; + QString output; + MMCZip::FilterFileFunction filter; + int recommendedRAM; +}; + class FlamePackExportTask : public Task { Q_OBJECT public: - FlamePackExportTask(const QString& name, - const QString& version, - const QString& author, - bool optionalFiles, - InstancePtr instance, - const QString& output, - MMCZip::FilterFileFunction filter, - int recommendedRAM); + FlamePackExportTask(FlamePackExportOptions&& options); protected: void executeTask() override; @@ -46,14 +49,6 @@ class FlamePackExportTask : public Task { static const QStringList FILE_EXTENSIONS; // inputs - const QString name, version, author; - const bool optionalFiles; - const InstancePtr instance; - MinecraftInstance* mcInstance; - const QDir gameRoot; - const QString output; - const MMCZip::FilterFileFunction filter; - const int m_recommendedRAM; struct ResolvedFile { int addonId; @@ -72,6 +67,9 @@ class FlamePackExportTask : public Task { bool isMod; }; + FlamePackExportOptions m_options; + QDir m_gameRoot; + FlameAPI api; QFileInfoList files; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index d9275a7ab..629ec6aa7 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -71,6 +71,7 @@ #include #include #include +#include #include #include @@ -1419,15 +1420,18 @@ void MainWindow::on_actionExportInstanceZip_triggered() void MainWindow::on_actionExportInstanceMrPack_triggered() { if (m_selectedInstance) { - ExportPackDialog dlg(m_selectedInstance, this); - dlg.exec(); + auto instance = std::dynamic_pointer_cast(m_selectedInstance); + if (instance != nullptr) { + ExportPackDialog dlg(instance, this); + dlg.exec(); + } } } void MainWindow::on_actionExportInstanceFlamePack_triggered() { if (m_selectedInstance) { - auto instance = dynamic_cast(m_selectedInstance.get()); + auto instance = std::dynamic_pointer_cast(m_selectedInstance); if (instance) { if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft"); cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") { @@ -1436,7 +1440,7 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered() msgBox.exec(); return; } - ExportPackDialog dlg(m_selectedInstance, this, ModPlatform::ResourceProvider::FLAME); + ExportPackDialog dlg(instance, this, ModPlatform::ResourceProvider::FLAME); dlg.exec(); } } diff --git a/launcher/ui/dialogs/ExportPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp index 675f0d158..15420616e 100644 --- a/launcher/ui/dialogs/ExportPackDialog.cpp +++ b/launcher/ui/dialogs/ExportPackDialog.cpp @@ -17,7 +17,7 @@ */ #include "ExportPackDialog.h" -#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" #include "modplatform/ModIndex.h" #include "modplatform/flame/FlamePackExportTask.h" #include "ui/dialogs/CustomMessageBox.h" @@ -33,7 +33,7 @@ #include "MMCZip.h" #include "modplatform/modrinth/ModrinthPackExportTask.h" -ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider) +ExportPackDialog::ExportPackDialog(MinecraftInstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider) : QDialog(parent), m_instance(instance), m_ui(new Ui::ExportPackDialog), m_provider(provider) { Q_ASSERT(m_provider == ModPlatform::ResourceProvider::MODRINTH || m_provider == ModPlatform::ResourceProvider::FLAME); @@ -172,10 +172,18 @@ void ExportPackDialog::done(int result) task = new ModrinthPackExportTask(name, m_ui->version->text(), m_ui->summary->toPlainText(), m_ui->optionalFiles->isChecked(), m_instance, output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1)); } else { - int recommendedRAM = m_ui->recommendedMemoryCheckBox->isChecked() ? m_ui->recommendedMemory->value() : 0; + FlamePackExportOptions options{}; - task = new FlamePackExportTask(name, m_ui->version->text(), m_ui->author->text(), m_ui->optionalFiles->isChecked(), m_instance, - output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1), recommendedRAM); + options.name = name; + options.version = m_ui->version->text(); + options.author = m_ui->author->text(); + options.optionalFiles = m_ui->optionalFiles->isChecked(); + options.instance = m_instance; + options.output = output; + options.filter = std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1); + options.recommendedRAM = m_ui->recommendedMemoryCheckBox->isChecked() ? m_ui->recommendedMemory->value() : 0; + + task = new FlamePackExportTask(std::move(options)); } connect(task, &Task::failed, diff --git a/launcher/ui/dialogs/ExportPackDialog.h b/launcher/ui/dialogs/ExportPackDialog.h index 092288d49..e93055d8d 100644 --- a/launcher/ui/dialogs/ExportPackDialog.h +++ b/launcher/ui/dialogs/ExportPackDialog.h @@ -22,6 +22,7 @@ #include "BaseInstance.h" #include "FastFileIconProvider.h" #include "FileIgnoreProxy.h" +#include "minecraft/MinecraftInstance.h" #include "modplatform/ModIndex.h" namespace Ui { @@ -32,7 +33,7 @@ class ExportPackDialog : public QDialog { Q_OBJECT public: - explicit ExportPackDialog(InstancePtr instance, + explicit ExportPackDialog(MinecraftInstancePtr instance, QWidget* parent = nullptr, ModPlatform::ResourceProvider provider = ModPlatform::ResourceProvider::MODRINTH); ~ExportPackDialog(); @@ -44,7 +45,7 @@ class ExportPackDialog : public QDialog { QString ignoreFileName(); private: - const InstancePtr m_instance; + const MinecraftInstancePtr m_instance; Ui::ExportPackDialog* m_ui; FileIgnoreProxy* m_proxy; FastFileIconProvider m_icons; 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 44/97] 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 45/97] 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 46/97] 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 47/97] 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 48/97] 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 49/97] 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 50/97] 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 51/97] 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 52/97] 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 53/97] 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 54/97] 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 55/97] 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 56/97] 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 57/97] 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 58/97] 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 59/97] 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 c3749c4fdc80a2863da7e273cb7062bd98922d4b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 7 May 2025 10:11:45 +0300 Subject: [PATCH 60/97] feat: add sink fail reason and correctly propagate it through the NetRequest Signed-off-by: Trial97 --- launcher/net/ByteArraySink.h | 6 +++++- launcher/net/FileSink.cpp | 9 ++++++++- launcher/net/NetRequest.cpp | 12 ++++++++---- launcher/net/Sink.h | 3 +++ launcher/screenshots/ImgurAlbumCreation.cpp | 3 +++ launcher/screenshots/ImgurUpload.cpp | 3 +++ launcher/tasks/ConcurrentTask.cpp | 8 ++++++-- 7 files changed, 36 insertions(+), 8 deletions(-) diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h index ac64052b9..f68230838 100644 --- a/launcher/net/ByteArraySink.h +++ b/launcher/net/ByteArraySink.h @@ -58,6 +58,7 @@ class ByteArraySink : public Sink { qWarning() << "ByteArraySink did not initialize the buffer because it's not addressable"; if (initAllValidators(request)) return Task::State::Running; + m_fail_reason = "Failed to initialize validators"; return Task::State::Failed; }; @@ -69,12 +70,14 @@ class ByteArraySink : public Sink { qWarning() << "ByteArraySink did not write the buffer because it's not addressable"; if (writeAllValidators(data)) return Task::State::Running; + m_fail_reason = "Failed to write validators"; return Task::State::Failed; } auto abort() -> Task::State override { failAllValidators(); + m_fail_reason = "Aborted"; return Task::State::Failed; } @@ -82,12 +85,13 @@ class ByteArraySink : public Sink { { if (finalizeAllValidators(reply)) return Task::State::Succeeded; + m_fail_reason = "Failed to finalize validators"; return Task::State::Failed; } auto hasLocalData() -> bool override { return false; } - private: + protected: std::shared_ptr m_output; }; } // namespace Net diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index 3a58a4667..1f519708f 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -51,6 +51,7 @@ Task::State FileSink::init(QNetworkRequest& request) // create a new save file and open it for writing if (!FS::ensureFilePathExists(m_filename)) { qCCritical(taskNetLogC) << "Could not create folder for " + m_filename; + m_fail_reason = "Could not create folder"; return Task::State::Failed; } @@ -58,11 +59,13 @@ Task::State FileSink::init(QNetworkRequest& request) m_output_file.reset(new PSaveFile(m_filename)); if (!m_output_file->open(QIODevice::WriteOnly)) { qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing"; + m_fail_reason = "Could not open file"; return Task::State::Failed; } if (initAllValidators(request)) return Task::State::Running; + m_fail_reason = "Failed to initialize validators"; return Task::State::Failed; } @@ -73,6 +76,7 @@ Task::State FileSink::write(QByteArray& data) m_output_file->cancelWriting(); m_output_file.reset(); m_wroteAnyData = false; + m_fail_reason = "Failed to write validators"; return Task::State::Failed; } @@ -105,13 +109,16 @@ Task::State FileSink::finalize(QNetworkReply& reply) if (gotFile || m_wroteAnyData) { // ask validators for data consistency // we only do this for actual downloads, not 'your data is still the same' cache hits - if (!finalizeAllValidators(reply)) + if (!finalizeAllValidators(reply)) { + m_fail_reason = "Failed to finalize validators"; return Task::State::Failed; + } // nothing went wrong... if (!m_output_file->commit()) { qCCritical(taskNetLogC) << "Failed to commit changes to " << m_filename; m_output_file->cancelWriting(); + m_fail_reason = "Failed to commit changes"; return Task::State::Failed; } } diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index ef533f599..7d8468a97 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -84,7 +84,8 @@ void NetRequest::executeTask() break; case State::Inactive: case State::Failed: - emit failed("Failed to initialize sink"); + m_failReason = m_sink->failReason(); + emit failed(m_sink->failReason()); emit finished(); return; case State::AbortedByUser: @@ -259,6 +260,7 @@ void NetRequest::downloadFinished() } else if (m_state == State::Failed) { qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString(); m_sink->abort(); + m_failReason = m_reply->errorString(); emit failed(m_reply->errorString()); emit finished(); return; @@ -278,7 +280,8 @@ void NetRequest::downloadFinished() if (m_state != State::Succeeded) { qCDebug(logCat) << getUid().toString() << "Request failed to write:" << m_url.toString(); m_sink->abort(); - emit failed("failed to write in sink"); + m_failReason = m_sink->failReason(); + emit failed(m_sink->failReason()); emit finished(); return; } @@ -289,7 +292,8 @@ void NetRequest::downloadFinished() if (m_state != State::Succeeded) { qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString(); m_sink->abort(); - emit failed("failed to finalize the request"); + m_failReason = m_sink->failReason(); + emit failed(m_sink->failReason()); emit finished(); return; } @@ -305,7 +309,7 @@ void NetRequest::downloadReadyRead() auto data = m_reply->readAll(); m_state = m_sink->write(data); if (m_state == State::Failed) { - qCCritical(logCat) << getUid().toString() << "Failed to process response chunk"; + qCCritical(logCat) << getUid().toString() << "Failed to process response chunk:" << m_sink->failReason(); } // qDebug() << "Request" << m_url.toString() << "gained" << data.size() << "bytes"; } else { diff --git a/launcher/net/Sink.h b/launcher/net/Sink.h index d1fd9de10..3f04cbd82 100644 --- a/launcher/net/Sink.h +++ b/launcher/net/Sink.h @@ -52,6 +52,8 @@ class Sink { virtual auto hasLocalData() -> bool = 0; + QString failReason() const { return m_fail_reason; } + void addValidator(Validator* validator) { if (validator) { @@ -95,5 +97,6 @@ class Sink { protected: std::vector> validators; + QString m_fail_reason; }; } // namespace Net diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp index 7ee98760a..1355c74c0 100644 --- a/launcher/screenshots/ImgurAlbumCreation.cpp +++ b/launcher/screenshots/ImgurAlbumCreation.cpp @@ -86,6 +86,7 @@ auto ImgurAlbumCreation::Sink::write(QByteArray& data) -> Task::State auto ImgurAlbumCreation::Sink::abort() -> Task::State { m_output.clear(); + m_fail_reason = "Aborted"; return Task::State::Failed; } @@ -95,11 +96,13 @@ auto ImgurAlbumCreation::Sink::finalize(QNetworkReply&) -> Task::State QJsonDocument doc = QJsonDocument::fromJson(m_output, &jsonError); if (jsonError.error != QJsonParseError::NoError) { qDebug() << jsonError.errorString(); + m_fail_reason = "Invalid json reply"; return Task::State::Failed; } auto object = doc.object(); if (!object.value("success").toBool()) { qDebug() << doc.toJson(); + m_fail_reason = "Failed to create album"; return Task::State::Failed; } m_result->deleteHash = object.value("data").toObject().value("deletehash").toString(); diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index 8b4ef5327..835a1ab81 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -90,6 +90,7 @@ auto ImgurUpload::Sink::write(QByteArray& data) -> Task::State auto ImgurUpload::Sink::abort() -> Task::State { m_output.clear(); + m_fail_reason = "Aborted"; return Task::State::Failed; } @@ -99,11 +100,13 @@ auto ImgurUpload::Sink::finalize(QNetworkReply&) -> Task::State QJsonDocument doc = QJsonDocument::fromJson(m_output, &jsonError); if (jsonError.error != QJsonParseError::NoError) { qDebug() << "imgur server did not reply with JSON" << jsonError.errorString(); + m_fail_reason = "Invalid json reply"; return Task::State::Failed; } auto object = doc.object(); if (!object.value("success").toBool()) { qDebug() << "Screenshot upload not successful:" << doc.toJson(); + m_fail_reason = "Screenshot was not uploaded successfully"; return Task::State::Failed; } m_shot->m_imgurId = object.value("data").toObject().value("id").toString(); diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index ad2a14c42..5cff93017 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -118,10 +118,14 @@ void ConcurrentTask::executeNextSubTask() } if (m_queue.isEmpty()) { if (m_doing.isEmpty()) { - if (m_failed.isEmpty()) + if (m_failed.isEmpty()) { emitSucceeded(); - else + } else if (m_failed.count() == 1) { + auto task = m_failed.keys().first(); + emitFailed(task->failReason()); + } else { emitFailed(tr("One or more subtasks failed")); + } } return; } From de541bf39700d028e87caf3704d753eb87edb70f Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 7 May 2025 10:12:46 +0300 Subject: [PATCH 61/97] refactor: paste upload to report the error directly Signed-off-by: Trial97 --- launcher/net/PasteUpload.cpp | 101 +++++++++++++++++++---------------- launcher/net/PasteUpload.h | 32 ++++------- launcher/ui/GuiUtil.cpp | 23 +++----- 3 files changed, 74 insertions(+), 82 deletions(-) diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 2333a2256..0bbd077cf 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -36,6 +36,7 @@ */ #include "PasteUpload.h" +#include #include #include @@ -99,86 +100,101 @@ QNetworkReply* PasteUpload::getReply(QNetworkRequest& request) return nullptr; }; -auto PasteUpload::Sink::init(QNetworkRequest&) -> Task::State +auto PasteUpload::Sink::finalize(QNetworkReply& reply) -> Task::State { - m_output.clear(); - return Task::State::Running; -}; + if (!finalizeAllValidators(reply)) { + m_fail_reason = "Failed to finalize validators"; + return Task::State::Failed; + } + int statusCode = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); -auto PasteUpload::Sink::write(QByteArray& data) -> Task::State -{ - m_output.append(data); - return Task::State::Running; -} + if (reply.error() != QNetworkReply::NetworkError::NoError) { + m_fail_reason = QObject::tr("Network error: %1").arg(reply.errorString()); + return Task::State::Failed; + } else if (statusCode != 200 && statusCode != 201) { + QString reasonPhrase = reply.attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + m_fail_reason = + QObject::tr("Error: %1 returned unexpected status code %2 %3").arg(m_d->url().toString()).arg(statusCode).arg(reasonPhrase); + return Task::State::Failed; + } -auto PasteUpload::Sink::abort() -> Task::State -{ - m_output.clear(); - return Task::State::Failed; -} - -auto PasteUpload::Sink::finalize(QNetworkReply&) -> Task::State -{ - switch (m_paste_type) { + switch (m_d->m_paste_type) { case PasteUpload::NullPointer: - m_result->link = QString::fromUtf8(m_output).trimmed(); + m_d->m_pasteLink = QString::fromUtf8(*m_output).trimmed(); break; case PasteUpload::Hastebin: { QJsonParseError jsonError; - auto doc = QJsonDocument::fromJson(m_output, &jsonError); + auto doc = QJsonDocument::fromJson(*m_output, &jsonError); if (jsonError.error != QJsonParseError::NoError) { qDebug() << "hastebin server did not reply with JSON" << jsonError.errorString(); + m_fail_reason = + QObject::tr("Failed to parse response from hastebin server: expected JSON but got an invalid response. Error: %1") + .arg(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; + m_d->m_pasteLink = m_d->m_baseUrl + "/" + key; } else { qDebug() << "Log upload failed:" << doc.toJson(); + m_fail_reason = QObject::tr("Error: %1 returned a malformed response body").arg(m_d->url().toString()); return Task::State::Failed; } break; } case PasteUpload::Mclogs: { QJsonParseError jsonError; - auto doc = QJsonDocument::fromJson(m_output, &jsonError); + auto doc = QJsonDocument::fromJson(*m_output, &jsonError); if (jsonError.error != QJsonParseError::NoError) { qDebug() << "mclogs server did not reply with JSON" << jsonError.errorString(); + m_fail_reason = + QObject::tr("Failed to parse response from mclogs server: expected JSON but got an invalid response. Error: %1") + .arg(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_result->link = obj["url"].toString(); + m_d->m_pasteLink = obj["url"].toString(); } else { - m_result->error = obj["error"].toString(); + QString error = obj["error"].toString(); + m_fail_reason = QObject::tr("Error: %1 returned an error: %2").arg(m_d->url().toString(), error); + return Task::State::Failed; } } else { qDebug() << "Log upload failed:" << doc.toJson(); + m_fail_reason = QObject::tr("Error: %1 returned a malformed response body").arg(m_d->url().toString()); return Task::State::Failed; } break; } case PasteUpload::PasteGG: QJsonParseError jsonError; - auto doc = QJsonDocument::fromJson(m_output, &jsonError); + auto doc = QJsonDocument::fromJson(*m_output, &jsonError); if (jsonError.error != QJsonParseError::NoError) { qDebug() << "pastegg server did not reply with JSON" << jsonError.errorString(); + m_fail_reason = + QObject::tr("Failed to parse response from pasteGG server: expected JSON but got an invalid response. Error: %1") + .arg(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_result->link = m_base_url + "/p/anonymous/" + obj["result"].toObject()["id"].toString(); + m_d->m_pasteLink = m_d->m_baseUrl + "/p/anonymous/" + obj["result"].toObject()["id"].toString(); } else { - m_result->error = obj["error"].toString(); - m_result->extra_message = (obj.contains("message") && obj["message"].isString()) ? obj["message"].toString() : "none"; + QString error = obj["error"].toString(); + QString message = (obj.contains("message") && obj["message"].isString()) ? obj["message"].toString() : "none"; + m_fail_reason = + QObject::tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_d->url().toString(), error, message); + return Task::State::Failed; } } else { qDebug() << "Log upload failed:" << doc.toJson(); + m_fail_reason = QObject::tr("Error: %1 returned a malformed response body").arg(m_d->url().toString()); return Task::State::Failed; } break; @@ -186,23 +202,18 @@ auto PasteUpload::Sink::finalize(QNetworkReply&) -> Task::State return Task::State::Succeeded; } -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; - 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; -} - -PasteUpload::PasteUpload(const QString& log, PasteType pasteType) : m_log(log), m_paste_type(pasteType) +PasteUpload::PasteUpload(const QString& log, QString url, PasteType pasteType) : m_log(log), m_baseUrl(url), m_paste_type(pasteType) { anonymizeLog(m_log); + auto base = PasteUpload::PasteTypes.at(pasteType); + if (m_baseUrl.isEmpty()) + m_baseUrl = base.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 == PasteUpload::PasteGG && m_baseUrl == base.defaultBase) + m_url = "https://api.paste.gg/v1/pastes"; + else + m_url = m_baseUrl + base.endpointPath; + + m_sink.reset(new Sink(this)); } diff --git a/launcher/net/PasteUpload.h b/launcher/net/PasteUpload.h index 55fb2231c..7f43779c4 100644 --- a/launcher/net/PasteUpload.h +++ b/launcher/net/PasteUpload.h @@ -35,6 +35,7 @@ #pragma once +#include "net/ByteArraySink.h" #include "net/NetRequest.h" #include "tasks/Task.h" @@ -67,40 +68,29 @@ class PasteUpload : public Net::NetRequest { }; static const std::array PasteTypes; - struct Result { - QString link; - QString error; - QString extra_message; - }; - using ResultPtr = std::shared_ptr; - - class Sink : public Net::Sink { + class Sink : public Net::ByteArraySink { public: - Sink(const PasteType pasteType, const QString base_url, ResultPtr result) - : m_paste_type(pasteType), m_base_url(base_url), m_result(result) {}; + Sink(PasteUpload* p) : Net::ByteArraySink(std::make_shared()), m_d(p) {}; virtual ~Sink() = default; 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* m_d; }; - PasteUpload(const QString& log, PasteType pasteType); + friend Sink; + + PasteUpload(const QString& log, QString url, PasteType pasteType); virtual ~PasteUpload() = default; - static NetRequest::Ptr make(const QString& log, PasteType pasteType, QString baseURL, ResultPtr result); + QString pasteLink() { return m_pasteLink; } private: virtual QNetworkReply* getReply(QNetworkRequest&) override; QString m_log; + QString m_pasteLink; + QString m_baseUrl; const PasteType m_paste_type; -}; \ No newline at end of file +}; diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index adb6e8bf2..141153b92 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -43,11 +43,10 @@ #include #include -#include - #include "FileSystem.h" #include "logs/AnonymizeLog.h" #include "net/NetJob.h" +#include "net/NetRequest.h" #include "net/PasteUpload.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" @@ -134,10 +133,10 @@ std::optional GuiUtil::uploadPaste(const QString& name, const QString& textToUpload = truncateLogForMclogs(text); } - auto result = std::make_shared(); auto job = NetJob::Ptr(new NetJob("Log Upload", APPLICATION->network())); - job->addNetAction(PasteUpload::make(textToUpload, pasteType, baseURL, result)); + auto pasteJob = new PasteUpload(textToUpload, baseURL, pasteType); + job->addNetAction(Net::NetRequest::Ptr(pasteJob)); QObject::connect(job.get(), &Task::failed, [parentWidget](QString reason) { CustomMessageBox::selectable(parentWidget, QObject::tr("Failed to upload logs!"), reason, QMessageBox::Critical)->show(); }); @@ -148,27 +147,19 @@ std::optional GuiUtil::uploadPaste(const QString& name, const QString& }); 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()) { + if (pasteJob->pasteLink().isEmpty()) { CustomMessageBox::selectable(parentWidget, QObject::tr("Failed to upload logs!"), "The upload link is empty", QMessageBox::Critical) ->show(); return {}; } - setClipboardText(result->link); + setClipboardText(pasteJob->pasteLink()); CustomMessageBox::selectable( parentWidget, QObject::tr("Upload finished"), - QObject::tr("The link to the uploaded log has been placed in your clipboard.").arg(result->link), + QObject::tr("The link to the uploaded log has been placed in your clipboard.").arg(pasteJob->pasteLink()), QMessageBox::Information) ->exec(); - return result->link; + return pasteJob->pasteLink(); } return {}; } From 3d0bef92a1f318494711059707b7aad04cf9b7f0 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 7 May 2025 10:19:01 +0300 Subject: [PATCH 62/97] feat: compound the conncurent task error Signed-off-by: Trial97 --- launcher/tasks/ConcurrentTask.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 5cff93017..84530ec99 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -122,9 +122,24 @@ void ConcurrentTask::executeNextSubTask() emitSucceeded(); } else if (m_failed.count() == 1) { auto task = m_failed.keys().first(); - emitFailed(task->failReason()); + auto reason = task->failReason(); + if (reason.isEmpty()) { // clearly a bug somewhere + reason = tr("Task failed"); + } + emitFailed(reason); } else { - emitFailed(tr("One or more subtasks failed")); + QStringList failReason; + for (auto t : m_failed) { + auto reason = t->failReason(); + if (!reason.isEmpty()) { + failReason << reason; + } + } + if (failReason.isEmpty()) { + emitFailed(tr("Multiple subtasks failed")); + } else { + emitFailed(tr("Multiple subtasks failed\n%1").arg(failReason.join("\n"))); + } } } return; From e1cfae5e063663d97ca9d1f5668432fe83a80180 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 7 May 2025 23:56:40 +0300 Subject: [PATCH 63/97] fix(skin manager): accept files with same name Signed-off-by: Trial97 --- launcher/minecraft/skins/SkinList.cpp | 25 +++++++++++++++++++++++-- launcher/minecraft/skins/SkinModel.cpp | 6 +++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index 56379aaab..be3bc776b 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -268,6 +268,26 @@ void SkinList::installSkins(const QStringList& iconFiles) installSkin(file); } +QString getUniqueFile(const QString& root, const QString& file) +{ + auto result = FS::PathCombine(root, file); + if (!QFileInfo::exists(result)) { + return result; + } + + QString baseName = QFileInfo(file).completeBaseName(); + QString extension = QFileInfo(file).suffix(); + int tries = 0; + while (QFileInfo::exists(result)) { + if (++tries > 256) + return {}; + + QString key = QString("%1%2.%3").arg(baseName).arg(tries).arg(extension); + result = FS::PathCombine(root, key); + } + + return result; +} QString SkinList::installSkin(const QString& file, const QString& name) { if (file.isEmpty()) @@ -282,7 +302,7 @@ QString SkinList::installSkin(const QString& file, const QString& name) if (fileinfo.suffix() != "png" && !SkinModel(fileinfo.absoluteFilePath()).isValid()) return tr("Skin images must be 64x64 or 64x32 pixel PNG files."); - QString target = FS::PathCombine(m_dir.absolutePath(), name.isEmpty() ? fileinfo.fileName() : name); + QString target = getUniqueFile(m_dir.absolutePath(), name.isEmpty() ? fileinfo.fileName() : name); return QFile::copy(file, target) ? "" : tr("Unable to copy file"); } @@ -371,7 +391,8 @@ bool SkinList::setData(const QModelIndex& idx, const QVariant& value, int role) auto& skin = m_skinList[row]; auto newName = value.toString(); if (skin.name() != newName) { - skin.rename(newName); + if (!skin.rename(newName)) + return false; save(); } return true; diff --git a/launcher/minecraft/skins/SkinModel.cpp b/launcher/minecraft/skins/SkinModel.cpp index b609bc6c7..209207215 100644 --- a/launcher/minecraft/skins/SkinModel.cpp +++ b/launcher/minecraft/skins/SkinModel.cpp @@ -122,7 +122,11 @@ QString SkinModel::name() const bool SkinModel::rename(QString newName) { auto info = QFileInfo(m_path); - m_path = FS::PathCombine(info.absolutePath(), newName + ".png"); + auto new_path = FS::PathCombine(info.absolutePath(), newName + ".png"); + if (QFileInfo::exists(new_path)) { + return false; + } + m_path = new_path; return FS::move(info.absoluteFilePath(), m_path); } From 69469b4484b88459a2fa74358197ac1dba4896ff Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 11 May 2025 03:33:03 +0800 Subject: [PATCH 64/97] 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 65/97] 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 66/97] 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 67/97] 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 68/97] 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 69/97] 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 70/97] 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 71/97] 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 72/97] 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 73/97] 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 74/97] 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 75/97] 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 776b15d587b32af0f64a4b60c92043a8b10ed507 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 16:08:06 +0000 Subject: [PATCH 76/97] chore(deps): update determinatesystems/update-flake-lock action to v25 --- .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 62852171b..da3fb144e 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - uses: cachix/install-nix-action@526118121621777ccd86f79b04685a9319637641 # v31 - - uses: DeterminateSystems/update-flake-lock@v24 + - uses: DeterminateSystems/update-flake-lock@v25 with: commit-msg: "chore(nix): update lockfile" pr-title: "chore(nix): update lockfile" From d9c9eb6521fceb8a9555758633a4b4a5633ebc07 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Sun, 18 May 2025 20:41:11 +0800 Subject: [PATCH 77/97] 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 78/97] 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 79/97] 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 80/97] 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 81/97] 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 82/97] 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 83/97] 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 84/97] 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 85/97] 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 86/97] 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 87/97] 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 88/97] 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 89/97] 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 64ef14100d88c4ba44e4716540c85b5180ec3d65 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 8 May 2025 13:10:37 +0300 Subject: [PATCH 90/97] feat(skin manager): add elytra preview Signed-off-by: Trial97 --- .../ui/dialogs/skins/SkinManageDialog.cpp | 31 ++++++++++++--- launcher/ui/dialogs/skins/SkinManageDialog.ui | 7 ++++ .../ui/dialogs/skins/draw/BoxGeometry.cpp | 8 +++- launcher/ui/dialogs/skins/draw/BoxGeometry.h | 1 + launcher/ui/dialogs/skins/draw/Scene.cpp | 38 +++++++++++++++++-- launcher/ui/dialogs/skins/draw/Scene.h | 5 ++- .../dialogs/skins/draw/SkinOpenGLWindow.cpp | 5 +++ .../ui/dialogs/skins/draw/SkinOpenGLWindow.h | 1 + 8 files changed, 85 insertions(+), 11 deletions(-) diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 3bc0bc2d9..8e661d37c 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -92,6 +92,10 @@ SkinManageDialog::SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct) connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(selectionChanged(QItemSelection, QItemSelection))); connect(m_ui->listView, &QListView::customContextMenuRequested, this, &SkinManageDialog::show_context_menu); + connect(m_ui->elytraCB, &QCheckBox::stateChanged, this, [this]() { + m_skinPreview->setElytraVisible(m_ui->elytraCB->isChecked()); + on_capeCombo_currentIndexChanged(0); + }); setupCapes(); @@ -159,10 +163,24 @@ void SkinManageDialog::on_fileBtn_clicked() } } -QPixmap previewCape(QImage capeImage) +QPixmap previewCape(QImage capeImage, bool elytra = false) { + if (elytra) { + auto wing = capeImage.copy(34, 0, 12, 22); + QImage mirrored = wing.mirrored(true, false); + + QImage combined(wing.width() * 2 - 2, wing.height(), capeImage.format()); + combined.fill(Qt::transparent); + + QPainter painter(&combined); + painter.drawImage(0, 0, wing); + painter.drawImage(wing.width() - 2, 0, mirrored); + painter.end(); + return QPixmap::fromImage(combined.scaled(96, 176, Qt::IgnoreAspectRatio, Qt::FastTransformation)); + } return QPixmap::fromImage(capeImage.copy(1, 1, 10, 16).scaled(80, 128, Qt::IgnoreAspectRatio, Qt::FastTransformation)); } + void SkinManageDialog::setupCapes() { // FIXME: add a model for this, download/refresh the capes on demand @@ -208,7 +226,7 @@ void SkinManageDialog::setupCapes() } } if (!capeImage.isNull()) { - m_ui->capeCombo->addItem(previewCape(capeImage), cape.alias, cape.id); + m_ui->capeCombo->addItem(previewCape(capeImage, m_ui->elytraCB->isChecked()), cape.alias, cape.id); } else { m_ui->capeCombo->addItem(cape.alias, cape.id); } @@ -222,7 +240,8 @@ void SkinManageDialog::on_capeCombo_currentIndexChanged(int index) auto id = m_ui->capeCombo->currentData(); auto cape = m_capes.value(id.toString(), {}); if (!cape.isNull()) { - m_ui->capeImage->setPixmap(previewCape(cape).scaled(size() * (1. / 3), Qt::KeepAspectRatio, Qt::FastTransformation)); + m_ui->capeImage->setPixmap( + previewCape(cape, m_ui->elytraCB->isChecked()).scaled(size() * (1. / 3), Qt::KeepAspectRatio, Qt::FastTransformation)); } else { m_ui->capeImage->clear(); } @@ -319,14 +338,14 @@ bool SkinManageDialog::eventFilter(QObject* obj, QEvent* ev) return QDialog::eventFilter(obj, ev); } -void SkinManageDialog::on_action_Rename_Skin_triggered(bool checked) +void SkinManageDialog::on_action_Rename_Skin_triggered(bool) { if (!m_selectedSkinKey.isEmpty()) { m_ui->listView->edit(m_ui->listView->currentIndex()); } } -void SkinManageDialog::on_action_Delete_Skin_triggered(bool checked) +void SkinManageDialog::on_action_Delete_Skin_triggered(bool) { if (m_selectedSkinKey.isEmpty()) return; @@ -523,7 +542,7 @@ void SkinManageDialog::resizeEvent(QResizeEvent* event) auto id = m_ui->capeCombo->currentData(); auto cape = m_capes.value(id.toString(), {}); if (!cape.isNull()) { - m_ui->capeImage->setPixmap(previewCape(cape).scaled(s, Qt::KeepAspectRatio, Qt::FastTransformation)); + m_ui->capeImage->setPixmap(previewCape(cape, m_ui->elytraCB->isChecked()).scaled(s, Qt::KeepAspectRatio, Qt::FastTransformation)); } else { m_ui->capeImage->clear(); } diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.ui b/launcher/ui/dialogs/skins/SkinManageDialog.ui index 7e8b4bc46..065c5cafc 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.ui +++ b/launcher/ui/dialogs/skins/SkinManageDialog.ui @@ -59,6 +59,13 @@ Cape + + + + Elytra + + + diff --git a/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp b/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp index b4ab8d4cc..f91fe2f1f 100644 --- a/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp +++ b/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp @@ -180,7 +180,8 @@ QList getCubeUVs(float u, float v, float width, float height, float d } namespace opengl { -BoxGeometry::BoxGeometry(QVector3D size, QVector3D position) : m_indexBuf(QOpenGLBuffer::IndexBuffer), m_size(size), m_position(position) +BoxGeometry::BoxGeometry(QVector3D size, QVector3D position) + : QOpenGLFunctions(), m_indexBuf(QOpenGLBuffer::IndexBuffer), m_size(size), m_position(position) { initializeOpenGLFunctions(); @@ -274,4 +275,9 @@ BoxGeometry* BoxGeometry::Plane() return b; } + +void BoxGeometry::scale(const QVector3D& vector) +{ + m_matrix.scale(vector); +} } // namespace opengl \ No newline at end of file diff --git a/launcher/ui/dialogs/skins/draw/BoxGeometry.h b/launcher/ui/dialogs/skins/draw/BoxGeometry.h index 1a245bc14..fa1a4c622 100644 --- a/launcher/ui/dialogs/skins/draw/BoxGeometry.h +++ b/launcher/ui/dialogs/skins/draw/BoxGeometry.h @@ -36,6 +36,7 @@ class BoxGeometry : protected QOpenGLFunctions { void initGeometry(float u, float v, float width, float height, float depth, float textureWidth = 64, float textureHeight = 64); void rotate(float angle, const QVector3D& vector); + void scale(const QVector3D& vector); private: QOpenGLBuffer m_vertexBuf; diff --git a/launcher/ui/dialogs/skins/draw/Scene.cpp b/launcher/ui/dialogs/skins/draw/Scene.cpp index 45d0ba191..89a783622 100644 --- a/launcher/ui/dialogs/skins/draw/Scene.cpp +++ b/launcher/ui/dialogs/skins/draw/Scene.cpp @@ -18,9 +18,16 @@ */ #include "ui/dialogs/skins/draw/Scene.h" + +#include +#include +#include +#include + namespace opengl { -Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : m_slim(slim), m_capeVisible(!cape.isNull()) +Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : QOpenGLFunctions(), m_slim(slim), m_capeVisible(!cape.isNull()) { + initializeOpenGLFunctions(); m_staticComponents = { // head new opengl::BoxGeometry(QVector3D(8, 8, 8), QVector3D(0, 4, 0), QPoint(0, 0), QVector3D(8, 8, 8)), @@ -57,6 +64,19 @@ Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : m_slim(slim), m_cape->rotate(10.8, QVector3D(1, 0, 0)); m_cape->rotate(180, QVector3D(0, 1, 0)); + auto leftWing = + new opengl::BoxGeometry(QVector3D(12, 22, 4), QVector3D(0, -13, -2), QPoint(22, 0), QVector3D(10, 20, 2), QSize(64, 32)); + leftWing->rotate(15, QVector3D(1, 0, 0)); + leftWing->rotate(15, QVector3D(0, 0, 1)); + leftWing->rotate(1, QVector3D(1, 0, 0)); + auto rightWing = + new opengl::BoxGeometry(QVector3D(12, 22, 4), QVector3D(0, -13, -2), QPoint(22, 0), QVector3D(10, 20, 2), QSize(64, 32)); + rightWing->scale(QVector3D(-1, 1, 1)); + rightWing->rotate(15, QVector3D(1, 0, 0)); + rightWing->rotate(15, QVector3D(0, 0, 1)); + rightWing->rotate(1, QVector3D(1, 0, 0)); + m_elytra << leftWing << rightWing; + // texture init m_skinTexture = new QOpenGLTexture(skin.mirrored()); m_skinTexture->setMinificationFilter(QOpenGLTexture::Nearest); @@ -68,7 +88,7 @@ Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : m_slim(slim), } Scene::~Scene() { - for (auto array : { m_staticComponents, m_normalArms, m_slimArms }) { + for (auto array : { m_staticComponents, m_normalArms, m_slimArms, m_elytra }) { for (auto g : array) { delete g; } @@ -95,7 +115,15 @@ void Scene::draw(QOpenGLShaderProgram* program) if (m_capeVisible) { m_capeTexture->bind(); program->setUniformValue("texture", 0); - m_cape->draw(program); + if (!m_elytraVisible) { + m_cape->draw(program); + } else { + glDisable(GL_CULL_FACE); + for (auto e : m_elytra) { + e->draw(program); + } + glEnable(GL_CULL_FACE); + } m_capeTexture->release(); } } @@ -131,4 +159,8 @@ void Scene::setCapeVisible(bool visible) { m_capeVisible = visible; } +void Scene::setElytraVisible(bool elytraVisible) +{ + m_elytraVisible = elytraVisible; +} } // namespace opengl \ No newline at end of file diff --git a/launcher/ui/dialogs/skins/draw/Scene.h b/launcher/ui/dialogs/skins/draw/Scene.h index 3560d1d74..c9bba1f20 100644 --- a/launcher/ui/dialogs/skins/draw/Scene.h +++ b/launcher/ui/dialogs/skins/draw/Scene.h @@ -22,7 +22,7 @@ #include namespace opengl { -class Scene { +class Scene : protected QOpenGLFunctions { public: Scene(const QImage& skin, bool slim, const QImage& cape); virtual ~Scene(); @@ -32,15 +32,18 @@ class Scene { void setCape(const QImage& cape); void setMode(bool slim); void setCapeVisible(bool visible); + void setElytraVisible(bool elytraVisible); private: QList m_staticComponents; QList m_normalArms; QList m_slimArms; BoxGeometry* m_cape = nullptr; + QList m_elytra; QOpenGLTexture* m_skinTexture = nullptr; QOpenGLTexture* m_capeTexture = nullptr; bool m_slim = false; bool m_capeVisible = false; + bool m_elytraVisible = false; }; } // namespace opengl \ No newline at end of file diff --git a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp index e1e539050..f035e6b91 100644 --- a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp +++ b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp @@ -263,3 +263,8 @@ void SkinOpenGLWindow::wheelEvent(QWheelEvent* event) m_distance = qMax(16.f, m_distance); // Clamp distance update(); // Trigger a repaint } +void SkinOpenGLWindow::setElytraVisible(bool visible) +{ + if (m_scene) + m_scene->setElytraVisible(visible); +} diff --git a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.h b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.h index e2c32da0f..2a06c23e5 100644 --- a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.h +++ b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.h @@ -43,6 +43,7 @@ class SkinOpenGLWindow : public QOpenGLWindow, protected QOpenGLFunctions { void updateScene(SkinModel* skin); void updateCape(const QImage& cape); + void setElytraVisible(bool visible); protected: void mousePressEvent(QMouseEvent* e) override; From a89caf7362d595a66c37d06a77f56270042784f4 Mon Sep 17 00:00:00 2001 From: Yihe Li Date: Thu, 22 May 2025 23:09:29 +0800 Subject: [PATCH 91/97] 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 92/97] 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") From ff1fb8755a7a2e2cb25dcc7a3ed271de88ff37dc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 25 May 2025 00:30:05 +0000 Subject: [PATCH 93/97] 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/dda3dcd3fe03e991015e9a74b22d35950f264a54?narHash=sha256-Ua0drDHawlzNqJnclTJGf87dBmaO/tn7iZ%2BTCkTRpRc%3D' (2025-05-08) → 'github:NixOS/nixpkgs/063f43f2dbdef86376cc29ad646c45c46e93234c?narHash=sha256-6m1Y3/4pVw1RWTsrkAK2VMYSzG4MMIj7sqUy7o8th1o%3D' (2025-05-23) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index d0cc6f54c..7479be1b7 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1746663147, - "narHash": "sha256-Ua0drDHawlzNqJnclTJGf87dBmaO/tn7iZ+TCkTRpRc=", + "lastModified": 1748026106, + "narHash": "sha256-6m1Y3/4pVw1RWTsrkAK2VMYSzG4MMIj7sqUy7o8th1o=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "dda3dcd3fe03e991015e9a74b22d35950f264a54", + "rev": "063f43f2dbdef86376cc29ad646c45c46e93234c", "type": "github" }, "original": { From 3690d935919271cc59a1ba1a33f543638c16c2f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 12:24:27 +0000 Subject: [PATCH 94/97] chore(deps): update cachix/install-nix-action digest to 17fe5fb --- .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 62852171b..8957e4a98 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@526118121621777ccd86f79b04685a9319637641 # v31 + - uses: cachix/install-nix-action@17fe5fb4a23ad6cbbe47d6b3f359611ad276644c # v31 - uses: DeterminateSystems/update-flake-lock@v24 with: From 03e1b7b4d591ffacabbe3a6cc5b13afad4a8c8ca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 May 2025 22:07:33 +0000 Subject: [PATCH 95/97] chore(deps): update determinatesystems/flakehub-cache-action action to v2 --- .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 5a40ebb1f..80b41161a 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -111,7 +111,7 @@ jobs: # For PRs - name: Setup Nix Magic Cache if: ${{ env.USE_DETERMINATE == 'true' }} - uses: DeterminateSystems/flakehub-cache-action@v1 + uses: DeterminateSystems/flakehub-cache-action@v2 # For in-tree builds - name: Setup Cachix From 75779a841ebbf9a3774887038ffed7b7412ae0d8 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 30 May 2025 18:55:58 +0000 Subject: [PATCH 96/97] Re-apply my suggestion Signed-off-by: TheKodeToad --- launcher/ui/dialogs/skins/SkinManageDialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.ui b/launcher/ui/dialogs/skins/SkinManageDialog.ui index 065c5cafc..aeb516854 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.ui +++ b/launcher/ui/dialogs/skins/SkinManageDialog.ui @@ -62,7 +62,7 @@ - Elytra + Preview Elytra From 48bc6ebcc2887c8694a3519e762c777763d82682 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 1 Jun 2025 00:33:52 +0000 Subject: [PATCH 97/97] 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/063f43f2dbdef86376cc29ad646c45c46e93234c?narHash=sha256-6m1Y3/4pVw1RWTsrkAK2VMYSzG4MMIj7sqUy7o8th1o%3D' (2025-05-23) → 'github:NixOS/nixpkgs/96ec055edbe5ee227f28cdbc3f1ddf1df5965102?narHash=sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg%3D' (2025-05-28) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index a4061e8e3..2d2f820f4 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1748026106, - "narHash": "sha256-6m1Y3/4pVw1RWTsrkAK2VMYSzG4MMIj7sqUy7o8th1o=", + "lastModified": 1748460289, + "narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "063f43f2dbdef86376cc29ad646c45c46e93234c", + "rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102", "type": "github" }, "original": {