/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsLookAndFeel.h" #include #include #include #include "nsStyleConsts.h" #include "nsUXThemeData.h" #include "nsUXThemeConstants.h" #include "nsWindowsHelpers.h" #include "WinUtils.h" #include "WindowsUIUtils.h" #include "mozilla/FontPropertyTypes.h" #include "mozilla/Telemetry.h" #include "mozilla/widget/WinRegistry.h" using namespace mozilla; using namespace mozilla::widget; static Maybe GetColorFromTheme(nsUXThemeClass cls, int32_t aPart, int32_t aState, int32_t aPropId) { COLORREF color; HRESULT hr = GetThemeColor(nsUXThemeData::GetTheme(cls), aPart, aState, aPropId, &color); if (hr == S_OK) { return Some(COLOREF_2_NSRGB(color)); } return Nothing(); } static int32_t GetSystemParam(long flag, int32_t def) { DWORD value; return ::SystemParametersInfo(flag, 0, &value, 0) ? value : def; } static int32_t GetTooltipOffsetVertical() { static constexpr DWORD kDefaultCursorSize = 32; const DWORD cursorSize = GetSystemParam(MOZ_SPI_CURSORSIZE, kDefaultCursorSize); if (cursorSize == kDefaultCursorSize) { return LookAndFeel::kDefaultTooltipOffset; } return std::ceilf(float(LookAndFeel::kDefaultTooltipOffset) * float(cursorSize) / float(kDefaultCursorSize)); } static bool SystemWantsDarkTheme() { if (nsUXThemeData::IsHighContrastOn()) { return LookAndFeel::IsDarkColor( LookAndFeel::Color(StyleSystemColor::Window, ColorScheme::Light, LookAndFeel::UseStandins::No)); } WinRegistry::Key key( HKEY_CURRENT_USER, u"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"_ns, WinRegistry::KeyMode::QueryValue); if (NS_WARN_IF(!key)) { return false; } uint32_t light = key.GetValueAsDword(u"AppsUseLightTheme"_ns).valueOr(1); return !light; } uint32_t nsLookAndFeel::SystemColorFilter() { if (NS_WARN_IF(!mColorFilterWatcher)) { return 0; } const auto& key = mColorFilterWatcher->GetKey(); if (!key.GetValueAsDword(u"Active"_ns).valueOr(0)) { return 0; } return key.GetValueAsDword(u"FilterType"_ns).valueOr(0); } nsLookAndFeel::nsLookAndFeel() { mozilla::Telemetry::Accumulate(mozilla::Telemetry::TOUCH_ENABLED_DEVICE, WinUtils::IsTouchDeviceSupportPresent()); } nsLookAndFeel::~nsLookAndFeel() = default; void nsLookAndFeel::NativeInit() { EnsureInit(); } /* virtual */ void nsLookAndFeel::RefreshImpl() { mInitialized = false; // Fetch system colors next time they're used. nsXPLookAndFeel::RefreshImpl(); } static bool UseNonNativeMenuColors(ColorScheme aScheme) { return !LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme) || aScheme == ColorScheme::Dark; } nsresult nsLookAndFeel::NativeGetColor(ColorID aID, ColorScheme aScheme, nscolor& aColor) { EnsureInit(); auto IsHighlightColor = [&] { switch (aID) { case ColorID::MozMenuhover: return !UseNonNativeMenuColors(aScheme); case ColorID::Highlight: case ColorID::Selecteditem: // We prefer the generic dark selection color if we don't have an // explicit one. return aScheme != ColorScheme::Dark || mDarkHighlight; case ColorID::IMESelectedRawTextBackground: case ColorID::IMESelectedConvertedTextBackground: return true; default: return false; } }; auto IsHighlightTextColor = [&] { switch (aID) { case ColorID::MozMenubarhovertext: if (UseNonNativeMenuColors(aScheme)) { return false; } [[fallthrough]]; case ColorID::MozMenuhovertext: if (UseNonNativeMenuColors(aScheme)) { return false; } return !mColorMenuHoverText; case ColorID::Highlighttext: case ColorID::Selecteditemtext: // We prefer the generic dark selection color if we don't have an // explicit one. return aScheme != ColorScheme::Dark || mDarkHighlightText; case ColorID::IMESelectedRawTextForeground: case ColorID::IMESelectedConvertedTextForeground: return true; default: return false; } }; if (IsHighlightColor()) { if (aScheme == ColorScheme::Dark && mDarkHighlight) { aColor = *mDarkHighlight; } else { aColor = GetColorForSysColorIndex(COLOR_HIGHLIGHT); } return NS_OK; } if (IsHighlightTextColor()) { if (aScheme == ColorScheme::Dark && mDarkHighlightText) { aColor = *mDarkHighlightText; } else { aColor = GetColorForSysColorIndex(COLOR_HIGHLIGHTTEXT); } return NS_OK; } // Titlebar colors are color-scheme aware. switch (aID) { case ColorID::Activecaption: aColor = mTitlebarColors.Get(aScheme, true).mBg; return NS_OK; case ColorID::Captiontext: aColor = mTitlebarColors.Get(aScheme, true).mFg; return NS_OK; case ColorID::Activeborder: aColor = mTitlebarColors.Get(aScheme, true).mBorder; return NS_OK; case ColorID::Inactivecaption: aColor = mTitlebarColors.Get(aScheme, false).mBg; return NS_OK; case ColorID::Inactivecaptiontext: aColor = mTitlebarColors.Get(aScheme, false).mFg; return NS_OK; case ColorID::Inactiveborder: aColor = mTitlebarColors.Get(aScheme, false).mBorder; return NS_OK; default: break; } if (aScheme == ColorScheme::Dark) { if (auto color = GenericDarkColor(aID)) { aColor = *color; return NS_OK; } } static constexpr auto kNonNativeMenuText = NS_RGB(0x15, 0x14, 0x1a); nsresult res = NS_OK; int idx; switch (aID) { case ColorID::IMERawInputBackground: case ColorID::IMEConvertedTextBackground: aColor = NS_TRANSPARENT; return NS_OK; case ColorID::IMERawInputForeground: case ColorID::IMEConvertedTextForeground: aColor = NS_SAME_AS_FOREGROUND_COLOR; return NS_OK; case ColorID::IMERawInputUnderline: case ColorID::IMEConvertedTextUnderline: aColor = NS_SAME_AS_FOREGROUND_COLOR; return NS_OK; case ColorID::IMESelectedRawTextUnderline: case ColorID::IMESelectedConvertedTextUnderline: aColor = NS_TRANSPARENT; return NS_OK; // New CSS 2 Color definitions case ColorID::Appworkspace: idx = COLOR_APPWORKSPACE; break; case ColorID::Background: idx = COLOR_BACKGROUND; break; case ColorID::Buttonface: case ColorID::MozButtonhoverface: case ColorID::MozButtonactiveface: case ColorID::MozButtondisabledface: case ColorID::MozColheader: case ColorID::MozColheaderhover: case ColorID::MozColheaderactive: idx = COLOR_BTNFACE; break; case ColorID::Buttonhighlight: idx = COLOR_BTNHIGHLIGHT; break; case ColorID::Buttonshadow: idx = COLOR_BTNSHADOW; break; case ColorID::Buttontext: case ColorID::MozButtonhovertext: case ColorID::MozButtonactivetext: idx = COLOR_BTNTEXT; break; case ColorID::MozCellhighlighttext: aColor = NS_RGB(0, 0, 0); return NS_OK; case ColorID::MozCellhighlight: aColor = NS_RGB(206, 206, 206); return NS_OK; case ColorID::Graytext: idx = COLOR_GRAYTEXT; break; case ColorID::MozMenubarhovertext: if (UseNonNativeMenuColors(aScheme)) { aColor = kNonNativeMenuText; return NS_OK; } [[fallthrough]]; case ColorID::MozMenuhovertext: if (UseNonNativeMenuColors(aScheme)) { aColor = kNonNativeMenuText; return NS_OK; } if (mColorMenuHoverText) { aColor = *mColorMenuHoverText; return NS_OK; } idx = COLOR_HIGHLIGHTTEXT; break; case ColorID::MozMenuhover: MOZ_ASSERT(UseNonNativeMenuColors(aScheme)); aColor = NS_RGB(0xe0, 0xe0, 0xe6); return NS_OK; case ColorID::MozMenuhoverdisabled: if (UseNonNativeMenuColors(aScheme)) { aColor = NS_RGB(0xf0, 0xf0, 0xf3); return NS_OK; } aColor = NS_TRANSPARENT; return NS_OK; case ColorID::Infobackground: idx = COLOR_INFOBK; break; case ColorID::Infotext: idx = COLOR_INFOTEXT; break; case ColorID::Menu: if (UseNonNativeMenuColors(aScheme)) { aColor = NS_RGB(0xf9, 0xf9, 0xfb); return NS_OK; } idx = COLOR_MENU; break; case ColorID::Menutext: if (UseNonNativeMenuColors(aScheme)) { aColor = kNonNativeMenuText; return NS_OK; } idx = COLOR_MENUTEXT; break; case ColorID::Scrollbar: idx = COLOR_SCROLLBAR; break; case ColorID::Threeddarkshadow: idx = COLOR_3DDKSHADOW; break; case ColorID::Threedface: idx = COLOR_3DFACE; break; case ColorID::Threedhighlight: idx = COLOR_3DHIGHLIGHT; break; case ColorID::Threedlightshadow: case ColorID::Buttonborder: case ColorID::MozDisabledfield: case ColorID::MozSidebarborder: idx = COLOR_3DLIGHT; break; case ColorID::Threedshadow: idx = COLOR_3DSHADOW; break; case ColorID::Window: idx = COLOR_WINDOW; break; case ColorID::Windowframe: idx = COLOR_WINDOWFRAME; break; case ColorID::Windowtext: idx = COLOR_WINDOWTEXT; break; case ColorID::MozEventreerow: case ColorID::MozOddtreerow: case ColorID::Field: case ColorID::MozSidebar: case ColorID::MozCombobox: idx = COLOR_WINDOW; break; case ColorID::Fieldtext: case ColorID::MozSidebartext: case ColorID::MozComboboxtext: idx = COLOR_WINDOWTEXT; break; case ColorID::MozHeaderbar: case ColorID::MozHeaderbarinactive: case ColorID::MozDialog: idx = COLOR_3DFACE; break; case ColorID::Accentcolor: aColor = mColorAccent; return NS_OK; case ColorID::Accentcolortext: aColor = mColorAccentText; return NS_OK; case ColorID::MozHeaderbartext: case ColorID::MozHeaderbarinactivetext: case ColorID::MozDialogtext: case ColorID::MozColheadertext: case ColorID::MozColheaderhovertext: case ColorID::MozColheaderactivetext: idx = COLOR_WINDOWTEXT; break; case ColorID::MozNativehyperlinktext: idx = COLOR_HOTLIGHT; break; case ColorID::Marktext: case ColorID::Mark: case ColorID::SpellCheckerUnderline: case ColorID::MozAutofillBackground: aColor = GetStandinForNativeColor(aID, aScheme); return NS_OK; default: idx = COLOR_WINDOW; res = NS_ERROR_FAILURE; break; } aColor = GetColorForSysColorIndex(idx); return res; } nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) { EnsureInit(); nsresult res = NS_OK; switch (aID) { case IntID::ScrollButtonLeftMouseButtonAction: aResult = 0; break; case IntID::ScrollButtonMiddleMouseButtonAction: case IntID::ScrollButtonRightMouseButtonAction: aResult = 3; break; case IntID::CaretBlinkTime: aResult = static_cast(::GetCaretBlinkTime()); break; case IntID::CaretBlinkCount: { int32_t timeout = GetSystemParam(SPI_GETCARETTIMEOUT, 5000); auto blinkTime = ::GetCaretBlinkTime(); if (timeout <= 0 || blinkTime <= 0) { aResult = -1; break; } // 2 * blinkTime because this integer is a full blink cycle. aResult = std::ceil(float(timeout) / (2.0f * float(blinkTime))); break; } case IntID::CaretWidth: aResult = 1; break; case IntID::SelectTextfieldsOnKeyFocus: // Select textfield content when focused by kbd // used by EventStateManager::sTextfieldSelectModel aResult = 1; break; case IntID::SubmenuDelay: // This will default to the Windows' default // (400ms) on error. aResult = GetSystemParam(SPI_GETMENUSHOWDELAY, 400); break; case IntID::MenusCanOverlapOSBar: // we want XUL popups to be able to overlap the task bar. aResult = 1; break; case IntID::DragThresholdX: // The system metric is the number of pixels at which a drag should // start. Our look and feel metric is the number of pixels you can // move before starting a drag, so subtract 1. aResult = ::GetSystemMetrics(SM_CXDRAG) - 1; break; case IntID::DragThresholdY: aResult = ::GetSystemMetrics(SM_CYDRAG) - 1; break; case IntID::UseAccessibilityTheme: // High contrast is a misnomer under Win32 -- any theme can be used with // it, e.g. normal contrast with large fonts, low contrast, etc. The high // contrast flag really means -- use this theme and don't override it. aResult = nsUXThemeData::IsHighContrastOn(); break; case IntID::ScrollArrowStyle: aResult = eScrollArrowStyle_Single; break; case IntID::TreeOpenDelay: aResult = 1000; break; case IntID::TreeCloseDelay: aResult = 0; break; case IntID::TreeLazyScrollDelay: aResult = 150; break; case IntID::TreeScrollDelay: aResult = 100; break; case IntID::TreeScrollLinesMax: aResult = 3; break; case IntID::WindowsAccentColorInTitlebar: { aResult = mTitlebarColors.mUseAccent; } break; case IntID::AlertNotificationOrigin: aResult = 0; { // Get task bar window handle HWND shellWindow = FindWindowW(L"Shell_TrayWnd", nullptr); if (shellWindow != nullptr) { // Determine position APPBARDATA appBarData; appBarData.hWnd = shellWindow; appBarData.cbSize = sizeof(appBarData); if (SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData)) { // Set alert origin as a bit field - see LookAndFeel.h // 0 represents bottom right, sliding vertically. switch (appBarData.uEdge) { case ABE_LEFT: aResult = NS_ALERT_HORIZONTAL | NS_ALERT_LEFT; break; case ABE_RIGHT: aResult = NS_ALERT_HORIZONTAL; break; case ABE_TOP: aResult = NS_ALERT_TOP; [[fallthrough]]; case ABE_BOTTOM: // If the task bar is right-to-left, // move the origin to the left if (::GetWindowLong(shellWindow, GWL_EXSTYLE) & WS_EX_LAYOUTRTL) aResult |= NS_ALERT_LEFT; break; } } } } break; case IntID::IMERawInputUnderlineStyle: case IntID::IMEConvertedTextUnderlineStyle: aResult = static_cast(StyleTextDecorationStyle::Dashed); break; case IntID::IMESelectedRawTextUnderlineStyle: case IntID::IMESelectedConvertedTextUnderline: aResult = static_cast(StyleTextDecorationStyle::None); break; case IntID::SpellCheckerUnderlineStyle: aResult = static_cast(StyleTextDecorationStyle::Wavy); break; case IntID::ScrollbarButtonAutoRepeatBehavior: aResult = 0; break; case IntID::SwipeAnimationEnabled: // Forcibly enable the swipe animation on Windows. It doesn't matter on // platforms where "Drag two fingers to scroll" isn't supported since on // the platforms we will never generate any swipe gesture events. aResult = 1; break; case IntID::UseOverlayScrollbars: aResult = WindowsUIUtils::ComputeOverlayScrollbars(); break; case IntID::AllowOverlayScrollbarsOverlap: aResult = 0; break; case IntID::ScrollbarDisplayOnMouseMove: aResult = 1; break; case IntID::ScrollbarFadeBeginDelay: aResult = 2500; break; case IntID::ScrollbarFadeDuration: aResult = 350; break; case IntID::ContextMenuOffsetVertical: case IntID::ContextMenuOffsetHorizontal: aResult = 2; break; case IntID::TooltipOffsetVertical: aResult = GetTooltipOffsetVertical(); break; case IntID::SystemUsesDarkTheme: aResult = SystemWantsDarkTheme(); break; case IntID::SystemScrollbarSize: aResult = std::max(WinUtils::GetSystemMetricsForDpi(SM_CXVSCROLL, 96), WinUtils::GetSystemMetricsForDpi(SM_CXHSCROLL, 96)); break; case IntID::PrefersReducedMotion: { BOOL enable = TRUE; ::SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &enable, 0); aResult = !enable; break; } case IntID::PrefersReducedTransparency: { // Prefers reduced transparency if the option for "Transparency Effects" // is disabled aResult = !WindowsUIUtils::ComputeTransparencyEffects(); break; } case IntID::InvertedColors: { // Color filter values // 1: Inverted // 2: Grayscale inverted aResult = mCurrentColorFilter == 1 || mCurrentColorFilter == 2; break; } case IntID::PrimaryPointerCapabilities: { aResult = static_cast( widget::WinUtils::GetPrimaryPointerCapabilities()); break; } case IntID::AllPointerCapabilities: { aResult = static_cast(widget::WinUtils::GetAllPointerCapabilities()); break; } case IntID::TouchDeviceSupportPresent: aResult = !!WinUtils::IsTouchDeviceSupportPresent(); break; case IntID::PanelAnimations: aResult = 1; break; case IntID::HideCursorWhileTyping: { BOOL enable = TRUE; ::SystemParametersInfoW(SPI_GETMOUSEVANISH, 0, &enable, 0); aResult = enable; break; } default: aResult = 0; res = NS_ERROR_FAILURE; } return res; } nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) { nsresult res = NS_OK; switch (aID) { case FloatID::IMEUnderlineRelativeSize: aResult = 1.0f; break; case FloatID::SpellCheckerUnderlineRelativeSize: aResult = 1.0f; break; case FloatID::TextScaleFactor: aResult = WindowsUIUtils::ComputeTextScaleFactor(); break; default: aResult = -1.0; res = NS_ERROR_FAILURE; } return res; } LookAndFeelFont nsLookAndFeel::GetLookAndFeelFontInternal( const LOGFONTW& aLogFont, bool aUseShellDlg) { LookAndFeelFont result{}; result.haveFont() = false; // Get scaling factor from physical to logical pixels double pixelScale = 1.0 / WinUtils::SystemScaleFactor() / LookAndFeel::GetTextScaleFactor(); // The lfHeight is in pixels, and it needs to be adjusted for the // device it will be displayed on. // Screens and Printers will differ in DPI // // So this accounts for the difference in the DeviceContexts // The pixelScale will typically be 1.0 for the screen // (though larger for hi-dpi screens where the Windows resolution // scale factor is 125% or 150% or even more), and could be // any value when going to a printer, for example pixelScale is // 6.25 when going to a 600dpi printer. float pixelHeight = -aLogFont.lfHeight; if (pixelHeight < 0) { nsAutoFont hFont(::CreateFontIndirectW(&aLogFont)); if (!hFont) { return result; } nsAutoHDC dc(::GetDC(nullptr)); HGDIOBJ hObject = ::SelectObject(dc, hFont); TEXTMETRIC tm; ::GetTextMetrics(dc, &tm); ::SelectObject(dc, hObject); pixelHeight = tm.tmAscent; } pixelHeight *= pixelScale; // we have problem on Simplified Chinese system because the system // report the default font size is 8 points. but if we use 8, the text // display very ugly. force it to be at 9 points (12 pixels) on that // system (cp936), but leave other sizes alone. if (pixelHeight < 12 && ::GetACP() == 936) { pixelHeight = 12; } result.haveFont() = true; if (aUseShellDlg) { result.name() = u"MS Shell Dlg 2"_ns; } else { result.name() = aLogFont.lfFaceName; } result.size() = pixelHeight; result.italic() = !!aLogFont.lfItalic; // FIXME: Other weights? result.weight() = ((aLogFont.lfWeight == FW_BOLD) ? FontWeight::BOLD : FontWeight::NORMAL) .ToFloat(); return result; } LookAndFeelFont nsLookAndFeel::GetLookAndFeelFont(LookAndFeel::FontID anID) { LookAndFeelFont result{}; result.haveFont() = false; // FontID::Icon is handled differently than the others if (anID == LookAndFeel::FontID::Icon) { LOGFONTW logFont; if (::SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(logFont), (PVOID)&logFont, 0)) { result = GetLookAndFeelFontInternal(logFont, false); } return result; } NONCLIENTMETRICSW ncm; ncm.cbSize = sizeof(NONCLIENTMETRICSW); if (!::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), (PVOID)&ncm, 0)) { return result; } switch (anID) { case LookAndFeel::FontID::Menu: case LookAndFeel::FontID::MozPullDownMenu: result = GetLookAndFeelFontInternal(ncm.lfMenuFont, false); break; case LookAndFeel::FontID::Caption: result = GetLookAndFeelFontInternal(ncm.lfCaptionFont, false); break; case LookAndFeel::FontID::SmallCaption: result = GetLookAndFeelFontInternal(ncm.lfSmCaptionFont, false); break; case LookAndFeel::FontID::StatusBar: result = GetLookAndFeelFontInternal(ncm.lfStatusFont, false); break; case LookAndFeel::FontID::MozButton: case LookAndFeel::FontID::MozField: case LookAndFeel::FontID::MozList: // XXX It's not clear to me whether this is exactly the right // set of LookAndFeel values to map to the dialog font; we may // want to add or remove cases here after reviewing the visual // results under various Windows versions. result = GetLookAndFeelFontInternal(ncm.lfMessageFont, true); break; default: result = GetLookAndFeelFontInternal(ncm.lfMessageFont, false); break; } return result; } bool nsLookAndFeel::NativeGetFont(LookAndFeel::FontID anID, nsString& aFontName, gfxFontStyle& aFontStyle) { LookAndFeelFont font = GetLookAndFeelFont(anID); return LookAndFeelFontToStyle(font, aFontName, aFontStyle); } /* virtual */ char16_t nsLookAndFeel::GetPasswordCharacterImpl() { #define UNICODE_BLACK_CIRCLE_CHAR 0x25cf return UNICODE_BLACK_CIRCLE_CHAR; } static nscolor GetAccentColorText(const nscolor aAccentColor) { // We want the color that we return for text that will be drawn over // a background that has the accent color to have good contrast with // the accent color. Windows itself uses either white or black text // depending on how light or dark the accent color is. We do the same // here based on the luminance of the accent color with a threshhold // value. This algorithm should match what Windows does. It comes from: // // https://docs.microsoft.com/en-us/windows/uwp/style/color float luminance = (NS_GET_R(aAccentColor) * 2 + NS_GET_G(aAccentColor) * 5 + NS_GET_B(aAccentColor)) / 8; return luminance <= 128 ? NS_RGB(255, 255, 255) : NS_RGB(0, 0, 0); } static Maybe GetAccentColorText(const Maybe& aAccentColor) { if (!aAccentColor) { return Nothing(); } return Some(GetAccentColorText(*aAccentColor)); } nscolor nsLookAndFeel::GetColorForSysColorIndex(int index) { MOZ_ASSERT(index >= SYS_COLOR_MIN && index <= SYS_COLOR_MAX); return mSysColorTable[index - SYS_COLOR_MIN]; } auto nsLookAndFeel::ComputeTitlebarColors() -> TitlebarColors { TitlebarColors result; // Start with the native / non-accent-in-titlebar colors. result.mActiveLight = {GetColorForSysColorIndex(COLOR_ACTIVECAPTION), GetColorForSysColorIndex(COLOR_CAPTIONTEXT), GetColorForSysColorIndex(COLOR_ACTIVEBORDER)}; result.mInactiveLight = {GetColorForSysColorIndex(COLOR_INACTIVECAPTION), GetColorForSysColorIndex(COLOR_INACTIVECAPTIONTEXT), GetColorForSysColorIndex(COLOR_INACTIVEBORDER)}; if (!nsUXThemeData::IsHighContrastOn()) { // Use our non-native colors. result.mActiveLight = { GetStandinForNativeColor(ColorID::Activecaption, ColorScheme::Light), GetStandinForNativeColor(ColorID::Captiontext, ColorScheme::Light), GetStandinForNativeColor(ColorID::Activeborder, ColorScheme::Light)}; result.mInactiveLight = { GetStandinForNativeColor(ColorID::Inactivecaption, ColorScheme::Light), GetStandinForNativeColor(ColorID::Inactivecaptiontext, ColorScheme::Light), GetStandinForNativeColor(ColorID::Inactiveborder, ColorScheme::Light)}; } // Our dark colors are always non-native. result.mActiveDark = {*GenericDarkColor(ColorID::Activecaption), *GenericDarkColor(ColorID::Captiontext), *GenericDarkColor(ColorID::Activeborder)}; result.mInactiveDark = {*GenericDarkColor(ColorID::Inactivecaption), *GenericDarkColor(ColorID::Inactivecaptiontext), *GenericDarkColor(ColorID::Inactiveborder)}; // TODO(bug 1825241): Somehow get notified when this changes? Hopefully the // sys color notification is enough. WinRegistry::Key dwmKey(HKEY_CURRENT_USER, u"SOFTWARE\\Microsoft\\Windows\\DWM"_ns, WinRegistry::KeyMode::QueryValue); if (NS_WARN_IF(!dwmKey)) { return result; } // The order of the color components in the DWORD stored in the registry // happens to be the same order as we store the components in nscolor // so we can just assign directly here. result.mAccent = dwmKey.GetValueAsDword(u"AccentColor"_ns); result.mAccentText = GetAccentColorText(result.mAccent); if (!result.mAccent) { return result; } result.mAccentInactive = dwmKey.GetValueAsDword(u"AccentColorInactive"_ns); result.mAccentInactiveText = GetAccentColorText(result.mAccentInactive); // The ColorPrevalence value is set to 1 when the "Show color on title bar" // setting in the Color section of Window's Personalization settings is // turned on. result.mUseAccent = dwmKey.GetValueAsDword(u"ColorPrevalence"_ns).valueOr(0) == 1; if (!result.mUseAccent) { return result; } // TODO(emilio): Consider reading ColorizationColorBalance to compute a // more correct border color, see [1]. Though for opaque accent colors this // isn't needed. // // [1]: // https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:ui/color/win/accent_color_observer.cc;l=42;drc=9d4eb7ed25296abba8fd525a6bdd0fdbf4bcdd9f result.mActiveDark.mBorder = result.mActiveLight.mBorder = *result.mAccent; result.mInactiveDark.mBorder = result.mInactiveLight.mBorder = result.mAccentInactive.valueOr(NS_RGB(57, 57, 57)); result.mActiveLight.mBg = result.mActiveDark.mBg = *result.mAccent; result.mActiveLight.mFg = result.mActiveDark.mFg = *result.mAccentText; if (result.mAccentInactive) { result.mInactiveLight.mBg = result.mInactiveDark.mBg = *result.mAccentInactive; result.mInactiveLight.mFg = result.mInactiveDark.mFg = *result.mAccentInactiveText; } else { // This is hand-picked to .8 to change the accent color a bit but not too // much. constexpr uint8_t kBgAlpha = 208; const auto BlendWithAlpha = [](nscolor aBg, nscolor aFg, uint8_t aAlpha) -> nscolor { return NS_ComposeColors( aBg, NS_RGBA(NS_GET_R(aFg), NS_GET_G(aFg), NS_GET_B(aFg), aAlpha)); }; result.mInactiveLight.mBg = BlendWithAlpha(NS_RGB(255, 255, 255), *result.mAccent, kBgAlpha); result.mInactiveDark.mBg = BlendWithAlpha(NS_RGB(0, 0, 0), *result.mAccent, kBgAlpha); result.mInactiveLight.mFg = result.mInactiveDark.mFg = *result.mAccentText; } return result; } void nsLookAndFeel::EnsureInit() { if (mInitialized) { return; } mInitialized = true; mColorMenuHoverText = ::GetColorFromTheme(eUXMenu, MENU_POPUPITEM, MPI_HOT, TMT_TEXTCOLOR); // Fill out the sys color table. for (int i = SYS_COLOR_MIN; i <= SYS_COLOR_MAX; ++i) { mSysColorTable[i - SYS_COLOR_MIN] = [&] { if (auto c = WindowsUIUtils::GetSystemColor(ColorScheme::Light, i)) { return *c; } DWORD color = ::GetSysColor(i); return COLOREF_2_NSRGB(color); }(); } mDarkHighlight = WindowsUIUtils::GetSystemColor(ColorScheme::Dark, COLOR_HIGHLIGHT); mDarkHighlightText = WindowsUIUtils::GetSystemColor(ColorScheme::Dark, COLOR_HIGHLIGHTTEXT); mTitlebarColors = ComputeTitlebarColors(); mColorAccent = [&] { if (auto accent = WindowsUIUtils::GetAccentColor()) { return *accent; } // Try the titlebar accent as a fallback. if (mTitlebarColors.mAccent) { return *mTitlebarColors.mAccent; } // Seems to be the default color (hardcoded because of bug 1065998) return NS_RGB(0, 120, 215); }(); mColorAccentText = GetAccentColorText(mColorAccent); if (!mColorFilterWatcher) { WinRegistry::Key key( HKEY_CURRENT_USER, u"Software\\Microsoft\\ColorFiltering"_ns, WinRegistry::KeyMode::QueryValue | WinRegistry::KeyMode::Notify); if (key) { mColorFilterWatcher = MakeUnique( std::move(key), GetCurrentSerialEventTarget(), [this] { MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); if (mCurrentColorFilter != SystemColorFilter()) { LookAndFeel::NotifyChangedAllWindows( widget::ThemeChangeKind::MediaQueriesOnly); } }); } } mCurrentColorFilter = SystemColorFilter(); RecordTelemetry(); }