mirror of
https://github.com/PrismLauncher/PrismLauncher
synced 2026-04-23 17:14:56 +00:00
Compare commits
84 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
031015b332 | ||
|
|
b4f34b87d6 | ||
|
|
de8ad56e60 | ||
|
|
b65f25fcfe | ||
|
|
f14701ffb7 | ||
|
|
672cd4d59c | ||
|
|
a7c91796b3 | ||
|
|
5a9fdffd7d | ||
|
|
e154413b1d | ||
|
|
541e5ca9fe | ||
|
|
48f240703f | ||
|
|
b595488487 | ||
|
|
e7322a4507 | ||
|
|
c67de94b3d | ||
|
|
e7a03d311c | ||
|
|
af8225e2da | ||
|
|
49e9f96327 | ||
|
|
cbaf45084e | ||
|
|
03799bf258 | ||
|
|
4344f5eef9 | ||
|
|
4872ec634c | ||
|
|
85613cfadc | ||
|
|
4a59e6012d | ||
|
|
ffded2ccac | ||
|
|
15b39af92e | ||
|
|
4ed3aa1f1c | ||
|
|
7d0d9a3827 | ||
|
|
b9fa4ffc00 | ||
|
|
f40cbf816e | ||
|
|
3ee45691ab | ||
|
|
8a68c625fb | ||
|
|
8eb9a9971b | ||
|
|
44e3ae59e4 | ||
|
|
8901da68c7 | ||
|
|
fa54329711 | ||
|
|
cddbb0e970 | ||
|
|
da50f0e9e3 | ||
|
|
28c42d04b6 | ||
|
|
5d9622db21 | ||
|
|
7e8db63882 | ||
|
|
519d8f7385 | ||
|
|
ece83eb637 | ||
|
|
06282c0363 | ||
|
|
a0c5893a98 | ||
|
|
fbec685eb5 | ||
|
|
0b578fa767 | ||
|
|
ae331cfc9a | ||
|
|
575be16d3e | ||
|
|
d5db0c6c1b | ||
|
|
1fec781251 | ||
|
|
08de904e21 | ||
|
|
7a1d2e41a1 | ||
|
|
1b650622ea | ||
|
|
88035b9815 | ||
|
|
ae7e143537 | ||
|
|
b230645d53 | ||
|
|
9b270f783e | ||
|
|
ac54df366b | ||
|
|
a17a45c748 | ||
|
|
a488eb6d5d | ||
|
|
f3ff0a730a | ||
|
|
966ecd00bd | ||
|
|
4b3aedd5d0 | ||
|
|
2219c37d7f | ||
|
|
b7344af313 | ||
|
|
9bccda0a79 | ||
|
|
013bb5cac3 | ||
|
|
e8afd48c67 | ||
|
|
2ef22124cd | ||
|
|
6b9d2dbb64 | ||
|
|
658a1391f8 | ||
|
|
4cf8cf7d18 | ||
|
|
724c9a4a2c | ||
|
|
ec4484282c | ||
|
|
c044ed36af | ||
|
|
91616ae9b6 | ||
|
|
2fe0569bd6 | ||
|
|
364968a6b4 | ||
|
|
fdd1a5dde8 | ||
|
|
4151db6c94 | ||
|
|
4706f894e3 | ||
|
|
bf75d50baf | ||
|
|
983bf34807 | ||
|
|
40b7cab3ed |
|
|
@ -55,7 +55,7 @@ runs:
|
|||
# TODO(@getchoo): Get this working on MSYS2!
|
||||
- name: Setup ccache
|
||||
if: ${{ (runner.os != 'Windows' || inputs.msystem == '') && inputs.build-type == 'Debug' }}
|
||||
uses: hendrikmuhs/ccache-action@v1.2.22
|
||||
uses: hendrikmuhs/ccache-action@v1.2.23
|
||||
with:
|
||||
variant: sccache
|
||||
create-symlink: ${{ runner.os != 'Windows' }}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ runs:
|
|||
|
||||
- name: Retrieve ccache cache (MinGW)
|
||||
if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }}
|
||||
uses: actions/cache@v5.0.4
|
||||
uses: actions/cache@v5.0.5
|
||||
with:
|
||||
path: '${{ github.workspace }}\.ccache'
|
||||
key: ${{ runner.os }}-mingw-w64-ccache-${{ github.run_id }}
|
||||
|
|
|
|||
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Create backport PRs
|
||||
uses: korthout/backport-action@v4.3.0
|
||||
uses: korthout/backport-action@v4.4
|
||||
with:
|
||||
# Config README: https://github.com/korthout/backport-action#backport-action
|
||||
pull_description: |-
|
||||
|
|
|
|||
2
.github/workflows/clang-tidy.yml
vendored
2
.github/workflows/clang-tidy.yml
vendored
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
submodules: "true"
|
||||
|
||||
- name: Setup sccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.22
|
||||
uses: hendrikmuhs/ccache-action@v1.2.23
|
||||
with:
|
||||
variant: sccache
|
||||
|
||||
|
|
|
|||
2
.github/workflows/container.yml
vendored
2
.github/workflows/container.yml
vendored
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
- arch: arm64
|
||||
os: ubuntu-24.04-arm
|
||||
- arch: amd64
|
||||
os: ubuntu-24.04-arm
|
||||
os: ubuntu-24.04
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
|
|
|||
2
.github/workflows/nix.yml
vendored
2
.github/workflows/nix.yml
vendored
|
|
@ -88,7 +88,7 @@ jobs:
|
|||
- os: ubuntu-22.04-arm
|
||||
system: aarch64-linux
|
||||
|
||||
- os: macos-14
|
||||
- os: macos-26
|
||||
system: aarch64-darwin
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
|
|
|||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -94,7 +94,7 @@ jobs:
|
|||
|
||||
- name: Create release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag_name: ${{ github.ref }}
|
||||
|
|
|
|||
2
.github/workflows/update-flake.yml
vendored
2
.github/workflows/update-flake.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: cachix/install-nix-action@96951a368ba55167b55f1c916f7d416bac6505fe # v31
|
||||
- uses: cachix/install-nix-action@616559265b40713947b9c190a8ff4b507b5df49b # v31
|
||||
|
||||
- uses: DeterminateSystems/update-flake-lock@v28
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CAC
|
|||
set(Launcher_LEGACY_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for legacy (<=1.5.2) FML Libraries.")
|
||||
|
||||
######## Set version numbers ########
|
||||
set(Launcher_VERSION_MAJOR 11)
|
||||
set(Launcher_VERSION_MAJOR 12)
|
||||
set(Launcher_VERSION_MINOR 0)
|
||||
set(Launcher_VERSION_PATCH 0)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1774709303,
|
||||
"narHash": "sha256-D4ely1FsBcvtj/qSrNhSWpq+CUZKNiKwJIxpxnfy9o4=",
|
||||
"rev": "8110df5ad7abf5d4c0f6fb0f8f978390e77f9685",
|
||||
"lastModified": 1776169885,
|
||||
"narHash": "sha256-Gk2T0tDDDAs319hp/ak+bAIUG5bPMvnNEjPV8CS86Fg=",
|
||||
"rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9",
|
||||
"type": "tarball",
|
||||
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre971119.8110df5ad7ab/nixexprs.tar.xz"
|
||||
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre980183.4bd9165a9165/nixexprs.tar.xz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
|
|
|
|||
|
|
@ -735,6 +735,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512);
|
||||
m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::defaultMaxJvmMem());
|
||||
m_settings->registerSetting("PermGen", 128);
|
||||
m_settings->registerSetting("LowMemWarning", true);
|
||||
|
||||
// Java Settings
|
||||
m_settings->registerSetting("JavaPath", "");
|
||||
|
|
@ -870,6 +871,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
resetIfInvalid(m_settings->registerSetting("LegacyFMLLibsURLOverride", "").get());
|
||||
}
|
||||
|
||||
m_settings->registerSetting("MetaRefreshOnLaunch", true);
|
||||
m_settings->registerSetting("CloseAfterLaunch", false);
|
||||
m_settings->registerSetting("QuitAfterGameStop", false);
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class BaseVersionList : public QAbstractListModel {
|
|||
* The task returned by this function should reset the model when it's done.
|
||||
* \return A pointer to a task that reloads the version list.
|
||||
*/
|
||||
virtual Task::Ptr getLoadTask() = 0;
|
||||
virtual Task::Ptr getLoadTask(bool forceReload = false) = 0;
|
||||
|
||||
//! Checks whether or not the list is loaded. If this returns false, the list should be
|
||||
// loaded.
|
||||
|
|
|
|||
|
|
@ -141,7 +141,6 @@ uint64_t HardwareInfo::availableRamMiB()
|
|||
}
|
||||
|
||||
#elif defined(Q_OS_MACOS)
|
||||
#include "mach/mach.h"
|
||||
#include "sys/sysctl.h"
|
||||
|
||||
QString HardwareInfo::cpuInfo()
|
||||
|
|
@ -171,18 +170,32 @@ uint64_t HardwareInfo::totalRamMiB()
|
|||
|
||||
uint64_t HardwareInfo::availableRamMiB()
|
||||
{
|
||||
mach_port_t host_port = mach_host_self();
|
||||
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
vm_statistics64_data_t vm_stats;
|
||||
|
||||
if (host_statistics64(host_port, HOST_VM_INFO64, reinterpret_cast<host_info64_t>(&vm_stats), &count) == KERN_SUCCESS) {
|
||||
// transforming bytes -> mib
|
||||
return (vm_stats.free_count + vm_stats.inactive_count) * vm_page_size / 1024 / 1024;
|
||||
MacOSHardwareInfo::MemoryPressureLevel MacOSHardwareInfo::memoryPressureLevel()
|
||||
{
|
||||
uint32_t level;
|
||||
size_t levelSize = sizeof level;
|
||||
if (sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &levelSize, nullptr, 0) == 0) {
|
||||
return static_cast<MemoryPressureLevel>(level);
|
||||
}
|
||||
|
||||
qWarning() << "Could not get available RAM: host_statistics64";
|
||||
return 0;
|
||||
qWarning() << "Could not get memory pressure level: sysctlbyname";
|
||||
return MemoryPressureLevel::Normal;
|
||||
}
|
||||
|
||||
QString MacOSHardwareInfo::memoryPressureLevelName()
|
||||
{
|
||||
// The names are internal, users refer to levels by their graph colors in Activity Monitor
|
||||
switch (memoryPressureLevel()) {
|
||||
case MemoryPressureLevel::Normal:
|
||||
return "Green";
|
||||
case MemoryPressureLevel::Warning:
|
||||
return "Yellow";
|
||||
case MemoryPressureLevel::Critical:
|
||||
return "Red";
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_LINUX)
|
||||
|
|
|
|||
|
|
@ -27,3 +27,16 @@ uint64_t totalRamMiB();
|
|||
uint64_t availableRamMiB();
|
||||
QStringList gpuInfo();
|
||||
} // namespace HardwareInfo
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
namespace MacOSHardwareInfo {
|
||||
enum class MemoryPressureLevel : uint8_t {
|
||||
Normal = 1,
|
||||
Warning = 2,
|
||||
Critical = 4,
|
||||
};
|
||||
|
||||
MemoryPressureLevel memoryPressureLevel();
|
||||
QString memoryPressureLevelName();
|
||||
} // namespace MacOSHardwareInfo
|
||||
#endif
|
||||
|
|
@ -18,7 +18,7 @@ bool InstanceCreationTask::abort()
|
|||
return m_gameFilesTask->abort();
|
||||
}
|
||||
|
||||
return true;
|
||||
return InstanceTask::abort();
|
||||
}
|
||||
|
||||
void InstanceCreationTask::executeTask()
|
||||
|
|
|
|||
|
|
@ -924,23 +924,23 @@ class InstanceStaging : public Task {
|
|||
connect(child, &Task::progress, this, &InstanceStaging::setProgress);
|
||||
connect(child, &Task::stepProgress, this, &InstanceStaging::propagateStepProgress);
|
||||
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded);
|
||||
m_backoffTimer.setSingleShot(true);
|
||||
}
|
||||
|
||||
virtual ~InstanceStaging() {}
|
||||
~InstanceStaging() override = default;
|
||||
|
||||
// FIXME/TODO: add ability to abort during instance commit retries
|
||||
bool abort() override
|
||||
{
|
||||
if (!canAbort())
|
||||
if (!canAbort()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_child->abort();
|
||||
}
|
||||
bool canAbort() const override { return (m_child && m_child->canAbort()); }
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override
|
||||
void executeTask() override
|
||||
{
|
||||
if (m_stagingPath.isNull()) {
|
||||
emitFailed(tr("Could not create staging folder"));
|
||||
|
|
@ -954,10 +954,8 @@ class InstanceStaging : public Task {
|
|||
private slots:
|
||||
void childSucceeded()
|
||||
{
|
||||
if (!isRunning())
|
||||
return;
|
||||
unsigned sleepTime = backoff();
|
||||
if (m_parent->commitStagedInstance(m_stagingPath, *m_child.get(), m_child->group(), *m_child.get())) {
|
||||
if (m_parent->commitStagedInstance(m_stagingPath, *m_child, m_child->group(), *m_child)) {
|
||||
m_backoffTimer.stop();
|
||||
emitSucceeded();
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -51,8 +51,9 @@ JavaInstallList::JavaInstallList(QObject* parent, bool onlyManagedVersions)
|
|||
: BaseVersionList(parent), m_only_managed_versions(onlyManagedVersions)
|
||||
{}
|
||||
|
||||
Task::Ptr JavaInstallList::getLoadTask()
|
||||
Task::Ptr JavaInstallList::getLoadTask(bool forceReload)
|
||||
{
|
||||
Q_UNUSED(forceReload)
|
||||
load();
|
||||
return getCurrentTask();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class JavaInstallList : public BaseVersionList {
|
|||
public:
|
||||
explicit JavaInstallList(QObject* parent = 0, bool onlyManagedVersions = false);
|
||||
|
||||
Task::Ptr getLoadTask() override;
|
||||
Task::Ptr getLoadTask(bool forceReload = false) override;
|
||||
bool isLoaded() override;
|
||||
const BaseVersion::Ptr at(int i) const override;
|
||||
int count() const override;
|
||||
|
|
|
|||
|
|
@ -82,12 +82,12 @@ QUrl BaseEntity::url() const
|
|||
return QUrl(metaOverride).resolved(localFilename());
|
||||
}
|
||||
|
||||
Task::Ptr BaseEntity::loadTask(Net::Mode mode)
|
||||
Task::Ptr BaseEntity::loadTask(Net::Mode mode, bool forceReload)
|
||||
{
|
||||
if (m_task && m_task->isRunning()) {
|
||||
return m_task;
|
||||
}
|
||||
m_task.reset(new BaseEntityLoadTask(this, mode));
|
||||
m_task.reset(new BaseEntityLoadTask(this, mode, forceReload));
|
||||
return m_task;
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +107,9 @@ BaseEntity::LoadStatus BaseEntity::status() const
|
|||
return m_load_status;
|
||||
}
|
||||
|
||||
BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode) : m_entity(parent), m_mode(mode) {}
|
||||
BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool forceReload)
|
||||
: m_entity(parent), m_mode(mode), m_force_reload(forceReload)
|
||||
{}
|
||||
|
||||
void BaseEntityLoadTask::executeTask()
|
||||
{
|
||||
|
|
@ -125,9 +127,11 @@ void BaseEntityLoadTask::executeTask()
|
|||
}
|
||||
|
||||
// on online the hash needs to match
|
||||
hashMatches = m_entity->m_sha256 == m_entity->m_file_sha256;
|
||||
const auto& expected = m_entity->m_sha256;
|
||||
const auto& actual = m_entity->m_file_sha256;
|
||||
hashMatches = expected == actual;
|
||||
if (m_mode == Net::Mode::Online && !m_entity->m_sha256.isEmpty() && !hashMatches) {
|
||||
throw Exception("mismatched checksum");
|
||||
throw Exception(QString("Checksum mismatch, expected sha256: %1, got: %2").arg(expected, actual));
|
||||
}
|
||||
|
||||
// load local file
|
||||
|
|
@ -149,13 +153,18 @@ void BaseEntityLoadTask::executeTask()
|
|||
auto wasLoadedOffline = m_entity->m_load_status != BaseEntity::LoadStatus::NotLoaded && m_mode == Net::Mode::Offline;
|
||||
// if has is not present allways fetch from remote(e.g. the main index file), else only fetch if hash doesn't match
|
||||
auto wasLoadedRemote = m_entity->m_sha256.isEmpty() ? m_entity->m_load_status == BaseEntity::LoadStatus::Remote : hashMatches;
|
||||
if (wasLoadedOffline || wasLoadedRemote) {
|
||||
if (wasLoadedOffline || (wasLoadedRemote && !m_force_reload)) {
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
m_task.reset(new NetJob(QObject::tr("Download of meta file %1").arg(m_entity->localFilename()), APPLICATION->network()));
|
||||
auto url = m_entity->url();
|
||||
auto entry = APPLICATION->metacache()->resolveEntry("meta", m_entity->localFilename());
|
||||
if (m_force_reload) {
|
||||
// clear validators so manual refreshes fetch a fresh body
|
||||
entry->setETag({});
|
||||
entry->setRemoteChangedTimestamp({});
|
||||
}
|
||||
entry->setStale(true);
|
||||
auto dl = Net::ApiDownload::makeCached(url, entry);
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class BaseEntity {
|
|||
void setSha256(QString sha256);
|
||||
|
||||
virtual void parse(const QJsonObject& obj) = 0;
|
||||
[[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online);
|
||||
[[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online, bool forceReload = false);
|
||||
|
||||
protected:
|
||||
QString m_sha256; // the expected sha256
|
||||
|
|
@ -58,7 +58,7 @@ class BaseEntityLoadTask : public Task {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode);
|
||||
explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool forceReload);
|
||||
~BaseEntityLoadTask() override = default;
|
||||
|
||||
virtual void executeTask() override;
|
||||
|
|
@ -68,6 +68,7 @@ class BaseEntityLoadTask : public Task {
|
|||
private:
|
||||
BaseEntity* m_entity;
|
||||
Net::Mode m_mode;
|
||||
bool m_force_reload = false;
|
||||
NetJob::Ptr m_task;
|
||||
};
|
||||
} // namespace Meta
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include "Index.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "JsonFormat.h"
|
||||
#include "QObjectPtr.h"
|
||||
#include "VersionList.h"
|
||||
|
|
@ -135,7 +136,7 @@ void Index::connectVersionList(const int row, const VersionList::Ptr& list)
|
|||
|
||||
Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mode mode, bool force)
|
||||
{
|
||||
if (mode == Net::Mode::Offline) {
|
||||
if (mode == Net::Mode::Offline || !APPLICATION->settings()->get("MetaRefreshOnLaunch").toBool()) {
|
||||
return get(uid, version)->loadTask(mode);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(
|
|||
setObjectName("Version list: " + uid);
|
||||
}
|
||||
|
||||
Task::Ptr VersionList::getLoadTask()
|
||||
Task::Ptr VersionList::getLoadTask(bool forceReload)
|
||||
{
|
||||
auto loadTask = makeShared<SequentialTask>(tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
|
||||
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online));
|
||||
loadTask->addTask(this->loadTask(Net::Mode::Online));
|
||||
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online, forceReload));
|
||||
loadTask->addTask(this->loadTask(Net::Mode::Online, forceReload));
|
||||
return loadTask;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class VersionList : public BaseVersionList, public BaseEntity {
|
|||
enum Roles { UidRole = Qt::UserRole + 100, TimeRole, RequiresRole, VersionPtrRole };
|
||||
|
||||
bool isLoaded() override;
|
||||
Task::Ptr getLoadTask() override;
|
||||
Task::Ptr getLoadTask(bool forceReload = false) override;
|
||||
const BaseVersion::Ptr at(int i) const override;
|
||||
int count() const override;
|
||||
void sortVersions() override;
|
||||
|
|
|
|||
|
|
@ -349,7 +349,8 @@ void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext,
|
|||
QStringList& jars,
|
||||
QStringList& nativeJars,
|
||||
const QString& overridePath,
|
||||
const QString& tempPath) const
|
||||
const QString& tempPath,
|
||||
bool addJarMods) const
|
||||
{
|
||||
QStringList native32, native64;
|
||||
jars.clear();
|
||||
|
|
@ -360,7 +361,7 @@ void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext,
|
|||
// NOTE: order is important here, add main jar last to the lists
|
||||
if (m_mainJar) {
|
||||
// FIXME: HACK!! jar modding is weird and unsystematic!
|
||||
if (m_jarMods.size()) {
|
||||
if (m_jarMods.size() && addJarMods) {
|
||||
QDir tempDir(tempPath);
|
||||
jars.append(tempDir.absoluteFilePath("minecraft.jar"));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,8 @@ class LaunchProfile : public ProblemProvider {
|
|||
QStringList& jars,
|
||||
QStringList& nativeJars,
|
||||
const QString& overridePath,
|
||||
const QString& tempPath) const;
|
||||
const QString& tempPath,
|
||||
bool addJarMods = true) const;
|
||||
bool hasTrait(const QString& trait) const;
|
||||
ProblemSeverity getProblemSeverity() const override;
|
||||
const QList<PatchProblem> getProblems() const override;
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
|
|||
if (sha1.size()) {
|
||||
auto dl = Net::ApiDownload::makeCached(url, entry, options);
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, sha1));
|
||||
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
|
||||
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url << "expected sha1:" << sha1;
|
||||
out.append(dl);
|
||||
} else {
|
||||
out.append(Net::ApiDownload::makeCached(url, entry, options));
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@ void MinecraftInstance::loadSpecificSettings()
|
|||
m_settings->registerOverride(global_settings->getSetting("MinMemAlloc"), memorySetting);
|
||||
m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting);
|
||||
m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting);
|
||||
m_settings->registerOverride(global_settings->getSetting("LowMemWarning"), memorySetting);
|
||||
|
||||
// Native library workarounds
|
||||
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
|
||||
|
|
|
|||
|
|
@ -157,7 +157,8 @@ bool WorldList::resetIcon(int row)
|
|||
return false;
|
||||
World& m = m_worlds[row];
|
||||
if (m.resetIcon()) {
|
||||
emit dataChanged(index(row), index(row), { WorldList::IconFileRole });
|
||||
QModelIndex modelIndex = index(row, NameColumn);
|
||||
emit dataChanged(modelIndex, modelIndex, { WorldList::IconFileRole });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -426,7 +427,7 @@ void WorldList::loadWorldsAsync()
|
|||
m_worlds[row].setSize(size);
|
||||
|
||||
// Notify views
|
||||
QModelIndex modelIndex = index(row);
|
||||
QModelIndex modelIndex = index(row, SizeColumn);
|
||||
emit dataChanged(modelIndex, modelIndex, { SizeRole });
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,26 +25,81 @@ EnsureAvailableMemory::EnsureAvailableMemory(LaunchTask* parent, MinecraftInstan
|
|||
|
||||
void EnsureAvailableMemory::executeTask()
|
||||
{
|
||||
const uint64_t available = HardwareInfo::availableRamMiB();
|
||||
const uint64_t min = m_instance->settings()->get("MinMemAlloc").toUInt();
|
||||
const uint64_t max = m_instance->settings()->get("MaxMemAlloc").toUInt();
|
||||
const uint64_t required = std::max(min, max);
|
||||
#ifdef Q_OS_MACOS
|
||||
QString text;
|
||||
switch (MacOSHardwareInfo::memoryPressureLevel()) {
|
||||
case MacOSHardwareInfo::MemoryPressureLevel::Normal:
|
||||
emitSucceeded();
|
||||
return;
|
||||
case MacOSHardwareInfo::MemoryPressureLevel::Warning:
|
||||
text =
|
||||
tr("The system is under increased memory pressure.\n"
|
||||
"This may lead to lag or slowdowns.\n"
|
||||
"If possible, close other applications before continuing.\n\n"
|
||||
"Launch anyway?");
|
||||
break;
|
||||
case MacOSHardwareInfo::MemoryPressureLevel::Critical:
|
||||
text =
|
||||
tr("Your system is under critical memory pressure.\n"
|
||||
"This may lead to severe slowdowns, crashes or system instability.\n"
|
||||
"It is recommended to close other applications or restart your system.\n\n"
|
||||
"Launch anyway?");
|
||||
break;
|
||||
}
|
||||
|
||||
if (required > available) {
|
||||
auto* dialog = CustomMessageBox::selectable(
|
||||
nullptr, tr("Not enough RAM"),
|
||||
tr("There is not enough RAM available to launch this instance with the current memory settings.\n\n"
|
||||
"Required: %1 MiB\nAvailable: %2 MiB\n\n"
|
||||
"Continue anyway? This may cause slowdowns in the game and your system.")
|
||||
.arg(required)
|
||||
.arg(available),
|
||||
QMessageBox::Icon::Warning, QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No,
|
||||
QMessageBox::StandardButton::No);
|
||||
const auto response = dialog->exec();
|
||||
bool shouldAbort = false;
|
||||
|
||||
if (m_instance->settings()->get("LowMemWarning").toBool()) {
|
||||
auto* dialog = CustomMessageBox::selectable(nullptr, tr("High memory pressure"), text, QMessageBox::Icon::Warning,
|
||||
QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No,
|
||||
QMessageBox::StandardButton::No);
|
||||
|
||||
shouldAbort = dialog->exec() == QMessageBox::No;
|
||||
dialog->deleteLater();
|
||||
}
|
||||
|
||||
const auto message = tr("The system is under high memory pressure");
|
||||
if (shouldAbort) {
|
||||
emit logLine(message, MessageLevel::Fatal);
|
||||
emitFailed(message);
|
||||
return;
|
||||
}
|
||||
|
||||
emit logLine(message, MessageLevel::Warning);
|
||||
emitSucceeded();
|
||||
#else
|
||||
const uint64_t available = HardwareInfo::availableRamMiB();
|
||||
if (available == 0) {
|
||||
// could not read
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t settingMin = m_instance->settings()->get("MinMemAlloc").toUInt();
|
||||
const uint64_t settingMax = m_instance->settings()->get("MaxMemAlloc").toUInt();
|
||||
const uint64_t max = std::max(settingMin, settingMax);
|
||||
|
||||
if (static_cast<double>(max) * 0.9 > static_cast<double>(available)) {
|
||||
bool shouldAbort = false;
|
||||
|
||||
if (m_instance->settings()->get("LowMemWarning").toBool()) {
|
||||
auto* dialog = CustomMessageBox::selectable(
|
||||
nullptr, tr("Low free memory"),
|
||||
tr("There might not be enough free RAM to launch this instance with the current memory settings.\n\n"
|
||||
"Maximum allocated: %1 MiB\nFree: %2 MiB (out of %3 MiB total)\n\n"
|
||||
"Launch anyway? This may cause slowdowns in the game and your system.")
|
||||
.arg(max)
|
||||
.arg(available)
|
||||
.arg(HardwareInfo::totalRamMiB()),
|
||||
QMessageBox::Icon::Warning, QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No,
|
||||
QMessageBox::StandardButton::No);
|
||||
|
||||
shouldAbort = dialog->exec() == QMessageBox::No;
|
||||
dialog->deleteLater();
|
||||
}
|
||||
|
||||
const auto message = tr("Not enough RAM available to launch this instance");
|
||||
if (response == QMessageBox::No) {
|
||||
if (shouldAbort) {
|
||||
emit logLine(message, MessageLevel::Fatal);
|
||||
emitFailed(message);
|
||||
return;
|
||||
|
|
@ -54,4 +109,5 @@ void EnsureAvailableMemory::executeTask()
|
|||
}
|
||||
|
||||
emitSucceeded();
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,16 +27,27 @@ void EnsureOfflineLibraries::executeTask()
|
|||
{
|
||||
const auto profile = m_instance->getPackProfile()->getProfile();
|
||||
QStringList allJars;
|
||||
profile->getLibraryFiles(m_instance->runtimeContext(), allJars, allJars, m_instance->getLocalLibraryPath(), m_instance->binRoot());
|
||||
profile->getLibraryFiles(m_instance->runtimeContext(), allJars, allJars, m_instance->getLocalLibraryPath(), m_instance->binRoot(),
|
||||
false);
|
||||
|
||||
QStringList missing;
|
||||
for (const auto& jar : allJars) {
|
||||
if (!QFileInfo::exists(jar)) {
|
||||
emit logLine(tr("This instance cannot be launched because some libraries are missing or have not been downloaded yet. Please "
|
||||
"try again in online mode with a working Internet connection"),
|
||||
MessageLevel::Fatal);
|
||||
emitFailed("Required libraries are missing");
|
||||
return;
|
||||
missing.append(jar);
|
||||
}
|
||||
}
|
||||
|
||||
emitSucceeded();
|
||||
if (missing.isEmpty()) {
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
|
||||
emit logLine("Missing libraries:", MessageLevel::Error);
|
||||
for (const auto& jar : missing) {
|
||||
emit logLine(" " + jar, MessageLevel::Error);
|
||||
}
|
||||
emit logLine(tr("\nThis instance cannot be launched because some libraries are missing or have not been downloaded yet. Please "
|
||||
"try again in online mode with a working Internet connection"),
|
||||
MessageLevel::Fatal);
|
||||
emitFailed("Required libraries are missing");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,13 +61,19 @@ void PrintInstanceInfo::executeTask()
|
|||
auto instance = m_parent->instance();
|
||||
QStringList log;
|
||||
|
||||
log << "";
|
||||
log << "OS: " + QString("%1 | %2 | %3").arg(QSysInfo::prettyProductName(), QSysInfo::kernelType(), QSysInfo::kernelVersion());
|
||||
#ifdef Q_OS_FREEBSD
|
||||
::runSysctlHwModel(log);
|
||||
::runPciconf(log);
|
||||
#else
|
||||
log << "CPU: " + HardwareInfo::cpuInfo();
|
||||
#ifdef Q_OS_MACOS
|
||||
log << "Memory pressure level: " + MacOSHardwareInfo::memoryPressureLevelName();
|
||||
#else
|
||||
log << QString("RAM: %1 MiB (available: %2 MiB)").arg(HardwareInfo::totalRamMiB()).arg(HardwareInfo::availableRamMiB());
|
||||
#endif
|
||||
|
||||
#endif
|
||||
log.append(HardwareInfo::gpuInfo());
|
||||
log << "";
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ bool SkinList::update()
|
|||
auto folderContents = m_dir.entryInfoList();
|
||||
// if there are any untracked files...
|
||||
for (QFileInfo entry : folderContents) {
|
||||
if (!entry.isFile() && entry.suffix() != "png")
|
||||
if (!entry.isFile() || entry.suffix() != "png")
|
||||
continue;
|
||||
|
||||
SkinModel w(entry.absoluteFilePath());
|
||||
|
|
|
|||
|
|
@ -148,9 +148,9 @@ Task::Ptr ResourceAPI::getProjectVersions(VersionSearchArgs&& args, Callback<QVe
|
|||
return netJob;
|
||||
}
|
||||
|
||||
Task::Ptr ResourceAPI::getProjectInfo(ProjectInfoArgs&& args, Callback<ModPlatform::IndexedPack::Ptr>&& callbacks) const
|
||||
Task::Ptr ResourceAPI::getProjectInfo(ProjectInfoArgs&& args, Callback<ModPlatform::IndexedPack::Ptr>&& callbacks, bool askRetry) const
|
||||
{
|
||||
auto [job, response] = getProject(args.pack->addonId.toString());
|
||||
auto [job, response] = getProject(args.pack->addonId.toString(), askRetry);
|
||||
|
||||
QObject::connect(job.get(), &NetJob::succeeded, [this, response, callbacks, args] {
|
||||
auto pack = args.pack;
|
||||
|
|
@ -284,7 +284,7 @@ QString ResourceAPI::mapMCVersionToModrinth(Version v) const
|
|||
return verStr;
|
||||
}
|
||||
|
||||
std::pair<Task::Ptr, QByteArray*> ResourceAPI::getProject(QString addonId) const
|
||||
std::pair<Task::Ptr, QByteArray*> ResourceAPI::getProject(QString addonId, bool askRetry) const
|
||||
{
|
||||
auto project_url_optional = getInfoURL(addonId);
|
||||
if (!project_url_optional.has_value())
|
||||
|
|
@ -293,6 +293,7 @@ std::pair<Task::Ptr, QByteArray*> ResourceAPI::getProject(QString addonId) const
|
|||
auto project_url = project_url_optional.value();
|
||||
|
||||
auto netJob = makeShared<NetJob>(QString("%1::GetProject").arg(addonId), APPLICATION->network());
|
||||
netJob->setAskRetry(askRetry);
|
||||
|
||||
auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(project_url));
|
||||
netJob->addNetAction(action);
|
||||
|
|
|
|||
|
|
@ -115,10 +115,10 @@ class ResourceAPI {
|
|||
public slots:
|
||||
virtual Task::Ptr searchProjects(SearchArgs&&, Callback<QList<ModPlatform::IndexedPack::Ptr>>&&) const;
|
||||
|
||||
virtual std::pair<Task::Ptr, QByteArray*> getProject(QString addonId) const;
|
||||
virtual std::pair<Task::Ptr, QByteArray*> getProject(QString addonId, bool askRetry = true) const;
|
||||
virtual std::pair<Task::Ptr, QByteArray*> getProjects(QStringList addonIds) const = 0;
|
||||
|
||||
virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback<ModPlatform::IndexedPack::Ptr>&&) const;
|
||||
virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback<ModPlatform::IndexedPack::Ptr>&&, bool askRetry = true) const;
|
||||
Task::Ptr getProjectVersions(VersionSearchArgs&& args, Callback<QVector<ModPlatform::IndexedVersion>>&& callbacks) const;
|
||||
virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, Callback<ModPlatform::IndexedVersion>&&) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@
|
|||
#include "Validator.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QFile>
|
||||
|
||||
namespace Net {
|
||||
class ChecksumValidator : public Validator {
|
||||
|
|
@ -69,10 +68,10 @@ class ChecksumValidator : public Validator {
|
|||
return true;
|
||||
}
|
||||
|
||||
auto validate(QNetworkReply&) -> bool override
|
||||
auto validate(QNetworkReply& reply) -> bool override
|
||||
{
|
||||
if (m_expected.size() && m_expected != hash()) {
|
||||
qWarning() << "Checksum mismatch, download is bad.";
|
||||
if (!m_expected.isEmpty() && m_expected != hash()) {
|
||||
qWarning() << "Checksum mismatch for URL:" << reply.url().toString() << "expected:" << m_expected << "got:" << hash();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -69,11 +69,15 @@ void NetJob::executeNextSubTask()
|
|||
// We're finished, check for failures and retry if we can (up to 3 times)
|
||||
if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) {
|
||||
m_try += 1;
|
||||
while (!m_failed.isEmpty()) {
|
||||
auto task = m_failed.take(*m_failed.keyBegin());
|
||||
m_done.remove(task.get());
|
||||
m_queue.enqueue(task);
|
||||
}
|
||||
m_failed.removeIf([this](QHash<Task*, Task::Ptr>::iterator task) {
|
||||
// there is no point in retying on 404 Not Found
|
||||
if (static_cast<Net::NetRequest*>(task->get())->replyStatusCode() == 404) {
|
||||
return false;
|
||||
}
|
||||
m_done.remove(task->get());
|
||||
m_queue.enqueue(*task);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
ConcurrentTask::executeNextSubTask();
|
||||
}
|
||||
|
|
@ -100,13 +104,18 @@ auto NetJob::canAbort() const -> bool
|
|||
|
||||
auto NetJob::abort() -> bool
|
||||
{
|
||||
bool fullyAborted = true;
|
||||
|
||||
// fail all downloads on the queue
|
||||
for (auto task : m_queue)
|
||||
m_failed.insert(task.get(), task);
|
||||
m_queue.clear();
|
||||
|
||||
if (m_doing.isEmpty()) {
|
||||
// no downloads to abort, NetJob is not running
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fullyAborted = true;
|
||||
|
||||
// abort active downloads
|
||||
auto toKill = m_doing.values();
|
||||
for (auto part : toKill) {
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ auto ImgurUpload::Sink::finalize(QNetworkReply&) -> Task::State
|
|||
Net::NetRequest::Ptr ImgurUpload::make(ScreenShot::Ptr m_shot)
|
||||
{
|
||||
auto up = makeShared<ImgurUpload>(m_shot->m_file);
|
||||
up->m_url = std::move(BuildConfig.IMGUR_BASE_URL + "image");
|
||||
up->m_url = BuildConfig.IMGUR_BASE_URL + "image";
|
||||
up->m_sink.reset(new Sink(m_shot));
|
||||
up->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(QList<Net::HeaderPair>{
|
||||
{ "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { "Accept", "application/json" } }));
|
||||
|
|
|
|||
|
|
@ -48,6 +48,13 @@ Task::Task(bool show_debug) : m_show_debug(show_debug)
|
|||
setAutoDelete(false);
|
||||
}
|
||||
|
||||
Task::~Task()
|
||||
{
|
||||
if (isRunning()) {
|
||||
qCWarning(taskLogC) << "Task" << describe() << "disposed while running!";
|
||||
}
|
||||
}
|
||||
|
||||
void Task::setStatus(const QString& new_status)
|
||||
{
|
||||
if (m_status != new_status) {
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class Task : public QObject, public QRunnable {
|
|||
|
||||
public:
|
||||
explicit Task(bool show_debug_log = true);
|
||||
virtual ~Task() = default;
|
||||
~Task() override;
|
||||
|
||||
bool isRunning() const;
|
||||
bool isFinished() const;
|
||||
|
|
@ -165,7 +165,7 @@ class Task : public QObject, public QRunnable {
|
|||
//! used by external code to ask the task to abort
|
||||
virtual bool abort()
|
||||
{
|
||||
if (canAbort())
|
||||
if (canAbort() && isRunning())
|
||||
emitAborted();
|
||||
return canAbort();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@
|
|||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
#include "ui/dialogs/NewsDialog.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui/dialogs/skins/SkinManageDialog.h"
|
||||
#include "ui/instanceview/InstanceDelegate.h"
|
||||
#include "ui/instanceview/InstanceProxyModel.h"
|
||||
#include "ui/instanceview/InstanceView.h"
|
||||
|
|
@ -180,7 +181,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||
ui->instanceToolBar->insertSeparator(ui->actionLaunchInstance);
|
||||
|
||||
// restore the instance toolbar settings
|
||||
auto const setting_name = QString("WideBarVisibility_%1").arg(ui->instanceToolBar->objectName());
|
||||
const auto setting_name = QString("WideBarVisibility_%1").arg(ui->instanceToolBar->objectName());
|
||||
instanceToolbarSetting = APPLICATION->settings()->getOrRegisterSetting(setting_name);
|
||||
|
||||
ui->instanceToolBar->setVisibilityState(QByteArray::fromBase64(instanceToolbarSetting->get().toString().toUtf8()));
|
||||
|
|
@ -396,6 +397,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||
// Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit.
|
||||
// Template hell sucks...
|
||||
connect(APPLICATION->accounts(), &AccountList::defaultAccountChanged, [this] { defaultAccountChanged(); });
|
||||
connect(APPLICATION->accounts(), &AccountList::listActivityChanged, [this] { defaultAccountChanged(); });
|
||||
connect(APPLICATION->accounts(), &AccountList::listChanged, [this] { defaultAccountChanged(); });
|
||||
|
||||
// Show initial account
|
||||
|
|
@ -653,6 +655,9 @@ void MainWindow::repopulateAccountsMenu()
|
|||
|
||||
auto accounts = APPLICATION->accounts();
|
||||
MinecraftAccountPtr defaultAccount = accounts->defaultAccount();
|
||||
|
||||
bool canChangeSkin = defaultAccount && (defaultAccount->accountType() == AccountType::MSA) && !defaultAccount->isActive();
|
||||
ui->actionManageSkins->setEnabled(canChangeSkin);
|
||||
|
||||
QString active_profileId = "";
|
||||
if (defaultAccount) {
|
||||
|
|
@ -709,6 +714,7 @@ void MainWindow::repopulateAccountsMenu()
|
|||
connect(ui->actionNoDefaultAccount, &QAction::triggered, this, &MainWindow::changeActiveAccount);
|
||||
|
||||
ui->accountsMenu->addSeparator();
|
||||
ui->accountsMenu->addAction(ui->actionManageSkins);
|
||||
ui->accountsMenu->addAction(ui->actionManageAccounts);
|
||||
|
||||
accountsButtonMenu->addActions(ui->accountsMenu->actions());
|
||||
|
|
@ -942,9 +948,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
|
|||
QUrl local_url;
|
||||
if (!url.isLocalFile()) { // download the remote resource and identify
|
||||
|
||||
const bool isExternalURLImport =
|
||||
(url.host().toLower() == "import") ||
|
||||
(url.path().startsWith("/import", Qt::CaseInsensitive));
|
||||
const bool isExternalURLImport = (url.host().toLower() == "import") || (url.path().startsWith("/import", Qt::CaseInsensitive));
|
||||
|
||||
QUrl dl_url;
|
||||
if (url.scheme() == "curseforge" || (url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME && url.host() == "install")) {
|
||||
|
|
@ -952,7 +956,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
|
|||
// format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
|
||||
// format of url binaryname://install?platform=curseforge&addonId=IDHERE&fileId=IDHERE
|
||||
QUrlQuery query(url);
|
||||
|
||||
|
||||
// check if this is a binaryname:// url
|
||||
if (url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME) {
|
||||
// check this is an curseforge platform request
|
||||
|
|
@ -1015,8 +1019,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
|
|||
receivedData.insert(it->first, it->second);
|
||||
emit APPLICATION->oauthReplyRecieved(receivedData);
|
||||
continue;
|
||||
} else if ((url.scheme() == "prismlauncher" || url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME)
|
||||
&& isExternalURLImport) {
|
||||
} else if ((url.scheme() == "prismlauncher" || url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME) && isExternalURLImport) {
|
||||
// PrismLauncher URL protocol modpack import
|
||||
// works for any prism fork
|
||||
// preferred import format: prismlauncher://import?url=ENCODED
|
||||
|
|
@ -1035,7 +1038,6 @@ void MainWindow::processURLs(QList<QUrl> urls)
|
|||
|
||||
// alternative import format: prismlauncher://import/ENCODED
|
||||
if (encodedTarget.isEmpty()) {
|
||||
|
||||
QString p = path;
|
||||
|
||||
if (p.startsWith("/import/", Qt::CaseInsensitive)) {
|
||||
|
|
@ -1050,12 +1052,9 @@ void MainWindow::processURLs(QList<QUrl> urls)
|
|||
}
|
||||
|
||||
if (encodedTarget.isEmpty()) {
|
||||
CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("Error"),
|
||||
tr("Invalid import link: missing 'url' parameter."),
|
||||
QMessageBox::Critical
|
||||
)->show();
|
||||
CustomMessageBox::selectable(this, tr("Error"), tr("Invalid import link: missing 'url' parameter."),
|
||||
QMessageBox::Critical)
|
||||
->show();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -1065,23 +1064,15 @@ void MainWindow::processURLs(QList<QUrl> urls)
|
|||
|
||||
// Validate: only allow http(s)
|
||||
if (!target.isValid() || (target.scheme() != "https" && target.scheme() != "http")) {
|
||||
CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("Error"),
|
||||
tr("Invalid import link: URL must be http(s)."),
|
||||
QMessageBox::Critical
|
||||
)->show();
|
||||
CustomMessageBox::selectable(this, tr("Error"), tr("Invalid import link: URL must be http(s)."), QMessageBox::Critical)
|
||||
->show();
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto res = QMessageBox::question(
|
||||
this,
|
||||
tr("Install modpack"),
|
||||
tr("Do you want to download and import a modpack from:\n%1\n\nURL:\n%2")
|
||||
.arg(target.host(), target.toString()),
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::Yes
|
||||
);
|
||||
this, tr("Install modpack"),
|
||||
tr("Do you want to download and import a modpack from:\n%1\n\nURL:\n%2").arg(target.host(), target.toString()),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
if (res != QMessageBox::Yes) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1396,6 +1387,16 @@ void MainWindow::on_actionEditInstance_triggered()
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_actionManageSkins_triggered()
|
||||
{
|
||||
auto account = APPLICATION->accounts()->defaultAccount();
|
||||
|
||||
if (account && (account->accountType() == AccountType::MSA) && !account->isActive()) {
|
||||
SkinManageDialog dialog(this, account);
|
||||
dialog.exec();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_actionManageAccounts_triggered()
|
||||
{
|
||||
APPLICATION->ShowGlobalSettings(this, "accounts");
|
||||
|
|
|
|||
|
|
@ -130,6 +130,8 @@ class MainWindow : public QMainWindow {
|
|||
|
||||
void on_actionSettings_triggered();
|
||||
|
||||
void on_actionManageSkins_triggered();
|
||||
|
||||
void on_actionManageAccounts_triggered();
|
||||
|
||||
void on_actionReportBug_triggered();
|
||||
|
|
|
|||
|
|
@ -322,6 +322,14 @@
|
|||
<enum>QAction::PreferencesRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionManageSkins">
|
||||
<property name="icon">
|
||||
<iconset theme="settings"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Manage &Skins...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionManageAccounts">
|
||||
<property name="icon">
|
||||
<iconset theme="accounts"/>
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ InstallLoaderDialog::InstallLoaderDialog(PackProfile* profile, const QString& ui
|
|||
buttonLayout->setContentsMargins(0, 0, 6, 6);
|
||||
#endif
|
||||
auto refreshButton = new QPushButton(tr("&Refresh"), this);
|
||||
connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(); });
|
||||
connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(true); });
|
||||
buttonLayout->addWidget(refreshButton);
|
||||
|
||||
buttons->setOrientation(Qt::Horizontal);
|
||||
|
|
|
|||
|
|
@ -252,10 +252,7 @@ void ProgressDialog::changeStepProgress(TaskStepProgress const& task_progress)
|
|||
task_bar->setValue(mapped_current);
|
||||
task_bar->setStatus(task_progress.status);
|
||||
task_bar->setDetails(task_progress.details);
|
||||
|
||||
if (task_progress.isDone()) {
|
||||
task_bar->setVisible(false);
|
||||
}
|
||||
task_bar->setVisible(!task_progress.isDone());
|
||||
}
|
||||
|
||||
void ProgressDialog::changeProgress(qint64 current, qint64 total)
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ BaseVersion::Ptr VersionSelectDialog::selectedVersion() const
|
|||
|
||||
void VersionSelectDialog::on_refreshButton_clicked()
|
||||
{
|
||||
m_versionWidget->loadList();
|
||||
m_versionWidget->loadList(true);
|
||||
}
|
||||
|
||||
void VersionSelectDialog::setExactFilter(BaseVersionList::ModelRoles role, QString filter)
|
||||
|
|
|
|||
|
|
@ -121,8 +121,8 @@ class InstallJavaPage : public QWidget, public BasePage {
|
|||
void selectSearch() { javaVersionSelect->selectSearch(); }
|
||||
void loadList()
|
||||
{
|
||||
majorVersionSelect->loadList();
|
||||
javaVersionSelect->loadList();
|
||||
majorVersionSelect->loadList(true);
|
||||
javaVersionSelect->loadList(true);
|
||||
}
|
||||
|
||||
public slots:
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ VersionList::VersionList(Meta::Version::Ptr version, QObject* parent) : BaseVers
|
|||
sortVersions();
|
||||
}
|
||||
|
||||
Task::Ptr VersionList::getLoadTask()
|
||||
Task::Ptr VersionList::getLoadTask(bool forceReload)
|
||||
{
|
||||
auto task = m_version->loadTask(Net::Mode::Online);
|
||||
auto task = m_version->loadTask(Net::Mode::Online, forceReload);
|
||||
connect(task.get(), &Task::finished, this, &VersionList::sortVersions);
|
||||
return task;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class VersionList : public BaseVersionList {
|
|||
public:
|
||||
explicit VersionList(Meta::Version::Ptr m_version, QObject* parent = 0);
|
||||
|
||||
Task::Ptr getLoadTask() override;
|
||||
Task::Ptr getLoadTask(bool forceReload = false) override;
|
||||
bool isLoaded() override;
|
||||
const BaseVersion::Ptr at(int i) const override;
|
||||
int count() const override;
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ void APIPage::loadSettings()
|
|||
ui->msaClientID->setText(msaClientID);
|
||||
QString metaURL = s->get("MetaURLOverride").toString();
|
||||
ui->metaURL->setText(metaURL);
|
||||
ui->metaRefreshOnLaunchCB->setCheckState(s->get("MetaRefreshOnLaunch").toBool() ? Qt::Checked : Qt::Unchecked);
|
||||
QString resourceURL = s->get("ResourceURLOverride").toString();
|
||||
ui->resourceURL->setText(resourceURL);
|
||||
QString fmlLibsURL = s->get("LegacyFMLLibsURLOverride").toString();
|
||||
|
|
@ -194,6 +195,7 @@ void APIPage::applySettings()
|
|||
|
||||
s->set("FallbackMRBlockedMods", ui->FallbackMRBlockedMods->checkState());
|
||||
s->set("MetaURLOverride", metaURL.toString());
|
||||
s->set("MetaRefreshOnLaunch", ui->metaRefreshOnLaunchCB->checkState() == Qt::Checked);
|
||||
s->set("ResourceURLOverride", resourceURL.toString());
|
||||
s->set("LegacyFMLLibsURLOverride", fmlLibsURL.toString());
|
||||
QString flameKey = ui->flameKey->text();
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>-262</y>
|
||||
<width>820</width>
|
||||
<height>908</height>
|
||||
<y>0</y>
|
||||
<width>825</width>
|
||||
<height>1236</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
|
|
@ -126,6 +126,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="metaRefreshOnLaunchCB">
|
||||
<property name="text">
|
||||
<string>Refresh on launch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
|||
|
|
@ -202,23 +202,24 @@ bool ManagedPackPage::runUpdateTask(InstanceTask* task)
|
|||
|
||||
unique_qobject_ptr<Task> wrapped_task(APPLICATION->instances()->wrapInstanceTask(task));
|
||||
|
||||
connect(task, &Task::failed,
|
||||
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
||||
connect(task, &Task::succeeded, [this, task]() {
|
||||
connect(wrapped_task.get(), &Task::failed,
|
||||
[this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
||||
connect(wrapped_task.get(), &Task::succeeded, [this, task]() {
|
||||
QStringList warnings = task->warnings();
|
||||
if (warnings.count())
|
||||
if (warnings.count()) {
|
||||
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
|
||||
}
|
||||
});
|
||||
connect(task, &Task::aborted, [this] {
|
||||
connect(wrapped_task.get(), &Task::aborted, [this] {
|
||||
CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)
|
||||
->show();
|
||||
});
|
||||
|
||||
ProgressDialog loadDialog(this);
|
||||
loadDialog.setSkipButton(true, tr("Abort"));
|
||||
loadDialog.execWithTask(task);
|
||||
loadDialog.execWithTask(wrapped_task.get());
|
||||
|
||||
return task->wasSuccessful();
|
||||
return wrapped_task->wasSuccessful();
|
||||
}
|
||||
|
||||
void ManagedPackPage::suggestVersion()
|
||||
|
|
@ -260,14 +261,16 @@ void ModrinthManagedPackPage::parseManagedPack()
|
|||
qDebug() << "Parsing Modrinth pack";
|
||||
|
||||
// No need for the extra work because we already have everything we need.
|
||||
if (m_loaded)
|
||||
if (m_loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_fetch_job && m_fetch_job->isRunning())
|
||||
if (m_fetch_job && m_fetch_job->isRunning()) {
|
||||
m_fetch_job->abort();
|
||||
}
|
||||
|
||||
ResourceAPI::Callback<QVector<ModPlatform::IndexedVersion>> callbacks{};
|
||||
m_pack = { m_inst->getManagedPackID() };
|
||||
m_pack = { .addonId = m_inst->getManagedPackID() };
|
||||
|
||||
// Use default if no callbacks are set
|
||||
callbacks.on_succeed = [this](auto& doc) {
|
||||
|
|
@ -284,8 +287,9 @@ void ModrinthManagedPackPage::parseManagedPack()
|
|||
|
||||
// NOTE: the id from version isn't the same id in the modpack format spec...
|
||||
// e.g. HexMC's 4.4.0 has versionId 4.0.0 in the modpack index..............
|
||||
if (version.version == m_inst->getManagedPackVersionName())
|
||||
if (version.version == m_inst->getManagedPackVersionName()) {
|
||||
name = tr("%1 (Current)").arg(name);
|
||||
}
|
||||
|
||||
ui->versionsComboBox->addItem(name, version.fileId);
|
||||
}
|
||||
|
|
@ -294,10 +298,14 @@ void ModrinthManagedPackPage::parseManagedPack()
|
|||
|
||||
m_loaded = true;
|
||||
};
|
||||
callbacks.on_fail = [this](QString reason, int) { setFailState(); };
|
||||
callbacks.on_fail = [this](const QString& /*reason*/, int) { setFailState(); };
|
||||
callbacks.on_abort = [this]() { setFailState(); };
|
||||
m_fetch_job = m_api.getProjectVersions(
|
||||
{ std::make_shared<ModPlatform::IndexedPack>(m_pack), {}, {}, ModPlatform::ResourceType::Modpack }, std::move(callbacks));
|
||||
m_fetch_job = m_api.getProjectVersions({ .pack = std::make_shared<ModPlatform::IndexedPack>(m_pack),
|
||||
.mcVersions = {},
|
||||
.loaders = {},
|
||||
.resourceType = ModPlatform::ResourceType::Modpack,
|
||||
.includeChangelog = true },
|
||||
std::move(callbacks));
|
||||
|
||||
ui->changelogTextBrowser->setText(tr("Fetching changelogs..."));
|
||||
|
||||
|
|
@ -406,14 +414,16 @@ void FlameManagedPackPage::parseManagedPack()
|
|||
}
|
||||
|
||||
// No need for the extra work because we already have everything we need.
|
||||
if (m_loaded)
|
||||
if (m_loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_fetch_job && m_fetch_job->isRunning())
|
||||
if (m_fetch_job && m_fetch_job->isRunning()) {
|
||||
m_fetch_job->abort();
|
||||
}
|
||||
|
||||
QString id = m_inst->getManagedPackID();
|
||||
m_pack = { id };
|
||||
m_pack = { .addonId = id };
|
||||
|
||||
ResourceAPI::Callback<QVector<ModPlatform::IndexedVersion>> callbacks{};
|
||||
|
||||
|
|
@ -430,8 +440,9 @@ void FlameManagedPackPage::parseManagedPack()
|
|||
for (const auto& version : m_pack.versions) {
|
||||
QString name = version.getVersionDisplayString();
|
||||
|
||||
if (version.fileId == m_inst->getManagedPackVersionID().toInt())
|
||||
if (version.fileId == m_inst->getManagedPackVersionID().toInt()) {
|
||||
name = tr("%1 (Current)").arg(name);
|
||||
}
|
||||
|
||||
ui->versionsComboBox->addItem(name, QVariant(version.fileId));
|
||||
}
|
||||
|
|
@ -440,10 +451,14 @@ void FlameManagedPackPage::parseManagedPack()
|
|||
|
||||
m_loaded = true;
|
||||
};
|
||||
callbacks.on_fail = [this](QString reason, int) { setFailState(); };
|
||||
callbacks.on_fail = [this](const QString& /*reason*/, int) { setFailState(); };
|
||||
callbacks.on_abort = [this]() { setFailState(); };
|
||||
m_fetch_job = m_api.getProjectVersions(
|
||||
{ std::make_shared<ModPlatform::IndexedPack>(m_pack), {}, {}, ModPlatform::ResourceType::Modpack }, std::move(callbacks));
|
||||
m_fetch_job = m_api.getProjectVersions({ .pack = std::make_shared<ModPlatform::IndexedPack>(m_pack),
|
||||
.mcVersions = {},
|
||||
.loaders = {},
|
||||
.resourceType = ModPlatform::ResourceType::Modpack,
|
||||
.includeChangelog = true },
|
||||
std::move(callbacks));
|
||||
|
||||
m_fetch_job->start();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
#include "McClient.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QTcpSocket>
|
||||
#include <utility>
|
||||
|
||||
#include <Exception.h>
|
||||
#include "Exception.h"
|
||||
#include "Json.h"
|
||||
#include "McClient.h"
|
||||
|
||||
// 7 first bits
|
||||
#define SEGMENT_BITS 0x7F
|
||||
// last bit
|
||||
#define CONTINUE_BIT 0x80
|
||||
|
||||
McClient::McClient(QObject* parent, QString domain, QString ip, short port) : QObject(parent), m_domain(domain), m_ip(ip), m_port(port) {}
|
||||
McClient::McClient(QObject* parent, QString domain, QString ip, const uint16_t port)
|
||||
: QObject(parent), m_domain(std::move(domain)), m_ip(std::move(ip)), m_port(port)
|
||||
{}
|
||||
|
||||
void McClient::getStatusData()
|
||||
{
|
||||
|
|
@ -33,13 +32,12 @@ void McClient::getStatusData()
|
|||
void McClient::sendRequest()
|
||||
{
|
||||
QByteArray data;
|
||||
writeVarInt(data, 0x00); // packet ID
|
||||
writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1)
|
||||
writeVarInt(data, m_domain.size()); // server address length
|
||||
writeString(data, m_domain.toStdString()); // server address
|
||||
writeFixedInt(data, m_port, 2); // server port
|
||||
writeVarInt(data, 0x01); // next state
|
||||
writePacketToSocket(data); // send handshake packet
|
||||
writeVarInt(data, 0x00); // packet ID
|
||||
writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1)
|
||||
writeString(data, m_domain); // server address
|
||||
writeUInt16(data, m_port); // server port
|
||||
writeVarInt(data, 0x01); // next state
|
||||
writePacketToSocket(data); // send handshake packet
|
||||
|
||||
writeVarInt(data, 0x00); // packet ID
|
||||
writePacketToSocket(data); // send status packet
|
||||
|
|
@ -47,17 +45,17 @@ void McClient::sendRequest()
|
|||
|
||||
void McClient::readRawResponse()
|
||||
{
|
||||
if (m_responseReadState == 2) {
|
||||
if (m_responseReadState == ResponseReadState::Finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_resp.append(m_socket.readAll());
|
||||
if (m_responseReadState == 0 && m_resp.size() >= 5) {
|
||||
if (m_responseReadState == ResponseReadState::Waiting && m_resp.size() >= 5) {
|
||||
m_wantedRespLength = readVarInt(m_resp);
|
||||
m_responseReadState = 1;
|
||||
m_responseReadState = ResponseReadState::GotLength;
|
||||
}
|
||||
|
||||
if (m_responseReadState == 1 && m_resp.size() >= m_wantedRespLength) {
|
||||
if (m_responseReadState == ResponseReadState::GotLength && m_resp.size() >= m_wantedRespLength) {
|
||||
if (m_resp.size() > m_wantedRespLength) {
|
||||
qDebug().nospace() << "Warning: Packet length doesn't match actual packet size (" << m_wantedRespLength << " expected vs "
|
||||
<< m_resp.size() << " received)";
|
||||
|
|
@ -67,7 +65,7 @@ void McClient::readRawResponse()
|
|||
} catch (const Exception& e) {
|
||||
emitFail(e.cause());
|
||||
}
|
||||
m_responseReadState = 2;
|
||||
m_responseReadState = ResponseReadState::Finished;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +73,7 @@ void McClient::parseResponse()
|
|||
{
|
||||
qDebug() << "Received response successfully";
|
||||
|
||||
int packetID = readVarInt(m_resp);
|
||||
const int packetID = readVarInt(m_resp);
|
||||
if (packetID != 0x00) {
|
||||
throw Exception(QString("Packet ID doesn't match expected value (0x00 vs 0x%1)").arg(packetID, 0, 16));
|
||||
}
|
||||
|
|
@ -84,7 +82,7 @@ void McClient::parseResponse()
|
|||
|
||||
// 'resp' should now be the JSON string
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc = Json::parseUntilGarbage(m_resp, &parseError);
|
||||
const QJsonDocument doc = Json::parseUntilGarbage(m_resp, &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qDebug() << "Failed to parse JSON:" << parseError.errorString();
|
||||
emitFail(parseError.errorString());
|
||||
|
|
@ -93,18 +91,23 @@ void McClient::parseResponse()
|
|||
emitSucceed(doc.object());
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(*-signed-bitwise)
|
||||
|
||||
// From https://wiki.vg/Protocol#VarInt_and_VarLong
|
||||
constexpr uint8_t g_varIntValueMask = 0x7F;
|
||||
constexpr uint8_t g_varIntContinue = 0x80;
|
||||
|
||||
void McClient::writeVarInt(QByteArray& data, int value)
|
||||
{
|
||||
while ((value & ~SEGMENT_BITS)) { // check if the value is too big to fit in 7 bits
|
||||
while ((value & ~g_varIntValueMask) != 0) { // check if the value is too big to fit in 7 bits
|
||||
// Write 7 bits
|
||||
data.append((value & SEGMENT_BITS) | CONTINUE_BIT);
|
||||
data.append(static_cast<uint8_t>((value & ~g_varIntValueMask) | g_varIntContinue)); // NOLINT(*-narrowing-conversions)
|
||||
|
||||
// Erase theses 7 bits from the value to write
|
||||
// Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
|
||||
value >>= 7;
|
||||
}
|
||||
data.append(value);
|
||||
data.append(static_cast<uint8_t>(value)); // NOLINT(*-narrowing-conversions)
|
||||
}
|
||||
|
||||
// From https://wiki.vg/Protocol#VarInt_and_VarLong
|
||||
|
|
@ -112,53 +115,56 @@ int McClient::readVarInt(QByteArray& data)
|
|||
{
|
||||
int value = 0;
|
||||
int position = 0;
|
||||
char currentByte;
|
||||
|
||||
while (position < 32) {
|
||||
currentByte = readByte(data);
|
||||
value |= (currentByte & SEGMENT_BITS) << position;
|
||||
const uint8_t currentByte = readByte(data);
|
||||
value |= (currentByte & g_varIntValueMask) << position;
|
||||
|
||||
if ((currentByte & CONTINUE_BIT) == 0)
|
||||
if ((currentByte & g_varIntContinue) == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
position += 7;
|
||||
}
|
||||
|
||||
if (position >= 32)
|
||||
if (position >= 32) {
|
||||
throw Exception("VarInt is too big");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
char McClient::readByte(QByteArray& data)
|
||||
// NOLINTEND(*-signed-bitwise)
|
||||
|
||||
uint8_t McClient::readByte(QByteArray& data)
|
||||
{
|
||||
if (data.isEmpty()) {
|
||||
throw Exception("No more bytes to read");
|
||||
}
|
||||
|
||||
char byte = data.at(0);
|
||||
const uint8_t byte = data.at(0);
|
||||
data.remove(0, 1);
|
||||
return byte;
|
||||
}
|
||||
|
||||
// write number with specified size in big endian format
|
||||
void McClient::writeFixedInt(QByteArray& data, int value, int size)
|
||||
void McClient::writeUInt16(QByteArray& data, const uint16_t value)
|
||||
{
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
data.append((value >> (i * 8)) & 0xFF);
|
||||
}
|
||||
QDataStream stream(&data, QIODeviceBase::Append);
|
||||
stream.setByteOrder(QDataStream::BigEndian);
|
||||
stream << value;
|
||||
}
|
||||
|
||||
void McClient::writeString(QByteArray& data, const std::string& value)
|
||||
void McClient::writeString(QByteArray& data, const QString& value)
|
||||
{
|
||||
data.append(value.c_str());
|
||||
writeVarInt(data, static_cast<int32_t>(value.size()));
|
||||
data.append(value.toUtf8());
|
||||
}
|
||||
|
||||
void McClient::writePacketToSocket(QByteArray& data)
|
||||
{
|
||||
// we prefix the packet with its length
|
||||
QByteArray dataWithSize;
|
||||
writeVarInt(dataWithSize, data.size());
|
||||
writeVarInt(dataWithSize, static_cast<int32_t>(data.size()));
|
||||
dataWithSize.append(data);
|
||||
|
||||
// write it to the socket
|
||||
|
|
@ -168,7 +174,7 @@ void McClient::writePacketToSocket(QByteArray& data)
|
|||
data.clear();
|
||||
}
|
||||
|
||||
void McClient::emitFail(QString error)
|
||||
void McClient::emitFail(const QString& error)
|
||||
{
|
||||
qDebug() << "Minecraft server ping for status error:" << error;
|
||||
emit failed(error);
|
||||
|
|
@ -177,6 +183,6 @@ void McClient::emitFail(QString error)
|
|||
|
||||
void McClient::emitSucceed(QJsonObject data)
|
||||
{
|
||||
emit succeeded(data);
|
||||
emit succeeded(std::move(data));
|
||||
emit finished();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
#include <QFuture>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include <Exception.h>
|
||||
|
||||
// Client for the Minecraft protocol
|
||||
class McClient : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
QString m_domain;
|
||||
QString m_ip;
|
||||
short m_port;
|
||||
QTcpSocket m_socket;
|
||||
|
||||
// 0: did not start reading the response yet
|
||||
// 1: read the response length, still reading the response
|
||||
// 2: finished reading the response
|
||||
unsigned m_responseReadState = 0;
|
||||
unsigned m_wantedRespLength = 0;
|
||||
QByteArray m_resp;
|
||||
|
||||
public:
|
||||
explicit McClient(QObject* parent, QString domain, QString ip, short port);
|
||||
explicit McClient(QObject* parent, QString domain, QString ip, uint16_t port);
|
||||
//! Read status data of the server, and calls the succeeded() signal with the parsed JSON data
|
||||
void getStatusData();
|
||||
|
||||
signals:
|
||||
void succeeded(QJsonObject data);
|
||||
void failed(QString error);
|
||||
void finished();
|
||||
|
||||
private:
|
||||
static uint8_t readByte(QByteArray& data);
|
||||
static int readVarInt(QByteArray& data);
|
||||
static void writeUInt16(QByteArray& data, uint16_t value);
|
||||
static void writeString(QByteArray& data, const QString& value);
|
||||
static void writeVarInt(QByteArray& data, int value);
|
||||
|
||||
private:
|
||||
void sendRequest();
|
||||
//! Accumulate data until we have a full response, then call parseResponse() once
|
||||
void readRawResponse();
|
||||
void parseResponse();
|
||||
|
||||
void writeVarInt(QByteArray& data, int value);
|
||||
int readVarInt(QByteArray& data);
|
||||
char readByte(QByteArray& data);
|
||||
//! write number with specified size in big endian format
|
||||
void writeFixedInt(QByteArray& data, int value, int size);
|
||||
void writeString(QByteArray& data, const std::string& value);
|
||||
|
||||
void writePacketToSocket(QByteArray& data);
|
||||
|
||||
void emitFail(QString error);
|
||||
void emitFail(const QString& error);
|
||||
void emitSucceed(QJsonObject data);
|
||||
|
||||
signals:
|
||||
void succeeded(QJsonObject data);
|
||||
void failed(QString error);
|
||||
void finished();
|
||||
private:
|
||||
enum class ResponseReadState : uint8_t {
|
||||
Waiting,
|
||||
GotLength,
|
||||
Finished
|
||||
};
|
||||
|
||||
QString m_domain;
|
||||
QString m_ip;
|
||||
uint16_t m_port;
|
||||
QTcpSocket m_socket;
|
||||
|
||||
ResponseReadState m_responseReadState = ResponseReadState::Waiting;
|
||||
int32_t m_wantedRespLength = 0;
|
||||
QByteArray m_resp;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@
|
|||
#include <QFileSystemModel>
|
||||
#include <QKeyEvent>
|
||||
#include <QLineEdit>
|
||||
#include <QMap>
|
||||
#include <QMenu>
|
||||
#include <QModelIndex>
|
||||
#include <QMutableListIterator>
|
||||
|
|
@ -53,6 +52,8 @@
|
|||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <Application.h>
|
||||
#include "settings/SettingsObject.h"
|
||||
|
|
@ -70,9 +71,14 @@
|
|||
#include "RWStorage.h"
|
||||
|
||||
class ScreenshotsFSModel : public QFileSystemModel {
|
||||
bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override
|
||||
public:
|
||||
bool canDropMimeData(const QMimeData* data,
|
||||
const Qt::DropAction action,
|
||||
const int row,
|
||||
const int column,
|
||||
const QModelIndex& parent) const override
|
||||
{
|
||||
QUrl root = QUrl::fromLocalFile(rootPath());
|
||||
const QUrl root = QUrl::fromLocalFile(rootPath());
|
||||
// this disables reordering items inside the model
|
||||
// by rejecting drops if the file is already inside the folder
|
||||
if (data->hasUrls()) {
|
||||
|
|
@ -92,8 +98,8 @@ using SharedIconCachePtr = std::shared_ptr<SharedIconCache>;
|
|||
class ThumbnailingResult : public QObject {
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
inline void emitResultsReady(const QString& path) { emit resultsReady(path); }
|
||||
inline void emitResultsFailed(const QString& path) { emit resultsFailed(path); }
|
||||
void emitResultsReady(const QString& path) { emit resultsReady(path); }
|
||||
void emitResultsFailed(const QString& path) { emit resultsFailed(path); }
|
||||
signals:
|
||||
void resultsReady(const QString& path);
|
||||
void resultsFailed(const QString& path);
|
||||
|
|
@ -101,32 +107,32 @@ class ThumbnailingResult : public QObject {
|
|||
|
||||
class ThumbnailRunnable : public QRunnable {
|
||||
public:
|
||||
ThumbnailRunnable(QString path, SharedIconCachePtr cache)
|
||||
ThumbnailRunnable(QString path, SharedIconCachePtr cache) : m_path(std::move(path)), m_cache(std::move(cache)) {}
|
||||
void run() override
|
||||
{
|
||||
m_path = path;
|
||||
m_cache = cache;
|
||||
}
|
||||
void run()
|
||||
{
|
||||
QFileInfo info(m_path);
|
||||
if (info.isDir())
|
||||
const QFileInfo info(m_path);
|
||||
if (info.isDir()) {
|
||||
return;
|
||||
if ((info.suffix().compare("png", Qt::CaseInsensitive) != 0))
|
||||
}
|
||||
if (info.suffix().compare("png", Qt::CaseInsensitive) != 0) {
|
||||
return;
|
||||
if (!m_cache->stale(m_path))
|
||||
}
|
||||
if (!m_cache->stale(m_path)) {
|
||||
return;
|
||||
QImage image(m_path);
|
||||
}
|
||||
const QImage image(m_path);
|
||||
if (image.isNull()) {
|
||||
m_resultEmitter.emitResultsFailed(m_path);
|
||||
qDebug() << "Error loading screenshot (perhaps too large?):" + m_path;
|
||||
return;
|
||||
}
|
||||
QImage small;
|
||||
if (image.width() > image.height())
|
||||
if (image.width() > image.height()) {
|
||||
small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation);
|
||||
else
|
||||
} else {
|
||||
small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation);
|
||||
QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2);
|
||||
}
|
||||
const QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2);
|
||||
QImage square(QSize(256, 256), QImage::Format_ARGB32);
|
||||
square.fill(Qt::transparent);
|
||||
|
||||
|
|
@ -134,7 +140,7 @@ class ThumbnailRunnable : public QRunnable {
|
|||
painter.drawImage(offset, small);
|
||||
painter.end();
|
||||
|
||||
QIcon icon(QPixmap::fromImage(square));
|
||||
const QIcon icon(QPixmap::fromImage(square));
|
||||
m_cache->add(m_path, icon);
|
||||
m_resultEmitter.emitResultsReady(m_path);
|
||||
}
|
||||
|
|
@ -148,59 +154,62 @@ class ThumbnailRunnable : public QRunnable {
|
|||
class FilterModel : public QIdentityProxyModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FilterModel(QObject* parent = 0) : QIdentityProxyModel(parent)
|
||||
explicit FilterModel(QObject* parent = nullptr) : QIdentityProxyModel(parent)
|
||||
{
|
||||
m_thumbnailingPool.setMaxThreadCount(4);
|
||||
m_thumbnailCache = std::make_shared<SharedIconCache>();
|
||||
m_thumbnailCache->add("placeholder", QIcon::fromTheme("screenshot-placeholder"));
|
||||
connect(&watcher, &QFileSystemWatcher::fileChanged, this, &FilterModel::fileChanged);
|
||||
}
|
||||
virtual ~FilterModel()
|
||||
~FilterModel() override
|
||||
{
|
||||
m_thumbnailingPool.clear();
|
||||
if (!m_thumbnailingPool.waitForDone(500))
|
||||
if (!m_thumbnailingPool.waitForDone(500)) {
|
||||
qDebug() << "Thumbnail pool took longer than 500ms to finish";
|
||||
}
|
||||
}
|
||||
virtual QVariant data(const QModelIndex& proxyIndex, int role = Qt::DisplayRole) const
|
||||
QVariant data(const QModelIndex& proxyIndex, const int role = Qt::DisplayRole) const override // NOLINT(*-default-arguments)
|
||||
{
|
||||
auto model = sourceModel();
|
||||
if (!model)
|
||||
return QVariant();
|
||||
const auto* model = sourceModel();
|
||||
if (!model) {
|
||||
return {};
|
||||
}
|
||||
if (role == Qt::DisplayRole || role == Qt::EditRole) {
|
||||
QVariant result = sourceModel()->data(mapToSource(proxyIndex), role);
|
||||
const QVariant result = model->data(mapToSource(proxyIndex), role);
|
||||
static const QRegularExpression s_removeChars("\\.png$");
|
||||
return result.toString().remove(s_removeChars);
|
||||
}
|
||||
if (role == Qt::DecorationRole) {
|
||||
QVariant result = sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole);
|
||||
QString filePath = result.toString();
|
||||
QIcon temp;
|
||||
const QVariant result = model->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole);
|
||||
const QString filePath = result.toString();
|
||||
if (!watched.contains(filePath)) {
|
||||
((QFileSystemWatcher&)watcher).addPath(filePath);
|
||||
((QSet<QString>&)watched).insert(filePath);
|
||||
const_cast<QFileSystemWatcher&>(watcher).addPath(filePath);
|
||||
const_cast<QSet<QString>&>(watched).insert(filePath);
|
||||
}
|
||||
if (m_thumbnailCache->get(filePath, temp)) {
|
||||
if (QIcon temp; m_thumbnailCache->get(filePath, temp)) {
|
||||
return temp;
|
||||
}
|
||||
if (!m_failed.contains(filePath)) {
|
||||
((FilterModel*)this)->thumbnailImage(filePath);
|
||||
const_cast<FilterModel*>(this)->thumbnailImage(filePath);
|
||||
}
|
||||
return (m_thumbnailCache->get("placeholder"));
|
||||
}
|
||||
return sourceModel()->data(mapToSource(proxyIndex), role);
|
||||
return model->data(mapToSource(proxyIndex), role);
|
||||
}
|
||||
virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole)
|
||||
bool setData(const QModelIndex& index, const QVariant& value, const int role = Qt::EditRole) override // NOLINT(*-default-arguments)
|
||||
{
|
||||
auto model = sourceModel();
|
||||
if (!model)
|
||||
auto* model = sourceModel();
|
||||
if (!model) {
|
||||
return false;
|
||||
if (role != Qt::EditRole)
|
||||
}
|
||||
if (role != Qt::EditRole) {
|
||||
return false;
|
||||
}
|
||||
// FIXME: this is a workaround for a bug in QFileSystemModel, where it doesn't
|
||||
// sort after renames
|
||||
{
|
||||
((QFileSystemModel*)model)->setNameFilterDisables(true);
|
||||
((QFileSystemModel*)model)->setNameFilterDisables(false);
|
||||
static_cast<QFileSystemModel*>(model)->setNameFilterDisables(true);
|
||||
static_cast<QFileSystemModel*>(model)->setNameFilterDisables(false);
|
||||
}
|
||||
return model->setData(mapToSource(index), value.toString() + ".png", role);
|
||||
}
|
||||
|
|
@ -208,15 +217,15 @@ class FilterModel : public QIdentityProxyModel {
|
|||
private:
|
||||
void thumbnailImage(QString path)
|
||||
{
|
||||
auto runnable = new ThumbnailRunnable(path, m_thumbnailCache);
|
||||
auto* runnable = new ThumbnailRunnable(std::move(path), m_thumbnailCache);
|
||||
connect(&runnable->m_resultEmitter, &ThumbnailingResult::resultsReady, this, &FilterModel::thumbnailReady);
|
||||
connect(&runnable->m_resultEmitter, &ThumbnailingResult::resultsFailed, this, &FilterModel::thumbnailFailed);
|
||||
m_thumbnailingPool.start(runnable);
|
||||
}
|
||||
private slots:
|
||||
void thumbnailReady(QString path) { emit layoutChanged(); }
|
||||
void thumbnailFailed(QString path) { m_failed.insert(path); }
|
||||
void fileChanged(QString filepath)
|
||||
void thumbnailReady(const QString& /*path*/) { emit layoutChanged(); }
|
||||
void thumbnailFailed(const QString& path) { m_failed.insert(path); }
|
||||
void fileChanged(const QString& filepath)
|
||||
{
|
||||
m_thumbnailCache->setStale(filepath);
|
||||
// reinsert the path...
|
||||
|
|
@ -237,13 +246,12 @@ class FilterModel : public QIdentityProxyModel {
|
|||
|
||||
class CenteredEditingDelegate : public QStyledItemDelegate {
|
||||
public:
|
||||
explicit CenteredEditingDelegate(QObject* parent = 0) : QStyledItemDelegate(parent) {}
|
||||
virtual ~CenteredEditingDelegate() {}
|
||||
virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
|
||||
explicit CenteredEditingDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {}
|
||||
~CenteredEditingDelegate() override = default;
|
||||
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override
|
||||
{
|
||||
auto widget = QStyledItemDelegate::createEditor(parent, option, index);
|
||||
auto foo = dynamic_cast<QLineEdit*>(widget);
|
||||
if (foo) {
|
||||
auto* widget = QStyledItemDelegate::createEditor(parent, option, index);
|
||||
if (auto* foo = dynamic_cast<QLineEdit*>(widget)) {
|
||||
foo->setAlignment(Qt::AlignHCenter);
|
||||
foo->setFrame(true);
|
||||
foo->setMaximumWidth(192);
|
||||
|
|
@ -252,10 +260,11 @@ class CenteredEditingDelegate : public QStyledItemDelegate {
|
|||
}
|
||||
};
|
||||
|
||||
ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) : QMainWindow(parent), ui(new Ui::ScreenshotsPage)
|
||||
ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent)
|
||||
: QMainWindow(parent), ui(new Ui::ScreenshotsPage), m_folder(std::move(path))
|
||||
{
|
||||
m_model.reset(new ScreenshotsFSModel());
|
||||
m_filterModel.reset(new FilterModel());
|
||||
m_model = std::make_shared<ScreenshotsFSModel>();
|
||||
m_filterModel = std::make_shared<FilterModel>();
|
||||
m_filterModel->setSourceModel(m_model.get());
|
||||
m_model->setFilter(QDir::Files);
|
||||
m_model->setReadOnly(false);
|
||||
|
|
@ -266,7 +275,6 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) : QMainWindow(pa
|
|||
constexpr int file_modified_column_index = 3;
|
||||
m_model->sort(file_modified_column_index, Qt::DescendingOrder);
|
||||
|
||||
m_folder = path;
|
||||
m_valid = FS::ensureFolderPathExists(m_folder);
|
||||
|
||||
ui->setupUi(this);
|
||||
|
|
@ -283,18 +291,19 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) : QMainWindow(pa
|
|||
ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
ui->listView->setItemDelegate(new CenteredEditingDelegate(this));
|
||||
ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu);
|
||||
connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::showContextMenu);
|
||||
connect(ui->listView, &QAbstractItemView::activated, this, &ScreenshotsPage::onItemActivated);
|
||||
}
|
||||
|
||||
bool ScreenshotsPage::eventFilter(QObject* obj, QEvent* evt)
|
||||
{
|
||||
if (obj != ui->listView)
|
||||
if (obj != ui->listView) {
|
||||
return QWidget::eventFilter(obj, evt);
|
||||
}
|
||||
if (evt->type() != QEvent::KeyPress) {
|
||||
return QWidget::eventFilter(obj, evt);
|
||||
}
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(evt);
|
||||
const auto* keyEvent = static_cast<QKeyEvent*>(evt);
|
||||
|
||||
if (keyEvent->matches(QKeySequence::Copy)) {
|
||||
on_actionCopy_File_s_triggered();
|
||||
|
|
@ -324,11 +333,11 @@ ScreenshotsPage::~ScreenshotsPage()
|
|||
delete ui;
|
||||
}
|
||||
|
||||
void ScreenshotsPage::ShowContextMenu(const QPoint& pos)
|
||||
void ScreenshotsPage::showContextMenu(const QPoint& pos)
|
||||
{
|
||||
auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
|
||||
auto* menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
|
||||
|
||||
if (ui->listView->selectionModel()->selectedRows().size() > 1) {
|
||||
if (ui->listView->selectionModel()->selectedIndexes().size() > 1) {
|
||||
menu->removeAction(ui->actionCopy_Image);
|
||||
}
|
||||
|
||||
|
|
@ -343,66 +352,75 @@ QMenu* ScreenshotsPage::createPopupMenu()
|
|||
return filteredMenu;
|
||||
}
|
||||
|
||||
void ScreenshotsPage::onItemActivated(QModelIndex index)
|
||||
void ScreenshotsPage::onItemActivated(QModelIndex index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
auto info = m_model->fileInfo(index);
|
||||
}
|
||||
const auto info = m_model->fileInfo(index);
|
||||
DesktopServices::openPath(info);
|
||||
}
|
||||
|
||||
void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected)
|
||||
void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& /*selected*/) const
|
||||
{
|
||||
const auto selected = ui->listView->selectionModel()->selectedIndexes();
|
||||
|
||||
bool allReadable = !selected.isEmpty();
|
||||
bool allWritable = !selected.isEmpty();
|
||||
|
||||
for (auto index : selected.indexes()) {
|
||||
if (!index.isValid())
|
||||
for (auto index : selected) {
|
||||
if (!index.isValid()) {
|
||||
break;
|
||||
}
|
||||
auto info = m_model->fileInfo(index);
|
||||
if (!info.isReadable())
|
||||
if (!info.isReadable()) {
|
||||
allReadable = false;
|
||||
if (!info.isWritable())
|
||||
}
|
||||
if (!info.isWritable()) {
|
||||
allWritable = false;
|
||||
}
|
||||
}
|
||||
|
||||
ui->actionUpload->setEnabled(allReadable);
|
||||
ui->actionCopy_Image->setEnabled(allReadable);
|
||||
ui->actionCopy_Image->setEnabled(allReadable && selected.size() == 1);
|
||||
ui->actionCopy_File_s->setEnabled(allReadable);
|
||||
ui->actionDelete->setEnabled(allWritable);
|
||||
ui->actionRename->setEnabled(allWritable);
|
||||
}
|
||||
|
||||
void ScreenshotsPage::on_actionView_Folder_triggered()
|
||||
void ScreenshotsPage::on_actionView_Folder_triggered() const
|
||||
{
|
||||
DesktopServices::openPath(m_folder, true);
|
||||
}
|
||||
|
||||
void ScreenshotsPage::on_actionUpload_triggered()
|
||||
{
|
||||
auto selection = ui->listView->selectionModel()->selectedRows();
|
||||
if (selection.isEmpty())
|
||||
auto selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString text;
|
||||
QUrl baseUrl(BuildConfig.IMGUR_BASE_URL);
|
||||
if (selection.size() > 1)
|
||||
const QUrl baseUrl(BuildConfig.IMGUR_BASE_URL);
|
||||
if (selection.size() > 1) {
|
||||
text = tr("You are about to upload %1 screenshots to %2.\n"
|
||||
"You should double-check for personal information.\n\n"
|
||||
"Are you sure?")
|
||||
.arg(QString::number(selection.size()), baseUrl.host());
|
||||
else
|
||||
} else {
|
||||
text = tr("You are about to upload the selected screenshot to %1.\n"
|
||||
"You should double-check for personal information.\n\n"
|
||||
"Are you sure?")
|
||||
.arg(baseUrl.host());
|
||||
}
|
||||
|
||||
auto response = CustomMessageBox::selectable(this, "Confirm Upload", text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::No)
|
||||
->exec();
|
||||
|
||||
if (response != QMessageBox::Yes)
|
||||
if (response != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<ScreenShot::Ptr> uploaded;
|
||||
auto job = NetJob::Ptr(new NetJob("Screenshot Upload", APPLICATION->network()));
|
||||
|
|
@ -416,7 +434,7 @@ void ScreenshotsPage::on_actionUpload_triggered()
|
|||
auto screenshot = std::make_shared<ScreenShot>(info);
|
||||
job->addNetAction(ImgurUpload::make(screenshot));
|
||||
|
||||
connect(job.get(), &Task::failed, [this](QString reason) {
|
||||
connect(job.get(), &Task::failed, [this](const QString& reason) {
|
||||
CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), reason, QMessageBox::Critical)->show();
|
||||
});
|
||||
connect(job.get(), &Task::aborted, [this] {
|
||||
|
|
@ -457,7 +475,7 @@ void ScreenshotsPage::on_actionUpload_triggered()
|
|||
task.addTask(job);
|
||||
task.addTask(albumTask);
|
||||
|
||||
connect(&task, &Task::failed, [this](QString reason) {
|
||||
connect(&task, &Task::failed, [this](const QString& reason) {
|
||||
CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), reason, QMessageBox::Critical)->show();
|
||||
});
|
||||
connect(&task, &Task::aborted, [this] {
|
||||
|
|
@ -485,24 +503,24 @@ void ScreenshotsPage::on_actionUpload_triggered()
|
|||
m_uploadActive = false;
|
||||
}
|
||||
|
||||
void ScreenshotsPage::on_actionCopy_Image_triggered()
|
||||
void ScreenshotsPage::on_actionCopy_Image_triggered() const
|
||||
{
|
||||
auto selection = ui->listView->selectionModel()->selectedRows();
|
||||
auto selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// You can only copy one image to the clipboard. In the case of multiple selected files, only the first one gets copied.
|
||||
auto item = selection[0];
|
||||
auto info = m_model->fileInfo(item);
|
||||
QImage image(info.absoluteFilePath());
|
||||
const auto item = selection.first();
|
||||
const auto info = m_model->fileInfo(item);
|
||||
const QImage image(info.absoluteFilePath());
|
||||
Q_ASSERT(!image.isNull());
|
||||
QApplication::clipboard()->setImage(image, QClipboard::Clipboard);
|
||||
}
|
||||
|
||||
void ScreenshotsPage::on_actionCopy_File_s_triggered()
|
||||
void ScreenshotsPage::on_actionCopy_File_s_triggered() const
|
||||
{
|
||||
auto selection = ui->listView->selectionModel()->selectedRows();
|
||||
auto selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() < 1) {
|
||||
// Don't do anything so we don't empty the users clipboard
|
||||
return;
|
||||
|
|
@ -513,7 +531,7 @@ void ScreenshotsPage::on_actionCopy_File_s_triggered()
|
|||
auto info = m_model->fileInfo(item);
|
||||
buf += "file:///" + info.absoluteFilePath() + "\r\n";
|
||||
}
|
||||
QMimeData* mimeData = new QMimeData();
|
||||
auto* mimeData = new QMimeData();
|
||||
mimeData->setData("text/uri-list", buf.toLocal8Bit());
|
||||
QApplication::clipboard()->setMimeData(mimeData);
|
||||
}
|
||||
|
|
@ -522,39 +540,43 @@ void ScreenshotsPage::on_actionDelete_triggered()
|
|||
{
|
||||
auto selected = ui->listView->selectionModel()->selectedIndexes();
|
||||
|
||||
int count = ui->listView->selectionModel()->selectedRows().size();
|
||||
const qsizetype count = selected.size();
|
||||
QString text;
|
||||
if (count > 1)
|
||||
if (count > 1) {
|
||||
text = tr("You are about to delete %1 screenshots.\n"
|
||||
"This may be permanent and they will be gone from the folder.\n\n"
|
||||
"Are you sure?")
|
||||
.arg(count);
|
||||
else
|
||||
text = tr("You are about to delete the selected screenshot.\n"
|
||||
"This may be permanent and it will be gone from the folder.\n\n"
|
||||
"Are you sure?")
|
||||
.arg(count);
|
||||
} else {
|
||||
text =
|
||||
tr("You are about to delete the selected screenshot.\n"
|
||||
"This may be permanent and it will be gone from the folder.\n\n"
|
||||
"Are you sure?");
|
||||
}
|
||||
|
||||
auto response =
|
||||
const auto response =
|
||||
CustomMessageBox::selectable(this, tr("Confirm Deletion"), text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No)->exec();
|
||||
|
||||
if (response != QMessageBox::Yes)
|
||||
if (response != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto item : selected) {
|
||||
if (FS::trash(m_model->filePath(item)))
|
||||
if (FS::trash(m_model->filePath(item))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_model->remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenshotsPage::on_actionRename_triggered()
|
||||
void ScreenshotsPage::on_actionRename_triggered() const
|
||||
{
|
||||
auto selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.isEmpty())
|
||||
if (selection.isEmpty()) {
|
||||
return;
|
||||
ui->listView->edit(selection[0]);
|
||||
}
|
||||
ui->listView->edit(selection.first());
|
||||
// TODO: mass renaming
|
||||
}
|
||||
|
||||
|
|
@ -564,8 +586,8 @@ void ScreenshotsPage::openedImpl()
|
|||
m_valid = FS::ensureFolderPathExists(m_folder);
|
||||
}
|
||||
if (m_valid) {
|
||||
QString path = QDir(m_folder).absolutePath();
|
||||
auto idx = m_model->setRootPath(path);
|
||||
const QString path = QDir(m_folder).absolutePath();
|
||||
const auto idx = m_model->setRootPath(path);
|
||||
if (idx.isValid()) {
|
||||
ui->listView->setModel(m_filterModel.get());
|
||||
connect(ui->listView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
|
||||
|
|
@ -577,7 +599,7 @@ void ScreenshotsPage::openedImpl()
|
|||
}
|
||||
}
|
||||
|
||||
auto const setting_name = QString("WideBarVisibility_%1").arg(id());
|
||||
const auto setting_name = QString("WideBarVisibility_%1").arg(id());
|
||||
m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name);
|
||||
|
||||
ui->toolBar->setVisibilityState(QByteArray::fromBase64(m_wide_bar_setting->get().toString().toUtf8()));
|
||||
|
|
|
|||
|
|
@ -78,14 +78,14 @@ class ScreenshotsPage : public QMainWindow, public BasePage {
|
|||
|
||||
private slots:
|
||||
void on_actionUpload_triggered();
|
||||
void on_actionCopy_Image_triggered();
|
||||
void on_actionCopy_File_s_triggered();
|
||||
void on_actionCopy_Image_triggered() const;
|
||||
void on_actionCopy_File_s_triggered() const;
|
||||
void on_actionDelete_triggered();
|
||||
void on_actionRename_triggered();
|
||||
void on_actionView_Folder_triggered();
|
||||
void onItemActivated(QModelIndex);
|
||||
void onCurrentSelectionChanged(const QItemSelection& selected);
|
||||
void ShowContextMenu(const QPoint& pos);
|
||||
void on_actionRename_triggered() const;
|
||||
void on_actionView_Folder_triggered() const;
|
||||
void onItemActivated(QModelIndex) const;
|
||||
void onCurrentSelectionChanged(const QItemSelection& selected) const;
|
||||
void showContextMenu(const QPoint& pos);
|
||||
|
||||
private:
|
||||
Ui::ScreenshotsPage* ui;
|
||||
|
|
|
|||
|
|
@ -80,14 +80,14 @@ void CustomPage::openedImpl()
|
|||
|
||||
void CustomPage::refresh()
|
||||
{
|
||||
ui->versionList->loadList();
|
||||
ui->versionList->loadList(true);
|
||||
}
|
||||
|
||||
void CustomPage::loaderRefresh()
|
||||
{
|
||||
if (ui->noneFilter->isChecked())
|
||||
return;
|
||||
ui->loaderVersionList->loadList();
|
||||
ui->loaderVersionList->loadList(true);
|
||||
}
|
||||
|
||||
void CustomPage::filterChanged()
|
||||
|
|
|
|||
|
|
@ -139,15 +139,18 @@ void ResourceModel::search()
|
|||
if (hasActiveSearchJob())
|
||||
return;
|
||||
|
||||
if (m_search_term.startsWith("#")) {
|
||||
if (m_search_state != SearchState::ResetRequested && m_search_term.startsWith("#")) {
|
||||
auto projectId = m_search_term.mid(1);
|
||||
if (!projectId.isEmpty()) {
|
||||
ResourceAPI::Callback<ModPlatform::IndexedPack::Ptr> callbacks;
|
||||
|
||||
callbacks.on_fail = [this](QString reason, int) {
|
||||
callbacks.on_fail = [this](QString reason, int network_error_code) {
|
||||
if (!s_running_models.constFind(this).value())
|
||||
return;
|
||||
searchRequestFailed(reason, -1);
|
||||
if (network_error_code == 404) {
|
||||
m_search_state = SearchState::ResetRequested;
|
||||
}
|
||||
searchRequestFailed(reason, network_error_code);
|
||||
};
|
||||
callbacks.on_abort = [this] {
|
||||
if (!s_running_models.constFind(this).value())
|
||||
|
|
@ -162,7 +165,7 @@ void ResourceModel::search()
|
|||
};
|
||||
auto project = std::make_shared<ModPlatform::IndexedPack>();
|
||||
project->addonId = projectId;
|
||||
if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks)); job)
|
||||
if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks), false); job)
|
||||
runSearchJob(job);
|
||||
return;
|
||||
}
|
||||
|
|
@ -407,6 +410,9 @@ void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int net
|
|||
// Network error
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods."));
|
||||
break;
|
||||
case 404:
|
||||
// 404 Not Found, some APIs return this when nothing is found, no need to bother the user
|
||||
break;
|
||||
case 409:
|
||||
// 409 Gone, notify user to update
|
||||
QMessageBox::critical(nullptr, tr("Error"),
|
||||
|
|
@ -414,7 +420,14 @@ void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int net
|
|||
break;
|
||||
}
|
||||
|
||||
m_search_state = SearchState::Finished;
|
||||
if (m_search_state == SearchState::ResetRequested) {
|
||||
clearData();
|
||||
|
||||
m_next_search_offset = 0;
|
||||
search();
|
||||
} else {
|
||||
m_search_state = SearchState::Finished;
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceModel::searchRequestAborted()
|
||||
|
|
|
|||
|
|
@ -165,12 +165,20 @@ void ListModel::fetchMore(const QModelIndex& parent)
|
|||
void ListModel::performPaginatedSearch()
|
||||
{
|
||||
static const FlameAPI api;
|
||||
if (m_currentSearchTerm.startsWith("#")) {
|
||||
|
||||
// activate search by id only for numerical values because all CurseForge ids are numerical
|
||||
static const QRegularExpression s_projectIdExpr("^\\#[0-9]+$");
|
||||
if (m_searchState != ResetRequested && s_projectIdExpr.match(m_currentSearchTerm).hasMatch()) {
|
||||
auto projectId = m_currentSearchTerm.mid(1);
|
||||
if (!projectId.isEmpty()) {
|
||||
ResourceAPI::Callback<ModPlatform::IndexedPack::Ptr> callbacks;
|
||||
|
||||
callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); };
|
||||
callbacks.on_fail = [this](QString reason, int network_error_code) {
|
||||
if (network_error_code == 404) {
|
||||
m_searchState = ResetRequested;
|
||||
}
|
||||
searchRequestFailed(reason);
|
||||
};
|
||||
callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); };
|
||||
callbacks.on_abort = [this] {
|
||||
qCritical() << "Search task aborted by an unknown reason!";
|
||||
|
|
@ -178,7 +186,7 @@ void ListModel::performPaginatedSearch()
|
|||
};
|
||||
auto project = std::make_shared<ModPlatform::IndexedPack>();
|
||||
project->addonId = projectId;
|
||||
if (auto job = api.getProjectInfo({ project }, std::move(callbacks)); job) {
|
||||
if (auto job = api.getProjectInfo({ project }, std::move(callbacks), false); job) {
|
||||
m_jobPtr = job;
|
||||
m_jobPtr->start();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,20 +135,26 @@ void ModpackListModel::performPaginatedSearch()
|
|||
return;
|
||||
static const ModrinthAPI api;
|
||||
|
||||
if (m_currentSearchTerm.startsWith("#")) {
|
||||
// Modrinth ids are not limited to numbers and can be any length
|
||||
if (m_searchState != ResetRequested && m_currentSearchTerm.startsWith("#")) {
|
||||
auto projectId = m_currentSearchTerm.mid(1);
|
||||
if (!projectId.isEmpty()) {
|
||||
ResourceAPI::Callback<ModPlatform::IndexedPack::Ptr> callbacks;
|
||||
|
||||
callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); };
|
||||
callbacks.on_fail = [this](QString reason, int network_error_code) {
|
||||
if (network_error_code == 404) {
|
||||
m_searchState = ResetRequested;
|
||||
}
|
||||
searchRequestFailed(reason, network_error_code);
|
||||
};
|
||||
callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); };
|
||||
callbacks.on_abort = [this] {
|
||||
qCritical() << "Search task aborted by an unknown reason!";
|
||||
searchRequestFailed("Aborted");
|
||||
searchRequestFailed("Aborted", 0);
|
||||
};
|
||||
auto project = std::make_shared<ModPlatform::IndexedPack>();
|
||||
project->addonId = projectId;
|
||||
if (auto job = api.getProjectInfo({ project }, std::move(callbacks)); job) {
|
||||
if (auto job = api.getProjectInfo({ project }, std::move(callbacks), false); job) {
|
||||
m_jobPtr = job;
|
||||
m_jobPtr->start();
|
||||
}
|
||||
|
|
@ -161,10 +167,10 @@ void ModpackListModel::performPaginatedSearch()
|
|||
ResourceAPI::Callback<QList<ModPlatform::IndexedPack::Ptr>> callbacks{};
|
||||
|
||||
callbacks.on_succeed = [this](auto& doc) { searchRequestFinished(doc); };
|
||||
callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); };
|
||||
callbacks.on_fail = [this](QString reason, int network_error_code) { searchRequestFailed(reason, network_error_code); };
|
||||
callbacks.on_abort = [this] {
|
||||
qCritical() << "Search task aborted by an unknown reason!";
|
||||
searchRequestFailed("Aborted");
|
||||
searchRequestFailed("Aborted", 0);
|
||||
};
|
||||
|
||||
auto netJob = api.searchProjects({ ModPlatform::ResourceType::Modpack, m_nextSearchOffset, m_currentSearchTerm, sort, m_filter->loaders,
|
||||
|
|
@ -316,13 +322,12 @@ void ModpackListModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack::Pt
|
|||
endInsertRows();
|
||||
}
|
||||
|
||||
void ModpackListModel::searchRequestFailed(QString)
|
||||
void ModpackListModel::searchRequestFailed(QString reason, int network_error_code)
|
||||
{
|
||||
auto failed_action = dynamic_cast<NetJob*>(m_jobPtr.get())->getFailedActions().at(0);
|
||||
if (failed_action->replyStatusCode() == -1) {
|
||||
// Network error
|
||||
if (network_error_code == -1) {
|
||||
// Unknown error in network stack
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
|
||||
} else if (failed_action->replyStatusCode() == 409) {
|
||||
} else if (network_error_code == 409) {
|
||||
// 409 Gone, notify user to update
|
||||
QMessageBox::critical(nullptr, tr("Error"),
|
||||
//: %1 refers to the launcher itself
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class ModpackListModel : public QAbstractListModel {
|
|||
|
||||
public slots:
|
||||
void searchRequestFinished(QList<ModPlatform::IndexedPack::Ptr>& doc_all);
|
||||
void searchRequestFailed(QString reason);
|
||||
void searchRequestFailed(QString reason, int network_error_code);
|
||||
void searchRequestForOneSucceeded(ModPlatform::IndexedPack::Ptr);
|
||||
|
||||
protected slots:
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@ void JavaSettingsWidget::loadSettings()
|
|||
m_ui->maxMemSpinBox->setValue(min);
|
||||
}
|
||||
m_ui->permGenSpinBox->setValue(settings->get("PermGen").toInt());
|
||||
m_ui->lowMemWarningCheckBox->setChecked(settings->get("LowMemWarning").toBool());
|
||||
|
||||
// Java arguments
|
||||
m_ui->javaArgumentsGroupBox->setChecked(m_instance == nullptr || settings->get("OverrideJavaArgs").toBool());
|
||||
|
|
@ -205,10 +206,12 @@ void JavaSettingsWidget::saveSettings()
|
|||
settings->set("MaxMemAlloc", min);
|
||||
}
|
||||
settings->set("PermGen", m_ui->permGenSpinBox->value());
|
||||
settings->set("LowMemWarning", m_ui->lowMemWarningCheckBox->isChecked());
|
||||
} else {
|
||||
settings->reset("MinMemAlloc");
|
||||
settings->reset("MaxMemAlloc");
|
||||
settings->reset("PermGen");
|
||||
settings->reset("LowMemWarning");
|
||||
}
|
||||
|
||||
// Java arguments
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
|
@ -86,7 +86,7 @@
|
|||
<item>
|
||||
<spacer name="horizontalSpacer_7">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
|
@ -101,10 +101,10 @@
|
|||
<item row="9" column="0">
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
<enum>QSizePolicy::Policy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
|
@ -160,10 +160,10 @@
|
|||
<item row="3" column="0">
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
<enum>QSizePolicy::Policy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
|
@ -190,156 +190,166 @@
|
|||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelMinMem">
|
||||
<property name="text">
|
||||
<string>M&inimum Memory Usage:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>minMemSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="minMemSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The amount of memory Minecraft is started with.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string notr="true"> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1048576</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>256</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>(-Xms)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelMaxMem">
|
||||
<property name="text">
|
||||
<string>Ma&ximum Memory Usage:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>maxMemSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="maxMemSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The maximum amount of memory Minecraft is allowed to use.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string notr="true"> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1048576</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>(-Xmx)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>&PermGen Size:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>permGenSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="permGenSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The amount of memory available to store loaded Java classes.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string notr="true"> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1048576</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>64</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>(-XX:PermSize)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="lowMemWarningCheckBox">
|
||||
<property name="text">
|
||||
<string>(-XX:PermSize)</string>
|
||||
<string>Warn when there is not enough memory available</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="permGenSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The amount of memory available to store loaded Java classes.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string notr="true"> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1048576</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>64</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="maxMemSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The maximum amount of memory Minecraft is allowed to use.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string notr="true"> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1048576</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>(-Xmx)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="minMemSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>The amount of memory Minecraft is started with.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string notr="true"> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1048576</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>256</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>&PermGen Size:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>permGenSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>(-Xms)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelMaxMem">
|
||||
<property name="text">
|
||||
<string>Ma&ximum Memory Usage:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>maxMemSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelMinMem">
|
||||
<property name="text">
|
||||
<string>M&inimum Memory Usage:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>minMemSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="4">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelMaxMemNotice">
|
||||
<property name="text">
|
||||
<string>Memory Notice</string>
|
||||
|
|
@ -382,9 +392,7 @@
|
|||
<tabstop>autodownloadJavaCheckBox</tabstop>
|
||||
<tabstop>javaTestBtn</tabstop>
|
||||
<tabstop>javaDownloadBtn</tabstop>
|
||||
<tabstop>minMemSpinBox</tabstop>
|
||||
<tabstop>maxMemSpinBox</tabstop>
|
||||
<tabstop>permGenSpinBox</tabstop>
|
||||
<tabstop>jvmArgsTextBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
|
|||
description_y -= opt.fontMetrics.height();
|
||||
|
||||
// On the bottom, aligned to the left after the icon, and featuring at most two lines of text (with some margin space to spare)
|
||||
painter->drawText(description_x, description_y, remaining_width, cut_text.size() * opt.fontMetrics.height(), Qt::TextWordWrap,
|
||||
painter->drawText(description_x, description_y, remaining_width, num_lines * opt.fontMetrics.height(), Qt::TextWordWrap,
|
||||
description);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -127,9 +127,9 @@ void VersionSelectWidget::closeEvent(QCloseEvent* event)
|
|||
QWidget::closeEvent(event);
|
||||
}
|
||||
|
||||
void VersionSelectWidget::loadList()
|
||||
void VersionSelectWidget::loadList(bool forceReload)
|
||||
{
|
||||
m_load_task = m_vlist->getLoadTask();
|
||||
m_load_task = m_vlist->getLoadTask(forceReload);
|
||||
connect(m_load_task.get(), &Task::succeeded, this, &VersionSelectWidget::onTaskSucceeded);
|
||||
connect(m_load_task.get(), &Task::failed, this, &VersionSelectWidget::onTaskFailed);
|
||||
connect(m_load_task.get(), &Task::progress, this, &VersionSelectWidget::changeProgress);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class VersionSelectWidget : public QWidget {
|
|||
void initialize(BaseVersionList* vlist, bool forceLoad = false);
|
||||
|
||||
//! Starts a task that loads the list.
|
||||
void loadList();
|
||||
void loadList(bool forceReload = false);
|
||||
|
||||
bool hasVersions() const;
|
||||
BaseVersion::Ptr selectedVersion() const;
|
||||
|
|
|
|||
|
|
@ -1160,8 +1160,6 @@ void PrismUpdaterApp::downloadReleasePage(const QString& api_url, int page)
|
|||
m_current_task.reset(download);
|
||||
connect(download.get(), &Net::Download::finished, this, [this]() {
|
||||
qDebug() << "Download" << m_current_task->getUid().toString() << "finished";
|
||||
m_current_task.reset();
|
||||
m_current_url = "";
|
||||
});
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
!include "x64.nsh"
|
||||
|
||||
AllowSkipFiles off
|
||||
Unicode true
|
||||
|
||||
Name "@Launcher_DisplayName@"
|
||||
|
|
|
|||
Loading…
Reference in a new issue