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("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
|
||||
m_settings->registerSetting("JsonEditor", QString());
|
||||
|
||||
@@ -956,12 +966,27 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
// Themes
|
||||
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
|
||||
{
|
||||
auto InstDirSetting = m_settings->getSetting("InstanceDir");
|
||||
// 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)
|
||||
QString instDir = InstDirSetting->get().toString();
|
||||
QString instDir = m_settings->get("InstanceDir").toString();
|
||||
qInfo() << "Instance path : " << instDir;
|
||||
if (FS::checkProblemticPathJava(QDir(instDir))) {
|
||||
qWarning() << "Your instance path contains \'!\' and this is known to cause java problems!";
|
||||
|
||||
@@ -1173,6 +1173,15 @@ if (NOT Apple)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
set(LAUNCHER_SOURCES
|
||||
${LAUNCHER_SOURCES}
|
||||
|
||||
macsandbox/SecurityBookmarkFileAccess.h
|
||||
macsandbox/SecurityBookmarkFileAccess.mm
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(LAUNCHER_SOURCES
|
||||
console/WindowsConsole.h
|
||||
|
||||
89
launcher/macsandbox/SecurityBookmarkFileAccess.h
Normal file
89
launcher/macsandbox/SecurityBookmarkFileAccess.h
Normal file
@@ -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
|
||||
172
launcher/macsandbox/SecurityBookmarkFileAccess.mm
Normal file
172
launcher/macsandbox/SecurityBookmarkFileAccess.mm
Normal file
@@ -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 <QVariant>
|
||||
#include <QDir>
|
||||
#include <utility>
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
#include "macsandbox/SecurityBookmarkFileAccess.h"
|
||||
#endif
|
||||
|
||||
SettingsObject::SettingsObject(QObject* parent) : QObject(parent) {}
|
||||
|
||||
@@ -78,9 +84,17 @@ std::shared_ptr<Setting> SettingsObject::getSetting(const QString& id) const
|
||||
return m_settings[id];
|
||||
}
|
||||
|
||||
QVariant SettingsObject::get(const QString& id) const
|
||||
QVariant SettingsObject::get(const QString& 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());
|
||||
}
|
||||
|
||||
@@ -90,11 +104,105 @@ bool SettingsObject::set(const QString& id, QVariant value)
|
||||
if (!setting) {
|
||||
qCritical() << QString("Error changing setting %1. Setting doesn't exist.").arg(id);
|
||||
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;
|
||||
}
|
||||
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
|
||||
{
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
#include <QVariant>
|
||||
#include <memory>
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
#include "macsandbox/SecurityBookmarkFileAccess.h"
|
||||
#endif
|
||||
|
||||
class Setting;
|
||||
class SettingsObject;
|
||||
|
||||
@@ -119,7 +123,27 @@ class SettingsObject : public QObject {
|
||||
* \return The setting's value as a 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.
|
||||
@@ -207,6 +231,9 @@ class SettingsObject : public QObject {
|
||||
|
||||
private:
|
||||
QMap<QString, std::shared_ptr<Setting>> m_settings;
|
||||
#ifdef Q_OS_MACOS
|
||||
SecurityBookmarkFileAccess m_sandboxedFileAccess;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool m_suspendSave = false;
|
||||
|
||||
@@ -488,7 +488,7 @@ void MinecraftSettingsWidget::openGlobalSettings()
|
||||
APPLICATION->ShowGlobalSettings(this, "minecraft-settings");
|
||||
}
|
||||
|
||||
void MinecraftSettingsWidget::updateAccountsMenu(const SettingsObject& settings)
|
||||
void MinecraftSettingsWidget::updateAccountsMenu(SettingsObject& settings)
|
||||
{
|
||||
m_ui->instanceAccountSelector->clear();
|
||||
auto accounts = APPLICATION->accounts();
|
||||
|
||||
@@ -54,7 +54,7 @@ class MinecraftSettingsWidget : public QWidget {
|
||||
|
||||
private:
|
||||
void openGlobalSettings();
|
||||
void updateAccountsMenu(const SettingsObject& settings);
|
||||
void updateAccountsMenu(SettingsObject& settings);
|
||||
bool isQuickPlaySupported();
|
||||
private slots:
|
||||
void saveSelectedLoaders();
|
||||
|
||||
Reference in New Issue
Block a user