Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into refresh_cats

This commit is contained in:
Trial97 2024-01-20 00:27:30 +02:00
commit 0303b1cc7f
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
30 changed files with 279 additions and 252 deletions

View File

@ -21,8 +21,23 @@ on:
WINDOWS_CODESIGN_PASSWORD:
description: Password for signing Windows builds
required: false
CACHIX_AUTH_TOKEN:
description: Private token for authenticating against Cachix cache
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
@ -151,7 +166,7 @@ jobs:
- name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
uses: actions/cache@v3.3.2
uses: actions/cache@v4.0.0
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
@ -336,6 +351,20 @@ jobs:
# 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
- name: Package (macOS)
if: runner.os == 'macOS'
run: |
@ -343,9 +372,34 @@ jobs:
cd ${{ env.INSTALL_DIR }}
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}'
else
APPLE_CODESIGN_ID='-'
fi
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
mv "PrismLauncher.app" "Prism Launcher.app"
tar -czf ../PrismLauncher.tar.gz *
- 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'
@ -520,7 +574,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.tar.gz
path: PrismLauncher.zip
- name: Upload binary zip (Windows)
if: runner.os == 'Windows'

View File

@ -32,6 +32,11 @@ jobs:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
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 }}

View File

@ -16,7 +16,12 @@ jobs:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
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 }}
@ -46,8 +51,8 @@ jobs:
mv PrismLauncher-Linux-Qt5*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
mv PrismLauncher-macOS-Legacy*/PrismLauncher.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip
tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
@ -102,6 +107,6 @@ jobs:
PrismLauncher-Windows-MSVC-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe
PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
PrismLauncher-macOS-${{ env.VERSION }}.zip
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
PrismLauncher-${{ env.VERSION }}.tar.gz

View File

@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@7ac1ec25491415c381d9b62f0657c7a028df52a7 # v24
- uses: cachix/install-nix-action@6004951b182f8860210c8d6f0d808ec5b1a33d28 # v25
- uses: DeterminateSystems/update-flake-lock@v20
with:

40
flake.lock generated
View File

@ -18,14 +18,16 @@
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
"nixpkgs-lib": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1704152458,
"narHash": "sha256-DS+dGw7SKygIWf9w4eNBUZsK+4Ug27NwEWmn2tnbycg=",
"lastModified": 1704982712,
"narHash": "sha256-2Ptt+9h8dczgle2Oo6z5ni5rt/uLMG47UFTR1ry/wgg=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "88a2cd8166694ba0b6cb374700799cec53aef527",
"rev": "07f6395285469419cf9d078f59b5b49993198c00",
"type": "github"
},
"original": {
@ -106,11 +108,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1704161960,
"narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=",
"lastModified": 1704842529,
"narHash": "sha256-OTeQA+F8d/Evad33JMfuXC89VMetQbsU4qcaePchGr4=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "63143ac2c9186be6d9da6035fa22620018c85932",
"rev": "eabe8d3eface69f5bb16c18f8662a702f50c20d5",
"type": "github"
},
"original": {
@ -120,24 +122,6 @@
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1703961334,
"narHash": "sha256-M1mV/Cq+pgjk0rt6VxoyyD+O8cOUiai8t9Q6Yyq4noY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b0d36bd0a420ecee3bc916c91886caca87c894e9",
"type": "github"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": [
@ -153,11 +137,11 @@
]
},
"locked": {
"lastModified": 1703939133,
"narHash": "sha256-Gxe+mfOT6bL7wLC/tuT2F+V+Sb44jNr8YsJ3cyIl4Mo=",
"lastModified": 1705072518,
"narHash": "sha256-90dERRuG781f0EWjn2AOtScZqsTcpIFLpY8TN2VbkL8=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "9d3d7e18c6bc4473d7520200d4ddab12f8402d38",
"rev": "274ae3979a0eacae422e1bbcf63b8b7a335e1114",
"type": "github"
},
"original": {

View File

@ -1,15 +1,25 @@
{
description = "A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once (Fork of MultiMC)";
nixConfig = {
extra-substituters = ["https://cache.garnix.io"];
extra-trusted-public-keys = ["cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g="];
};
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
flake-parts = {
url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs";
};
nix-filter.url = "github:numtide/nix-filter";
pre-commit-hooks = {
url = "github:cachix/pre-commit-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
inputs.nixpkgs-stable.follows = "nixpkgs";
inputs.flake-compat.follows = "flake-compat";
inputs = {
nixpkgs.follows = "nixpkgs";
nixpkgs-stable.follows = "nixpkgs";
flake-compat.follows = "flake-compat";
};
};
flake-compat = {
url = "github:edolstra/flake-compat";

View File

@ -751,6 +751,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("ModrinthToken", "");
m_settings->registerSetting("UserAgentOverride", "");
// FTBApp instances
m_settings->registerSetting("FTBAppInstancesPath", "");
// Init page provider
{
m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings"));

View File

@ -37,140 +37,33 @@
#include <QDesktopServices>
#include <QDir>
#include <QProcess>
/**
* This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing.
*/
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
template <typename T>
bool IndirectOpen(T callable, qint64* pid_forked = nullptr)
{
auto pid = fork();
if (pid_forked) {
if (pid > 0)
*pid_forked = pid;
else
*pid_forked = 0;
}
if (pid == -1) {
qWarning() << "IndirectOpen failed to fork: " << errno;
return false;
}
// child - do the stuff
if (pid == 0) {
// unset all this garbage so it doesn't get passed to the child process
qunsetenv("LD_PRELOAD");
qunsetenv("LD_LIBRARY_PATH");
qunsetenv("LD_DEBUG");
qunsetenv("QT_PLUGIN_PATH");
qunsetenv("QT_FONTPATH");
// open the URL
auto status = callable();
// detach from the parent process group.
setsid();
// die. now. do not clean up anything, it would just hang forever.
_exit(status ? 0 : 1);
} else {
// parent - assume it worked.
int status;
while (waitpid(pid, &status, 0)) {
if (WIFEXITED(status)) {
return WEXITSTATUS(status) == 0;
}
if (WIFSIGNALED(status)) {
return false;
}
}
return true;
}
}
#endif
#include "FileSystem.h"
namespace DesktopServices {
bool openDirectory(const QString& path, [[maybe_unused]] bool ensureExists)
bool openPath(const QFileInfo& path, bool ensureFolderPathExists)
{
qDebug() << "Opening directory" << path;
QDir parentPath;
QDir dir(path);
if (ensureExists && !dir.exists()) {
parentPath.mkpath(dir.absolutePath());
qDebug() << "Opening path" << path;
if (ensureFolderPathExists) {
FS::ensureFolderPathExists(path);
}
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
return IndirectOpen(f);
}
#endif
return f();
return openUrl(QUrl::fromLocalFile(QFileInfo(path).absolutePath()));
}
bool openFile(const QString& path)
bool openPath(const QString& path, bool ensureFolderPathExists)
{
qDebug() << "Opening file" << path;
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
return IndirectOpen(f);
} else {
return f();
}
#else
return f();
#endif
}
bool openFile(const QString& application, const QString& path, const QString& workingDirectory, qint64* pid)
{
qDebug() << "Opening file" << path << "using" << application;
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
if (!isSandbox()) {
return IndirectOpen([&]() { return QProcess::startDetached(application, QStringList() << path, workingDirectory); }, pid);
} else {
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
}
#else
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
#endif
return openPath(QFileInfo(path), ensureFolderPathExists);
}
bool run(const QString& application, const QStringList& args, const QString& workingDirectory, qint64* pid)
{
qDebug() << "Running" << application << "with args" << args.join(' ');
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
return IndirectOpen([&]() { return QProcess::startDetached(application, args, workingDirectory); }, pid);
} else {
return QProcess::startDetached(application, args, workingDirectory, pid);
}
#else
return QProcess::startDetached(application, args, workingDirectory, pid);
#endif
}
bool openUrl(const QUrl& url)
{
qDebug() << "Opening URL" << url.toString();
auto f = [&]() { return QDesktopServices::openUrl(url); };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
return IndirectOpen(f);
} else {
return f();
}
#else
return f();
#endif
return QDesktopServices::openUrl(url);
}
bool isFlatpak()
@ -191,9 +84,4 @@ bool isSnap()
#endif
}
bool isSandbox()
{
return isSnap() || isFlatpak();
}
} // namespace DesktopServices

View File

@ -3,31 +3,30 @@
#include <QString>
#include <QUrl>
class QFileInfo;
/**
* This wraps around QDesktopServices and adds workarounds where needed
* Use this instead of QDesktopServices!
*/
namespace DesktopServices {
/**
* Open a file in whatever application is applicable
* Open a path in whatever application is applicable.
* @param ensureFolderPathExists Make sure the path exists
*/
bool openFile(const QString& path);
bool openPath(const QFileInfo& path, bool ensureFolderPathExists = false);
/**
* Open a file in the specified application
* Open a path in whatever application is applicable.
* @param ensureFolderPathExists Make sure the path exists
*/
bool openFile(const QString& application, const QString& path, const QString& workingDirectory = QString(), qint64* pid = 0);
bool openPath(const QString& path, bool ensureFolderPathExists = false);
/**
* Run an application
*/
bool run(const QString& application, const QStringList& args, const QString& workingDirectory = QString(), qint64* pid = 0);
/**
* Open a directory
*/
bool openDirectory(const QString& path, bool ensureExists = false);
/**
* Open the URL, most likely in a browser. Maybe.
*/
@ -42,9 +41,4 @@ bool isFlatpak();
* Determine whether the launcher is running in a Snap environment
*/
bool isSnap();
/**
* Determine whether the launcher is running in a sandboxed (Flatpak or Snap) environment
*/
bool isSandbox();
} // namespace DesktopServices

View File

@ -272,15 +272,19 @@ bool ensureFilePathExists(QString filenamepath)
return success;
}
bool ensureFolderPathExists(QString foldernamepath)
bool ensureFolderPathExists(const QFileInfo folderPath)
{
QFileInfo a(foldernamepath);
QDir dir;
QString ensuredPath = a.filePath();
QString ensuredPath = folderPath.filePath();
bool success = dir.mkpath(ensuredPath);
return success;
}
bool ensureFolderPathExists(const QString folderPathName)
{
return ensureFolderPathExists(QFileInfo(folderPathName));
}
bool copyFileAttributes(QString src, QString dst)
{
#ifdef Q_OS_WIN32

View File

@ -91,7 +91,13 @@ bool ensureFilePathExists(QString filenamepath);
* Creates all the folders in a path for the specified path
* last segment of the path is treated as a folder name and is created!
*/
bool ensureFolderPathExists(QString filenamepath);
bool ensureFolderPathExists(const QFileInfo folderPath);
/**
* Creates all the folders in a path for the specified path
* last segment of the path is treated as a folder name and is created!
*/
bool ensureFolderPathExists(const QString folderPathName);
/**
* @brief Copies a directory and it's contents from src to dest

View File

@ -2,6 +2,8 @@
#include "ui/dialogs/CustomMessageBox.h"
#include <QPushButton>
InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name)
{
auto dialog =
@ -27,16 +29,15 @@ ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name)
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
.arg(original_version_name),
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
info->setButtonText(QMessageBox::Ok, QObject::tr("Update existing instance"));
info->setButtonText(QMessageBox::Abort, QObject::tr("Create new instance"));
info->setButtonText(QMessageBox::Reset, QObject::tr("Cancel"));
QMessageBox::Information, QMessageBox::Cancel);
QAbstractButton* update = info->addButton(QObject::tr("Update existing instance"), QMessageBox::AcceptRole);
QAbstractButton* skip = info->addButton(QObject::tr("Create new instance"), QMessageBox::ResetRole);
info->exec();
if (info->clickedButton() == info->button(QMessageBox::Ok))
if (info->clickedButton() == update)
return ShouldUpdate::Update;
if (info->clickedButton() == info->button(QMessageBox::Abort))
if (info->clickedButton() == skip)
return ShouldUpdate::SkipUpdating;
return ShouldUpdate::Cancel;
}

View File

@ -42,7 +42,6 @@
#include "ui/InstanceWindow.h"
#include "ui/MainWindow.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ProfileSelectDialog.h"
#include "ui/dialogs/ProfileSetupDialog.h"
#include "ui/dialogs/ProgressDialog.h"
@ -144,6 +143,12 @@ void LaunchController::login()
bool tryagain = true;
unsigned int tries = 0;
if (m_accountToUse->accountType() != AccountType::Offline && m_accountToUse->accountState() == AccountState::Offline) {
// Force account refresh on the account used to launch the instance updating the AccountState
// only on first try and if it is not meant to be offline
auto accounts = APPLICATION->accounts();
accounts->requestRefresh(m_accountToUse->internalId());
}
while (tryagain) {
if (tries > 0 && tries % 3 == 0) {
auto result =
@ -250,12 +255,6 @@ void LaunchController::login()
progDialog.execWithTask(task.get());
continue;
}
// FIXME: this is missing - the meaning is that the account is queued for refresh and we should wait for that
/*
case AccountState::Queued: {
return;
}
*/
case AccountState::Expired: {
auto errorString = tr("The account has expired and needs to be logged into manually again.");
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), errorString, QMessageBox::StandardButton::Ok,

View File

@ -52,8 +52,6 @@
#include <FileSystem.h>
#include <QSaveFile>
#include <chrono>
enum AccountListVersion { MojangMSA = 3 };
AccountList::AccountList(QObject* parent) : QAbstractListModel(parent)

View File

@ -1197,43 +1197,43 @@ void MainWindow::undoTrashInstance()
void MainWindow::on_actionViewLauncherRootFolder_triggered()
{
DesktopServices::openDirectory(".");
DesktopServices::openPath(".");
}
void MainWindow::on_actionViewInstanceFolder_triggered()
{
QString str = APPLICATION->settings()->get("InstanceDir").toString();
DesktopServices::openDirectory(str);
DesktopServices::openPath(str);
}
void MainWindow::on_actionViewCentralModsFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->settings()->get("CentralModsDir").toString(), true);
DesktopServices::openPath(APPLICATION->settings()->get("CentralModsDir").toString(), true);
}
void MainWindow::on_actionViewIconThemeFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path(), true);
DesktopServices::openPath(APPLICATION->themeManager()->getIconThemesFolder().path(), true);
}
void MainWindow::on_actionViewWidgetThemeFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path(), true);
DesktopServices::openPath(APPLICATION->themeManager()->getApplicationThemesFolder().path(), true);
}
void MainWindow::on_actionViewCatPackFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->themeManager()->getCatPacksFolder().path(), true);
DesktopServices::openPath(APPLICATION->themeManager()->getCatPacksFolder().path(), true);
}
void MainWindow::on_actionViewIconsFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->icons()->getDirectory(), true);
DesktopServices::openPath(APPLICATION->icons()->getDirectory(), true);
}
void MainWindow::on_actionViewLogsFolder_triggered()
{
DesktopServices::openDirectory("logs", true);
DesktopServices::openPath("logs", true);
}
void MainWindow::refreshInstances()
@ -1452,7 +1452,7 @@ void MainWindow::on_actionViewSelectedInstFolder_triggered()
{
if (m_selectedInstance) {
QString str = m_selectedInstance->instanceRoot();
DesktopServices::openDirectory(QDir(str).absolutePath());
DesktopServices::openPath(QFileInfo(str));
}
}

View File

@ -159,5 +159,5 @@ IconPickerDialog::~IconPickerDialog()
void IconPickerDialog::openFolder()
{
DesktopServices::openDirectory(APPLICATION->icons()->getDirectory(), true);
DesktopServices::openPath(APPLICATION->icons()->getDirectory(), true);
}

View File

@ -443,6 +443,9 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri
reqItem->insertChildren(i++, { reqItem });
}
}
ui->toggleDepsButton->show();
m_deps << item_top;
}
auto changelog_item = new QTreeWidgetItem(item_top);

View File

@ -13,6 +13,7 @@ ReviewMessageBox::ReviewMessageBox(QWidget* parent, [[maybe_unused]] QString con
auto back_button = ui->buttonBox->button(QDialogButtonBox::Cancel);
back_button->setText(tr("Back"));
ui->toggleDepsButton->hide();
ui->modTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch);
ui->modTreeWidget->header()->setStretchLastSection(false);
ui->modTreeWidget->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
@ -75,6 +76,8 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
}
itemTop->insertChildren(childIndx++, { requiredByItem });
ui->toggleDepsButton->show();
m_deps << itemTop;
}
auto versionTypeItem = new QTreeWidgetItem(itemTop);
@ -108,3 +111,10 @@ void ReviewMessageBox::retranslateUi(QString resources_name)
ui->explainLabel->setText(tr("You're about to download the following %1:").arg(resources_name));
ui->onlyCheckedLabel->setText(tr("Only %1 with a check will be downloaded!").arg(resources_name));
}
void ReviewMessageBox::on_toggleDepsButton_clicked()
{
m_deps_checked = !m_deps_checked;
auto state = m_deps_checked ? Qt::Checked : Qt::Unchecked;
for (auto dep : m_deps)
dep->setCheckState(0, state);
};

View File

@ -1,6 +1,7 @@
#pragma once
#include <QDialog>
#include <QTreeWidgetItem>
namespace Ui {
class ReviewMessageBox;
@ -28,8 +29,14 @@ class ReviewMessageBox : public QDialog {
~ReviewMessageBox() override;
protected slots:
void on_toggleDepsButton_clicked();
protected:
ReviewMessageBox(QWidget* parent, const QString& title, const QString& icon);
Ui::ReviewMessageBox* ui;
QList<QTreeWidgetItem*> m_deps;
bool m_deps_checked = true;
};

View File

@ -44,15 +44,20 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="explainLabel">
</widget>
<widget class="QLabel" name="explainLabel"/>
</item>
<item row="5" column="0" rowspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="onlyCheckedLabel">
<widget class="QPushButton" name="toggleDepsButton">
<property name="text">
<string>Toggle Dependencies</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="onlyCheckedLabel"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">

View File

@ -290,12 +290,12 @@ void ExternalResourcesPage::disableItem()
void ExternalResourcesPage::viewConfigs()
{
DesktopServices::openDirectory(m_instance->instanceConfigFolder(), true);
DesktopServices::openPath(m_instance->instanceConfigFolder(), true);
}
void ExternalResourcesPage::viewFolder()
{
DesktopServices::openDirectory(m_model->dir().absolutePath(), true);
DesktopServices::openPath(m_model->dir().absolutePath(), true);
}
bool ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous)

View File

@ -324,8 +324,7 @@ void ScreenshotsPage::onItemActivated(QModelIndex index)
if (!index.isValid())
return;
auto info = m_model->fileInfo(index);
QString fileName = info.absoluteFilePath();
DesktopServices::openFile(info.absoluteFilePath());
DesktopServices::openPath(info);
}
void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected)
@ -352,7 +351,7 @@ void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected)
void ScreenshotsPage::on_actionView_Folder_triggered()
{
DesktopServices::openDirectory(m_folder, true);
DesktopServices::openPath(m_folder, true);
}
void ScreenshotsPage::on_actionUpload_triggered()

View File

@ -447,12 +447,12 @@ void VersionPage::on_actionAdd_Empty_triggered()
void VersionPage::on_actionLibrariesFolder_triggered()
{
DesktopServices::openDirectory(m_inst->getLocalLibraryPath(), true);
DesktopServices::openPath(m_inst->getLocalLibraryPath(), true);
}
void VersionPage::on_actionMinecraftFolder_triggered()
{
DesktopServices::openDirectory(m_inst->gameRoot(), true);
DesktopServices::openPath(m_inst->gameRoot(), true);
}
void VersionPage::versionCurrent(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)

View File

@ -207,7 +207,7 @@ void WorldListPage::on_actionRemove_triggered()
void WorldListPage::on_actionView_Folder_triggered()
{
DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true);
DesktopServices::openPath(m_worlds->dir().absolutePath(), true);
}
void WorldListPage::on_actionDatapacks_triggered()
@ -223,7 +223,7 @@ void WorldListPage::on_actionDatapacks_triggered()
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
DesktopServices::openDirectory(FS::PathCombine(fullPath, "datapacks"), true);
DesktopServices::openPath(FS::PathCombine(fullPath, "datapacks"), true);
}
void WorldListPage::on_actionReset_Icon_triggered()

View File

@ -20,6 +20,7 @@
#include "ui/widgets/ProjectItem.h"
#include "ui_ImportFTBPage.h"
#include <QFileDialog>
#include <QWidget>
#include "FileSystem.h"
#include "ListModel.h"
@ -56,6 +57,13 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg
connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch);
connect(ui->browseButton, &QPushButton::clicked, this, [this] {
auto path = listModel->getPath();
QString dir = QFileDialog::getExistingDirectory(this, tr("Select FTBApp instances directory"), path, QFileDialog::ShowDirsOnly);
if (!dir.isEmpty())
listModel->setPath(dir);
});
ui->modpackList->setItemDelegate(new ProjectItemDelegate(this));
ui->modpackList->selectionModel()->reset();
}

View File

@ -11,7 +11,7 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<item row="2" column="1">
<widget class="QTreeView" name="modpackList">
<property name="maximumSize">
<size>
@ -21,28 +21,7 @@
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QComboBox" name="sortByBox">
@ -69,6 +48,54 @@
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="browseButton">
<property name="toolTip">
<string>Select FTBApp instances directory</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="viewfolder">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>Note: If your FTB instances are not in the default location, select it using the button next to search.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

View File

@ -17,11 +17,13 @@
*/
#include "ListModel.h"
#include <qfileinfo.h>
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QIcon>
#include <QProcessEnvironment>
#include "Application.h"
#include "FileSystem.h"
#include "StringUtils.h"
#include "modplatform/import_ftb/PackHelpers.h"
@ -29,7 +31,7 @@
namespace FTBImportAPP {
QString getPath()
QString getStaticPath()
{
QString partialPath;
#if defined(Q_OS_OSX)
@ -42,14 +44,14 @@ QString getPath()
return FS::PathCombine(partialPath, ".ftba");
}
const QString ListModel::FTB_APP_PATH = getPath();
static const QString FTB_APP_PATH = FS::PathCombine(getStaticPath(), "instances");
void ListModel::update()
{
beginResetModel();
modpacks.clear();
QString instancesPath = FS::PathCombine(FTB_APP_PATH, "instances");
QString instancesPath = getPath();
if (auto instancesInfo = QFileInfo(instancesPath); instancesInfo.exists() && instancesInfo.isDir()) {
QDirIterator directoryIterator(instancesPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden,
QDirIterator::FollowSymlinks);
@ -168,4 +170,17 @@ FilterModel::Sorting FilterModel::getCurrentSorting()
{
return currentSorting;
}
void ListModel::setPath(QString path)
{
APPLICATION->settings()->set("FTBAppInstancesPath", path);
update();
}
QString ListModel::getPath()
{
auto path = APPLICATION->settings()->get("FTBAppInstancesPath").toString();
if (path.isEmpty() || !QFileInfo(path).exists())
path = FTB_APP_PATH;
return path;
}
} // namespace FTBImportAPP

View File

@ -60,7 +60,8 @@ class ListModel : public QAbstractListModel {
void update();
static const QString FTB_APP_PATH;
QString getPath();
void setPath(QString path);
private:
ModpackList modpacks;

View File

@ -35,11 +35,11 @@ ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget* parent) : QWidget(pa
connect(ui->backgroundCatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyCatTheme);
connect(ui->iconsFolder, &QPushButton::clicked, this,
[] { DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path()); });
[] { DesktopServices::openPath(APPLICATION->themeManager()->getIconThemesFolder().path()); });
connect(ui->widgetStyleFolder, &QPushButton::clicked, this,
[] { DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path()); });
[] { DesktopServices::openPath(APPLICATION->themeManager()->getApplicationThemesFolder().path()); });
connect(ui->catPackFolder, &QPushButton::clicked, this,
[] { DesktopServices::openDirectory(APPLICATION->themeManager()->getCatPacksFolder().path()); });
[] { DesktopServices::openPath(APPLICATION->themeManager()->getCatPacksFolder().path()); });
connect(ui->refreshButton, &QPushButton::clicked, this, &ThemeCustomizationWidget::refresh);
}

View File

@ -9,7 +9,8 @@ See [Package variants](#package-variants) for a list of available packages.
## Installing a development release (flake)
We use [garnix](https://garnix.io/) to build and cache our development builds.
If you want to avoid rebuilds you may add the garnix cache to your substitutors.
If you want to avoid rebuilds you may add the garnix cache to your substitutors, or use `--accept-flake-config`
to temporarily enable it when using `nix` commands.
Example (NixOS):