Use security-scoped bookmarks for directory settings on macOS (#3616)
This commit is contained in:
@@ -706,6 +706,16 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
m_settings->registerSetting("SkinsDir", "skins");
|
m_settings->registerSetting("SkinsDir", "skins");
|
||||||
m_settings->registerSetting("JavaDir", "java");
|
m_settings->registerSetting("JavaDir", "java");
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
// Folder security-scoped bookmarks
|
||||||
|
m_settings->registerSetting("InstanceDirBookmark", "");
|
||||||
|
m_settings->registerSetting("CentralModsDirBookmark", "");
|
||||||
|
m_settings->registerSetting("IconsDirBookmark", "");
|
||||||
|
m_settings->registerSetting("DownloadsDirBookmark", "");
|
||||||
|
m_settings->registerSetting("SkinsDirBookmark", "");
|
||||||
|
m_settings->registerSetting("JavaDirBookmark", "");
|
||||||
|
#endif
|
||||||
|
|
||||||
// Editors
|
// Editors
|
||||||
m_settings->registerSetting("JsonEditor", QString());
|
m_settings->registerSetting("JsonEditor", QString());
|
||||||
|
|
||||||
@@ -956,12 +966,27 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
// Themes
|
// Themes
|
||||||
m_themeManager = std::make_unique<ThemeManager>();
|
m_themeManager = std::make_unique<ThemeManager>();
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
// for macOS: getting directory settings will generate URL security-scoped bookmarks if needed and not present
|
||||||
|
// this facilitates a smooth transition from a non-sandboxed version of the launcher, that likely can access the directory,
|
||||||
|
// and a sandboxed version that can't access the directory without a bookmark
|
||||||
|
// this section can likely be removed once the sandboxed version has been released for a while and migrations aren't done anymore
|
||||||
|
{
|
||||||
|
m_settings->get("InstanceDir");
|
||||||
|
m_settings->get("CentralModsDir");
|
||||||
|
m_settings->get("IconsDir");
|
||||||
|
m_settings->get("DownloadsDir");
|
||||||
|
m_settings->get("SkinsDir");
|
||||||
|
m_settings->get("JavaDir");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// initialize and load all instances
|
// initialize and load all instances
|
||||||
{
|
{
|
||||||
auto InstDirSetting = m_settings->getSetting("InstanceDir");
|
auto InstDirSetting = m_settings->getSetting("InstanceDir");
|
||||||
// instance path: check for problems with '!' in instance path and warn the user in the log
|
// instance path: check for problems with '!' in instance path and warn the user in the log
|
||||||
// and remember that we have to show him a dialog when the gui starts (if it does so)
|
// and remember that we have to show him a dialog when the gui starts (if it does so)
|
||||||
QString instDir = InstDirSetting->get().toString();
|
QString instDir = m_settings->get("InstanceDir").toString();
|
||||||
qInfo() << "Instance path : " << instDir;
|
qInfo() << "Instance path : " << instDir;
|
||||||
if (FS::checkProblemticPathJava(QDir(instDir))) {
|
if (FS::checkProblemticPathJava(QDir(instDir))) {
|
||||||
qWarning() << "Your instance path contains \'!\' and this is known to cause java problems!";
|
qWarning() << "Your instance path contains \'!\' and this is known to cause java problems!";
|
||||||
|
|||||||
@@ -1173,6 +1173,15 @@ if (NOT Apple)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
set(LAUNCHER_SOURCES
|
||||||
|
${LAUNCHER_SOURCES}
|
||||||
|
|
||||||
|
macsandbox/SecurityBookmarkFileAccess.h
|
||||||
|
macsandbox/SecurityBookmarkFileAccess.mm
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(LAUNCHER_SOURCES
|
set(LAUNCHER_SOURCES
|
||||||
console/WindowsConsole.h
|
console/WindowsConsole.h
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2024 Kenneth Chew <79120643+kthchew@users.noreply.github.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FILEACCESS_H
|
||||||
|
#define FILEACCESS_H
|
||||||
|
|
||||||
|
#include <QtCore/QMap>
|
||||||
|
#include <QtCore/QSet>
|
||||||
|
Q_FORWARD_DECLARE_OBJC_CLASS(NSData);
|
||||||
|
Q_FORWARD_DECLARE_OBJC_CLASS(NSURL);
|
||||||
|
Q_FORWARD_DECLARE_OBJC_CLASS(NSString);
|
||||||
|
Q_FORWARD_DECLARE_OBJC_CLASS(NSAutoreleasePool);
|
||||||
|
Q_FORWARD_DECLARE_OBJC_CLASS(NSMutableDictionary);
|
||||||
|
Q_FORWARD_DECLARE_OBJC_CLASS(NSMutableSet);
|
||||||
|
class QString;
|
||||||
|
class QByteArray;
|
||||||
|
class QUrl;
|
||||||
|
|
||||||
|
class SecurityBookmarkFileAccess {
|
||||||
|
/// The keys are bookmarks and the values are URLs.
|
||||||
|
NSMutableDictionary* m_bookmarks;
|
||||||
|
/// The keys are paths and the values are bookmarks.
|
||||||
|
NSMutableDictionary* m_paths;
|
||||||
|
/// Contains URLs that are currently being accessed.
|
||||||
|
NSMutableSet* m_activeURLs;
|
||||||
|
|
||||||
|
bool m_readOnly;
|
||||||
|
|
||||||
|
NSURL* securityScopedBookmarkToNSURL(QByteArray& bookmark, bool& isStale);
|
||||||
|
public:
|
||||||
|
/// \param readOnly A boolean indicating whether the bookmark should be read-only.
|
||||||
|
SecurityBookmarkFileAccess(bool readOnly = false);
|
||||||
|
~SecurityBookmarkFileAccess();
|
||||||
|
|
||||||
|
/// Get a security scoped bookmark from a URL.
|
||||||
|
///
|
||||||
|
/// The URL must be accessible before calling this function. That is, call `startAccessingSecurityScopedResource()` before calling
|
||||||
|
/// this function. Note that this is called implicitly if the user selects the directory from a file picker.
|
||||||
|
/// \param url The URL to get the security scoped bookmark from.
|
||||||
|
/// \return A QByteArray containing the security scoped bookmark.
|
||||||
|
QByteArray urlToSecurityScopedBookmark(const QUrl& url);
|
||||||
|
/// Get a security scoped bookmark from a path.
|
||||||
|
///
|
||||||
|
/// The path must be accessible before calling this function. That is, call `startAccessingSecurityScopedResource()` before calling
|
||||||
|
/// this function. Note that this is called implicitly if the user selects the directory from a file picker.
|
||||||
|
/// \param path The path to get the security scoped bookmark from.
|
||||||
|
/// \return A QByteArray containing the security scoped bookmark.
|
||||||
|
QByteArray pathToSecurityScopedBookmark(const QString& path);
|
||||||
|
/// Get a QUrl from a security scoped bookmark. If the bookmark is stale, isStale will be set to true and the bookmark will be updated.
|
||||||
|
///
|
||||||
|
/// You must check whether the URL is valid before using it.
|
||||||
|
/// \param bookmark The security scoped bookmark to get the URL from.
|
||||||
|
/// \param isStale A boolean that will be set to true if the bookmark is stale.
|
||||||
|
/// \return The URL from the security scoped bookmark.
|
||||||
|
QUrl securityScopedBookmarkToURL(QByteArray& bookmark, bool& isStale);
|
||||||
|
|
||||||
|
/// Makes the file or directory at the path pointed to by the bookmark accessible. Unlike `startAccessingSecurityScopedResource()`, this
|
||||||
|
/// class ensures that only one "access" is active at a time. Calling this function again after the security-scoped resource has
|
||||||
|
/// already been used will do nothing, and a single call to `stopUsingSecurityScopedBookmark()` will release the resource provided that
|
||||||
|
/// this is the only `SecurityBookmarkFileAccess` accessing the resource.
|
||||||
|
///
|
||||||
|
/// If the bookmark is stale, `isStale` will be set to true and the bookmark will be updated. Stored copies of the bookmark need to be
|
||||||
|
/// updated.
|
||||||
|
/// \param bookmark The security scoped bookmark to start accessing.
|
||||||
|
/// \param isStale A boolean that will be set to true if the bookmark is stale.
|
||||||
|
/// \return A boolean indicating whether the bookmark was successfully accessed.
|
||||||
|
bool startUsingSecurityScopedBookmark(QByteArray& bookmark, bool& isStale);
|
||||||
|
void stopUsingSecurityScopedBookmark(QByteArray& bookmark);
|
||||||
|
|
||||||
|
/// Returns true if access to the `path` is currently being maintained by this object.
|
||||||
|
bool isAccessingPath(const QString& path);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FILEACCESS_H
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2024 Kenneth Chew <79120643+kthchew@users.noreply.github.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "SecurityBookmarkFileAccess.h"
|
||||||
|
|
||||||
|
#include <Foundation/Foundation.h>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
QByteArray SecurityBookmarkFileAccess::urlToSecurityScopedBookmark(const QUrl& url)
|
||||||
|
{
|
||||||
|
if (!url.isLocalFile())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
NSError* error = nil;
|
||||||
|
NSURL* nsurl = [url.toNSURL() absoluteURL];
|
||||||
|
NSData* bookmark;
|
||||||
|
if ([m_paths objectForKey:[nsurl path]]) {
|
||||||
|
bookmark = m_paths[[nsurl path]];
|
||||||
|
} else {
|
||||||
|
bookmark = [nsurl bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope |
|
||||||
|
(m_readOnly ? NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess : 0)
|
||||||
|
includingResourceValuesForKeys:nil
|
||||||
|
relativeToURL:nil
|
||||||
|
error:&error];
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove/reapply access to ensure that write access is immediately cut off for read-only bookmarks
|
||||||
|
// sometimes you need to call this twice to actually stop access (extra calls aren't harmful)
|
||||||
|
[nsurl stopAccessingSecurityScopedResource];
|
||||||
|
[nsurl stopAccessingSecurityScopedResource];
|
||||||
|
nsurl = [NSURL URLByResolvingBookmarkData:bookmark
|
||||||
|
options:NSURLBookmarkResolutionWithSecurityScope |
|
||||||
|
(m_readOnly ? NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess : 0)
|
||||||
|
relativeToURL:nil
|
||||||
|
bookmarkDataIsStale:nil
|
||||||
|
error:&error];
|
||||||
|
m_paths[[nsurl path]] = bookmark;
|
||||||
|
m_bookmarks[bookmark] = nsurl;
|
||||||
|
|
||||||
|
QByteArray qBookmark = QByteArray::fromNSData(bookmark);
|
||||||
|
bool isStale = false;
|
||||||
|
startUsingSecurityScopedBookmark(qBookmark, isStale);
|
||||||
|
|
||||||
|
return qBookmark;
|
||||||
|
}
|
||||||
|
|
||||||
|
SecurityBookmarkFileAccess::SecurityBookmarkFileAccess(bool readOnly) : m_readOnly(readOnly)
|
||||||
|
{
|
||||||
|
m_bookmarks = [NSMutableDictionary new];
|
||||||
|
m_paths = [NSMutableDictionary new];
|
||||||
|
m_activeURLs = [NSMutableSet new];
|
||||||
|
}
|
||||||
|
|
||||||
|
SecurityBookmarkFileAccess::~SecurityBookmarkFileAccess()
|
||||||
|
{
|
||||||
|
for (NSURL* url : m_activeURLs) {
|
||||||
|
[url stopAccessingSecurityScopedResource];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray SecurityBookmarkFileAccess::pathToSecurityScopedBookmark(const QString& path)
|
||||||
|
{
|
||||||
|
return urlToSecurityScopedBookmark(QUrl::fromLocalFile(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
NSURL* SecurityBookmarkFileAccess::securityScopedBookmarkToNSURL(QByteArray& bookmark, bool& isStale)
|
||||||
|
{
|
||||||
|
NSError* error = nil;
|
||||||
|
BOOL localStale = NO;
|
||||||
|
NSURL* nsurl = [NSURL URLByResolvingBookmarkData:bookmark.toNSData()
|
||||||
|
options:NSURLBookmarkResolutionWithSecurityScope |
|
||||||
|
(m_readOnly ? NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess : 0)
|
||||||
|
relativeToURL:nil
|
||||||
|
bookmarkDataIsStale:&localStale
|
||||||
|
error:&error];
|
||||||
|
if (error) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
isStale = localStale;
|
||||||
|
if (isStale) {
|
||||||
|
NSData* nsBookmark = [nsurl bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope |
|
||||||
|
(m_readOnly ? NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess : 0)
|
||||||
|
includingResourceValuesForKeys:nil
|
||||||
|
relativeToURL:nil
|
||||||
|
error:&error];
|
||||||
|
if (error) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
bookmark = QByteArray::fromNSData(nsBookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSData* nsBookmark = bookmark.toNSData();
|
||||||
|
m_paths[[nsurl path]] = nsBookmark;
|
||||||
|
m_bookmarks[nsBookmark] = nsurl;
|
||||||
|
|
||||||
|
return nsurl;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl SecurityBookmarkFileAccess::securityScopedBookmarkToURL(QByteArray& bookmark, bool& isStale)
|
||||||
|
{
|
||||||
|
if (bookmark.isEmpty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
NSURL* url = securityScopedBookmarkToNSURL(bookmark, isStale);
|
||||||
|
if (!url)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return QUrl::fromNSURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SecurityBookmarkFileAccess::startUsingSecurityScopedBookmark(QByteArray& bookmark, bool& isStale)
|
||||||
|
{
|
||||||
|
NSURL* url = [m_bookmarks objectForKey:bookmark.toNSData()] ? m_bookmarks[bookmark.toNSData()]
|
||||||
|
: securityScopedBookmarkToNSURL(bookmark, isStale);
|
||||||
|
if ([m_activeURLs containsObject:url])
|
||||||
|
return false;
|
||||||
|
|
||||||
|
[url stopAccessingSecurityScopedResource];
|
||||||
|
if ([url startAccessingSecurityScopedResource]) {
|
||||||
|
[m_activeURLs addObject:url];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecurityBookmarkFileAccess::stopUsingSecurityScopedBookmark(QByteArray& bookmark)
|
||||||
|
{
|
||||||
|
if (![m_bookmarks objectForKey:bookmark.toNSData()])
|
||||||
|
return;
|
||||||
|
NSURL* url = m_bookmarks[bookmark.toNSData()];
|
||||||
|
|
||||||
|
if ([m_activeURLs containsObject:url]) {
|
||||||
|
[url stopAccessingSecurityScopedResource];
|
||||||
|
[url stopAccessingSecurityScopedResource];
|
||||||
|
|
||||||
|
[m_activeURLs removeObject:url];
|
||||||
|
[m_paths removeObjectForKey:[url path]];
|
||||||
|
[m_bookmarks removeObjectForKey:bookmark.toNSData()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SecurityBookmarkFileAccess::isAccessingPath(const QString& path)
|
||||||
|
{
|
||||||
|
NSData* bookmark = [m_paths objectForKey:path.toNSString()];
|
||||||
|
if (!bookmark && path.endsWith('/')) {
|
||||||
|
bookmark = [m_paths objectForKey:path.left(path.length() - 1).toNSString()];
|
||||||
|
}
|
||||||
|
if (!bookmark) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
NSURL* url = [m_bookmarks objectForKey:bookmark];
|
||||||
|
return [m_activeURLs containsObject:url];
|
||||||
|
}
|
||||||
@@ -20,6 +20,12 @@
|
|||||||
#include "settings/Setting.h"
|
#include "settings/Setting.h"
|
||||||
|
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
#include <QDir>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
#include "macsandbox/SecurityBookmarkFileAccess.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
SettingsObject::SettingsObject(QObject* parent) : QObject(parent) {}
|
SettingsObject::SettingsObject(QObject* parent) : QObject(parent) {}
|
||||||
|
|
||||||
@@ -78,9 +84,17 @@ std::shared_ptr<Setting> SettingsObject::getSetting(const QString& id) const
|
|||||||
return m_settings[id];
|
return m_settings[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant SettingsObject::get(const QString& id) const
|
QVariant SettingsObject::get(const QString& id)
|
||||||
{
|
{
|
||||||
auto setting = getSetting(id);
|
auto setting = getSetting(id);
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
// for macOS, use a security scoped bookmark for the paths
|
||||||
|
if (id.endsWith("Dir")) {
|
||||||
|
return { getPathFromBookmark(id) };
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return (setting ? setting->get() : QVariant());
|
return (setting ? setting->get() : QVariant());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,11 +104,105 @@ bool SettingsObject::set(const QString& id, QVariant value)
|
|||||||
if (!setting) {
|
if (!setting) {
|
||||||
qCritical() << QString("Error changing setting %1. Setting doesn't exist.").arg(id);
|
qCritical() << QString("Error changing setting %1. Setting doesn't exist.").arg(id);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
}
|
||||||
setting->set(value);
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
// for macOS, keep a security scoped bookmark for the paths
|
||||||
|
if (value.userType() == QMetaType::QString && id.endsWith("Dir")) {
|
||||||
|
setPathWithBookmark(id, value.toString());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
setting->set(std::move(value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
QString SettingsObject::getPathFromBookmark(const QString& id)
|
||||||
|
{
|
||||||
|
auto setting = getSetting(id);
|
||||||
|
if (!setting) {
|
||||||
|
qCritical() << QString("Error changing setting %1. Setting doesn't exist.").arg(id);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// there is no need to use bookmarks if the default value is used or the directory is within the data directory (already can access)
|
||||||
|
if (setting->get() == setting->defValue() || QDir(setting->get().toString()).absolutePath().startsWith(QDir::current().absolutePath())) {
|
||||||
|
return setting->get().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bookmarkId = id + "Bookmark";
|
||||||
|
auto bookmarkSetting = getSetting(bookmarkId);
|
||||||
|
if (!bookmarkSetting) {
|
||||||
|
qCritical() << QString("Error changing setting %1. Bookmark setting doesn't exist.").arg(id);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray bookmark = bookmarkSetting->get().toByteArray();
|
||||||
|
if (bookmark.isEmpty()) {
|
||||||
|
qDebug() << "Creating bookmark for" << id << "at" << setting->get().toString();
|
||||||
|
setPathWithBookmark(id, setting->get().toString());
|
||||||
|
return setting->get().toString();
|
||||||
|
}
|
||||||
|
bool stale;
|
||||||
|
QUrl url = m_sandboxedFileAccess.securityScopedBookmarkToURL(bookmark, stale);
|
||||||
|
if (url.isValid()) {
|
||||||
|
if (stale) {
|
||||||
|
setting->set(url.path());
|
||||||
|
bookmarkSetting->set(bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sandboxedFileAccess.startUsingSecurityScopedBookmark(bookmark, stale);
|
||||||
|
// already did a stale check, no need to do it again
|
||||||
|
|
||||||
|
// convert to relative path to current directory if `url` is a descendant of the current directory
|
||||||
|
QDir currentDir = QDir::current().absolutePath();
|
||||||
|
return url.path().startsWith(currentDir.absolutePath()) ? currentDir.relativeFilePath(url.path()) : url.path();
|
||||||
|
}
|
||||||
|
|
||||||
|
return setting->get().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SettingsObject::setPathWithBookmark(const QString& id, const QString& path)
|
||||||
|
{
|
||||||
|
auto setting = getSetting(id);
|
||||||
|
if (!setting) {
|
||||||
|
qCritical() << QString("Error changing setting %1. Setting doesn't exist.").arg(id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDir dir(path);
|
||||||
|
if (!dir.exists()) {
|
||||||
|
qCritical() << QString("Error changing setting %1. Path doesn't exist.").arg(id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QString absolutePath = dir.absolutePath();
|
||||||
|
QString bookmarkId = id + "Bookmark";
|
||||||
|
std::shared_ptr<Setting> bookmarkSetting = getSetting(bookmarkId);
|
||||||
|
// there is no need to use bookmarks if the default value is used or the directory is within the data directory (already can access)
|
||||||
|
if (path == setting->defValue().toString() || absolutePath.startsWith(QDir::current().absolutePath())) {
|
||||||
|
bookmarkSetting->reset();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
QByteArray bytes = m_sandboxedFileAccess.pathToSecurityScopedBookmark(absolutePath);
|
||||||
|
if (bytes.isEmpty()) {
|
||||||
|
qCritical() << QString("Failed to create bookmark for %1 - no access?").arg(id);
|
||||||
|
// TODO: show an alert to the user asking them to reselect the directory
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto oldBookmark = bookmarkSetting->get().toByteArray();
|
||||||
|
m_sandboxedFileAccess.stopUsingSecurityScopedBookmark(oldBookmark);
|
||||||
|
if (!bytes.isEmpty() && bookmarkSetting) {
|
||||||
|
bookmarkSetting->set(bytes);
|
||||||
|
bool stale;
|
||||||
|
m_sandboxedFileAccess.startUsingSecurityScopedBookmark(bytes, stale);
|
||||||
|
// just created the bookmark, it shouldn't be stale
|
||||||
|
}
|
||||||
|
|
||||||
|
setting->set(path);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void SettingsObject::reset(const QString& id) const
|
void SettingsObject::reset(const QString& id) const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,6 +23,10 @@
|
|||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
#include "macsandbox/SecurityBookmarkFileAccess.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
class Setting;
|
class Setting;
|
||||||
class SettingsObject;
|
class SettingsObject;
|
||||||
|
|
||||||
@@ -119,7 +123,27 @@ class SettingsObject : public QObject {
|
|||||||
* \return The setting's value as a QVariant.
|
* \return The setting's value as a QVariant.
|
||||||
* If no setting with the given ID exists, returns an invalid QVariant.
|
* If no setting with the given ID exists, returns an invalid QVariant.
|
||||||
*/
|
*/
|
||||||
QVariant get(const QString& id) const;
|
QVariant get(const QString& id);
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
/*!
|
||||||
|
* \brief Get the path to the file or directory represented by the bookmark stored in the associated setting.
|
||||||
|
* \param id The setting ID of the relevant directory - this should not include "Bookmark" at the end.
|
||||||
|
* \return A path to the file or directory represented by the bookmark.
|
||||||
|
* If a bookmark is not valid or stored, use default logic (directly return the stored path).
|
||||||
|
* This can attempt to create a bookmark if the path is accessible and the bookmark is not valid.
|
||||||
|
*/
|
||||||
|
QString getPathFromBookmark(const QString& id);
|
||||||
|
/*!
|
||||||
|
* \brief Set a security-scoped bookmark to the provided path for the associated setting.
|
||||||
|
* \param id The setting ID of the relevant directory - this should not include "Bookmark" at the end.
|
||||||
|
* \param path The new desired path.
|
||||||
|
* \return A boolean indicating whether a bookmark was successfully set.
|
||||||
|
* The path needs to be accessible to the launcher before calling this function. For example,
|
||||||
|
* it could come from a user selection in an open panel.
|
||||||
|
*/
|
||||||
|
bool setPathWithBookmark(const QString& id, const QString& path);
|
||||||
|
#endif
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Sets the value of the setting with the given ID.
|
* \brief Sets the value of the setting with the given ID.
|
||||||
@@ -207,6 +231,9 @@ class SettingsObject : public QObject {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
QMap<QString, std::shared_ptr<Setting>> m_settings;
|
QMap<QString, std::shared_ptr<Setting>> m_settings;
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
SecurityBookmarkFileAccess m_sandboxedFileAccess;
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool m_suspendSave = false;
|
bool m_suspendSave = false;
|
||||||
|
|||||||
@@ -488,7 +488,7 @@ void MinecraftSettingsWidget::openGlobalSettings()
|
|||||||
APPLICATION->ShowGlobalSettings(this, "minecraft-settings");
|
APPLICATION->ShowGlobalSettings(this, "minecraft-settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
void MinecraftSettingsWidget::updateAccountsMenu(const SettingsObject& settings)
|
void MinecraftSettingsWidget::updateAccountsMenu(SettingsObject& settings)
|
||||||
{
|
{
|
||||||
m_ui->instanceAccountSelector->clear();
|
m_ui->instanceAccountSelector->clear();
|
||||||
auto accounts = APPLICATION->accounts();
|
auto accounts = APPLICATION->accounts();
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class MinecraftSettingsWidget : public QWidget {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void openGlobalSettings();
|
void openGlobalSettings();
|
||||||
void updateAccountsMenu(const SettingsObject& settings);
|
void updateAccountsMenu(SettingsObject& settings);
|
||||||
bool isQuickPlaySupported();
|
bool isQuickPlaySupported();
|
||||||
private slots:
|
private slots:
|
||||||
void saveSelectedLoaders();
|
void saveSelectedLoaders();
|
||||||
|
|||||||
Reference in New Issue
Block a user