From c21eb30a2b80a7e5bc6fece3da3f4bc68e6997cd Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 25 Apr 2023 16:53:07 +0200 Subject: [PATCH] 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. --- Ladybird/EventLoopImplementationQt.cpp | 68 +++++++++++++------ Ladybird/EventLoopImplementationQt.h | 11 ++- Ladybird/main.cpp | 1 + Userland/Libraries/LibCore/EventLoop.cpp | 5 ++ Userland/Libraries/LibCore/EventLoop.h | 4 ++ .../LibCore/EventLoopImplementation.h | 2 + .../LibCore/EventLoopImplementationUnix.cpp | 4 ++ .../LibCore/EventLoopImplementationUnix.h | 2 + .../Libraries/LibCore/ThreadEventQueue.cpp | 7 +- 9 files changed, 79 insertions(+), 25 deletions(-) diff --git a/Ladybird/EventLoopImplementationQt.cpp b/Ladybird/EventLoopImplementationQt.cpp index 26ae64a98b..29e49718ee 100644 --- a/Ladybird/EventLoopImplementationQt.cpp +++ b/Ladybird/EventLoopImplementationQt.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace Ladybird { @@ -34,38 +35,48 @@ struct ThreadData { 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; int EventLoopImplementationQt::exec() { - // NOTE: We don't use QEventLoop::exec() here since it wouldn't process the Core::ThreadEventQueue. - while (!m_exit_code.has_value()) { - pump(PumpMode::WaitForEvents); - } - return m_exit_code.value(); + if (is_main_loop()) + return QCoreApplication::exec(); + return m_event_loop.exec(); } size_t EventLoopImplementationQt::pump(PumpMode mode) { - bool result = Core::ThreadEventQueue::current().process() != 0; - if (mode == PumpMode::WaitForEvents) - result |= m_event_loop.processEvents(QEventLoop::WaitForMoreEvents); - else - result |= m_event_loop.processEvents(); - Core::ThreadEventQueue::current().process(); + auto result = Core::ThreadEventQueue::current().process(); + if (mode == PumpMode::WaitForEvents) { + if (is_main_loop()) + QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); + else + m_event_loop.processEvents(QEventLoop::WaitForMoreEvents); + } else { + } + result += Core::ThreadEventQueue::current().process(); return result; } 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() { - m_event_loop.wakeUp(); + if (!is_main_loop()) + m_event_loop.wakeUp(); } void EventLoopImplementationQt::deferred_invoke(Function function) @@ -76,6 +87,16 @@ void EventLoopImplementationQt::deferred_invoke(Function 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) { auto& thread_data = ThreadData::the(); @@ -88,11 +109,7 @@ int EventLoopImplementationQt::register_timer(Core::Object& object, int millisec auto object = weak_object.strong_ref(); if (!object) return; - if (should_fire_when_not_visible == Core::TimerShouldFireWhenNotVisible::No) { - if (!object->is_visible_for_timer_purposes()) - return; - } - Core::ThreadEventQueue::current().post_event(*object, make(timer_id)); + qt_timer_fired(timer_id, should_fire_when_not_visible, *object); }); timer->start(); 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); } +static void qt_notifier_activated(Core::Notifier& notifier) +{ + Core::NotifierActivationEvent event(notifier.fd()); + notifier.dispatch_event(event); +} + void EventLoopImplementationQt::register_notifier(Core::Notifier& notifier) { QSocketNotifier::Type type; @@ -120,8 +143,8 @@ void EventLoopImplementationQt::register_notifier(Core::Notifier& notifier) TODO(); } auto socket_notifier = make(notifier.fd(), type); - QObject::connect(socket_notifier, &QSocketNotifier::activated, [fd = notifier.fd(), ¬ifier] { - Core::ThreadEventQueue::current().post_event(notifier, make(fd)); + QObject::connect(socket_notifier, &QSocketNotifier::activated, [¬ifier] { + qt_notifier_activated(notifier); }); ThreadData::the().notifiers.set(¬ifier, move(socket_notifier)); @@ -132,4 +155,9 @@ void EventLoopImplementationQt::unregister_notifier(Core::Notifier& notifier) ThreadData::the().notifiers.remove(¬ifier); } +void EventLoopImplementationQt::did_post_event() +{ + m_process_core_events_timer.start(); +} + } diff --git a/Ladybird/EventLoopImplementationQt.h b/Ladybird/EventLoopImplementationQt.h index 313abaf02f..b0ac788adb 100644 --- a/Ladybird/EventLoopImplementationQt.h +++ b/Ladybird/EventLoopImplementationQt.h @@ -35,6 +35,8 @@ public: virtual void register_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. virtual void unquit() override { } virtual bool was_exit_requested() const override { return false; } @@ -42,12 +44,15 @@ public: virtual int register_signal(int, Function) override { return 0; } virtual void unregister_signal(int) override { } -protected: - EventLoopImplementationQt(); + void set_main_loop() { m_main_loop = true; } private: + EventLoopImplementationQt(); + bool is_main_loop() const { return m_main_loop; } + QEventLoop m_event_loop; - Optional m_exit_code; + QTimer m_process_core_events_timer; + bool m_main_loop { false }; }; } diff --git a/Ladybird/main.cpp b/Ladybird/main.cpp index ad3b538c91..796267beda 100644 --- a/Ladybird/main.cpp +++ b/Ladybird/main.cpp @@ -56,6 +56,7 @@ ErrorOr serenity_main(Main::Arguments arguments) Core::EventLoop::make_implementation = Ladybird::EventLoopImplementationQt::create; Core::EventLoop event_loop; + static_cast(event_loop.impl()).set_main_loop(); TRY(handle_attached_debugger()); diff --git a/Userland/Libraries/LibCore/EventLoop.cpp b/Userland/Libraries/LibCore/EventLoop.cpp index c45e99f67b..1d848760f9 100644 --- a/Userland/Libraries/LibCore/EventLoop.cpp +++ b/Userland/Libraries/LibCore/EventLoop.cpp @@ -170,4 +170,9 @@ bool EventLoop::was_exit_requested() const return m_impl->was_exit_requested(); } +void EventLoop::did_post_event(Badge) +{ + m_impl->did_post_event(); +} + } diff --git a/Userland/Libraries/LibCore/EventLoop.h b/Userland/Libraries/LibCore/EventLoop.h index 759a4a6950..65061bea7b 100644 --- a/Userland/Libraries/LibCore/EventLoop.h +++ b/Userland/Libraries/LibCore/EventLoop.h @@ -19,6 +19,7 @@ namespace Core { class EventLoopImplementation; +class ThreadEventQueue; // 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, @@ -95,6 +96,9 @@ public: static Function()> make_implementation; + void did_post_event(Badge); + EventLoopImplementation& impl() { return *m_impl; } + private: void wait_for_event(WaitMode); Optional