mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-10-21 15:40:28 +00:00
To detect system time zone changes on Windows, the event we need to look for is WM_TIMECHANGE. The problem is how the callback with said message actually gets invoked is very particular. (1) We must have an active message pump (event loop) for the message to ever be processed. (2) We must be a GUI application as WM_TIMECHANGE messages are seemingly sent to top level windows only. It doesn't say that in the docs for the event, but attempts of creating a LibTest-based application with a message pump and a message only window and never receiving the event point to that probably being true. This workaround is built off the fact that Qt's message pump defined internally in QEventDispatcherWin32::processEvents does in fact receive WM_TIMECHANGE events, even though it is not exposed as a QEvent::Type. Given the requirements stated above it makes sense that it works here as the message pump is executing in a QGuiApplication context. So we use a native event filter to hook into the unexposed WM_TIMECHANGE event and forward it along to the on_time_zone_changed() callback. Note that if a Windows GUI framework is done in the future, we'll have to re-add support to ensure the TimeZoneWatcher still gets invoked.
228 lines
6.8 KiB
C++
228 lines
6.8 KiB
C++
/*
|
|
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibCore/ArgsParser.h>
|
|
#include <LibWebView/EventLoop/EventLoopImplementationQt.h>
|
|
#include <LibWebView/URL.h>
|
|
#include <UI/Qt/Application.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::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 WebView::EventLoopManagerQt);
|
|
m_application = make<LadybirdQApplication>(arguments());
|
|
}
|
|
|
|
auto event_loop = WebView::Application::create_platform_event_loop();
|
|
|
|
if (!browser_options().headless_mode.has_value())
|
|
static_cast<WebView::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
|
|
{
|
|
auto const* clipboard = QGuiApplication::clipboard();
|
|
return utf16_string_from_qstring(clipboard->text());
|
|
}
|
|
|
|
Vector<Web::Clipboard::SystemClipboardRepresentation> Application::clipboard_entries() const
|
|
{
|
|
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)
|
|
{
|
|
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();
|
|
}
|
|
|
|
}
|