chromium/ui/color/win/accent_color_observer.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/color/win/accent_color_observer.h"

#include <utility>

#include "base/no_destructor.h"
#include "base/task/sequenced_task_runner.h"
#include "skia/ext/skia_utils_win.h"
#include "ui/gfx/color_utils.h"

namespace ui {

// static
AccentColorObserver* AccentColorObserver::Get() {
  static base::NoDestructor<AccentColorObserver> observer;
  return observer.get();
}

AccentColorObserver::AccentColorObserver() {
  dwm_key_ = std::make_unique<base::win::RegKey>(
      HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\DWM", KEY_READ);
  if (dwm_key_->Valid()) {
    OnDwmKeyUpdated();
  } else {
    dwm_key_.reset();
  }
}

AccentColorObserver::~AccentColorObserver() = default;

base::CallbackListSubscription AccentColorObserver::Subscribe(
    base::RepeatingClosure callback) {
  return callbacks_.Add(std::move(callback));
}

void AccentColorObserver::SetAccentColorForTesting(
    std::optional<SkColor> accent_color) {
  accent_color_ = accent_color;
  callbacks_.Notify();
}

void AccentColorObserver::SetUseDwmFrameColorForTesting(
    bool use_dwm_frame_color) {
  use_dwm_frame_color_ = use_dwm_frame_color;
  callbacks_.Notify();
}

void AccentColorObserver::OnDwmKeyUpdated() {
  accent_border_color_ = std::nullopt;
  DWORD colorization_color, colorization_color_balance;
  if ((dwm_key_->ReadValueDW(L"ColorizationColor", &colorization_color) ==
       ERROR_SUCCESS) &&
      (dwm_key_->ReadValueDW(L"ColorizationColorBalance",
                             &colorization_color_balance) == ERROR_SUCCESS)) {
    // The accent border color is a linear blend between the colorization
    // color and the neutral #d9d9d9. colorization_color_balance is the
    // percentage of the colorization color in that blend.
    //
    // On Windows version 1611 colorization_color_balance can be 0xfffffff3 if
    // the accent color is taken from the background and either the background
    // is a solid color or was just changed to a slideshow. It's unclear what
    // that value's supposed to mean, so change it to 80 to match Edge's
    // behavior.
    if (colorization_color_balance > 100)
      colorization_color_balance = 80;

    // colorization_color's high byte is not an alpha value, so replace it
    // with 0xff to make an opaque ARGB color.
    SkColor input_color = SkColorSetA(colorization_color, 0xff);

    accent_border_color_ =
        color_utils::AlphaBlend(input_color, SkColorSetRGB(0xd9, 0xd9, 0xd9),
                                colorization_color_balance / 100.0f);
  }

  accent_color_ = std::nullopt;
  accent_color_inactive_ = std::nullopt;
  DWORD accent_color = 0;
  if (dwm_key_->ReadValueDW(L"AccentColor", &accent_color) == ERROR_SUCCESS) {
    accent_color_ = skia::COLORREFToSkColor(accent_color);
    DWORD accent_color_inactive = 0;
    if (dwm_key_->ReadValueDW(L"AccentColorInactive", &accent_color_inactive) ==
        ERROR_SUCCESS) {
      accent_color_inactive_ = skia::COLORREFToSkColor(accent_color_inactive);
    }
  }

  DWORD color_prevalence;
  use_dwm_frame_color_ =
      accent_color_.has_value() &&
      (dwm_key_->ReadValueDW(L"ColorPrevalence", &color_prevalence) ==
       ERROR_SUCCESS) &&
      color_prevalence == 1;

  callbacks_.Notify();

  // Watch for future changes. If there is no task runner, this is a test or
  // tool context and watching is unnecessary.
  if (!base::SequencedTaskRunner::HasCurrentDefault() ||
      !dwm_key_->StartWatching(base::BindOnce(
          &AccentColorObserver::OnDwmKeyUpdated, base::Unretained(this)))) {
    dwm_key_.reset();
  }
}

}  // namespace ui