"Open Recent" Menu on Mac, Weird Behavior

I’m using JUCE 5.4.7 and using Mac OS X Catalina with XCode 11.3.1. I’m building a sandboxed app and trying to get the “Open Recent” menu to work. I’m using this line of code when the menu is created:

juce::MenuBarModel::setMacMainMenu(mainMenuModel.get(), &macAppMenu, TRANS(“Open Recent”));

When the app runs and the menu is created, I get the application menu (the one next to the Apple icon) and the “File” menu (under which the “Open Recent” menu is supposed to appear as a submenu), but nothing else. (The app has “File”, “Edit”, “Insert”, “View”, “Media” and “Help” menus but only the “File” menu shows.) Neither of the two menus opens up when I click on them (they are both empty). The app doesn’t crash and otherwise runs fine, with the exception that its app menu isn’t working.

If I remove the third argument to setMacMainMenu() everything works fine and all of our menus and submenus appear (but if I select a file from our own “Recent Files” menu that we wrote ourselves, the OS won’t let the app open the file). I’m also using RecentlyOpenedFilesList::registerRecentFileNatively() to register files that the user opens. I see a “RecentFilesMenuTemplate.nib” in my project directory and a little debugging showed me that it’s being loaded (juce_Mac_MainMenu.mm line 363 returns an NSNib* object). There are a few forum topics from 2012 about this but I can’t find any answers there.

Does anyone have any idea what I could be doing wrong?

For anyone else trying to solve this issue: JUCE’s solution to the recent files menu seems to be a kludge that doesn’t work… I got this working perfectly using “bookmarks” and our own implementation of the recent files menu.

How are you able to retrieve the bookmark for a file on Mac, though?

Here’s the interface I came up with for managing them (hope it’s helpful–it’ll at least show you the objc calls to retrieve/activate them). The Apple docs were helpful–if I remember correctly you need to add an entitlement to your app for it to work… I store the bookmarks themselves as MemoryBlock objects in the app’s preferences file:

This is basically just a unique_ptr-type wrapper around Apple’s NSURL object (the url gets stored when the bookmark is “activated”):

class OSXBookmark {
    void* url = nullptr;

  public:
    OSXBookmark() {}
    ~OSXBookmark();
    bool activateBookmark(juce::MemoryBlock& bookmark);
    void deactivateBookmark();
    static juce::MemoryBlock getBookmarkForFile(const juce::String& filename);
    OSXBookmark(const OSXBookmark&) = delete;
    OSXBookmark(OSXBookmark&& other) : url(other.url) { other.url = nullptr; }
    OSXBookmark& operator=(const OSXBookmark& other) = delete;
    OSXBookmark& operator=(OSXBookmark&& other) {
	url = other.url;
	other.url = nullptr;
	return *this;
    }
};

Function to get the bookmark as a MemoryBlock:

juce::MemoryBlock OSXBookmark::getBookmarkForFile(const juce::String& filename) {
    @autoreleasepool {
	NSURL* u = [NSURL fileURLWithPath:[NSString stringWithUTF8String:filename.toRawUTF8()]];
	NSData* d = [u bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:nil];
	return d ? juce::MemoryBlock{[d bytes], [d length]} : juce::MemoryBlock{};
    }
}

Function to activate the bookmark using the MemoryBlock data (memory block might be replaced with new bookmark data):

bool OSXBookmark::activateBookmark(juce::MemoryBlock& bookmark) {
    @autoreleasepool {
	bool r = false;
	if (!url) {
	    NSData* d = [NSData dataWithBytes:bookmark.getData() length:bookmark.getSize()];
	    if (!d)
		return false;
	    BOOL st = NO;
	    url = [[NSURL URLByResolvingBookmarkData:d options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&st error:nil] retain]; // done when app is relaunched
	    if (st) {
		d = [(NSURL*)url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:nil];
		if (d) {
		    bookmark.replaceWith([d bytes], [d length]);
		    r = true;
		}
	    }
	}
	[(NSURL*)url startAccessingSecurityScopedResource];
	return r;
    }
}

void OSXBookmark::deactivateBookmark() {
    @autoreleasepool {
	[(NSURL*)url stopAccessingSecurityScopedResource];
    }
}

Destructor:

OSXBookmark::~OSXBookmark() {
    @autoreleasepool {
	[(NSURL*)url release];
    }
}

Thanks so much. I think this is just what I was looking for!