diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index 3470da1..ff39851b 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn @@ -5628,8 +5628,12 @@ sources += [ "views/chrome_browser_main_extra_parts_views_linux.cc", "views/chrome_browser_main_extra_parts_views_linux.h", + "views/dark_mode_manager_linux.cc", + "views/dark_mode_manager_linux.h", ] deps += [ + "//components/dbus/thread_linux", + "//dbus", "//ui/base/cursor", "//ui/ozone", ] diff --git a/chrome/browser/ui/DEPS b/chrome/browser/ui/DEPS index fc3fab23..b56a704e 100644 --- a/chrome/browser/ui/DEPS +++ b/chrome/browser/ui/DEPS @@ -42,6 +42,9 @@ "browser_navigator_browsertest\.cc": [ "+ash/shell.h", ], + "dark_mode_manager_linux\.cc": [ + "+dbus", + ], "fullscreen_controller_interactive_browsertest\.cc": [ "+ash/shell.h", ], diff --git a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc index dbc9cc4e..d7fad5b 100644 --- a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc +++ b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc @@ -7,6 +7,7 @@ #include "base/metrics/histogram_macros.h" #include "chrome/browser/themes/theme_service_aura_linux.h" #include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/views/dark_mode_manager_linux.h" #include "chrome/browser/ui/views/theme_profile_key.h" #include "ui/base/buildflags.h" #include "ui/base/cursor/cursor_factory.h" @@ -56,6 +57,8 @@ UMA_HISTOGRAM_ENUMERATION("Linux.SystemTheme.Default", linux_ui_theme->GetNativeTheme()->system_theme()); } + + dark_mode_manager_ = std::make_unique(); } void ChromeBrowserMainExtraPartsViewsLinux::PreCreateThreads() { diff --git a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h index 392d14c..6deb520 100644 --- a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h +++ b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h @@ -13,6 +13,7 @@ namespace ui { class LinuxUiGetter; +class DarkModeManagerLinux; } // Extra parts, which are used by both Ozone/X11/Wayland and inherited by the @@ -41,6 +42,8 @@ absl::optional display_observer_; std::unique_ptr linux_ui_getter_; + + std::unique_ptr dark_mode_manager_; }; #endif // CHROME_BROWSER_UI_VIEWS_CHROME_BROWSER_MAIN_EXTRA_PARTS_VIEWS_LINUX_H_ diff --git a/chrome/browser/ui/views/dark_mode_manager_linux.cc b/chrome/browser/ui/views/dark_mode_manager_linux.cc new file mode 100644 index 0000000..bb638f7 --- /dev/null +++ b/chrome/browser/ui/views/dark_mode_manager_linux.cc @@ -0,0 +1,160 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/views/dark_mode_manager_linux.h" + +#include "base/functional/bind.h" +#include "base/logging.h" +#include "components/dbus/thread_linux/dbus_thread_linux.h" +#include "dbus/bus.h" +#include "dbus/message.h" +#include "dbus/object_proxy.h" +#include "ui/linux/linux_ui.h" +#include "ui/linux/linux_ui_factory.h" +#include "ui/native_theme/native_theme.h" + +namespace { + +constexpr char kFreedesktopSettingsService[] = "org.freedesktop.portal.Desktop"; +constexpr char kFreedesktopSettingsObjectPath[] = + "/org/freedesktop/portal/desktop"; +constexpr char kFreedesktopSettingsInterface[] = + "org.freedesktop.portal.Settings"; +constexpr char kSettingChangedSignal[] = "SettingChanged"; +constexpr char kReadMethod[] = "Read"; +constexpr char kSettingsNamespace[] = "org.freedesktop.appearance"; +constexpr char kColorSchemeKey[] = "color-scheme"; +constexpr int kFreedesktopColorSchemeDark = 1; + +scoped_refptr CreateBus() { + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SESSION; + options.connection_type = dbus::Bus::PRIVATE; + options.dbus_task_runner = dbus_thread_linux::GetTaskRunner(); + return base::MakeRefCounted(options); +} + +} // namespace + +namespace ui { + +DarkModeManagerLinux::DarkModeManagerLinux() + : bus_(CreateBus()), + settings_proxy_(bus_->GetObjectProxy( + kFreedesktopSettingsService, + dbus::ObjectPath(kFreedesktopSettingsObjectPath))) { + // Subscribe to changes in the color scheme preference. + settings_proxy_->ConnectToSignal( + kFreedesktopSettingsInterface, kSettingChangedSignal, + base::BindRepeating(&DarkModeManagerLinux::OnPortalSettingChanged, + weak_ptr_factory_.GetWeakPtr()), + base::BindOnce(&DarkModeManagerLinux::OnSignalConnected, + weak_ptr_factory_.GetWeakPtr())); + + // Read initial color scheme preference. + dbus::MethodCall method_call(kFreedesktopSettingsInterface, kReadMethod); + dbus::MessageWriter writer(&method_call); + writer.AppendString(kSettingsNamespace); + writer.AppendString(kColorSchemeKey); + settings_proxy_->CallMethod( + &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + base::BindOnce(&DarkModeManagerLinux::OnReadColorSchemeResponse, + weak_ptr_factory_.GetWeakPtr())); + + // Read the toolkit preference while asynchronously fetching the + // portal preference. + if (auto* linux_ui_theme = ui::GetDefaultLinuxUiTheme()) { + auto* native_theme = linux_ui_theme->GetNativeTheme(); + native_theme_observer_.Observe(native_theme); + SetColorScheme(native_theme->ShouldUseDarkColors()); + } +} + +DarkModeManagerLinux::~DarkModeManagerLinux() { + settings_proxy_ = nullptr; + dbus::Bus* const bus_ptr = bus_.get(); + bus_ptr->GetDBusTaskRunner()->PostTask( + FROM_HERE, base::BindOnce(&dbus::Bus::ShutdownAndBlock, std::move(bus_))); +} + +void DarkModeManagerLinux::OnNativeThemeUpdated( + ui::NativeTheme* observed_theme) { + SetColorScheme(observed_theme->ShouldUseDarkColors()); +} + +void DarkModeManagerLinux::OnSignalConnected(const std::string& interface_name, + const std::string& signal_name, + bool connected) { + // Nothing to do. Continue using the toolkit setting if !connected. +} + +void DarkModeManagerLinux::OnPortalSettingChanged(dbus::Signal* signal) { + dbus::MessageReader reader(signal); + + std::string namespace_changed; + std::string key_changed; + dbus::MessageReader variant_reader(nullptr); + if (!reader.PopString(&namespace_changed) || + !reader.PopString(&key_changed) || !reader.PopVariant(&variant_reader)) { + LOG(ERROR) << "Received malformed Setting Changed signal from " + "org.freedesktop.portal.Settings"; + return; + } + + if (namespace_changed != kSettingsNamespace || + key_changed != kColorSchemeKey) { + return; + } + + uint32_t new_color_scheme; + if (!variant_reader.PopUint32(&new_color_scheme)) { + LOG(ERROR) + << "Failed to read color-scheme value from SettingChanged signal"; + return; + } + + SetColorScheme(new_color_scheme == kFreedesktopColorSchemeDark); +} + +void DarkModeManagerLinux::OnReadColorSchemeResponse(dbus::Response* response) { + if (!response) { + // Continue using the toolkit setting. + return; + } + + dbus::MessageReader reader(response); + dbus::MessageReader variant_reader(nullptr); + if (!reader.PopVariant(&variant_reader)) { + LOG(ERROR) << "Failed to read variant from Read method response"; + return; + } + + uint32_t new_color_scheme; + if (!variant_reader.PopVariantOfUint32(&new_color_scheme)) { + LOG(ERROR) << "Failed to read color-scheme value from Read " + "method response"; + return; + } + + // Ignore future updates from the toolkit theme. + native_theme_observer_.Reset(); + + SetColorScheme(new_color_scheme == kFreedesktopColorSchemeDark); +} + +void DarkModeManagerLinux::SetColorScheme(bool prefer_dark_theme) { + if (prefer_dark_theme_ == prefer_dark_theme) { + return; + } + prefer_dark_theme_ = prefer_dark_theme; + + NativeTheme* web_theme = NativeTheme::GetInstanceForWeb(); + web_theme->set_use_dark_colors(prefer_dark_theme_); + web_theme->set_preferred_color_scheme( + prefer_dark_theme_ ? NativeTheme::PreferredColorScheme::kDark + : NativeTheme::PreferredColorScheme::kLight); + web_theme->NotifyOnNativeThemeUpdated(); +} + +} // namespace ui diff --git a/chrome/browser/ui/views/dark_mode_manager_linux.h b/chrome/browser/ui/views/dark_mode_manager_linux.h new file mode 100644 index 0000000..34b07ff --- /dev/null +++ b/chrome/browser/ui/views/dark_mode_manager_linux.h @@ -0,0 +1,62 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_VIEWS_DARK_MODE_MANAGER_LINUX_H_ +#define CHROME_BROWSER_UI_VIEWS_DARK_MODE_MANAGER_LINUX_H_ + +#include + +#include "base/memory/scoped_refptr.h" +#include "base/memory/weak_ptr.h" +#include "base/scoped_observation.h" +#include "ui/native_theme/native_theme_observer.h" + +namespace dbus { +class Bus; +class ObjectProxy; +class Response; +class Signal; +} // namespace dbus + +namespace ui { + +// Observes the system color scheme preference using +// org.freedesktop.portal.Settings. Falls back to the toolkit preference if +// org.freedesktop.portal.Settings is unavailable. Propagates the dark mode +// preference to the web theme. +class DarkModeManagerLinux : public NativeThemeObserver { + public: + DarkModeManagerLinux(); + DarkModeManagerLinux(const DarkModeManagerLinux&) = delete; + DarkModeManagerLinux& operator=(const DarkModeManagerLinux&) = delete; + ~DarkModeManagerLinux() override; + + private: + // ui::NativeThemeObserver: + void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override; + + // D-Bus async handlers + void OnSignalConnected(const std::string& interface_name, + const std::string& signal_name, + bool connected); + void OnPortalSettingChanged(dbus::Signal* signal); + void OnReadColorSchemeResponse(dbus::Response* response); + + // Sets `prefer_dark_theme_` and propagates to the web theme. + void SetColorScheme(bool prefer_dark_theme); + + scoped_refptr bus_; + raw_ptr settings_proxy_; + + bool prefer_dark_theme_ = false; + + base::ScopedObservation + native_theme_observer_{this}; + + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace ui + +#endif // CHROME_BROWSER_UI_VIEWS_DARK_MODE_MANAGER_LINUX_H_ diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc index 91b1e98..7adddbd 100644 --- a/chrome/common/chrome_features.cc +++ b/chrome/common/chrome_features.cc @@ -1448,17 +1448,17 @@ BASE_FEATURE(kWebShare, "WebShare", base::FEATURE_DISABLED_BY_DEFAULT); #endif -// Whether to enable "dark mode" enhancements in Mac Mojave or Windows 10 for -// UIs implemented with web technologies. +// Whether to enable "dark mode" enhancements in Mac Mojave, Windows 10, or +// Linux for UIs implemented with web technologies. BASE_FEATURE(kWebUIDarkMode, "WebUIDarkMode", #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID) || \ - BUILDFLAG(IS_CHROMEOS) + BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) base::FEATURE_ENABLED_BY_DEFAULT #else base::FEATURE_DISABLED_BY_DEFAULT #endif // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID) || - // BUILDFLAG(IS_CHROMEOS) + // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) ); #if BUILDFLAG(IS_CHROMEOS_ASH) diff --git a/ui/qt/qt_ui.cc b/ui/qt/qt_ui.cc index b188ad0..6c0d2cd 100644 --- a/ui/qt/qt_ui.cc +++ b/ui/qt/qt_ui.cc @@ -98,6 +98,13 @@ QtNativeTheme& operator=(const QtNativeTheme&) = delete; ~QtNativeTheme() override = default; + void ThemeChanged(bool prefer_dark_theme) { + set_use_dark_colors(IsForcedDarkMode() || prefer_dark_theme); + set_preferred_color_scheme(CalculatePreferredColorScheme()); + + NotifyOnNativeThemeUpdated(); + } + // ui::NativeTheme: DISABLE_CFI_VCALL void PaintFrameTopArea(cc::PaintCanvas* canvas, @@ -387,7 +394,7 @@ } void QtUi::ThemeChanged() { - native_theme_->NotifyOnNativeThemeUpdated(); + native_theme_->ThemeChanged(PreferDarkTheme()); } DISABLE_CFI_VCALL diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index decfb02b6817e..108e2af907e25 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn @@ -5632,20 +5632,24 @@ static_library("ui") { ] } - if (use_aura) { + if (use_aura && (is_linux || is_chromeos_lacros)) { # These files can do Gtk+-based theming for builds with gtk enabled. - if (is_linux || is_chromeos_lacros) { + sources += [ + "views/chrome_browser_main_extra_parts_views_linux.cc", + "views/chrome_browser_main_extra_parts_views_linux.h", + ] + deps += [ + "//ui/base/cursor", + "//ui/ozone", + ] + if (use_dbus) { sources += [ - "views/chrome_browser_main_extra_parts_views_linux.cc", - "views/chrome_browser_main_extra_parts_views_linux.h", "views/dark_mode_manager_linux.cc", "views/dark_mode_manager_linux.h", ] deps += [ "//components/dbus/thread_linux", "//dbus", - "//ui/base/cursor", - "//ui/ozone", ] } } diff --git a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc index d7fad5b5b9007..23d0611fdb2b5 100644 --- a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc +++ b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.cc @@ -7,7 +7,6 @@ #include "base/metrics/histogram_macros.h" #include "chrome/browser/themes/theme_service_aura_linux.h" #include "chrome/browser/ui/browser_list.h" -#include "chrome/browser/ui/views/dark_mode_manager_linux.h" #include "chrome/browser/ui/views/theme_profile_key.h" #include "ui/base/buildflags.h" #include "ui/base/cursor/cursor_factory.h" @@ -19,6 +18,10 @@ #include "ui/native_theme/native_theme.h" #include "ui/ozone/public/ozone_platform.h" +#if defined(USE_DBUS) +#include "chrome/browser/ui/views/dark_mode_manager_linux.h" +#endif + namespace { class LinuxUiGetterImpl : public ui::LinuxUiGetter { @@ -57,8 +60,9 @@ void ChromeBrowserMainExtraPartsViewsLinux::ToolkitInitialized() { UMA_HISTOGRAM_ENUMERATION("Linux.SystemTheme.Default", linux_ui_theme->GetNativeTheme()->system_theme()); } - +#if defined(USE_DBUS) dark_mode_manager_ = std::make_unique(); +#endif } void ChromeBrowserMainExtraPartsViewsLinux::PreCreateThreads() { diff --git a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h index 6deb5205d198a..bc9167bda1fc3 100644 --- a/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h +++ b/chrome/browser/ui/views/chrome_browser_main_extra_parts_views_linux.h @@ -13,7 +13,9 @@ namespace ui { class LinuxUiGetter; +#if defined(USE_DBUS) class DarkModeManagerLinux; +#endif } // Extra parts, which are used by both Ozone/X11/Wayland and inherited by the @@ -42,8 +44,9 @@ class ChromeBrowserMainExtraPartsViewsLinux absl::optional display_observer_; std::unique_ptr linux_ui_getter_; - +#if defined(USE_DBUS) std::unique_ptr dark_mode_manager_; +#endif }; #endif // CHROME_BROWSER_UI_VIEWS_CHROME_BROWSER_MAIN_EXTRA_PARTS_VIEWS_LINUX_H_