mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-18 00:09:44 +00:00
LibWebView+Services+UI: Move the EventLoop implementations to LibWebView
We currently compile the Qt event loop files multiple times, for every target which wants to use them. This patch moves these to LibWebView as a central location to avoid this.
This commit is contained in:
parent
bd50a31be6
commit
9e1f001ffe
Notes:
github-actions[bot]
2024-11-11 12:36:54 +00:00
Author: https://github.com/trflynn89
Commit: 9e1f001ffe
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2269
17 changed files with 98 additions and 91 deletions
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <LibCore/EventLoopImplementation.h>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
class EventLoopManagerMacOS final : public Core::EventLoopManager {
|
||||
public:
|
||||
virtual NonnullOwnPtr<Core::EventLoopImplementation> make_implementation() override;
|
||||
|
||||
virtual intptr_t register_timer(Core::EventReceiver&, int interval_milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible) override;
|
||||
virtual void unregister_timer(intptr_t timer_id) override;
|
||||
|
||||
virtual void register_notifier(Core::Notifier&) override;
|
||||
virtual void unregister_notifier(Core::Notifier&) override;
|
||||
|
||||
virtual void did_post_event() override;
|
||||
|
||||
virtual int register_signal(int, Function<void(int)>) override;
|
||||
virtual void unregister_signal(int) override;
|
||||
};
|
||||
|
||||
class EventLoopImplementationMacOS final : public Core::EventLoopImplementation {
|
||||
public:
|
||||
// FIXME: This currently only manages the main NSApp event loop, as that is all we currently
|
||||
// interact with. When we need multiple event loops, or an event loop that isn't the
|
||||
// NSApp loop, we will need to create our own CFRunLoop.
|
||||
static NonnullOwnPtr<EventLoopImplementationMacOS> create();
|
||||
|
||||
virtual int exec() override;
|
||||
virtual size_t pump(PumpMode) override;
|
||||
virtual void quit(int) override;
|
||||
virtual void wake() override;
|
||||
virtual void post_event(Core::EventReceiver& receiver, NonnullOwnPtr<Core::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; }
|
||||
virtual void notify_forked_and_in_child() override { }
|
||||
|
||||
private:
|
||||
EventLoopImplementationMacOS() = default;
|
||||
|
||||
int m_exit_code { 0 };
|
||||
};
|
||||
|
||||
}
|
414
Libraries/LibWebView/EventLoop/EventLoopImplementationMacOS.mm
Normal file
414
Libraries/LibWebView/EventLoop/EventLoopImplementationMacOS.mm
Normal file
|
@ -0,0 +1,414 @@
|
|||
/*
|
||||
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/IDAllocator.h>
|
||||
#include <AK/Singleton.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <LibCore/Event.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibCore/ThreadEventQueue.h>
|
||||
#include <LibWebView/EventLoop/EventLoopImplementationMacOS.h>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#include <sys/event.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
struct ThreadData {
|
||||
static ThreadData& the()
|
||||
{
|
||||
static thread_local ThreadData s_thread_data;
|
||||
return s_thread_data;
|
||||
}
|
||||
|
||||
Core::Notifier& notifier_by_fd(int fd)
|
||||
{
|
||||
for (auto notifier : notifiers) {
|
||||
if (notifier.key->fd() == fd)
|
||||
return *notifier.key;
|
||||
}
|
||||
|
||||
// If we didn't have a notifier for the provided FD, it should have been unregistered.
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
IDAllocator timer_id_allocator;
|
||||
HashMap<int, CFRunLoopTimerRef> timers;
|
||||
HashMap<Core::Notifier*, CFRunLoopSourceRef> notifiers;
|
||||
};
|
||||
|
||||
class SignalHandlers : public RefCounted<SignalHandlers> {
|
||||
AK_MAKE_NONCOPYABLE(SignalHandlers);
|
||||
AK_MAKE_NONMOVABLE(SignalHandlers);
|
||||
|
||||
public:
|
||||
SignalHandlers(int signal_number, CFFileDescriptorCallBack);
|
||||
~SignalHandlers();
|
||||
|
||||
void dispatch();
|
||||
int add(Function<void(int)>&& handler);
|
||||
bool remove(int handler_id);
|
||||
|
||||
bool is_empty() const
|
||||
{
|
||||
if (m_calling_handlers) {
|
||||
for (auto const& handler : m_handlers_pending) {
|
||||
if (handler.value)
|
||||
return false; // an add is pending
|
||||
}
|
||||
}
|
||||
return m_handlers.is_empty();
|
||||
}
|
||||
|
||||
bool have(int handler_id) const
|
||||
{
|
||||
if (m_calling_handlers) {
|
||||
auto it = m_handlers_pending.find(handler_id);
|
||||
if (it != m_handlers_pending.end()) {
|
||||
if (!it->value)
|
||||
return false; // a deletion is pending
|
||||
}
|
||||
}
|
||||
return m_handlers.contains(handler_id);
|
||||
}
|
||||
|
||||
int m_signal_number;
|
||||
void (*m_original_handler)(int);
|
||||
HashMap<int, Function<void(int)>> m_handlers;
|
||||
HashMap<int, Function<void(int)>> m_handlers_pending;
|
||||
bool m_calling_handlers { false };
|
||||
CFRunLoopSourceRef m_source { nullptr };
|
||||
int m_kevent_fd = { -1 };
|
||||
};
|
||||
|
||||
SignalHandlers::SignalHandlers(int signal_number, CFFileDescriptorCallBack handle_signal)
|
||||
: m_signal_number(signal_number)
|
||||
, m_original_handler(signal(signal_number, [](int) {}))
|
||||
{
|
||||
m_kevent_fd = kqueue();
|
||||
if (m_kevent_fd < 0) {
|
||||
dbgln("Unable to create kqueue to register signal {}: {}", signal_number, strerror(errno));
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
struct kevent changes = {};
|
||||
EV_SET(&changes, signal_number, EVFILT_SIGNAL, EV_ADD | EV_RECEIPT, 0, 0, nullptr);
|
||||
if (auto res = kevent(m_kevent_fd, &changes, 1, &changes, 1, NULL); res < 0) {
|
||||
dbgln("Unable to register signal {}: {}", signal_number, strerror(errno));
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
CFFileDescriptorContext context = { 0, this, nullptr, nullptr, nullptr };
|
||||
CFFileDescriptorRef kq_ref = CFFileDescriptorCreate(kCFAllocatorDefault, m_kevent_fd, FALSE, handle_signal, &context);
|
||||
|
||||
m_source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, kq_ref, 0);
|
||||
CFRunLoopAddSource(CFRunLoopGetMain(), m_source, kCFRunLoopDefaultMode);
|
||||
|
||||
CFFileDescriptorEnableCallBacks(kq_ref, kCFFileDescriptorReadCallBack);
|
||||
CFRelease(kq_ref);
|
||||
}
|
||||
|
||||
SignalHandlers::~SignalHandlers()
|
||||
{
|
||||
CFRunLoopRemoveSource(CFRunLoopGetMain(), m_source, kCFRunLoopDefaultMode);
|
||||
CFRelease(m_source);
|
||||
(void)::signal(m_signal_number, m_original_handler);
|
||||
::close(m_kevent_fd);
|
||||
}
|
||||
|
||||
struct SignalHandlersInfo {
|
||||
HashMap<int, NonnullRefPtr<SignalHandlers>> signal_handlers;
|
||||
int next_signal_id { 0 };
|
||||
};
|
||||
|
||||
static Singleton<SignalHandlersInfo> s_signals;
|
||||
static SignalHandlersInfo* signals_info()
|
||||
{
|
||||
return s_signals.ptr();
|
||||
}
|
||||
|
||||
void SignalHandlers::dispatch()
|
||||
{
|
||||
TemporaryChange change(m_calling_handlers, true);
|
||||
for (auto& handler : m_handlers)
|
||||
handler.value(m_signal_number);
|
||||
if (!m_handlers_pending.is_empty()) {
|
||||
// Apply pending adds/removes
|
||||
for (auto& handler : m_handlers_pending) {
|
||||
if (handler.value) {
|
||||
auto result = m_handlers.set(handler.key, move(handler.value));
|
||||
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
|
||||
} else {
|
||||
m_handlers.remove(handler.key);
|
||||
}
|
||||
}
|
||||
m_handlers_pending.clear();
|
||||
}
|
||||
}
|
||||
|
||||
int SignalHandlers::add(Function<void(int)>&& handler)
|
||||
{
|
||||
int id = ++signals_info()->next_signal_id; // TODO: worry about wrapping and duplicates?
|
||||
if (m_calling_handlers)
|
||||
m_handlers_pending.set(id, move(handler));
|
||||
else
|
||||
m_handlers.set(id, move(handler));
|
||||
return id;
|
||||
}
|
||||
|
||||
bool SignalHandlers::remove(int handler_id)
|
||||
{
|
||||
VERIFY(handler_id != 0);
|
||||
if (m_calling_handlers) {
|
||||
auto it = m_handlers.find(handler_id);
|
||||
if (it != m_handlers.end()) {
|
||||
// Mark pending remove
|
||||
m_handlers_pending.set(handler_id, {});
|
||||
return true;
|
||||
}
|
||||
it = m_handlers_pending.find(handler_id);
|
||||
if (it != m_handlers_pending.end()) {
|
||||
if (!it->value)
|
||||
return false; // already was marked as deleted
|
||||
it->value = nullptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return m_handlers.remove(handler_id);
|
||||
}
|
||||
|
||||
static void post_application_event()
|
||||
{
|
||||
auto* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
|
||||
location:NSMakePoint(0, 0)
|
||||
modifierFlags:0
|
||||
timestamp:0
|
||||
windowNumber:0
|
||||
context:nil
|
||||
subtype:0
|
||||
data1:0
|
||||
data2:0];
|
||||
|
||||
[NSApp postEvent:event atStart:NO];
|
||||
}
|
||||
|
||||
NonnullOwnPtr<Core::EventLoopImplementation> EventLoopManagerMacOS::make_implementation()
|
||||
{
|
||||
return EventLoopImplementationMacOS::create();
|
||||
}
|
||||
|
||||
intptr_t EventLoopManagerMacOS::register_timer(Core::EventReceiver& receiver, int interval_milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible should_fire_when_not_visible)
|
||||
{
|
||||
auto& thread_data = ThreadData::the();
|
||||
|
||||
auto timer_id = thread_data.timer_id_allocator.allocate();
|
||||
auto weak_receiver = receiver.make_weak_ptr();
|
||||
|
||||
auto interval_seconds = static_cast<double>(interval_milliseconds) / 1000.0;
|
||||
auto first_fire_time = CFAbsoluteTimeGetCurrent() + interval_seconds;
|
||||
|
||||
auto* timer = CFRunLoopTimerCreateWithHandler(
|
||||
kCFAllocatorDefault, first_fire_time, should_reload ? interval_seconds : 0, 0, 0,
|
||||
^(CFRunLoopTimerRef) {
|
||||
auto receiver = weak_receiver.strong_ref();
|
||||
if (!receiver) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (should_fire_when_not_visible == Core::TimerShouldFireWhenNotVisible::No) {
|
||||
if (!receiver->is_visible_for_timer_purposes()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Core::TimerEvent event;
|
||||
receiver->dispatch_event(event);
|
||||
});
|
||||
|
||||
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
|
||||
thread_data.timers.set(timer_id, timer);
|
||||
|
||||
return timer_id;
|
||||
}
|
||||
|
||||
void EventLoopManagerMacOS::unregister_timer(intptr_t timer_id)
|
||||
{
|
||||
auto& thread_data = ThreadData::the();
|
||||
thread_data.timer_id_allocator.deallocate(static_cast<int>(timer_id));
|
||||
|
||||
auto timer = thread_data.timers.take(static_cast<int>(timer_id));
|
||||
VERIFY(timer.has_value());
|
||||
CFRunLoopTimerInvalidate(*timer);
|
||||
CFRelease(*timer);
|
||||
}
|
||||
|
||||
static void socket_notifier(CFSocketRef socket, CFSocketCallBackType notification_type, CFDataRef, void const*, void*)
|
||||
{
|
||||
auto& notifier = ThreadData::the().notifier_by_fd(CFSocketGetNative(socket));
|
||||
|
||||
// This socket callback is not quite re-entrant. If Core::Notifier::dispatch_event blocks, e.g.
|
||||
// to wait upon a Core::Promise, this socket will not receive any more notifications until that
|
||||
// promise is resolved or rejected. So we mark this socket as able to receive more notifications
|
||||
// before dispatching the event, which allows it to be triggered again.
|
||||
CFSocketEnableCallBacks(socket, notification_type);
|
||||
|
||||
Core::NotifierActivationEvent event(notifier.fd(), notifier.type());
|
||||
notifier.dispatch_event(event);
|
||||
|
||||
// This manual process of enabling the callbacks also seems to require waking the event loop,
|
||||
// otherwise it hangs indefinitely in any ongoing pump(PumpMode::WaitForEvents) invocation.
|
||||
post_application_event();
|
||||
}
|
||||
|
||||
void EventLoopManagerMacOS::register_notifier(Core::Notifier& notifier)
|
||||
{
|
||||
auto notification_type = kCFSocketNoCallBack;
|
||||
|
||||
switch (notifier.type()) {
|
||||
case Core::Notifier::Type::Read:
|
||||
notification_type = kCFSocketReadCallBack;
|
||||
break;
|
||||
case Core::Notifier::Type::Write:
|
||||
notification_type = kCFSocketWriteCallBack;
|
||||
break;
|
||||
default:
|
||||
TODO();
|
||||
break;
|
||||
}
|
||||
|
||||
CFSocketContext context { .version = 0, .info = nullptr, .retain = nullptr, .release = nullptr, .copyDescription = nullptr };
|
||||
auto* socket = CFSocketCreateWithNative(kCFAllocatorDefault, notifier.fd(), notification_type, &socket_notifier, &context);
|
||||
|
||||
CFOptionFlags sockopt = CFSocketGetSocketFlags(socket);
|
||||
sockopt &= ~kCFSocketAutomaticallyReenableReadCallBack;
|
||||
sockopt &= ~kCFSocketCloseOnInvalidate;
|
||||
CFSocketSetSocketFlags(socket, sockopt);
|
||||
|
||||
auto* source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
|
||||
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
|
||||
|
||||
CFRelease(socket);
|
||||
|
||||
ThreadData::the().notifiers.set(¬ifier, source);
|
||||
}
|
||||
|
||||
void EventLoopManagerMacOS::unregister_notifier(Core::Notifier& notifier)
|
||||
{
|
||||
if (auto source = ThreadData::the().notifiers.take(¬ifier); source.has_value()) {
|
||||
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), *source, kCFRunLoopCommonModes);
|
||||
CFRelease(*source);
|
||||
}
|
||||
}
|
||||
|
||||
void EventLoopManagerMacOS::did_post_event()
|
||||
{
|
||||
post_application_event();
|
||||
}
|
||||
|
||||
static void handle_signal(CFFileDescriptorRef f, CFOptionFlags callback_types, void* info)
|
||||
{
|
||||
VERIFY(callback_types & kCFFileDescriptorReadCallBack);
|
||||
auto* signal_handlers = static_cast<SignalHandlers*>(info);
|
||||
|
||||
struct kevent event { };
|
||||
|
||||
// returns number of events that have occurred since last call
|
||||
(void)::kevent(CFFileDescriptorGetNativeDescriptor(f), nullptr, 0, &event, 1, nullptr);
|
||||
CFFileDescriptorEnableCallBacks(f, kCFFileDescriptorReadCallBack);
|
||||
|
||||
signal_handlers->dispatch();
|
||||
}
|
||||
|
||||
int EventLoopManagerMacOS::register_signal(int signal_number, Function<void(int)> handler)
|
||||
{
|
||||
VERIFY(signal_number != 0);
|
||||
auto& info = *signals_info();
|
||||
auto handlers = info.signal_handlers.find(signal_number);
|
||||
if (handlers == info.signal_handlers.end()) {
|
||||
auto signal_handlers = adopt_ref(*new SignalHandlers(signal_number, &handle_signal));
|
||||
auto handler_id = signal_handlers->add(move(handler));
|
||||
info.signal_handlers.set(signal_number, move(signal_handlers));
|
||||
return handler_id;
|
||||
} else {
|
||||
return handlers->value->add(move(handler));
|
||||
}
|
||||
}
|
||||
|
||||
void EventLoopManagerMacOS::unregister_signal(int handler_id)
|
||||
{
|
||||
VERIFY(handler_id != 0);
|
||||
int remove_signal_number = 0;
|
||||
auto& info = *signals_info();
|
||||
for (auto& h : info.signal_handlers) {
|
||||
auto& handlers = *h.value;
|
||||
if (handlers.remove(handler_id)) {
|
||||
if (handlers.is_empty())
|
||||
remove_signal_number = handlers.m_signal_number;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (remove_signal_number != 0)
|
||||
info.signal_handlers.remove(remove_signal_number);
|
||||
}
|
||||
|
||||
NonnullOwnPtr<EventLoopImplementationMacOS> EventLoopImplementationMacOS::create()
|
||||
{
|
||||
return adopt_own(*new EventLoopImplementationMacOS);
|
||||
}
|
||||
|
||||
int EventLoopImplementationMacOS::exec()
|
||||
{
|
||||
[NSApp run];
|
||||
return m_exit_code;
|
||||
}
|
||||
|
||||
size_t EventLoopImplementationMacOS::pump(PumpMode mode)
|
||||
{
|
||||
auto* wait_until = mode == PumpMode::WaitForEvents ? [NSDate distantFuture] : [NSDate distantPast];
|
||||
|
||||
auto* event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
untilDate:wait_until
|
||||
inMode:NSDefaultRunLoopMode
|
||||
dequeue:YES];
|
||||
|
||||
while (event) {
|
||||
[NSApp sendEvent:event];
|
||||
|
||||
event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
untilDate:nil
|
||||
inMode:NSDefaultRunLoopMode
|
||||
dequeue:YES];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void EventLoopImplementationMacOS::quit(int exit_code)
|
||||
{
|
||||
m_exit_code = exit_code;
|
||||
[NSApp stop:nil];
|
||||
}
|
||||
|
||||
void EventLoopImplementationMacOS::wake()
|
||||
{
|
||||
CFRunLoopWakeUp(CFRunLoopGetCurrent());
|
||||
}
|
||||
|
||||
void EventLoopImplementationMacOS::post_event(Core::EventReceiver& receiver, NonnullOwnPtr<Core::Event>&& event)
|
||||
{
|
||||
m_thread_event_queue.post_event(receiver, move(event));
|
||||
|
||||
if (&m_thread_event_queue != &Core::ThreadEventQueue::current())
|
||||
wake();
|
||||
}
|
||||
|
||||
}
|
394
Libraries/LibWebView/EventLoop/EventLoopImplementationQt.cpp
Normal file
394
Libraries/LibWebView/EventLoop/EventLoopImplementationQt.cpp
Normal file
|
@ -0,0 +1,394 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/IDAllocator.h>
|
||||
#include <AK/Singleton.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <LibCore/Event.h>
|
||||
#include <LibCore/EventReceiver.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibCore/ThreadEventQueue.h>
|
||||
#include <LibWebView/EventLoop/EventLoopImplementationQt.h>
|
||||
#include <LibWebView/EventLoop/EventLoopImplementationQtEventTarget.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QEvent>
|
||||
#include <QEventLoop>
|
||||
#include <QSocketNotifier>
|
||||
#include <QTimer>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
struct ThreadData;
|
||||
static thread_local ThreadData* s_thread_data;
|
||||
|
||||
struct ThreadData {
|
||||
static ThreadData& the()
|
||||
{
|
||||
if (!s_thread_data) {
|
||||
// FIXME: Don't leak this.
|
||||
s_thread_data = new ThreadData;
|
||||
}
|
||||
return *s_thread_data;
|
||||
}
|
||||
|
||||
HashMap<Core::Notifier*, NonnullOwnPtr<QSocketNotifier>> notifiers;
|
||||
};
|
||||
|
||||
class QtEventLoopManagerEvent final : public QEvent {
|
||||
public:
|
||||
static QEvent::Type process_event_queue_event_type()
|
||||
{
|
||||
static auto const type = static_cast<QEvent::Type>(QEvent::registerEventType());
|
||||
return type;
|
||||
}
|
||||
|
||||
QtEventLoopManagerEvent(QEvent::Type type)
|
||||
: QEvent(type)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class SignalHandlers : public RefCounted<SignalHandlers> {
|
||||
AK_MAKE_NONCOPYABLE(SignalHandlers);
|
||||
AK_MAKE_NONMOVABLE(SignalHandlers);
|
||||
|
||||
public:
|
||||
SignalHandlers(int signal_number, void (*handle_signal)(int));
|
||||
~SignalHandlers();
|
||||
|
||||
void dispatch();
|
||||
int add(Function<void(int)>&& handler);
|
||||
bool remove(int handler_id);
|
||||
|
||||
bool is_empty() const
|
||||
{
|
||||
if (m_calling_handlers) {
|
||||
for (auto const& handler : m_handlers_pending) {
|
||||
if (handler.value)
|
||||
return false; // an add is pending
|
||||
}
|
||||
}
|
||||
return m_handlers.is_empty();
|
||||
}
|
||||
|
||||
bool have(int handler_id) const
|
||||
{
|
||||
if (m_calling_handlers) {
|
||||
auto it = m_handlers_pending.find(handler_id);
|
||||
if (it != m_handlers_pending.end()) {
|
||||
if (!it->value)
|
||||
return false; // a deletion is pending
|
||||
}
|
||||
}
|
||||
return m_handlers.contains(handler_id);
|
||||
}
|
||||
|
||||
int m_signal_number;
|
||||
void (*m_original_handler)(int);
|
||||
HashMap<int, Function<void(int)>> m_handlers;
|
||||
HashMap<int, Function<void(int)>> m_handlers_pending;
|
||||
bool m_calling_handlers { false };
|
||||
};
|
||||
|
||||
SignalHandlers::SignalHandlers(int signal_number, void (*handle_signal)(int))
|
||||
: m_signal_number(signal_number)
|
||||
, m_original_handler(signal(signal_number, handle_signal))
|
||||
{
|
||||
}
|
||||
|
||||
SignalHandlers::~SignalHandlers()
|
||||
{
|
||||
(void)::signal(m_signal_number, m_original_handler);
|
||||
}
|
||||
|
||||
struct SignalHandlersInfo {
|
||||
HashMap<int, NonnullRefPtr<SignalHandlers>> signal_handlers;
|
||||
int next_signal_id { 0 };
|
||||
};
|
||||
|
||||
static Singleton<SignalHandlersInfo> s_signals;
|
||||
static SignalHandlersInfo* signals_info()
|
||||
{
|
||||
return s_signals.ptr();
|
||||
}
|
||||
|
||||
void SignalHandlers::dispatch()
|
||||
{
|
||||
TemporaryChange change(m_calling_handlers, true);
|
||||
for (auto& handler : m_handlers)
|
||||
handler.value(m_signal_number);
|
||||
if (!m_handlers_pending.is_empty()) {
|
||||
// Apply pending adds/removes
|
||||
for (auto& handler : m_handlers_pending) {
|
||||
if (handler.value) {
|
||||
auto result = m_handlers.set(handler.key, move(handler.value));
|
||||
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
|
||||
} else {
|
||||
m_handlers.remove(handler.key);
|
||||
}
|
||||
}
|
||||
m_handlers_pending.clear();
|
||||
}
|
||||
}
|
||||
|
||||
int SignalHandlers::add(Function<void(int)>&& handler)
|
||||
{
|
||||
int id = ++signals_info()->next_signal_id; // TODO: worry about wrapping and duplicates?
|
||||
if (m_calling_handlers)
|
||||
m_handlers_pending.set(id, move(handler));
|
||||
else
|
||||
m_handlers.set(id, move(handler));
|
||||
return id;
|
||||
}
|
||||
|
||||
bool SignalHandlers::remove(int handler_id)
|
||||
{
|
||||
VERIFY(handler_id != 0);
|
||||
if (m_calling_handlers) {
|
||||
auto it = m_handlers.find(handler_id);
|
||||
if (it != m_handlers.end()) {
|
||||
// Mark pending remove
|
||||
m_handlers_pending.set(handler_id, {});
|
||||
return true;
|
||||
}
|
||||
it = m_handlers_pending.find(handler_id);
|
||||
if (it != m_handlers_pending.end()) {
|
||||
if (!it->value)
|
||||
return false; // already was marked as deleted
|
||||
it->value = nullptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return m_handlers.remove(handler_id);
|
||||
}
|
||||
|
||||
static void dispatch_signal(int signal_number)
|
||||
{
|
||||
auto& info = *signals_info();
|
||||
auto handlers = info.signal_handlers.find(signal_number);
|
||||
if (handlers != info.signal_handlers.end()) {
|
||||
// Make sure we bump the ref count while dispatching the handlers!
|
||||
// This allows a handler to unregister/register while the handlers
|
||||
// are being called!
|
||||
auto handler = handlers->value;
|
||||
handler->dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
EventLoopImplementationQt::EventLoopImplementationQt()
|
||||
: m_event_loop(make<QEventLoop>())
|
||||
{
|
||||
}
|
||||
|
||||
EventLoopImplementationQt::~EventLoopImplementationQt() = default;
|
||||
|
||||
int EventLoopImplementationQt::exec()
|
||||
{
|
||||
if (is_main_loop())
|
||||
return QCoreApplication::exec();
|
||||
return m_event_loop->exec();
|
||||
}
|
||||
|
||||
size_t EventLoopImplementationQt::pump(PumpMode mode)
|
||||
{
|
||||
auto result = Core::ThreadEventQueue::current().process();
|
||||
auto qt_mode = mode == PumpMode::WaitForEvents ? QEventLoop::WaitForMoreEvents : QEventLoop::AllEvents;
|
||||
if (is_main_loop())
|
||||
QCoreApplication::processEvents(qt_mode);
|
||||
else
|
||||
m_event_loop->processEvents(qt_mode);
|
||||
result += Core::ThreadEventQueue::current().process();
|
||||
return result;
|
||||
}
|
||||
|
||||
void EventLoopImplementationQt::quit(int code)
|
||||
{
|
||||
if (is_main_loop())
|
||||
QCoreApplication::exit(code);
|
||||
else
|
||||
m_event_loop->exit(code);
|
||||
}
|
||||
|
||||
void EventLoopImplementationQt::wake()
|
||||
{
|
||||
if (!is_main_loop())
|
||||
m_event_loop->wakeUp();
|
||||
}
|
||||
|
||||
void EventLoopImplementationQt::post_event(Core::EventReceiver& receiver, NonnullOwnPtr<Core::Event>&& event)
|
||||
{
|
||||
m_thread_event_queue.post_event(receiver, move(event));
|
||||
if (&m_thread_event_queue != &Core::ThreadEventQueue::current())
|
||||
wake();
|
||||
}
|
||||
|
||||
void EventLoopImplementationQt::set_main_loop()
|
||||
{
|
||||
m_main_loop = true;
|
||||
|
||||
auto& event_loop_manager = static_cast<EventLoopManagerQt&>(Core::EventLoopManager::the());
|
||||
event_loop_manager.set_main_loop_signal_notifiers({});
|
||||
}
|
||||
|
||||
static void qt_timer_fired(Core::TimerShouldFireWhenNotVisible should_fire_when_not_visible, Core::EventReceiver& object)
|
||||
{
|
||||
if (should_fire_when_not_visible == Core::TimerShouldFireWhenNotVisible::No) {
|
||||
if (!object.is_visible_for_timer_purposes())
|
||||
return;
|
||||
}
|
||||
Core::TimerEvent event;
|
||||
object.dispatch_event(event);
|
||||
}
|
||||
|
||||
intptr_t EventLoopManagerQt::register_timer(Core::EventReceiver& object, int milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible should_fire_when_not_visible)
|
||||
{
|
||||
auto timer = new QTimer;
|
||||
timer->setTimerType(Qt::PreciseTimer);
|
||||
timer->setInterval(milliseconds);
|
||||
timer->setSingleShot(!should_reload);
|
||||
auto weak_object = object.make_weak_ptr();
|
||||
QObject::connect(timer, &QTimer::timeout, [should_fire_when_not_visible, weak_object = move(weak_object)] {
|
||||
auto object = weak_object.strong_ref();
|
||||
if (!object)
|
||||
return;
|
||||
qt_timer_fired(should_fire_when_not_visible, *object);
|
||||
});
|
||||
timer->start();
|
||||
return bit_cast<intptr_t>(timer);
|
||||
}
|
||||
|
||||
void EventLoopManagerQt::unregister_timer(intptr_t timer_id)
|
||||
{
|
||||
auto* timer = bit_cast<QTimer*>(timer_id);
|
||||
delete timer;
|
||||
}
|
||||
|
||||
static void qt_notifier_activated(Core::Notifier& notifier)
|
||||
{
|
||||
Core::NotifierActivationEvent event(notifier.fd(), notifier.type());
|
||||
notifier.dispatch_event(event);
|
||||
}
|
||||
|
||||
void EventLoopManagerQt::register_notifier(Core::Notifier& notifier)
|
||||
{
|
||||
QSocketNotifier::Type type;
|
||||
switch (notifier.type()) {
|
||||
case Core::Notifier::Type::Read:
|
||||
type = QSocketNotifier::Read;
|
||||
break;
|
||||
case Core::Notifier::Type::Write:
|
||||
type = QSocketNotifier::Write;
|
||||
break;
|
||||
default:
|
||||
TODO();
|
||||
}
|
||||
auto socket_notifier = make<QSocketNotifier>(notifier.fd(), type);
|
||||
QObject::connect(socket_notifier, &QSocketNotifier::activated, [¬ifier] {
|
||||
qt_notifier_activated(notifier);
|
||||
});
|
||||
|
||||
ThreadData::the().notifiers.set(¬ifier, move(socket_notifier));
|
||||
}
|
||||
|
||||
void EventLoopManagerQt::unregister_notifier(Core::Notifier& notifier)
|
||||
{
|
||||
ThreadData::the().notifiers.remove(¬ifier);
|
||||
}
|
||||
|
||||
void EventLoopManagerQt::handle_signal(int signal_number)
|
||||
{
|
||||
auto& that = static_cast<EventLoopManagerQt&>(Core::EventLoopManager::the());
|
||||
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425
|
||||
// Apparently warn_unused_result ignoring (void) casts is a feature
|
||||
[[maybe_unused]] auto _ = ::write(that.m_signal_socket_fds[1], &signal_number, sizeof(signal_number));
|
||||
}
|
||||
|
||||
int EventLoopManagerQt::register_signal(int signal_number, Function<void(int)> handler)
|
||||
{
|
||||
VERIFY(signal_number != 0);
|
||||
auto& info = *signals_info();
|
||||
auto handlers = info.signal_handlers.find(signal_number);
|
||||
if (handlers == info.signal_handlers.end()) {
|
||||
auto signal_handlers = adopt_ref(*new SignalHandlers(signal_number, EventLoopManagerQt::handle_signal));
|
||||
auto handler_id = signal_handlers->add(move(handler));
|
||||
info.signal_handlers.set(signal_number, move(signal_handlers));
|
||||
return handler_id;
|
||||
} else {
|
||||
return handlers->value->add(move(handler));
|
||||
}
|
||||
}
|
||||
|
||||
void EventLoopManagerQt::unregister_signal(int handler_id)
|
||||
{
|
||||
VERIFY(handler_id != 0);
|
||||
int remove_signal_number = 0;
|
||||
auto& info = *signals_info();
|
||||
for (auto& h : info.signal_handlers) {
|
||||
auto& handlers = *h.value;
|
||||
if (handlers.remove(handler_id)) {
|
||||
if (handlers.is_empty())
|
||||
remove_signal_number = handlers.m_signal_number;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (remove_signal_number != 0)
|
||||
info.signal_handlers.remove(remove_signal_number);
|
||||
}
|
||||
|
||||
void EventLoopManagerQt::did_post_event()
|
||||
{
|
||||
QCoreApplication::postEvent(m_main_thread_event_target.ptr(), new QtEventLoopManagerEvent(QtEventLoopManagerEvent::process_event_queue_event_type()));
|
||||
}
|
||||
|
||||
bool EventLoopManagerQt::event_target_received_event(Badge<EventLoopImplementationQtEventTarget>, QEvent* event)
|
||||
{
|
||||
if (event->type() == QtEventLoopManagerEvent::process_event_queue_event_type()) {
|
||||
Core::ThreadEventQueue::current().process();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
EventLoopManagerQt::EventLoopManagerQt()
|
||||
: m_main_thread_event_target(make<EventLoopImplementationQtEventTarget>())
|
||||
{
|
||||
}
|
||||
|
||||
void EventLoopManagerQt::set_main_loop_signal_notifiers(Badge<EventLoopImplementationQt>)
|
||||
{
|
||||
MUST(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, m_signal_socket_fds));
|
||||
m_signal_socket_notifier = new QSocketNotifier(m_signal_socket_fds[0], QSocketNotifier::Read);
|
||||
QObject::connect(m_signal_socket_notifier, &QSocketNotifier::activated, [this] {
|
||||
int signal_number = {};
|
||||
ssize_t nread;
|
||||
do {
|
||||
errno = 0;
|
||||
nread = read(this->m_signal_socket_fds[0], &signal_number, sizeof(signal_number));
|
||||
if (nread >= 0)
|
||||
break;
|
||||
} while (errno == EINTR);
|
||||
VERIFY(nread == sizeof(signal_number));
|
||||
dispatch_signal(signal_number);
|
||||
});
|
||||
m_signal_socket_notifier->setEnabled(true);
|
||||
}
|
||||
|
||||
EventLoopManagerQt::~EventLoopManagerQt()
|
||||
{
|
||||
delete m_signal_socket_notifier;
|
||||
::close(m_signal_socket_fds[0]);
|
||||
::close(m_signal_socket_fds[1]);
|
||||
}
|
||||
|
||||
NonnullOwnPtr<Core::EventLoopImplementation> EventLoopManagerQt::make_implementation()
|
||||
{
|
||||
return adopt_own(*new EventLoopImplementationQt);
|
||||
}
|
||||
|
||||
}
|
79
Libraries/LibWebView/EventLoop/EventLoopImplementationQt.h
Normal file
79
Libraries/LibWebView/EventLoop/EventLoopImplementationQt.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <LibCore/EventLoopImplementation.h>
|
||||
|
||||
class QEvent;
|
||||
class QEventLoop;
|
||||
class QSocketNotifier;
|
||||
|
||||
namespace WebView {
|
||||
|
||||
class EventLoopImplementationQt;
|
||||
class EventLoopImplementationQtEventTarget;
|
||||
|
||||
class EventLoopManagerQt final : public Core::EventLoopManager {
|
||||
public:
|
||||
EventLoopManagerQt();
|
||||
virtual ~EventLoopManagerQt() override;
|
||||
virtual NonnullOwnPtr<Core::EventLoopImplementation> make_implementation() override;
|
||||
|
||||
virtual intptr_t register_timer(Core::EventReceiver&, int milliseconds, bool should_reload, Core::TimerShouldFireWhenNotVisible) override;
|
||||
virtual void unregister_timer(intptr_t timer_id) override;
|
||||
|
||||
virtual void register_notifier(Core::Notifier&) override;
|
||||
virtual void unregister_notifier(Core::Notifier&) override;
|
||||
|
||||
virtual void did_post_event() override;
|
||||
static bool event_target_received_event(Badge<EventLoopImplementationQtEventTarget>, QEvent* event);
|
||||
|
||||
virtual int register_signal(int, Function<void(int)>) override;
|
||||
virtual void unregister_signal(int) override;
|
||||
|
||||
void set_main_loop_signal_notifiers(Badge<EventLoopImplementationQt>);
|
||||
|
||||
private:
|
||||
static void handle_signal(int);
|
||||
|
||||
NonnullOwnPtr<EventLoopImplementationQtEventTarget> m_main_thread_event_target;
|
||||
QSocketNotifier* m_signal_socket_notifier { nullptr };
|
||||
int m_signal_socket_fds[2] = { -1, -1 };
|
||||
};
|
||||
|
||||
class EventLoopImplementationQt final : public Core::EventLoopImplementation {
|
||||
public:
|
||||
static NonnullOwnPtr<EventLoopImplementationQt> create() { return adopt_own(*new EventLoopImplementationQt); }
|
||||
|
||||
virtual ~EventLoopImplementationQt() override;
|
||||
|
||||
virtual int exec() override;
|
||||
virtual size_t pump(PumpMode) override;
|
||||
virtual void quit(int) override;
|
||||
virtual void wake() override;
|
||||
virtual void post_event(Core::EventReceiver& receiver, NonnullOwnPtr<Core::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; }
|
||||
virtual void notify_forked_and_in_child() override { }
|
||||
|
||||
void set_main_loop();
|
||||
|
||||
private:
|
||||
friend class EventLoopManagerQt;
|
||||
|
||||
EventLoopImplementationQt();
|
||||
bool is_main_loop() const { return m_main_loop; }
|
||||
|
||||
NonnullOwnPtr<QEventLoop> m_event_loop;
|
||||
bool m_main_loop { false };
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Gregory Bertilson <zaggy1024@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWebView/EventLoop/EventLoopImplementationQt.h>
|
||||
#include <LibWebView/EventLoop/EventLoopImplementationQtEventTarget.h>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
bool EventLoopImplementationQtEventTarget::event(QEvent* event)
|
||||
{
|
||||
return EventLoopManagerQt::event_target_received_event({}, event);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Gregory Bertilson <zaggy1024@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QEvent>
|
||||
#include <QObject>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
class EventLoopImplementationQtEventTarget final : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
virtual bool event(QEvent* event) override;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue