aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
Diffstat (limited to 'launcher')
-rw-r--r--launcher/Application.cpp2
-rw-r--r--launcher/CMakeLists.txt2
-rw-r--r--launcher/FileSystem.cpp11
-rw-r--r--launcher/launch/steps/CheckJava.cpp15
-rw-r--r--launcher/launch/steps/CheckJava.h2
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp2
-rw-r--r--launcher/modplatform/flame/FlameAPI.h26
-rw-r--r--launcher/mojang/PackageManifest.cpp427
-rw-r--r--launcher/mojang/PackageManifest.h171
-rw-r--r--launcher/ui/MainWindow.cpp212
-rw-r--r--launcher/ui/pages/BasePage.h2
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.cpp2
-rw-r--r--launcher/ui/pages/instance/ScreenshotsPage.cpp15
-rw-r--r--launcher/ui/widgets/PageContainer.cpp4
14 files changed, 145 insertions, 748 deletions
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 724e6e44..1d97a5f2 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -594,7 +594,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Java Settings
m_settings->registerSetting("JavaPath", "");
- m_settings->registerSetting("JavaTimestamp", 0);
+ m_settings->registerSetting("JavaSignature", "");
m_settings->registerSetting("JavaArchitecture", "");
m_settings->registerSetting("JavaRealArchitecture", "");
m_settings->registerSetting("JavaVersion", "");
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 3cc8b6e1..312288a1 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -377,8 +377,6 @@ set(MINECRAFT_SOURCES
minecraft/services/SkinDelete.cpp
minecraft/services/SkinDelete.h
- mojang/PackageManifest.h
- mojang/PackageManifest.cpp
minecraft/Agent.h)
# the screenshots feature
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 835ad925..1ea9f755 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -372,7 +372,7 @@ void create_link::make_link_list(const QString& offset)
auto src_path = source_it.next();
auto relative_path = src_dir.relativeFilePath(src_path);
- if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth){
+ if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth) {
relative_path = pathTruncate(relative_path, m_max_depth);
src_path = src_dir.filePath(relative_path);
if (linkedPaths.contains(src_path)) {
@@ -663,7 +663,7 @@ QString pathTruncate(const QString& path, int depth)
QString trunc = QFileInfo(path).path();
- if (pathDepth(trunc) > depth ) {
+ if (pathDepth(trunc) > depth) {
return pathTruncate(trunc, depth);
}
@@ -769,6 +769,9 @@ QString getDesktopDir()
// Cross-platform Shortcut creation
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon)
{
+ if (destination.isEmpty()) {
+ destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name));
+ }
#if defined(Q_OS_MACOS)
destination += ".command";
@@ -791,6 +794,8 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
return true;
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
+ if (!destination.endsWith(".desktop")) // in case of isFlatpak destination is already populated
+ destination += ".desktop";
QFile f(destination);
f.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream stream(&f);
@@ -974,7 +979,7 @@ FilesystemType getFilesystemType(const QString& name)
{
for (auto iter = s_filesystem_type_names.constBegin(); iter != s_filesystem_type_names.constEnd(); ++iter) {
auto fs_names = iter.value();
- if(fs_names.contains(name.toUpper()))
+ if (fs_names.contains(name.toUpper()))
return iter.key();
}
return FilesystemType::UNKNOWN;
diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp
index f0187586..7d697ba9 100644
--- a/launcher/launch/steps/CheckJava.cpp
+++ b/launcher/launch/steps/CheckJava.cpp
@@ -81,15 +81,20 @@ void CheckJava::executeTask()
}
QFileInfo javaInfo(realJavaPath);
- qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
- auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
+ qint64 javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
+ auto storedSignature = settings->get("JavaSignature").toString();
auto storedArchitecture = settings->get("JavaArchitecture").toString();
auto storedRealArchitecture = settings->get("JavaRealArchitecture").toString();
auto storedVersion = settings->get("JavaVersion").toString();
auto storedVendor = settings->get("JavaVendor").toString();
- m_javaUnixTime = javaUnixTime;
+
+ QCryptographicHash hash(QCryptographicHash::Sha1);
+ hash.addData(QByteArray::number(javaUnixTime));
+ hash.addData(m_javaPath.toUtf8());
+ m_javaSignature = hash.result().toHex();
+
// if timestamps are not the same, or something is missing, check!
- if (javaUnixTime != storedUnixTime || storedVersion.size() == 0
+ if (m_javaSignature != storedSignature || storedVersion.size() == 0
|| storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0
|| storedVendor.size() == 0)
{
@@ -140,7 +145,7 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
instance->settings()->set("JavaRealArchitecture", result.realPlatform);
instance->settings()->set("JavaVendor", result.javaVendor);
- instance->settings()->set("JavaTimestamp", m_javaUnixTime);
+ instance->settings()->set("JavaSignature", m_javaSignature);
emitSucceeded();
return;
}
diff --git a/launcher/launch/steps/CheckJava.h b/launcher/launch/steps/CheckJava.h
index d084b132..bbf06b7c 100644
--- a/launcher/launch/steps/CheckJava.h
+++ b/launcher/launch/steps/CheckJava.h
@@ -40,6 +40,6 @@ private:
private:
QString m_javaPath;
- qlonglong m_javaUnixTime;
+ QString m_javaSignature;
JavaCheckerPtr m_JavaChecker;
};
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index f8ed5214..aab930de 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -148,7 +148,7 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
// special!
- m_settings->registerPassthrough(global_settings->getSetting("JavaTimestamp"), javaOrLocation);
+ m_settings->registerPassthrough(global_settings->getSetting("JavaSignature"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), javaOrLocation);
m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation);
diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h
index a0611957..0a6dc78f 100644
--- a/launcher/modplatform/flame/FlameAPI.h
+++ b/launcher/modplatform/flame/FlameAPI.h
@@ -77,24 +77,28 @@ class FlameAPI : public NetworkResourceAPI {
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
{
- auto mappedModLoader = getMappedModLoader(args.loaders.value());
auto addonId = args.pack.addonId.toString();
- if (args.loaders.value() & Quilt) {
- auto overide = ModPlatform::getOverrideDeps();
- auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
- return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
- });
- if (over != overide.cend()) {
- mappedModLoader = 5;
- }
- }
QString url{ QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(addonId) };
QStringList get_parameters;
if (args.mcVersions.has_value())
get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString()));
- if (args.loaders.has_value())
+
+ if (args.loaders.has_value()) {
+ int mappedModLoader = getMappedModLoader(args.loaders.value());
+
+ if (args.loaders.value() & Quilt) {
+ auto overide = ModPlatform::getOverrideDeps();
+ auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
+ return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
+ });
+ if (over != overide.cend()) {
+ mappedModLoader = 5;
+ }
+ }
+
get_parameters.append(QString("modLoaderType=%1").arg(mappedModLoader));
+ }
return url + get_parameters.join('&');
};
diff --git a/launcher/mojang/PackageManifest.cpp b/launcher/mojang/PackageManifest.cpp
deleted file mode 100644
index b3dfd7fc..00000000
--- a/launcher/mojang/PackageManifest.cpp
+++ /dev/null
@@ -1,427 +0,0 @@
-#include "PackageManifest.h"
-#include <Json.h>
-#include <QDir>
-#include <QDirIterator>
-#include <QCryptographicHash>
-#include <QDebug>
-
-#ifndef Q_OS_WIN32
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#endif
-
-namespace mojang_files {
-
-const Hash hash_of_empty_string = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
-
-int Path::compare(const Path& rhs) const
-{
- auto left_cursor = begin();
- auto left_end = end();
- auto right_cursor = rhs.begin();
- auto right_end = rhs.end();
-
- while (left_cursor != left_end && right_cursor != right_end)
- {
- if(*left_cursor < *right_cursor)
- {
- return -1;
- }
- else if(*left_cursor > *right_cursor)
- {
- return 1;
- }
- left_cursor++;
- right_cursor++;
- }
-
- if(left_cursor == left_end)
- {
- if(right_cursor == right_end)
- {
- return 0;
- }
- return -1;
- }
- return 1;
-}
-
-void Package::addFile(const Path& path, const File& file) {
- addFolder(path.parent_path());
- files[path] = file;
-}
-
-void Package::addFolder(Path folder) {
- if(!folder.has_parent_path()) {
- return;
- }
- do {
- folders.insert(folder);
- folder = folder.parent_path();
- } while(folder.has_parent_path());
-}
-
-void Package::addLink(const Path& path, const Path& target) {
- addFolder(path.parent_path());
- symlinks[path] = target;
-}
-
-void Package::addSource(const FileSource& source) {
- sources[source.hash] = source;
-}
-
-
-namespace {
-void fromJson(QJsonDocument & doc, Package & out) {
- std::set<Path> seen_paths;
- if (!doc.isObject())
- {
- throw JSONValidationError("file manifest is not an object");
- }
- QJsonObject root = doc.object();
-
- auto filesObj = Json::ensureObject(root, "files");
- auto iter = filesObj.begin();
- while (iter != filesObj.end())
- {
- Path objectPath = Path(iter.key());
- auto value = iter.value();
- iter++;
- if(seen_paths.count(objectPath)) {
- throw JSONValidationError("duplicate path inside manifest, the manifest is invalid");
- }
- if (!value.isObject())
- {
- throw JSONValidationError("file entry inside manifest is not an an object");
- }
- seen_paths.insert(objectPath);
-
- auto fileObject = value.toObject();
- auto type = Json::requireString(fileObject, "type");
- if(type == "directory") {
- out.addFolder(objectPath);
- continue;
- }
- else if(type == "file") {
- FileSource bestSource;
- File file;
- file.executable = Json::ensureBoolean(fileObject, QString("executable"), false);
- auto downloads = Json::requireObject(fileObject, "downloads");
- for(auto iter2 = downloads.begin(); iter2 != downloads.end(); iter2++) {
- FileSource source;
-
- auto downloadObject = Json::requireObject(iter2.value());
- source.hash = Json::requireString(downloadObject, "sha1");
- source.size = Json::requireInteger(downloadObject, "size");
- source.url = Json::requireString(downloadObject, "url");
-
- auto compression = iter2.key();
- if(compression == "raw") {
- file.hash = source.hash;
- file.size = source.size;
- source.compression = Compression::Raw;
- }
- else if (compression == "lzma") {
- source.compression = Compression::Lzma;
- }
- else {
- continue;
- }
- bestSource.upgrade(source);
- }
- if(bestSource.isBad()) {
- throw JSONValidationError("No valid compression method for file " + iter.key());
- }
- out.addFile(objectPath, file);
- out.addSource(bestSource);
- }
- else if(type == "link") {
- auto target = Json::requireString(fileObject, "target");
- out.symlinks[objectPath] = target;
- out.addLink(objectPath, target);
- }
- else {
- throw JSONValidationError("Invalid item type in manifest: " + type);
- }
- }
- // make sure the containing folder exists
- out.folders.insert(Path());
-}
-}
-
-Package Package::fromManifestContents(const QByteArray& contents)
-{
- Package out;
- try
- {
- auto doc = Json::requireDocument(contents, "Manifest");
- fromJson(doc, out);
- return out;
- }
- catch (const Exception &e)
- {
- qDebug() << QString("Unable to parse manifest: %1").arg(e.cause());
- out.valid = false;
- return out;
- }
-}
-
-Package Package::fromManifestFile(const QString & filename) {
- Package out;
- try
- {
- auto doc = Json::requireDocument(filename, filename);
- fromJson(doc, out);
- return out;
- }
- catch (const Exception &e)
- {
- qDebug() << QString("Unable to parse manifest file %1: %2").arg(filename, e.cause());
- out.valid = false;
- return out;
- }
-}
-
-#ifndef Q_OS_WIN32
-
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-
-namespace {
-// FIXME: Qt obscures symlink targets by making them absolute. that is useless. this is the workaround - we do it ourselves
-bool actually_read_symlink_target(const QString & filepath, Path & out)
-{
- struct ::stat st;
- // FIXME: here, we assume the native filesystem encoding. May the Gods have mercy upon our Souls.
- QByteArray nativePath = filepath.toUtf8();
- const char * filepath_cstr = nativePath.data();
-
- if (lstat(filepath_cstr, &st) != 0)
- {
- return false;
- }
-
- auto size = st.st_size ? st.st_size + 1 : PATH_MAX;
- std::string temp(size, '\0');
- // because we don't realiably know how long the damn thing actually is, we loop and expand. POSIX is naff
- do
- {
- auto link_length = ::readlink(filepath_cstr, &temp[0], temp.size());
- if(link_length == -1)
- {
- return false;
- }
- if(std::string::size_type(link_length) < temp.size())
- {
- // buffer was long enough and we managed to read the link target. RETURN here.
- temp.resize(link_length);
- out = Path(QString::fromUtf8(temp.c_str()));
- return true;
- }
- temp.resize(temp.size() * 2);
- } while (true);
-}
-}
-#endif
-
-// FIXME: Qt filesystem abstraction is bad, but ... let's hope it doesn't break too much?
-// FIXME: The error handling is just DEFICIENT
-Package Package::fromInspectedFolder(const QString& folderPath)
-{
- QDir root(folderPath);
-
- Package out;
- QDirIterator iterator(folderPath, QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden, QDirIterator::Subdirectories);
- while(iterator.hasNext()) {
- iterator.next();
-
- auto fileInfo = iterator.fileInfo();
- auto relPath = root.relativeFilePath(fileInfo.filePath());
- // FIXME: this is probably completely busted on Windows anyway, so just disable it.
- // Qt makes shit up and doesn't understand the platform details
- // TODO: Actually use a filesystem library that isn't terrible and has decen license.
- // I only know one, and I wrote it. Sadly, currently proprietary. PAIN.
-#ifndef Q_OS_WIN32
- if(fileInfo.isSymLink()) {
- Path targetPath;
- if(!actually_read_symlink_target(fileInfo.filePath(), targetPath)) {
- qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath();
- out.valid = false;
- }
- out.addLink(relPath, targetPath);
- }
- else
-#endif
- if(fileInfo.isDir()) {
- out.addFolder(relPath);
- }
- else if(fileInfo.isFile()) {
- File f;
- f.executable = fileInfo.isExecutable();
- f.size = fileInfo.size();
- // FIXME: async / optimize the hashing
- QFile input(fileInfo.absoluteFilePath());
- if(!input.open(QIODevice::ReadOnly)) {
- qCritical() << "Folder inspection: Failed to open file:" << fileInfo.absoluteFilePath();
- out.valid = false;
- break;
- }
- f.hash = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Sha1).toHex().constData();
- out.addFile(relPath, f);
- }
- else {
- // Something else... oh my
- qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath();
- out.valid = false;
- break;
- }
- }
- out.folders.insert(Path("."));
- out.valid = true;
- return out;
-}
-
-namespace {
-struct shallow_first_sort
-{
- bool operator()(const Path &lhs, const Path &rhs) const
- {
- auto lhs_depth = lhs.length();
- auto rhs_depth = rhs.length();
- if(lhs_depth < rhs_depth)
- {
- return true;
- }
- else if(lhs_depth == rhs_depth)
- {
- if(lhs < rhs)
- {
- return true;
- }
- }
- return false;
- }
-};
-
-struct deep_first_sort
-{
- bool operator()(const Path &lhs, const Path &rhs) const
- {
- auto lhs_depth = lhs.length();
- auto rhs_depth = rhs.length();
- if(lhs_depth > rhs_depth)
- {
- return true;
- }
- else if(lhs_depth == rhs_depth)
- {
- if(lhs < rhs)
- {
- return true;
- }
- }
- return false;
- }
-};
-}
-
-UpdateOperations UpdateOperations::resolve(const Package& from, const Package& to)
-{
- UpdateOperations out;
-
- if(!from.valid || !to.valid) {
- out.valid = false;
- return out;
- }
-
- // Files
- for(auto iter = from.files.begin(); iter != from.files.end(); iter++) {
- const auto &current_hash = iter->second.hash;
- const auto &current_executable = iter->second.executable;
- const auto &path = iter->first;
-
- auto iter2 = to.files.find(path);
- if(iter2 == to.files.end()) {
- // removed
- out.deletes.push_back(path);
- continue;
- }
- auto new_hash = iter2->second.hash;
- auto new_executable = iter2->second.executable;
- if (current_hash != new_hash) {
- out.deletes.push_back(path);
- out.downloads.emplace(
- std::pair<Path, FileDownload>{
- path,
- FileDownload(to.sources.at(iter2->second.hash), iter2->second.executable)
- }
- );
- }
- else if (current_executable != new_executable) {
- out.executable_fixes[path] = new_executable;
- }
- }
- for(auto iter = to.files.begin(); iter != to.files.end(); iter++) {
- auto path = iter->first;
- if(!from.files.count(path)) {
- out.downloads.emplace(
- std::pair<Path, FileDownload>{
- path,
- FileDownload(to.sources.at(iter->second.hash), iter->second.executable)
- }
- );
- }
- }
-
- // Folders
- std::set<Path, deep_first_sort> remove_folders;
- std::set<Path, shallow_first_sort> make_folders;
- for(auto from_path: from.folders) {
- auto iter = to.folders.find(from_path);
- if(iter == to.folders.end()) {
- remove_folders.insert(from_path);
- }
- }
- for(auto & rmdir: remove_folders) {
- out.rmdirs.push_back(rmdir);
- }
- for(auto to_path: to.folders) {
- auto iter = from.folders.find(to_path);
- if(iter == from.folders.end()) {
- make_folders.insert(to_path);
- }
- }
- for(auto & mkdir: make_folders) {
- out.mkdirs.push_back(mkdir);
- }
-
- // Symlinks
- for(auto iter = from.symlinks.begin(); iter != from.symlinks.end(); iter++) {
- const auto &current_target = iter->second;
- const auto &path = iter->first;
-
- auto iter2 = to.symlinks.find(path);
- if(iter2 == to.symlinks.end()) {
- // removed
- out.deletes.push_back(path);
- continue;
- }
- const auto &new_target = iter2->second;
- if (current_target != new_target) {
- out.deletes.push_back(path);
- out.mklinks[path] = iter2->second;
- }
- }
- for(auto iter = to.symlinks.begin(); iter != to.symlinks.end(); iter++) {
- auto path = iter->first;
- if(!from.symlinks.count(path)) {
- out.mklinks[path] = iter->second;
- }
- }
- out.valid = true;
- return out;
-}
-
-}
diff --git a/launcher/mojang/PackageManifest.h b/launcher/mojang/PackageManifest.h
deleted file mode 100644
index fd7ab0ad..00000000
--- a/launcher/mojang/PackageManifest.h
+++ /dev/null
@@ -1,171 +0,0 @@
-#pragma once
-
-#include <QString>
-#include <map>
-#include <set>
-#include <QStringList>
-#include "tasks/Task.h"
-
-namespace mojang_files {
-
-using Hash = QString;
-extern const Hash empty_hash;
-
-// simple-ish path implementation. assumes always relative and does not allow '..' entries
-class Path
-{
-public:
- using parts_type = QStringList;
-
- Path() = default;
- Path(QString string) {
- auto parts_in = string.split('/');
- for(auto & part: parts_in) {
- if(part.isEmpty() || part == ".") {
- continue;
- }
- if(part == "..") {
- if(parts.size()) {
- parts.pop_back();
- }
- continue;
- }
- parts.push_back(part);
- }
- }
-
- bool has_parent_path() const
- {
- return parts.size() > 0;
- }
-
- Path parent_path() const
- {
- if (parts.empty())
- return Path();
- return Path(parts.begin(), std::prev(parts.end()));
- }
-
- bool empty() const
- {
- return parts.empty();
- }
-
- int length() const
- {
- return parts.length();
- }
-
- bool operator==(const Path & rhs) const {
- return parts == rhs.parts;
- }
-
- bool operator!=(const Path & rhs) const {
- return parts != rhs.parts;
- }
-
- inline bool operator<(const Path& rhs) const
- {
- return compare(rhs) < 0;
- }
-
- parts_type::const_iterator begin() const
- {
- return parts.begin();
- }
-
- parts_type::const_iterator end() const
- {
- return parts.end();
- }
-
- QString toString() const {
- return parts.join("/");
- }
-
-private:
- Path(const parts_type::const_iterator & start, const parts_type::const_iterator & end) {
- auto cursor = start;
- while(cursor != end) {
- parts.push_back(*cursor);
- cursor++;
- }
- }
- int compare(const Path& p) const;
-
- parts_type parts;
-};
-
-
-enum class Compression {
- Raw,
- Lzma,
- Unknown
-};
-
-
-struct FileSource
-{
- Compression compression = Compression::Unknown;
- Hash hash;
- QString url;
- std::size_t size = 0;
- void upgrade(const FileSource & other) {
- if(compression == Compression::Unknown || other.size < size) {
- *this = other;
- }
- }
- bool isBad() const {
- return compression == Compression::Unknown;
- }
-};
-
-struct File
-{
- Hash hash;
- bool executable;
- std::uint64_t size = 0;
-};
-
-struct Package {
- static Package fromInspectedFolder(const QString &folderPath);
- static Package fromManifestFile(const QString &path);
- static Package fromManifestContents(const QByteArray& contents);
-
- explicit operator bool() const
- {
- return valid;
- }
- void addFolder(Path folder);
- void addFile(const Path & path, const File & file);
- void addLink(const Path & path, const Path & target);
- void addSource(const FileSource & source);
-
- std::map<Hash, FileSource> sources;
- bool valid = true;
- std::set<Path> folders;
- std::map<Path, File> files;
- std::map<Path, Path> symlinks;
-};
-
-struct FileDownload : FileSource
-{
- FileDownload(const FileSource& source, bool executable) {
- static_cast<FileSource &> (*this) = source;
- this->executable = executable;
- }
- bool executable = false;
-};
-
-struct UpdateOperations {
- static UpdateOperations resolve(const Package & from, const Package & to);
- bool valid = false;
- std::vector<Path> deletes;
- std::vector<Path> rmdirs;
- std::vector<Path> mkdirs;
- std::map<Path, FileDownload> downloads;
- std::map<Path, Path> mklinks;
- std::map<Path, bool> executable_fixes;
-};
-
-}
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index e04011ca..496738e3 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -224,6 +224,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
// disabled until we have an instance selected
ui->instanceToolBar->setEnabled(false);
setInstanceActionsEnabled(false);
+
+ // add a close button at the end of the main toolbar when running on gamescope / steam deck
+ // FIXME: detect if we don't have server side decorations instead
+ if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") {
+ ui->mainToolBar->addAction(ui->actionCloseWindow);
+ }
+
}
// add the toolbar toggles to the view menu
@@ -1503,140 +1510,113 @@ void MainWindow::on_actionKillInstance_triggered()
void MainWindow::on_actionCreateInstanceShortcut_triggered()
{
- if (m_selectedInstance)
- {
- auto desktopPath = FS::getDesktopDir();
- if (desktopPath.isEmpty()) {
- // TODO come up with an alternative solution (open "save file" dialog)
- QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!"));
- return;
- }
+ if (!m_selectedInstance)
+ return;
+ auto desktopPath = FS::getDesktopDir();
+ if (desktopPath.isEmpty()) {
+ // TODO come up with an alternative solution (open "save file" dialog)
+ QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!"));
+ return;
+ }
+ QString desktopFilePath;
+ QString appPath = QApplication::applicationFilePath();
+ QString iconPath;
+ QStringList args;
#if defined(Q_OS_MACOS)
- QString appPath = QApplication::applicationFilePath();
- if (appPath.startsWith("/private/var/")) {
- QMessageBox::critical(this, tr("Create instance shortcut"), tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
- return;
- }
-
- if (FS::createShortcut(FS::PathCombine(desktopPath, m_selectedInstance->name()),
- appPath, { "--launch", m_selectedInstance->id() },
- m_selectedInstance->name(), "")) {
- QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
- }
- else
- {
- QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
- }
+ if (appPath.startsWith("/private/var/")) {
+ QMessageBox::critical(this, tr("Create instance shortcut"),
+ tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
+ return;
+ }
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
- QString appPath = QApplication::applicationFilePath();
- if (appPath.startsWith("/tmp/.mount_")) {
- // AppImage!
- appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
- if (appPath.isEmpty())
- {
- QMessageBox::critical(this, tr("Create instance shortcut"), tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)"));
- }
- else if (appPath.endsWith("/"))
- {
- appPath.chop(1);
- }
+ if (appPath.startsWith("/tmp/.mount_")) {
+ // AppImage!
+ appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
+ if (appPath.isEmpty()) {
+ QMessageBox::critical(this, tr("Create instance shortcut"),
+ tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)"));
+ } else if (appPath.endsWith("/")) {
+ appPath.chop(1);
}
+ }
- auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
- if (icon == nullptr)
- {
- icon = APPLICATION->icons()->icon("grass");
- }
+ auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
+ if (icon == nullptr) {
+ icon = APPLICATION->icons()->icon("grass");
+ }
- QString iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png");
+ iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png");
- QFile iconFile(iconPath);
- if (!iconFile.open(QFile::WriteOnly))
- {
- QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
- return;
- }
- bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG");
- iconFile.close();
+ QFile iconFile(iconPath);
+ if (!iconFile.open(QFile::WriteOnly)) {
+ QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
+ return;
+ }
+ bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG");
+ iconFile.close();
- if (!success)
- {
- iconFile.remove();
- QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
- return;
- }
+ if (!success) {
+ iconFile.remove();
+ QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
+ return;
+ }
+
+ if (DesktopServices::isFlatpak()) {
+ desktopFilePath = FS::PathCombine(desktopPath, FS::RemoveInvalidFilenameChars(m_selectedInstance->name()) + ".desktop");
+ QFileDialog fileDialog;
+ // workaround to make sure the portal file dialog opens in the desktop directory
+ fileDialog.setDirectoryUrl(desktopPath);
+ desktopFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), desktopFilePath, tr("Desktop Entries (*.desktop)"));
+ if (desktopFilePath.isEmpty())
+ return; // file dialog canceled by user
+ appPath = "flatpak";
+ QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME;
+ flatpakAppId.remove(".desktop");
+ args.append({ "run", flatpakAppId });
+ }
- QString desktopFilePath = FS::PathCombine(desktopPath, m_selectedInstance->name() + ".desktop");
- QStringList args;
- if (DesktopServices::isFlatpak()) {
- QFileDialog fileDialog;
- // workaround to make sure the portal file dialog opens in the desktop directory
- fileDialog.setDirectoryUrl(desktopPath);
- desktopFilePath = fileDialog.getSaveFileName(
- this, tr("Create Shortcut"), desktopFilePath,
- tr("Desktop Entries (*.desktop)"));
- if (desktopFilePath.isEmpty())
- return; // file dialog canceled by user
- appPath = "flatpak";
- QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME;
- flatpakAppId.remove(".desktop");
- args.append({ "run", flatpakAppId });
- }
- args.append({ "--launch", m_selectedInstance->id() });
- if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) {
- QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
- }
- else
- {
- iconFile.remove();
- QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
- }
#elif defined(Q_OS_WIN)
- auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
- if (icon == nullptr)
- {
- icon = APPLICATION->icons()->icon("grass");
- }
+ auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
+ if (icon == nullptr) {
+ icon = APPLICATION->icons()->icon("grass");
+ }
- QString iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico");
+ iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico");
- // part of fix for weird bug involving the window icon being replaced
- // dunno why it happens, but this 2-line fix seems to be enough, so w/e
- auto appIcon = APPLICATION->getThemedIcon("logo");
+ // part of fix for weird bug involving the window icon being replaced
+ // dunno why it happens, but this 2-line fix seems to be enough, so w/e
+ auto appIcon = APPLICATION->getThemedIcon("logo");
- QFile iconFile(iconPath);
- if (!iconFile.open(QFile::WriteOnly))
- {
- QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
- return;
- }
- bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO");
- iconFile.close();
+ QFile iconFile(iconPath);
+ if (!iconFile.open(QFile::WriteOnly)) {
+ QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
+ return;
+ }
+ bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO");
+ iconFile.close();
- // restore original window icon
- QGuiApplication::setWindowIcon(appIcon);
+ // restore original window icon
+ QGuiApplication::setWindowIcon(appIcon);
- if (!success)
- {
- iconFile.remove();
- QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
- return;
- }
+ if (!success) {
+ iconFile.remove();
+ QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
+ return;
+ }
- if (FS::createShortcut(FS::PathCombine(desktopPath, m_selectedInstance->name()),
- QApplication::applicationFilePath(), { "--launch", m_selectedInstance->id() },
- m_selectedInstance->name(), iconPath)) {
- QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
- }
- else
- {
- iconFile.remove();
- QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
- }
#else
- QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!"));
+ QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!"));
+ return;
+#endif
+ args.append({ "--launch", m_selectedInstance->id() });
+ if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) {
+ QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
+ } else {
+#if not defined(Q_OS_MACOS)
+ iconFile.remove();
#endif
+ QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
}
}
diff --git a/launcher/ui/pages/BasePage.h b/launcher/ui/pages/BasePage.h
index 5537c28f..dc2bde99 100644
--- a/launcher/ui/pages/BasePage.h
+++ b/launcher/ui/pages/BasePage.h
@@ -44,7 +44,7 @@
class BasePage {
public:
- using updateExtraInfoFunc = std::function<void(QString)>;
+ using updateExtraInfoFunc = std::function<void(QString, QString)>;
virtual ~BasePage() {}
virtual QString id() const = 0;
virtual QString displayName() const = 0;
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
index 8e5226ef..173bcb66 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
@@ -83,7 +83,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current);
auto updateExtra = [this]() {
if (updateExtraInfo)
- updateExtraInfo(extraHeaderInfoString());
+ updateExtraInfo(id(), extraHeaderInfoString());
};
connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra);
connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra);
diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp
index ca368d3b..35237594 100644
--- a/launcher/ui/pages/instance/ScreenshotsPage.cpp
+++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp
@@ -36,6 +36,7 @@
*/
#include "ScreenshotsPage.h"
+#include "BuildConfig.h"
#include "ui_ScreenshotsPage.h"
#include <QModelIndex>
@@ -380,16 +381,18 @@ void ScreenshotsPage::on_actionUpload_triggered()
if (selection.isEmpty())
return;
-
QString text;
+ QUrl baseUrl(BuildConfig.IMGUR_BASE_URL);
if (selection.size() > 1)
- text = tr("You are about to upload %1 screenshots.\n\n"
+ 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(selection.size());
+ .arg(QString::number(selection.size()), baseUrl.host());
else
- text =
- tr("You are about to upload the selected screenshot.\n\n"
- "Are you sure?");
+ 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)
diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp
index 34df42ec..b98c9796 100644
--- a/launcher/ui/widgets/PageContainer.cpp
+++ b/launcher/ui/widgets/PageContainer.cpp
@@ -93,8 +93,8 @@ PageContainer::PageContainer(BasePageProvider *pageProvider, QString defaultId,
page->listIndex = counter;
page->setParentContainer(this);
counter++;
- page->updateExtraInfo = [this](QString info) {
- if (m_currentPage)
+ page->updateExtraInfo = [this](QString id, QString info) {
+ if (m_currentPage && id == m_currentPage->id())
m_header->setText(m_currentPage->displayName() + info);
};
}