Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into fix/component-version-resolusion

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2024-08-23 08:29:43 +03:00
commit 575cdb043b
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
97 changed files with 3260 additions and 804 deletions

View File

@ -25,7 +25,7 @@ jobs:
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs - name: Create backport PRs
uses: korthout/backport-action@v3.0.2 uses: korthout/backport-action@v3.1.0
with: with:
# Config README: https://github.com/korthout/backport-action#backport-action # Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |- pull_description: |-

View File

@ -266,23 +266,23 @@ jobs:
- name: Configure CMake (macOS) - name: Configure CMake (macOS)
if: runner.os == 'macOS' && matrix.qt_ver == 6 if: runner.os == 'macOS' && matrix.qt_ver == 6
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_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 }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_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
- name: Configure CMake (macOS-Legacy) - name: Configure CMake (macOS-Legacy)
if: runner.os == 'macOS' && matrix.qt_ver == 5 if: runner.os == 'macOS' && matrix.qt_ver == 5
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_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 }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_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 }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja
- name: Configure CMake (Windows MinGW-w64) - name: Configure CMake (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0} shell: msys2 {0}
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_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=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_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) - name: Configure CMake (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' if: runner.os == 'Windows' && matrix.msystem == ''
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=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 }} cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_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 }}
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
if ("${{ env.CCACHE_VAR }}") if ("${{ env.CCACHE_VAR }}")
{ {
@ -297,7 +297,7 @@ jobs:
- name: Configure CMake (Linux) - name: Configure CMake (Linux)
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -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 }} -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DENABLE_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 # BUILD

View File

@ -219,6 +219,19 @@ set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" CACHE STRING "URL
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules") set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against") set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against")
# Java downloader
set(ENABLE_JAVA_DOWNLOADER_DEFAULT ON)
# Although we recommend enabling this, we cannot guarantee binary compatibility on
# differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this
# feature if they know it will work with their distribution.
if(UNIX AND NOT APPLE)
set(ENABLE_JAVA_DOWNLOADER_DEFAULT OFF)
endif()
# Java downloader
option(ENABLE_JAVA_DOWNLOADER "Build the java downloader feature" ${ENABLE_JAVA_DOWNLOADER_DEFAULT})
# Native libraries # Native libraries
if(UNIX AND APPLE) if(UNIX AND APPLE)
set(Launcher_GLFW_LIBRARY_NAME "libglfw.dylib" CACHE STRING "Name of native glfw library") set(Launcher_GLFW_LIBRARY_NAME "libglfw.dylib" CACHE STRING "Name of native glfw library")

View File

@ -81,6 +81,9 @@ Config::Config()
UPDATER_ENABLED = true; UPDATER_ENABLED = true;
} }
#cmakedefine01 ENABLE_JAVA_DOWNLOADER
JAVA_DOWNLOADER_ENABLED = ENABLE_JAVA_DOWNLOADER;
GIT_COMMIT = "@Launcher_GIT_COMMIT@"; GIT_COMMIT = "@Launcher_GIT_COMMIT@";
GIT_TAG = "@Launcher_GIT_TAG@"; GIT_TAG = "@Launcher_GIT_TAG@";
GIT_REFSPEC = "@Launcher_GIT_REFSPEC@"; GIT_REFSPEC = "@Launcher_GIT_REFSPEC@";

View File

@ -67,6 +67,7 @@ class Config {
QString VERSION_CHANNEL; QString VERSION_CHANNEL;
bool UPDATER_ENABLED = false; bool UPDATER_ENABLED = false;
bool JAVA_DOWNLOADER_ENABLED = false;
/// A short string identifying this build's platform or distribution. /// A short string identifying this build's platform or distribution.
QString BUILD_PLATFORM; QString BUILD_PLATFORM;

12
flake.lock generated
View File

@ -75,11 +75,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1723175592, "lastModified": 1723637854,
"narHash": "sha256-M0xJ3FbDUc4fRZ84dPGx5VvgFsOzds77KiBMW/mMTnI=", "narHash": "sha256-med8+5DSWa2UnOqtdICndjDAEjxr5D7zaIiK4pn0Q7c=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "5e0ca22929f3342b19569b21b2f3462f053e497b", "rev": "c3aa7b8938b17aebd2deecf7be0636000d62a2b9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -103,11 +103,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1723202784, "lastModified": 1723803910,
"narHash": "sha256-qbhjc/NEGaDbyy0ucycubq4N3//gDFFH3DOmp1D3u1Q=", "narHash": "sha256-yezvUuFiEnCFbGuwj/bQcqg7RykIEqudOy/RBrId0pc=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "c7012d0c18567c889b948781bc74a501e92275d1", "rev": "bfef0ada09e2c8ac55bbcd0831bd0c9d42e651ba",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -44,6 +44,7 @@
#include "BuildConfig.h" #include "BuildConfig.h"
#include "DataMigrationTask.h" #include "DataMigrationTask.h"
#include "java/JavaInstallList.h"
#include "net/PasteUpload.h" #include "net/PasteUpload.h"
#include "pathmatcher/MultiMatcher.h" #include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/SimplePrefixMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h"
@ -125,6 +126,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <sys.h> #include <sys.h>
#include "SysInfo.h"
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
#include <dlfcn.h> #include <dlfcn.h>
@ -602,6 +604,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
m_settings->registerSetting("DownloadsDirWatchRecursive", false); m_settings->registerSetting("DownloadsDirWatchRecursive", false);
m_settings->registerSetting("SkinsDir", "skins"); m_settings->registerSetting("SkinsDir", "skins");
m_settings->registerSetting("JavaDir", "java");
// Editors // Editors
m_settings->registerSetting("JsonEditor", QString()); m_settings->registerSetting("JsonEditor", QString());
@ -630,7 +633,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// Memory // Memory
m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512); m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512);
m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, suitableMaxMem()); m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::suitableMaxMem());
m_settings->registerSetting("PermGen", 128); m_settings->registerSetting("PermGen", 128);
// Java Settings // Java Settings
@ -644,6 +647,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("JvmArgs", ""); m_settings->registerSetting("JvmArgs", "");
m_settings->registerSetting("IgnoreJavaCompatibility", false); m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", false); m_settings->registerSetting("IgnoreJavaWizard", false);
auto defaultEnableAutoJava = m_settings->get("JavaPath").toString().isEmpty();
m_settings->registerSetting("AutomaticJavaSwitch", defaultEnableAutoJava);
m_settings->registerSetting("AutomaticJavaDownload", defaultEnableAutoJava);
// Legacy settings // Legacy settings
m_settings->registerSetting("OnlineFixes", false); m_settings->registerSetting("OnlineFixes", false);
@ -873,6 +879,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_metacache->addBase("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath()); m_metacache->addBase("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath());
m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("meta", QDir("meta").absolutePath()); m_metacache->addBase("meta", QDir("meta").absolutePath());
m_metacache->addBase("java", QDir("cache/java").absolutePath());
m_metacache->Load(); m_metacache->Load();
qDebug() << "<> Cache initialized."; qDebug() << "<> Cache initialized.";
} }
@ -1740,20 +1747,6 @@ QString Application::getUserAgentUncached()
return BuildConfig.USER_AGENT_UNCACHED; return BuildConfig.USER_AGENT_UNCACHED;
} }
int Application::suitableMaxMem()
{
float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte;
int maxMemoryAlloc;
// If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB
if (totalRAM < (4096 * 1.5))
maxMemoryAlloc = (int)(totalRAM / 1.5);
else
maxMemoryAlloc = 4096;
return maxMemoryAlloc;
}
bool Application::handleDataMigration(const QString& currentData, bool Application::handleDataMigration(const QString& currentData,
const QString& oldData, const QString& oldData,
const QString& name, const QString& name,
@ -1860,3 +1853,7 @@ QUrl Application::normalizeImportUrl(QString const& url)
return QUrl::fromUserInput(url); return QUrl::fromUserInput(url);
} }
} }
const QString Application::javaPath()
{
return m_settings->get("JavaDir").toString();
}

View File

@ -161,6 +161,9 @@ class Application : public QApplication {
/// the data path the application is using /// the data path the application is using
const QString& dataRoot() { return m_dataPath; } const QString& dataRoot() { return m_dataPath; }
/// the java installed path the application is using
const QString javaPath();
bool isPortable() { return m_portable; } bool isPortable() { return m_portable; }
const Capabilities capabilities() { return m_capabilities; } const Capabilities capabilities() { return m_capabilities; }
@ -179,8 +182,6 @@ class Application : public QApplication {
void ShowGlobalSettings(class QWidget* parent, QString open_page = QString()); void ShowGlobalSettings(class QWidget* parent, QString open_page = QString());
int suitableMaxMem();
bool updaterEnabled(); bool updaterEnabled();
QString updaterBinaryName(); QString updaterBinaryName();

View File

@ -78,6 +78,14 @@ QVariant BaseVersionList::data(const QModelIndex& index, int role) const
case TypeRole: case TypeRole:
return version->typeString(); return version->typeString();
case JavaMajorRole: {
auto major = version->name();
if (major.startsWith("java")) {
major = "Java " + major.mid(4);
}
return major;
}
default: default:
return QVariant(); return QVariant();
} }
@ -110,6 +118,8 @@ QHash<int, QByteArray> BaseVersionList::roleNames() const
roles.insert(TypeRole, "type"); roles.insert(TypeRole, "type");
roles.insert(BranchRole, "branch"); roles.insert(BranchRole, "branch");
roles.insert(PathRole, "path"); roles.insert(PathRole, "path");
roles.insert(ArchitectureRole, "architecture"); roles.insert(JavaNameRole, "javaName");
roles.insert(CPUArchitectureRole, "architecture");
roles.insert(JavaMajorRole, "javaMajor");
return roles; return roles;
} }

View File

@ -48,7 +48,9 @@ class BaseVersionList : public QAbstractListModel {
TypeRole, TypeRole,
BranchRole, BranchRole,
PathRole, PathRole,
ArchitectureRole, JavaNameRole,
JavaMajorRole,
CPUArchitectureRole,
SortRole SortRole
}; };
using RoleList = QList<int>; using RoleList = QList<int>;

View File

@ -24,6 +24,8 @@ set(CORE_SOURCES
NullInstance.h NullInstance.h
MMCZip.h MMCZip.h
MMCZip.cpp MMCZip.cpp
Untar.h
Untar.cpp
StringUtils.h StringUtils.h
StringUtils.cpp StringUtils.cpp
QVariantUtils.h QVariantUtils.h
@ -277,6 +279,8 @@ set(MINECRAFT_SOURCES
minecraft/launch/ScanModFolders.h minecraft/launch/ScanModFolders.h
minecraft/launch/VerifyJavaInstall.cpp minecraft/launch/VerifyJavaInstall.cpp
minecraft/launch/VerifyJavaInstall.h minecraft/launch/VerifyJavaInstall.h
minecraft/launch/AutoInstallJava.cpp
minecraft/launch/AutoInstallJava.h
minecraft/GradleSpecifier.h minecraft/GradleSpecifier.h
minecraft/MinecraftInstance.cpp minecraft/MinecraftInstance.cpp
@ -422,8 +426,6 @@ set(SETTINGS_SOURCES
set(JAVA_SOURCES set(JAVA_SOURCES
java/JavaChecker.h java/JavaChecker.h
java/JavaChecker.cpp java/JavaChecker.cpp
java/JavaCheckerJob.h
java/JavaCheckerJob.cpp
java/JavaInstall.h java/JavaInstall.h
java/JavaInstall.cpp java/JavaInstall.cpp
java/JavaInstallList.h java/JavaInstallList.h
@ -432,6 +434,18 @@ set(JAVA_SOURCES
java/JavaUtils.cpp java/JavaUtils.cpp
java/JavaVersion.h java/JavaVersion.h
java/JavaVersion.cpp java/JavaVersion.cpp
java/JavaMetadata.h
java/JavaMetadata.cpp
java/download/ArchiveDownloadTask.cpp
java/download/ArchiveDownloadTask.h
java/download/ManifestDownloadTask.cpp
java/download/ManifestDownloadTask.h
ui/java/InstallJavaDialog.h
ui/java/InstallJavaDialog.cpp
ui/java/VersionList.h
ui/java/VersionList.cpp
) )
set(TRANSLATIONS_SOURCES set(TRANSLATIONS_SOURCES
@ -767,6 +781,8 @@ SET(LAUNCHER_SOURCES
DataMigrationTask.cpp DataMigrationTask.cpp
ApplicationMessage.h ApplicationMessage.h
ApplicationMessage.cpp ApplicationMessage.cpp
SysInfo.h
SysInfo.cpp
# GUI - general utilities # GUI - general utilities
DesktopServices.h DesktopServices.h
@ -805,8 +821,6 @@ SET(LAUNCHER_SOURCES
# GUI - windows # GUI - windows
ui/GuiUtil.h ui/GuiUtil.h
ui/GuiUtil.cpp ui/GuiUtil.cpp
ui/ColorCache.h
ui/ColorCache.cpp
ui/MainWindow.h ui/MainWindow.h
ui/MainWindow.cpp ui/MainWindow.cpp
ui/InstanceWindow.h ui/InstanceWindow.h

View File

@ -276,6 +276,9 @@ bool ensureFolderPathExists(const QFileInfo folderPath)
{ {
QDir dir; QDir dir;
QString ensuredPath = folderPath.filePath(); QString ensuredPath = folderPath.filePath();
if (folderPath.exists())
return true;
bool success = dir.mkpath(ensuredPath); bool success = dir.mkpath(ensuredPath);
return success; return success;
} }

View File

@ -1,16 +1,12 @@
#include "Filter.h" #include "Filter.h"
Filter::~Filter() {}
ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern) {} ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern) {}
ContainsFilter::~ContainsFilter() {}
bool ContainsFilter::accepts(const QString& value) bool ContainsFilter::accepts(const QString& value)
{ {
return value.contains(pattern); return value.contains(pattern);
} }
ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern) {} ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern) {}
ExactFilter::~ExactFilter() {}
bool ExactFilter::accepts(const QString& value) bool ExactFilter::accepts(const QString& value)
{ {
return value == pattern; return value == pattern;
@ -27,10 +23,15 @@ RegexpFilter::RegexpFilter(const QString& regexp, bool invert) : invert(invert)
pattern.setPattern(regexp); pattern.setPattern(regexp);
pattern.optimize(); pattern.optimize();
} }
RegexpFilter::~RegexpFilter() {}
bool RegexpFilter::accepts(const QString& value) bool RegexpFilter::accepts(const QString& value)
{ {
auto match = pattern.match(value); auto match = pattern.match(value);
bool matched = match.hasMatch(); bool matched = match.hasMatch();
return invert ? (!matched) : (matched); return invert ? (!matched) : (matched);
} }
ExactListFilter::ExactListFilter(const QStringList& pattern) : m_pattern(pattern) {}
bool ExactListFilter::accepts(const QString& value)
{
return m_pattern.isEmpty() || m_pattern.contains(value);
}

View File

@ -5,14 +5,14 @@
class Filter { class Filter {
public: public:
virtual ~Filter(); virtual ~Filter() = default;
virtual bool accepts(const QString& value) = 0; virtual bool accepts(const QString& value) = 0;
}; };
class ContainsFilter : public Filter { class ContainsFilter : public Filter {
public: public:
ContainsFilter(const QString& pattern); ContainsFilter(const QString& pattern);
virtual ~ContainsFilter(); virtual ~ContainsFilter() = default;
bool accepts(const QString& value) override; bool accepts(const QString& value) override;
private: private:
@ -22,7 +22,7 @@ class ContainsFilter : public Filter {
class ExactFilter : public Filter { class ExactFilter : public Filter {
public: public:
ExactFilter(const QString& pattern); ExactFilter(const QString& pattern);
virtual ~ExactFilter(); virtual ~ExactFilter() = default;
bool accepts(const QString& value) override; bool accepts(const QString& value) override;
private: private:
@ -32,7 +32,7 @@ class ExactFilter : public Filter {
class ExactIfPresentFilter : public Filter { class ExactIfPresentFilter : public Filter {
public: public:
ExactIfPresentFilter(const QString& pattern); ExactIfPresentFilter(const QString& pattern);
~ExactIfPresentFilter() override = default; virtual ~ExactIfPresentFilter() override = default;
bool accepts(const QString& value) override; bool accepts(const QString& value) override;
private: private:
@ -42,10 +42,20 @@ class ExactIfPresentFilter : public Filter {
class RegexpFilter : public Filter { class RegexpFilter : public Filter {
public: public:
RegexpFilter(const QString& regexp, bool invert); RegexpFilter(const QString& regexp, bool invert);
virtual ~RegexpFilter(); virtual ~RegexpFilter() = default;
bool accepts(const QString& value) override; bool accepts(const QString& value) override;
private: private:
QRegularExpression pattern; QRegularExpression pattern;
bool invert = false; bool invert = false;
}; };
class ExactListFilter : public Filter {
public:
ExactListFilter(const QStringList& pattern = {});
virtual ~ExactListFilter() = default;
bool accepts(const QString& value) override;
private:
const QStringList& m_pattern;
};

View File

@ -63,7 +63,7 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent)
return true; return true;
} }
void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result) void JavaCommon::javaWasOk(QWidget* parent, const JavaChecker::Result& result)
{ {
QString text; QString text;
text += QObject::tr( text += QObject::tr(
@ -79,7 +79,7 @@ void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result)
CustomMessageBox::selectable(parent, QObject::tr("Java test success"), text, QMessageBox::Information)->show(); CustomMessageBox::selectable(parent, QObject::tr("Java test success"), text, QMessageBox::Information)->show();
} }
void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaCheckResult& result) void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaChecker::Result& result)
{ {
auto htmlError = result.errorLog; auto htmlError = result.errorLog;
QString text; QString text;
@ -89,7 +89,7 @@ void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaCheckResult& result)
CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show();
} }
void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result) void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& result)
{ {
QString text; QString text;
text += QObject::tr( text += QObject::tr(
@ -116,34 +116,26 @@ void JavaCommon::TestCheck::run()
emit finished(); emit finished();
return; return;
} }
checker.reset(new JavaChecker()); checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0, this));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
checker->m_path = m_path; checker->start();
checker->performCheck();
} }
void JavaCommon::TestCheck::checkFinished(JavaCheckResult result) void JavaCommon::TestCheck::checkFinished(const JavaChecker::Result& result)
{ {
if (result.validity != JavaCheckResult::Validity::Valid) { if (result.validity != JavaChecker::Result::Validity::Valid) {
javaBinaryWasBad(m_parent, result); javaBinaryWasBad(m_parent, result);
emit finished(); emit finished();
return; return;
} }
checker.reset(new JavaChecker()); checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0, this));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
checker->m_path = m_path; checker->start();
checker->m_args = m_args;
checker->m_minMem = m_minMem;
checker->m_maxMem = m_maxMem;
if (result.javaVersion.requiresPermGen()) {
checker->m_permGen = m_permGen;
}
checker->performCheck();
} }
void JavaCommon::TestCheck::checkFinishedWithArgs(JavaCheckResult result) void JavaCommon::TestCheck::checkFinishedWithArgs(const JavaChecker::Result& result)
{ {
if (result.validity == JavaCheckResult::Validity::Valid) { if (result.validity == JavaChecker::Result::Validity::Valid) {
javaWasOk(m_parent, result); javaWasOk(m_parent, result);
emit finished(); emit finished();
return; return;

View File

@ -10,11 +10,11 @@ namespace JavaCommon {
bool checkJVMArgs(QString args, QWidget* parent); bool checkJVMArgs(QString args, QWidget* parent);
// Show a dialog saying that the Java binary was usable // Show a dialog saying that the Java binary was usable
void javaWasOk(QWidget* parent, const JavaCheckResult& result); void javaWasOk(QWidget* parent, const JavaChecker::Result& result);
// Show a dialog saying that the Java binary was not usable because of bad options // Show a dialog saying that the Java binary was not usable because of bad options
void javaArgsWereBad(QWidget* parent, const JavaCheckResult& result); void javaArgsWereBad(QWidget* parent, const JavaChecker::Result& result);
// Show a dialog saying that the Java binary was not usable // Show a dialog saying that the Java binary was not usable
void javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result); void javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& result);
// Show a dialog if we couldn't find Java Checker // Show a dialog if we couldn't find Java Checker
void javaCheckNotFound(QWidget* parent); void javaCheckNotFound(QWidget* parent);
@ -32,11 +32,11 @@ class TestCheck : public QObject {
void finished(); void finished();
private slots: private slots:
void checkFinished(JavaCheckResult result); void checkFinished(const JavaChecker::Result& result);
void checkFinishedWithArgs(JavaCheckResult result); void checkFinishedWithArgs(const JavaChecker::Result& result);
private: private:
std::shared_ptr<JavaChecker> checker; JavaChecker::Ptr checker;
QWidget* m_parent = nullptr; QWidget* m_parent = nullptr;
QString m_path; QString m_path;
QString m_args; QString m_args;

View File

@ -2,7 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -536,6 +536,10 @@ bool ExportToZipTask::abort()
void ExtractZipTask::executeTask() void ExtractZipTask::executeTask()
{ {
if (!m_input->isOpen() && !m_input->open(QuaZip::mdUnzip)) {
emitFailed(tr("Unable to open supplied zip file."));
return;
}
m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); }); m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); });
connect(&m_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExtractZipTask::finish); connect(&m_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExtractZipTask::finish);
m_zip_watcher.setFuture(m_zip_future); m_zip_watcher.setFuture(m_zip_future);

View File

@ -2,7 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -208,6 +208,9 @@ class ExportToZipTask : public Task {
class ExtractZipTask : public Task { class ExtractZipTask : public Task {
public: public:
ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "")
: ExtractZipTask(std::make_shared<QuaZip>(input), outputDir, subdirectory)
{}
ExtractZipTask(std::shared_ptr<QuaZip> input, QDir outputDir, QString subdirectory = "") ExtractZipTask(std::shared_ptr<QuaZip> input, QDir outputDir, QString subdirectory = "")
: m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory) : m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory)
{} {}

99
launcher/SysInfo.cpp Normal file
View File

@ -0,0 +1,99 @@
#include <QDebug>
#include <QString>
#include "sys.h"
#ifdef Q_OS_MACOS
#include <sys/sysctl.h>
#endif
#include <QFile>
#include <QMap>
#include <QProcess>
#include <QStandardPaths>
#ifdef Q_OS_MACOS
bool rosettaDetect()
{
int ret = 0;
size_t size = sizeof(ret);
if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) {
return false;
}
return ret == 1;
}
#endif
namespace SysInfo {
QString currentSystem()
{
#if defined(Q_OS_LINUX)
return "linux";
#elif defined(Q_OS_MACOS)
return "osx";
#elif defined(Q_OS_WINDOWS)
return "windows";
#elif defined(Q_OS_FREEBSD)
return "freebsd";
#elif defined(Q_OS_OPENBSD)
return "openbsd";
#else
return "unknown";
#endif
}
QString useQTForArch()
{
#if defined(Q_OS_MACOS) && !defined(Q_PROCESSOR_ARM)
if (rosettaDetect()) {
return "arm64";
} else {
return "x86_64";
}
#endif
return QSysInfo::currentCpuArchitecture();
}
int suitableMaxMem()
{
float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte;
int maxMemoryAlloc;
// If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB
if (totalRAM < (4096 * 1.5))
maxMemoryAlloc = (int)(totalRAM / 1.5);
else
maxMemoryAlloc = 4096;
return maxMemoryAlloc;
}
QString getSupportedJavaArchitecture()
{
auto sys = currentSystem();
auto arch = useQTForArch();
if (sys == "windows") {
if (arch == "x86_64")
return "windows-x64";
if (arch == "i386")
return "windows-x86";
// Unknown, maybe arm, appending arch
return "windows-" + arch;
}
if (sys == "osx") {
if (arch == "arm64")
return "mac-os-arm64";
if (arch.contains("64"))
return "mac-os-64";
if (arch.contains("86"))
return "mac-os-86";
// Unknown, maybe something new, appending arch
return "mac-os-" + arch;
} else if (sys == "linux") {
if (arch == "x86_64")
return "linux-x64";
if (arch == "i386")
return "linux-x86";
// will work for arm32 arm(64)
return "linux-" + arch;
}
return {};
}
} // namespace SysInfo

8
launcher/SysInfo.h Normal file
View File

@ -0,0 +1,8 @@
#include <QString>
namespace SysInfo {
QString currentSystem();
QString useQTForArch();
QString getSupportedJavaArchitecture();
int suitableMaxMem();
} // namespace SysInfo

260
launcher/Untar.cpp Normal file
View File

@ -0,0 +1,260 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* 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 "Untar.h"
#include <quagzipfile.h>
#include <QByteArray>
#include <QFileInfo>
#include <QIODevice>
#include <QString>
#include "FileSystem.h"
// adaptation of the:
// - https://github.com/madler/zlib/blob/develop/contrib/untgz/untgz.c
// - https://en.wikipedia.org/wiki/Tar_(computing)
// - https://github.com/euroelessar/cutereader/blob/master/karchive/src/ktar.cpp
#define BLOCKSIZE 512
#define SHORTNAMESIZE 100
enum class TypeFlag : char {
Regular = '0', // regular file
ARegular = 0, // regular file
Link = '1', // link
Symlink = '2', // reserved
Character = '3', // character special
Block = '4', // block special
Directory = '5', // directory
FIFO = '6', // FIFO special
Contiguous = '7', // reserved
// Posix stuff
GlobalPosixHeader = 'g',
ExtendedPosixHeader = 'x',
// 'A' 'Z' Vendor specific extensions(POSIX .1 - 1988)
// GNU
GNULongLink = 'K', /* long link name */
GNULongName = 'L', /* long file name */
};
// struct Header { /* byte offset */
// char name[100]; /* 0 */
// char mode[8]; /* 100 */
// char uid[8]; /* 108 */
// char gid[8]; /* 116 */
// char size[12]; /* 124 */
// char mtime[12]; /* 136 */
// char chksum[8]; /* 148 */
// TypeFlag typeflag; /* 156 */
// char linkname[100]; /* 157 */
// char magic[6]; /* 257 */
// char version[2]; /* 263 */
// char uname[32]; /* 265 */
// char gname[32]; /* 297 */
// char devmajor[8]; /* 329 */
// char devminor[8]; /* 337 */
// char prefix[155]; /* 345 */
// /* 500 */
// };
bool readLonglink(QIODevice* in, qint64 size, QByteArray& longlink)
{
qint64 n = 0;
size--; // ignore trailing null
if (size < 0) {
qCritical() << "The filename size is negative";
return false;
}
longlink.resize(size + (BLOCKSIZE - size % BLOCKSIZE)); // make the size divisible by BLOCKSIZE
for (qint64 offset = 0; offset < longlink.size(); offset += BLOCKSIZE) {
n = in->read(longlink.data() + offset, BLOCKSIZE);
if (n != BLOCKSIZE) {
qCritical() << "The expected blocksize was not respected for the name";
return false;
}
}
longlink.truncate(qstrlen(longlink.constData()));
return true;
}
int getOctal(char* buffer, int maxlenght, bool* ok)
{
return QByteArray(buffer, qstrnlen(buffer, maxlenght)).toInt(ok, 8);
}
QString decodeName(char* name)
{
return QFile::decodeName(QByteArray(name, qstrnlen(name, 100)));
}
bool Tar::extract(QIODevice* in, QString dst)
{
char buffer[BLOCKSIZE];
QString name, symlink, firstFolderName;
bool doNotReset = false, ok;
while (true) {
auto n = in->read(buffer, BLOCKSIZE);
if (n != BLOCKSIZE) { // allways expect complete blocks
qCritical() << "The expected blocksize was not respected";
return false;
}
if (buffer[0] == 0) { // end of archive
return true;
}
int mode = getOctal(buffer + 100, 8, &ok) | QFile::ReadUser | QFile::WriteUser; // hack to ensure write and read permisions
if (!ok) {
qCritical() << "The file mode can't be read";
return false;
}
// there are names that are exactly 100 bytes long
// and neither longlink nor \0 terminated (bug:101472)
if (name.isEmpty()) {
name = decodeName(buffer);
if (!firstFolderName.isEmpty() && name.startsWith(firstFolderName)) {
name = name.mid(firstFolderName.size());
}
}
if (symlink.isEmpty())
symlink = decodeName(buffer);
qint64 size = getOctal(buffer + 124, 12, &ok);
if (!ok) {
qCritical() << "The file size can't be read";
return false;
}
switch (TypeFlag(buffer[156])) {
case TypeFlag::Regular:
/* fallthrough */
case TypeFlag::ARegular: {
auto fileName = FS::PathCombine(dst, name);
if (!FS::ensureFilePathExists(fileName)) {
qCritical() << "Can't ensure the file path to exist: " << fileName;
return false;
}
QFile out(fileName);
if (!out.open(QFile::WriteOnly)) {
qCritical() << "Can't open file:" << fileName;
return false;
}
out.setPermissions(QFile::Permissions(mode));
while (size > 0) {
QByteArray tmp(BLOCKSIZE, 0);
n = in->read(tmp.data(), BLOCKSIZE);
if (n != BLOCKSIZE) {
qCritical() << "The expected blocksize was not respected when reading file";
return false;
}
tmp.truncate(qMin(qint64(BLOCKSIZE), size));
out.write(tmp);
size -= BLOCKSIZE;
}
break;
}
case TypeFlag::Directory: {
if (firstFolderName.isEmpty()) {
firstFolderName = name;
break;
}
auto folderPath = FS::PathCombine(dst, name);
if (!FS::ensureFolderPathExists(folderPath)) {
qCritical() << "Can't ensure that folder exists: " << folderPath;
return false;
}
break;
}
case TypeFlag::GNULongLink: {
doNotReset = true;
QByteArray longlink;
if (readLonglink(in, size, longlink)) {
symlink = QFile::decodeName(longlink.constData());
} else {
qCritical() << "Failed to read long link";
return false;
}
break;
}
case TypeFlag::GNULongName: {
doNotReset = true;
QByteArray longlink;
if (readLonglink(in, size, longlink)) {
name = QFile::decodeName(longlink.constData());
} else {
qCritical() << "Failed to read long name";
return false;
}
break;
}
case TypeFlag::Link:
/* fallthrough */
case TypeFlag::Symlink: {
auto fileName = FS::PathCombine(dst, name);
if (!FS::create_link(FS::PathCombine(QFileInfo(fileName).path(), symlink), fileName)()) { // do not use symlinks
qCritical() << "Can't create link for:" << fileName << " to:" << FS::PathCombine(QFileInfo(fileName).path(), symlink);
return false;
}
FS::ensureFilePathExists(fileName);
QFile::setPermissions(fileName, QFile::Permissions(mode));
break;
}
case TypeFlag::Character:
/* fallthrough */
case TypeFlag::Block:
/* fallthrough */
case TypeFlag::FIFO:
/* fallthrough */
case TypeFlag::Contiguous:
/* fallthrough */
case TypeFlag::GlobalPosixHeader:
/* fallthrough */
case TypeFlag::ExtendedPosixHeader:
/* fallthrough */
default:
break;
}
if (!doNotReset) {
name.truncate(0);
symlink.truncate(0);
}
doNotReset = false;
}
return true;
}
bool GZTar::extract(QString src, QString dst)
{
QuaGzipFile a(src);
if (!a.open(QIODevice::ReadOnly)) {
qCritical() << "Can't open tar file:" << src;
return false;
}
return Tar::extract(&a, dst);
}

46
launcher/Untar.h Normal file
View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* 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 <QIODevice>
// this is a hack used for the java downloader (feel free to remove it in favor of a library)
// both extract functions will extract the first folder inside dest(disregarding the prefix)
namespace Tar {
bool extract(QIODevice* in, QString dst);
}
namespace GZTar {
bool extract(QString src, QString dst);
}

View File

@ -114,10 +114,14 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation,
return tr("Branch"); return tr("Branch");
case Type: case Type:
return tr("Type"); return tr("Type");
case Architecture: case CPUArchitecture:
return tr("Architecture"); return tr("Architecture");
case Path: case Path:
return tr("Path"); return tr("Path");
case JavaName:
return tr("Java Name");
case JavaMajor:
return tr("Major Version");
case Time: case Time:
return tr("Released"); return tr("Released");
} }
@ -131,10 +135,14 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation,
return tr("The version's branch"); return tr("The version's branch");
case Type: case Type:
return tr("The version's type"); return tr("The version's type");
case Architecture: case CPUArchitecture:
return tr("CPU Architecture"); return tr("CPU Architecture");
case Path: case Path:
return tr("Filesystem path to this version"); return tr("Filesystem path to this version");
case JavaName:
return tr("The alternative name of the java version");
case JavaMajor:
return tr("The java major version");
case Time: case Time:
return tr("Release date of this version"); return tr("Release date of this version");
} }
@ -165,10 +173,14 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const
return sourceModel()->data(parentIndex, BaseVersionList::BranchRole); return sourceModel()->data(parentIndex, BaseVersionList::BranchRole);
case Type: case Type:
return sourceModel()->data(parentIndex, BaseVersionList::TypeRole); return sourceModel()->data(parentIndex, BaseVersionList::TypeRole);
case Architecture: case CPUArchitecture:
return sourceModel()->data(parentIndex, BaseVersionList::ArchitectureRole); return sourceModel()->data(parentIndex, BaseVersionList::CPUArchitectureRole);
case Path: case Path:
return sourceModel()->data(parentIndex, BaseVersionList::PathRole); return sourceModel()->data(parentIndex, BaseVersionList::PathRole);
case JavaName:
return sourceModel()->data(parentIndex, BaseVersionList::JavaNameRole);
case JavaMajor:
return sourceModel()->data(parentIndex, BaseVersionList::JavaMajorRole);
case Time: case Time:
return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate(); return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate();
default: default:
@ -308,12 +320,18 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw)
m_columns.push_back(ParentVersion); m_columns.push_back(ParentVersion);
} }
*/ */
if (roles.contains(BaseVersionList::ArchitectureRole)) { if (roles.contains(BaseVersionList::CPUArchitectureRole)) {
m_columns.push_back(Architecture); m_columns.push_back(CPUArchitecture);
} }
if (roles.contains(BaseVersionList::PathRole)) { if (roles.contains(BaseVersionList::PathRole)) {
m_columns.push_back(Path); m_columns.push_back(Path);
} }
if (roles.contains(BaseVersionList::JavaNameRole)) {
m_columns.push_back(JavaName);
}
if (roles.contains(BaseVersionList::JavaMajorRole)) {
m_columns.push_back(JavaMajor);
}
if (roles.contains(Meta::VersionList::TimeRole)) { if (roles.contains(Meta::VersionList::TimeRole)) {
m_columns.push_back(Time); m_columns.push_back(Time);
} }

View File

@ -9,7 +9,7 @@ class VersionFilterModel;
class VersionProxyModel : public QAbstractProxyModel { class VersionProxyModel : public QAbstractProxyModel {
Q_OBJECT Q_OBJECT
public: public:
enum Column { Name, ParentVersion, Branch, Type, Architecture, Path, Time }; enum Column { Name, ParentVersion, Branch, Type, CPUArchitecture, Path, Time, JavaName, JavaMajor };
using FilterMap = QHash<BaseVersionList::ModelRoles, std::shared_ptr<Filter>>; using FilterMap = QHash<BaseVersionList::ModelRoles, std::shared_ptr<Filter>>;
public: public:

View File

@ -40,14 +40,15 @@
#include <QMap> #include <QMap>
#include <QProcess> #include <QProcess>
#include "Application.h"
#include "Commandline.h" #include "Commandline.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "JavaUtils.h" #include "java/JavaUtils.h"
JavaChecker::JavaChecker(QObject* parent) : QObject(parent) {} JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id, QObject* parent)
: Task(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id)
{}
void JavaChecker::performCheck() void JavaChecker::executeTask()
{ {
QString checkerJar = JavaUtils::getJavaCheckPath(); QString checkerJar = JavaUtils::getJavaCheckPath();
@ -72,7 +73,7 @@ void JavaChecker::performCheck()
if (m_maxMem != 0) { if (m_maxMem != 0) {
args << QString("-Xmx%1m").arg(m_maxMem); args << QString("-Xmx%1m").arg(m_maxMem);
} }
if (m_permGen != 64) { if (m_permGen != 64 && m_permGen != 0) {
args << QString("-XX:PermSize=%1m").arg(m_permGen); args << QString("-XX:PermSize=%1m").arg(m_permGen);
} }
@ -115,11 +116,10 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
QProcessPtr _process = process; QProcessPtr _process = process;
process.reset(); process.reset();
JavaCheckResult result; Result result = {
{ m_path,
result.path = m_path; m_id,
result.id = m_id; };
}
result.errorLog = m_stderr; result.errorLog = m_stderr;
result.outLog = m_stdout; result.outLog = m_stdout;
qDebug() << "STDOUT" << m_stdout; qDebug() << "STDOUT" << m_stdout;
@ -127,8 +127,9 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
qDebug() << "Java checker finished with status" << status << "exit code" << exitcode; qDebug() << "Java checker finished with status" << status << "exit code" << exitcode;
if (status == QProcess::CrashExit || exitcode == 1) { if (status == QProcess::CrashExit || exitcode == 1) {
result.validity = JavaCheckResult::Validity::Errored; result.validity = Result::Validity::Errored;
emit checkFinished(result); emit checkFinished(result);
emitSucceeded();
return; return;
} }
@ -161,8 +162,9 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
} }
if (!results.contains("os.arch") || !results.contains("java.version") || !results.contains("java.vendor") || !success) { if (!results.contains("os.arch") || !results.contains("java.version") || !results.contains("java.vendor") || !success) {
result.validity = JavaCheckResult::Validity::ReturnedInvalidData; result.validity = Result::Validity::ReturnedInvalidData;
emit checkFinished(result); emit checkFinished(result);
emitSucceeded();
return; return;
} }
@ -171,7 +173,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
auto java_vendor = results["java.vendor"]; auto java_vendor = results["java.vendor"];
bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64"; bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64";
result.validity = JavaCheckResult::Validity::Valid; result.validity = Result::Validity::Valid;
result.is_64bit = is_64; result.is_64bit = is_64;
result.mojangPlatform = is_64 ? "64" : "32"; result.mojangPlatform = is_64 ? "64" : "32";
result.realPlatform = os_arch; result.realPlatform = os_arch;
@ -179,6 +181,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
result.javaVendor = java_vendor; result.javaVendor = java_vendor;
qDebug() << "Java checker succeeded."; qDebug() << "Java checker succeeded.";
emit checkFinished(result); emit checkFinished(result);
emitSucceeded();
} }
void JavaChecker::error(QProcess::ProcessError err) void JavaChecker::error(QProcess::ProcessError err)
@ -190,15 +193,9 @@ void JavaChecker::error(QProcess::ProcessError err)
qDebug() << "Native environment:"; qDebug() << "Native environment:";
qDebug() << QProcessEnvironment::systemEnvironment().toStringList(); qDebug() << QProcessEnvironment::systemEnvironment().toStringList();
killTimer.stop(); killTimer.stop();
JavaCheckResult result; emit checkFinished({ m_path, m_id });
{
result.path = m_path;
result.id = m_id;
}
emit checkFinished(result);
return;
} }
emitSucceeded();
} }
void JavaChecker::timeout() void JavaChecker::timeout()

View File

@ -3,14 +3,19 @@
#include <QTimer> #include <QTimer>
#include <memory> #include <memory>
#include "QObjectPtr.h"
#include "JavaVersion.h" #include "JavaVersion.h"
#include "QObjectPtr.h"
#include "tasks/Task.h"
class JavaChecker; class JavaChecker : public Task {
Q_OBJECT
public:
using QProcessPtr = shared_qobject_ptr<QProcess>;
using Ptr = shared_qobject_ptr<JavaChecker>;
struct JavaCheckResult { struct Result {
QString path; QString path;
int id;
QString mojangPlatform; QString mojangPlatform;
QString realPlatform; QString realPlatform;
JavaVersion javaVersion; JavaVersion javaVersion;
@ -18,34 +23,31 @@ struct JavaCheckResult {
QString outLog; QString outLog;
QString errorLog; QString errorLog;
bool is_64bit = false; bool is_64bit = false;
int id;
enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored; enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
}; };
using QProcessPtr = shared_qobject_ptr<QProcess>; explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0, QObject* parent = 0);
using JavaCheckerPtr = shared_qobject_ptr<JavaChecker>;
class JavaChecker : public QObject {
Q_OBJECT
public:
explicit JavaChecker(QObject* parent = 0);
void performCheck();
QString m_path;
QString m_args;
int m_id = 0;
int m_minMem = 0;
int m_maxMem = 0;
int m_permGen = 64;
signals: signals:
void checkFinished(JavaCheckResult result); void checkFinished(const Result& result);
protected:
virtual void executeTask() override;
private: private:
QProcessPtr process; QProcessPtr process;
QTimer killTimer; QTimer killTimer;
QString m_stdout; QString m_stdout;
QString m_stderr; QString m_stderr;
public slots:
QString m_path;
QString m_args;
int m_minMem = 0;
int m_maxMem = 0;
int m_permGen = 64;
int m_id = 0;
private slots:
void timeout(); void timeout();
void finished(int exitcode, QProcess::ExitStatus); void finished(int exitcode, QProcess::ExitStatus);
void error(QProcess::ProcessError); void error(QProcess::ProcessError);

View File

@ -1,41 +0,0 @@
/* 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 "JavaCheckerJob.h"
#include <QDebug>
void JavaCheckerJob::partFinished(JavaCheckResult result)
{
num_finished++;
qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" << javacheckers.size();
setProgress(num_finished, javacheckers.size());
javaresults.replace(result.id, result);
if (num_finished == javacheckers.size()) {
emitSucceeded();
}
}
void JavaCheckerJob::executeTask()
{
qDebug() << m_job_name.toLocal8Bit() << " started.";
for (auto iter : javacheckers) {
javaresults.append(JavaCheckResult());
connect(iter.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
iter->performCheck();
}
}

View File

@ -1,56 +0,0 @@
/* 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 <QtNetwork>
#include "JavaChecker.h"
#include "tasks/Task.h"
class JavaCheckerJob;
using JavaCheckerJobPtr = shared_qobject_ptr<JavaCheckerJob>;
// FIXME: this just seems horribly redundant
class JavaCheckerJob : public Task {
Q_OBJECT
public:
explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {};
virtual ~JavaCheckerJob() {};
bool addJavaCheckerAction(JavaCheckerPtr base)
{
javacheckers.append(base);
// if this is already running, the action needs to be started right away!
if (isRunning()) {
setProgress(num_finished, javacheckers.size());
connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
base->performCheck();
}
return true;
}
QList<JavaCheckResult> getResults() { return javaresults; }
private slots:
void partFinished(JavaCheckResult result);
protected:
virtual void executeTask() override;
private:
QString m_job_name;
QList<JavaCheckerPtr> javacheckers;
QList<JavaCheckResult> javaresults;
int num_finished = 0;
};

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -40,6 +40,7 @@ struct JavaInstall : public BaseVersion {
QString arch; QString arch;
QString path; QString path;
bool recommended = false; bool recommended = false;
bool is_64bit = false;
}; };
using JavaInstallPtr = std::shared_ptr<JavaInstall>; using JavaInstallPtr = std::shared_ptr<JavaInstall>;

View File

@ -38,13 +38,17 @@
#include <QtXml> #include <QtXml>
#include <QDebug> #include <QDebug>
#include <algorithm>
#include "java/JavaCheckerJob.h" #include "Application.h"
#include "java/JavaChecker.h"
#include "java/JavaInstallList.h" #include "java/JavaInstallList.h"
#include "java/JavaUtils.h" #include "java/JavaUtils.h"
#include "minecraft/VersionFilterData.h" #include "tasks/ConcurrentTask.h"
JavaInstallList::JavaInstallList(QObject* parent) : BaseVersionList(parent) {} JavaInstallList::JavaInstallList(QObject* parent, bool onlyManagedVersions)
: BaseVersionList(parent), m_only_managed_versions(onlyManagedVersions)
{}
Task::Ptr JavaInstallList::getLoadTask() Task::Ptr JavaInstallList::getLoadTask()
{ {
@ -55,7 +59,7 @@ Task::Ptr JavaInstallList::getLoadTask()
Task::Ptr JavaInstallList::getCurrentTask() Task::Ptr JavaInstallList::getCurrentTask()
{ {
if (m_status == Status::InProgress) { if (m_status == Status::InProgress) {
return m_loadTask; return m_load_task;
} }
return nullptr; return nullptr;
} }
@ -64,8 +68,8 @@ void JavaInstallList::load()
{ {
if (m_status != Status::InProgress) { if (m_status != Status::InProgress) {
m_status = Status::InProgress; m_status = Status::InProgress;
m_loadTask.reset(new JavaListLoadTask(this)); m_load_task.reset(new JavaListLoadTask(this, m_only_managed_versions));
m_loadTask->start(); m_load_task->start();
} }
} }
@ -106,7 +110,7 @@ QVariant JavaInstallList::data(const QModelIndex& index, int role) const
return version->recommended; return version->recommended;
case PathRole: case PathRole:
return version->path; return version->path;
case ArchitectureRole: case CPUArchitectureRole:
return version->arch; return version->arch;
default: default:
return QVariant(); return QVariant();
@ -115,7 +119,7 @@ QVariant JavaInstallList::data(const QModelIndex& index, int role) const
BaseVersionList::RoleList JavaInstallList::providesRoles() const BaseVersionList::RoleList JavaInstallList::providesRoles() const
{ {
return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole }; return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, CPUArchitectureRole };
} }
void JavaInstallList::updateListData(QList<BaseVersion::Ptr> versions) void JavaInstallList::updateListData(QList<BaseVersion::Ptr> versions)
@ -129,7 +133,7 @@ void JavaInstallList::updateListData(QList<BaseVersion::Ptr> versions)
} }
endResetModel(); endResetModel();
m_status = Status::Done; m_status = Status::Done;
m_loadTask.reset(); m_load_task.reset();
} }
bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right) bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right)
@ -146,35 +150,30 @@ void JavaInstallList::sortVersions()
endResetModel(); endResetModel();
} }
JavaListLoadTask::JavaListLoadTask(JavaInstallList* vlist) : Task() JavaListLoadTask::JavaListLoadTask(JavaInstallList* vlist, bool onlyManagedVersions) : Task(), m_only_managed_versions(onlyManagedVersions)
{ {
m_list = vlist; m_list = vlist;
m_currentRecommended = NULL; m_current_recommended = NULL;
} }
JavaListLoadTask::~JavaListLoadTask() {}
void JavaListLoadTask::executeTask() void JavaListLoadTask::executeTask()
{ {
setStatus(tr("Detecting Java installations...")); setStatus(tr("Detecting Java installations..."));
JavaUtils ju; JavaUtils ju;
QList<QString> candidate_paths = ju.FindJavaPaths(); QList<QString> candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths();
m_job.reset(new JavaCheckerJob("Java detection")); ConcurrentTask::Ptr job(new ConcurrentTask(this, "Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
m_job.reset(job);
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
connect(m_job.get(), &Task::progress, this, &Task::setProgress); connect(m_job.get(), &Task::progress, this, &Task::setProgress);
qDebug() << "Probing the following Java paths: "; qDebug() << "Probing the following Java paths: ";
int id = 0; int id = 0;
for (QString candidate : candidate_paths) { for (QString candidate : candidate_paths) {
qDebug() << " " << candidate; auto checker = new JavaChecker(candidate, "", 0, 0, 0, id, this);
connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; });
auto candidate_checker = new JavaChecker(); job->addTask(Task::Ptr(checker));
candidate_checker->m_path = candidate;
candidate_checker->m_id = id;
m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker));
id++; id++;
} }
@ -184,16 +183,17 @@ void JavaListLoadTask::executeTask()
void JavaListLoadTask::javaCheckerFinished() void JavaListLoadTask::javaCheckerFinished()
{ {
QList<JavaInstallPtr> candidates; QList<JavaInstallPtr> candidates;
auto results = m_job->getResults(); std::sort(m_results.begin(), m_results.end(), [](const JavaChecker::Result& a, const JavaChecker::Result& b) { return a.id < b.id; });
qDebug() << "Found the following valid Java installations:"; qDebug() << "Found the following valid Java installations:";
for (JavaCheckResult result : results) { for (auto result : m_results) {
if (result.validity == JavaCheckResult::Validity::Valid) { if (result.validity == JavaChecker::Result::Validity::Valid) {
JavaInstallPtr javaVersion(new JavaInstall()); JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = result.javaVersion; javaVersion->id = result.javaVersion;
javaVersion->arch = result.realPlatform; javaVersion->arch = result.realPlatform;
javaVersion->path = result.path; javaVersion->path = result.path;
javaVersion->is_64bit = result.is_64bit;
candidates.append(javaVersion); candidates.append(javaVersion);
qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path; qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path;

View File

@ -19,9 +19,9 @@
#include <QObject> #include <QObject>
#include "BaseVersionList.h" #include "BaseVersionList.h"
#include "java/JavaChecker.h"
#include "tasks/Task.h" #include "tasks/Task.h"
#include "JavaCheckerJob.h"
#include "JavaInstall.h" #include "JavaInstall.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
@ -33,7 +33,7 @@ class JavaInstallList : public BaseVersionList {
enum class Status { NotDone, InProgress, Done }; enum class Status { NotDone, InProgress, Done };
public: public:
explicit JavaInstallList(QObject* parent = 0); explicit JavaInstallList(QObject* parent = 0, bool onlyManagedVersions = false);
[[nodiscard]] Task::Ptr getLoadTask() override; [[nodiscard]] Task::Ptr getLoadTask() override;
bool isLoaded() override; bool isLoaded() override;
@ -53,23 +53,27 @@ class JavaInstallList : public BaseVersionList {
protected: protected:
Status m_status = Status::NotDone; Status m_status = Status::NotDone;
shared_qobject_ptr<JavaListLoadTask> m_loadTask; shared_qobject_ptr<JavaListLoadTask> m_load_task;
QList<BaseVersion::Ptr> m_vlist; QList<BaseVersion::Ptr> m_vlist;
bool m_only_managed_versions;
}; };
class JavaListLoadTask : public Task { class JavaListLoadTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit JavaListLoadTask(JavaInstallList* vlist); explicit JavaListLoadTask(JavaInstallList* vlist, bool onlyManagedVersions = false);
virtual ~JavaListLoadTask(); virtual ~JavaListLoadTask() = default;
protected:
void executeTask() override; void executeTask() override;
public slots: public slots:
void javaCheckerFinished(); void javaCheckerFinished();
protected: protected:
shared_qobject_ptr<JavaCheckerJob> m_job; Task::Ptr m_job;
JavaInstallList* m_list; JavaInstallList* m_list;
JavaInstall* m_currentRecommended; JavaInstall* m_current_recommended;
QList<JavaChecker::Result> m_results;
bool m_only_managed_versions;
}; };

View File

@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "java/JavaMetadata.h"
#include <memory>
#include "Json.h"
#include "StringUtils.h"
#include "java/JavaVersion.h"
#include "minecraft/ParseUtils.h"
namespace Java {
DownloadType parseDownloadType(QString javaDownload)
{
if (javaDownload == "manifest")
return DownloadType::Manifest;
else if (javaDownload == "archive")
return DownloadType::Archive;
else
return DownloadType::Unknown;
}
QString downloadTypeToString(DownloadType javaDownload)
{
switch (javaDownload) {
case DownloadType::Manifest:
return "manifest";
case DownloadType::Archive:
return "archive";
case DownloadType::Unknown:
break;
}
return "unknown";
}
MetadataPtr parseJavaMeta(const QJsonObject& in)
{
auto meta = std::make_shared<Metadata>();
meta->m_name = Json::ensureString(in, "name", "");
meta->vendor = Json::ensureString(in, "vendor", "");
meta->url = Json::ensureString(in, "url", "");
meta->releaseTime = timeFromS3Time(Json::ensureString(in, "releaseTime", ""));
meta->downloadType = parseDownloadType(Json::ensureString(in, "downloadType", ""));
meta->packageType = Json::ensureString(in, "packageType", "");
meta->runtimeOS = Json::ensureString(in, "runtimeOS", "unknown");
if (in.contains("checksum")) {
auto obj = Json::requireObject(in, "checksum");
meta->checksumHash = Json::ensureString(obj, "hash", "");
meta->checksumType = Json::ensureString(obj, "type", "");
}
if (in.contains("version")) {
auto obj = Json::requireObject(in, "version");
auto name = Json::ensureString(obj, "name", "");
auto major = Json::ensureInteger(obj, "major", 0);
auto minor = Json::ensureInteger(obj, "minor", 0);
auto security = Json::ensureInteger(obj, "security", 0);
auto build = Json::ensureInteger(obj, "build", 0);
meta->version = JavaVersion(major, minor, security, build, name);
}
return meta;
}
bool Metadata::operator<(const Metadata& rhs)
{
auto id = version;
if (id < rhs.version) {
return true;
}
if (id > rhs.version) {
return false;
}
auto date = releaseTime;
if (date < rhs.releaseTime) {
return true;
}
if (date > rhs.releaseTime) {
return false;
}
return StringUtils::naturalCompare(m_name, rhs.m_name, Qt::CaseInsensitive) < 0;
}
bool Metadata::operator==(const Metadata& rhs)
{
return version == rhs.version && m_name == rhs.m_name;
}
bool Metadata::operator>(const Metadata& rhs)
{
return (!operator<(rhs)) && (!operator==(rhs));
}
bool Metadata::operator<(BaseVersion& a)
{
try {
return operator<(dynamic_cast<Metadata&>(a));
} catch (const std::bad_cast& e) {
return BaseVersion::operator<(a);
}
}
bool Metadata::operator>(BaseVersion& a)
{
try {
return operator>(dynamic_cast<Metadata&>(a));
} catch (const std::bad_cast& e) {
return BaseVersion::operator>(a);
}
}
} // namespace Java

View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QDateTime>
#include <QJsonObject>
#include <QString>
#include <memory>
#include "BaseVersion.h"
#include "java/JavaVersion.h"
namespace Java {
enum class DownloadType { Manifest, Archive, Unknown };
class Metadata : public BaseVersion {
public:
virtual QString descriptor() override { return version.toString(); }
virtual QString name() override { return m_name; }
virtual QString typeString() const override { return vendor; }
virtual bool operator<(BaseVersion& a) override;
virtual bool operator>(BaseVersion& a) override;
bool operator<(const Metadata& rhs);
bool operator==(const Metadata& rhs);
bool operator>(const Metadata& rhs);
QString m_name;
QString vendor;
QString url;
QDateTime releaseTime;
QString checksumType;
QString checksumHash;
DownloadType downloadType;
QString packageType;
JavaVersion version;
QString runtimeOS;
};
using MetadataPtr = std::shared_ptr<Metadata>;
DownloadType parseDownloadType(QString javaDownload);
QString downloadTypeToString(DownloadType javaDownload);
MetadataPtr parseJavaMeta(const QJsonObject& libObj);
} // namespace Java

View File

@ -347,6 +347,7 @@ QList<QString> JavaUtils::FindJavaPaths()
} }
candidates.append(getMinecraftJavaBundle()); candidates.append(getMinecraftJavaBundle());
candidates.append(getPrismJavaBundle());
candidates = addJavasFromEnv(candidates); candidates = addJavasFromEnv(candidates);
candidates.removeDuplicates(); candidates.removeDuplicates();
return candidates; return candidates;
@ -391,6 +392,7 @@ QList<QString> JavaUtils::FindJavaPaths()
} }
javas.append(getMinecraftJavaBundle()); javas.append(getMinecraftJavaBundle());
javas.append(getPrismJavaBundle());
javas = addJavasFromEnv(javas); javas = addJavasFromEnv(javas);
javas.removeDuplicates(); javas.removeDuplicates();
return javas; return javas;
@ -454,6 +456,7 @@ QList<QString> JavaUtils::FindJavaPaths()
scanJavaDirs(FS::PathCombine(home, ".gradle/jdks")); scanJavaDirs(FS::PathCombine(home, ".gradle/jdks"));
javas.append(getMinecraftJavaBundle()); javas.append(getMinecraftJavaBundle());
javas.append(getPrismJavaBundle());
javas = addJavasFromEnv(javas); javas = addJavasFromEnv(javas);
javas.removeDuplicates(); javas.removeDuplicates();
return javas; return javas;
@ -467,6 +470,8 @@ QList<QString> JavaUtils::FindJavaPaths()
javas.append(this->GetDefaultJava()->path); javas.append(this->GetDefaultJava()->path);
javas.append(getMinecraftJavaBundle()); javas.append(getMinecraftJavaBundle());
javas.append(getPrismJavaBundle());
javas.removeDuplicates();
return addJavasFromEnv(javas); return addJavasFromEnv(javas);
} }
#endif #endif
@ -478,12 +483,10 @@ QString JavaUtils::getJavaCheckPath()
QStringList getMinecraftJavaBundle() QStringList getMinecraftJavaBundle()
{ {
QString executable = "java";
QStringList processpaths; QStringList processpaths;
#if defined(Q_OS_OSX) #if defined(Q_OS_OSX)
processpaths << FS::PathCombine(QDir::homePath(), FS::PathCombine("Library", "Application Support", "minecraft", "runtime")); processpaths << FS::PathCombine(QDir::homePath(), FS::PathCombine("Library", "Application Support", "minecraft", "runtime"));
#elif defined(Q_OS_WIN32) #elif defined(Q_OS_WIN32)
executable += "w.exe";
auto appDataPath = QProcessEnvironment::systemEnvironment().value("APPDATA", ""); auto appDataPath = QProcessEnvironment::systemEnvironment().value("APPDATA", "");
processpaths << FS::PathCombine(QFileInfo(appDataPath).absoluteFilePath(), ".minecraft", "runtime"); processpaths << FS::PathCombine(QFileInfo(appDataPath).absoluteFilePath(), ".minecraft", "runtime");
@ -508,7 +511,7 @@ QStringList getMinecraftJavaBundle()
auto binFound = false; auto binFound = false;
for (auto& entry : entries) { for (auto& entry : entries) {
if (entry.baseName() == "bin") { if (entry.baseName() == "bin") {
javas.append(FS::PathCombine(entry.canonicalFilePath(), executable)); javas.append(FS::PathCombine(entry.canonicalFilePath(), JavaUtils::javaExecutable));
binFound = true; binFound = true;
break; break;
} }
@ -521,3 +524,33 @@ QStringList getMinecraftJavaBundle()
} }
return javas; return javas;
} }
#if defined(Q_OS_WIN32)
const QString JavaUtils::javaExecutable = "javaw.exe";
#else
const QString JavaUtils::javaExecutable = "java";
#endif
QStringList getPrismJavaBundle()
{
QList<QString> javas;
auto scanDir = [&](QString prefix) {
javas.append(FS::PathCombine(prefix, "jre", "bin", JavaUtils::javaExecutable));
javas.append(FS::PathCombine(prefix, "bin", JavaUtils::javaExecutable));
javas.append(FS::PathCombine(prefix, JavaUtils::javaExecutable));
};
auto scanJavaDir = [&](const QString& dirPath) {
QDir dir(dirPath);
if (!dir.exists())
return;
auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (auto& entry : entries) {
scanDir(entry.canonicalFilePath());
}
};
scanJavaDir(APPLICATION->javaPath());
return javas;
}

View File

@ -15,10 +15,9 @@
#pragma once #pragma once
#include <QProcess>
#include <QStringList> #include <QStringList>
#include "java/JavaInstall.h"
#include "JavaChecker.h"
#include "JavaInstallList.h"
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <windows.h> #include <windows.h>
@ -27,6 +26,7 @@
QString stripVariableEntries(QString name, QString target, QString remove); QString stripVariableEntries(QString name, QString target, QString remove);
QProcessEnvironment CleanEnviroment(); QProcessEnvironment CleanEnviroment();
QStringList getMinecraftJavaBundle(); QStringList getMinecraftJavaBundle();
QStringList getPrismJavaBundle();
class JavaUtils : public QObject { class JavaUtils : public QObject {
Q_OBJECT Q_OBJECT
@ -42,4 +42,5 @@ class JavaUtils : public QObject {
#endif #endif
static QString getJavaCheckPath(); static QString getJavaCheckPath();
static const QString javaExecutable;
}; };

View File

@ -43,12 +43,12 @@ QString JavaVersion::toString() const
return m_string; return m_string;
} }
bool JavaVersion::requiresPermGen() bool JavaVersion::requiresPermGen() const
{ {
return !m_parseable || m_major < 8; return !m_parseable || m_major < 8;
} }
bool JavaVersion::isModular() bool JavaVersion::isModular() const
{ {
return m_parseable && m_major >= 9; return m_parseable && m_major >= 9;
} }
@ -59,12 +59,6 @@ bool JavaVersion::operator<(const JavaVersion& rhs)
auto major = m_major; auto major = m_major;
auto rmajor = rhs.m_major; auto rmajor = rhs.m_major;
// HACK: discourage using java 9
if (major > 8)
major = -major;
if (rmajor > 8)
rmajor = -rmajor;
if (major < rmajor) if (major < rmajor)
return true; return true;
if (major > rmajor) if (major > rmajor)
@ -109,3 +103,24 @@ bool JavaVersion::operator>(const JavaVersion& rhs)
{ {
return (!operator<(rhs)) && (!operator==(rhs)); return (!operator<(rhs)) && (!operator==(rhs));
} }
JavaVersion::JavaVersion(int major, int minor, int security, int build, QString name)
: m_major(major), m_minor(minor), m_security(security), m_name(name), m_parseable(true)
{
QStringList versions;
if (build != 0) {
m_prerelease = QString::number(build);
versions.push_front(m_prerelease);
}
if (m_security != 0)
versions.push_front(QString::number(m_security));
else if (!versions.isEmpty())
versions.push_front("0");
if (m_minor != 0)
versions.push_front(QString::number(m_minor));
else if (!versions.isEmpty())
versions.push_front("0");
versions.push_front(QString::number(m_major));
m_string = versions.join(".");
}

View File

@ -16,6 +16,7 @@ class JavaVersion {
public: public:
JavaVersion() {} JavaVersion() {}
JavaVersion(const QString& rhs); JavaVersion(const QString& rhs);
JavaVersion(int major, int minor, int security, int build = 0, QString name = "");
JavaVersion& operator=(const QString& rhs); JavaVersion& operator=(const QString& rhs);
@ -23,21 +24,24 @@ class JavaVersion {
bool operator==(const JavaVersion& rhs); bool operator==(const JavaVersion& rhs);
bool operator>(const JavaVersion& rhs); bool operator>(const JavaVersion& rhs);
bool requiresPermGen(); bool requiresPermGen() const;
bool isModular(); bool isModular() const;
QString toString() const; QString toString() const;
int major() { return m_major; } int major() const { return m_major; }
int minor() { return m_minor; } int minor() const { return m_minor; }
int security() { return m_security; } int security() const { return m_security; }
QString build() const { return m_prerelease; }
QString name() const { return m_name; }
private: private:
QString m_string; QString m_string;
int m_major = 0; int m_major = 0;
int m_minor = 0; int m_minor = 0;
int m_security = 0; int m_security = 0;
QString m_name = "";
bool m_parseable = false; bool m_parseable = false;
QString m_prerelease; QString m_prerelease;
}; };

View File

@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "java/download/ArchiveDownloadTask.h"
#include <quazip.h>
#include <memory>
#include "MMCZip.h"
#include "Application.h"
#include "Untar.h"
#include "net/ChecksumValidator.h"
#include "net/NetJob.h"
#include "tasks/Task.h"
namespace Java {
ArchiveDownloadTask::ArchiveDownloadTask(QUrl url, QString final_path, QString checksumType, QString checksumHash)
: m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash)
{}
void ArchiveDownloadTask::executeTask()
{
// JRE found ! download the zip
setStatus(tr("Downloading Java"));
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", m_url.fileName());
auto download = makeShared<NetJob>(QString("JRE::DownloadJava"), APPLICATION->network());
auto action = Net::Download::makeCached(m_url, entry);
if (!m_checksum_hash.isEmpty() && !m_checksum_type.isEmpty()) {
auto hashType = QCryptographicHash::Algorithm::Sha1;
if (m_checksum_type == "sha256") {
hashType = QCryptographicHash::Algorithm::Sha256;
}
action->addValidator(new Net::ChecksumValidator(hashType, QByteArray::fromHex(m_checksum_hash.toUtf8())));
}
download->addNetAction(action);
auto fullPath = entry->getFullPath();
connect(download.get(), &Task::failed, this, &ArchiveDownloadTask::emitFailed);
connect(download.get(), &Task::progress, this, &ArchiveDownloadTask::setProgress);
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::succeeded, [this, fullPath] {
// This should do all of the extracting and creating folders
extractJava(fullPath);
});
m_task = download;
m_task->start();
}
void ArchiveDownloadTask::extractJava(QString input)
{
setStatus(tr("Extracting java"));
if (input.endsWith("tar")) {
setStatus(tr("Extracting Java (Progress is not reported for tar archives)"));
QFile in(input);
if (!in.open(QFile::ReadOnly)) {
emitFailed(tr("Unable to open supplied tar file."));
return;
}
if (!Tar::extract(&in, QDir(m_final_path).absolutePath())) {
emitFailed(tr("Unable to extract supplied tar file."));
return;
}
emitSucceeded();
return;
} else if (input.endsWith("tar.gz") || input.endsWith("taz") || input.endsWith("tgz")) {
setStatus(tr("Extracting Java (Progress is not reported for tar archives)"));
if (!GZTar::extract(input, QDir(m_final_path).absolutePath())) {
emitFailed(tr("Unable to extract supplied tar file."));
return;
}
emitSucceeded();
return;
} else if (input.endsWith("zip")) {
auto zip = std::make_shared<QuaZip>(input);
if (!zip->open(QuaZip::mdUnzip)) {
emitFailed(tr("Unable to open supplied zip file."));
return;
}
auto files = zip->getFileNameList();
if (files.isEmpty()) {
emitFailed(tr("No files were found in the supplied zip file,"));
return;
}
m_task = makeShared<MMCZip::ExtractZipTask>(zip, m_final_path, files[0]);
auto progressStep = std::make_shared<TaskStepProgress>();
connect(m_task.get(), &Task::finished, this, [this, progressStep] {
progressStep->state = TaskStepState::Succeeded;
stepProgress(*progressStep);
});
connect(m_task.get(), &Task::succeeded, this, &ArchiveDownloadTask::emitSucceeded);
connect(m_task.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted);
connect(m_task.get(), &Task::failed, this, [this, progressStep](QString reason) {
progressStep->state = TaskStepState::Failed;
stepProgress(*progressStep);
emitFailed(reason);
});
connect(m_task.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress);
connect(m_task.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
progressStep->update(current, total);
stepProgress(*progressStep);
});
connect(m_task.get(), &Task::status, this, [this, progressStep](QString status) {
progressStep->status = status;
stepProgress(*progressStep);
});
m_task->start();
return;
}
emitFailed(tr("Could not determine archive type!"));
}
bool ArchiveDownloadTask::abort()
{
auto aborted = canAbort();
if (m_task)
aborted = m_task->abort();
emitAborted();
return aborted;
};
} // namespace Java

View File

@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QUrl>
#include "tasks/Task.h"
namespace Java {
class ArchiveDownloadTask : public Task {
Q_OBJECT
public:
ArchiveDownloadTask(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = "");
virtual ~ArchiveDownloadTask() = default;
[[nodiscard]] bool canAbort() const override { return true; }
void executeTask() override;
virtual bool abort() override;
private slots:
void extractJava(QString input);
protected:
QUrl m_url;
QString m_final_path;
QString m_checksum_type;
QString m_checksum_hash;
Task::Ptr m_task;
};
} // namespace Java

View File

@ -0,0 +1,138 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "java/download/ManifestDownloadTask.h"
#include "Application.h"
#include "FileSystem.h"
#include "Json.h"
#include "net/ChecksumValidator.h"
#include "net/NetJob.h"
struct File {
QString path;
QString url;
QByteArray hash;
bool isExec;
};
namespace Java {
ManifestDownloadTask::ManifestDownloadTask(QUrl url, QString final_path, QString checksumType, QString checksumHash)
: m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash)
{}
void ManifestDownloadTask::executeTask()
{
setStatus(tr("Downloading Java"));
auto download = makeShared<NetJob>(QString("JRE::DownloadJava"), APPLICATION->network());
auto files = std::make_shared<QByteArray>();
auto action = Net::Download::makeByteArray(m_url, files);
if (!m_checksum_hash.isEmpty() && !m_checksum_type.isEmpty()) {
auto hashType = QCryptographicHash::Algorithm::Sha1;
if (m_checksum_type == "sha256") {
hashType = QCryptographicHash::Algorithm::Sha256;
}
action->addValidator(new Net::ChecksumValidator(hashType, QByteArray::fromHex(m_checksum_hash.toUtf8())));
}
download->addNetAction(action);
connect(download.get(), &Task::failed, this, &ManifestDownloadTask::emitFailed);
connect(download.get(), &Task::progress, this, &ManifestDownloadTask::setProgress);
connect(download.get(), &Task::stepProgress, this, &ManifestDownloadTask::propagateStepProgress);
connect(download.get(), &Task::status, this, &ManifestDownloadTask::setStatus);
connect(download.get(), &Task::details, this, &ManifestDownloadTask::setDetails);
connect(download.get(), &Task::succeeded, [files, this] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response at " << parse_error.offset << ". Reason: " << parse_error.errorString();
qWarning() << *files;
emitFailed(parse_error.errorString());
return;
}
downloadJava(doc);
});
m_task = download;
m_task->start();
}
void ManifestDownloadTask::downloadJava(const QJsonDocument& doc)
{
// valid json doc, begin making jre spot
FS::ensureFolderPathExists(m_final_path);
std::vector<File> toDownload;
auto list = Json::ensureObject(Json::ensureObject(doc.object()), "files");
for (const auto& paths : list.keys()) {
auto file = FS::PathCombine(m_final_path, paths);
const QJsonObject& meta = Json::ensureObject(list, paths);
auto type = Json::ensureString(meta, "type");
if (type == "directory") {
FS::ensureFolderPathExists(file);
} else if (type == "link") {
// this is linux only !
auto path = Json::ensureString(meta, "target");
if (!path.isEmpty()) {
auto target = FS::PathCombine(file, "../" + path);
QFile(target).link(file);
}
} else if (type == "file") {
// TODO download compressed version if it exists ?
auto raw = Json::ensureObject(Json::ensureObject(meta, "downloads"), "raw");
auto isExec = Json::ensureBoolean(meta, "executable", false);
auto url = Json::ensureString(raw, "url");
if (!url.isEmpty() && QUrl(url).isValid()) {
auto f = File{ file, url, QByteArray::fromHex(Json::ensureString(raw, "sha1").toLatin1()), isExec };
toDownload.push_back(f);
}
}
}
auto elementDownload = makeShared<NetJob>("JRE::FileDownload", APPLICATION->network());
for (const auto& file : toDownload) {
auto dl = Net::Download::makeFile(file.url, file.path);
if (!file.hash.isEmpty()) {
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash));
}
if (file.isExec) {
connect(dl.get(), &Net::Download::succeeded,
[file] { QFile(file.path).setPermissions(QFile(file.path).permissions() | QFileDevice::Permissions(0x1111)); });
}
elementDownload->addNetAction(dl);
}
connect(elementDownload.get(), &Task::failed, this, &ManifestDownloadTask::emitFailed);
connect(elementDownload.get(), &Task::progress, this, &ManifestDownloadTask::setProgress);
connect(elementDownload.get(), &Task::stepProgress, this, &ManifestDownloadTask::propagateStepProgress);
connect(elementDownload.get(), &Task::status, this, &ManifestDownloadTask::setStatus);
connect(elementDownload.get(), &Task::details, this, &ManifestDownloadTask::setDetails);
connect(elementDownload.get(), &Task::succeeded, this, &ManifestDownloadTask::emitSucceeded);
m_task = elementDownload;
m_task->start();
}
bool ManifestDownloadTask::abort()
{
auto aborted = canAbort();
if (m_task)
aborted = m_task->abort();
emitAborted();
return aborted;
};
} // namespace Java

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QUrl>
#include "tasks/Task.h"
namespace Java {
class ManifestDownloadTask : public Task {
Q_OBJECT
public:
ManifestDownloadTask(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = "");
virtual ~ManifestDownloadTask() = default;
[[nodiscard]] bool canAbort() const override { return true; }
void executeTask() override;
virtual bool abort() override;
private slots:
void downloadJava(const QJsonDocument& doc);
protected:
QUrl m_url;
QString m_final_path;
QString m_checksum_type;
QString m_checksum_hash;
Task::Ptr m_task;
};
} // namespace Java

View File

@ -37,6 +37,7 @@
#include <FileSystem.h> #include <FileSystem.h>
#include <launch/LaunchTask.h> #include <launch/LaunchTask.h>
#include <sys.h> #include <sys.h>
#include <QCryptographicHash>
#include <QFileInfo> #include <QFileInfo>
#include <QStandardPaths> #include <QStandardPaths>
#include "java/JavaUtils.h" #include "java/JavaUtils.h"
@ -45,20 +46,23 @@ void CheckJava::executeTask()
{ {
auto instance = m_parent->instance(); auto instance = m_parent->instance();
auto settings = instance->settings(); auto settings = instance->settings();
m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString());
QString javaPathSetting = settings->get("JavaPath").toString();
m_javaPath = FS::ResolveExecutable(javaPathSetting);
bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool(); bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool();
auto realJavaPath = QStandardPaths::findExecutable(m_javaPath); auto realJavaPath = QStandardPaths::findExecutable(m_javaPath);
if (realJavaPath.isEmpty()) { if (realJavaPath.isEmpty()) {
if (perInstance) { if (perInstance) {
emit logLine(QString("The java binary \"%1\" couldn't be found. Please fix the java path " emit logLine(QString("The Java binary \"%1\" couldn't be found. Please fix the Java path "
"override in the instance's settings or disable it.") "override in the instance's settings or disable it.")
.arg(m_javaPath), .arg(javaPathSetting),
MessageLevel::Warning); MessageLevel::Warning);
} else { } else {
emit logLine(QString("The java binary \"%1\" couldn't be found. Please set up java in " emit logLine(QString("The Java binary \"%1\" couldn't be found. Please set up Java in "
"the settings.") "the settings.")
.arg(m_javaPath), .arg(javaPathSetting),
MessageLevel::Warning); MessageLevel::Warning);
} }
emitFailed(QString("Java path is not valid.")); emitFailed(QString("Java path is not valid."));
@ -90,11 +94,10 @@ void CheckJava::executeTask()
// if timestamps are not the same, or something is missing, check! // if timestamps are not the same, or something is missing, check!
if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 || if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 ||
storedRealArchitecture.size() == 0 || storedVendor.size() == 0) { storedRealArchitecture.size() == 0 || storedVendor.size() == 0) {
m_JavaChecker.reset(new JavaChecker); m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0, this));
emit logLine(QString("Checking Java version..."), MessageLevel::Launcher); emit logLine(QString("Checking Java version..."), MessageLevel::Launcher);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
m_JavaChecker->m_path = realJavaPath; m_JavaChecker->start();
m_JavaChecker->performCheck();
return; return;
} else { } else {
auto verString = instance->settings()->get("JavaVersion").toString(); auto verString = instance->settings()->get("JavaVersion").toString();
@ -106,10 +109,10 @@ void CheckJava::executeTask()
emitSucceeded(); emitSucceeded();
} }
void CheckJava::checkJavaFinished(JavaCheckResult result) void CheckJava::checkJavaFinished(const JavaChecker::Result& result)
{ {
switch (result.validity) { switch (result.validity) {
case JavaCheckResult::Validity::Errored: { case JavaChecker::Result::Validity::Errored: {
// Error message displayed if java can't start // Error message displayed if java can't start
emit logLine(QString("Could not start java:"), MessageLevel::Error); emit logLine(QString("Could not start java:"), MessageLevel::Error);
emit logLines(result.errorLog.split('\n'), MessageLevel::Error); emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
@ -117,14 +120,14 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
emitFailed(QString("Could not start java!")); emitFailed(QString("Could not start java!"));
return; return;
} }
case JavaCheckResult::Validity::ReturnedInvalidData: { case JavaChecker::Result::Validity::ReturnedInvalidData: {
emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error); emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error);
emit logLines(result.outLog.split('\n'), MessageLevel::Warning); emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher); emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher);
emitSucceeded(); emitSucceeded();
return; return;
} }
case JavaCheckResult::Validity::Valid: { case JavaChecker::Result::Validity::Valid: {
auto instance = m_parent->instance(); auto instance = m_parent->instance();
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor); printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor);
instance->settings()->set("JavaVersion", result.javaVersion.toString()); instance->settings()->set("JavaVersion", result.javaVersion.toString());

View File

@ -28,7 +28,7 @@ class CheckJava : public LaunchStep {
virtual void executeTask(); virtual void executeTask();
virtual bool canAbort() const { return false; } virtual bool canAbort() const { return false; }
private slots: private slots:
void checkJavaFinished(JavaCheckResult result); void checkJavaFinished(const JavaChecker::Result& result);
private: private:
void printJavaInfo(const QString& version, const QString& architecture, const QString& realArchitecture, const QString& vendor); void printJavaInfo(const QString& version, const QString& architecture, const QString& realArchitecture, const QString& vendor);
@ -37,5 +37,5 @@ class CheckJava : public LaunchStep {
private: private:
QString m_javaPath; QString m_javaPath;
QString m_javaSignature; QString m_javaSignature;
JavaCheckerPtr m_JavaChecker; JavaChecker::Ptr m_JavaChecker;
}; };

View File

@ -101,6 +101,13 @@ QVariant VersionList::data(const QModelIndex& index, int role) const
return QVariant::fromValue(version); return QVariant::fromValue(version);
case RecommendedRole: case RecommendedRole:
return version->isRecommended() || m_externalRecommendsVersions.contains(version->version()); return version->isRecommended() || m_externalRecommendsVersions.contains(version->version());
case JavaMajorRole: {
auto major = version->version();
if (major.startsWith("java")) {
major = "Java " + major.mid(4);
}
return major;
}
// FIXME: this should be determined in whatever view/proxy is used... // FIXME: this should be determined in whatever view/proxy is used...
// case LatestRole: return version == getLatestStable(); // case LatestRole: return version == getLatestStable();
default: default:
@ -110,10 +117,14 @@ QVariant VersionList::data(const QModelIndex& index, int role) const
BaseVersionList::RoleList VersionList::providesRoles() const BaseVersionList::RoleList VersionList::providesRoles() const
{ {
return { VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole, TypeRole, UidRole, return m_provided_roles;
TimeRole, RequiresRole, SortRole, RecommendedRole, LatestRole, VersionPtrRole };
} }
void VersionList::setProvidedRoles(RoleList roles)
{
m_provided_roles = roles;
};
QHash<int, QByteArray> VersionList::roleNames() const QHash<int, QByteArray> VersionList::roleNames() const
{ {
QHash<int, QByteArray> roles = BaseVersionList::roleNames(); QHash<int, QByteArray> roles = BaseVersionList::roleNames();

View File

@ -50,6 +50,8 @@ class VersionList : public BaseVersionList, public BaseEntity {
RoleList providesRoles() const override; RoleList providesRoles() const override;
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
void setProvidedRoles(RoleList roles);
QString localFilename() const override; QString localFilename() const override;
QString uid() const { return m_uid; } QString uid() const { return m_uid; }
@ -88,6 +90,9 @@ class VersionList : public BaseVersionList, public BaseEntity {
Version::Ptr m_recommended; Version::Ptr m_recommended;
RoleList m_provided_roles = { VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole, TypeRole, UidRole,
TimeRole, RequiresRole, SortRole, RecommendedRole, LatestRole, VersionPtrRole };
void setupAddedVersion(int row, const Version::Ptr& version); void setupAddedVersion(int row, const Version::Ptr& version);
}; };
} // namespace Meta } // namespace Meta

View File

@ -164,6 +164,11 @@ void LaunchProfile::applyCompatibleJavaMajors(QList<int>& javaMajor)
{ {
m_compatibleJavaMajors.append(javaMajor); m_compatibleJavaMajors.append(javaMajor);
} }
void LaunchProfile::applyCompatibleJavaName(QString javaName)
{
if (!javaName.isEmpty())
m_compatibleJavaName = javaName;
}
void LaunchProfile::applyLibrary(LibraryPtr library, const RuntimeContext& runtimeContext) void LaunchProfile::applyLibrary(LibraryPtr library, const RuntimeContext& runtimeContext)
{ {
@ -334,6 +339,11 @@ const QList<int>& LaunchProfile::getCompatibleJavaMajors() const
return m_compatibleJavaMajors; return m_compatibleJavaMajors;
} }
const QString LaunchProfile::getCompatibleJavaName() const
{
return m_compatibleJavaName;
}
void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext, void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext,
QStringList& jars, QStringList& jars,
QStringList& nativeJars, QStringList& nativeJars,

View File

@ -59,6 +59,7 @@ class LaunchProfile : public ProblemProvider {
void applyMavenFile(LibraryPtr library, const RuntimeContext& runtimeContext); void applyMavenFile(LibraryPtr library, const RuntimeContext& runtimeContext);
void applyAgent(AgentPtr agent, const RuntimeContext& runtimeContext); void applyAgent(AgentPtr agent, const RuntimeContext& runtimeContext);
void applyCompatibleJavaMajors(QList<int>& javaMajor); void applyCompatibleJavaMajors(QList<int>& javaMajor);
void applyCompatibleJavaName(QString javaName);
void applyMainJar(LibraryPtr jar); void applyMainJar(LibraryPtr jar);
void applyProblemSeverity(ProblemSeverity severity); void applyProblemSeverity(ProblemSeverity severity);
/// clear the profile /// clear the profile
@ -80,6 +81,7 @@ class LaunchProfile : public ProblemProvider {
const QList<LibraryPtr>& getMavenFiles() const; const QList<LibraryPtr>& getMavenFiles() const;
const QList<AgentPtr>& getAgents() const; const QList<AgentPtr>& getAgents() const;
const QList<int>& getCompatibleJavaMajors() const; const QList<int>& getCompatibleJavaMajors() const;
const QString getCompatibleJavaName() const;
const LibraryPtr getMainJar() const; const LibraryPtr getMainJar() const;
void getLibraryFiles(const RuntimeContext& runtimeContext, void getLibraryFiles(const RuntimeContext& runtimeContext,
QStringList& jars, QStringList& jars,
@ -150,5 +152,7 @@ class LaunchProfile : public ProblemProvider {
/// compatible java major versions /// compatible java major versions
QList<int> m_compatibleJavaMajors; QList<int> m_compatibleJavaMajors;
QString m_compatibleJavaName;
ProblemSeverity m_problemSeverity = ProblemSeverity::None; ProblemSeverity m_problemSeverity = ProblemSeverity::None;
}; };

View File

@ -38,6 +38,8 @@
#include "MinecraftInstance.h" #include "MinecraftInstance.h"
#include "Application.h" #include "Application.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "QObjectPtr.h"
#include "minecraft/launch/AutoInstallJava.h"
#include "minecraft/launch/CreateGameFolders.h" #include "minecraft/launch/CreateGameFolders.h"
#include "minecraft/launch/ExtractNatives.h" #include "minecraft/launch/ExtractNatives.h"
#include "minecraft/launch/PrintInstanceInfo.h" #include "minecraft/launch/PrintInstanceInfo.h"
@ -134,25 +136,21 @@ void MinecraftInstance::loadSpecificSettings()
return; return;
// Java Settings // Java Settings
auto javaOverride = m_settings->registerSetting("OverrideJava", false);
auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false); auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false);
auto argsOverride = m_settings->registerSetting("OverrideJavaArgs", false); auto argsOverride = m_settings->registerSetting("OverrideJavaArgs", false);
m_settings->registerSetting("AutomaticJava", false);
// combinations
auto javaOrLocation = std::make_shared<OrSetting>("JavaOrLocationOverride", javaOverride, locationOverride);
auto javaOrArgs = std::make_shared<OrSetting>("JavaOrArgsOverride", javaOverride, argsOverride);
if (auto global_settings = globalSettings()) { if (auto global_settings = globalSettings()) {
m_settings->registerOverride(global_settings->getSetting("JavaPath"), javaOrLocation); m_settings->registerOverride(global_settings->getSetting("JavaPath"), locationOverride);
m_settings->registerOverride(global_settings->getSetting("JvmArgs"), javaOrArgs); m_settings->registerOverride(global_settings->getSetting("JvmArgs"), argsOverride);
m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), javaOrLocation); m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), locationOverride);
// special! // special!
m_settings->registerPassthrough(global_settings->getSetting("JavaSignature"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaSignature"), locationOverride);
m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), locationOverride);
m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), locationOverride);
m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), locationOverride);
m_settings->registerPassthrough(global_settings->getSetting("JavaVendor"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaVendor"), locationOverride);
// Window Size // Window Size
auto windowSetting = m_settings->registerSetting("OverrideWindow", false); auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
@ -1060,11 +1058,6 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(makeShared<TextPrint>(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::Launcher)); process->appendStep(makeShared<TextPrint>(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::Launcher));
} }
// check java
{
process->appendStep(makeShared<CheckJava>(pptr));
}
// create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732) // create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732)
{ {
process->appendStep(makeShared<CreateGameFolders>(pptr)); process->appendStep(makeShared<CreateGameFolders>(pptr));
@ -1107,6 +1100,12 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(makeShared<Update>(pptr, Net::Mode::Offline)); process->appendStep(makeShared<Update>(pptr, Net::Mode::Offline));
} }
// check java
{
process->appendStep(makeShared<AutoInstallJava>(pptr));
process->appendStep(makeShared<CheckJava>(pptr));
}
// if there are any jar mods // if there are any jar mods
{ {
process->appendStep(makeShared<ModMinecraftJar>(pptr)); process->appendStep(makeShared<ModMinecraftJar>(pptr));

View File

@ -185,6 +185,9 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject& in, VersionFi
out->compatibleJavaMajors.append(requireInteger(compatible)); out->compatibleJavaMajors.append(requireInteger(compatible));
} }
} }
if (in.contains("compatibleJavaName")) {
out->compatibleJavaName = requireString(in.value("compatibleJavaName"));
}
if (in.contains("downloads")) { if (in.contains("downloads")) {
auto downloadsObj = requireObject(in, "downloads"); auto downloadsObj = requireObject(in, "downloads");
@ -259,6 +262,9 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
} }
out.insert("compatibleJavaMajors", compatibleJavaMajorsOut); out.insert("compatibleJavaMajors", compatibleJavaMajorsOut);
} }
if (!in->compatibleJavaName.isEmpty()) {
writeString(out, "compatibleJavaName", in->compatibleJavaName);
}
} }
QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr& patch) QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr& patch)

View File

@ -36,6 +36,8 @@
#include "OneSixVersionFormat.h" #include "OneSixVersionFormat.h"
#include <Json.h> #include <Json.h>
#include <minecraft/MojangVersionFormat.h> #include <minecraft/MojangVersionFormat.h>
#include <QList>
#include "java/JavaMetadata.h"
#include "minecraft/Agent.h" #include "minecraft/Agent.h"
#include "minecraft/ParseUtils.h" #include "minecraft/ParseUtils.h"
@ -255,6 +257,13 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc
out->m_volatile = requireBoolean(root, "volatile"); out->m_volatile = requireBoolean(root, "volatile");
} }
if (root.contains("runtimes")) {
out->runtimes = {};
for (auto runtime : ensureArray(root, "runtimes")) {
out->runtimes.append(Java::parseJavaMeta(ensureObject(runtime)));
}
}
/* removed features that shouldn't be used */ /* removed features that shouldn't be used */
if (root.contains("tweakers")) { if (root.contains("tweakers")) {
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'")); out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'"));

View File

@ -73,6 +73,7 @@ void VersionFile::applyTo(LaunchProfile* profile, const RuntimeContext& runtimeC
profile->applyMods(mods); profile->applyMods(mods);
profile->applyTraits(traits); profile->applyTraits(traits);
profile->applyCompatibleJavaMajors(compatibleJavaMajors); profile->applyCompatibleJavaMajors(compatibleJavaMajors);
profile->applyCompatibleJavaName(compatibleJavaName);
for (auto library : libraries) { for (auto library : libraries) {
profile->applyLibrary(library, runtimeContext); profile->applyLibrary(library, runtimeContext);

View File

@ -36,6 +36,8 @@
#pragma once #pragma once
#include <QDateTime> #include <QDateTime>
#include <QHash>
#include <QList>
#include <QSet> #include <QSet>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
@ -45,6 +47,7 @@
#include "Agent.h" #include "Agent.h"
#include "Library.h" #include "Library.h"
#include "ProblemProvider.h" #include "ProblemProvider.h"
#include "java/JavaMetadata.h"
#include "minecraft/Rule.h" #include "minecraft/Rule.h"
class PackProfile; class PackProfile;
@ -98,6 +101,9 @@ class VersionFile : public ProblemContainer {
/// Mojang: list of compatible java majors /// Mojang: list of compatible java majors
QList<int> compatibleJavaMajors; QList<int> compatibleJavaMajors;
/// Mojang: the name of recommended java version
QString compatibleJavaName;
/// Mojang: type of the Minecraft version /// Mojang: type of the Minecraft version
QString type; QString type;
@ -149,6 +155,8 @@ class VersionFile : public ProblemContainer {
/// is volatile -- may be removed as soon as it is no longer needed by something else /// is volatile -- may be removed as soon as it is no longer needed by something else
bool m_volatile = false; bool m_volatile = false;
QList<Java::MetadataPtr> runtimes;
public: public:
// Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more. // Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more.
QMap<QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads; QMap<QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads;

View File

@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* 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 "AutoInstallJava.h"
#include <QDir>
#include <QFileInfo>
#include <memory>
#include "Application.h"
#include "FileSystem.h"
#include "MessageLevel.h"
#include "SysInfo.h"
#include "java/JavaInstall.h"
#include "java/JavaInstallList.h"
#include "java/JavaUtils.h"
#include "java/JavaVersion.h"
#include "java/download/ArchiveDownloadTask.h"
#include "java/download/ManifestDownloadTask.h"
#include "meta/Index.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "net/Mode.h"
AutoInstallJava::AutoInstallJava(LaunchTask* parent)
: LaunchStep(parent)
, m_instance(std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()))
, m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {};
void AutoInstallJava::executeTask()
{
auto settings = m_instance->settings();
if (!APPLICATION->settings()->get("AutomaticJavaSwitch").toBool() ||
(settings->get("OverrideJavaLocation").toBool() && QFileInfo::exists(settings->get("JavaPath").toString()))) {
emitSucceeded();
return;
}
auto packProfile = m_instance->getPackProfile();
if (!APPLICATION->settings()->get("AutomaticJavaDownload").toBool()) {
auto javas = APPLICATION->javalist();
m_current_task = javas->getLoadTask();
connect(m_current_task.get(), &Task::finished, this, [this, javas, packProfile] {
for (auto i = 0; i < javas->count(); i++) {
auto java = std::dynamic_pointer_cast<JavaInstall>(javas->at(i));
if (java && packProfile->getProfile()->getCompatibleJavaMajors().contains(java->id.major())) {
if (!java->is_64bit) {
emit logLine(tr("The automatic Java mechanism detected a 32-bit installation of Java."), MessageLevel::Info);
}
setJavaPath(java->path);
return;
}
}
emit logLine(tr("No compatible Java version was found. Using the default one."), MessageLevel::Warning);
emitSucceeded();
});
connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress);
connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress);
connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus);
connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails);
emit progressReportingRequest();
return;
}
if (m_supported_arch.isEmpty()) {
emit logLine(tr("Your system (%1-%2) is not compatible with automatic Java installation. Using the default Java path.")
.arg(SysInfo::currentSystem(), SysInfo::useQTForArch()),
MessageLevel::Warning);
emitSucceeded();
return;
}
auto wantedJavaName = packProfile->getProfile()->getCompatibleJavaName();
if (wantedJavaName.isEmpty()) {
emit logLine(tr("Your meta information is out of date or doesn't have the information necessary to determine what installation of "
"Java should be used. "
"Using the default Java path."),
MessageLevel::Warning);
emitSucceeded();
return;
}
QDir javaDir(APPLICATION->javaPath());
auto wantedJavaPath = javaDir.absoluteFilePath(wantedJavaName);
if (QFileInfo::exists(wantedJavaPath)) {
setJavaPathFromPartial();
return;
}
auto versionList = APPLICATION->metadataIndex()->get("net.minecraft.java");
m_current_task = versionList->getLoadTask();
connect(m_current_task.get(), &Task::succeeded, this, &AutoInstallJava::tryNextMajorJava);
connect(m_current_task.get(), &Task::failed, this, &AutoInstallJava::emitFailed);
connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress);
connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress);
connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus);
connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails);
if (!m_current_task->isRunning()) {
m_current_task->start();
}
emit progressReportingRequest();
}
void AutoInstallJava::setJavaPath(QString path)
{
auto settings = m_instance->settings();
settings->set("OverrideJavaLocation", true);
settings->set("JavaPath", path);
settings->set("AutomaticJava", true);
emit logLine(tr("Compatible Java found at: %1.").arg(path), MessageLevel::Info);
emitSucceeded();
}
void AutoInstallJava::setJavaPathFromPartial()
{
auto packProfile = m_instance->getPackProfile();
auto javaName = packProfile->getProfile()->getCompatibleJavaName();
QDir javaDir(APPLICATION->javaPath());
// just checking if the executable is there should suffice
// but if needed this can be achieved through refreshing the javalist
// and retrieving the path that contains the java name
auto relativeBinary = FS::PathCombine(javaName, "bin", JavaUtils::javaExecutable);
auto finalPath = javaDir.absoluteFilePath(relativeBinary);
if (QFileInfo::exists(finalPath)) {
setJavaPath(finalPath);
} else {
emit logLine(tr("No compatible Java version was found (the binary file does not exist). Using the default one."),
MessageLevel::Warning);
emitSucceeded();
}
return;
}
void AutoInstallJava::downloadJava(Meta::Version::Ptr version, QString javaName)
{
auto runtimes = version->data()->runtimes;
for (auto java : runtimes) {
if (java->runtimeOS == m_supported_arch && java->name() == javaName) {
QDir javaDir(APPLICATION->javaPath());
auto final_path = javaDir.absoluteFilePath(java->m_name);
switch (java->downloadType) {
case Java::DownloadType::Manifest:
m_current_task = makeShared<Java::ManifestDownloadTask>(java->url, final_path, java->checksumType, java->checksumHash);
break;
case Java::DownloadType::Archive:
m_current_task = makeShared<Java::ArchiveDownloadTask>(java->url, final_path, java->checksumType, java->checksumHash);
break;
case Java::DownloadType::Unknown:
emitFailed(tr("Could not determine Java download type!"));
return;
}
auto deletePath = [final_path] { FS::deletePath(final_path); };
connect(m_current_task.get(), &Task::failed, this, [this, deletePath](QString reason) {
deletePath();
emitFailed(reason);
});
connect(this, &Task::aborted, this, [this, deletePath] {
m_current_task->abort();
deletePath();
});
connect(m_current_task.get(), &Task::succeeded, this, &AutoInstallJava::setJavaPathFromPartial);
connect(m_current_task.get(), &Task::failed, this, &AutoInstallJava::tryNextMajorJava);
connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress);
connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress);
connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus);
connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails);
m_current_task->start();
return;
}
}
tryNextMajorJava();
}
void AutoInstallJava::tryNextMajorJava()
{
if (!isRunning())
return;
auto versionList = APPLICATION->metadataIndex()->get("net.minecraft.java");
auto packProfile = m_instance->getPackProfile();
auto wantedJavaName = packProfile->getProfile()->getCompatibleJavaName();
auto majorJavaVersions = packProfile->getProfile()->getCompatibleJavaMajors();
if (m_majorJavaVersionIndex >= majorJavaVersions.length()) {
emit logLine(
tr("No versions of Java were found for your operating system: %1-%2").arg(SysInfo::currentSystem(), SysInfo::useQTForArch()),
MessageLevel::Warning);
emit logLine(tr("No compatible version of Java was found. Using the default one."), MessageLevel::Warning);
emitSucceeded();
return;
}
auto majorJavaVersion = majorJavaVersions[m_majorJavaVersionIndex];
m_majorJavaVersionIndex++;
auto javaMajor = versionList->getVersion(QString("java%1").arg(majorJavaVersion));
if (javaMajor->isLoaded()) {
downloadJava(javaMajor, wantedJavaName);
} else {
m_current_task = APPLICATION->metadataIndex()->loadVersion("net.minecraft.java", javaMajor->version(), Net::Mode::Online);
connect(m_current_task.get(), &Task::succeeded, this,
[this, javaMajor, wantedJavaName] { downloadJava(javaMajor, wantedJavaName); });
connect(m_current_task.get(), &Task::failed, this, &AutoInstallJava::tryNextMajorJava);
connect(m_current_task.get(), &Task::progress, this, &AutoInstallJava::setProgress);
connect(m_current_task.get(), &Task::stepProgress, this, &AutoInstallJava::propagateStepProgress);
connect(m_current_task.get(), &Task::status, this, &AutoInstallJava::setStatus);
connect(m_current_task.get(), &Task::details, this, &AutoInstallJava::setDetails);
if (!m_current_task->isRunning()) {
m_current_task->start();
}
}
}
bool AutoInstallJava::abort()
{
if (m_current_task && m_current_task->canAbort())
return m_current_task->abort();
return true;
}

View File

@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* 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 <launch/LaunchStep.h>
#include <launch/LaunchTask.h>
#include "java/JavaMetadata.h"
#include "meta/Version.h"
#include "minecraft/MinecraftInstance.h"
#include "tasks/Task.h"
class AutoInstallJava : public LaunchStep {
Q_OBJECT
public:
explicit AutoInstallJava(LaunchTask* parent);
~AutoInstallJava() override = default;
void executeTask() override;
bool canAbort() const override { return m_current_task ? m_current_task->canAbort() : false; }
bool abort() override;
protected:
void setJavaPath(QString path);
void setJavaPathFromPartial();
void downloadJava(Meta::Version::Ptr version, QString javaName);
void tryNextMajorJava();
private:
MinecraftInstancePtr m_instance;
Task::Ptr m_current_task;
qsizetype m_majorJavaVersionIndex = 0;
const QString m_supported_arch;
};

View File

@ -34,7 +34,12 @@
*/ */
#include "VerifyJavaInstall.h" #include "VerifyJavaInstall.h"
#include <memory>
#include "Application.h"
#include "MessageLevel.h"
#include "java/JavaInstall.h"
#include "java/JavaInstallList.h"
#include "java/JavaVersion.h" #include "java/JavaVersion.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
@ -46,6 +51,15 @@ void VerifyJavaInstall::executeTask()
auto settings = instance->settings(); auto settings = instance->settings();
auto storedVersion = settings->get("JavaVersion").toString(); auto storedVersion = settings->get("JavaVersion").toString();
auto ignoreCompatibility = settings->get("IgnoreJavaCompatibility").toBool(); auto ignoreCompatibility = settings->get("IgnoreJavaCompatibility").toBool();
auto javaArchitecture = settings->get("JavaArchitecture").toString();
auto maxMemAlloc = settings->get("MaxMemAlloc").toInt();
if (javaArchitecture == "32" && maxMemAlloc > 2048) {
emit logLine(tr("Max memory allocation exceeds the supported value.\n"
"The selected installation of Java is 32-bit and doesn't support more than 2048MiB of RAM.\n"
"The instance may not start due to this."),
MessageLevel::Error);
}
auto compatibleMajors = packProfile->getProfile()->getCompatibleJavaMajors(); auto compatibleMajors = packProfile->getProfile()->getCompatibleJavaMajors();

View File

@ -353,5 +353,11 @@
<file>scalable/instances/neoforged.svg</file> <file>scalable/instances/neoforged.svg</file>
<file>128x128/instances/forge.png</file> <!-- LGPL3 Forge Development LLC --> <file>128x128/instances/forge.png</file> <!-- LGPL3 Forge Development LLC -->
<file>128x128/instances/liteloader.png</file> <!-- CC-BY-SA 4.0 LiteLoader --> <file>128x128/instances/liteloader.png</file> <!-- CC-BY-SA 4.0 LiteLoader -->
<!-- java providers -->
<file>scalable/adoptium.svg</file> <!-- The Adoptium Logo is a registered trademark of the Eclipse Foundation. -->
<file>scalable/azul.svg</file> <!-- Azul, Zulu, Azul Systems, the Azul Systems logo, Azul Zulu are either registered trademarks or trademarks of Azul Systems, registered in the U.S. and elsewhere. -->
<file>scalable/mojang.svg</file> <!-- The Mojang Logo is a registered trademark of Mojang AB. -->
</qresource> </qresource>
</RCC> </RCC>

View File

@ -0,0 +1,180 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
sodipodi:docname="Eclipse_Adoptium_Logo_A_only_no_outline.svg"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
id="svg8"
version="1.1"
viewBox="0 0 800 800"
height="800"
width="800"
inkscape:export-filename="Eclipse_Adoptium_Logo.png"
inkscape:export-xdpi="61.439999"
inkscape:export-ydpi="61.439999"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath242">
<path
d="m 987.855,678.469 -0.019,-0.008 -107.453,233.098 -81.121,-175.989 235.718,-106.09 0.01,0.02 c -21.01,10.078 -37.838,27.52 -47.135,48.969 z"
id="path240" />
</clipPath>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-481.882,253.823,253.823,481.882,1152.35,658.95)"
spreadMethod="pad"
id="linearGradient248">
<stop
style="stop-opacity:1;stop-color:#421644"
offset="0"
id="stop244" />
<stop
style="stop-opacity:1;stop-color:#151530"
offset="1"
id="stop246" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath258">
<path
d="m 1164.62,758.102 0.08,0.039 -194.618,422.029 c -0.547,1.32 -1.184,2.59 -1.789,3.88 l -1.453,3.16 -0.059,-0.03 c -0.324,0.65 -0.703,1.27 -1.054,1.9 7.382,-13.68 11.589,-29.34 11.589,-45.98 0,-14.34 -3.191,-27.91 -8.777,-40.15 l 0.059,-0.03 -88.215,-191.361 107.453,-233.098 0.019,0.008 c 14.915,-34.387 49.125,-58.457 89.005,-58.457 53.57,0 96.99,43.429 96.99,97 0,14.707 -3.37,28.597 -9.23,41.09 z"
id="path256" />
</clipPath>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(265.274,-595.434,-595.434,-265.274,846.292,1230.46)"
spreadMethod="pad"
id="linearGradient264">
<stop
style="stop-opacity:1;stop-color:#a21058"
offset="0"
id="stop260" />
<stop
style="stop-opacity:1;stop-color:#421644"
offset="1"
id="stop262" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath274">
<path
d="m 880.316,1240.11 c -37.687,0 -70.273,-21.54 -86.328,-52.93 l -0.058,0.03 -1.457,-3.16 c -0.606,-1.29 -1.239,-2.56 -1.785,-3.88 l -194.622,-422.029 0.079,-0.039 c -5.86,-12.493 -9.227,-26.383 -9.227,-41.09 0,-53.571 43.418,-97 96.992,-97 39.871,0 74.09,24.07 89.004,58.457 l 0.02,-0.008 195.664,424.459 -0.059,0.03 c 5.586,12.24 8.777,25.81 8.777,40.15 0,53.58 -43.425,97.01 -97,97.01 z"
id="path272" />
</clipPath>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(259.548,595.434,595.434,-259.548,649.304,625.36)"
spreadMethod="pad"
id="linearGradient280">
<stop
style="stop-opacity:1;stop-color:#ee1a6d"
offset="0"
id="stop276" />
<stop
style="stop-opacity:1;stop-color:#a21058"
offset="1"
id="stop278" />
</linearGradient>
</defs>
<sodipodi:namedview
height="100mm"
inkscape:window-maximized="1"
inkscape:window-y="-8"
inkscape:window-x="1912"
inkscape:window-height="1057"
inkscape:window-width="1920"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="g216"
inkscape:document-units="px"
inkscape:cy="396.88538"
inkscape:cx="322.07976"
inkscape:zoom="0.72187089"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#494949"
id="base"
inkscape:showpageshadow="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<g
inkscape:export-ydpi="96"
inkscape:export-xdpi="96"
transform="matrix(0.0826697,0,0,-0.0826697,-36.864228,126.62103)"
id="g216">
<g
id="g236"
transform="matrix(13.679999,0,0,13.679976,-6760.5652,-16025.746)">
<g
clip-path="url(#clipPath242)"
id="g238">
<path
id="path250"
style="fill:url(#linearGradient248);fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 987.855,678.469 -0.019,-0.008 -107.453,233.098 -81.121,-175.989 235.718,-106.09 0.01,0.02 c -21.01,10.078 -37.838,27.52 -47.135,48.969" />
</g>
</g>
<g
id="g252"
transform="matrix(13.679999,0,0,13.679976,-6760.5652,-16025.746)">
<g
clip-path="url(#clipPath258)"
id="g254">
<path
id="path266"
style="fill:url(#linearGradient264);fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 1164.62,758.102 0.08,0.039 -194.618,422.029 c -0.547,1.32 -1.184,2.59 -1.789,3.88 l -1.453,3.16 -0.059,-0.03 c -0.324,0.65 -0.703,1.27 -1.054,1.9 7.382,-13.68 11.589,-29.34 11.589,-45.98 0,-14.34 -3.191,-27.91 -8.777,-40.15 l 0.059,-0.03 -88.215,-191.361 107.453,-233.098 0.019,0.008 c 14.915,-34.387 49.125,-58.457 89.005,-58.457 53.57,0 96.99,43.429 96.99,97 0,14.707 -3.37,28.597 -9.23,41.09" />
</g>
</g>
<g
id="g268"
transform="matrix(1.2000009,0,0,1.1999989,-853.84911,665.71675)">
<g
clip-path="url(#clipPath274)"
id="g270"
transform="matrix(11.39999,0,0,11.39999,-4922.2595,-13909.564)">
<path
id="path282"
style="fill:url(#linearGradient280);fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 880.316,1240.11 c -37.687,0 -70.273,-21.54 -86.328,-52.93 l -0.058,0.03 -1.457,-3.16 c -0.606,-1.29 -1.239,-2.56 -1.785,-3.88 l -194.622,-422.029 0.079,-0.039 c -5.86,-12.493 -9.227,-26.383 -9.227,-41.09 0,-53.571 43.418,-97 96.992,-97 39.871,0 74.09,24.07 89.004,58.457 l 0.02,-0.008 195.664,424.459 -0.059,0.03 c 5.586,12.24 8.777,25.81 8.777,40.15 0,53.58 -43.425,97.01 -97,97.01" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" fill="none" style="scroll-behavior: auto !important;">
<path d="M26.792 4.49429L7.62244 44.4309C7.20797 45.2944 8.12743 46.1903 8.97988 45.7536L28.26 35.8772C28.6557 35.6745 29.1376 35.7574 29.4429 36.0805L53.2128 61.2399C53.994 62.0668 55.3295 61.1553 54.844 60.1264L28.5979 4.50031C28.2386 3.73874 27.1564 3.73514 26.792 4.49429Z" fill="#00374A"/>
<path d="M26.792 4.49429L7.62244 44.4309C7.20797 45.2944 8.12743 46.1903 8.97988 45.7536L28.26 35.8772C28.6557 35.6745 29.1376 35.7574 29.4429 36.0805L53.2128 61.2399C53.994 62.0668 55.3295 61.1553 54.844 60.1264L28.5979 4.50031C28.2386 3.73874 27.1564 3.73514 26.792 4.49429Z" fill="url(#paint0_linear_3578_2134)" fill-opacity="0.7" style="mix-blend-mode:overlay"/>
<path d="M34.0172 15.5765L60.9237 19.1042C61.9046 19.2329 62.1231 20.5555 61.2357 20.9928L33.4872 34.6659L9.3254 45.9918C8.34481 46.4514 7.43514 45.2419 8.14881 44.4273L33.135 15.909C33.3551 15.6578 33.686 15.5331 34.0172 15.5765Z" fill="#006588"/>
<path d="M34.0172 15.5765L60.9237 19.1042C61.9046 19.2329 62.1231 20.5555 61.2357 20.9928L33.4872 34.6659L9.3254 45.9918C8.34481 46.4514 7.43514 45.2419 8.14881 44.4273L33.135 15.909C33.3551 15.6578 33.686 15.5331 34.0172 15.5765Z" fill="url(#paint1_linear_3578_2134)" fill-opacity="0.7" style="mix-blend-mode:overlay"/>
<defs>
<linearGradient id="paint0_linear_3578_2134" x1="31.2618" y1="2.59998" x2="31.2618" y2="65.8" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<linearGradient id="paint1_linear_3578_2134" x1="46.2268" y1="4.17812" x2="31.5574" y2="56.1518" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="800"
height="800"
viewBox="0 0 800 800"
xml:space="preserve"
style="scroll-behavior: auto !important;"
id="svg4"
sodipodi:docname="Mojang_Studios_Logo_(2020,_icon).svg"
inkscape:export-filename="Mojang_Studios_Logo_(2020,_icon).png"
inkscape:export-xdpi="61.439999"
inkscape:export-ydpi="61.439999"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview4"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:zoom="1.46625"
inkscape:cx="374.08355"
inkscape:cy="369.30946"
inkscape:window-width="2560"
inkscape:window-height="1369"
inkscape:window-x="6392"
inkscape:window-y="802"
inkscape:window-maximized="1"
inkscape:current-layer="g3" />&#10;<desc
id="desc1">Created with Fabric.js 3.6.3</desc>&#10;<defs
id="defs1">&#10;</defs>&#10;<g
transform="matrix(2 0 0 2 399.75 399.75)"
id="g4">&#10;<g
style=""
id="g3">&#10; <g
transform="matrix(3.37 0 0 3.37 0 0)"
id="g1">&#10;<polygon
style="display:inline;opacity:1;fill:#f0313b;fill-rule:nonzero;stroke:#f0313b;stroke-width:8;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0"
vector-effect="non-scaling-stroke"
points="50,50 50,-50 -50,-50 -50,50 "
id="polygon1" />&#10;</g>&#10; <g
transform="matrix(3.78 0 0 3.78 0.01 0)"
id="g2">&#10;<path
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;"
vector-effect="non-scaling-stroke"
transform=" translate(-44.68, -41.16)"
d="M 13.51988 41.11152 q 0 -12.31086 0.0047 -24.62173 c 0.00469 -2.87722 0.16418 -3.03875 3.01027 -3.04146 q 21.527 -0.02051 43.054 -0.01353 c 2.06114 0.0004 2.36366 0.32134 2.39718 2.42144 c 0.0238 1.49194 -0.01523 2.98567 0.03993 4.47616 a 6.77817 6.77817 0 0 0 6.69647 6.91868 c 1.65784 0.11443 3.328 0.04044 4.99213 0.07612 c 1.797 0.03852 2.04939 0.30467 2.12207 2.13943 c 0.01137 0.28664 0.01239 0.57383 0.01247 0.86076 q 0.00432 17.64834 0.00423 35.29668 c -0.00158 3.2118 -0.0499 3.24957 -3.28974 3.26017 q -3.78871 0.01236 -7.57745 -0.01166 c -2.96088 -0.02069 -3.03087 -0.09046 -3.032 -2.98084 q -0.00659 -16.87367 -0.00343 -33.74735 c 0 -0.40175 0.01018 -0.80387 -0.00321 -1.20519 a 3.329 3.329 0 0 0 -3.548 -3.56756 c -1.08962 -0.032 -2.18238 -0.03167 -3.27158 0.01034 a 3.34773 3.34773 0 0 0 -3.482 3.47659 c -0.03516 0.62965 -0.02128 1.26231 -0.02172 1.8936 q -0.00641 9.21165 -0.01056 18.42326 c -0.00046 0.68869 0.01794 1.37909 -0.02307 2.06567 c -0.075 1.25525 -0.44257 1.71282 -1.65149 1.72825 q -5.25132 0.06705 -10.50391 -0.00754 c -1.2165 -0.01879 -1.58169 -0.48679 -1.66785 -1.72683 c -0.03575 -0.51435 -0.01843 -1.03265 -0.019 -1.54914 q -0.0112 -9.81428 -0.0222 -19.62854 a 13.12088 13.12088 0 0 0 -0.05025 -1.71908 a 3.25128 3.25128 0 0 0 -3.10179 -2.93609 a 33.39881 33.39881 0 0 0 -3.95853 0.00271 a 3.1568 3.1568 0 0 0 -3.15611 3.21043 c -0.05842 1.26029 -0.03349 2.52474 -0.03457 3.78735 q -0.01343 15.66842 -0.02277 31.33681 c -0.00035 0.45913 0.01748 0.92 -0.01557 1.377 c -0.09 1.24451 -0.45629 1.713 -1.67232 1.72878 q -5.25117 0.06813 -10.50381 -0.01831 c -1.23915 -0.02079 -1.58295 -0.457 -1.6812 -1.71692 c -0.04454 -0.5709 -0.02489 -1.1472 -0.025 -1.72107 q -0.00183 -12.13872 -0.00083 -24.27744 Z"
stroke-linecap="round"
id="path1" />&#10;</g>&#10;</g>&#10;</g>&#10;</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,32 +0,0 @@
#include "ColorCache.h"
/**
* Blend the color with the front color, adapting to the back color
*/
QColor ColorCache::blend(QColor color)
{
if (Rainbow::luma(m_front) > Rainbow::luma(m_back)) {
// for dark color schemes, produce a fitting color first
color = Rainbow::tint(m_front, color, 0.5);
}
// adapt contrast
return Rainbow::mix(m_front, color, m_bias);
}
/**
* Blend the color with the back color
*/
QColor ColorCache::blendBackground(QColor color)
{
// adapt contrast
return Rainbow::mix(m_back, color, m_bias);
}
void ColorCache::recolorAll()
{
auto iter = m_colors.begin();
while (iter != m_colors.end()) {
iter->front = blend(iter->original);
iter->back = blendBackground(iter->original);
}
}

View File

@ -1,106 +0,0 @@
#pragma once
#include <MessageLevel.h>
#include <rainbow.h>
#include <QMap>
#include <QtGui/QColor>
class ColorCache {
public:
ColorCache(QColor front, QColor back, qreal bias)
{
m_front = front;
m_back = back;
m_bias = bias;
};
void addColor(int key, QColor color) { m_colors[key] = { color, blend(color), blendBackground(color) }; }
void setForeground(QColor front)
{
if (m_front != front) {
m_front = front;
recolorAll();
}
}
void setBackground(QColor back)
{
if (m_back != back) {
m_back = back;
recolorAll();
}
}
QColor getFront(int key)
{
auto iter = m_colors.find(key);
if (iter == m_colors.end()) {
return QColor();
}
return (*iter).front;
}
QColor getBack(int key)
{
auto iter = m_colors.find(key);
if (iter == m_colors.end()) {
return QColor();
}
return (*iter).back;
}
/**
* Blend the color with the front color, adapting to the back color
*/
QColor blend(QColor color);
/**
* Blend the color with the back color
*/
QColor blendBackground(QColor color);
protected:
void recolorAll();
protected:
struct ColorEntry {
QColor original;
QColor front;
QColor back;
};
protected:
qreal m_bias;
QColor m_front;
QColor m_back;
QMap<int, ColorEntry> m_colors;
};
class LogColorCache : public ColorCache {
public:
LogColorCache(QColor front, QColor back) : ColorCache(front, back, 1.0)
{
addColor((int)MessageLevel::Launcher, QColor("purple"));
addColor((int)MessageLevel::Debug, QColor("green"));
addColor((int)MessageLevel::Warning, QColor("orange"));
addColor((int)MessageLevel::Error, QColor("red"));
addColor((int)MessageLevel::Fatal, QColor("red"));
addColor((int)MessageLevel::Message, front);
}
QColor getFront(MessageLevel::Enum level)
{
if (!m_colors.contains((int)level)) {
return ColorCache::getFront((int)MessageLevel::Message);
}
return ColorCache::getFront((int)level);
}
QColor getBack(MessageLevel::Enum level)
{
if (level == MessageLevel::Fatal) {
return QColor(Qt::black);
}
return QColor(Qt::transparent);
}
};

View File

@ -121,7 +121,7 @@ void VersionSelectDialog::setResizeOn(int column)
int VersionSelectDialog::exec() int VersionSelectDialog::exec()
{ {
QDialog::open(); QDialog::open();
m_versionWidget->initialize(m_vlist); m_versionWidget->initialize(m_vlist, true);
m_versionWidget->selectSearch(); m_versionWidget->selectSearch();
if (resizeOnColumn != -1) { if (resizeOnColumn != -1) {
m_versionWidget->setResizeOn(resizeOnColumn); m_versionWidget->setResizeOn(resizeOnColumn);

View File

@ -26,10 +26,6 @@ class QDialogButtonBox;
class VersionSelectWidget; class VersionSelectWidget;
class QPushButton; class QPushButton;
namespace Ui {
class VersionSelectDialog;
}
class VersionProxyModel; class VersionProxyModel;
class VersionSelectDialog : public QDialog { class VersionSelectDialog : public QDialog {
@ -37,7 +33,7 @@ class VersionSelectDialog : public QDialog {
public: public:
explicit VersionSelectDialog(BaseVersionList* vlist, QString title, QWidget* parent = 0, bool cancelable = true); explicit VersionSelectDialog(BaseVersionList* vlist, QString title, QWidget* parent = 0, bool cancelable = true);
virtual ~VersionSelectDialog() {}; virtual ~VersionSelectDialog() = default;
int exec() override; int exec() override;

View File

@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "InstallJavaDialog.h"
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QMessageBox>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include "Application.h"
#include "BaseVersionList.h"
#include "FileSystem.h"
#include "Filter.h"
#include "java/download/ArchiveDownloadTask.h"
#include "java/download/ManifestDownloadTask.h"
#include "meta/Index.h"
#include "meta/VersionList.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/java/VersionList.h"
#include "ui/widgets/PageContainer.h"
#include "ui/widgets/VersionSelectWidget.h"
class InstallJavaPage : public QWidget, public BasePage {
public:
Q_OBJECT
public:
explicit InstallJavaPage(const QString& id, const QString& iconName, const QString& name, QWidget* parent = nullptr)
: QWidget(parent), uid(id), iconName(iconName), name(name)
{
setObjectName(QStringLiteral("VersionSelectWidget"));
horizontalLayout = new QHBoxLayout(this);
horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
horizontalLayout->setContentsMargins(0, 0, 0, 0);
majorVersionSelect = new VersionSelectWidget(this);
majorVersionSelect->selectCurrent();
majorVersionSelect->setEmptyString(tr("No java versions are currently available in the meta."));
majorVersionSelect->setEmptyErrorString(tr("Couldn't load or download the java version lists!"));
horizontalLayout->addWidget(majorVersionSelect, 1);
javaVersionSelect = new VersionSelectWidget(this);
javaVersionSelect->setEmptyString(tr("No java versions are currently available for your OS."));
javaVersionSelect->setEmptyErrorString(tr("Couldn't load or download the java version lists!"));
horizontalLayout->addWidget(javaVersionSelect, 4);
connect(majorVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &InstallJavaPage::setSelectedVersion);
connect(majorVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &InstallJavaPage::selectionChanged);
connect(javaVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &InstallJavaPage::selectionChanged);
QMetaObject::connectSlotsByName(this);
}
~InstallJavaPage()
{
delete horizontalLayout;
delete majorVersionSelect;
delete javaVersionSelect;
}
//! loads the list if needed.
void initialize(Meta::VersionList::Ptr vlist)
{
vlist->setProvidedRoles({ BaseVersionList::JavaMajorRole, BaseVersionList::RecommendedRole, BaseVersionList::VersionPointerRole });
majorVersionSelect->initialize(vlist.get());
}
void setSelectedVersion(BaseVersion::Ptr version)
{
auto dcast = std::dynamic_pointer_cast<Meta::Version>(version);
if (!dcast) {
return;
}
javaVersionSelect->initialize(new Java::VersionList(dcast, this));
javaVersionSelect->selectCurrent();
}
QString id() const override { return uid; }
QString displayName() const override { return name; }
QIcon icon() const override { return APPLICATION->getThemedIcon(iconName); }
void openedImpl() override
{
if (loaded)
return;
const auto versions = APPLICATION->metadataIndex()->get(uid);
if (!versions)
return;
initialize(versions);
loaded = true;
}
void setParentContainer(BasePageContainer* container) override
{
auto dialog = dynamic_cast<QDialog*>(dynamic_cast<PageContainer*>(container)->parent());
connect(javaVersionSelect->view(), &QAbstractItemView::doubleClicked, dialog, &QDialog::accept);
}
BaseVersion::Ptr selectedVersion() const { return javaVersionSelect->selectedVersion(); }
void selectSearch() { javaVersionSelect->selectSearch(); }
void loadList()
{
majorVersionSelect->loadList();
javaVersionSelect->loadList();
}
public slots:
void setRecommendedMajors(const QStringList& majors)
{
m_recommended_majors = majors;
recommendedFilterChanged();
}
void setRecomend(bool recomend)
{
m_recommend = recomend;
recommendedFilterChanged();
}
void recommendedFilterChanged()
{
if (m_recommend) {
majorVersionSelect->setFilter(BaseVersionList::ModelRoles::JavaMajorRole, new ExactListFilter(m_recommended_majors));
} else {
majorVersionSelect->setFilter(BaseVersionList::ModelRoles::JavaMajorRole, new ExactListFilter());
}
}
signals:
void selectionChanged();
private:
const QString uid;
const QString iconName;
const QString name;
bool loaded = false;
QHBoxLayout* horizontalLayout = nullptr;
VersionSelectWidget* majorVersionSelect = nullptr;
VersionSelectWidget* javaVersionSelect = nullptr;
QStringList m_recommended_majors;
bool m_recommend;
};
static InstallJavaPage* pageCast(BasePage* page)
{
auto result = dynamic_cast<InstallJavaPage*>(page);
Q_ASSERT(result != nullptr);
return result;
}
namespace Java {
QStringList getRecommendedJavaVersionsFromVersionList(Meta::VersionList::Ptr list)
{
QStringList recommendedJavas;
for (auto ver : list->versions()) {
auto major = ver->version();
if (major.startsWith("java")) {
major = "Java " + major.mid(4);
}
recommendedJavas.append(major);
}
return recommendedJavas;
}
InstallDialog::InstallDialog(const QString& uid, BaseInstance* instance, QWidget* parent)
: QDialog(parent), container(new PageContainer(this, QString(), this)), buttons(new QDialogButtonBox(this))
{
auto layout = new QVBoxLayout(this);
container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
layout->addWidget(container);
auto buttonLayout = new QHBoxLayout(this);
auto refreshLayout = new QHBoxLayout(this);
auto refreshButton = new QPushButton(tr("&Refresh"), this);
connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(); });
refreshLayout->addWidget(refreshButton);
auto recommendedCheckBox = new QCheckBox("Recommended", this);
recommendedCheckBox->setCheckState(Qt::CheckState::Checked);
connect(recommendedCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
for (BasePage* page : container->getPages()) {
pageCast(page)->setRecomend(state == Qt::Checked);
}
});
refreshLayout->addWidget(recommendedCheckBox);
buttonLayout->addLayout(refreshLayout);
buttons->setOrientation(Qt::Horizontal);
buttons->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
buttons->button(QDialogButtonBox::Ok)->setText(tr("Download"));
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
buttonLayout->addWidget(buttons);
layout->addLayout(buttonLayout);
setWindowTitle(dialogTitle());
setWindowModality(Qt::WindowModal);
resize(840, 480);
QStringList recommendedJavas;
if (auto mcInst = dynamic_cast<MinecraftInstance*>(instance); mcInst) {
auto mc = mcInst->getPackProfile()->getComponent("net.minecraft");
if (mc) {
auto file = mc->getVersionFile(); // no need for load as it should already be loaded
if (file) {
for (auto major : file->compatibleJavaMajors) {
recommendedJavas.append(QString("Java %1").arg(major));
}
}
}
} else {
const auto versions = APPLICATION->metadataIndex()->get("net.minecraft.java");
if (versions) {
if (versions->isLoaded()) {
recommendedJavas = getRecommendedJavaVersionsFromVersionList(versions);
} else {
auto newTask = versions->getLoadTask();
if (newTask) {
connect(newTask.get(), &Task::succeeded, this, [this, versions] {
auto recommendedJavas = getRecommendedJavaVersionsFromVersionList(versions);
for (BasePage* page : container->getPages()) {
pageCast(page)->setRecommendedMajors(recommendedJavas);
}
});
if (!newTask->isRunning())
newTask->start();
} else {
recommendedJavas = getRecommendedJavaVersionsFromVersionList(versions);
}
}
}
}
for (BasePage* page : container->getPages()) {
if (page->id() == uid)
container->selectPage(page->id());
auto cast = pageCast(page);
cast->setRecomend(true);
connect(cast, &InstallJavaPage::selectionChanged, this, [this, cast] { validate(cast); });
if (!recommendedJavas.isEmpty()) {
cast->setRecommendedMajors(recommendedJavas);
}
}
connect(container, &PageContainer::selectedPageChanged, this, [this](BasePage* previous, BasePage* selected) { validate(selected); });
pageCast(container->selectedPage())->selectSearch();
validate(container->selectedPage());
}
QList<BasePage*> InstallDialog::getPages()
{
return {
// Mojang
new InstallJavaPage("net.minecraft.java", "mojang", tr("Mojang")),
// Adoptium
new InstallJavaPage("net.adoptium.java", "adoptium", tr("Adoptium")),
// Azul
new InstallJavaPage("com.azul.java", "azul", tr("Azul Zulu")),
};
}
QString InstallDialog::dialogTitle()
{
return tr("Install Java");
}
void InstallDialog::validate(BasePage* selected)
{
buttons->button(QDialogButtonBox::Ok)->setEnabled(!!std::dynamic_pointer_cast<Java::Metadata>(pageCast(selected)->selectedVersion()));
}
void InstallDialog::done(int result)
{
if (result == Accepted) {
auto* page = pageCast(container->selectedPage());
if (page->selectedVersion()) {
auto meta = std::dynamic_pointer_cast<Java::Metadata>(page->selectedVersion());
if (meta) {
Task::Ptr task;
auto final_path = FS::PathCombine(APPLICATION->javaPath(), meta->m_name);
auto deletePath = [final_path] { FS::deletePath(final_path); };
switch (meta->downloadType) {
case Java::DownloadType::Manifest:
task = makeShared<ManifestDownloadTask>(meta->url, final_path, meta->checksumType, meta->checksumHash);
break;
case Java::DownloadType::Archive:
task = makeShared<ArchiveDownloadTask>(meta->url, final_path, meta->checksumType, meta->checksumHash);
break;
case Java::DownloadType::Unknown:
QString error = QString(tr("Could not determine Java download type!"));
CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show();
deletePath();
}
connect(task.get(), &Task::failed, this, [this, &deletePath](QString reason) {
QString error = QString("Java download failed: %1").arg(reason);
CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show();
deletePath();
});
connect(task.get(), &Task::aborted, this, deletePath);
ProgressDialog pg(this);
pg.setSkipButton(true, tr("Abort"));
pg.execWithTask(task.get());
} else {
return;
}
} else {
return;
}
}
QDialog::done(result);
}
} // namespace Java
#include "InstallJavaDialog.moc"

View File

@ -0,0 +1,47 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QDialog>
#include "BaseInstance.h"
#include "ui/pages/BasePageProvider.h"
class MinecraftInstance;
class PageContainer;
class PackProfile;
class QDialogButtonBox;
namespace Java {
class InstallDialog final : public QDialog, private BasePageProvider {
Q_OBJECT
public:
explicit InstallDialog(const QString& uid = QString(), BaseInstance* instance = nullptr, QWidget* parent = nullptr);
QList<BasePage*> getPages() override;
QString dialogTitle() override;
void validate(BasePage* selected);
void done(int result) override;
private:
PageContainer* container;
QDialogButtonBox* buttons;
};
} // namespace Java

View File

@ -0,0 +1,126 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "VersionList.h"
#include <memory>
#include "BaseVersionList.h"
#include "SysInfo.h"
#include "java/JavaMetadata.h"
#include "meta/VersionList.h"
namespace Java {
VersionList::VersionList(Meta::Version::Ptr version, QObject* parent) : BaseVersionList(parent), m_version(version)
{
if (version->isLoaded())
sortVersions();
}
Task::Ptr VersionList::getLoadTask()
{
auto task = m_version->loadTask(Net::Mode::Online);
connect(task.get(), &Task::finished, this, &VersionList::sortVersions);
return task;
}
const BaseVersion::Ptr VersionList::at(int i) const
{
return m_vlist.at(i);
}
bool VersionList::isLoaded()
{
return m_version->isLoaded();
}
int VersionList::count() const
{
return m_vlist.count();
}
QVariant VersionList::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() > count())
return QVariant();
auto version = (m_vlist[index.row()]);
switch (role) {
case SortRole:
return -index.row();
case VersionPointerRole:
return QVariant::fromValue(std::dynamic_pointer_cast<BaseVersion>(m_vlist[index.row()]));
case VersionIdRole:
return version->descriptor();
case VersionRole:
return version->version.toString();
case RecommendedRole:
return false; // do not recommend any version
case JavaNameRole:
return version->name();
case JavaMajorRole: {
auto major = version->version.toString();
if (major.startsWith("java")) {
major = "Java " + major.mid(4);
}
return major;
}
case TypeRole:
return version->packageType;
case Meta::VersionList::TimeRole:
return version->releaseTime;
default:
return QVariant();
}
}
BaseVersionList::RoleList VersionList::providesRoles() const
{
return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, JavaNameRole, TypeRole, Meta::VersionList::TimeRole };
}
bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right)
{
auto rleft = std::dynamic_pointer_cast<Java::Metadata>(right);
auto rright = std::dynamic_pointer_cast<Java::Metadata>(left);
return (*rleft) < (*rright);
}
void VersionList::sortVersions()
{
if (!m_version || !m_version->data())
return;
QString versionStr = SysInfo::getSupportedJavaArchitecture();
beginResetModel();
auto runtimes = m_version->data()->runtimes;
m_vlist = {};
if (!versionStr.isEmpty() && !runtimes.isEmpty()) {
std::copy_if(runtimes.begin(), runtimes.end(), std::back_inserter(m_vlist),
[versionStr](Java::MetadataPtr val) { return val->runtimeOS == versionStr; });
std::sort(m_vlist.begin(), m_vlist.end(), sortJavas);
} else {
qWarning() << "No Java versions found for your operating system." << SysInfo::currentSystem() << " " << SysInfo::useQTForArch();
}
endResetModel();
}
} // namespace Java

View File

@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2024 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "BaseVersionList.h"
#include "java/JavaMetadata.h"
#include "meta/Version.h"
namespace Java {
class VersionList : public BaseVersionList {
Q_OBJECT
public:
explicit VersionList(Meta::Version::Ptr m_version, QObject* parent = 0);
Task::Ptr getLoadTask() override;
bool isLoaded() override;
const BaseVersion::Ptr at(int i) const override;
int count() const override;
void sortVersions() override;
QVariant data(const QModelIndex& index, int role) const override;
RoleList providesRoles() const override;
protected slots:
void updateListData(QList<BaseVersion::Ptr>) override {}
protected:
Meta::Version::Ptr m_version;
QList<Java::MetadataPtr> m_vlist;
};
} // namespace Java

View File

@ -16,7 +16,6 @@
#pragma once #pragma once
#include <functional> #include <functional>
#include <memory>
#include "ui/pages/BasePage.h" #include "ui/pages/BasePage.h"
class BasePageProvider { class BasePageProvider {

View File

@ -35,12 +35,18 @@
*/ */
#include "JavaPage.h" #include "JavaPage.h"
#include "BuildConfig.h"
#include "JavaCommon.h" #include "JavaCommon.h"
#include "java/JavaInstall.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/java/InstallJavaDialog.h"
#include "ui_JavaPage.h" #include "ui_JavaPage.h"
#include <QCheckBox>
#include <QDir> #include <QDir>
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QStringListModel>
#include <QTabBar> #include <QTabBar>
#include "ui/dialogs/VersionSelectDialog.h" #include "ui/dialogs/VersionSelectDialog.h"
@ -56,7 +62,22 @@
JavaPage::JavaPage(QWidget* parent) : QWidget(parent), ui(new Ui::JavaPage) JavaPage::JavaPage(QWidget* parent) : QWidget(parent), ui(new Ui::JavaPage)
{ {
ui->setupUi(this); ui->setupUi(this);
if (BuildConfig.JAVA_DOWNLOADER_ENABLED) {
ui->managedJavaList->initialize(new JavaInstallList(this, true));
ui->managedJavaList->setResizeOn(2);
ui->managedJavaList->selectCurrent();
ui->managedJavaList->setEmptyString(tr("No managed java versions are installed"));
ui->managedJavaList->setEmptyErrorString(tr("Couldn't load the managed java list!"));
connect(ui->autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this] {
ui->autodownloadCheckBox->setEnabled(ui->autodetectJavaCheckBox->isChecked());
if (!ui->autodetectJavaCheckBox->isChecked())
ui->autodownloadCheckBox->setChecked(false);
});
} else {
ui->autodownloadCheckBox->setHidden(true);
ui->tabWidget->tabBar()->hide(); ui->tabWidget->tabBar()->hide();
}
loadSettings(); loadSettings();
updateThresholds(); updateThresholds();
@ -94,6 +115,8 @@ void JavaPage::applySettings()
s->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " ")); s->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " "));
s->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked()); s->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked());
s->set("IgnoreJavaWizard", ui->skipJavaWizardCheckbox->isChecked()); s->set("IgnoreJavaWizard", ui->skipJavaWizardCheckbox->isChecked());
s->set("AutomaticJavaSwitch", ui->autodetectJavaCheckBox->isChecked());
s->set("AutomaticJavaDownload", ui->autodownloadCheckBox->isChecked());
JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget()); JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget());
} }
void JavaPage::loadSettings() void JavaPage::loadSettings()
@ -116,6 +139,8 @@ void JavaPage::loadSettings()
ui->jvmArgsTextBox->setPlainText(s->get("JvmArgs").toString()); ui->jvmArgsTextBox->setPlainText(s->get("JvmArgs").toString());
ui->skipCompatibilityCheckbox->setChecked(s->get("IgnoreJavaCompatibility").toBool()); ui->skipCompatibilityCheckbox->setChecked(s->get("IgnoreJavaCompatibility").toBool());
ui->skipJavaWizardCheckbox->setChecked(s->get("IgnoreJavaWizard").toBool()); ui->skipJavaWizardCheckbox->setChecked(s->get("IgnoreJavaWizard").toBool());
ui->autodetectJavaCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool());
ui->autodownloadCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool() && s->get("AutomaticJavaDownload").toBool());
} }
void JavaPage::on_javaDetectBtn_clicked() void JavaPage::on_javaDetectBtn_clicked()
@ -134,6 +159,14 @@ void JavaPage::on_javaDetectBtn_clicked()
if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) { if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) {
java = std::dynamic_pointer_cast<JavaInstall>(vselect.selectedVersion()); java = std::dynamic_pointer_cast<JavaInstall>(vselect.selectedVersion());
ui->javaPathTextBox->setText(java->path); ui->javaPathTextBox->setText(java->path);
if (!java->is_64bit && APPLICATION->settings()->get("MaxMemAlloc").toInt() > 2048) {
CustomMessageBox::selectable(this, tr("Confirm Selection"),
tr("You selected a 32-bit version of Java.\n"
"This installation does not support more than 2048MiB of RAM.\n"
"Please make sure that the maximum memory value is lower."),
QMessageBox::Warning, QMessageBox::Ok, QMessageBox::Ok)
->exec();
}
} }
} }
@ -166,6 +199,13 @@ void JavaPage::on_javaTestBtn_clicked()
checker->run(); checker->run();
} }
void JavaPage::on_downloadJavaButton_clicked()
{
auto jdialog = new Java::InstallDialog({}, nullptr, this);
jdialog->exec();
ui->managedJavaList->loadList();
}
void JavaPage::on_maxMemSpinBox_valueChanged([[maybe_unused]] int i) void JavaPage::on_maxMemSpinBox_valueChanged([[maybe_unused]] int i)
{ {
updateThresholds(); updateThresholds();
@ -210,3 +250,35 @@ void JavaPage::updateThresholds()
ui->labelMaxMemIcon->setPixmap(pix); ui->labelMaxMemIcon->setPixmap(pix);
} }
} }
void JavaPage::on_removeJavaButton_clicked()
{
auto version = ui->managedJavaList->selectedVersion();
auto dcast = std::dynamic_pointer_cast<JavaInstall>(version);
if (!dcast) {
return;
}
QDir dir(APPLICATION->javaPath());
auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (auto& entry : entries) {
if (dcast->path.startsWith(entry.canonicalFilePath())) {
auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"),
tr("You are about to remove the Java installation named \"%1\".\n"
"Are you sure?")
.arg(entry.fileName()),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response == QMessageBox::Yes) {
FS::deletePath(entry.canonicalFilePath());
ui->managedJavaList->loadList();
}
break;
}
}
}
void JavaPage::on_refreshJavaButton_clicked()
{
ui->managedJavaList->loadList();
}

View File

@ -38,7 +38,7 @@
#include <Application.h> #include <Application.h>
#include <QObjectPtr.h> #include <QObjectPtr.h>
#include <QDialog> #include <QDialog>
#include <memory> #include <QStringListModel>
#include "JavaCommon.h" #include "JavaCommon.h"
#include "ui/pages/BasePage.h" #include "ui/pages/BasePage.h"
@ -72,6 +72,9 @@ class JavaPage : public QWidget, public BasePage {
void on_javaDetectBtn_clicked(); void on_javaDetectBtn_clicked();
void on_javaTestBtn_clicked(); void on_javaTestBtn_clicked();
void on_javaBrowseBtn_clicked(); void on_javaBrowseBtn_clicked();
void on_downloadJavaButton_clicked();
void on_removeJavaButton_clicked();
void on_refreshJavaButton_clicked();
void on_maxMemSpinBox_valueChanged(int i); void on_maxMemSpinBox_valueChanged(int i);
void checkerFinished(); void checkerFinished();

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>545</width> <width>559</width>
<height>580</height> <height>659</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -34,9 +34,9 @@
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="tab"> <widget class="QWidget" name="general">
<attribute name="title"> <attribute name="title">
<string notr="true">Tab 1</string> <string notr="true">General</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
@ -160,25 +160,6 @@
<string>Java Runtime</string> <string>Java Runtime</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="7" column="0" colspan="3">
<widget class="QPlainTextEdit" name="jvmArgsTextBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
</widget>
</item>
<item row="4" column="0"> <item row="4" column="0">
<widget class="QCheckBox" name="skipCompatibilityCheckbox"> <widget class="QCheckBox" name="skipCompatibilityCheckbox">
<property name="sizePolicy"> <property name="sizePolicy">
@ -225,7 +206,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="6" column="0"> <item row="8" column="0">
<widget class="QLabel" name="labelJVMArgs"> <widget class="QLabel" name="labelJVMArgs">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred"> <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
@ -241,6 +222,45 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0">
<widget class="QCheckBox" name="skipJavaWizardCheckbox">
<property name="toolTip">
<string>If enabled, the launcher will not prompt you to choose a Java version if one isn't found.</string>
</property>
<property name="text">
<string>Skip Java &amp;Wizard</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="3">
<widget class="QPlainTextEdit" name="jvmArgsTextBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="autodetectJavaCheckBox">
<property name="toolTip">
<string>Automatically selects the Java version that is compatible with the current Minecraft instance, based on the major version required.</string>
</property>
<property name="text">
<string>Autodetect Java version</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3"> <item row="1" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
@ -277,13 +297,16 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="5" column="0"> <item row="7" column="0">
<widget class="QCheckBox" name="skipJavaWizardCheckbox"> <widget class="QCheckBox" name="autodownloadCheckBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip"> <property name="toolTip">
<string>If enabled, the launcher will not prompt you to choose a Java version if one isn't found.</string> <string>Automatically downloads and selects the Java version recommended by Mojang.</string>
</property> </property>
<property name="text"> <property name="text">
<string>Skip Java &amp;Wizard</string> <string>Auto-download Mojang Java</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -305,16 +328,106 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="management">
<attribute name="title">
<string>Management</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Downloaded Java Versions</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="VersionSelectWidget" name="managedJavaList" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="managedJavaBtnLayout">
<item>
<widget class="QPushButton" name="downloadJavaButton">
<property name="text">
<string>Download</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeJavaButton">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="refreshJavaButton">
<property name="text">
<string>Refresh</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="managementSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>VersionSelectWidget</class>
<extends>QWidget</extends>
<header>ui/widgets/VersionSelectWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops> <tabstops>
<tabstop>minMemSpinBox</tabstop> <tabstop>minMemSpinBox</tabstop>
<tabstop>maxMemSpinBox</tabstop> <tabstop>maxMemSpinBox</tabstop>
<tabstop>permGenSpinBox</tabstop> <tabstop>permGenSpinBox</tabstop>
<tabstop>javaBrowseBtn</tabstop>
<tabstop>javaPathTextBox</tabstop> <tabstop>javaPathTextBox</tabstop>
<tabstop>javaBrowseBtn</tabstop>
<tabstop>javaDetectBtn</tabstop>
<tabstop>javaTestBtn</tabstop>
<tabstop>skipCompatibilityCheckbox</tabstop>
<tabstop>skipJavaWizardCheckbox</tabstop>
<tabstop>jvmArgsTextBox</tabstop>
<tabstop>tabWidget</tabstop> <tabstop>tabWidget</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>

View File

@ -4,6 +4,7 @@
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (c) 2022 dada513 <dada513@protonmail.com> * Copyright (c) 2022 dada513 <dada513@protonmail.com>
* Copyright (C) 2022 Tayou <git@tayou.org> * Copyright (C) 2022 Tayou <git@tayou.org>
* Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -50,6 +51,7 @@
#include "DesktopServices.h" #include "DesktopServices.h"
#include "settings/SettingsObject.h" #include "settings/SettingsObject.h"
#include "ui/themes/ITheme.h" #include "ui/themes/ITheme.h"
#include "ui/themes/ThemeManager.h"
#include "updater/ExternalUpdater.h" #include "updater/ExternalUpdater.h"
#include <QApplication> #include <QApplication>
@ -66,9 +68,6 @@ enum InstSortMode {
LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage) LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage)
{ {
ui->setupUi(this); ui->setupUi(this);
auto origForeground = ui->fontPreview->palette().color(ui->fontPreview->foregroundRole());
auto origBackground = ui->fontPreview->palette().color(ui->fontPreview->backgroundRole());
m_colors.reset(new LogColorCache(origForeground, origBackground));
ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name); ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name);
ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch); ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch);
@ -80,8 +79,9 @@ LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::Launch
ui->updateSettingsBox->setHidden(!APPLICATION->updater()); ui->updateSettingsBox->setHidden(!APPLICATION->updater());
connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview())); connect(ui->fontSizeBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &LauncherPage::refreshFontPreview);
connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview())); connect(ui->consoleFont, &QFontComboBox::currentFontChanged, this, &LauncherPage::refreshFontPreview);
connect(ui->themeCustomizationWidget, &ThemeCustomizationWidget::currentWidgetThemeChanged, this, &LauncherPage::refreshFontPreview);
connect(ui->themeCustomizationWidget, &ThemeCustomizationWidget::currentCatChanged, APPLICATION, &Application::currentCatChanged); connect(ui->themeCustomizationWidget, &ThemeCustomizationWidget::currentCatChanged, APPLICATION, &Application::currentCatChanged);
} }
@ -173,6 +173,16 @@ void LauncherPage::on_downloadsDirBrowseBtn_clicked()
} }
} }
void LauncherPage::on_javaDirBrowseBtn_clicked()
{
QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Java Folder"), ui->javaDirTextBox->text());
if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) {
QString cooked_dir = FS::NormalizePath(raw_dir);
ui->javaDirTextBox->setText(cooked_dir);
}
}
void LauncherPage::on_skinsDirBrowseBtn_clicked() void LauncherPage::on_skinsDirBrowseBtn_clicked()
{ {
QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Skins Folder"), ui->skinsDirTextBox->text()); QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Skins Folder"), ui->skinsDirTextBox->text());
@ -223,6 +233,7 @@ void LauncherPage::applySettings()
s->set("IconsDir", ui->iconsDirTextBox->text()); s->set("IconsDir", ui->iconsDirTextBox->text());
s->set("DownloadsDir", ui->downloadsDirTextBox->text()); s->set("DownloadsDir", ui->downloadsDirTextBox->text());
s->set("SkinsDir", ui->skinsDirTextBox->text()); s->set("SkinsDir", ui->skinsDirTextBox->text());
s->set("JavaDir", ui->javaDirTextBox->text());
s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked()); s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked());
auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId(); auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId();
@ -289,6 +300,7 @@ void LauncherPage::loadSettings()
ui->iconsDirTextBox->setText(s->get("IconsDir").toString()); ui->iconsDirTextBox->setText(s->get("IconsDir").toString());
ui->downloadsDirTextBox->setText(s->get("DownloadsDir").toString()); ui->downloadsDirTextBox->setText(s->get("DownloadsDir").toString());
ui->skinsDirTextBox->setText(s->get("SkinsDir").toString()); ui->skinsDirTextBox->setText(s->get("SkinsDir").toString());
ui->javaDirTextBox->setText(s->get("JavaDir").toString());
ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool()); ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool());
QString sortMode = s->get("InstSortMode").toString(); QString sortMode = s->get("InstSortMode").toString();
@ -311,37 +323,47 @@ void LauncherPage::loadSettings()
void LauncherPage::refreshFontPreview() void LauncherPage::refreshFontPreview()
{ {
const LogColors& colors = APPLICATION->themeManager()->getLogColors();
int fontSize = ui->fontSizeBox->value(); int fontSize = ui->fontSizeBox->value();
QString fontFamily = ui->consoleFont->currentFont().family(); QString fontFamily = ui->consoleFont->currentFont().family();
ui->fontPreview->clear(); ui->fontPreview->clear();
defaultFormat->setFont(QFont(fontFamily, fontSize)); defaultFormat->setFont(QFont(fontFamily, fontSize));
{
auto print = [this, colors](const QString& message, MessageLevel::Enum level) {
QTextCharFormat format(*defaultFormat); QTextCharFormat format(*defaultFormat);
format.setForeground(m_colors->getFront(MessageLevel::Error));
QColor bg = colors.background.value(level);
QColor fg = colors.foreground.value(level);
if (bg.isValid())
format.setBackground(bg);
if (fg.isValid())
format.setForeground(fg);
// append a paragraph/line // append a paragraph/line
auto workCursor = ui->fontPreview->textCursor(); auto workCursor = ui->fontPreview->textCursor();
workCursor.movePosition(QTextCursor::End); workCursor.movePosition(QTextCursor::End);
workCursor.insertText(tr("[Something/ERROR] A spooky error!"), format); workCursor.insertText(message, format);
workCursor.insertBlock(); workCursor.insertBlock();
} };
{
QTextCharFormat format(*defaultFormat); print(QString("%1 version: %2 (%3)\n")
format.setForeground(m_colors->getFront(MessageLevel::Message)); .arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString(), BuildConfig.BUILD_PLATFORM),
// append a paragraph/line MessageLevel::Launcher);
auto workCursor = ui->fontPreview->textCursor();
workCursor.movePosition(QTextCursor::End); QDate today = QDate::currentDate();
workCursor.insertText(tr("[Test/INFO] A harmless message..."), format);
workCursor.insertBlock(); if (today.month() == 10 && today.day() == 31)
} print(tr("[Test/ERROR] OOoooOOOoooo! A spooky error!"), MessageLevel::Error);
{ else
QTextCharFormat format(*defaultFormat); print(tr("[Test/ERROR] A spooky error!"), MessageLevel::Error);
format.setForeground(m_colors->getFront(MessageLevel::Warning));
// append a paragraph/line print(tr("[Test/INFO] A harmless message..."), MessageLevel::Info);
auto workCursor = ui->fontPreview->textCursor(); print(tr("[Test/WARN] A not so spooky warning."), MessageLevel::Warning);
workCursor.movePosition(QTextCursor::End); print(tr("[Test/DEBUG] A secret debugging message..."), MessageLevel::Debug);
workCursor.insertText(tr("[Something/WARN] A not so spooky warning."), format); print(tr("[Test/FATAL] A terrifying fatal error!"), MessageLevel::Fatal);
workCursor.insertBlock();
}
} }
void LauncherPage::retranslate() void LauncherPage::retranslate()

View File

@ -41,7 +41,6 @@
#include <Application.h> #include <Application.h>
#include <translations/TranslationsModel.h> #include <translations/TranslationsModel.h>
#include "java/JavaChecker.h" #include "java/JavaChecker.h"
#include "ui/ColorCache.h"
#include "ui/pages/BasePage.h" #include "ui/pages/BasePage.h"
class QTextCharFormat; class QTextCharFormat;
@ -74,6 +73,7 @@ class LauncherPage : public QWidget, public BasePage {
void on_modsDirBrowseBtn_clicked(); void on_modsDirBrowseBtn_clicked();
void on_iconsDirBrowseBtn_clicked(); void on_iconsDirBrowseBtn_clicked();
void on_downloadsDirBrowseBtn_clicked(); void on_downloadsDirBrowseBtn_clicked();
void on_javaDirBrowseBtn_clicked();
void on_skinsDirBrowseBtn_clicked(); void on_skinsDirBrowseBtn_clicked();
void on_metadataDisableBtn_clicked(); void on_metadataDisableBtn_clicked();
@ -93,7 +93,5 @@ class LauncherPage : public QWidget, public BasePage {
// default format for the font preview... // default format for the font preview...
QTextCharFormat* defaultFormat; QTextCharFormat* defaultFormat;
std::unique_ptr<LogColorCache> m_colors;
std::shared_ptr<TranslationsModel> m_languageModel; std::shared_ptr<TranslationsModel> m_languageModel;
}; };

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>511</width> <width>511</width>
<height>691</height> <height>726</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -94,7 +94,7 @@
<string>Folders</string> <string>Folders</string>
</property> </property>
<layout class="QGridLayout" name="foldersBoxLayout"> <layout class="QGridLayout" name="foldersBoxLayout">
<item row="4" column="0"> <item row="8" column="0">
<widget class="QLabel" name="labelDownloadsDir"> <widget class="QLabel" name="labelDownloadsDir">
<property name="text"> <property name="text">
<string>&amp;Downloads:</string> <string>&amp;Downloads:</string>
@ -104,42 +104,59 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="8" column="2">
<widget class="QLabel" name="labelInstDir">
<property name="text">
<string>I&amp;nstances:</string>
</property>
<property name="buddy">
<cstring>instDirTextBox</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="instDirTextBox"/>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="downloadsDirTextBox"/>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="iconsDirTextBox"/>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="skinsDirTextBox"/>
</item>
<item row="4" column="2">
<widget class="QToolButton" name="downloadsDirBrowseBtn"> <widget class="QToolButton" name="downloadsDirBrowseBtn">
<property name="text"> <property name="text">
<string>Browse</string> <string>Browse</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="2" column="1">
<widget class="QLineEdit" name="modsDirTextBox"/> <widget class="QLineEdit" name="iconsDirTextBox"/>
</item> </item>
<item row="1" column="2"> <item row="3" column="1">
<widget class="QToolButton" name="modsDirBrowseBtn"> <widget class="QLineEdit" name="javaDirTextBox"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="labelSkinsDir">
<property name="text"> <property name="text">
<string>Browse</string> <string>&amp;Skins:</string>
</property>
<property name="buddy">
<cstring>skinsDirTextBox</cstring>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelIconsDir">
<property name="text">
<string>&amp;Icons:</string>
</property>
<property name="buddy">
<cstring>iconsDirTextBox</cstring>
</property>
</widget>
</item>
<item row="9" column="1" colspan="2">
<widget class="QCheckBox" name="downloadsDirWatchRecursiveCheckBox">
<property name="toolTip">
<string>When enabled, in addition to the downloads folder, its sub folders will also be searched when looking for resources (e.g. when looking for blocked mods on CurseForge).</string>
</property>
<property name="text">
<string>Check downloads folder recursively</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="downloadsDirTextBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="labelJavaDir">
<property name="text">
<string>&amp;Java:</string>
</property>
<property name="buddy">
<cstring>javaDirTextBox</cstring>
</property> </property>
</widget> </widget>
</item> </item>
@ -153,6 +170,22 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1">
<widget class="QLineEdit" name="skinsDirTextBox"/>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="modsDirTextBox"/>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="instDirTextBox"/>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="modsDirBrowseBtn">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
<item row="0" column="2"> <item row="0" column="2">
<widget class="QToolButton" name="instDirBrowseBtn"> <widget class="QToolButton" name="instDirBrowseBtn">
<property name="text"> <property name="text">
@ -167,40 +200,27 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="0" column="0">
<widget class="QLabel" name="labelIconsDir"> <widget class="QLabel" name="labelInstDir">
<property name="text"> <property name="text">
<string>&amp;Icons:</string> <string>I&amp;nstances:</string>
</property> </property>
<property name="buddy"> <property name="buddy">
<cstring>iconsDirTextBox</cstring> <cstring>instDirTextBox</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="2"> <item row="3" column="2">
<widget class="QToolButton" name="skinsDirBrowseBtn"> <widget class="QToolButton" name="javaDirBrowseBtn">
<property name="text"> <property name="text">
<string>Browse</string> <string>Browse</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="4" column="2">
<widget class="QLabel" name="labelSkinsDir"> <widget class="QToolButton" name="skinsDirBrowseBtn">
<property name="text"> <property name="text">
<string>&amp;Skins:</string> <string>Browse</string>
</property>
<property name="buddy">
<cstring>skinsDirTextBox</cstring>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QCheckBox" name="downloadsDirWatchRecursiveCheckBox">
<property name="toolTip">
<string>When enabled, in addition to the downloads folder, its sub folders will also be searched when looking for resources (e.g. when looking for blocked mods on CurseForge).</string>
</property>
<property name="text">
<string>Check downloads folder recursively</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -38,6 +38,9 @@
#include "InstanceSettingsPage.h" #include "InstanceSettingsPage.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/WorldList.h" #include "minecraft/WorldList.h"
#include "settings/Setting.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/java/InstallJavaDialog.h"
#include "ui_InstanceSettingsPage.h" #include "ui_InstanceSettingsPage.h"
#include <QDialog> #include <QDialog>
@ -64,6 +67,8 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance* inst, QWidget* parent)
m_settings = inst->settings(); m_settings = inst->settings();
ui->setupUi(this); ui->setupUi(this);
ui->javaDownloadBtn->setHidden(!BuildConfig.JAVA_DOWNLOADER_ENABLED);
connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked); connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked);
connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings); connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings);
connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings); connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);
@ -204,9 +209,6 @@ void InstanceSettingsPage::applySettings()
m_settings->reset("JvmArgs"); m_settings->reset("JvmArgs");
} }
// old generic 'override both' is removed.
m_settings->reset("OverrideJava");
// Custom Commands // Custom Commands
bool custcmd = ui->customCommands->checked(); bool custcmd = ui->customCommands->checked();
m_settings->set("OverrideCommands", custcmd); m_settings->set("OverrideCommands", custcmd);
@ -342,10 +344,11 @@ void InstanceSettingsPage::loadSettings()
ui->labelPermgenNote->setVisible(permGenVisible); ui->labelPermgenNote->setVisible(permGenVisible);
// Java Settings // Java Settings
bool overrideJava = m_settings->get("OverrideJava").toBool(); bool overrideLocation = m_settings->get("OverrideJavaLocation").toBool();
bool overrideLocation = m_settings->get("OverrideJavaLocation").toBool() || overrideJava; bool overrideArgs = m_settings->get("OverrideJavaArgs").toBool();
bool overrideArgs = m_settings->get("OverrideJavaArgs").toBool() || overrideJava;
connect(m_settings->getSetting("OverrideJavaLocation").get(), &Setting::SettingChanged, ui->javaSettingsGroupBox,
[this] { ui->javaSettingsGroupBox->setChecked(m_settings->get("OverrideJavaLocation").toBool()); });
ui->javaSettingsGroupBox->setChecked(overrideLocation); ui->javaSettingsGroupBox->setChecked(overrideLocation);
ui->javaPathTextBox->setText(m_settings->get("JavaPath").toString()); ui->javaPathTextBox->setText(m_settings->get("JavaPath").toString());
ui->skipCompatibilityCheckbox->setChecked(m_settings->get("IgnoreJavaCompatibility").toBool()); ui->skipCompatibilityCheckbox->setChecked(m_settings->get("IgnoreJavaCompatibility").toBool());
@ -431,6 +434,12 @@ void InstanceSettingsPage::loadSettings()
ui->onlineFixes->setChecked(m_settings->get("OnlineFixes").toBool()); ui->onlineFixes->setChecked(m_settings->get("OnlineFixes").toBool());
} }
void InstanceSettingsPage::on_javaDownloadBtn_clicked()
{
auto jdialog = new Java::InstallDialog({}, m_instance, this);
jdialog->exec();
}
void InstanceSettingsPage::on_javaDetectBtn_clicked() void InstanceSettingsPage::on_javaDetectBtn_clicked()
{ {
if (JavaUtils::getJavaCheckPath().isEmpty()) { if (JavaUtils::getJavaCheckPath().isEmpty()) {
@ -452,6 +461,15 @@ void InstanceSettingsPage::on_javaDetectBtn_clicked()
ui->labelPermGen->setVisible(visible); ui->labelPermGen->setVisible(visible);
ui->labelPermgenNote->setVisible(visible); ui->labelPermgenNote->setVisible(visible);
m_settings->set("PermGenVisible", visible); m_settings->set("PermGenVisible", visible);
if (!java->is_64bit && m_settings->get("MaxMemAlloc").toInt() > 2048) {
CustomMessageBox::selectable(this, tr("Confirm Selection"),
tr("You selected a 32-bit version of Java.\n"
"This installation does not support more than 2048MiB of RAM.\n"
"Please make sure that the maximum memory value is lower."),
QMessageBox::Warning, QMessageBox::Ok, QMessageBox::Ok)
->exec();
}
} }
} }

View File

@ -69,6 +69,7 @@ class InstanceSettingsPage : public QWidget, public BasePage {
void on_javaDetectBtn_clicked(); void on_javaDetectBtn_clicked();
void on_javaTestBtn_clicked(); void on_javaTestBtn_clicked();
void on_javaBrowseBtn_clicked(); void on_javaBrowseBtn_clicked();
void on_javaDownloadBtn_clicked();
void on_maxMemSpinBox_valueChanged(int i); void on_maxMemSpinBox_valueChanged(int i);
void on_serverJoinAddressButton_toggled(bool checked); void on_serverJoinAddressButton_toggled(bool checked);
void on_worldJoinButton_toggled(bool checked); void on_worldJoinButton_toggled(bool checked);

View File

@ -84,6 +84,13 @@
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="javaDownloadBtn">
<property name="text">
<string>Download Java</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="javaDetectBtn"> <widget class="QPushButton" name="javaDetectBtn">
<property name="text"> <property name="text">
@ -764,6 +771,12 @@
<tabstop>openGlobalJavaSettingsButton</tabstop> <tabstop>openGlobalJavaSettingsButton</tabstop>
<tabstop>settingsTabs</tabstop> <tabstop>settingsTabs</tabstop>
<tabstop>javaSettingsGroupBox</tabstop> <tabstop>javaSettingsGroupBox</tabstop>
<tabstop>javaPathTextBox</tabstop>
<tabstop>javaBrowseBtn</tabstop>
<tabstop>javaDownloadBtn</tabstop>
<tabstop>javaDetectBtn</tabstop>
<tabstop>javaTestBtn</tabstop>
<tabstop>skipCompatibilityCheckbox</tabstop>
<tabstop>memoryGroupBox</tabstop> <tabstop>memoryGroupBox</tabstop>
<tabstop>minMemSpinBox</tabstop> <tabstop>minMemSpinBox</tabstop>
<tabstop>maxMemSpinBox</tabstop> <tabstop>maxMemSpinBox</tabstop>
@ -783,6 +796,18 @@
<tabstop>useNativeOpenALCheck</tabstop> <tabstop>useNativeOpenALCheck</tabstop>
<tabstop>showGameTime</tabstop> <tabstop>showGameTime</tabstop>
<tabstop>recordGameTime</tabstop> <tabstop>recordGameTime</tabstop>
<tabstop>miscellaneousSettingsBox</tabstop>
<tabstop>closeAfterLaunchCheck</tabstop>
<tabstop>quitAfterGameStopCheck</tabstop>
<tabstop>perfomanceGroupBox</tabstop>
<tabstop>enableFeralGamemodeCheck</tabstop>
<tabstop>enableMangoHud</tabstop>
<tabstop>useDiscreteGpuCheck</tabstop>
<tabstop>gameTimeGroupBox</tabstop>
<tabstop>serverJoinGroupBox</tabstop>
<tabstop>serverJoinAddress</tabstop>
<tabstop>instanceAccountGroupBox</tabstop>
<tabstop>instanceAccountSelector</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -3,7 +3,7 @@
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> * Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -47,8 +47,8 @@
#include "launch/LaunchTask.h" #include "launch/LaunchTask.h"
#include "settings/Setting.h" #include "settings/Setting.h"
#include "ui/ColorCache.h"
#include "ui/GuiUtil.h" #include "ui/GuiUtil.h"
#include "ui/themes/ThemeManager.h"
#include <BuildConfig.h> #include <BuildConfig.h>
@ -57,26 +57,36 @@ class LogFormatProxyModel : public QIdentityProxyModel {
LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {} LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {}
QVariant data(const QModelIndex& index, int role) const override QVariant data(const QModelIndex& index, int role) const override
{ {
const LogColors& colors = APPLICATION->themeManager()->getLogColors();
switch (role) { switch (role) {
case Qt::FontRole: case Qt::FontRole:
return m_font; return m_font;
case Qt::ForegroundRole: { case Qt::ForegroundRole: {
MessageLevel::Enum level = (MessageLevel::Enum)QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); auto level = static_cast<MessageLevel::Enum>(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt());
return m_colors->getFront(level); QColor result = colors.foreground.value(level);
if (result.isValid())
return result;
break;
} }
case Qt::BackgroundRole: { case Qt::BackgroundRole: {
MessageLevel::Enum level = (MessageLevel::Enum)QIdentityProxyModel::data(index, LogModel::LevelRole).toInt(); auto level = static_cast<MessageLevel::Enum>(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt());
return m_colors->getBack(level); QColor result = colors.background.value(level);
if (result.isValid())
return result;
break;
} }
default: }
return QIdentityProxyModel::data(index, role); return QIdentityProxyModel::data(index, role);
} }
}
void setFont(QFont font) { m_font = font; } void setFont(QFont font) { m_font = font; }
void setColors(LogColorCache* colors) { m_colors.reset(colors); }
QModelIndex find(const QModelIndex& start, const QString& value, bool reverse) const QModelIndex find(const QModelIndex& start, const QString& value, bool reverse) const
{ {
QModelIndex parentIndex = parent(start); QModelIndex parentIndex = parent(start);
@ -125,7 +135,6 @@ class LogFormatProxyModel : public QIdentityProxyModel {
private: private:
QFont m_font; QFont m_font;
std::unique_ptr<LogColorCache> m_colors;
}; };
LogPage::LogPage(InstancePtr instance, QWidget* parent) : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) LogPage::LogPage(InstancePtr instance, QWidget* parent) : QWidget(parent), ui(new Ui::LogPage), m_instance(instance)
@ -134,12 +143,6 @@ LogPage::LogPage(InstancePtr instance, QWidget* parent) : QWidget(parent), ui(ne
ui->tabWidget->tabBar()->hide(); ui->tabWidget->tabBar()->hide();
m_proxy = new LogFormatProxyModel(this); m_proxy = new LogFormatProxyModel(this);
// set up text colors in the log proxy and adapt them to the current theme foreground and background
{
auto origForeground = ui->text->palette().color(ui->text->foregroundRole());
auto origBackground = ui->text->palette().color(ui->text->backgroundRole());
m_proxy->setColors(new LogColorCache(origForeground, origBackground));
}
// set up fonts in the log proxy // set up fonts in the log proxy
{ {

View File

@ -407,6 +407,11 @@ void VersionPage::on_actionChange_version_triggered()
bool important = false; bool important = false;
if (uid == "net.minecraft") { if (uid == "net.minecraft") {
important = true; important = true;
if (APPLICATION->settings()->get("AutomaticJavaSwitch").toBool() && m_inst->settings()->get("AutomaticJava").toBool() &&
m_inst->settings()->get("OverrideJavaLocation").toBool()) {
m_inst->settings()->set("OverrideJavaLocation", false);
m_inst->settings()->set("JavaPath", "");
}
} }
m_profile->setComponentVersion(uid, vselect.selectedVersion()->descriptor(), important); m_profile->setComponentVersion(uid, vselect.selectedVersion()->descriptor(), important);
m_profile->resolve(Net::Mode::Online); m_profile->resolve(Net::Mode::Online);

View File

@ -12,12 +12,8 @@
#include <sys.h> #include <sys.h>
#include "FileSystem.h"
#include "JavaCommon.h" #include "JavaCommon.h"
#include "java/JavaInstall.h"
#include "java/JavaUtils.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/widgets/JavaSettingsWidget.h" #include "ui/widgets/JavaSettingsWidget.h"
#include "ui/widgets/VersionSelectWidget.h" #include "ui/widgets/VersionSelectWidget.h"
@ -57,6 +53,8 @@ bool JavaWizardPage::validatePage()
{ {
auto settings = APPLICATION->settings(); auto settings = APPLICATION->settings();
auto result = m_java_widget->validate(); auto result = m_java_widget->validate();
settings->set("AutomaticJavaSwitch", m_java_widget->autoDetectJava());
settings->set("AutomaticJavaDownload", m_java_widget->autoDownloadJava());
switch (result) { switch (result) {
default: default:
case JavaSettingsWidget::ValidationStatus::Bad: { case JavaSettingsWidget::ValidationStatus::Bad: {

View File

@ -46,11 +46,6 @@ QString BrightTheme::name()
return QObject::tr("Bright"); return QObject::tr("Bright");
} }
bool BrightTheme::hasColorScheme()
{
return true;
}
QPalette BrightTheme::colorScheme() QPalette BrightTheme::colorScheme()
{ {
QPalette brightPalette; QPalette brightPalette;

View File

@ -45,7 +45,6 @@ class BrightTheme : public FusionTheme {
QString tooltip() override; QString tooltip() override;
bool hasStyleSheet() override; bool hasStyleSheet() override;
QString appStyleSheet() override; QString appStyleSheet() override;
bool hasColorScheme() override;
QPalette colorScheme() override; QPalette colorScheme() override;
double fadeAmount() override; double fadeAmount() override;
QColor fadeColor() override; QColor fadeColor() override;

View File

@ -2,6 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2024 Tayou <git@tayou.org> * Copyright (C) 2024 Tayou <git@tayou.org>
* Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -39,121 +40,6 @@
const char* themeFile = "theme.json"; const char* themeFile = "theme.json";
static bool readThemeJson(const QString& path,
QPalette& palette,
double& fadeAmount,
QColor& fadeColor,
QString& name,
QString& widgets,
QString& qssFilePath,
bool& dataIncomplete)
{
QFileInfo pathInfo(path);
if (pathInfo.exists() && pathInfo.isFile()) {
try {
auto doc = Json::requireDocument(path, "Theme JSON file");
const QJsonObject root = doc.object();
dataIncomplete = !root.contains("qssFilePath");
name = Json::requireString(root, "name", "Theme name");
widgets = Json::requireString(root, "widgets", "Qt widget theme");
qssFilePath = Json::ensureString(root, "qssFilePath", "themeStyle.css");
auto colorsRoot = Json::requireObject(root, "colors", "colors object");
auto readColor = [&](QString colorName) -> QColor {
auto colorValue = Json::ensureString(colorsRoot, colorName, QString());
if (!colorValue.isEmpty()) {
QColor color(colorValue);
if (!color.isValid()) {
themeWarningLog() << "Color value" << colorValue << "for" << colorName << "was not recognized.";
return QColor();
}
return color;
}
return QColor();
};
auto readAndSetColor = [&](QPalette::ColorRole role, QString colorName) {
auto color = readColor(colorName);
if (color.isValid()) {
palette.setColor(role, color);
} else {
themeDebugLog() << "Color value for" << colorName << "was not present.";
}
};
// palette
readAndSetColor(QPalette::Window, "Window");
readAndSetColor(QPalette::WindowText, "WindowText");
readAndSetColor(QPalette::Base, "Base");
readAndSetColor(QPalette::AlternateBase, "AlternateBase");
readAndSetColor(QPalette::ToolTipBase, "ToolTipBase");
readAndSetColor(QPalette::ToolTipText, "ToolTipText");
readAndSetColor(QPalette::Text, "Text");
readAndSetColor(QPalette::Button, "Button");
readAndSetColor(QPalette::ButtonText, "ButtonText");
readAndSetColor(QPalette::BrightText, "BrightText");
readAndSetColor(QPalette::Link, "Link");
readAndSetColor(QPalette::Highlight, "Highlight");
readAndSetColor(QPalette::HighlightedText, "HighlightedText");
// fade
fadeColor = readColor("fadeColor");
fadeAmount = Json::ensureDouble(colorsRoot, "fadeAmount", 0.5, "fade amount");
} catch (const Exception& e) {
themeWarningLog() << "Couldn't load theme json: " << e.cause();
return false;
}
} else {
themeDebugLog() << "No theme json present.";
return false;
}
return true;
}
static bool writeThemeJson(const QString& path,
const QPalette& palette,
double fadeAmount,
QColor fadeColor,
QString name,
QString widgets,
QString qssFilePath)
{
QJsonObject rootObj;
rootObj.insert("name", name);
rootObj.insert("widgets", widgets);
rootObj.insert("qssFilePath", qssFilePath);
QJsonObject colorsObj;
auto insertColor = [&](QPalette::ColorRole role, QString colorName) { colorsObj.insert(colorName, palette.color(role).name()); };
// palette
insertColor(QPalette::Window, "Window");
insertColor(QPalette::WindowText, "WindowText");
insertColor(QPalette::Base, "Base");
insertColor(QPalette::AlternateBase, "AlternateBase");
insertColor(QPalette::ToolTipBase, "ToolTipBase");
insertColor(QPalette::ToolTipText, "ToolTipText");
insertColor(QPalette::Text, "Text");
insertColor(QPalette::Button, "Button");
insertColor(QPalette::ButtonText, "ButtonText");
insertColor(QPalette::BrightText, "BrightText");
insertColor(QPalette::Link, "Link");
insertColor(QPalette::Highlight, "Highlight");
insertColor(QPalette::HighlightedText, "HighlightedText");
// fade
colorsObj.insert("fadeColor", fadeColor.name());
colorsObj.insert("fadeAmount", fadeAmount);
rootObj.insert("colors", colorsObj);
try {
Json::write(rootObj, path);
return true;
} catch ([[maybe_unused]] const Exception& e) {
themeWarningLog() << "Failed to write theme json to" << path;
return false;
}
}
/// @param baseTheme Base Theme /// @param baseTheme Base Theme
/// @param fileInfo FileInfo object for file to load /// @param fileInfo FileInfo object for file to load
/// @param isManifest whether to load a theme manifest or a qss file /// @param isManifest whether to load a theme manifest or a qss file
@ -176,23 +62,22 @@ CustomTheme::CustomTheme(ITheme* baseTheme, QFileInfo& fileInfo, bool isManifest
auto themeFilePath = FS::PathCombine(path, themeFile); auto themeFilePath = FS::PathCombine(path, themeFile);
bool jsonDataIncomplete = false;
m_palette = baseTheme->colorScheme(); m_palette = baseTheme->colorScheme();
if (readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets, m_qssFilePath, jsonDataIncomplete)) {
bool hasCustomLogColors = false;
if (read(themeFilePath, hasCustomLogColors)) {
// If theme data was found, fade "Disabled" color of each role according to FadeAmount // If theme data was found, fade "Disabled" color of each role according to FadeAmount
m_palette = fadeInactive(m_palette, m_fadeAmount, m_fadeColor); m_palette = fadeInactive(m_palette, m_fadeAmount, m_fadeColor);
if (!hasCustomLogColors)
m_logColors = defaultLogColors(m_palette);
} else { } else {
themeDebugLog() << "Did not read theme json file correctly, not changing theme, keeping previous."; themeDebugLog() << "Did not read theme json file correctly, not changing theme, keeping previous.";
m_logColors = defaultLogColors(m_palette);
return; return;
} }
// FIXME: This is kinda jank, it only actually checks if the qss file path is not present. It should actually check for any relevant
// missing data (e.g. name, colors)
if (jsonDataIncomplete) {
writeThemeJson(fileInfo.absoluteFilePath(), m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets, m_qssFilePath);
}
auto qssFilePath = FS::PathCombine(path, m_qssFilePath); auto qssFilePath = FS::PathCombine(path, m_qssFilePath);
QFileInfo info(qssFilePath); QFileInfo info(qssFilePath);
if (info.isFile()) { if (info.isFile()) {
@ -251,11 +136,6 @@ QString CustomTheme::name()
return m_name; return m_name;
} }
bool CustomTheme::hasColorScheme()
{
return true;
}
QPalette CustomTheme::colorScheme() QPalette CustomTheme::colorScheme()
{ {
return m_palette; return m_palette;
@ -289,3 +169,99 @@ QString CustomTheme::tooltip()
{ {
return m_tooltip; return m_tooltip;
} }
bool CustomTheme::read(const QString& path, bool& hasCustomLogColors)
{
QFileInfo pathInfo(path);
if (pathInfo.exists() && pathInfo.isFile()) {
try {
auto doc = Json::requireDocument(path, "Theme JSON file");
const QJsonObject root = doc.object();
m_name = Json::requireString(root, "name", "Theme name");
m_widgets = Json::requireString(root, "widgets", "Qt widget theme");
m_qssFilePath = Json::ensureString(root, "qssFilePath", "themeStyle.css");
auto readColor = [&](const QJsonObject& colors, const QString& colorName) -> QColor {
auto colorValue = Json::ensureString(colors, colorName, QString());
if (!colorValue.isEmpty()) {
QColor color(colorValue);
if (!color.isValid()) {
themeWarningLog() << "Color value" << colorValue << "for" << colorName << "was not recognized.";
return {};
}
return color;
}
return {};
};
if (root.contains("colors")) {
auto colorsRoot = Json::requireObject(root, "colors");
auto readAndSetPaletteColor = [&](QPalette::ColorRole role, const QString& colorName) {
auto color = readColor(colorsRoot, colorName);
if (color.isValid()) {
m_palette.setColor(role, color);
} else {
themeDebugLog() << "Color value for" << colorName << "was not present.";
}
};
// palette
readAndSetPaletteColor(QPalette::Window, "Window");
readAndSetPaletteColor(QPalette::WindowText, "WindowText");
readAndSetPaletteColor(QPalette::Base, "Base");
readAndSetPaletteColor(QPalette::AlternateBase, "AlternateBase");
readAndSetPaletteColor(QPalette::ToolTipBase, "ToolTipBase");
readAndSetPaletteColor(QPalette::ToolTipText, "ToolTipText");
readAndSetPaletteColor(QPalette::Text, "Text");
readAndSetPaletteColor(QPalette::Button, "Button");
readAndSetPaletteColor(QPalette::ButtonText, "ButtonText");
readAndSetPaletteColor(QPalette::BrightText, "BrightText");
readAndSetPaletteColor(QPalette::Link, "Link");
readAndSetPaletteColor(QPalette::Highlight, "Highlight");
readAndSetPaletteColor(QPalette::HighlightedText, "HighlightedText");
// fade
m_fadeColor = readColor(colorsRoot, "fadeColor");
m_fadeAmount = Json::ensureDouble(colorsRoot, "fadeAmount", 0.5, "fade amount");
}
if (root.contains("logColors")) {
hasCustomLogColors = true;
auto logColorsRoot = Json::requireObject(root, "logColors");
auto readAndSetLogColor = [&](MessageLevel::Enum level, bool fg, const QString& colorName) {
auto color = readColor(logColorsRoot, colorName);
if (color.isValid()) {
if (fg)
m_logColors.foreground[level] = color;
else
m_logColors.background[level] = color;
} else {
themeDebugLog() << "Color value for" << colorName << "was not present.";
}
};
readAndSetLogColor(MessageLevel::Message, false, "MessageHighlight");
readAndSetLogColor(MessageLevel::Launcher, false, "LauncherHighlight");
readAndSetLogColor(MessageLevel::Debug, false, "DebugHighlight");
readAndSetLogColor(MessageLevel::Warning, false, "WarningHighlight");
readAndSetLogColor(MessageLevel::Error, false, "ErrorHighlight");
readAndSetLogColor(MessageLevel::Fatal, false, "FatalHighlight");
readAndSetLogColor(MessageLevel::Message, true, "Message");
readAndSetLogColor(MessageLevel::Launcher, true, "Launcher");
readAndSetLogColor(MessageLevel::Debug, true, "Debug");
readAndSetLogColor(MessageLevel::Warning, true, "Warning");
readAndSetLogColor(MessageLevel::Error, true, "Error");
readAndSetLogColor(MessageLevel::Fatal, true, "Fatal");
}
} catch (const Exception& e) {
themeWarningLog() << "Couldn't load theme json: " << e.cause();
return false;
}
} else {
themeDebugLog() << "No theme json present.";
return false;
}
return true;
}

View File

@ -2,6 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2024 Tayou <git@tayou.org> * Copyright (C) 2024 Tayou <git@tayou.org>
* Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -47,14 +48,16 @@ class CustomTheme : public ITheme {
QString tooltip() override; QString tooltip() override;
bool hasStyleSheet() override; bool hasStyleSheet() override;
QString appStyleSheet() override; QString appStyleSheet() override;
bool hasColorScheme() override;
QPalette colorScheme() override; QPalette colorScheme() override;
double fadeAmount() override; double fadeAmount() override;
QColor fadeColor() override; QColor fadeColor() override;
QString qtTheme() override; QString qtTheme() override;
LogColors logColorScheme() override { return m_logColors; }
QStringList searchPaths() override; QStringList searchPaths() override;
private: /* data */ private:
bool read(const QString& path, bool& hasCustomLogColors);
QPalette m_palette; QPalette m_palette;
QColor m_fadeColor; QColor m_fadeColor;
double m_fadeAmount; double m_fadeAmount;
@ -63,6 +66,7 @@ class CustomTheme : public ITheme {
QString m_id; QString m_id;
QString m_widgets; QString m_widgets;
QString m_qssFilePath; QString m_qssFilePath;
LogColors m_logColors;
/** /**
* The tooltip could be defined in the theme json, * The tooltip could be defined in the theme json,
* or composed of other fields that could be in there. * or composed of other fields that could be in there.

View File

@ -2,6 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2024 Tayou <git@tayou.org> * Copyright (C) 2024 Tayou <git@tayou.org>
* Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -46,11 +47,6 @@ QString DarkTheme::name()
return QObject::tr("Dark"); return QObject::tr("Dark");
} }
bool DarkTheme::hasColorScheme()
{
return true;
}
QPalette DarkTheme::colorScheme() QPalette DarkTheme::colorScheme()
{ {
QPalette darkPalette; QPalette darkPalette;
@ -90,6 +86,7 @@ QString DarkTheme::appStyleSheet()
{ {
return "QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }"; return "QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }";
} }
QString DarkTheme::tooltip() QString DarkTheme::tooltip()
{ {
return ""; return "";

View File

@ -45,7 +45,6 @@ class DarkTheme : public FusionTheme {
QString tooltip() override; QString tooltip() override;
bool hasStyleSheet() override; bool hasStyleSheet() override;
QString appStyleSheet() override; QString appStyleSheet() override;
bool hasColorScheme() override;
QPalette colorScheme() override; QPalette colorScheme() override;
double fadeAmount() override; double fadeAmount() override;
QColor fadeColor() override; QColor fadeColor() override;

View File

@ -44,9 +44,7 @@ void ITheme::apply(bool)
{ {
APPLICATION->setStyleSheet(QString()); APPLICATION->setStyleSheet(QString());
QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme()))); QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme())));
if (hasColorScheme()) {
QApplication::setPalette(colorScheme()); QApplication::setPalette(colorScheme());
}
APPLICATION->setStyleSheet(appStyleSheet()); APPLICATION->setStyleSheet(appStyleSheet());
QDir::setSearchPaths("theme", searchPaths()); QDir::setSearchPaths("theme", searchPaths());
} }
@ -73,3 +71,30 @@ QPalette ITheme::fadeInactive(QPalette in, qreal bias, QColor color)
blend(QPalette::HighlightedText); blend(QPalette::HighlightedText);
return in; return in;
} }
LogColors ITheme::defaultLogColors(const QPalette& palette)
{
LogColors result;
const QColor& bg = palette.color(QPalette::Base);
const QColor& fg = palette.color(QPalette::Text);
auto blend = [bg, fg](QColor color) {
if (Rainbow::luma(fg) > Rainbow::luma(bg)) {
// for dark color schemes, produce a fitting color first
color = Rainbow::tint(fg, color, 0.5);
}
// adapt contrast
return Rainbow::mix(fg, color, 1);
};
result.background[MessageLevel::Fatal] = Qt::black;
result.foreground[MessageLevel::Launcher] = blend(QColor("purple"));
result.foreground[MessageLevel::Debug] = blend(QColor("green"));
result.foreground[MessageLevel::Warning] = blend(QColor("orange"));
result.foreground[MessageLevel::Error] = blend(QColor("red"));
result.foreground[MessageLevel::Fatal] = blend(QColor("red"));
return result;
}

View File

@ -2,6 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Tayou <git@tayou.org> * Copyright (C) 2022 Tayou <git@tayou.org>
* Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -33,11 +34,19 @@
* limitations under the License. * limitations under the License.
*/ */
#pragma once #pragma once
#include <MessageLevel.h>
#include <QMap>
#include <QPalette> #include <QPalette>
#include <QString> #include <QString>
class QStyle; class QStyle;
struct LogColors {
QMap<MessageLevel::Enum, QColor> background;
QMap<MessageLevel::Enum, QColor> foreground;
};
// TODO: rename to Theme; this is not an interface as it contains method implementations
class ITheme { class ITheme {
public: public:
virtual ~ITheme() {} virtual ~ITheme() {}
@ -48,11 +57,12 @@ class ITheme {
virtual bool hasStyleSheet() = 0; virtual bool hasStyleSheet() = 0;
virtual QString appStyleSheet() = 0; virtual QString appStyleSheet() = 0;
virtual QString qtTheme() = 0; virtual QString qtTheme() = 0;
virtual bool hasColorScheme() = 0;
virtual QPalette colorScheme() = 0; virtual QPalette colorScheme() = 0;
virtual QColor fadeColor() = 0; virtual QColor fadeColor() = 0;
virtual double fadeAmount() = 0; virtual double fadeAmount() = 0;
virtual LogColors logColorScheme() { return defaultLogColors(colorScheme()); }
virtual QStringList searchPaths() { return {}; } virtual QStringList searchPaths() { return {}; }
static QPalette fadeInactive(QPalette in, qreal bias, QColor color); static QPalette fadeInactive(QPalette in, qreal bias, QColor color);
static LogColors defaultLogColors(const QPalette& palette);
}; };

View File

@ -125,8 +125,3 @@ bool SystemTheme::hasStyleSheet()
{ {
return false; return false;
} }
bool SystemTheme::hasColorScheme()
{
return true;
}

View File

@ -48,7 +48,6 @@ class SystemTheme : public ITheme {
QString qtTheme() override; QString qtTheme() override;
bool hasStyleSheet() override; bool hasStyleSheet() override;
QString appStyleSheet() override; QString appStyleSheet() override;
bool hasColorScheme() override;
QPalette colorScheme() override; QPalette colorScheme() override;
double fadeAmount() override; double fadeAmount() override;
QColor fadeColor() override; QColor fadeColor() override;

View File

@ -123,11 +123,11 @@ void ThemeManager::initializeWidgets()
{ {
themeDebugLog() << "Determining System Widget Theme..."; themeDebugLog() << "Determining System Widget Theme...";
const auto& style = QApplication::style(); const auto& style = QApplication::style();
currentlySelectedSystemTheme = style->objectName(); m_currentlySelectedSystemTheme = style->objectName();
themeDebugLog() << "System theme seems to be:" << currentlySelectedSystemTheme; themeDebugLog() << "System theme seems to be:" << m_currentlySelectedSystemTheme;
themeDebugLog() << "<> Initializing Widget Themes"; themeDebugLog() << "<> Initializing Widget Themes";
themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique<SystemTheme>(currentlySelectedSystemTheme, true)); themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique<SystemTheme>(m_currentlySelectedSystemTheme, true));
auto darkThemeId = addTheme(std::make_unique<DarkTheme>()); auto darkThemeId = addTheme(std::make_unique<DarkTheme>());
themeDebugLog() << "Loading Built-in Theme:" << darkThemeId; themeDebugLog() << "Loading Built-in Theme:" << darkThemeId;
themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique<BrightTheme>()); themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique<BrightTheme>());
@ -196,8 +196,8 @@ QList<ITheme*> ThemeManager::getValidApplicationThemes()
QList<CatPack*> ThemeManager::getValidCatPacks() QList<CatPack*> ThemeManager::getValidCatPacks()
{ {
QList<CatPack*> ret; QList<CatPack*> ret;
ret.reserve(m_cat_packs.size()); ret.reserve(m_catPacks.size());
for (auto&& [id, theme] : m_cat_packs) { for (auto&& [id, theme] : m_catPacks) {
ret.append(theme.get()); ret.append(theme.get());
} }
return ret; return ret;
@ -246,6 +246,8 @@ void ThemeManager::setApplicationTheme(const QString& name, bool initial)
auto& theme = themeIter->second; auto& theme = themeIter->second;
themeDebugLog() << "applying theme" << theme->name(); themeDebugLog() << "applying theme" << theme->name();
theme->apply(initial); theme->apply(initial);
m_logColors = theme->logColorScheme();
} else { } else {
themeWarningLog() << "Tried to set invalid theme:" << name; themeWarningLog() << "Tried to set invalid theme:" << name;
} }
@ -258,7 +260,7 @@ void ThemeManager::applyCurrentlySelectedTheme(bool initial)
themeDebugLog() << "<> Icon theme set."; themeDebugLog() << "<> Icon theme set.";
auto applicationTheme = settings->get("ApplicationTheme").toString(); auto applicationTheme = settings->get("ApplicationTheme").toString();
if (applicationTheme == "") { if (applicationTheme == "") {
applicationTheme = currentlySelectedSystemTheme; applicationTheme = m_currentlySelectedSystemTheme;
} }
setApplicationTheme(applicationTheme, initial); setApplicationTheme(applicationTheme, initial);
themeDebugLog() << "<> Application theme set."; themeDebugLog() << "<> Application theme set.";
@ -266,8 +268,8 @@ void ThemeManager::applyCurrentlySelectedTheme(bool initial)
QString ThemeManager::getCatPack(QString catName) QString ThemeManager::getCatPack(QString catName)
{ {
auto catIter = m_cat_packs.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString()); auto catIter = m_catPacks.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString());
if (catIter != m_cat_packs.end()) { if (catIter != m_catPacks.end()) {
auto& catPack = catIter->second; auto& catPack = catIter->second;
themeDebugLog() << "applying catpack" << catPack->id(); themeDebugLog() << "applying catpack" << catPack->id();
return catPack->path(); return catPack->path();
@ -275,14 +277,14 @@ QString ThemeManager::getCatPack(QString catName)
themeWarningLog() << "Tried to get invalid catPack:" << catName; themeWarningLog() << "Tried to get invalid catPack:" << catName;
} }
return m_cat_packs.begin()->second->path(); return m_catPacks.begin()->second->path();
} }
QString ThemeManager::addCatPack(std::unique_ptr<CatPack> catPack) QString ThemeManager::addCatPack(std::unique_ptr<CatPack> catPack)
{ {
QString id = catPack->id(); QString id = catPack->id();
if (m_cat_packs.find(id) == m_cat_packs.end()) if (m_catPacks.find(id) == m_catPacks.end())
m_cat_packs.emplace(id, std::move(catPack)); m_catPacks.emplace(id, std::move(catPack));
else else
themeWarningLog() << "CatPack(" << id << ") not added to prevent id duplication"; themeWarningLog() << "CatPack(" << id << ") not added to prevent id duplication";
return id; return id;
@ -340,8 +342,8 @@ void ThemeManager::refresh()
{ {
m_themes.clear(); m_themes.clear();
m_icons.clear(); m_icons.clear();
m_cat_packs.clear(); m_catPacks.clear();
initializeThemes(); initializeThemes();
initializeCatPacks(); initializeCatPacks();
}; }

View File

@ -2,7 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2024 Tayou <git@tayou.org> * Copyright (C) 2024 Tayou <git@tayou.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> * Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -55,6 +55,8 @@ class ThemeManager {
QString getCatPack(QString catName = ""); QString getCatPack(QString catName = "");
QList<CatPack*> getValidCatPacks(); QList<CatPack*> getValidCatPacks();
const LogColors& getLogColors() { return m_logColors; }
void refresh(); void refresh();
private: private:
@ -63,8 +65,9 @@ class ThemeManager {
QDir m_iconThemeFolder{ "iconthemes" }; QDir m_iconThemeFolder{ "iconthemes" };
QDir m_applicationThemeFolder{ "themes" }; QDir m_applicationThemeFolder{ "themes" };
QDir m_catPacksFolder{ "catpacks" }; QDir m_catPacksFolder{ "catpacks" };
std::map<QString, std::unique_ptr<CatPack>> m_cat_packs; std::map<QString, std::unique_ptr<CatPack>> m_catPacks;
QString currentlySelectedSystemTheme; QString m_currentlySelectedSystemTheme;
LogColors m_logColors;
void initializeThemes(); void initializeThemes();
void initializeCatPacks(); void initializeCatPacks();

View File

@ -4,6 +4,7 @@
#include <QGroupBox> #include <QGroupBox>
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QMessageBox>
#include <QPushButton> #include <QPushButton>
#include <QSpinBox> #include <QSpinBox>
#include <QToolButton> #include <QToolButton>
@ -11,12 +12,16 @@
#include <sys.h> #include <sys.h>
#include "DesktopServices.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "JavaCommon.h" #include "JavaCommon.h"
#include "java/JavaChecker.h"
#include "java/JavaInstall.h" #include "java/JavaInstall.h"
#include "java/JavaInstallList.h"
#include "java/JavaUtils.h" #include "java/JavaUtils.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/java/InstallJavaDialog.h"
#include "ui/widgets/VersionSelectWidget.h" #include "ui/widgets/VersionSelectWidget.h"
#include "Application.h" #include "Application.h"
@ -38,6 +43,9 @@ JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent)
connect(m_javaBrowseBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaBrowseBtn_clicked); connect(m_javaBrowseBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaBrowseBtn_clicked);
connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited); connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited);
connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaSettingsWidget::on_javaStatusBtn_clicked); connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaSettingsWidget::on_javaStatusBtn_clicked);
if (BuildConfig.JAVA_DOWNLOADER_ENABLED) {
connect(m_javaDownloadBtn, &QPushButton::clicked, this, &JavaSettingsWidget::javaDownloadBtn_clicked);
}
} }
void JavaSettingsWidget::setupUi() void JavaSettingsWidget::setupUi()
@ -120,6 +128,38 @@ void JavaSettingsWidget::setupUi()
m_verticalLayout->addWidget(m_memoryGroupBox); m_verticalLayout->addWidget(m_memoryGroupBox);
m_horizontalBtnLayout = new QHBoxLayout();
m_horizontalBtnLayout->setObjectName(QStringLiteral("horizontalBtnLayout"));
if (BuildConfig.JAVA_DOWNLOADER_ENABLED) {
m_javaDownloadBtn = new QPushButton(tr("Download Java"), this);
m_horizontalBtnLayout->addWidget(m_javaDownloadBtn);
}
m_verticalLayout->addLayout(m_horizontalBtnLayout);
m_autoJavaGroupBox = new QGroupBox(this);
m_autoJavaGroupBox->setObjectName(QStringLiteral("autoJavaGroupBox"));
m_veriticalJavaLayout = new QVBoxLayout(m_autoJavaGroupBox);
m_veriticalJavaLayout->setObjectName(QStringLiteral("veriticalJavaLayout"));
m_autodetectJavaCheckBox = new QCheckBox(m_autoJavaGroupBox);
m_autodetectJavaCheckBox->setObjectName("autodetectJavaCheckBox");
m_veriticalJavaLayout->addWidget(m_autodetectJavaCheckBox);
if (BuildConfig.JAVA_DOWNLOADER_ENABLED) {
m_autodownloadCheckBox = new QCheckBox(m_autoJavaGroupBox);
m_autodownloadCheckBox->setObjectName("autodownloadCheckBox");
m_autodownloadCheckBox->setEnabled(false);
m_veriticalJavaLayout->addWidget(m_autodownloadCheckBox);
connect(m_autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this] {
m_autodownloadCheckBox->setEnabled(m_autodetectJavaCheckBox->isChecked());
if (!m_autodetectJavaCheckBox->isChecked())
m_autodownloadCheckBox->setChecked(false);
});
}
m_verticalLayout->addWidget(m_autoJavaGroupBox);
retranslate(); retranslate();
} }
@ -137,6 +177,19 @@ void JavaSettingsWidget::initialize()
m_maxMemSpinBox->setValue(observedMaxMemory); m_maxMemSpinBox->setValue(observedMaxMemory);
m_permGenSpinBox->setValue(observedPermGenMemory); m_permGenSpinBox->setValue(observedPermGenMemory);
updateThresholds(); updateThresholds();
if (BuildConfig.JAVA_DOWNLOADER_ENABLED) {
auto button =
CustomMessageBox::selectable(this, tr("Automatic Java Download"),
tr("%1 can automatically download the correct Java version for each version of Minecraft..\n"
"Do you want to enable Java auto-download?\n")
.arg(BuildConfig.LAUNCHER_DISPLAYNAME),
QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)
->exec();
auto checked = button == QMessageBox::Yes;
m_autodetectJavaCheckBox->setChecked(checked);
m_autodownloadCheckBox->setChecked(checked);
}
} }
void JavaSettingsWidget::refresh() void JavaSettingsWidget::refresh()
@ -153,21 +206,53 @@ JavaSettingsWidget::ValidationStatus JavaSettingsWidget::validate()
switch (javaStatus) { switch (javaStatus) {
default: default:
case JavaStatus::NotSet: case JavaStatus::NotSet:
/* fallthrough */
case JavaStatus::DoesNotExist: case JavaStatus::DoesNotExist:
/* fallthrough */
case JavaStatus::DoesNotStart: case JavaStatus::DoesNotStart:
/* fallthrough */
case JavaStatus::ReturnedInvalidData: { case JavaStatus::ReturnedInvalidData: {
int button = CustomMessageBox::selectable(this, tr("No Java version selected"), if (!(BuildConfig.JAVA_DOWNLOADER_ENABLED && m_autodownloadCheckBox->isChecked())) { // the java will not be autodownloaded
tr("You didn't select a Java version or selected something that doesn't work.\n" int button = QMessageBox::No;
if (m_result.mojangPlatform == "32" && maxHeapSize() > 2048) {
button = CustomMessageBox::selectable(
this, tr("32-bit Java detected"),
tr("You selected a 32-bit installation of Java, but allocated more than 2048MiB as maximum memory.\n"
"%1 will not be able to start Minecraft.\n" "%1 will not be able to start Minecraft.\n"
"Do you wish to proceed without any Java?" "Do you wish to proceed?"
"\n\n" "\n\n"
"You can change the Java version in the settings later.\n") "You can change the Java version in the settings later.\n")
.arg(BuildConfig.LAUNCHER_DISPLAYNAME), .arg(BuildConfig.LAUNCHER_DISPLAYNAME),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::NoButton) QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No | QMessageBox::Help, QMessageBox::NoButton)
->exec(); ->exec();
} else {
button = CustomMessageBox::selectable(this, tr("No Java version selected"),
tr("You either didn't select a Java version or selected one that does not work.\n"
"%1 will not be able to start Minecraft.\n"
"Do you wish to proceed without a functional version of Java?"
"\n\n"
"You can change the Java version in the settings later.\n")
.arg(BuildConfig.LAUNCHER_DISPLAYNAME),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No | QMessageBox::Help,
QMessageBox::NoButton)
->exec();
}
switch (button) {
case QMessageBox::Yes:
return ValidationStatus::JavaBad;
case QMessageBox::Help:
DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("java-wizard")));
/* fallthrough */
case QMessageBox::No:
/* fallthrough */
default:
return ValidationStatus::Bad;
}
if (button == QMessageBox::No) { if (button == QMessageBox::No) {
return ValidationStatus::Bad; return ValidationStatus::Bad;
} }
}
return ValidationStatus::JavaBad; return ValidationStatus::JavaBad;
} break; } break;
case JavaStatus::Pending: { case JavaStatus::Pending: {
@ -250,21 +335,22 @@ void JavaSettingsWidget::javaVersionSelected(BaseVersion::Ptr version)
void JavaSettingsWidget::on_javaBrowseBtn_clicked() void JavaSettingsWidget::on_javaBrowseBtn_clicked()
{ {
QString filter; auto filter = QString("Java (%1)").arg(JavaUtils::javaExecutable);
#if defined Q_OS_WIN32 auto raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"), QString(), filter);
filter = "Java (javaw.exe)";
#else
filter = "Java (java)";
#endif
QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"), QString(), filter);
if (raw_path.isEmpty()) { if (raw_path.isEmpty()) {
return; return;
} }
QString cooked_path = FS::NormalizePath(raw_path); auto cooked_path = FS::NormalizePath(raw_path);
m_javaPathTextBox->setText(cooked_path); m_javaPathTextBox->setText(cooked_path);
checkJavaPath(cooked_path); checkJavaPath(cooked_path);
} }
void JavaSettingsWidget::javaDownloadBtn_clicked()
{
auto jdialog = new Java::InstallDialog({}, nullptr, this);
jdialog->exec();
}
void JavaSettingsWidget::on_javaStatusBtn_clicked() void JavaSettingsWidget::on_javaStatusBtn_clicked()
{ {
QString text; QString text;
@ -359,30 +445,25 @@ void JavaSettingsWidget::checkJavaPath(const QString& path)
return; return;
} }
setJavaStatus(JavaStatus::Pending); setJavaStatus(JavaStatus::Pending);
m_checker.reset(new JavaChecker()); m_checker.reset(
m_checker->m_path = path; new JavaChecker(path, "", minHeapSize(), maxHeapSize(), m_permGenSpinBox->isVisible() ? m_permGenSpinBox->value() : 0, 0, this));
m_checker->m_minMem = minHeapSize();
m_checker->m_maxMem = maxHeapSize();
if (m_permGenSpinBox->isVisible()) {
m_checker->m_permGen = m_permGenSpinBox->value();
}
connect(m_checker.get(), &JavaChecker::checkFinished, this, &JavaSettingsWidget::checkFinished); connect(m_checker.get(), &JavaChecker::checkFinished, this, &JavaSettingsWidget::checkFinished);
m_checker->performCheck(); m_checker->start();
} }
void JavaSettingsWidget::checkFinished(JavaCheckResult result) void JavaSettingsWidget::checkFinished(const JavaChecker::Result& result)
{ {
m_result = result; m_result = result;
switch (result.validity) { switch (result.validity) {
case JavaCheckResult::Validity::Valid: { case JavaChecker::Result::Validity::Valid: {
setJavaStatus(JavaStatus::Good); setJavaStatus(JavaStatus::Good);
break; break;
} }
case JavaCheckResult::Validity::ReturnedInvalidData: { case JavaChecker::Result::Validity::ReturnedInvalidData: {
setJavaStatus(JavaStatus::ReturnedInvalidData); setJavaStatus(JavaStatus::ReturnedInvalidData);
break; break;
} }
case JavaCheckResult::Validity::Errored: { case JavaChecker::Result::Validity::Errored: {
setJavaStatus(JavaStatus::DoesNotStart); setJavaStatus(JavaStatus::DoesNotStart);
break; break;
} }
@ -403,6 +484,11 @@ void JavaSettingsWidget::retranslate()
m_minMemSpinBox->setToolTip(tr("The amount of memory Minecraft is started with.")); m_minMemSpinBox->setToolTip(tr("The amount of memory Minecraft is started with."));
m_permGenSpinBox->setToolTip(tr("The amount of memory available to store loaded Java classes.")); m_permGenSpinBox->setToolTip(tr("The amount of memory available to store loaded Java classes."));
m_javaBrowseBtn->setText(tr("Browse")); m_javaBrowseBtn->setText(tr("Browse"));
if (BuildConfig.JAVA_DOWNLOADER_ENABLED) {
m_autodownloadCheckBox->setText(tr("Auto-download Mojang Java"));
}
m_autodetectJavaCheckBox->setText(tr("Autodetect Java version"));
m_autoJavaGroupBox->setTitle(tr("Autodetect Java"));
} }
void JavaSettingsWidget::updateThresholds() void JavaSettingsWidget::updateThresholds()
@ -418,6 +504,9 @@ void JavaSettingsWidget::updateThresholds()
} else if (observedMaxMemory < observedMinMemory) { } else if (observedMaxMemory < observedMinMemory) {
iconName = "status-yellow"; iconName = "status-yellow";
m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value")); m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else if (observedMaxMemory > 2048 && m_result.is_64bit) {
iconName = "status-bad";
m_labelMaxMemIcon->setToolTip(tr("You are exceeding the maximum allocation supported by 32-bit installations of Java."));
} else { } else {
iconName = "status-good"; iconName = "status-good";
m_labelMaxMemIcon->setToolTip(""); m_labelMaxMemIcon->setToolTip("");
@ -430,3 +519,13 @@ void JavaSettingsWidget::updateThresholds()
m_labelMaxMemIcon->setPixmap(pix); m_labelMaxMemIcon->setPixmap(pix);
} }
} }
bool JavaSettingsWidget::autoDownloadJava() const
{
return m_autodownloadCheckBox && m_autodownloadCheckBox->isChecked();
}
bool JavaSettingsWidget::autoDetectJava() const
{
return m_autodetectJavaCheckBox->isChecked();
}

View File

@ -4,6 +4,7 @@
#include <BaseVersion.h> #include <BaseVersion.h>
#include <QObjectPtr.h> #include <QObjectPtr.h>
#include <java/JavaChecker.h> #include <java/JavaChecker.h>
#include <qcheckbox.h>
#include <QIcon> #include <QIcon>
class QLineEdit; class QLineEdit;
@ -25,7 +26,7 @@ class JavaSettingsWidget : public QWidget {
public: public:
explicit JavaSettingsWidget(QWidget* parent); explicit JavaSettingsWidget(QWidget* parent);
virtual ~JavaSettingsWidget() {}; virtual ~JavaSettingsWidget() = default;
enum class JavaStatus { NotSet, Pending, Good, DoesNotExist, DoesNotStart, ReturnedInvalidData } javaStatus = JavaStatus::NotSet; enum class JavaStatus { NotSet, Pending, Good, DoesNotExist, DoesNotStart, ReturnedInvalidData } javaStatus = JavaStatus::NotSet;
@ -41,6 +42,8 @@ class JavaSettingsWidget : public QWidget {
int minHeapSize() const; int minHeapSize() const;
int maxHeapSize() const; int maxHeapSize() const;
QString javaPath() const; QString javaPath() const;
bool autoDetectJava() const;
bool autoDownloadJava() const;
void updateThresholds(); void updateThresholds();
@ -50,7 +53,8 @@ class JavaSettingsWidget : public QWidget {
void javaVersionSelected(BaseVersion::Ptr version); void javaVersionSelected(BaseVersion::Ptr version);
void on_javaBrowseBtn_clicked(); void on_javaBrowseBtn_clicked();
void on_javaStatusBtn_clicked(); void on_javaStatusBtn_clicked();
void checkFinished(JavaCheckResult result); void javaDownloadBtn_clicked();
void checkFinished(const JavaChecker::Result& result);
protected: /* methods */ protected: /* methods */
void checkJavaPathOnEdit(const QString& path); void checkJavaPathOnEdit(const QString& path);
@ -76,15 +80,23 @@ class JavaSettingsWidget : public QWidget {
QSpinBox* m_minMemSpinBox = nullptr; QSpinBox* m_minMemSpinBox = nullptr;
QLabel* m_labelPermGen = nullptr; QLabel* m_labelPermGen = nullptr;
QSpinBox* m_permGenSpinBox = nullptr; QSpinBox* m_permGenSpinBox = nullptr;
QHBoxLayout* m_horizontalBtnLayout = nullptr;
QPushButton* m_javaDownloadBtn = nullptr;
QIcon goodIcon; QIcon goodIcon;
QIcon yellowIcon; QIcon yellowIcon;
QIcon badIcon; QIcon badIcon;
QGroupBox* m_autoJavaGroupBox = nullptr;
QVBoxLayout* m_veriticalJavaLayout = nullptr;
QCheckBox* m_autodetectJavaCheckBox = nullptr;
QCheckBox* m_autodownloadCheckBox = nullptr;
unsigned int observedMinMemory = 0; unsigned int observedMinMemory = 0;
unsigned int observedMaxMemory = 0; unsigned int observedMaxMemory = 0;
unsigned int observedPermGenMemory = 0; unsigned int observedPermGenMemory = 0;
QString queuedCheck; QString queuedCheck;
uint64_t m_availableMemory = 0ull; uint64_t m_availableMemory = 0ull;
shared_qobject_ptr<JavaChecker> m_checker; shared_qobject_ptr<JavaChecker> m_checker;
JavaCheckResult m_result; JavaChecker::Result m_result;
}; };

View File

@ -105,14 +105,14 @@ bool VersionSelectWidget::eventFilter(QObject* watched, QEvent* event)
return QObject::eventFilter(watched, event); return QObject::eventFilter(watched, event);
} }
void VersionSelectWidget::initialize(BaseVersionList* vlist) void VersionSelectWidget::initialize(BaseVersionList* vlist, bool forceLoad)
{ {
m_vlist = vlist; m_vlist = vlist;
m_proxyModel->setSourceModel(vlist); m_proxyModel->setSourceModel(vlist);
listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch); listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch);
if (!m_vlist->isLoaded()) { if (!m_vlist->isLoaded() || forceLoad) {
loadList(); loadList();
} else { } else {
if (m_proxyModel->rowCount() == 0) { if (m_proxyModel->rowCount() == 0) {

View File

@ -54,7 +54,7 @@ class VersionSelectWidget : public QWidget {
~VersionSelectWidget(); ~VersionSelectWidget();
//! loads the list if needed. //! loads the list if needed.
void initialize(BaseVersionList* vlist); void initialize(BaseVersionList* vlist, bool forceLoad = false);
//! Starts a task that loads the list. //! Starts a task that loads the list.
void loadList(); void loadList();