mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 00:32:45 +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:
		
							parent
							
								
									0f22dfa634
								
							
						
					
					
						commit
						c21eb30a2b
					
				
					 9 changed files with 79 additions and 25 deletions
				
			
		|  | @ -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(), ¬ifier] { |     QObject::connect(socket_notifier, &QSocketNotifier::activated, [¬ifier] { | ||||||
|         Core::ThreadEventQueue::current().post_event(notifier, make<Core::NotifierActivationEvent>(fd)); |         qt_notifier_activated(notifier); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     ThreadData::the().notifiers.set(¬ifier, move(socket_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); |     ThreadData::the().notifiers.remove(¬ifier); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void EventLoopImplementationQt::did_post_event() | ||||||
|  | { | ||||||
|  |     m_process_core_events_timer.start(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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 }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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()); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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(); | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -522,4 +522,8 @@ void EventLoopImplementationUnix::unregister_notifier(Notifier& notifier) | ||||||
|     ThreadData::the().notifiers.remove(¬ifier); |     ThreadData::the().notifiers.remove(¬ifier); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void EventLoopImplementationUnix::did_post_event() | ||||||
|  | { | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling