blob: f5502d6f286177635967cd204d5e311d880cb67b [file] [log] [blame]
// 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 <tuple>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ui/ui_features.h"
#include "components/dbus/thread_linux/dbus_thread_linux.h"
#include "components/dbus/utils/call_method.h"
#include "components/dbus/utils/connect_to_signal.h"
#include "components/dbus/utils/variant.h"
#include "components/dbus/xdg/systemd.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 ui {
DarkModeManagerLinux::DarkModeManagerLinux()
: DarkModeManagerLinux(dbus_thread_linux::GetSharedSessionBus(),
ui::GetDefaultLinuxUiTheme(),
&ui::GetLinuxUiThemes()) {}
DarkModeManagerLinux::DarkModeManagerLinux(
scoped_refptr<dbus::Bus> bus,
LinuxUiTheme* default_linux_ui_theme,
const std::vector<raw_ptr<LinuxUiTheme, VectorExperimental>>*
linux_ui_themes)
: linux_ui_themes_(linux_ui_themes),
bus_(bus),
settings_proxy_(bus_->GetObjectProxy(
kFreedesktopSettingsService,
dbus::ObjectPath(kFreedesktopSettingsObjectPath))) {
dbus_xdg::SetSystemdScopeUnitNameForXdgPortal(
bus_.get(), base::BindOnce(&DarkModeManagerLinux::OnSystemdUnitStarted,
weak_ptr_factory_.GetWeakPtr()));
// Read the toolkit preference while asynchronously fetching the
// portal preference.
if (default_linux_ui_theme) {
auto* native_theme = default_linux_ui_theme->GetNativeTheme();
native_theme_observer_.Observe(native_theme);
SetColorScheme(native_theme->preferred_color_scheme(), true);
}
}
DarkModeManagerLinux::~DarkModeManagerLinux() = default;
// static
NativeTheme::PreferredColorScheme
DarkModeManagerLinux::FreedesktopColorSchemeToNativeThemeColorScheme(
FreedesktopColorScheme color_scheme) {
switch (color_scheme) {
case FreedesktopColorScheme::kNoPreference:
return NativeTheme::PreferredColorScheme::kNoPreference;
case FreedesktopColorScheme::kDark:
return NativeTheme::PreferredColorScheme::kDark;
case FreedesktopColorScheme::kLight:
return NativeTheme::PreferredColorScheme::kLight;
}
return NativeTheme::PreferredColorScheme::kNoPreference;
}
void DarkModeManagerLinux::OnNativeThemeUpdated(
ui::NativeTheme* observed_theme) {
SetColorScheme(observed_theme->preferred_color_scheme(), true);
}
void DarkModeManagerLinux::OnSystemdUnitStarted(dbus_xdg::SystemdUnitStatus) {
// Subscribe to changes in the color scheme preference.
dbus_utils::ConnectToSignal(
settings_proxy_, kFreedesktopSettingsInterface, kSettingChangedSignal,
base::BindRepeating(&DarkModeManagerLinux::OnPortalSettingChanged,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&DarkModeManagerLinux::OnSignalConnected,
weak_ptr_factory_.GetWeakPtr()));
// Read initial color scheme preference.
dbus_utils::CallMethod<"ss", "v">(
settings_proxy_, kFreedesktopSettingsInterface, kReadMethod,
base::BindOnce(&DarkModeManagerLinux::OnReadColorScheme,
weak_ptr_factory_.GetWeakPtr()),
kSettingsNamespace, kColorSchemeKey);
// Read initial accent color preference.
if (base::FeatureList::IsEnabled(features::kUsePortalAccentColor)) {
dbus_utils::CallMethod<"ss", "v">(
settings_proxy_, kFreedesktopSettingsInterface, kReadMethod,
base::BindOnce(&DarkModeManagerLinux::OnReadAccentColor,
weak_ptr_factory_.GetWeakPtr()),
kSettingsNamespace, kAccentColorKey);
}
}
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_utils::ConnectToSignalResultSig<"ssv"> result) {
if (!result.has_value()) {
LOG(ERROR) << "Received malformed Setting Changed signal from "
"org.freedesktop.portal.Settings";
return;
}
auto& [namespace_changed, key_changed, value_variant] = result.value();
if (namespace_changed != kSettingsNamespace) {
return;
}
if (key_changed == kColorSchemeKey) {
auto new_color_scheme = std::move(value_variant).Take<uint32_t>();
if (!new_color_scheme.has_value()) {
LOG(ERROR)
<< "Failed to read color-scheme value from SettingChanged signal";
return;
}
SetColorScheme(FreedesktopColorSchemeToNativeThemeColorScheme(
static_cast<FreedesktopColorScheme>(*new_color_scheme)),
false);
} else if (key_changed == kAccentColorKey &&
base::FeatureList::IsEnabled(features::kUsePortalAccentColor)) {
SetAccentColor(std::move(value_variant));
}
}
void DarkModeManagerLinux::OnReadColorScheme(
dbus_utils::CallMethodResultSig<"v"> result) {
if (!result.has_value()) {
// Continue using the toolkit setting.
return;
}
auto& [outer_variant] = result.value();
auto inner_variant = std::move(outer_variant).Take<dbus_utils::Variant>();
if (!inner_variant.has_value()) {
LOG(ERROR) << "Failed to read inner variant from Read method response";
return;
}
auto new_color_scheme = std::move(*inner_variant).Take<uint32_t>();
if (!new_color_scheme.has_value()) {
LOG(ERROR) << "Failed to read color-scheme value from Read "
"method response";
return;
}
// Once we read the org.freedesktop.appearance color-scheme setting
// successfully, it should always take precedence over the toolkit color
// scheme.
ignore_toolkit_theme_changes_ = true;
SetColorScheme(FreedesktopColorSchemeToNativeThemeColorScheme(
static_cast<FreedesktopColorScheme>(*new_color_scheme)),
false);
}
void DarkModeManagerLinux::OnReadAccentColor(
dbus_utils::CallMethodResultSig<"v"> result) {
if (!result.has_value()) {
// Continue using the toolkit setting.
return;
}
auto& [outer_variant] = result.value();
auto inner_variant = std::move(outer_variant).Take<dbus_utils::Variant>();
if (!inner_variant.has_value()) {
LOG(ERROR) << "Failed to read inner variant from Read method response";
return;
}
SetAccentColor(std::move(*inner_variant));
}
void DarkModeManagerLinux::SetColorScheme(
NativeTheme::PreferredColorScheme color_scheme,
bool from_toolkit_theme) {
if (from_toolkit_theme && ignore_toolkit_theme_changes_) {
return;
}
if (!from_toolkit_theme) {
for (ui::LinuxUiTheme* linux_ui_theme : *linux_ui_themes_) {
linux_ui_theme->SetDarkTheme(color_scheme ==
NativeTheme::PreferredColorScheme::kDark);
}
}
if (preferred_color_scheme_ == color_scheme) {
return;
}
preferred_color_scheme_ = color_scheme;
auto* const native_theme = NativeTheme::GetInstanceForNativeUi();
native_theme->set_preferred_color_scheme(preferred_color_scheme_);
native_theme->NotifyOnNativeThemeUpdated();
}
void DarkModeManagerLinux::SetAccentColor(dbus_utils::Variant variant) {
auto color_tuple =
std::move(variant).Take<std::tuple<double, double, double>>();
if (!color_tuple.has_value()) {
LOG(ERROR) << "Failed to read accent-color from variant";
return;
}
auto& [r, g, b] = *color_tuple;
bool valid = (r >= 0.0 && r <= 1.0) && (g >= 0.0 && g <= 1.0) &&
(b >= 0.0 && b <= 1.0);
std::optional<SkColor> accent_color;
if (valid) {
accent_color = SkColorSetRGB(r * 255, g * 255, b * 255);
}
for (ui::LinuxUiTheme* linux_ui_theme : *linux_ui_themes_) {
linux_ui_theme->SetAccentColor(accent_color);
}
auto* const native_theme = NativeTheme::GetInstanceForNativeUi();
native_theme->set_user_color(accent_color);
native_theme->NotifyOnNativeThemeUpdated();
}
} // namespace ui