LibCore+LibWebView+UI/Qt: Support TimeZoneWatcher on Windows

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.
This commit is contained in:
ayeteadoe 2025-09-16 18:06:00 -07:00 committed by Jelle Raaijmakers
parent ba9c8b8462
commit f4c8fd4bef
5 changed files with 87 additions and 1 deletions

View File

@ -90,6 +90,12 @@ elseif (APPLE AND NOT IOS)
Platform/ProcessStatisticsMach.cpp
TimeZoneWatcherMacOS.mm
)
elseif (WIN32)
list(APPEND SOURCES
FileWatcherUnimplemented.cpp
Platform/ProcessStatisticsUnimplemented.cpp
TimeZoneWatcherWindows.cpp
)
else()
list(APPEND SOURCES
FileWatcherUnimplemented.cpp

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2025, ayeteadoe <ayeteadoe@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Platform.h>
#include <LibCore/TimeZoneWatcher.h>
#if !defined(AK_OS_WINDOWS)
static_assert(false, "This file must only be used for Windows");
#endif
namespace Core {
class TimeZoneWatcherImpl final : public TimeZoneWatcher {
public:
static ErrorOr<NonnullOwnPtr<TimeZoneWatcherImpl>> create()
{
return adopt_own(*new TimeZoneWatcherImpl());
}
private:
TimeZoneWatcherImpl() = default;
};
ErrorOr<NonnullOwnPtr<TimeZoneWatcher>> TimeZoneWatcher::create()
{
return TimeZoneWatcherImpl::create();
}
}

View File

@ -362,7 +362,7 @@ ErrorOr<void> Application::launch_services()
} else {
m_time_zone_watcher = time_zone_watcher.release_value();
m_time_zone_watcher->on_time_zone_changed = []() {
m_time_zone_watcher->on_time_zone_changed = [] {
WebContentClient::for_each_client([&](WebView::WebContentClient& client) {
client.async_system_time_zone_changed();
return IterationDecision::Continue;
@ -874,6 +874,13 @@ void Application::refresh_tab_list()
m_devtools->refresh_tab_list();
}
Optional<Core::TimeZoneWatcher&> Application::time_zone_watcher()
{
if (m_time_zone_watcher != nullptr)
return *m_time_zone_watcher;
return {};
}
Vector<DevTools::TabDescription> Application::tab_list() const
{
Vector<DevTools::TabDescription> tabs;

View File

@ -105,6 +105,8 @@ public:
ErrorOr<void> toggle_devtools_enabled();
void refresh_tab_list();
Optional<Core::TimeZoneWatcher&> time_zone_watcher();
protected:
explicit Application(Optional<ByteString> ladybird_binary_path = {});

View File

@ -18,8 +18,39 @@
#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)
@ -31,6 +62,14 @@ public:
{
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)