1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
12 #include "nsLookAndFeel.h"
17 #include <pango/pango.h>
18 #include <pango/pango-fontmap.h>
19 #include <fontconfig/fontconfig.h>
22 #include "GUniquePtr.h"
23 #include "nsGtkUtils.h"
24 #include "gfxPlatformGtk.h"
25 #include "mozilla/FontPropertyTypes.h"
26 #include "mozilla/Preferences.h"
27 #include "mozilla/RelativeLuminanceUtils.h"
28 #include "mozilla/StaticPrefs_layout.h"
29 #include "mozilla/StaticPrefs_widget.h"
30 #include "mozilla/StaticPrefs_browser.h"
31 #include "mozilla/AutoRestore.h"
32 #include "mozilla/Telemetry.h"
33 #include "mozilla/ScopeExit.h"
34 #include "mozilla/WidgetUtilsGtk.h"
35 #include "ScreenHelperGTK.h"
36 #include "ScrollbarDrawing.h"
38 #include "gtkdrawing.h"
40 #include "nsStyleConsts.h"
41 #include "gfxFontConstants.h"
42 #include "WidgetUtils.h"
45 #include "mozilla/gfx/2D.h"
47 #include <cairo-gobject.h>
49 #include "WidgetStyleCache.h"
51 #include "nsCSSColorUtils.h"
52 #include "mozilla/Preferences.h"
54 using namespace mozilla
;
55 using namespace mozilla::widget
;
58 # include "mozilla/Logging.h"
59 # include "nsTArray.h"
61 static LazyLogModule
gLnfLog("LookAndFeel");
62 # define LOGLNF(...) MOZ_LOG(gLnfLog, LogLevel::Debug, (__VA_ARGS__))
63 # define LOGLNF_ENABLED() MOZ_LOG_TEST(gLnfLog, LogLevel::Debug)
66 # define LOGLNF_ENABLED() false
67 #endif /* MOZ_LOGGING */
69 #define GDK_COLOR_TO_NS_RGB(c) \
70 ((nscolor)NS_RGB(c.red >> 8, c.green >> 8, c.blue >> 8))
71 #define GDK_RGBA_TO_NS_RGBA(c) \
72 ((nscolor)NS_RGBA((int)((c).red * 255), (int)((c).green * 255), \
73 (int)((c).blue * 255), (int)((c).alpha * 255)))
75 static bool sIgnoreChangedSettings
= false;
77 static void OnSettingsChange() {
78 if (sIgnoreChangedSettings
) {
81 // TODO: We could be more granular here, but for now assume everything
83 LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::StyleAndLayout
);
84 widget::IMContextWrapper::OnThemeChanged();
87 static void settings_changed_cb(GtkSettings
*, GParamSpec
*, void*) {
91 static bool sCSDAvailable
;
93 static nsCString
GVariantToString(GVariant
* aVariant
) {
95 gchar
* s
= g_variant_print(aVariant
, TRUE
);
103 static nsDependentCString
GVariantGetString(GVariant
* aVariant
) {
105 const gchar
* v
= g_variant_get_string(aVariant
, &len
);
106 return nsDependentCString(v
, len
);
109 static void settings_changed_signal_cb(GDBusProxy
* proxy
, gchar
* sender_name
,
110 gchar
* signal_name
, GVariant
* parameters
,
111 gpointer user_data
) {
112 LOGLNF("Settings Change sender=%s signal=%s params=%s\n", sender_name
,
113 signal_name
, GVariantToString(parameters
).get());
114 if (strcmp(signal_name
, "SettingChanged")) {
115 NS_WARNING("Unknown change signal for settings");
118 RefPtr
<GVariant
> ns
= dont_AddRef(g_variant_get_child_value(parameters
, 0));
119 RefPtr
<GVariant
> key
= dont_AddRef(g_variant_get_child_value(parameters
, 1));
120 // Third parameter is the value, but we don't care about it.
121 if (!ns
|| !key
|| !g_variant_is_of_type(ns
, G_VARIANT_TYPE_STRING
) ||
122 !g_variant_is_of_type(key
, G_VARIANT_TYPE_STRING
)) {
123 MOZ_ASSERT(false, "Unexpected setting change signal parameters");
127 auto* lnf
= static_cast<nsLookAndFeel
*>(user_data
);
129 auto nsStr
= GVariantGetString(ns
);
130 auto keyStr
= GVariantGetString(key
);
131 if (nsStr
.Equals("org.freedesktop.appearance"_ns
) &&
132 keyStr
.Equals("color-scheme"_ns
)) {
133 lnf
->OnColorSchemeSettingChanged();
137 void nsLookAndFeel::WatchDBus() {
138 GUniquePtr
<GError
> error
;
139 mDBusSettingsProxy
= dont_AddRef(g_dbus_proxy_new_for_bus_sync(
140 G_BUS_TYPE_SESSION
, G_DBUS_PROXY_FLAGS_NONE
, nullptr,
141 "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop",
142 "org.freedesktop.portal.Settings", nullptr, getter_Transfers(error
)));
143 if (mDBusSettingsProxy
) {
144 g_signal_connect(mDBusSettingsProxy
, "g-signal",
145 G_CALLBACK(settings_changed_signal_cb
), this);
147 LOGLNF("Can't create DBus proxy for settings: %s\n", error
->message
);
151 // DBus interface was started after L&F init so we need to load
152 // our settings from DBus explicitly.
153 if (!sIgnoreChangedSettings
) {
154 OnColorSchemeSettingChanged();
158 void nsLookAndFeel::UnwatchDBus() {
159 if (mDBusSettingsProxy
) {
160 g_signal_handlers_disconnect_by_func(
161 mDBusSettingsProxy
, FuncToGpointer(settings_changed_signal_cb
), this);
162 mDBusSettingsProxy
= nullptr;
166 nsLookAndFeel::nsLookAndFeel() {
167 static constexpr nsLiteralCString kObservedSettings
[] = {
168 // Affects system font sizes.
169 "notify::gtk-xft-dpi"_ns
,
170 // Affects mSystemTheme and mAltTheme as expected.
171 "notify::gtk-theme-name"_ns
,
173 "notify::gtk-font-name"_ns
,
174 // prefers-reduced-motion
175 "notify::gtk-enable-animations"_ns
,
176 // CSD media queries, etc.
177 "notify::gtk-decoration-layout"_ns
,
178 // Text resolution affects system font and widget sizes.
179 "notify::resolution"_ns
,
180 // These three Affect mCaretBlinkTime
181 "notify::gtk-cursor-blink"_ns
,
182 "notify::gtk-cursor-blink-time"_ns
,
183 "notify::gtk-cursor-blink-timeout"_ns
,
184 // Affects SelectTextfieldsOnKeyFocus
185 "notify::gtk-entry-select-on-focus"_ns
,
186 // Affects ScrollToClick
187 "notify::gtk-primary-button-warps-slider"_ns
,
188 // Affects SubmenuDelay
189 "notify::gtk-menu-popup-delay"_ns
,
190 // Affects DragThresholdX/Y
191 "notify::gtk-dnd-drag-threshold"_ns
,
194 GtkSettings
* settings
= gtk_settings_get_default();
195 for (const auto& setting
: kObservedSettings
) {
196 g_signal_connect_after(settings
, setting
.get(),
197 G_CALLBACK(settings_changed_cb
), nullptr);
201 nsWindow::GetSystemGtkWindowDecoration() != nsWindow::GTK_DECORATION_NONE
;
203 if (ShouldUsePortal(PortalKind::Settings
)) {
204 mDBusID
= g_bus_watch_name(
205 G_BUS_TYPE_SESSION
, "org.freedesktop.portal.Desktop",
206 G_BUS_NAME_WATCHER_FLAGS_AUTO_START
,
207 [](GDBusConnection
*, const gchar
*, const gchar
*,
208 gpointer data
) -> void {
209 auto* lnf
= static_cast<nsLookAndFeel
*>(data
);
212 [](GDBusConnection
*, const gchar
*, gpointer data
) -> void {
213 auto* lnf
= static_cast<nsLookAndFeel
*>(data
);
220 nsLookAndFeel::~nsLookAndFeel() {
221 ClearRoundedCornerProvider();
223 g_bus_unwatch_name(mDBusID
);
227 g_signal_handlers_disconnect_by_func(
228 gtk_settings_get_default(), FuncToGpointer(settings_changed_cb
), nullptr);
232 static void DumpStyleContext(GtkStyleContext
* aStyle
) {
233 static auto sGtkStyleContextToString
=
234 reinterpret_cast<char* (*)(GtkStyleContext
*, gint
)>(
235 dlsym(RTLD_DEFAULT
, "gtk_style_context_to_string"));
236 char* str
= sGtkStyleContextToString(aStyle
, ~0);
239 str
= gtk_widget_path_to_string(gtk_style_context_get_path(aStyle
));
245 // Modifies color |*aDest| as if a pattern of color |aSource| was painted with
246 // CAIRO_OPERATOR_OVER to a surface with color |*aDest|.
247 static void ApplyColorOver(const GdkRGBA
& aSource
, GdkRGBA
* aDest
) {
248 gdouble sourceCoef
= aSource
.alpha
;
249 gdouble destCoef
= aDest
->alpha
* (1.0 - sourceCoef
);
250 gdouble resultAlpha
= sourceCoef
+ destCoef
;
251 if (resultAlpha
!= 0.0) { // don't divide by zero
252 destCoef
/= resultAlpha
;
253 sourceCoef
/= resultAlpha
;
254 aDest
->red
= sourceCoef
* aSource
.red
+ destCoef
* aDest
->red
;
255 aDest
->green
= sourceCoef
* aSource
.green
+ destCoef
* aDest
->green
;
256 aDest
->blue
= sourceCoef
* aSource
.blue
+ destCoef
* aDest
->blue
;
257 aDest
->alpha
= resultAlpha
;
261 static void GetLightAndDarkness(const GdkRGBA
& aColor
, double* aLightness
,
263 double sum
= aColor
.red
+ aColor
.green
+ aColor
.blue
;
264 *aLightness
= sum
* aColor
.alpha
;
265 *aDarkness
= (3.0 - sum
) * aColor
.alpha
;
268 static bool GetGradientColors(const GValue
* aValue
, GdkRGBA
* aLightColor
,
269 GdkRGBA
* aDarkColor
) {
270 if (!G_TYPE_CHECK_VALUE_TYPE(aValue
, CAIRO_GOBJECT_TYPE_PATTERN
)) {
274 auto pattern
= static_cast<cairo_pattern_t
*>(g_value_get_boxed(aValue
));
279 // Just picking the lightest and darkest colors as simple samples rather
280 // than trying to blend, which could get messy if there are many stops.
281 if (CAIRO_STATUS_SUCCESS
!=
282 cairo_pattern_get_color_stop_rgba(pattern
, 0, nullptr, &aDarkColor
->red
,
283 &aDarkColor
->green
, &aDarkColor
->blue
,
284 &aDarkColor
->alpha
)) {
288 double maxLightness
, maxDarkness
;
289 GetLightAndDarkness(*aDarkColor
, &maxLightness
, &maxDarkness
);
290 *aLightColor
= *aDarkColor
;
294 CAIRO_STATUS_SUCCESS
==
295 cairo_pattern_get_color_stop_rgba(pattern
, index
, nullptr, &stop
.red
,
296 &stop
.green
, &stop
.blue
, &stop
.alpha
);
298 double lightness
, darkness
;
299 GetLightAndDarkness(stop
, &lightness
, &darkness
);
300 if (lightness
> maxLightness
) {
301 maxLightness
= lightness
;
304 if (darkness
> maxDarkness
) {
305 maxDarkness
= darkness
;
313 static bool GetColorFromImagePattern(const GValue
* aValue
, nscolor
* aColor
) {
314 if (!G_TYPE_CHECK_VALUE_TYPE(aValue
, CAIRO_GOBJECT_TYPE_PATTERN
)) {
318 auto* pattern
= static_cast<cairo_pattern_t
*>(g_value_get_boxed(aValue
));
323 cairo_surface_t
* surface
;
324 if (cairo_pattern_get_surface(pattern
, &surface
) != CAIRO_STATUS_SUCCESS
) {
328 cairo_format_t format
= cairo_image_surface_get_format(surface
);
329 if (format
== CAIRO_FORMAT_INVALID
) {
332 int width
= cairo_image_surface_get_width(surface
);
333 int height
= cairo_image_surface_get_height(surface
);
334 int stride
= cairo_image_surface_get_stride(surface
);
335 if (!width
|| !height
) {
339 // Guesstimate the central pixel would have a sensible color.
343 unsigned char* data
= cairo_image_surface_get_data(surface
);
345 // Most (all?) GTK images / patterns / etc use ARGB32.
346 case CAIRO_FORMAT_ARGB32
: {
347 size_t offset
= x
* 4 + y
* stride
;
348 uint32_t* pixel
= reinterpret_cast<uint32_t*>(data
+ offset
);
349 *aColor
= gfx::sRGBColor::UnusualFromARGB(*pixel
).ToABGR();
359 static bool GetUnicoBorderGradientColors(GtkStyleContext
* aContext
,
360 GdkRGBA
* aLightColor
,
361 GdkRGBA
* aDarkColor
) {
362 // Ubuntu 12.04 has GTK engine Unico-1.0.2, which overrides render_frame,
363 // providing its own border code. Ubuntu 14.04 has
364 // Unico-1.0.3+14.04.20140109, which does not override render_frame, and
365 // so does not need special attention. The earlier Unico can be detected
366 // by the -unico-border-gradient style property it registers.
367 // gtk_style_properties_lookup_property() is checked first to avoid the
368 // warning from gtk_style_context_get_property() when the property does
369 // not exist. (gtk_render_frame() of GTK+ 3.16 no longer uses the
371 const char* propertyName
= "-unico-border-gradient";
372 if (!gtk_style_properties_lookup_property(propertyName
, nullptr, nullptr))
375 // -unico-border-gradient is used only when the CSS node's engine is Unico.
376 GtkThemingEngine
* engine
;
377 GtkStateFlags state
= gtk_style_context_get_state(aContext
);
378 gtk_style_context_get(aContext
, state
, "engine", &engine
, nullptr);
379 if (strcmp(g_type_name(G_TYPE_FROM_INSTANCE(engine
)), "UnicoEngine") != 0)
382 // draw_border() of Unico engine uses -unico-border-gradient
383 // in preference to border-color.
384 GValue value
= G_VALUE_INIT
;
385 gtk_style_context_get_property(aContext
, propertyName
, state
, &value
);
387 bool result
= GetGradientColors(&value
, aLightColor
, aDarkColor
);
389 g_value_unset(&value
);
393 // Sets |aLightColor| and |aDarkColor| to colors from |aContext|. Returns
394 // true if |aContext| uses these colors to render a visible border.
395 // If returning false, then the colors returned are a fallback from the
396 // border-color value even though |aContext| does not use these colors to
398 static bool GetBorderColors(GtkStyleContext
* aContext
, GdkRGBA
* aLightColor
,
399 GdkRGBA
* aDarkColor
) {
400 // Determine whether the border on this style context is visible.
401 GtkStateFlags state
= gtk_style_context_get_state(aContext
);
402 GtkBorderStyle borderStyle
;
403 gtk_style_context_get(aContext
, state
, GTK_STYLE_PROPERTY_BORDER_STYLE
,
404 &borderStyle
, nullptr);
405 bool visible
= borderStyle
!= GTK_BORDER_STYLE_NONE
&&
406 borderStyle
!= GTK_BORDER_STYLE_HIDDEN
;
408 // GTK has an initial value of zero for border-widths, and so themes
409 // need to explicitly set border-widths to make borders visible.
411 gtk_style_context_get_border(aContext
, state
, &border
);
412 visible
= border
.top
!= 0 || border
.right
!= 0 || border
.bottom
!= 0 ||
417 GetUnicoBorderGradientColors(aContext
, aLightColor
, aDarkColor
))
420 // The initial value for the border-color is the foreground color, and so
421 // this will usually return a color distinct from the background even if
422 // there is no visible border detected.
423 gtk_style_context_get_border_color(aContext
, state
, aDarkColor
);
424 // TODO GTK3 - update aLightColor
425 // for GTK_BORDER_STYLE_INSET/OUTSET/GROVE/RIDGE border styles.
426 // https://bugzilla.mozilla.org/show_bug.cgi?id=978172#c25
427 *aLightColor
= *aDarkColor
;
431 static bool GetBorderColors(GtkStyleContext
* aContext
, nscolor
* aLightColor
,
432 nscolor
* aDarkColor
) {
433 GdkRGBA lightColor
, darkColor
;
434 bool ret
= GetBorderColors(aContext
, &lightColor
, &darkColor
);
435 *aLightColor
= GDK_RGBA_TO_NS_RGBA(lightColor
);
436 *aDarkColor
= GDK_RGBA_TO_NS_RGBA(darkColor
);
440 // Finds ideal cell highlight colors used for unfocused+selected cells distinct
441 // from both Highlight, used as focused+selected background, and the listbox
442 // background which is assumed to be similar to -moz-field
443 void nsLookAndFeel::PerThemeData::InitCellHighlightColors() {
444 int32_t minLuminosityDifference
= NS_SUFFICIENT_LUMINOSITY_DIFFERENCE_BG
;
445 int32_t backLuminosityDifference
=
446 NS_LUMINOSITY_DIFFERENCE(mWindow
.mBg
, mField
.mBg
);
447 if (backLuminosityDifference
>= minLuminosityDifference
) {
448 mCellHighlight
= mWindow
;
452 uint16_t hue
, sat
, luminance
;
454 mCellHighlight
= mField
;
456 NS_RGB2HSV(mCellHighlight
.mBg
, hue
, sat
, luminance
, alpha
);
459 // Lighten the color if the color is very dark
460 if (luminance
<= step
) {
463 // Darken it if it is very light
464 else if (luminance
>= 255 - step
) {
467 // Otherwise, compute what works best depending on the text luminance.
469 uint16_t textHue
, textSat
, textLuminance
;
471 NS_RGB2HSV(mCellHighlight
.mFg
, textHue
, textSat
, textLuminance
, textAlpha
);
472 // Text is darker than background, use a lighter shade
473 if (textLuminance
< luminance
) {
476 // Otherwise, use a darker shade
481 NS_HSV2RGB(mCellHighlight
.mBg
, hue
, sat
, luminance
, alpha
);
484 void nsLookAndFeel::NativeInit() { EnsureInit(); }
486 void nsLookAndFeel::RefreshImpl() {
487 mInitialized
= false;
490 nsXPLookAndFeel::RefreshImpl();
493 nsresult
nsLookAndFeel::NativeGetColor(ColorID aID
, ColorScheme aScheme
,
498 aScheme
== ColorScheme::Light
? LightTheme() : DarkTheme();
499 return theme
.GetColor(aID
, aColor
);
502 static bool ShouldUseColorForActiveDarkScrollbarThumb(nscolor aColor
) {
503 auto IsDifferentEnough
= [](int32_t aChannel
, int32_t aOtherChannel
) {
504 return std::abs(aChannel
- aOtherChannel
) > 10;
506 return IsDifferentEnough(NS_GET_R(aColor
), NS_GET_G(aColor
)) ||
507 IsDifferentEnough(NS_GET_R(aColor
), NS_GET_B(aColor
));
510 static bool ShouldUseThemedScrollbarColor(StyleSystemColor aID
, nscolor aColor
,
515 if (StaticPrefs::widget_non_native_theme_scrollbar_dark_themed()) {
518 return aID
== StyleSystemColor::ThemedScrollbarThumbActive
&&
519 StaticPrefs::widget_non_native_theme_scrollbar_active_always_themed();
522 nsresult
nsLookAndFeel::PerThemeData::GetColor(ColorID aID
,
523 nscolor
& aColor
) const {
524 nsresult res
= NS_OK
;
527 // These colors don't seem to be used for anything anymore in Mozilla
528 // The CSS2 colors below are used.
529 case ColorID::Appworkspace
: // MDI background color
530 case ColorID::Background
: // desktop background
531 case ColorID::Window
:
532 case ColorID::Windowframe
:
533 case ColorID::MozDialog
:
534 case ColorID::MozCombobox
:
535 aColor
= mWindow
.mBg
;
537 case ColorID::Windowtext
:
538 case ColorID::MozDialogtext
:
539 aColor
= mWindow
.mFg
;
541 case ColorID::IMESelectedRawTextBackground
:
542 case ColorID::IMESelectedConvertedTextBackground
:
543 case ColorID::Highlight
: // preference selected item,
544 aColor
= mSelectedText
.mBg
;
546 case ColorID::Highlighttext
:
547 if (NS_GET_A(mSelectedText
.mBg
) < 155) {
548 aColor
= NS_SAME_AS_FOREGROUND_COLOR
;
552 case ColorID::IMESelectedRawTextForeground
:
553 case ColorID::IMESelectedConvertedTextForeground
:
554 aColor
= mSelectedText
.mFg
;
556 case ColorID::Selecteditem
:
557 aColor
= mSelectedItem
.mBg
;
559 case ColorID::Selecteditemtext
:
560 aColor
= mSelectedItem
.mFg
;
562 case ColorID::Accentcolor
:
563 aColor
= mAccent
.mBg
;
565 case ColorID::Accentcolortext
:
566 aColor
= mAccent
.mFg
;
568 case ColorID::MozCellhighlight
:
569 aColor
= mCellHighlight
.mBg
;
571 case ColorID::MozCellhighlighttext
:
572 aColor
= mCellHighlight
.mFg
;
574 case ColorID::IMERawInputBackground
:
575 case ColorID::IMEConvertedTextBackground
:
576 aColor
= NS_TRANSPARENT
;
578 case ColorID::IMERawInputForeground
:
579 case ColorID::IMEConvertedTextForeground
:
580 aColor
= NS_SAME_AS_FOREGROUND_COLOR
;
582 case ColorID::IMERawInputUnderline
:
583 case ColorID::IMEConvertedTextUnderline
:
584 aColor
= NS_SAME_AS_FOREGROUND_COLOR
;
586 case ColorID::IMESelectedRawTextUnderline
:
587 case ColorID::IMESelectedConvertedTextUnderline
:
588 aColor
= NS_TRANSPARENT
;
590 case ColorID::Scrollbar
:
591 aColor
= mThemedScrollbar
;
593 case ColorID::ThemedScrollbar
:
594 aColor
= mThemedScrollbar
;
595 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
596 return NS_ERROR_FAILURE
;
599 case ColorID::ThemedScrollbarInactive
:
600 aColor
= mThemedScrollbarInactive
;
601 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
602 return NS_ERROR_FAILURE
;
605 case ColorID::ThemedScrollbarThumb
:
606 aColor
= mThemedScrollbarThumb
;
607 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
608 return NS_ERROR_FAILURE
;
611 case ColorID::ThemedScrollbarThumbHover
:
612 aColor
= mThemedScrollbarThumbHover
;
613 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
614 return NS_ERROR_FAILURE
;
617 case ColorID::ThemedScrollbarThumbActive
:
618 aColor
= mThemedScrollbarThumbActive
;
619 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
620 return NS_ERROR_FAILURE
;
623 case ColorID::ThemedScrollbarThumbInactive
:
624 aColor
= mThemedScrollbarThumbInactive
;
625 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
626 return NS_ERROR_FAILURE
;
630 // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
631 case ColorID::Activeborder
:
632 // active window border
633 aColor
= mMozWindowActiveBorder
;
635 case ColorID::Inactiveborder
:
636 // inactive window border
637 aColor
= mMozWindowInactiveBorder
;
639 case ColorID::Graytext
: // disabled text in windows, menus, etc.
642 case ColorID::Activecaption
:
643 aColor
= mTitlebar
.mBg
;
645 case ColorID::Captiontext
: // text in active window caption (titlebar)
646 aColor
= mTitlebar
.mFg
;
648 case ColorID::Inactivecaption
:
649 // inactive window caption
650 aColor
= mTitlebarInactive
.mBg
;
652 case ColorID::Inactivecaptiontext
:
653 aColor
= mTitlebarInactive
.mFg
;
655 case ColorID::Infobackground
:
658 case ColorID::Infotext
:
664 case ColorID::Menutext
:
667 case ColorID::MozHeaderbar
:
668 aColor
= mHeaderBar
.mBg
;
670 case ColorID::MozHeaderbartext
:
671 aColor
= mHeaderBar
.mFg
;
673 case ColorID::MozHeaderbarinactive
:
674 aColor
= mHeaderBarInactive
.mBg
;
676 case ColorID::MozHeaderbarinactivetext
:
677 aColor
= mHeaderBarInactive
.mFg
;
679 case ColorID::Threedface
:
680 case ColorID::Buttonface
:
681 case ColorID::MozButtondisabledface
:
683 aColor
= mWindow
.mBg
;
686 case ColorID::Buttontext
:
687 // text on push buttons
688 aColor
= mButton
.mFg
;
691 case ColorID::Buttonhighlight
:
692 // 3-D highlighted edge color
693 case ColorID::Threedhighlight
:
694 // 3-D highlighted outer edge color
695 aColor
= mFrameOuterLightBorder
;
698 case ColorID::Buttonshadow
:
699 // 3-D shadow edge color
700 case ColorID::Threedshadow
:
701 // 3-D shadow inner edge color
702 aColor
= mFrameInnerDarkBorder
;
705 case ColorID::Threedlightshadow
:
706 case ColorID::Buttonborder
:
707 case ColorID::MozDisabledfield
:
708 aColor
= mIsDark
? *GenericDarkColor(aID
) : NS_RGB(0xE0, 0xE0, 0xE0);
710 case ColorID::Threeddarkshadow
:
711 aColor
= mIsDark
? *GenericDarkColor(aID
) : NS_RGB(0xDC, 0xDC, 0xDC);
714 case ColorID::MozEventreerow
:
718 case ColorID::Fieldtext
:
721 case ColorID::MozButtonhoverface
:
722 case ColorID::MozButtonactiveface
:
723 aColor
= mButtonHover
.mBg
;
725 case ColorID::MozButtonhovertext
:
726 aColor
= mButtonHover
.mFg
;
728 case ColorID::MozButtonactivetext
:
729 aColor
= mButtonActiveText
;
731 case ColorID::MozMenuhover
:
732 aColor
= mMenuHover
.mBg
;
734 case ColorID::MozMenuhovertext
:
735 aColor
= mMenuHover
.mFg
;
737 case ColorID::MozMenuhoverdisabled
:
738 aColor
= NS_TRANSPARENT
;
740 case ColorID::MozOddtreerow
:
741 aColor
= mOddCellBackground
;
743 case ColorID::MozNativehyperlinktext
:
744 aColor
= mNativeHyperLinkText
;
746 case ColorID::MozNativevisitedhyperlinktext
:
747 aColor
= mNativeVisitedHyperLinkText
;
749 case ColorID::MozComboboxtext
:
750 aColor
= mComboBoxText
;
752 case ColorID::MozColheadertext
:
753 aColor
= mMozColHeaderText
;
755 case ColorID::MozColheaderhovertext
:
756 aColor
= mMozColHeaderHoverText
;
758 case ColorID::SpellCheckerUnderline
:
760 case ColorID::Marktext
:
761 aColor
= GetStandinForNativeColor(
762 aID
, mIsDark
? ColorScheme::Dark
: ColorScheme::Light
);
765 /* default color is BLACK */
767 res
= NS_ERROR_FAILURE
;
774 static int32_t CheckWidgetStyle(GtkWidget
* aWidget
, const char* aStyle
,
776 gboolean value
= FALSE
;
777 gtk_widget_style_get(aWidget
, aStyle
, &value
, nullptr);
778 return value
? aResult
: 0;
781 static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle(
782 GtkWidget
* aWidget
) {
783 if (!aWidget
) return mozilla::LookAndFeel::eScrollArrowStyle_Single
;
785 return CheckWidgetStyle(aWidget
, "has-backward-stepper",
786 mozilla::LookAndFeel::eScrollArrow_StartBackward
) |
787 CheckWidgetStyle(aWidget
, "has-forward-stepper",
788 mozilla::LookAndFeel::eScrollArrow_EndForward
) |
789 CheckWidgetStyle(aWidget
, "has-secondary-backward-stepper",
790 mozilla::LookAndFeel::eScrollArrow_EndBackward
) |
791 CheckWidgetStyle(aWidget
, "has-secondary-forward-stepper",
792 mozilla::LookAndFeel::eScrollArrow_StartForward
);
795 nsresult
nsLookAndFeel::NativeGetInt(IntID aID
, int32_t& aResult
) {
796 nsresult res
= NS_OK
;
798 // We use delayed initialization by EnsureInit() here
799 // to make sure mozilla::Preferences is available (Bug 115807).
800 // IntID::UseAccessibilityTheme is requested before user preferences
801 // are read, and so EnsureInit(), which depends on preference values,
802 // is deliberately delayed until required.
804 case IntID::ScrollButtonLeftMouseButtonAction
:
807 case IntID::ScrollButtonMiddleMouseButtonAction
:
810 case IntID::ScrollButtonRightMouseButtonAction
:
813 case IntID::CaretBlinkTime
:
815 aResult
= mCaretBlinkTime
;
817 case IntID::CaretBlinkCount
:
819 aResult
= mCaretBlinkCount
;
821 case IntID::CaretWidth
:
824 case IntID::ShowCaretDuringSelection
:
827 case IntID::SelectTextfieldsOnKeyFocus
: {
828 GtkSettings
* settings
;
829 gboolean select_on_focus
;
831 settings
= gtk_settings_get_default();
832 g_object_get(settings
, "gtk-entry-select-on-focus", &select_on_focus
,
841 case IntID::ScrollToClick
: {
842 GtkSettings
* settings
;
843 gboolean warps_slider
= FALSE
;
845 settings
= gtk_settings_get_default();
846 if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings
),
847 "gtk-primary-button-warps-slider")) {
848 g_object_get(settings
, "gtk-primary-button-warps-slider", &warps_slider
,
857 case IntID::SubmenuDelay
: {
858 GtkSettings
* settings
;
861 settings
= gtk_settings_get_default();
862 g_object_get(settings
, "gtk-menu-popup-delay", &delay
, nullptr);
863 aResult
= (int32_t)delay
;
866 case IntID::TooltipDelay
: {
870 case IntID::MenusCanOverlapOSBar
:
871 // we want XUL popups to be able to overlap the task bar.
874 case IntID::SkipNavigatingDisabledMenuItem
:
877 case IntID::DragThresholdX
:
878 case IntID::DragThresholdY
: {
880 g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold",
881 &threshold
, nullptr);
885 case IntID::ScrollArrowStyle
: {
886 GtkWidget
* scrollbar
= GetWidget(MOZ_GTK_SCROLLBAR_VERTICAL
);
887 aResult
= ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar
);
890 case IntID::TreeOpenDelay
:
893 case IntID::TreeCloseDelay
:
896 case IntID::TreeLazyScrollDelay
:
899 case IntID::TreeScrollDelay
:
902 case IntID::TreeScrollLinesMax
:
905 case IntID::AlertNotificationOrigin
:
906 aResult
= NS_ALERT_TOP
;
908 case IntID::IMERawInputUnderlineStyle
:
909 case IntID::IMEConvertedTextUnderlineStyle
:
910 aResult
= static_cast<int32_t>(StyleTextDecorationStyle::Solid
);
912 case IntID::IMESelectedRawTextUnderlineStyle
:
913 case IntID::IMESelectedConvertedTextUnderline
:
914 aResult
= static_cast<int32_t>(StyleTextDecorationStyle::None
);
916 case IntID::SpellCheckerUnderlineStyle
:
917 aResult
= static_cast<int32_t>(StyleTextDecorationStyle::Wavy
);
919 case IntID::MenuBarDrag
:
921 aResult
= mSystemTheme
.mMenuSupportsDrag
;
923 case IntID::ScrollbarButtonAutoRepeatBehavior
:
926 case IntID::SwipeAnimationEnabled
:
929 case IntID::ContextMenuOffsetVertical
:
930 case IntID::ContextMenuOffsetHorizontal
:
933 case IntID::GTKCSDAvailable
:
934 aResult
= sCSDAvailable
;
936 case IntID::GTKCSDMaximizeButton
:
938 aResult
= mCSDMaximizeButton
;
940 case IntID::GTKCSDMinimizeButton
:
942 aResult
= mCSDMinimizeButton
;
944 case IntID::GTKCSDCloseButton
:
946 aResult
= mCSDCloseButton
;
948 case IntID::GTKCSDReversedPlacement
:
950 aResult
= mCSDReversedPlacement
;
952 case IntID::PrefersReducedMotion
: {
954 aResult
= mPrefersReducedMotion
;
957 case IntID::SystemUsesDarkTheme
: {
959 if (mColorSchemePreference
) {
960 aResult
= *mColorSchemePreference
== ColorScheme::Dark
;
962 aResult
= mSystemTheme
.mIsDark
;
966 case IntID::GTKCSDMaximizeButtonPosition
:
967 aResult
= mCSDMaximizeButtonPosition
;
969 case IntID::GTKCSDMinimizeButtonPosition
:
970 aResult
= mCSDMinimizeButtonPosition
;
972 case IntID::GTKCSDCloseButtonPosition
:
973 aResult
= mCSDCloseButtonPosition
;
975 case IntID::UseAccessibilityTheme
:
976 // If high contrast is enabled, enable prefers-reduced-transparency media
977 // query as well as there is no dedicated option.
978 case IntID::PrefersReducedTransparency
:
980 aResult
= mSystemTheme
.mHighContrast
;
982 case IntID::InvertedColors
:
983 // No GTK API for checking if inverted colors is enabled
986 case IntID::TitlebarRadius
: {
988 aResult
= EffectiveTheme().mTitlebarRadius
;
991 case IntID::AllowOverlayScrollbarsOverlap
: {
995 case IntID::ScrollbarFadeBeginDelay
: {
999 case IntID::ScrollbarFadeDuration
: {
1003 case IntID::ScrollbarDisplayOnMouseMove
: {
1007 case IntID::PanelAnimations
:
1008 aResult
= [&]() -> bool {
1009 if (!sCSDAvailable
) {
1010 // Disabled on systems without CSD, see bug 1385079.
1013 if (GdkIsWaylandDisplay()) {
1014 // Disabled on wayland, see bug 1800442 and bug 1800368.
1020 case IntID::UseOverlayScrollbars
: {
1021 aResult
= StaticPrefs::widget_gtk_overlay_scrollbars_enabled();
1024 case IntID::HideCursorWhileTyping
: {
1025 aResult
= StaticPrefs::widget_gtk_hide_pointer_while_typing_enabled();
1028 case IntID::TouchDeviceSupportPresent
:
1029 aResult
= widget::WidgetUtilsGTK::IsTouchDeviceSupportPresent();
1033 res
= NS_ERROR_FAILURE
;
1039 nsresult
nsLookAndFeel::NativeGetFloat(FloatID aID
, float& aResult
) {
1040 nsresult rv
= NS_OK
;
1042 case FloatID::IMEUnderlineRelativeSize
:
1045 case FloatID::SpellCheckerUnderlineRelativeSize
:
1048 case FloatID::CaretAspectRatio
:
1050 aResult
= mSystemTheme
.mCaretRatio
;
1052 case FloatID::TextScaleFactor
:
1053 aResult
= gfxPlatformGtk::GetFontScaleFactor();
1057 rv
= NS_ERROR_FAILURE
;
1062 static void GetSystemFontInfo(GtkStyleContext
* aStyle
, nsString
* aFontName
,
1063 gfxFontStyle
* aFontStyle
) {
1064 aFontStyle
->style
= FontSlantStyle::NORMAL
;
1067 // https://git.gnome.org/browse/gtk+/tree/gtk/gtkwidget.c?h=3.22.19#n10333
1068 PangoFontDescription
* desc
;
1069 gtk_style_context_get(aStyle
, gtk_style_context_get_state(aStyle
), "font",
1072 aFontStyle
->systemFont
= true;
1074 constexpr auto quote
= u
"\""_ns
;
1075 NS_ConvertUTF8toUTF16
family(pango_font_description_get_family(desc
));
1076 *aFontName
= quote
+ family
+ quote
;
1078 aFontStyle
->weight
=
1079 FontWeight::FromInt(pango_font_description_get_weight(desc
));
1081 // FIXME: Set aFontStyle->stretch correctly!
1082 aFontStyle
->stretch
= FontStretch::NORMAL
;
1084 float size
= float(pango_font_description_get_size(desc
)) / PANGO_SCALE
;
1086 // |size| is now either pixels or pango-points (not Mozilla-points!)
1088 if (!pango_font_description_get_size_is_absolute(desc
)) {
1089 // |size| is in pango-points, so convert to pixels.
1090 size
*= float(gfxPlatformGtk::GetFontScaleDPI()) / POINTS_PER_INCH_FLOAT
;
1093 // |size| is now pixels but not scaled for the hidpi displays,
1094 aFontStyle
->size
= size
;
1096 pango_font_description_free(desc
);
1099 bool nsLookAndFeel::NativeGetFont(FontID aID
, nsString
& aFontName
,
1100 gfxFontStyle
& aFontStyle
) {
1101 return mSystemTheme
.GetFont(aID
, aFontName
, aFontStyle
);
1104 bool nsLookAndFeel::PerThemeData::GetFont(FontID aID
, nsString
& aFontName
,
1105 gfxFontStyle
& aFontStyle
) const {
1107 case FontID::Menu
: // css2
1108 case FontID::MozPullDownMenu
: // css3
1109 aFontName
= mMenuFontName
;
1110 aFontStyle
= mMenuFontStyle
;
1113 case FontID::MozField
: // css3
1114 case FontID::MozList
: // css3
1115 aFontName
= mFieldFontName
;
1116 aFontStyle
= mFieldFontStyle
;
1119 case FontID::MozButton
: // css3
1120 aFontName
= mButtonFontName
;
1121 aFontStyle
= mButtonFontStyle
;
1124 case FontID::Caption
: // css2
1125 case FontID::Icon
: // css2
1126 case FontID::MessageBox
: // css2
1127 case FontID::SmallCaption
: // css2
1128 case FontID::StatusBar
: // css2
1130 aFontName
= mDefaultFontName
;
1131 aFontStyle
= mDefaultFontStyle
;
1135 // Convert GDK pixels to CSS pixels.
1136 // When "layout.css.devPixelsPerPx" > 0, this is not a direct conversion.
1137 // The difference produces a scaling of system fonts in proportion with
1138 // other scaling from the change in CSS pixel sizes.
1139 aFontStyle
.size
/= LookAndFeel::GetTextScaleFactor();
1143 // Check color contrast according to
1144 // https://www.w3.org/TR/AERT/#color-contrast
1145 static bool HasGoodContrastVisibility(GdkRGBA
& aColor1
, GdkRGBA
& aColor2
) {
1146 int32_t luminosityDifference
= NS_LUMINOSITY_DIFFERENCE(
1147 GDK_RGBA_TO_NS_RGBA(aColor1
), GDK_RGBA_TO_NS_RGBA(aColor2
));
1148 if (luminosityDifference
< NS_SUFFICIENT_LUMINOSITY_DIFFERENCE
) {
1152 double colorDifference
= std::abs(aColor1
.red
- aColor2
.red
) +
1153 std::abs(aColor1
.green
- aColor2
.green
) +
1154 std::abs(aColor1
.blue
- aColor2
.blue
);
1155 return (colorDifference
* 255.0 > 500.0);
1158 // Check if the foreground/background colors match with default white/black
1159 // html page colors.
1160 static bool IsGtkThemeCompatibleWithHTMLColors() {
1161 GdkRGBA white
= {1.0, 1.0, 1.0};
1162 GdkRGBA black
= {0.0, 0.0, 0.0};
1164 GtkStyleContext
* style
= GetStyleContext(MOZ_GTK_WINDOW
);
1167 gtk_style_context_get_color(style
, GTK_STATE_FLAG_NORMAL
, &textColor
);
1169 // Theme text color and default white html page background
1170 if (!HasGoodContrastVisibility(textColor
, white
)) {
1174 GdkRGBA backgroundColor
;
1175 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
,
1178 // Theme background color and default white html page background
1179 if (HasGoodContrastVisibility(backgroundColor
, white
)) {
1183 // Theme background color and default black text color
1184 return HasGoodContrastVisibility(backgroundColor
, black
);
1187 static nsCString
GetGtkSettingsStringKey(const char* aKey
) {
1188 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
1190 GtkSettings
* settings
= gtk_settings_get_default();
1191 char* value
= nullptr;
1192 g_object_get(settings
, aKey
, &value
, nullptr);
1200 static nsCString
GetGtkTheme() {
1201 return GetGtkSettingsStringKey("gtk-theme-name");
1204 static bool GetPreferDarkTheme() {
1205 GtkSettings
* settings
= gtk_settings_get_default();
1206 gboolean preferDarkTheme
= FALSE
;
1207 g_object_get(settings
, "gtk-application-prefer-dark-theme", &preferDarkTheme
,
1209 return preferDarkTheme
== TRUE
;
1212 // It seems GTK doesn't have an API to query if the current theme is "light" or
1213 // "dark", so we synthesize it from the CSS2 Window/WindowText colors instead,
1214 // by comparing their luminosity.
1215 static bool GetThemeIsDark() {
1217 GtkStyleContext
* style
= GetStyleContext(MOZ_GTK_WINDOW
);
1218 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &bg
);
1219 gtk_style_context_get_color(style
, GTK_STATE_FLAG_NORMAL
, &fg
);
1220 return RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(bg
)) <
1221 RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(fg
));
1224 void nsLookAndFeel::RestoreSystemTheme() {
1225 LOGLNF("RestoreSystemTheme(%s, %d, %d)\n", mSystemTheme
.mName
.get(),
1226 mSystemTheme
.mPreferDarkTheme
, mSystemThemeOverridden
);
1228 if (!mSystemThemeOverridden
) {
1232 // Available on Gtk 3.20+.
1233 static auto sGtkSettingsResetProperty
=
1234 (void (*)(GtkSettings
*, const gchar
*))dlsym(
1235 RTLD_DEFAULT
, "gtk_settings_reset_property");
1237 GtkSettings
* settings
= gtk_settings_get_default();
1238 if (sGtkSettingsResetProperty
) {
1239 sGtkSettingsResetProperty(settings
, "gtk-theme-name");
1240 sGtkSettingsResetProperty(settings
, "gtk-application-prefer-dark-theme");
1242 g_object_set(settings
, "gtk-theme-name", mSystemTheme
.mName
.get(),
1243 "gtk-application-prefer-dark-theme",
1244 mSystemTheme
.mPreferDarkTheme
, nullptr);
1246 mSystemThemeOverridden
= false;
1247 UpdateRoundedBottomCornerStyles();
1251 static bool AnyColorChannelIsDifferent(nscolor aColor
) {
1252 return NS_GET_R(aColor
) != NS_GET_G(aColor
) ||
1253 NS_GET_R(aColor
) != NS_GET_B(aColor
);
1256 bool nsLookAndFeel::ConfigureAltTheme() {
1257 GtkSettings
* settings
= gtk_settings_get_default();
1258 // Toggling gtk-application-prefer-dark-theme is not enough generally to
1259 // switch from dark to light theme. If the theme didn't change, and we have
1260 // a dark theme, try to first remove -Dark{,er,est} from the theme name to
1261 // find the light variant.
1262 if (mSystemTheme
.mIsDark
) {
1263 nsCString potentialLightThemeName
= mSystemTheme
.mName
;
1265 constexpr nsLiteralCString kSubstringsToRemove
[] = {
1266 "-darkest"_ns
, "-darker"_ns
, "-dark"_ns
,
1267 "-Darkest"_ns
, "-Darker"_ns
, "-Dark"_ns
,
1268 "_darkest"_ns
, "_darker"_ns
, "_dark"_ns
,
1269 "_Darkest"_ns
, "_Darker"_ns
, "_Dark"_ns
,
1273 for (const auto& s
: kSubstringsToRemove
) {
1274 potentialLightThemeName
= mSystemTheme
.mName
;
1275 potentialLightThemeName
.ReplaceSubstring(s
, ""_ns
);
1276 if (potentialLightThemeName
.Length() != mSystemTheme
.mName
.Length()) {
1282 LOGLNF(" found potential light variant of %s: %s",
1283 mSystemTheme
.mName
.get(), potentialLightThemeName
.get());
1284 g_object_set(settings
, "gtk-theme-name", potentialLightThemeName
.get(),
1285 "gtk-application-prefer-dark-theme", !mSystemTheme
.mIsDark
,
1289 if (!GetThemeIsDark()) {
1290 return true; // Success!
1295 LOGLNF(" toggling gtk-application-prefer-dark-theme");
1296 g_object_set(settings
, "gtk-application-prefer-dark-theme",
1297 !mSystemTheme
.mIsDark
, nullptr);
1299 if (mSystemTheme
.mIsDark
!= GetThemeIsDark()) {
1300 return true; // Success!
1303 LOGLNF(" didn't work, falling back to default theme");
1304 // If the theme still didn't change enough, fall back to Adwaita with the
1305 // appropriate color preference.
1306 g_object_set(settings
, "gtk-theme-name", "Adwaita",
1307 "gtk-application-prefer-dark-theme", !mSystemTheme
.mIsDark
,
1311 // If it _still_ didn't change enough, and we're looking for a dark theme,
1312 // try to set Adwaita-dark as a theme name. This might be needed in older GTK
1314 if (!mSystemTheme
.mIsDark
&& !GetThemeIsDark()) {
1315 LOGLNF(" last resort Adwaita-dark fallback");
1316 g_object_set(settings
, "gtk-theme-name", "Adwaita-dark", nullptr);
1323 void nsLookAndFeel::ConfigureAndInitializeAltTheme() {
1324 const bool fellBackToDefaultTheme
= !ConfigureAltTheme();
1328 // Some of the alt theme colors we can grab from the system theme, if we fell
1329 // back to the default light / dark themes.
1330 if (fellBackToDefaultTheme
) {
1331 if (StaticPrefs::widget_gtk_alt_theme_selection()) {
1332 mAltTheme
.mSelectedText
= mSystemTheme
.mSelectedText
;
1335 if (StaticPrefs::widget_gtk_alt_theme_scrollbar_active() &&
1336 (!mAltTheme
.mIsDark
|| ShouldUseColorForActiveDarkScrollbarThumb(
1337 mSystemTheme
.mThemedScrollbarThumbActive
))) {
1338 mAltTheme
.mThemedScrollbarThumbActive
=
1339 mSystemTheme
.mThemedScrollbarThumbActive
;
1342 if (StaticPrefs::widget_gtk_alt_theme_accent()) {
1343 mAltTheme
.mAccent
= mSystemTheme
.mAccent
;
1347 // Special case for Adwaita: In GTK3 we don't have more proper accent colors,
1348 // so we use the selected background colors. Those colors, however, don't have
1349 // much contrast in dark mode (see bug 1741293). We know, however, that GTK4
1350 // uses the light accent color for the dark theme, see:
1352 // https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/named-colors.html#accent-colors
1354 // So we manually do that.
1355 if (mSystemTheme
.mFamily
== ThemeFamily::Adwaita
) {
1356 auto& dark
= mSystemTheme
.mIsDark
? mSystemTheme
: mAltTheme
;
1357 auto& light
= mSystemTheme
.mIsDark
? mAltTheme
: mSystemTheme
;
1358 dark
.mAccent
= light
.mAccent
;
1361 // Right now we're using the opposite color-scheme theme, make sure to record
1363 mSystemThemeOverridden
= true;
1364 UpdateRoundedBottomCornerStyles();
1367 void nsLookAndFeel::ClearRoundedCornerProvider() {
1368 if (mRoundedCornerProvider
) {
1369 gtk_style_context_remove_provider_for_screen(
1370 gdk_screen_get_default(),
1371 GTK_STYLE_PROVIDER(mRoundedCornerProvider
.get()));
1372 mRoundedCornerProvider
= nullptr;
1376 void nsLookAndFeel::UpdateRoundedBottomCornerStyles() {
1377 ClearRoundedCornerProvider();
1378 if (!StaticPrefs::widget_gtk_rounded_bottom_corners_enabled()) {
1381 int32_t radius
= EffectiveTheme().mTitlebarRadius
;
1385 mRoundedCornerProvider
= dont_AddRef(gtk_css_provider_new());
1386 nsPrintfCString
string(
1387 "window.csd decoration {"
1388 "border-bottom-right-radius: %dpx;"
1389 "border-bottom-left-radius: %dpx;"
1392 GUniquePtr
<GError
> error
;
1393 if (!gtk_css_provider_load_from_data(mRoundedCornerProvider
.get(),
1394 string
.get(), string
.Length(),
1395 getter_Transfers(error
))) {
1396 NS_WARNING(nsPrintfCString("Failed to load provider: %s - %s\n",
1397 string
.get(), error
? error
->message
: nullptr)
1400 gtk_style_context_add_provider_for_screen(
1401 gdk_screen_get_default(),
1402 GTK_STYLE_PROVIDER(mRoundedCornerProvider
.get()),
1403 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
1406 Maybe
<ColorScheme
> nsLookAndFeel::ComputeColorSchemeSetting() {
1408 // Check the pref explicitly here. Usually this shouldn't be needed, but
1409 // since we can only load one GTK theme at a time, and the pref will
1410 // override the effective value that the rest of gecko assumes for the
1411 // "system" color scheme, we need to factor it in our GTK theme decisions.
1413 if (NS_SUCCEEDED(Preferences::GetInt("ui.systemUsesDarkTheme", &pref
))) {
1414 return Some(pref
? ColorScheme::Dark
: ColorScheme::Light
);
1418 if (!mDBusSettingsProxy
) {
1421 GUniquePtr
<GError
> error
;
1422 RefPtr
<GVariant
> variant
= dont_AddRef(g_dbus_proxy_call_sync(
1423 mDBusSettingsProxy
, "Read",
1424 g_variant_new("(ss)", "org.freedesktop.appearance", "color-scheme"),
1425 G_DBUS_CALL_FLAGS_NONE
,
1426 StaticPrefs::widget_gtk_settings_portal_timeout_ms(), nullptr,
1427 getter_Transfers(error
)));
1429 LOGLNF("color-scheme query error: %s\n", error
->message
);
1432 LOGLNF("color-scheme query result: %s\n", GVariantToString(variant
).get());
1433 variant
= dont_AddRef(g_variant_get_child_value(variant
, 0));
1434 while (variant
&& g_variant_is_of_type(variant
, G_VARIANT_TYPE_VARIANT
)) {
1435 // Unbox the return value.
1436 variant
= dont_AddRef(g_variant_get_variant(variant
));
1438 if (!variant
|| !g_variant_is_of_type(variant
, G_VARIANT_TYPE_UINT32
)) {
1439 MOZ_ASSERT(false, "Unexpected color-scheme query return value");
1442 switch (g_variant_get_uint32(variant
)) {
1444 MOZ_FALLTHROUGH_ASSERT("Unexpected color-scheme query return value");
1448 return Some(ColorScheme::Dark
);
1450 return Some(ColorScheme::Light
);
1455 void nsLookAndFeel::Initialize() {
1456 LOGLNF("nsLookAndFeel::Initialize");
1457 MOZ_DIAGNOSTIC_ASSERT(!mInitialized
);
1458 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
1459 "LookAndFeel init should be done on the main thread");
1461 mInitialized
= true;
1463 GtkSettings
* settings
= gtk_settings_get_default();
1464 if (MOZ_UNLIKELY(!settings
)) {
1465 NS_WARNING("EnsureInit: No settings");
1469 AutoRestore
<bool> restoreIgnoreSettings(sIgnoreChangedSettings
);
1470 sIgnoreChangedSettings
= true;
1472 // Our current theme may be different from the system theme if we're matching
1473 // the Firefox theme or using the alt theme intentionally due to the
1474 // color-scheme preference. Make sure to restore the original system theme.
1475 RestoreSystemTheme();
1477 // First initialize global settings.
1478 InitializeGlobalSettings();
1480 // Record our system theme settings now.
1481 mSystemTheme
.Init();
1483 // Find the alternative-scheme theme (light if the system theme is dark, or
1484 // vice versa), configure it and initialize it.
1485 ConfigureAndInitializeAltTheme();
1487 LOGLNF("System Theme: %s. Alt Theme: %s\n", mSystemTheme
.mName
.get(),
1488 mAltTheme
.mName
.get());
1490 // Go back to the system theme or keep the alt theme configured, depending on
1491 // Firefox theme or user color-scheme preference.
1492 ConfigureFinalEffectiveTheme();
1497 void nsLookAndFeel::OnColorSchemeSettingChanged() {
1498 if (NS_WARN_IF(mColorSchemePreference
== ComputeColorSchemeSetting())) {
1499 // We sometimes get duplicate color-scheme changes from dbus, avoid doing
1500 // extra work if not needed.
1506 void nsLookAndFeel::InitializeGlobalSettings() {
1507 GtkSettings
* settings
= gtk_settings_get_default();
1509 mColorSchemePreference
= ComputeColorSchemeSetting();
1511 gboolean enableAnimations
= false;
1512 g_object_get(settings
, "gtk-enable-animations", &enableAnimations
, nullptr);
1513 mPrefersReducedMotion
= !enableAnimations
;
1515 gint blink_time
= 0; // In milliseconds
1516 gint blink_timeout
= 0; // in seconds
1518 g_object_get(settings
, "gtk-cursor-blink-time", &blink_time
,
1519 "gtk-cursor-blink-timeout", &blink_timeout
, "gtk-cursor-blink",
1522 // https://docs.gtk.org/gtk3/property.Settings.gtk-cursor-blink-timeout.html:
1524 // Setting this to zero has the same effect as setting
1525 // GtkSettings:gtk-cursor-blink to FALSE.
1527 mCaretBlinkTime
= blink
&& blink_timeout
? (int32_t)blink_time
: 0;
1529 if (mCaretBlinkTime
) {
1530 // blink_time * 2 because blink count is a full blink cycle.
1532 std::max(1, int32_t(std::ceil(float(blink_timeout
* 1000) /
1533 (float(blink_time
) * 2.0f
))));
1535 mCaretBlinkCount
= -1;
1538 mCSDCloseButton
= false;
1539 mCSDMinimizeButton
= false;
1540 mCSDMaximizeButton
= false;
1541 mCSDCloseButtonPosition
= 0;
1542 mCSDMinimizeButtonPosition
= 0;
1543 mCSDMaximizeButtonPosition
= 0;
1545 // We need to initialize whole CSD config explicitly because it's queried
1546 // as -moz-gtk* media features.
1547 ButtonLayout buttonLayout
[TOOLBAR_BUTTONS
];
1549 size_t activeButtons
=
1550 GetGtkHeaderBarButtonLayout(Span(buttonLayout
), &mCSDReversedPlacement
);
1551 for (size_t i
= 0; i
< activeButtons
; i
++) {
1552 // We check if a button is represented on the right side of the tabbar.
1553 // Then we assign it a value from 3 to 5, instead of 0 to 2 when it is on
1555 const ButtonLayout
& layout
= buttonLayout
[i
];
1556 int32_t* pos
= nullptr;
1557 switch (layout
.mType
) {
1558 case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE
:
1559 mCSDMinimizeButton
= true;
1560 pos
= &mCSDMinimizeButtonPosition
;
1562 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
:
1563 mCSDMaximizeButton
= true;
1564 pos
= &mCSDMaximizeButtonPosition
;
1566 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE
:
1567 mCSDCloseButton
= true;
1568 pos
= &mCSDCloseButtonPosition
;
1580 void nsLookAndFeel::ConfigureFinalEffectiveTheme() {
1581 MOZ_ASSERT(mSystemThemeOverridden
,
1582 "By this point, the alt theme should be configured");
1583 const bool shouldUseSystemTheme
= [&] {
1584 using ChromeSetting
= PreferenceSheet::ChromeColorSchemeSetting
;
1585 // NOTE: We can't call ColorSchemeForChrome directly because this might run
1586 // while we're computing it.
1587 switch (PreferenceSheet::ColorSchemeSettingForChrome()) {
1588 case ChromeSetting::Light
:
1589 return !mSystemTheme
.mIsDark
;
1590 case ChromeSetting::Dark
:
1591 return mSystemTheme
.mIsDark
;
1592 case ChromeSetting::System
:
1595 if (!mColorSchemePreference
) {
1598 const bool preferenceIsDark
= *mColorSchemePreference
== ColorScheme::Dark
;
1599 return preferenceIsDark
== mSystemTheme
.mIsDark
;
1602 const bool usingSystem
= !mSystemThemeOverridden
;
1603 LOGLNF("OverrideSystemThemeIfNeeded(matchesSystem=%d, usingSystem=%d)\n",
1604 shouldUseSystemTheme
, usingSystem
);
1606 if (shouldUseSystemTheme
) {
1607 RestoreSystemTheme();
1608 } else if (usingSystem
) {
1609 LOGLNF("Setting theme %s, %d\n", mAltTheme
.mName
.get(),
1610 mAltTheme
.mPreferDarkTheme
);
1612 GtkSettings
* settings
= gtk_settings_get_default();
1613 if (mSystemTheme
.mName
== mAltTheme
.mName
) {
1614 // Prefer setting only gtk-application-prefer-dark-theme, so we can still
1615 // get notified from notify::gtk-theme-name if the user changes the theme.
1616 g_object_set(settings
, "gtk-application-prefer-dark-theme",
1617 mAltTheme
.mPreferDarkTheme
, nullptr);
1619 g_object_set(settings
, "gtk-theme-name", mAltTheme
.mName
.get(),
1620 "gtk-application-prefer-dark-theme",
1621 mAltTheme
.mPreferDarkTheme
, nullptr);
1623 mSystemThemeOverridden
= true;
1624 UpdateRoundedBottomCornerStyles();
1629 static bool GetColorFromBackgroundImage(GtkStyleContext
* aStyle
,
1630 nscolor aForForegroundColor
,
1631 GtkStateFlags aState
, nscolor
* aColor
) {
1632 GValue value
= G_VALUE_INIT
;
1633 gtk_style_context_get_property(aStyle
, "background-image", aState
, &value
);
1634 auto cleanup
= MakeScopeExit([&] { g_value_unset(&value
); });
1635 if (GetColorFromImagePattern(&value
, aColor
)) {
1640 GdkRGBA light
, dark
;
1641 if (GetGradientColors(&value
, &light
, &dark
)) {
1642 nscolor l
= GDK_RGBA_TO_NS_RGBA(light
);
1643 nscolor d
= GDK_RGBA_TO_NS_RGBA(dark
);
1644 // Return the one with more contrast.
1645 // TODO(emilio): This could do interpolation or what not but seems
1647 if (NS_LUMINOSITY_DIFFERENCE(l
, aForForegroundColor
) >
1648 NS_LUMINOSITY_DIFFERENCE(d
, aForForegroundColor
)) {
1660 static nscolor
GetBackgroundColor(
1661 GtkStyleContext
* aStyle
, nscolor aForForegroundColor
,
1662 GtkStateFlags aState
= GTK_STATE_FLAG_NORMAL
,
1663 nscolor aOverBackgroundColor
= NS_TRANSPARENT
) {
1664 // Try to synthesize a color from a background-image.
1665 nscolor imageColor
= NS_TRANSPARENT
;
1666 if (GetColorFromBackgroundImage(aStyle
, aForForegroundColor
, aState
,
1668 if (NS_GET_A(imageColor
) == 255) {
1674 gtk_style_context_get_background_color(aStyle
, aState
, &gdkColor
);
1675 nscolor bgColor
= GDK_RGBA_TO_NS_RGBA(gdkColor
);
1676 // background-image paints over background-color.
1677 const nscolor finalColor
= NS_ComposeColors(bgColor
, imageColor
);
1678 if (finalColor
!= aOverBackgroundColor
) {
1681 return NS_TRANSPARENT
;
1684 static nscolor
GetTextColor(GtkStyleContext
* aStyle
,
1685 GtkStateFlags aState
= GTK_STATE_FLAG_NORMAL
) {
1687 gtk_style_context_get_color(aStyle
, aState
, &color
);
1688 return GDK_RGBA_TO_NS_RGBA(color
);
1691 using ColorPair
= nsLookAndFeel::ColorPair
;
1692 static ColorPair
GetColorPair(GtkStyleContext
* aStyle
,
1693 GtkStateFlags aState
= GTK_STATE_FLAG_NORMAL
) {
1695 result
.mFg
= GetTextColor(aStyle
, aState
);
1696 result
.mBg
= GetBackgroundColor(aStyle
, result
.mFg
, aState
);
1700 static bool GetNamedColorPair(GtkStyleContext
* aStyle
, const char* aBgName
,
1701 const char* aFgName
, ColorPair
* aPair
) {
1703 if (!gtk_style_context_lookup_color(aStyle
, aBgName
, &bg
) ||
1704 !gtk_style_context_lookup_color(aStyle
, aFgName
, &fg
)) {
1708 aPair
->mBg
= GDK_RGBA_TO_NS_RGBA(bg
);
1709 aPair
->mFg
= GDK_RGBA_TO_NS_RGBA(fg
);
1711 // If the colors are semi-transparent and the theme provides a
1712 // background color, blend with them to get the "final" color, see
1714 if (NS_GET_A(aPair
->mBg
) != 255 &&
1715 (gtk_style_context_lookup_color(aStyle
, "bg_color", &bg
) ||
1716 gtk_style_context_lookup_color(aStyle
, "theme_bg_color", &bg
))) {
1717 aPair
->mBg
= NS_ComposeColors(GDK_RGBA_TO_NS_RGBA(bg
), aPair
->mBg
);
1720 // A semi-transparent foreground color would be kinda silly, but is done
1722 if (NS_GET_A(aPair
->mFg
) != 255) {
1723 aPair
->mFg
= NS_ComposeColors(aPair
->mBg
, aPair
->mFg
);
1729 static void EnsureColorPairIsOpaque(ColorPair
& aPair
) {
1730 // Blend with white, ensuring the color is opaque, so that the UI doesn't have
1731 // to care about alpha.
1732 aPair
.mBg
= NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), aPair
.mBg
);
1733 aPair
.mFg
= NS_ComposeColors(aPair
.mBg
, aPair
.mFg
);
1736 static void PreferDarkerBackground(ColorPair
& aPair
) {
1737 // We use the darker one unless the foreground isn't really a color (is all
1738 // white / black / gray) and the background is, in which case we stick to what
1740 if (RelativeLuminanceUtils::Compute(aPair
.mBg
) >
1741 RelativeLuminanceUtils::Compute(aPair
.mFg
) &&
1742 (AnyColorChannelIsDifferent(aPair
.mFg
) ||
1743 !AnyColorChannelIsDifferent(aPair
.mBg
))) {
1744 std::swap(aPair
.mBg
, aPair
.mFg
);
1748 void nsLookAndFeel::PerThemeData::Init() {
1749 mName
= GetGtkTheme();
1752 if (mName
.EqualsLiteral("Adwaita") || mName
.EqualsLiteral("Adwaita-dark")) {
1753 return ThemeFamily::Adwaita
;
1755 if (mName
.EqualsLiteral("Breeze") || mName
.EqualsLiteral("Breeze-Dark")) {
1756 return ThemeFamily::Breeze
;
1758 if (StringBeginsWith(mName
, "Yaru"_ns
)) {
1759 return ThemeFamily::Yaru
;
1761 return ThemeFamily::Other
;
1764 GtkStyleContext
* style
;
1766 mHighContrast
= StaticPrefs::widget_content_gtk_high_contrast_enabled() &&
1767 mName
.Find("HighContrast"_ns
) >= 0;
1769 mPreferDarkTheme
= GetPreferDarkTheme();
1771 mIsDark
= GetThemeIsDark();
1773 mCompatibleWithHTMLLightColors
=
1774 !mIsDark
&& IsGtkThemeCompatibleWithHTMLColors();
1777 // Some themes style the <trough>, while others style the <scrollbar>
1778 // itself, so we look at both and compose the colors.
1779 style
= GetStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL
);
1780 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
1781 mThemedScrollbar
= GDK_RGBA_TO_NS_RGBA(color
);
1782 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_BACKDROP
,
1784 mThemedScrollbarInactive
= GDK_RGBA_TO_NS_RGBA(color
);
1786 style
= GetStyleContext(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
);
1787 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
1789 NS_ComposeColors(mThemedScrollbar
, GDK_RGBA_TO_NS_RGBA(color
));
1790 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_BACKDROP
,
1792 mThemedScrollbarInactive
=
1793 NS_ComposeColors(mThemedScrollbarInactive
, GDK_RGBA_TO_NS_RGBA(color
));
1795 style
= GetStyleContext(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL
);
1796 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
1797 mThemedScrollbarThumb
= GDK_RGBA_TO_NS_RGBA(color
);
1798 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_PRELIGHT
,
1800 mThemedScrollbarThumbHover
= GDK_RGBA_TO_NS_RGBA(color
);
1801 gtk_style_context_get_background_color(
1802 style
, GtkStateFlags(GTK_STATE_FLAG_PRELIGHT
| GTK_STATE_FLAG_ACTIVE
),
1804 mThemedScrollbarThumbActive
= GDK_RGBA_TO_NS_RGBA(color
);
1805 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_BACKDROP
,
1807 mThemedScrollbarThumbInactive
= GDK_RGBA_TO_NS_RGBA(color
);
1809 // Make sure that the thumb is visible, at least.
1810 const bool fallbackToUnthemedColors
= [&] {
1811 if (!StaticPrefs::widget_gtk_theme_scrollbar_colors_enabled()) {
1815 if (!ShouldHonorThemeScrollbarColors()) {
1818 // If any of the scrollbar thumb colors are fully transparent, fall back to
1820 if (!NS_GET_A(mThemedScrollbarThumb
) ||
1821 !NS_GET_A(mThemedScrollbarThumbHover
) ||
1822 !NS_GET_A(mThemedScrollbarThumbActive
)) {
1825 // If the thumb and track are the same color and opaque, fall back to
1826 // non-native colors as well.
1827 if (mThemedScrollbar
== mThemedScrollbarThumb
&&
1828 NS_GET_A(mThemedScrollbar
) == 0xff) {
1834 if (fallbackToUnthemedColors
) {
1836 // Taken from Adwaita-dark.
1837 mThemedScrollbar
= NS_RGB(0x31, 0x31, 0x31);
1838 mThemedScrollbarInactive
= NS_RGB(0x2d, 0x2d, 0x2d);
1839 mThemedScrollbarThumb
= NS_RGB(0xa3, 0xa4, 0xa4);
1840 mThemedScrollbarThumbInactive
= NS_RGB(0x59, 0x5a, 0x5a);
1842 // Taken from Adwaita.
1843 mThemedScrollbar
= NS_RGB(0xce, 0xce, 0xce);
1844 mThemedScrollbarInactive
= NS_RGB(0xec, 0xed, 0xef);
1845 mThemedScrollbarThumb
= NS_RGB(0x82, 0x81, 0x7e);
1846 mThemedScrollbarThumbInactive
= NS_RGB(0xce, 0xcf, 0xce);
1849 mThemedScrollbarThumbHover
= ThemeColors::AdjustUnthemedScrollbarThumbColor(
1850 mThemedScrollbarThumb
, dom::ElementState::HOVER
);
1851 mThemedScrollbarThumbActive
=
1852 ThemeColors::AdjustUnthemedScrollbarThumbColor(
1853 mThemedScrollbarThumb
, dom::ElementState::ACTIVE
);
1856 // The label is not added to a parent widget, but shared for constructing
1857 // different style contexts. The node hierarchy is constructed only on
1858 // the label style context.
1859 GtkWidget
* labelWidget
= gtk_label_new("M");
1860 g_object_ref_sink(labelWidget
);
1863 style
= GetStyleContext(MOZ_GTK_WINDOW
);
1865 mWindow
.mFg
= GetTextColor(style
);
1866 mWindow
.mBg
= GetBackgroundColor(style
, mWindow
.mFg
);
1868 gtk_style_context_get_border_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
1869 mMozWindowActiveBorder
= GDK_RGBA_TO_NS_RGBA(color
);
1871 gtk_style_context_get_border_color(style
, GTK_STATE_FLAG_INSENSITIVE
, &color
);
1872 mMozWindowInactiveBorder
= GDK_RGBA_TO_NS_RGBA(color
);
1874 style
= GetStyleContext(MOZ_GTK_WINDOW_CONTAINER
);
1876 GtkStyleContext
* labelStyle
= CreateStyleForWidget(labelWidget
, style
);
1877 GetSystemFontInfo(labelStyle
, &mDefaultFontName
, &mDefaultFontStyle
);
1878 g_object_unref(labelStyle
);
1881 // tooltip foreground and background
1882 style
= GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL
);
1883 mInfo
.mFg
= GetTextColor(style
);
1884 style
= GetStyleContext(MOZ_GTK_TOOLTIP
);
1885 mInfo
.mBg
= GetBackgroundColor(style
, mInfo
.mFg
);
1887 style
= GetStyleContext(MOZ_GTK_MENUITEM
);
1889 GtkStyleContext
* accelStyle
=
1890 CreateStyleForWidget(gtk_accel_label_new("M"), style
);
1892 GetSystemFontInfo(accelStyle
, &mMenuFontName
, &mMenuFontStyle
);
1894 gtk_style_context_get_color(accelStyle
, GTK_STATE_FLAG_NORMAL
, &color
);
1895 mMenu
.mFg
= GetTextColor(accelStyle
);
1896 mGrayText
= GetTextColor(accelStyle
, GTK_STATE_FLAG_INSENSITIVE
);
1897 g_object_unref(accelStyle
);
1900 const auto effectiveTitlebarStyle
=
1901 HeaderBarShouldDrawContainer(MOZ_GTK_HEADER_BAR
) ? MOZ_GTK_HEADERBAR_FIXED
1902 : MOZ_GTK_HEADER_BAR
;
1903 style
= GetStyleContext(effectiveTitlebarStyle
);
1905 mTitlebar
= GetColorPair(style
, GTK_STATE_FLAG_NORMAL
);
1906 mTitlebarInactive
= GetColorPair(style
, GTK_STATE_FLAG_BACKDROP
);
1907 mTitlebarRadius
= IsSolidCSDStyleUsed() ? 0 : GetBorderRadius(style
);
1910 // We special-case the header bar color in Adwaita, Yaru and Breeze to be the
1911 // titlebar color, because it looks better and matches what apps do by
1912 // default, see bug 1838460.
1914 // We only do this in the relevant desktop environments, however, since in
1915 // other cases we don't really know if the DE's titlebars are going to match.
1917 // For breeze, additionally we read the KDE colors directly, if available,
1918 // since these are user-configurable.
1920 // For most other themes or those in unknown DEs, we use the menubar colors.
1922 // FIXME(emilio): Can we do something a bit less special-case-y?
1923 const bool shouldUseTitlebarColorsForHeaderBar
= [&] {
1924 if (mFamily
== ThemeFamily::Adwaita
|| mFamily
== ThemeFamily::Yaru
) {
1925 return IsGnomeDesktopEnvironment();
1927 if (mFamily
== ThemeFamily::Breeze
) {
1928 return IsKdeDesktopEnvironment();
1933 if (shouldUseTitlebarColorsForHeaderBar
) {
1934 mHeaderBar
= mTitlebar
;
1935 mHeaderBarInactive
= mTitlebarInactive
;
1936 if (mFamily
== ThemeFamily::Breeze
) {
1937 GetNamedColorPair(style
, "theme_header_background_breeze",
1938 "theme_header_foreground_breeze", &mHeaderBar
);
1939 GetNamedColorPair(style
, "theme_header_background_backdrop_breeze",
1940 "theme_header_foreground_backdrop_breeze",
1941 &mHeaderBarInactive
);
1944 style
= GetStyleContext(MOZ_GTK_MENUBARITEM
);
1945 mHeaderBar
.mFg
= GetTextColor(style
);
1946 mHeaderBarInactive
.mFg
= GetTextColor(style
, GTK_STATE_FLAG_BACKDROP
);
1948 style
= GetStyleContext(MOZ_GTK_MENUBAR
);
1949 mHeaderBar
.mBg
= GetBackgroundColor(style
, mHeaderBar
.mFg
);
1950 mHeaderBarInactive
.mBg
= GetBackgroundColor(style
, mHeaderBarInactive
.mFg
,
1951 GTK_STATE_FLAG_BACKDROP
);
1954 style
= GetStyleContext(MOZ_GTK_MENUPOPUP
);
1956 nscolor color
= GetBackgroundColor(style
, mMenu
.mFg
);
1957 if (NS_GET_A(color
)) {
1960 // Some themes only style menupopups with the backdrop pseudo-class. Since a
1961 // context / popup menu always seems to match that, try that before giving
1963 color
= GetBackgroundColor(style
, mMenu
.mFg
, GTK_STATE_FLAG_BACKDROP
);
1964 if (NS_GET_A(color
)) {
1967 // If we get here we couldn't figure out the right color to use. Rather than
1968 // falling back to transparent, fall back to the window background.
1970 "Couldn't find menu background color, falling back to window "
1975 style
= GetStyleContext(MOZ_GTK_MENUITEM
);
1976 gtk_style_context_get_color(style
, GTK_STATE_FLAG_PRELIGHT
, &color
);
1977 mMenuHover
.mFg
= GDK_RGBA_TO_NS_RGBA(color
);
1978 mMenuHover
.mBg
= NS_ComposeColors(
1980 GetBackgroundColor(style
, mMenu
.mFg
, GTK_STATE_FLAG_PRELIGHT
, mMenu
.mBg
));
1982 GtkWidget
* parent
= gtk_fixed_new();
1983 GtkWidget
* window
= gtk_window_new(GTK_WINDOW_POPUP
);
1984 GtkWidget
* treeView
= gtk_tree_view_new();
1985 GtkWidget
* linkButton
= gtk_link_button_new("http://example.com/");
1986 GtkWidget
* menuBar
= gtk_menu_bar_new();
1987 GtkWidget
* menuBarItem
= gtk_menu_item_new();
1988 GtkWidget
* entry
= gtk_entry_new();
1989 GtkWidget
* textView
= gtk_text_view_new();
1991 gtk_container_add(GTK_CONTAINER(parent
), treeView
);
1992 gtk_container_add(GTK_CONTAINER(parent
), linkButton
);
1993 gtk_container_add(GTK_CONTAINER(parent
), menuBar
);
1994 gtk_menu_shell_append(GTK_MENU_SHELL(menuBar
), menuBarItem
);
1995 gtk_container_add(GTK_CONTAINER(window
), parent
);
1996 gtk_container_add(GTK_CONTAINER(parent
), entry
);
1997 gtk_container_add(GTK_CONTAINER(parent
), textView
);
2001 // If the text window background is translucent, then the background of
2002 // the textview root node is visible.
2003 style
= GetStyleContext(MOZ_GTK_TEXT_VIEW
);
2004 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
,
2007 style
= GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT
);
2008 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2009 ApplyColorOver(color
, &bgColor
);
2010 mField
.mBg
= GDK_RGBA_TO_NS_RGBA(bgColor
);
2011 gtk_style_context_get_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2012 mField
.mFg
= GDK_RGBA_TO_NS_RGBA(color
);
2014 // Selected text and background
2016 GtkStyleContext
* selectionStyle
=
2017 GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT_SELECTION
);
2018 auto GrabSelectionColors
= [&](GtkStyleContext
* style
) {
2019 gtk_style_context_get_background_color(
2021 static_cast<GtkStateFlags
>(GTK_STATE_FLAG_FOCUSED
|
2022 GTK_STATE_FLAG_SELECTED
),
2024 mSelectedText
.mBg
= GDK_RGBA_TO_NS_RGBA(color
);
2025 gtk_style_context_get_color(
2027 static_cast<GtkStateFlags
>(GTK_STATE_FLAG_FOCUSED
|
2028 GTK_STATE_FLAG_SELECTED
),
2030 mSelectedText
.mFg
= GDK_RGBA_TO_NS_RGBA(color
);
2032 GrabSelectionColors(selectionStyle
);
2033 if (mSelectedText
.mBg
== mSelectedText
.mFg
) {
2034 // Some old distros/themes don't properly use the .selection style, so
2035 // fall back to the regular text view style.
2036 GrabSelectionColors(style
);
2039 // Default selected item color is the selection background / foreground
2040 // colors, but we prefer named colors, as those are more general purpose
2041 // than the actual selection style, which might e.g. be too-transparent.
2043 // NOTE(emilio): It's unclear which one of the theme_selected_* or the
2044 // selected_* pairs should we prefer, in all themes that define both that
2045 // I've found, they're always the same.
2046 if (!GetNamedColorPair(style
, "selected_bg_color", "selected_fg_color",
2048 !GetNamedColorPair(style
, "theme_selected_bg_color",
2049 "theme_selected_fg_color", &mSelectedItem
)) {
2050 mSelectedItem
= mSelectedText
;
2053 EnsureColorPairIsOpaque(mSelectedItem
);
2055 // In a similar fashion, default accent color is the selected item/text
2056 // pair, but we also prefer named colors, if available.
2058 // accent_{bg,fg}_color is not _really_ a gtk3 thing (it's a gtk4 thing),
2059 // but if gtk 3 themes want to specify these we let them, see:
2061 // https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/named-colors.html#accent-colors
2062 if (!GetNamedColorPair(style
, "accent_bg_color", "accent_fg_color",
2064 mAccent
= mSelectedItem
;
2067 EnsureColorPairIsOpaque(mAccent
);
2068 PreferDarkerBackground(mAccent
);
2071 // Button text color
2072 style
= GetStyleContext(MOZ_GTK_BUTTON
);
2074 GtkStyleContext
* labelStyle
= CreateStyleForWidget(labelWidget
, style
);
2075 GetSystemFontInfo(labelStyle
, &mButtonFontName
, &mButtonFontStyle
);
2076 g_object_unref(labelStyle
);
2079 gtk_style_context_get_border_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2080 mButton
.mBg
= GDK_RGBA_TO_NS_RGBA(color
);
2081 gtk_style_context_get_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2082 mButton
.mFg
= GDK_RGBA_TO_NS_RGBA(color
);
2083 gtk_style_context_get_color(style
, GTK_STATE_FLAG_PRELIGHT
, &color
);
2084 mButtonHover
.mFg
= GDK_RGBA_TO_NS_RGBA(color
);
2085 gtk_style_context_get_color(style
, GTK_STATE_FLAG_ACTIVE
, &color
);
2086 mButtonActiveText
= GDK_RGBA_TO_NS_RGBA(color
);
2087 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_PRELIGHT
,
2089 mButtonHover
.mBg
= GDK_RGBA_TO_NS_RGBA(color
);
2090 if (!NS_GET_A(mButtonHover
.mBg
)) {
2091 mButtonHover
.mBg
= mWindow
.mBg
;
2094 // Combobox text color
2095 style
= GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA
);
2096 gtk_style_context_get_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2097 mComboBoxText
= GDK_RGBA_TO_NS_RGBA(color
);
2099 // GTK's guide to fancy odd row background colors:
2100 // 1) Check if a theme explicitly defines an odd row color
2101 // 2) If not, check if it defines an even row color, and darken it
2102 // slightly by a hardcoded value (gtkstyle.c)
2103 // 3) If neither are defined, take the base background color and
2104 // darken that by a hardcoded value
2105 style
= GetStyleContext(MOZ_GTK_TREEVIEW
);
2107 // Get odd row background color
2108 gtk_style_context_save(style
);
2109 gtk_style_context_add_region(style
, GTK_STYLE_REGION_ROW
, GTK_REGION_ODD
);
2110 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2111 mOddCellBackground
= GDK_RGBA_TO_NS_RGBA(color
);
2112 gtk_style_context_restore(style
);
2114 // Column header colors
2115 style
= GetStyleContext(MOZ_GTK_TREE_HEADER_CELL
);
2116 gtk_style_context_get_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2117 mMozColHeaderText
= GDK_RGBA_TO_NS_RGBA(color
);
2118 gtk_style_context_get_color(style
, GTK_STATE_FLAG_PRELIGHT
, &color
);
2119 mMozColHeaderHoverText
= GDK_RGBA_TO_NS_RGBA(color
);
2121 // Compute cell highlight colors
2122 InitCellHighlightColors();
2124 // GtkFrame has a "border" subnode on which Adwaita draws the border.
2125 // Some themes do not draw on this node but draw a border on the widget
2126 // root node, so check the root node if no border is found on the border
2128 style
= GetStyleContext(MOZ_GTK_FRAME_BORDER
);
2129 bool themeUsesColors
=
2130 GetBorderColors(style
, &mFrameOuterLightBorder
, &mFrameInnerDarkBorder
);
2131 if (!themeUsesColors
) {
2132 style
= GetStyleContext(MOZ_GTK_FRAME
);
2133 GetBorderColors(style
, &mFrameOuterLightBorder
, &mFrameInnerDarkBorder
);
2136 // Some themes have a unified menu bar, and support window dragging on it
2137 gboolean supports_menubar_drag
= FALSE
;
2138 GParamSpec
* param_spec
= gtk_widget_class_find_style_property(
2139 GTK_WIDGET_GET_CLASS(menuBar
), "window-dragging");
2141 if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec
), G_TYPE_BOOLEAN
)) {
2142 gtk_widget_style_get(menuBar
, "window-dragging", &supports_menubar_drag
,
2146 mMenuSupportsDrag
= supports_menubar_drag
;
2148 // TODO: It returns wrong color for themes which
2149 // sets link color for GtkLabel only as we query
2150 // GtkLinkButton style here.
2151 style
= gtk_widget_get_style_context(linkButton
);
2152 gtk_style_context_get_color(style
, GTK_STATE_FLAG_LINK
, &color
);
2153 mNativeHyperLinkText
= GDK_RGBA_TO_NS_RGBA(color
);
2155 gtk_style_context_get_color(style
, GTK_STATE_FLAG_VISITED
, &color
);
2156 mNativeVisitedHyperLinkText
= GDK_RGBA_TO_NS_RGBA(color
);
2158 // invisible character styles
2160 g_object_get(entry
, "invisible-char", &value
, nullptr);
2161 mInvisibleCharacter
= char16_t(value
);
2164 gtk_widget_style_get(entry
, "cursor-aspect-ratio", &mCaretRatio
, nullptr);
2166 GetSystemFontInfo(gtk_widget_get_style_context(entry
), &mFieldFontName
,
2169 gtk_widget_destroy(window
);
2170 g_object_unref(labelWidget
);
2172 if (LOGLNF_ENABLED()) {
2173 LOGLNF("Initialized theme %s (%d)\n", mName
.get(), mPreferDarkTheme
);
2174 for (auto id
: MakeEnumeratedRange(ColorID::End
)) {
2176 nsresult rv
= GetColor(id
, color
);
2177 LOGLNF(" * color %d: pref=%s success=%d value=%x\n", int(id
),
2178 GetColorPrefName(id
), NS_SUCCEEDED(rv
),
2179 NS_SUCCEEDED(rv
) ? color
: 0);
2181 LOGLNF(" * titlebar-radius: %d\n", mTitlebarRadius
);
2186 char16_t
nsLookAndFeel::GetPasswordCharacterImpl() {
2188 return mSystemTheme
.mInvisibleCharacter
;
2191 bool nsLookAndFeel::GetEchoPasswordImpl() { return false; }
2193 bool nsLookAndFeel::GetDefaultDrawInTitlebar() { return sCSDAvailable
; }
2195 void nsLookAndFeel::GetThemeInfo(nsACString
& aInfo
) {
2196 aInfo
.Append(mSystemTheme
.mName
);
2197 aInfo
.Append(" / ");
2198 aInfo
.Append(mAltTheme
.mName
);
2201 bool nsLookAndFeel::WidgetUsesImage(WidgetNodeType aNodeType
) {
2202 static constexpr GtkStateFlags sFlagsToCheck
[]{
2203 GTK_STATE_FLAG_NORMAL
, GTK_STATE_FLAG_PRELIGHT
,
2204 GtkStateFlags(GTK_STATE_FLAG_PRELIGHT
| GTK_STATE_FLAG_ACTIVE
),
2205 GTK_STATE_FLAG_BACKDROP
, GTK_STATE_FLAG_INSENSITIVE
};
2207 GtkStyleContext
* style
= GetStyleContext(aNodeType
);
2209 GValue value
= G_VALUE_INIT
;
2210 for (GtkStateFlags state
: sFlagsToCheck
) {
2211 gtk_style_context_get_property(style
, "background-image", state
, &value
);
2212 bool hasPattern
= G_VALUE_TYPE(&value
) == CAIRO_GOBJECT_TYPE_PATTERN
&&
2213 g_value_get_boxed(&value
);
2214 g_value_unset(&value
);
2222 void nsLookAndFeel::RecordLookAndFeelSpecificTelemetry() {
2223 // Gtk version we're on.
2225 version
.AppendPrintf("%d.%d", gtk_major_version
, gtk_minor_version
);
2226 Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_GTK_VERSION
, version
);
2228 // Whether the current Gtk theme has scrollbar buttons.
2229 bool hasScrollbarButtons
=
2230 GetInt(LookAndFeel::IntID::ScrollArrowStyle
) != eScrollArrow_None
;
2231 mozilla::Telemetry::ScalarSet(
2232 mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_HAS_SCROLLBAR_BUTTONS
,
2233 hasScrollbarButtons
);
2235 // Whether the current Gtk theme uses something other than a solid color
2236 // background for scrollbar parts.
2237 bool scrollbarUsesImage
= !ShouldHonorThemeScrollbarColors();
2238 mozilla::Telemetry::ScalarSet(
2239 mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_SCROLLBAR_USES_IMAGES
,
2240 scrollbarUsesImage
);
2243 bool nsLookAndFeel::ShouldHonorThemeScrollbarColors() {
2244 // If the Gtk theme uses anything other than solid color backgrounds for Gtk
2245 // scrollbar parts, this is a good indication that painting XUL scrollbar part
2246 // elements using colors extracted from the theme won't provide good results.
2247 return !WidgetUsesImage(MOZ_GTK_SCROLLBAR_VERTICAL
) &&
2248 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL
) &&
2249 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
) &&
2250 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL
);
2254 #undef LOGLNF_ENABLED