mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 09:02:43 +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/Object.h> | ||||
| #include <LibCore/ThreadEventQueue.h> | ||||
| #include <QCoreApplication> | ||||
| #include <QTimer> | ||||
| 
 | ||||
| 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<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) | ||||
| { | ||||
|     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<Core::TimerEvent>(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<QSocketNotifier>(notifier.fd(), type); | ||||
|     QObject::connect(socket_notifier, &QSocketNotifier::activated, [fd = notifier.fd(), ¬ifier] { | ||||
|         Core::ThreadEventQueue::current().post_event(notifier, make<Core::NotifierActivationEvent>(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(); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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<void(int)>) 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<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 event_loop; | ||||
|     static_cast<Ladybird::EventLoopImplementationQt&>(event_loop.impl()).set_main_loop(); | ||||
| 
 | ||||
|     TRY(handle_attached_debugger()); | ||||
| 
 | ||||
|  |  | |||
|  | @ -170,4 +170,9 @@ bool EventLoop::was_exit_requested() const | |||
|     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 { | ||||
| 
 | ||||
| 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<NonnullOwnPtr<EventLoopImplementation>()> make_implementation; | ||||
| 
 | ||||
|     void did_post_event(Badge<ThreadEventQueue>); | ||||
|     EventLoopImplementation& impl() { return *m_impl; } | ||||
| 
 | ||||
| private: | ||||
|     void wait_for_event(WaitMode); | ||||
|     Optional<Time> get_next_timer_expiration(); | ||||
|  |  | |||
|  | @ -37,6 +37,8 @@ public: | |||
|     virtual void register_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.
 | ||||
|     virtual void unquit() = 0; | ||||
|     virtual bool was_exit_requested() const = 0; | ||||
|  |  | |||
|  | @ -522,4 +522,8 @@ void EventLoopImplementationUnix::unregister_notifier(Notifier& notifier) | |||
|     ThreadData::the().notifiers.remove(¬ifier); | ||||
| } | ||||
| 
 | ||||
| void EventLoopImplementationUnix::did_post_event() | ||||
| { | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -31,6 +31,8 @@ public: | |||
|     virtual void register_notifier(Notifier&) override; | ||||
|     virtual void unregister_notifier(Notifier&) override; | ||||
| 
 | ||||
|     virtual void did_post_event() override; | ||||
| 
 | ||||
|     virtual void unquit() override; | ||||
|     virtual bool was_exit_requested() const 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) | ||||
| { | ||||
|     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) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling