0

I need to implement a custom Cocoa main loop using metal-cpp (the source code is also available here). I use an extended version of metal-cpp, so all called below methods were implemented in my version of metal-cpp.

I've create a custom app delegate:

class AppDelegate: public NS::ApplicationDelegate
{
    public:
        AppDelegate(const char* app_name);
        ~AppDelegate();
        
        NS::Menu* createMenuBar();
        virtual void applicationWillFinishLaunching(NS::Notification* notification_ptr) override;
        virtual void applicationDidFinishLaunching(NS::Notification* notification_ptr) override;
        virtual bool applicationShouldTerminateAfterLastWindowClosed(NS::Application* sender_ptr) override;

    private:
        const char* m_app_name;
};

AppDelegate::AppDelegate(const char* app_name):
    m_app_name(app_name)
{
}

AppDelegate::~AppDelegate()
{
}

NS::Menu* AppDelegate::createMenuBar()
{
    // Menu creation
}

void AppDelegate::applicationWillFinishLaunching(NS::Notification* notification_ptr)
{
    NS::Menu* menu_ptr = createMenuBar();
    NS::Application* app_ptr = reinterpret_cast<NS::Application*>(notification_ptr->object());
    app_ptr->setMainMenu(menu_ptr);
    app_ptr->setActivationPolicy(NS::ActivationPolicy::ActivationPolicyRegular);
}

void AppDelegate::applicationDidFinishLaunching(NS::Notification* notification)
{
    NS::Application* application = reinterpret_cast< NS::Application* >( notification->object() );
    application->activateIgnoringOtherApps( true );
}

bool AppDelegate::applicationShouldTerminateAfterLastWindowClosed(NS::Application* sender_ptr)
{
    return false;
}

And a custom application class:

struct AppConfig {
    std::string appName;
};

class App
{
    public:
        App(AppConfig config);
        virtual ~App() {}

        virtual Result      start() = 0;
        virtual Result      update() = 0;
        virtual bool        isFinished() const = 0;
        virtual Result      stop() = 0;
        const AppConfig&    configuration() const;

    protected:
        AppConfig   config;
};

App::App(AppConfig config):
    config(std::move(config))
{
}

const AppConfig& App::configuration() const {
    return config;
}

For macOS the App is implemented by DarwinApp:

class DarwinApp: public App
{
    public:
        DarwinApp(AppConfig config);
        ~DarwinApp();

        Result  start() override;
        Result  update() override;
        bool    isFinished() const override;
        Result  stop() override;
    private:
        bool    m_is_finished;
};

DarwinApp::DarwinApp(AppConfig config):
    App(std::move(config)),
    m_is_finished(true)
{
}

DarwinApp::~DarwinApp()
{
}

Result DarwinApp::start()
{
    auto autorelease_pool = NS::AutoreleasePool::alloc()->init();

    AppDelegate app_delegate(config.appName.c_str());
    NS::Application* application = NS::Application::sharedApplication();
    application->setDelegate(&app_delegate);
    application->finishLaunching();

    autorelease_pool->release();

    m_is_finished = false;

    return b3::SUCCESS;
}

Result DarwinApp::update()
{
    NS::Application* application = NS::Application::sharedApplication();

    NS::String* defaultRunLoopMode = NS::String::string(NS::DefaultRunLoopMode, NS::StringEncoding::ASCIIStringEncoding);

    NS::Event* event = application->nextEvent(NS::EventMask::AnyEventMask, nil, defaultRunLoopMode, true);

    if (event)
    {
        application->sendEvent(event);
        event->release();
    }

    usleep(10000);

    return SUCCESS;
}

bool DarwinApp::isFinished() const
{
    return m_is_finished;
}

Result DarwinApp::stop()
{
    m_is_finished = true;
    return SUCCESS;
}

This solution is based on that answer.

Finally, I implement an event loop:

int main() {
    AppConfig config = {};
    config.appName = "Tim Cook, add C++ support!";
    Application app(config);
    std::cout << "App: initialized" << std::endl;
    app.start();
    std::cout << "App: started" << std::endl;
    do
    {
        app.update();
    } while (!app.isFinished());
    return 0;
}

It works, of course, BUT macOS marks such app as Not responding and it doesn't build app menu (if to call a simple NSApplication instance run() the menu shows normally, everything works).

What's a right way to make the app responsible instead of usleep()?

Or is there a some C++ example of a custom main loop?

P.S. I don't remember already this is which iteration of the code refactoring. I've re-tried a lot of approaches, they all lead me to Not responsible or to blocking run() and stop() doesn't help. I know to stop() worked you need to send a custom UI event, unfortunately, in my case it just blocked me on postEvent(). In short, everywhere I meet some problems. This solution at least runs a custom main loop, so I try to fix Not responsible error just.

P.P.S. Please, don't send me to GLFW sources, I am researching it for few days already. As much as I could understand it and implement using metal-cpp led me to infinite postEvent in the app delegate.

2
  • 2
    Does this answer your question? Custom main application loop in cocoa Commented Mar 10, 2024 at 9:45
  • @Willeke, thank you for the link but I visited it before already. Unfortunately, it requires to call a wrapper for the event loop. I mean we don't build an event loop in main(), we still need to call a NS entrypoint and pass our main loop (run()). I am looking for a way to exclude this wrapper. Commented Mar 10, 2024 at 10:02

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.