After much digging around and tweaking, I believe have arrived to a solution for giving a sandboxed mac app permission to save/read data files to directories other than those supplied normally, like Documents or Music folders. I borrowed the code somewhere (unfortunately I don’t remember where) and adjusted it so that it worked cleanly with JUCE.
It needed to use the “bookmark location” class from the native mac framework.
Here is the code I ended up with.
Functions.h
/*
==============================================================================
Functions.h
Created: 11 Dec 2023 9:25:15am
Author: Jacques Mignault
==============================================================================
*/
#pragma once
#ifndef BOOKMARKLOCATION_HPP
#define BOOKMARKLOCATION_HPP
#include <string>
/**
* Class BookmarkLocation
*
* This class provides an interface to handle security-scoped bookmarks in a macOS
* sandboxed environment. It allows the user to choose a folder, create a security-scoped
* bookmark for it, and save/load this bookmark to/from a file. Additionally, it manages
* access to the folder using the bookmark.
*/
class BookmarkLocation {
public:
/**
* Constructor
*/
BookmarkLocation();
/**
* Destructor
*/
~BookmarkLocation();
/**
* Saves the security-scoped bookmark of the chosen folder to a specified file.
*
* @param bookmarkPath The file path where the bookmark data will be saved.
* @return true if the bookmark is successfully saved, false otherwise.
*/
bool saveBookmark(const std::string& bookmarkPath);
/**
* Loads a security-scoped bookmark from a specified file and resolves it to a URL.
*
* @param bookmarkPath The file path from where the bookmark data will be loaded.
* @return The path of the bookmarked folder if successful, an empty string otherwise.
*/
std::string loadBookmark(const std::string& bookmarkPath);
/**
* Attempts to start accessing the resource associated with the current bookmark.
* Call this method before trying to access the folder's contents.
*/
void startAccess();
/**
* Stops accessing the resource associated with the current bookmark.
* Always call this method after you're done accessing the folder's contents.
*/
void stopAccess();
/**
* Retrieves the path of the currently selected folder.
*
* @return A std::string representing the path of the selected folder.
* Returns an empty string if no folder is selected.
*/
std::string getSelectedFolderPath() const;
void setSelectedFolderPath(const std::string folderPath);
private:
/** Implementation class forward declaration */
class Impl;
/** Pointer to the implementation class */
Impl* pImpl;
};
#endif // BOOKMARKLOCATION_HPP
Functions.mm
/*
==============================================================================
Functions.mm
Created: 11 Dec 2023 9:24:40am
Author: Jacques Mignault
==============================================================================
*/
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "Functions.hpp"
class BookmarkLocation::Impl {
public:
NSURL *folderURL;
NSData *bookmarkData;
Impl() : folderURL(nil), bookmarkData(nil) {}
~Impl() {
[folderURL release];
[bookmarkData release];
}
bool saveBookmark(const std::string& bookmarkPath) {
NSError *error = nil;
bookmarkData = [[folderURL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:nil
relativeToURL:nil
error:&error] retain];
if (bookmarkData) {
[bookmarkData writeToFile:[NSString stringWithUTF8String:bookmarkPath.c_str()] atomically:YES];
return true;
}
return false;
}
std::string loadBookmark(const std::string& bookmarkPath) {
NSData *data = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:bookmarkPath.c_str()]];
NSError *error = nil;
BOOL isStale;
folderURL = [[NSURL URLByResolvingBookmarkData:data
options:NSURLBookmarkResolutionWithSecurityScope
relativeToURL:nil
bookmarkDataIsStale:&isStale
error:&error] retain];
if (folderURL && !isStale) {
return [[folderURL path] UTF8String];
}
return "";
}
void startAccess() {
[folderURL startAccessingSecurityScopedResource];
}
void stopAccess() {
[folderURL stopAccessingSecurityScopedResource];
}
// Inside the Impl class
std::string getSelectedFolderPath() const {
if (folderURL) {
return [[folderURL path] UTF8String];
}
return "";
}
void setSelectedFolderPath(const std::string folderPath) {
NSString *objcString = [NSString stringWithUTF8String:folderPath.c_str()];
folderURL = [NSURL fileURLWithPath:objcString];
}
};
// C++ class implementation using the Pimpl idiom
BookmarkLocation::BookmarkLocation() : pImpl(new Impl()) {}
BookmarkLocation::~BookmarkLocation() { delete pImpl; }
//bool BookmarkLocation::chooseFolder() {
// return pImpl->chooseFolder();
//}
bool BookmarkLocation::saveBookmark(const std::string& bookmarkPath) {
return pImpl->saveBookmark(bookmarkPath);
}
std::string BookmarkLocation::loadBookmark(const std::string& bookmarkPath) {
return pImpl->loadBookmark(bookmarkPath);
}
void BookmarkLocation::startAccess() {
pImpl->startAccess();
}
void BookmarkLocation::stopAccess() {
pImpl->stopAccess();
}
// Corresponding method in BookmarkLocation class
std::string BookmarkLocation::getSelectedFolderPath() const {
return pImpl->getSelectedFolderPath();
}
void BookmarkLocation::setSelectedFolderPath(const std::string folderPath) {
return pImpl->setSelectedFolderPath(folderPath);
}
You need to include the hpp file, making sure you’re on a mac.
#if JUCE_MAC
#include "Functions.hpp"
#endif
Then simply create a BookmarkLocation object in your header:
BookmarkLocation bookmark_location;
Afterwards you can use the class like this, where you customSaveFolder was collected from a simple file open dialog:
#if JUCE_MAC
DBG("opening bookmark : " + getFilesFolder().getFullPathName());
bookmark_location.setSelectedFolderPath(customSaveFolder.toStdString());
bookmark_location.loadBookmark(getSettingsFolder().getChildFile("bookmark").getFullPathName().toStdString());
bookmark_location.startAccess();
#endif
// be sure to stopAccess once you're done.
bookmark_location.stopAccess();
Please let me know if you are experiencing issues with this code, or if you find a better possibility.
Cheers !
