1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-20 13:05:07 +00:00

Ladybird+LibCore: Use QCoreApplication to drive the main Qt event loop

Using QEventLoop works for everything but it breaks *one* little feature
that we care about: automatically quitting the app when all windows have
been closed.

That only works if you drive the outermost main event loop with a
QCoreApplication instead of a QEventLoop. This is unfortunate, as it
complicates our API a little bit, but I'm sure we can think of a way to
make this nicer someday.

In order for QCoreApplication::exec() to process our own
ThreadEventQueue, we now have a zero-timer that we kick whenever new
events are posted to the thread queue.
This commit is contained in:
Andreas Kling 2023-04-25 16:53:07 +02:00
parent 0f22dfa634
commit c21eb30a2b
9 changed files with 79 additions and 25 deletions

View file

@ -10,6 +10,7 @@
#include <LibCore/Notifier.h> #include <LibCore/Notifier.h>
#include <LibCore/Object.h> #include <LibCore/Object.h>
#include <LibCore/ThreadEventQueue.h> #include <LibCore/ThreadEventQueue.h>
#include <QCoreApplication>
#include <QTimer> #include <QTimer>
namespace Ladybird { namespace Ladybird {
@ -34,38 +35,48 @@ struct ThreadData {
EventLoopImplementationQt::EventLoopImplementationQt() EventLoopImplementationQt::EventLoopImplementationQt()
{ {
m_process_core_events_timer.setSingleShot(true);
m_process_core_events_timer.setInterval(0);
QObject::connect(&m_process_core_events_timer, &QTimer::timeout, [] {
Core::ThreadEventQueue::current().process();
});
} }
EventLoopImplementationQt::~EventLoopImplementationQt() = default; EventLoopImplementationQt::~EventLoopImplementationQt() = default;
int EventLoopImplementationQt::exec() int EventLoopImplementationQt::exec()
{ {
// NOTE: We don't use QEventLoop::exec() here since it wouldn't process the Core::ThreadEventQueue. if (is_main_loop())
while (!m_exit_code.has_value()) { return QCoreApplication::exec();
pump(PumpMode::WaitForEvents); return m_event_loop.exec();
}
return m_exit_code.value();
} }
size_t EventLoopImplementationQt::pump(PumpMode mode) size_t EventLoopImplementationQt::pump(PumpMode mode)
{ {
bool result = Core::ThreadEventQueue::current().process() != 0; auto result = Core::ThreadEventQueue::current().process();
if (mode == PumpMode::WaitForEvents) if (mode == PumpMode::WaitForEvents) {
result |= m_event_loop.processEvents(QEventLoop::WaitForMoreEvents); if (is_main_loop())
else QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
result |= m_event_loop.processEvents(); else
Core::ThreadEventQueue::current().process(); m_event_loop.processEvents(QEventLoop::WaitForMoreEvents);
} else {
}
result += Core::ThreadEventQueue::current().process();
return result; return result;
} }
void EventLoopImplementationQt::quit(int code) void EventLoopImplementationQt::quit(int code)
{ {
m_exit_code = code; if (is_main_loop())
QCoreApplication::exit(code);
else
m_event_loop.exit(code);
} }
void EventLoopImplementationQt::wake() void EventLoopImplementationQt::wake()
{ {
m_event_loop.wakeUp(); if (!is_main_loop())
m_event_loop.wakeUp();
} }
void EventLoopImplementationQt::deferred_invoke(Function<void()> function) void EventLoopImplementationQt::deferred_invoke(Function<void()> function)
@ -76,6 +87,16 @@ void EventLoopImplementationQt::deferred_invoke(Function<void()> function)
}); });
} }
static void qt_timer_fired(int timer_id, Core::TimerShouldFireWhenNotVisible should_fire_when_not_visible, Core::Object& object)
{
if (should_fire_when_not_visible == Core::TimerShouldFireWhenNotVisible::No) {
if (!object.is_visible_for_timer_purposes())
return;
}
Core::TimerEvent event(timer_id);
object.dispatch_event(event);
}
int EventLoopImplementationQt::register_timer(Core::Object& object, int milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible should_fire_when_not_visible) int EventLoopImplementationQt::register_timer(Core::Object& object, int milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible should_fire_when_not_visible)
{ {
auto& thread_data = ThreadData::the(); auto& thread_data = ThreadData::the();
@ -88,11 +109,7 @@ int EventLoopImplementationQt::register_timer(Core::Object& object, int millisec
auto object = weak_object.strong_ref(); auto object = weak_object.strong_ref();
if (!object) if (!object)
return; return;
if (should_fire_when_not_visible == Core::TimerShouldFireWhenNotVisible::No) { qt_timer_fired(timer_id, should_fire_when_not_visible, *object);
if (!object->is_visible_for_timer_purposes())
return;
}
Core::ThreadEventQueue::current().post_event(*object, make<Core::TimerEvent>(timer_id));
}); });
timer->start(); timer->start();
thread_data.timers.set(timer_id, move(timer)); thread_data.timers.set(timer_id, move(timer));
@ -106,6 +123,12 @@ bool EventLoopImplementationQt::unregister_timer(int timer_id)
return thread_data.timers.remove(timer_id); return thread_data.timers.remove(timer_id);
} }
static void qt_notifier_activated(Core::Notifier& notifier)
{
Core::NotifierActivationEvent event(notifier.fd());
notifier.dispatch_event(event);
}
void EventLoopImplementationQt::register_notifier(Core::Notifier& notifier) void EventLoopImplementationQt::register_notifier(Core::Notifier& notifier)
{ {
QSocketNotifier::Type type; QSocketNotifier::Type type;
@ -120,8 +143,8 @@ void EventLoopImplementationQt::register_notifier(Core::Notifier& notifier)
TODO(); TODO();
} }
auto socket_notifier = make<QSocketNotifier>(notifier.fd(), type); auto socket_notifier = make<QSocketNotifier>(notifier.fd(), type);
QObject::connect(socket_notifier, &QSocketNotifier::activated, [fd = notifier.fd(), &notifier] { QObject::connect(socket_notifier, &QSocketNotifier::activated, [&notifier] {
Core::ThreadEventQueue::current().post_event(notifier, make<Core::NotifierActivationEvent>(fd)); qt_notifier_activated(notifier);
}); });
ThreadData::the().notifiers.set(&notifier, move(socket_notifier)); ThreadData::the().notifiers.set(&notifier, move(socket_notifier));
@ -132,4 +155,9 @@ void EventLoopImplementationQt::unregister_notifier(Core::Notifier& notifier)
ThreadData::the().notifiers.remove(&notifier); ThreadData::the().notifiers.remove(&notifier);
} }
void EventLoopImplementationQt::did_post_event()
{
m_process_core_events_timer.start();
}
} }

View file

@ -35,6 +35,8 @@ public:
virtual void register_notifier(Core::Notifier&) override; virtual void register_notifier(Core::Notifier&) override;
virtual void unregister_notifier(Core::Notifier&) override; virtual void unregister_notifier(Core::Notifier&) override;
virtual void did_post_event() override;
// FIXME: These APIs only exist for obscure use-cases inside SerenityOS. Try to get rid of them. // FIXME: These APIs only exist for obscure use-cases inside SerenityOS. Try to get rid of them.
virtual void unquit() override { } virtual void unquit() override { }
virtual bool was_exit_requested() const override { return false; } virtual bool was_exit_requested() const override { return false; }
@ -42,12 +44,15 @@ public:
virtual int register_signal(int, Function<void(int)>) override { return 0; } virtual int register_signal(int, Function<void(int)>) override { return 0; }
virtual void unregister_signal(int) override { } virtual void unregister_signal(int) override { }
protected: void set_main_loop() { m_main_loop = true; }
EventLoopImplementationQt();
private: private:
EventLoopImplementationQt();
bool is_main_loop() const { return m_main_loop; }
QEventLoop m_event_loop; QEventLoop m_event_loop;
Optional<int> m_exit_code; QTimer m_process_core_events_timer;
bool m_main_loop { false };
}; };
} }

View file

@ -56,6 +56,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
Core::EventLoop::make_implementation = Ladybird::EventLoopImplementationQt::create; Core::EventLoop::make_implementation = Ladybird::EventLoopImplementationQt::create;
Core::EventLoop event_loop; Core::EventLoop event_loop;
static_cast<Ladybird::EventLoopImplementationQt&>(event_loop.impl()).set_main_loop();
TRY(handle_attached_debugger()); TRY(handle_attached_debugger());

View file

@ -170,4 +170,9 @@ bool EventLoop::was_exit_requested() const
return m_impl->was_exit_requested(); return m_impl->was_exit_requested();
} }
void EventLoop::did_post_event(Badge<Core::ThreadEventQueue>)
{
m_impl->did_post_event();
}
} }

View file

@ -19,6 +19,7 @@
namespace Core { namespace Core {
class EventLoopImplementation; class EventLoopImplementation;
class ThreadEventQueue;
// The event loop enables asynchronous (not parallel or multi-threaded) computing by efficiently handling events from various sources. // The event loop enables asynchronous (not parallel or multi-threaded) computing by efficiently handling events from various sources.
// Event loops are most important for GUI programs, where the various GUI updates and action callbacks run on the EventLoop, // Event loops are most important for GUI programs, where the various GUI updates and action callbacks run on the EventLoop,
@ -95,6 +96,9 @@ public:
static Function<NonnullOwnPtr<EventLoopImplementation>()> make_implementation; static Function<NonnullOwnPtr<EventLoopImplementation>()> make_implementation;
void did_post_event(Badge<ThreadEventQueue>);
EventLoopImplementation& impl() { return *m_impl; }
private: private:
void wait_for_event(WaitMode); void wait_for_event(WaitMode);
Optional<Time> get_next_timer_expiration(); Optional<Time> get_next_timer_expiration();

View file

@ -37,6 +37,8 @@ public:
virtual void register_notifier(Notifier&) = 0; virtual void register_notifier(Notifier&) = 0;
virtual void unregister_notifier(Notifier&) = 0; virtual void unregister_notifier(Notifier&) = 0;
virtual void did_post_event() = 0;
// FIXME: These APIs only exist for obscure use-cases inside SerenityOS. Try to get rid of them. // FIXME: These APIs only exist for obscure use-cases inside SerenityOS. Try to get rid of them.
virtual void unquit() = 0; virtual void unquit() = 0;
virtual bool was_exit_requested() const = 0; virtual bool was_exit_requested() const = 0;

View file

@ -522,4 +522,8 @@ void EventLoopImplementationUnix::unregister_notifier(Notifier& notifier)
ThreadData::the().notifiers.remove(&notifier); ThreadData::the().notifiers.remove(&notifier);
} }
void EventLoopImplementationUnix::did_post_event()
{
}
} }

View file

@ -31,6 +31,8 @@ public:
virtual void register_notifier(Notifier&) override; virtual void register_notifier(Notifier&) override;
virtual void unregister_notifier(Notifier&) override; virtual void unregister_notifier(Notifier&) override;
virtual void did_post_event() override;
virtual void unquit() override; virtual void unquit() override;
virtual bool was_exit_requested() const override; virtual bool was_exit_requested() const override;
virtual void notify_forked_and_in_child() override; virtual void notify_forked_and_in_child() override;

View file

@ -62,8 +62,11 @@ ThreadEventQueue::~ThreadEventQueue() = default;
void ThreadEventQueue::post_event(Core::Object& receiver, NonnullOwnPtr<Core::Event> event) void ThreadEventQueue::post_event(Core::Object& receiver, NonnullOwnPtr<Core::Event> event)
{ {
Threading::MutexLocker lock(m_private->mutex); {
m_private->queued_events.empend(receiver, move(event)); Threading::MutexLocker lock(m_private->mutex);
m_private->queued_events.empend(receiver, move(event));
}
Core::EventLoop::current().did_post_event({});
} }
void ThreadEventQueue::add_job(NonnullRefPtr<Promise<NonnullRefPtr<Object>>> promise) void ThreadEventQueue::add_job(NonnullRefPtr<Promise<NonnullRefPtr<Object>>> promise)