1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121

base / win / dark_mode_support.cc [blame]

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

#include "base/win/dark_mode_support.h"

#include <windows.h>

#include "base/check.h"
#include "base/native_library.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"

namespace {

// APIs for controlling how an app and window respond to system-level
// dark/light modes.

// Available on Wwindows build base::win::Version::WIN10_19H1 and up.
enum class PreferredAppMode {
  kDefault,
  kAllowDark,
  kForceDark,
  kForceLight,
  kMax
};

// The following APIs and code was based on information from here:
// https://github.com/ysc3839/win32-darkmode

// Only available on Windows build base::win::Version::WIN10_RS5.
// NOLINTNEXTLINE(readability/casting)
using UxThemeAllowDarkModeForAppFunc = bool(WINAPI*)(bool allow);

// Available on Windows build base::win::Version::WIN10_19H1 and up.
using UxThemeSetPreferredAppModeFunc =
    // NOLINTNEXTLINE(readability/casting)
    PreferredAppMode(WINAPI*)(PreferredAppMode app_mode);

// Available on Windows build base::win::Version::WIN10_RS5 and up.
// NOLINTNEXTLINE(readability/casting)
using UxThemeAllowDarkModeForWindowFunc = bool(WINAPI*)(HWND hwnd, bool allow);

// The following two ordinals are mutually exclusive and represent a difference
// between base::win::Version::WIN10_RS5 and base::win::Version::WIN10_19H1.
constexpr WORD kUxThemeAllowDarkModeForAppOrdinal = 135;
constexpr WORD kUxThemeSetPreferredAppModeOrdinal = 135;
constexpr WORD kUxThemeAllowDarkModeForWindowOrdinal = 133;

struct DarkModeSupport {
  UxThemeAllowDarkModeForAppFunc allow_dark_mode_for_app = nullptr;
  UxThemeSetPreferredAppModeFunc set_preferred_app_mode = nullptr;
  UxThemeAllowDarkModeForWindowFunc allow_dark_mode_for_window = nullptr;
};

const DarkModeSupport& GetDarkModeSupport() {
  static const DarkModeSupport dark_mode_support =
      [] {
        DarkModeSupport dark_mode_support;
        auto* os_info = base::win::OSInfo::GetInstance();
        // Dark mode only works on WIN10_RS5 and up. uxtheme.dll depends on
        // GDI32.dll which is not available under win32k lockdown sandbox.
        if (os_info->version() >= base::win::Version::WIN10_RS5 &&
            base::win::IsUser32AndGdi32Available()) {
          base::NativeLibraryLoadError error;
          HMODULE ux_theme_lib = base::PinSystemLibrary(L"uxtheme.dll", &error);
          DCHECK(!error.code);
          if (os_info->version() >= base::win::Version::WIN10_19H1) {
            dark_mode_support.set_preferred_app_mode =
                reinterpret_cast<UxThemeSetPreferredAppModeFunc>(
                    ::GetProcAddress(
                        ux_theme_lib,
                        MAKEINTRESOURCEA(kUxThemeSetPreferredAppModeOrdinal)));
          } else {
            dark_mode_support.allow_dark_mode_for_app =
                reinterpret_cast<UxThemeAllowDarkModeForAppFunc>(
                    ::GetProcAddress(
                        ux_theme_lib,
                        MAKEINTRESOURCEA(kUxThemeAllowDarkModeForAppOrdinal)));
          }
          dark_mode_support.allow_dark_mode_for_window =
              reinterpret_cast<UxThemeAllowDarkModeForWindowFunc>(
                  ::GetProcAddress(
                      ux_theme_lib,
                      MAKEINTRESOURCEA(kUxThemeAllowDarkModeForWindowOrdinal)));
        }
        return dark_mode_support;
      }();
  return dark_mode_support;
}

}  // namespace

namespace base::win {

bool IsDarkModeAvailable() {
  auto& dark_mode_support = GetDarkModeSupport();
  return (dark_mode_support.allow_dark_mode_for_app ||
          dark_mode_support.set_preferred_app_mode) &&
         dark_mode_support.allow_dark_mode_for_window;
}

void AllowDarkModeForApp(bool allow) {
  if (!IsDarkModeAvailable())
    return;
  auto& dark_mode_support = GetDarkModeSupport();
  if (dark_mode_support.set_preferred_app_mode) {
    dark_mode_support.set_preferred_app_mode(
        allow ? PreferredAppMode::kAllowDark : PreferredAppMode::kDefault);
  } else {
    dark_mode_support.allow_dark_mode_for_app(allow);
  }
}

bool AllowDarkModeForWindow(HWND hwnd, bool allow) {
  if (!IsDarkModeAvailable())
    return false;
  return GetDarkModeSupport().allow_dark_mode_for_window(hwnd, allow);
}

}  // namespace base::win