Hi ![]()
I want to use a system tray icon with a native macOS menu, which is done by using the showDropdownMenu() function.
My issue is that when calling JUCEApplication::getInstance()->systemRequestedQuit(); from one of the menu items of the system tray icon, the message dispatch loop hangs until the program is clicked again (either the tray icon or the edit: clicking anywhere in the screen actually closes the program). Note that the same function called from a non-native system tray icon menu, or from the macOS menu on the top left, does not have the same issue.
In the debugger I can see the call to shutdownNSApp(); at juce_MessageManager_mac.mm:352 which seem to be fine. But then the program is stuck in [NSApp run]; of runDispatchLoop() (same file at line 335).
Just FYI, there is no example anywhere on how to use SystemTrayIconComponent::showDropdownMenu so I found my way - which might be wrong and might be the cause of this issue. If not, then it must be a bug somewhere in the JUCE code.
Here’s a test project:
test-project-with-issue.zip (3.1 KB)
Thank you!
Simon
PS: this is a continuation of a previous post, though the topic is slightly different and the subject name does not match anymore - so I feel it deserves its own topic.
PPS: Just for easy reference, here is the Main.cpp from the upload above:
#include <JuceHeader.h>
class TrayIconComponent : public juce::SystemTrayIconComponent,
private juce::Timer
{
public:
TrayIconComponent() {
juce::Image icon(juce::Image::ARGB, 32, 32, true);
juce::Graphics g(icon);
g.fillAll(juce::Colours::transparentBlack);
g.setColour(juce::Colours::white);
g.setFont(24.0f); // Adjust the font size as needed
juce::String text = "T";
g.drawText(text, 0, 0, 32, 32, juce::Justification::centredLeft, true);
setIconImage(icon, icon);
}
/// @brief This function handles the mouse click event on the system tray icon
/// @param event
void mouseDown(const juce::MouseEvent &event) override
{
juce::Process::makeForegroundProcess();
startTimer(50); // like the JUCE demo, we use a timer to give a bit of time before showing the menu
}
private:
void timerCallback() override {
stopTimer();
juce::PopupMenu m;
m.addItem("Quit", [] () { juce::JUCEApplication::getInstance()->systemRequestedQuit(); } );
juce::MessageManager::callAsync([this, m]() { showDropdownMenu(m); });
}
class MyMenuBarModel final : public juce::MenuBarModel
{
public:
MyMenuBarModel() {
juce::PopupMenu m;
m.addItem("Quit", [] () { juce::JUCEApplication::getInstance()->systemRequestedQuit(); } );
juce::MenuBarModel::setMacMainMenu(this, &m);
}
~MyMenuBarModel() override {
juce::MenuBarModel::setMacMainMenu (nullptr);
}
juce::StringArray getMenuBarNames() override { return {""}; }
juce::PopupMenu getMenuForIndex (int, const juce::String&) override { return juce::PopupMenu(); }
void menuItemSelected (int, int) override { }
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MyMenuBarModel)
};
MyMenuBarModel model;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TrayIconComponent)
};
class MainWindow : public juce::DocumentWindow
{
public:
MainWindow (juce::String name) : DocumentWindow (name, juce::Colours::lightgrey, DocumentWindow::allButtons) {
setUsingNativeTitleBar (true);
centreWithSize (400, 400);
setResizable (true, true);
setVisible (true);
}
void closeButtonPressed() override {
juce::JUCEApplication::getInstance()->systemRequestedQuit();
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
};
class NewProjectApplication : public juce::JUCEApplication
{
public:
NewProjectApplication() {}
const juce::String getApplicationName() override { return ProjectInfo::projectName; }
const juce::String getApplicationVersion() override { return ProjectInfo::versionString; }
bool moreThanOneInstanceAllowed() override { return false; }
//==============================================================================
void initialise (const juce::String& commandLine) override
{
mainWindow.reset (new MainWindow (getApplicationName()));
trayIconComponent.reset(new TrayIconComponent());
}
void shutdown() override
{
trayIconComponent = nullptr;
mainWindow = nullptr;
}
//==============================================================================
void systemRequestedQuit() override
{
// This is called when the app is being asked to quit: you can ignore this
// request and let the app carry on running, or call quit() to allow the app to close.
quit();
}
void anotherInstanceStarted (const juce::String& commandLine) override
{
// When another instance of the app is launched while this one is running,
// this method is invoked, and the commandLine parameter tells you what
// the other instance's command-line arguments were.
}
private:
std::unique_ptr<MainWindow> mainWindow;
std::unique_ptr<TrayIconComponent> trayIconComponent;
};
//==============================================================================
// This macro generates the main() routine that launches the app.
START_JUCE_APPLICATION (NewProjectApplication)
