ladybird/UI/Qt/Application.cpp
Timothy Flynn 7d6616669c LibWebView+UI: Move the UI event loops to the UI folder
Once upon a time, we needed the UI-specific event loops outside of the
UI process. This is no longer the case. Let's move the event loops back
to the UI folder to remove the awkward interface library we were left
with.
2025-12-05 14:24:05 -05:00

239 lines
7.2 KiB
C++

/*
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/ArgsParser.h>
#include <LibWebView/URL.h>
#include <UI/Qt/Application.h>
#include <UI/Qt/EventLoopImplementationQt.h>
#include <UI/Qt/Settings.h>
#include <UI/Qt/StringUtils.h>
#include <QClipboard>
#include <QDesktopServices>
#include <QFileDialog>
#include <QFileOpenEvent>
#include <QMessageBox>
#include <QMimeData>
#if defined(AK_OS_WINDOWS)
# include <AK/Windows.h>
# include <LibCore/TimeZoneWatcher.h>
# include <QAbstractNativeEventFilter>
#endif
namespace Ladybird {
#if defined(AK_OS_WINDOWS)
class NativeWindowsTimeChangeEventFilter : public QAbstractNativeEventFilter {
public:
NativeWindowsTimeChangeEventFilter(Core::TimeZoneWatcher& time_zone_watcher)
: m_time_zone_watcher(time_zone_watcher)
{
}
bool nativeEventFilter(QByteArray const& event_type, void* message, qintptr*) override
{
if (event_type == QByteArrayLiteral("windows_generic_MSG")) {
auto msg = static_cast<MSG*>(message);
if (msg->message == WM_TIMECHANGE) {
m_time_zone_watcher.on_time_zone_changed();
}
}
return false;
}
private:
Core::TimeZoneWatcher& m_time_zone_watcher;
};
#endif
class LadybirdQApplication : public QApplication {
public:
explicit LadybirdQApplication(Main::Arguments& arguments)
: QApplication(arguments.argc, arguments.argv)
{
}
virtual bool event(QEvent* event) override
{
auto& application = static_cast<Application&>(WebView::Application::the());
#if defined(AK_OS_WINDOWS)
static Optional<NativeWindowsTimeChangeEventFilter> time_change_event_filter {};
if (auto time_zone_watcher = application.time_zone_watcher(); !time_change_event_filter.has_value() && time_zone_watcher.has_value()) {
time_change_event_filter.emplace(time_zone_watcher.value());
installNativeEventFilter(&time_change_event_filter.value());
}
#endif
switch (event->type()) {
case QEvent::FileOpen: {
if (!application.on_open_file)
break;
auto const& open_event = *static_cast<QFileOpenEvent const*>(event);
auto file = ak_string_from_qstring(open_event.file());
if (auto file_url = WebView::sanitize_url(file); file_url.has_value())
application.on_open_file(file_url.release_value());
break;
}
default:
break;
}
return QApplication::event(event);
}
};
Application::Application() = default;
Application::~Application() = default;
void Application::create_platform_options(WebView::BrowserOptions&, WebView::RequestServerOptions&, WebView::WebContentOptions& web_content_options)
{
web_content_options.config_path = Settings::the()->directory();
}
NonnullOwnPtr<Core::EventLoop> Application::create_platform_event_loop()
{
if (!browser_options().headless_mode.has_value()) {
Core::EventLoopManager::install(*new EventLoopManagerQt);
m_application = make<LadybirdQApplication>(arguments());
}
auto event_loop = WebView::Application::create_platform_event_loop();
if (!browser_options().headless_mode.has_value())
static_cast<EventLoopImplementationQt&>(event_loop->impl()).set_main_loop();
return event_loop;
}
BrowserWindow& Application::new_window(Vector<URL::URL> const& initial_urls, BrowserWindow::IsPopupWindow is_popup_window, Tab* parent_tab, Optional<u64> page_index)
{
auto* window = new BrowserWindow(initial_urls, is_popup_window, parent_tab, move(page_index));
set_active_window(*window);
window->show();
if (initial_urls.is_empty()) {
auto* tab = window->current_tab();
if (tab) {
tab->set_url_is_hidden(true);
tab->focus_location_editor();
}
}
window->activateWindow();
window->raise();
return *window;
}
Optional<WebView::ViewImplementation&> Application::active_web_view() const
{
if (auto* active_tab = this->active_tab())
return active_tab->view();
return {};
}
Optional<WebView::ViewImplementation&> Application::open_blank_new_tab(Web::HTML::ActivateTab activate_tab) const
{
auto& tab = active_window().create_new_tab(activate_tab);
return tab.view();
}
Optional<ByteString> Application::ask_user_for_download_folder() const
{
auto path = QFileDialog::getExistingDirectory(nullptr, "Select download directory", QDir::homePath());
if (path.isNull())
return {};
return ak_byte_string_from_qstring(path);
}
void Application::display_download_confirmation_dialog(StringView download_name, LexicalPath const& path) const
{
auto message = MUST(String::formatted("{} saved to: {}", download_name, path));
QMessageBox dialog(active_tab());
dialog.setWindowTitle("Ladybird");
dialog.setIcon(QMessageBox::Information);
dialog.setText(qstring_from_ak_string(message));
dialog.addButton(QMessageBox::Ok);
dialog.addButton(QMessageBox::Open)->setText("Open folder");
if (dialog.exec() == QMessageBox::Open) {
auto path_url = QUrl::fromLocalFile(qstring_from_ak_string(path.dirname()));
QDesktopServices::openUrl(path_url);
}
}
void Application::display_error_dialog(StringView error_message) const
{
QMessageBox::warning(active_tab(), "Ladybird", qstring_from_ak_string(error_message));
}
Utf16String Application::clipboard_text() const
{
if (browser_options().headless_mode.has_value())
return WebView::Application::clipboard_text();
auto const* clipboard = QGuiApplication::clipboard();
return utf16_string_from_qstring(clipboard->text());
}
Vector<Web::Clipboard::SystemClipboardRepresentation> Application::clipboard_entries() const
{
if (browser_options().headless_mode.has_value())
return WebView::Application::clipboard_entries();
Vector<Web::Clipboard::SystemClipboardRepresentation> representations;
auto const* clipboard = QGuiApplication::clipboard();
auto const* mime_data = clipboard->mimeData();
if (!mime_data)
return {};
for (auto const& format : mime_data->formats()) {
auto data = ak_byte_string_from_qbytearray(mime_data->data(format));
auto mime_type = ak_string_from_qstring(format);
representations.empend(move(data), move(mime_type));
}
return representations;
}
void Application::insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresentation entry)
{
if (browser_options().headless_mode.has_value()) {
WebView::Application::insert_clipboard_entry(move(entry));
return;
}
auto* mime_data = new QMimeData();
mime_data->setData(qstring_from_ak_string(entry.mime_type), qbytearray_from_ak_string(entry.data));
auto* clipboard = QGuiApplication::clipboard();
clipboard->setMimeData(mime_data);
}
void Application::on_devtools_enabled() const
{
WebView::Application::on_devtools_enabled();
if (m_active_window)
m_active_window->on_devtools_enabled();
}
void Application::on_devtools_disabled() const
{
WebView::Application::on_devtools_disabled();
if (m_active_window)
m_active_window->on_devtools_disabled();
}
}