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"
55 # include <X11/XKBlib.h>
59 # include <xkbcommon/xkbcommon.h>
62 using namespace mozilla
;
63 using namespace mozilla::widget
;
66 # include "mozilla/Logging.h"
67 # include "nsTArray.h"
69 static LazyLogModule
gLnfLog("LookAndFeel");
70 # define LOGLNF(...) MOZ_LOG(gLnfLog, LogLevel::Debug, (__VA_ARGS__))
71 # define LOGLNF_ENABLED() MOZ_LOG_TEST(gLnfLog, LogLevel::Debug)
74 # define LOGLNF_ENABLED() false
75 #endif /* MOZ_LOGGING */
77 #define GDK_COLOR_TO_NS_RGB(c) \
78 ((nscolor)NS_RGB(c.red >> 8, c.green >> 8, c.blue >> 8))
79 #define GDK_RGBA_TO_NS_RGBA(c) \
80 ((nscolor)NS_RGBA((int)((c).red * 255), (int)((c).green * 255), \
81 (int)((c).blue * 255), (int)((c).alpha * 255)))
83 static bool sIgnoreChangedSettings
= false;
85 static void OnSettingsChange() {
86 if (sIgnoreChangedSettings
) {
89 // TODO: We could be more granular here, but for now assume everything
91 LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::StyleAndLayout
);
92 widget::IMContextWrapper::OnThemeChanged();
95 static void settings_changed_cb(GtkSettings
*, GParamSpec
*, void*) {
99 static bool sCSDAvailable
;
101 static nsCString
GVariantToString(GVariant
* aVariant
) {
103 gchar
* s
= g_variant_print(aVariant
, TRUE
);
111 static nsDependentCString
GVariantGetString(GVariant
* aVariant
) {
113 const gchar
* v
= g_variant_get_string(aVariant
, &len
);
114 return nsDependentCString(v
, len
);
117 static void settings_changed_signal_cb(GDBusProxy
* proxy
, gchar
* sender_name
,
118 gchar
* signal_name
, GVariant
* parameters
,
119 gpointer user_data
) {
120 LOGLNF("Settings Change sender=%s signal=%s params=%s\n", sender_name
,
121 signal_name
, GVariantToString(parameters
).get());
122 if (strcmp(signal_name
, "SettingChanged")) {
123 NS_WARNING("Unknown change signal for settings");
126 RefPtr
<GVariant
> ns
= dont_AddRef(g_variant_get_child_value(parameters
, 0));
127 RefPtr
<GVariant
> key
= dont_AddRef(g_variant_get_child_value(parameters
, 1));
128 // Third parameter is the value, but we don't care about it.
129 if (!ns
|| !key
|| !g_variant_is_of_type(ns
, G_VARIANT_TYPE_STRING
) ||
130 !g_variant_is_of_type(key
, G_VARIANT_TYPE_STRING
)) {
131 MOZ_ASSERT(false, "Unexpected setting change signal parameters");
135 auto* lnf
= static_cast<nsLookAndFeel
*>(user_data
);
137 auto nsStr
= GVariantGetString(ns
);
138 auto keyStr
= GVariantGetString(key
);
139 if (nsStr
.Equals("org.freedesktop.appearance"_ns
) &&
140 keyStr
.Equals("color-scheme"_ns
)) {
141 lnf
->OnColorSchemeSettingChanged();
145 void nsLookAndFeel::WatchDBus() {
146 GUniquePtr
<GError
> error
;
147 mDBusSettingsProxy
= dont_AddRef(g_dbus_proxy_new_for_bus_sync(
148 G_BUS_TYPE_SESSION
, G_DBUS_PROXY_FLAGS_NONE
, nullptr,
149 "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop",
150 "org.freedesktop.portal.Settings", nullptr, getter_Transfers(error
)));
151 if (mDBusSettingsProxy
) {
152 g_signal_connect(mDBusSettingsProxy
, "g-signal",
153 G_CALLBACK(settings_changed_signal_cb
), this);
155 LOGLNF("Can't create DBus proxy for settings: %s\n", error
->message
);
159 // DBus interface was started after L&F init so we need to load
160 // our settings from DBus explicitly.
161 if (!sIgnoreChangedSettings
) {
162 OnColorSchemeSettingChanged();
166 void nsLookAndFeel::UnwatchDBus() {
167 if (mDBusSettingsProxy
) {
168 g_signal_handlers_disconnect_by_func(
169 mDBusSettingsProxy
, FuncToGpointer(settings_changed_signal_cb
), this);
170 mDBusSettingsProxy
= nullptr;
174 nsLookAndFeel::nsLookAndFeel() {
175 static constexpr nsLiteralCString kObservedSettings
[] = {
176 // Affects system font sizes.
177 "notify::gtk-xft-dpi"_ns
,
178 // Affects mSystemTheme and mAltTheme as expected.
179 "notify::gtk-theme-name"_ns
,
181 "notify::gtk-font-name"_ns
,
182 // prefers-reduced-motion
183 "notify::gtk-enable-animations"_ns
,
184 // CSD media queries, etc.
185 "notify::gtk-decoration-layout"_ns
,
186 // Text resolution affects system font and widget sizes.
187 "notify::resolution"_ns
,
188 // These three Affect mCaretBlinkTime
189 "notify::gtk-cursor-blink"_ns
,
190 "notify::gtk-cursor-blink-time"_ns
,
191 "notify::gtk-cursor-blink-timeout"_ns
,
192 // Affects SelectTextfieldsOnKeyFocus
193 "notify::gtk-entry-select-on-focus"_ns
,
194 // Affects ScrollToClick
195 "notify::gtk-primary-button-warps-slider"_ns
,
196 // Affects SubmenuDelay
197 "notify::gtk-menu-popup-delay"_ns
,
198 // Affects DragThresholdX/Y
199 "notify::gtk-dnd-drag-threshold"_ns
,
200 // Affects titlebar actions loaded at moz_gtk_refresh().
201 "notify::gtk-titlebar-double-click"_ns
,
202 "notify::gtk-titlebar-middle-click"_ns
,
205 GtkSettings
* settings
= gtk_settings_get_default();
206 for (const auto& setting
: kObservedSettings
) {
207 g_signal_connect_after(settings
, setting
.get(),
208 G_CALLBACK(settings_changed_cb
), nullptr);
212 nsWindow::GetSystemGtkWindowDecoration() != nsWindow::GTK_DECORATION_NONE
;
214 if (ShouldUsePortal(PortalKind::Settings
)) {
215 mDBusID
= g_bus_watch_name(
216 G_BUS_TYPE_SESSION
, "org.freedesktop.portal.Desktop",
217 G_BUS_NAME_WATCHER_FLAGS_AUTO_START
,
218 [](GDBusConnection
*, const gchar
*, const gchar
*,
219 gpointer data
) -> void {
220 auto* lnf
= static_cast<nsLookAndFeel
*>(data
);
223 [](GDBusConnection
*, const gchar
*, gpointer data
) -> void {
224 auto* lnf
= static_cast<nsLookAndFeel
*>(data
);
229 if (IsKdeDesktopEnvironment()) {
230 GUniquePtr
<gchar
> path(
231 g_strconcat(g_get_user_config_dir(), "/gtk-3.0/colors.css", NULL
));
232 mKdeColors
= dont_AddRef(g_file_new_for_path(path
.get()));
233 mKdeColorsMonitor
= dont_AddRef(
234 g_file_monitor_file(mKdeColors
.get(), G_FILE_MONITOR_NONE
, NULL
, NULL
));
235 if (mKdeColorsMonitor
) {
236 g_signal_connect(mKdeColorsMonitor
.get(), "changed",
237 G_CALLBACK(settings_changed_cb
), NULL
);
242 nsLookAndFeel::~nsLookAndFeel() {
243 ClearRoundedCornerProvider();
245 g_bus_unwatch_name(mDBusID
);
249 g_signal_handlers_disconnect_by_func(
250 gtk_settings_get_default(), FuncToGpointer(settings_changed_cb
), nullptr);
254 static void DumpStyleContext(GtkStyleContext
* aStyle
) {
255 static auto sGtkStyleContextToString
=
256 reinterpret_cast<char* (*)(GtkStyleContext
*, gint
)>(
257 dlsym(RTLD_DEFAULT
, "gtk_style_context_to_string"));
258 char* str
= sGtkStyleContextToString(aStyle
, ~0);
261 str
= gtk_widget_path_to_string(gtk_style_context_get_path(aStyle
));
267 // Modifies color |*aDest| as if a pattern of color |aSource| was painted with
268 // CAIRO_OPERATOR_OVER to a surface with color |*aDest|.
269 static void ApplyColorOver(const GdkRGBA
& aSource
, GdkRGBA
* aDest
) {
270 gdouble sourceCoef
= aSource
.alpha
;
271 gdouble destCoef
= aDest
->alpha
* (1.0 - sourceCoef
);
272 gdouble resultAlpha
= sourceCoef
+ destCoef
;
273 if (resultAlpha
!= 0.0) { // don't divide by zero
274 destCoef
/= resultAlpha
;
275 sourceCoef
/= resultAlpha
;
276 aDest
->red
= sourceCoef
* aSource
.red
+ destCoef
* aDest
->red
;
277 aDest
->green
= sourceCoef
* aSource
.green
+ destCoef
* aDest
->green
;
278 aDest
->blue
= sourceCoef
* aSource
.blue
+ destCoef
* aDest
->blue
;
279 aDest
->alpha
= resultAlpha
;
283 static void GetLightAndDarkness(const GdkRGBA
& aColor
, double* aLightness
,
285 double sum
= aColor
.red
+ aColor
.green
+ aColor
.blue
;
286 *aLightness
= sum
* aColor
.alpha
;
287 *aDarkness
= (3.0 - sum
) * aColor
.alpha
;
290 static bool GetGradientColors(const GValue
* aValue
, GdkRGBA
* aLightColor
,
291 GdkRGBA
* aDarkColor
) {
292 if (!G_TYPE_CHECK_VALUE_TYPE(aValue
, CAIRO_GOBJECT_TYPE_PATTERN
)) {
296 auto pattern
= static_cast<cairo_pattern_t
*>(g_value_get_boxed(aValue
));
301 // Just picking the lightest and darkest colors as simple samples rather
302 // than trying to blend, which could get messy if there are many stops.
303 if (CAIRO_STATUS_SUCCESS
!=
304 cairo_pattern_get_color_stop_rgba(pattern
, 0, nullptr, &aDarkColor
->red
,
305 &aDarkColor
->green
, &aDarkColor
->blue
,
306 &aDarkColor
->alpha
)) {
310 double maxLightness
, maxDarkness
;
311 GetLightAndDarkness(*aDarkColor
, &maxLightness
, &maxDarkness
);
312 *aLightColor
= *aDarkColor
;
316 CAIRO_STATUS_SUCCESS
==
317 cairo_pattern_get_color_stop_rgba(pattern
, index
, nullptr, &stop
.red
,
318 &stop
.green
, &stop
.blue
, &stop
.alpha
);
320 double lightness
, darkness
;
321 GetLightAndDarkness(stop
, &lightness
, &darkness
);
322 if (lightness
> maxLightness
) {
323 maxLightness
= lightness
;
326 if (darkness
> maxDarkness
) {
327 maxDarkness
= darkness
;
335 static bool GetColorFromImagePattern(const GValue
* aValue
, nscolor
* aColor
) {
336 if (!G_TYPE_CHECK_VALUE_TYPE(aValue
, CAIRO_GOBJECT_TYPE_PATTERN
)) {
340 auto* pattern
= static_cast<cairo_pattern_t
*>(g_value_get_boxed(aValue
));
345 cairo_surface_t
* surface
;
346 if (cairo_pattern_get_surface(pattern
, &surface
) != CAIRO_STATUS_SUCCESS
) {
350 cairo_format_t format
= cairo_image_surface_get_format(surface
);
351 if (format
== CAIRO_FORMAT_INVALID
) {
354 int width
= cairo_image_surface_get_width(surface
);
355 int height
= cairo_image_surface_get_height(surface
);
356 int stride
= cairo_image_surface_get_stride(surface
);
357 if (!width
|| !height
) {
361 // Guesstimate the central pixel would have a sensible color.
365 unsigned char* data
= cairo_image_surface_get_data(surface
);
367 // Most (all?) GTK images / patterns / etc use ARGB32.
368 case CAIRO_FORMAT_ARGB32
: {
369 size_t offset
= x
* 4 + y
* stride
;
370 uint32_t* pixel
= reinterpret_cast<uint32_t*>(data
+ offset
);
371 *aColor
= gfx::sRGBColor::UnusualFromARGB(*pixel
).ToABGR();
381 static bool GetUnicoBorderGradientColors(GtkStyleContext
* aContext
,
382 GdkRGBA
* aLightColor
,
383 GdkRGBA
* aDarkColor
) {
384 // Ubuntu 12.04 has GTK engine Unico-1.0.2, which overrides render_frame,
385 // providing its own border code. Ubuntu 14.04 has
386 // Unico-1.0.3+14.04.20140109, which does not override render_frame, and
387 // so does not need special attention. The earlier Unico can be detected
388 // by the -unico-border-gradient style property it registers.
389 // gtk_style_properties_lookup_property() is checked first to avoid the
390 // warning from gtk_style_context_get_property() when the property does
391 // not exist. (gtk_render_frame() of GTK+ 3.16 no longer uses the
393 const char* propertyName
= "-unico-border-gradient";
394 if (!gtk_style_properties_lookup_property(propertyName
, nullptr, nullptr))
397 // -unico-border-gradient is used only when the CSS node's engine is Unico.
398 GtkThemingEngine
* engine
;
399 GtkStateFlags state
= gtk_style_context_get_state(aContext
);
400 gtk_style_context_get(aContext
, state
, "engine", &engine
, nullptr);
401 if (strcmp(g_type_name(G_TYPE_FROM_INSTANCE(engine
)), "UnicoEngine") != 0)
404 // draw_border() of Unico engine uses -unico-border-gradient
405 // in preference to border-color.
406 GValue value
= G_VALUE_INIT
;
407 gtk_style_context_get_property(aContext
, propertyName
, state
, &value
);
409 bool result
= GetGradientColors(&value
, aLightColor
, aDarkColor
);
411 g_value_unset(&value
);
415 // Sets |aLightColor| and |aDarkColor| to colors from |aContext|. Returns
416 // true if |aContext| uses these colors to render a visible border.
417 // If returning false, then the colors returned are a fallback from the
418 // border-color value even though |aContext| does not use these colors to
420 static bool GetBorderColors(GtkStyleContext
* aContext
, GdkRGBA
* aLightColor
,
421 GdkRGBA
* aDarkColor
) {
422 // Determine whether the border on this style context is visible.
423 GtkStateFlags state
= gtk_style_context_get_state(aContext
);
424 GtkBorderStyle borderStyle
;
425 gtk_style_context_get(aContext
, state
, GTK_STYLE_PROPERTY_BORDER_STYLE
,
426 &borderStyle
, nullptr);
427 bool visible
= borderStyle
!= GTK_BORDER_STYLE_NONE
&&
428 borderStyle
!= GTK_BORDER_STYLE_HIDDEN
;
430 // GTK has an initial value of zero for border-widths, and so themes
431 // need to explicitly set border-widths to make borders visible.
433 gtk_style_context_get_border(aContext
, state
, &border
);
434 visible
= border
.top
!= 0 || border
.right
!= 0 || border
.bottom
!= 0 ||
439 GetUnicoBorderGradientColors(aContext
, aLightColor
, aDarkColor
))
442 // The initial value for the border-color is the foreground color, and so
443 // this will usually return a color distinct from the background even if
444 // there is no visible border detected.
445 gtk_style_context_get_border_color(aContext
, state
, aDarkColor
);
446 // TODO GTK3 - update aLightColor
447 // for GTK_BORDER_STYLE_INSET/OUTSET/GROVE/RIDGE border styles.
448 // https://bugzilla.mozilla.org/show_bug.cgi?id=978172#c25
449 *aLightColor
= *aDarkColor
;
453 static bool GetBorderColors(GtkStyleContext
* aContext
, nscolor
* aLightColor
,
454 nscolor
* aDarkColor
) {
455 GdkRGBA lightColor
, darkColor
;
456 bool ret
= GetBorderColors(aContext
, &lightColor
, &darkColor
);
457 *aLightColor
= GDK_RGBA_TO_NS_RGBA(lightColor
);
458 *aDarkColor
= GDK_RGBA_TO_NS_RGBA(darkColor
);
462 // Finds ideal cell highlight colors used for unfocused+selected cells distinct
463 // from both Highlight, used as focused+selected background, and the listbox
464 // background which is assumed to be similar to -moz-field
465 void nsLookAndFeel::PerThemeData::InitCellHighlightColors() {
466 int32_t minLuminosityDifference
= NS_SUFFICIENT_LUMINOSITY_DIFFERENCE_BG
;
467 int32_t backLuminosityDifference
=
468 NS_LUMINOSITY_DIFFERENCE(mWindow
.mBg
, mField
.mBg
);
469 if (backLuminosityDifference
>= minLuminosityDifference
) {
470 mCellHighlight
= mWindow
;
474 uint16_t hue
, sat
, luminance
;
476 mCellHighlight
= mField
;
478 NS_RGB2HSV(mCellHighlight
.mBg
, hue
, sat
, luminance
, alpha
);
481 // Lighten the color if the color is very dark
482 if (luminance
<= step
) {
485 // Darken it if it is very light
486 else if (luminance
>= 255 - step
) {
489 // Otherwise, compute what works best depending on the text luminance.
491 uint16_t textHue
, textSat
, textLuminance
;
493 NS_RGB2HSV(mCellHighlight
.mFg
, textHue
, textSat
, textLuminance
, textAlpha
);
494 // Text is darker than background, use a lighter shade
495 if (textLuminance
< luminance
) {
498 // Otherwise, use a darker shade
503 NS_HSV2RGB(mCellHighlight
.mBg
, hue
, sat
, luminance
, alpha
);
506 void nsLookAndFeel::NativeInit() { EnsureInit(); }
508 void nsLookAndFeel::RefreshImpl() {
509 mInitialized
= false;
512 nsXPLookAndFeel::RefreshImpl();
515 nsresult
nsLookAndFeel::NativeGetColor(ColorID aID
, ColorScheme aScheme
,
520 aScheme
== ColorScheme::Light
? LightTheme() : DarkTheme();
521 return theme
.GetColor(aID
, aColor
);
524 static bool ShouldUseColorForActiveDarkScrollbarThumb(nscolor aColor
) {
525 auto IsDifferentEnough
= [](int32_t aChannel
, int32_t aOtherChannel
) {
526 return std::abs(aChannel
- aOtherChannel
) > 10;
528 return IsDifferentEnough(NS_GET_R(aColor
), NS_GET_G(aColor
)) ||
529 IsDifferentEnough(NS_GET_R(aColor
), NS_GET_B(aColor
));
532 static bool ShouldUseThemedScrollbarColor(StyleSystemColor aID
, nscolor aColor
,
537 if (StaticPrefs::widget_non_native_theme_scrollbar_dark_themed()) {
540 return aID
== StyleSystemColor::ThemedScrollbarThumbActive
&&
541 StaticPrefs::widget_non_native_theme_scrollbar_active_always_themed();
544 nsresult
nsLookAndFeel::PerThemeData::GetColor(ColorID aID
,
545 nscolor
& aColor
) const {
546 nsresult res
= NS_OK
;
549 // These colors don't seem to be used for anything anymore in Mozilla
550 // The CSS2 colors below are used.
551 case ColorID::Appworkspace
: // MDI background color
552 case ColorID::Background
: // desktop background
553 case ColorID::Window
:
554 case ColorID::Windowframe
:
555 case ColorID::MozCombobox
:
556 aColor
= mWindow
.mBg
;
558 case ColorID::Windowtext
:
559 aColor
= mWindow
.mFg
;
561 case ColorID::MozDialog
:
562 aColor
= mDialog
.mBg
;
564 case ColorID::MozDialogtext
:
565 aColor
= mDialog
.mFg
;
567 case ColorID::IMESelectedRawTextBackground
:
568 case ColorID::IMESelectedConvertedTextBackground
:
569 case ColorID::Highlight
: // preference selected item,
570 aColor
= mSelectedText
.mBg
;
572 case ColorID::Highlighttext
:
573 if (NS_GET_A(mSelectedText
.mBg
) < 155) {
574 aColor
= NS_SAME_AS_FOREGROUND_COLOR
;
578 case ColorID::IMESelectedRawTextForeground
:
579 case ColorID::IMESelectedConvertedTextForeground
:
580 aColor
= mSelectedText
.mFg
;
582 case ColorID::Selecteditem
:
583 aColor
= mSelectedItem
.mBg
;
585 case ColorID::Selecteditemtext
:
586 aColor
= mSelectedItem
.mFg
;
588 case ColorID::Accentcolor
:
589 aColor
= mAccent
.mBg
;
591 case ColorID::Accentcolortext
:
592 aColor
= mAccent
.mFg
;
594 case ColorID::MozCellhighlight
:
595 aColor
= mCellHighlight
.mBg
;
597 case ColorID::MozCellhighlighttext
:
598 aColor
= mCellHighlight
.mFg
;
600 case ColorID::IMERawInputBackground
:
601 case ColorID::IMEConvertedTextBackground
:
602 aColor
= NS_TRANSPARENT
;
604 case ColorID::IMERawInputForeground
:
605 case ColorID::IMEConvertedTextForeground
:
606 aColor
= NS_SAME_AS_FOREGROUND_COLOR
;
608 case ColorID::IMERawInputUnderline
:
609 case ColorID::IMEConvertedTextUnderline
:
610 aColor
= NS_SAME_AS_FOREGROUND_COLOR
;
612 case ColorID::IMESelectedRawTextUnderline
:
613 case ColorID::IMESelectedConvertedTextUnderline
:
614 aColor
= NS_TRANSPARENT
;
616 case ColorID::Scrollbar
:
617 aColor
= mThemedScrollbar
;
619 case ColorID::ThemedScrollbar
:
620 aColor
= mThemedScrollbar
;
621 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
622 return NS_ERROR_FAILURE
;
625 case ColorID::ThemedScrollbarInactive
:
626 aColor
= mThemedScrollbarInactive
;
627 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
628 return NS_ERROR_FAILURE
;
631 case ColorID::ThemedScrollbarThumb
:
632 aColor
= mThemedScrollbarThumb
;
633 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
634 return NS_ERROR_FAILURE
;
637 case ColorID::ThemedScrollbarThumbHover
:
638 aColor
= mThemedScrollbarThumbHover
;
639 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
640 return NS_ERROR_FAILURE
;
643 case ColorID::ThemedScrollbarThumbActive
:
644 aColor
= mThemedScrollbarThumbActive
;
645 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
646 return NS_ERROR_FAILURE
;
649 case ColorID::ThemedScrollbarThumbInactive
:
650 aColor
= mThemedScrollbarThumbInactive
;
651 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
652 return NS_ERROR_FAILURE
;
656 // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
657 case ColorID::Activeborder
:
658 // active window border
659 aColor
= mMozWindowActiveBorder
;
661 case ColorID::Inactiveborder
:
662 // inactive window border
663 aColor
= mMozWindowInactiveBorder
;
665 case ColorID::Graytext
: // disabled text in windows, menus, etc.
668 case ColorID::Activecaption
:
669 aColor
= mTitlebar
.mBg
;
671 case ColorID::Captiontext
: // text in active window caption (titlebar)
672 aColor
= mTitlebar
.mFg
;
674 case ColorID::Inactivecaption
:
675 // inactive window caption
676 aColor
= mTitlebarInactive
.mBg
;
678 case ColorID::Inactivecaptiontext
:
679 aColor
= mTitlebarInactive
.mFg
;
681 case ColorID::Infobackground
:
684 case ColorID::Infotext
:
690 case ColorID::Menutext
:
693 case ColorID::MozHeaderbar
:
694 aColor
= mHeaderBar
.mBg
;
696 case ColorID::MozHeaderbartext
:
697 aColor
= mHeaderBar
.mFg
;
699 case ColorID::MozHeaderbarinactive
:
700 aColor
= mHeaderBarInactive
.mBg
;
702 case ColorID::MozHeaderbarinactivetext
:
703 aColor
= mHeaderBarInactive
.mFg
;
705 case ColorID::Threedface
:
706 case ColorID::Buttonface
:
707 case ColorID::MozButtondisabledface
:
709 aColor
= mWindow
.mBg
;
712 case ColorID::Buttontext
:
713 // text on push buttons
714 aColor
= mButton
.mFg
;
717 case ColorID::Buttonhighlight
:
718 // 3-D highlighted edge color
719 case ColorID::Threedhighlight
:
720 // 3-D highlighted outer edge color
721 aColor
= mThreeDHighlight
;
724 case ColorID::Buttonshadow
:
725 // 3-D shadow edge color
726 case ColorID::Threedshadow
:
727 // 3-D shadow inner edge color
728 aColor
= mThreeDShadow
;
730 case ColorID::Buttonborder
:
731 aColor
= mButtonBorder
;
733 case ColorID::Threedlightshadow
:
734 case ColorID::MozDisabledfield
:
735 aColor
= mIsDark
? *GenericDarkColor(aID
) : NS_RGB(0xE0, 0xE0, 0xE0);
737 case ColorID::Threeddarkshadow
:
738 aColor
= mIsDark
? *GenericDarkColor(aID
) : NS_RGB(0xDC, 0xDC, 0xDC);
741 case ColorID::MozEventreerow
:
745 case ColorID::Fieldtext
:
748 case ColorID::MozSidebar
:
749 aColor
= mSidebar
.mBg
;
751 case ColorID::MozSidebartext
:
752 aColor
= mSidebar
.mFg
;
754 case ColorID::MozSidebarborder
:
755 aColor
= mSidebarBorder
;
757 case ColorID::MozButtonhoverface
:
758 aColor
= mButtonHover
.mBg
;
760 case ColorID::MozButtonhovertext
:
761 aColor
= mButtonHover
.mFg
;
763 case ColorID::MozButtonactiveface
:
764 aColor
= mButtonActive
.mBg
;
766 case ColorID::MozButtonactivetext
:
767 aColor
= mButtonActive
.mFg
;
769 case ColorID::MozMenuhover
:
770 aColor
= mMenuHover
.mBg
;
772 case ColorID::MozMenuhovertext
:
773 aColor
= mMenuHover
.mFg
;
775 case ColorID::MozMenuhoverdisabled
:
776 aColor
= NS_TRANSPARENT
;
778 case ColorID::MozOddtreerow
:
779 aColor
= mOddCellBackground
;
781 case ColorID::MozNativehyperlinktext
:
782 aColor
= mNativeHyperLinkText
;
784 case ColorID::MozNativevisitedhyperlinktext
:
785 aColor
= mNativeVisitedHyperLinkText
;
787 case ColorID::MozComboboxtext
:
788 aColor
= mComboBoxText
;
790 case ColorID::MozColheader
:
791 aColor
= mMozColHeader
.mBg
;
793 case ColorID::MozColheadertext
:
794 aColor
= mMozColHeader
.mFg
;
796 case ColorID::MozColheaderhover
:
797 aColor
= mMozColHeaderHover
.mBg
;
799 case ColorID::MozColheaderhovertext
:
800 aColor
= mMozColHeaderHover
.mFg
;
802 case ColorID::MozColheaderactive
:
803 aColor
= mMozColHeaderActive
.mBg
;
805 case ColorID::MozColheaderactivetext
:
806 aColor
= mMozColHeaderActive
.mFg
;
808 case ColorID::SpellCheckerUnderline
:
810 case ColorID::Marktext
:
811 case ColorID::MozAutofillBackground
:
812 case ColorID::TargetTextBackground
:
813 case ColorID::TargetTextForeground
:
814 aColor
= GetStandinForNativeColor(
815 aID
, mIsDark
? ColorScheme::Dark
: ColorScheme::Light
);
818 /* default color is BLACK */
820 res
= NS_ERROR_FAILURE
;
827 static int32_t CheckWidgetStyle(GtkWidget
* aWidget
, const char* aStyle
,
829 gboolean value
= FALSE
;
830 gtk_widget_style_get(aWidget
, aStyle
, &value
, nullptr);
831 return value
? aResult
: 0;
834 static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle(
835 GtkWidget
* aWidget
) {
836 if (!aWidget
) return mozilla::LookAndFeel::eScrollArrowStyle_Single
;
838 return CheckWidgetStyle(aWidget
, "has-backward-stepper",
839 mozilla::LookAndFeel::eScrollArrow_StartBackward
) |
840 CheckWidgetStyle(aWidget
, "has-forward-stepper",
841 mozilla::LookAndFeel::eScrollArrow_EndForward
) |
842 CheckWidgetStyle(aWidget
, "has-secondary-backward-stepper",
843 mozilla::LookAndFeel::eScrollArrow_EndBackward
) |
844 CheckWidgetStyle(aWidget
, "has-secondary-forward-stepper",
845 mozilla::LookAndFeel::eScrollArrow_StartForward
);
848 nsresult
nsLookAndFeel::NativeGetInt(IntID aID
, int32_t& aResult
) {
849 nsresult res
= NS_OK
;
851 // We use delayed initialization by EnsureInit() here
852 // to make sure mozilla::Preferences is available (Bug 115807).
853 // IntID::UseAccessibilityTheme is requested before user preferences
854 // are read, and so EnsureInit(), which depends on preference values,
855 // is deliberately delayed until required.
857 case IntID::ScrollButtonLeftMouseButtonAction
:
860 case IntID::ScrollButtonMiddleMouseButtonAction
:
863 case IntID::ScrollButtonRightMouseButtonAction
:
866 case IntID::CaretBlinkTime
:
868 aResult
= mCaretBlinkTime
;
870 case IntID::CaretBlinkCount
:
872 aResult
= mCaretBlinkCount
;
874 case IntID::CaretWidth
:
877 case IntID::SelectTextfieldsOnKeyFocus
: {
878 GtkSettings
* settings
;
879 gboolean select_on_focus
;
881 settings
= gtk_settings_get_default();
882 g_object_get(settings
, "gtk-entry-select-on-focus", &select_on_focus
,
891 case IntID::ScrollToClick
: {
892 GtkSettings
* settings
;
893 gboolean warps_slider
= FALSE
;
895 settings
= gtk_settings_get_default();
896 if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings
),
897 "gtk-primary-button-warps-slider")) {
898 g_object_get(settings
, "gtk-primary-button-warps-slider", &warps_slider
,
907 case IntID::SubmenuDelay
: {
908 GtkSettings
* settings
;
911 settings
= gtk_settings_get_default();
912 g_object_get(settings
, "gtk-menu-popup-delay", &delay
, nullptr);
913 aResult
= (int32_t)delay
;
916 case IntID::MenusCanOverlapOSBar
:
919 case IntID::SkipNavigatingDisabledMenuItem
:
922 case IntID::DragThresholdX
:
923 case IntID::DragThresholdY
: {
925 g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold",
926 &threshold
, nullptr);
930 case IntID::ScrollArrowStyle
: {
931 GtkWidget
* scrollbar
= GetWidget(MOZ_GTK_SCROLLBAR_VERTICAL
);
932 aResult
= ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar
);
935 case IntID::TreeOpenDelay
:
938 case IntID::TreeCloseDelay
:
941 case IntID::TreeLazyScrollDelay
:
944 case IntID::TreeScrollDelay
:
947 case IntID::TreeScrollLinesMax
:
950 case IntID::AlertNotificationOrigin
:
951 aResult
= NS_ALERT_TOP
;
953 case IntID::IMERawInputUnderlineStyle
:
954 case IntID::IMEConvertedTextUnderlineStyle
:
955 aResult
= static_cast<int32_t>(StyleTextDecorationStyle::Solid
);
957 case IntID::IMESelectedRawTextUnderlineStyle
:
958 case IntID::IMESelectedConvertedTextUnderline
:
959 aResult
= static_cast<int32_t>(StyleTextDecorationStyle::None
);
961 case IntID::SpellCheckerUnderlineStyle
:
962 aResult
= static_cast<int32_t>(StyleTextDecorationStyle::Wavy
);
964 case IntID::MenuBarDrag
:
966 aResult
= mSystemTheme
.mMenuSupportsDrag
;
968 case IntID::ScrollbarButtonAutoRepeatBehavior
:
971 case IntID::SwipeAnimationEnabled
:
974 case IntID::ContextMenuOffsetVertical
:
975 case IntID::ContextMenuOffsetHorizontal
:
978 case IntID::GTKCSDAvailable
:
979 aResult
= sCSDAvailable
;
981 case IntID::GTKCSDMaximizeButton
:
983 aResult
= mCSDMaximizeButton
;
985 case IntID::GTKCSDMinimizeButton
:
987 aResult
= mCSDMinimizeButton
;
989 case IntID::GTKCSDCloseButton
:
991 aResult
= mCSDCloseButton
;
993 case IntID::GTKCSDReversedPlacement
:
995 aResult
= mCSDReversedPlacement
;
997 case IntID::PrefersReducedMotion
: {
999 aResult
= mPrefersReducedMotion
;
1002 case IntID::SystemUsesDarkTheme
: {
1004 if (mColorSchemePreference
) {
1005 aResult
= *mColorSchemePreference
== ColorScheme::Dark
;
1007 aResult
= mSystemTheme
.mIsDark
;
1011 case IntID::GTKCSDMaximizeButtonPosition
:
1012 aResult
= mCSDMaximizeButtonPosition
;
1014 case IntID::GTKCSDMinimizeButtonPosition
:
1015 aResult
= mCSDMinimizeButtonPosition
;
1017 case IntID::GTKCSDCloseButtonPosition
:
1018 aResult
= mCSDCloseButtonPosition
;
1020 case IntID::GTKThemeFamily
: {
1022 aResult
= int32_t(EffectiveTheme().mFamily
);
1025 case IntID::UseAccessibilityTheme
:
1026 // If high contrast is enabled, enable prefers-reduced-transparency media
1027 // query as well as there is no dedicated option.
1028 case IntID::PrefersReducedTransparency
:
1030 aResult
= mSystemTheme
.mHighContrast
;
1032 case IntID::InvertedColors
:
1033 // No GTK API for checking if inverted colors is enabled
1036 case IntID::TitlebarRadius
: {
1038 aResult
= EffectiveTheme().mTitlebarRadius
;
1041 case IntID::TitlebarButtonSpacing
: {
1043 aResult
= EffectiveTheme().mTitlebarButtonSpacing
;
1046 case IntID::AllowOverlayScrollbarsOverlap
: {
1050 case IntID::ScrollbarFadeBeginDelay
: {
1054 case IntID::ScrollbarFadeDuration
: {
1058 case IntID::ScrollbarDisplayOnMouseMove
: {
1062 case IntID::PanelAnimations
:
1063 aResult
= [&]() -> bool {
1064 if (!sCSDAvailable
) {
1065 // Disabled on systems without CSD, see bug 1385079.
1068 if (GdkIsWaylandDisplay()) {
1069 // Disabled on wayland, see bug 1800442 and bug 1800368.
1075 case IntID::UseOverlayScrollbars
: {
1076 aResult
= StaticPrefs::widget_gtk_overlay_scrollbars_enabled();
1079 case IntID::HideCursorWhileTyping
: {
1080 aResult
= StaticPrefs::widget_gtk_hide_pointer_while_typing_enabled();
1083 case IntID::TouchDeviceSupportPresent
:
1084 aResult
= widget::WidgetUtilsGTK::IsTouchDeviceSupportPresent();
1088 res
= NS_ERROR_FAILURE
;
1094 nsresult
nsLookAndFeel::NativeGetFloat(FloatID aID
, float& aResult
) {
1095 nsresult rv
= NS_OK
;
1097 case FloatID::IMEUnderlineRelativeSize
:
1100 case FloatID::SpellCheckerUnderlineRelativeSize
:
1103 case FloatID::CaretAspectRatio
:
1105 aResult
= mSystemTheme
.mCaretRatio
;
1107 case FloatID::TextScaleFactor
:
1108 aResult
= gfxPlatformGtk::GetFontScaleFactor();
1112 rv
= NS_ERROR_FAILURE
;
1117 static void GetSystemFontInfo(GtkStyleContext
* aStyle
, nsString
* aFontName
,
1118 gfxFontStyle
* aFontStyle
) {
1119 aFontStyle
->style
= FontSlantStyle::NORMAL
;
1122 // https://git.gnome.org/browse/gtk+/tree/gtk/gtkwidget.c?h=3.22.19#n10333
1123 PangoFontDescription
* desc
;
1124 gtk_style_context_get(aStyle
, gtk_style_context_get_state(aStyle
), "font",
1127 aFontStyle
->systemFont
= true;
1129 constexpr auto quote
= u
"\""_ns
;
1130 NS_ConvertUTF8toUTF16
family(pango_font_description_get_family(desc
));
1131 *aFontName
= quote
+ family
+ quote
;
1133 aFontStyle
->weight
=
1134 FontWeight::FromInt(pango_font_description_get_weight(desc
));
1136 // FIXME: Set aFontStyle->stretch correctly!
1137 aFontStyle
->stretch
= FontStretch::NORMAL
;
1139 float size
= float(pango_font_description_get_size(desc
)) / PANGO_SCALE
;
1141 // |size| is now either pixels or pango-points (not Mozilla-points!)
1143 if (!pango_font_description_get_size_is_absolute(desc
)) {
1144 // |size| is in pango-points, so convert to pixels.
1145 size
*= float(gfxPlatformGtk::GetFontScaleDPI()) / POINTS_PER_INCH_FLOAT
;
1148 // |size| is now pixels but not scaled for the hidpi displays,
1149 aFontStyle
->size
= size
;
1151 pango_font_description_free(desc
);
1154 bool nsLookAndFeel::NativeGetFont(FontID aID
, nsString
& aFontName
,
1155 gfxFontStyle
& aFontStyle
) {
1156 return mSystemTheme
.GetFont(aID
, aFontName
, aFontStyle
);
1159 bool nsLookAndFeel::PerThemeData::GetFont(FontID aID
, nsString
& aFontName
,
1160 gfxFontStyle
& aFontStyle
) const {
1162 case FontID::Menu
: // css2
1163 case FontID::MozPullDownMenu
: // css3
1164 aFontName
= mMenuFontName
;
1165 aFontStyle
= mMenuFontStyle
;
1168 case FontID::MozField
: // css3
1169 case FontID::MozList
: // css3
1170 aFontName
= mFieldFontName
;
1171 aFontStyle
= mFieldFontStyle
;
1174 case FontID::MozButton
: // css3
1175 aFontName
= mButtonFontName
;
1176 aFontStyle
= mButtonFontStyle
;
1179 case FontID::Caption
: // css2
1180 case FontID::Icon
: // css2
1181 case FontID::MessageBox
: // css2
1182 case FontID::SmallCaption
: // css2
1183 case FontID::StatusBar
: // css2
1185 aFontName
= mDefaultFontName
;
1186 aFontStyle
= mDefaultFontStyle
;
1190 // Convert GDK pixels to CSS pixels.
1191 // When "layout.css.devPixelsPerPx" > 0, this is not a direct conversion.
1192 // The difference produces a scaling of system fonts in proportion with
1193 // other scaling from the change in CSS pixel sizes.
1194 aFontStyle
.size
/= LookAndFeel::GetTextScaleFactor();
1198 static nsCString
GetGtkSettingsStringKey(const char* aKey
) {
1199 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
1201 GtkSettings
* settings
= gtk_settings_get_default();
1202 char* value
= nullptr;
1203 g_object_get(settings
, aKey
, &value
, nullptr);
1211 static nsCString
GetGtkTheme() {
1212 return GetGtkSettingsStringKey("gtk-theme-name");
1215 static bool GetPreferDarkTheme() {
1216 GtkSettings
* settings
= gtk_settings_get_default();
1217 gboolean preferDarkTheme
= FALSE
;
1218 g_object_get(settings
, "gtk-application-prefer-dark-theme", &preferDarkTheme
,
1220 return preferDarkTheme
== TRUE
;
1223 // It seems GTK doesn't have an API to query if the current theme is "light" or
1224 // "dark", so we synthesize it from the CSS2 Window/WindowText colors instead,
1225 // by comparing their luminosity.
1226 static bool GetThemeIsDark() {
1228 GtkStyleContext
* style
= GetStyleContext(MOZ_GTK_WINDOW
);
1229 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &bg
);
1230 gtk_style_context_get_color(style
, GTK_STATE_FLAG_NORMAL
, &fg
);
1231 return RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(bg
)) <
1232 RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(fg
));
1235 void nsLookAndFeel::RestoreSystemTheme() {
1236 LOGLNF("RestoreSystemTheme(%s, %d, %d)\n", mSystemTheme
.mName
.get(),
1237 mSystemTheme
.mPreferDarkTheme
, mSystemThemeOverridden
);
1239 if (!mSystemThemeOverridden
) {
1243 // Available on Gtk 3.20+.
1244 static auto sGtkSettingsResetProperty
=
1245 (void (*)(GtkSettings
*, const gchar
*))dlsym(
1246 RTLD_DEFAULT
, "gtk_settings_reset_property");
1248 GtkSettings
* settings
= gtk_settings_get_default();
1249 if (sGtkSettingsResetProperty
) {
1250 sGtkSettingsResetProperty(settings
, "gtk-theme-name");
1251 sGtkSettingsResetProperty(settings
, "gtk-application-prefer-dark-theme");
1253 g_object_set(settings
, "gtk-theme-name", mSystemTheme
.mName
.get(),
1254 "gtk-application-prefer-dark-theme",
1255 mSystemTheme
.mPreferDarkTheme
, nullptr);
1257 mSystemThemeOverridden
= false;
1258 UpdateRoundedBottomCornerStyles();
1262 static bool AnyColorChannelIsDifferent(nscolor aColor
) {
1263 return NS_GET_R(aColor
) != NS_GET_G(aColor
) ||
1264 NS_GET_R(aColor
) != NS_GET_B(aColor
);
1267 bool nsLookAndFeel::ConfigureAltTheme() {
1268 GtkSettings
* settings
= gtk_settings_get_default();
1269 // Toggling gtk-application-prefer-dark-theme is not enough generally to
1270 // switch from dark to light theme. If the theme didn't change, and we have
1271 // a dark theme, try to first remove -Dark{,er,est} from the theme name to
1272 // find the light variant.
1273 if (mSystemTheme
.mIsDark
) {
1274 nsCString potentialLightThemeName
= mSystemTheme
.mName
;
1276 constexpr nsLiteralCString kSubstringsToRemove
[] = {
1277 "-darkest"_ns
, "-darker"_ns
, "-dark"_ns
,
1278 "-Darkest"_ns
, "-Darker"_ns
, "-Dark"_ns
,
1279 "_darkest"_ns
, "_darker"_ns
, "_dark"_ns
,
1280 "_Darkest"_ns
, "_Darker"_ns
, "_Dark"_ns
,
1284 for (const auto& s
: kSubstringsToRemove
) {
1285 potentialLightThemeName
= mSystemTheme
.mName
;
1286 potentialLightThemeName
.ReplaceSubstring(s
, ""_ns
);
1287 if (potentialLightThemeName
.Length() != mSystemTheme
.mName
.Length()) {
1293 LOGLNF(" found potential light variant of %s: %s",
1294 mSystemTheme
.mName
.get(), potentialLightThemeName
.get());
1295 g_object_set(settings
, "gtk-theme-name", potentialLightThemeName
.get(),
1296 "gtk-application-prefer-dark-theme", !mSystemTheme
.mIsDark
,
1300 if (!GetThemeIsDark()) {
1301 return true; // Success!
1306 LOGLNF(" toggling gtk-application-prefer-dark-theme");
1307 g_object_set(settings
, "gtk-application-prefer-dark-theme",
1308 !mSystemTheme
.mIsDark
, nullptr);
1310 if (mSystemTheme
.mIsDark
!= GetThemeIsDark()) {
1311 return true; // Success!
1314 LOGLNF(" didn't work, falling back to default theme");
1315 // If the theme still didn't change enough, fall back to Adwaita with the
1316 // appropriate color preference.
1317 g_object_set(settings
, "gtk-theme-name", "Adwaita",
1318 "gtk-application-prefer-dark-theme", !mSystemTheme
.mIsDark
,
1322 // If it _still_ didn't change enough, and we're looking for a dark theme,
1323 // try to set Adwaita-dark as a theme name. This might be needed in older GTK
1325 if (!mSystemTheme
.mIsDark
&& !GetThemeIsDark()) {
1326 LOGLNF(" last resort Adwaita-dark fallback");
1327 g_object_set(settings
, "gtk-theme-name", "Adwaita-dark", nullptr);
1334 // We override some adwaita colors from GTK3 to LibAdwaita, see:
1335 // https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/named-colors.html
1336 void nsLookAndFeel::MaybeApplyAdwaitaOverrides() {
1337 auto& dark
= mSystemTheme
.mIsDark
? mSystemTheme
: mAltTheme
;
1338 auto& light
= mSystemTheme
.mIsDark
? mAltTheme
: mSystemTheme
;
1340 // Unconditional special case for Adwaita-dark: In GTK3 we don't have more
1341 // proper accent colors, so we use the selected background colors. Those
1342 // colors, however, don't have much contrast in dark mode (see bug 1741293).
1343 if (dark
.mFamily
== ThemeFamily::Adwaita
) {
1344 dark
.mAccent
= {NS_RGB(0x35, 0x84, 0xe4), NS_RGB(0xff, 0xff, 0xff)};
1345 dark
.mSelectedText
= dark
.mAccent
;
1348 if (light
.mFamily
== ThemeFamily::Adwaita
) {
1349 light
.mAccent
= {NS_RGB(0x35, 0x84, 0xe4), NS_RGB(0xff, 0xff, 0xff)};
1350 light
.mSelectedText
= light
.mAccent
;
1353 if (!StaticPrefs::widget_gtk_libadwaita_colors_enabled()) {
1357 if (light
.mFamily
== ThemeFamily::Adwaita
) {
1358 // #323232 is rgba(0,0,0,.8) over #fafafa.
1360 light
.mDialog
= {NS_RGB(0xfa, 0xfa, 0xfa), NS_RGB(0x32, 0x32, 0x32)};
1361 light
.mField
= {NS_RGB(0xff, 0xff, 0xff), NS_RGB(0x32, 0x32, 0x32)};
1363 // We use the sidebar colors for the headerbar in light mode background
1364 // because it creates much better contrast. GTK headerbar colors are white,
1365 // and meant to "blend" with the contents otherwise.
1366 // #2f2f2f is rgba(0,0,0,.8) over #ebebeb.
1367 light
.mSidebar
= light
.mHeaderBar
=
1368 light
.mTitlebar
= {NS_RGB(0xeb, 0xeb, 0xeb), NS_RGB(0x2f, 0x2f, 0x2f)};
1369 light
.mHeaderBarInactive
= light
.mTitlebarInactive
= {
1370 NS_RGB(0xf2, 0xf2, 0xf2), NS_RGB(0x2f, 0x2f, 0x2f)};
1371 light
.mThreeDShadow
= NS_RGB(0xe0, 0xe0, 0xe0);
1372 light
.mSidebarBorder
= NS_RGBA(0, 0, 0, 18);
1375 if (dark
.mFamily
== ThemeFamily::Adwaita
) {
1376 dark
.mWindow
= {NS_RGB(0x24, 0x24, 0x24), NS_RGB(0xff, 0xff, 0xff)};
1377 dark
.mDialog
= {NS_RGB(0x38, 0x38, 0x38), NS_RGB(0xff, 0xff, 0xff)};
1378 dark
.mField
= {NS_RGB(0x3a, 0x3a, 0x3a), NS_RGB(0xff, 0xff, 0xff)};
1379 dark
.mSidebar
= dark
.mHeaderBar
=
1380 dark
.mTitlebar
= {NS_RGB(0x30, 0x30, 0x30), NS_RGB(0xff, 0xff, 0xff)};
1381 dark
.mHeaderBarInactive
= dark
.mTitlebarInactive
= {
1382 NS_RGB(0x24, 0x24, 0x24), NS_RGB(0xff, 0xff, 0xff)};
1383 // headerbar_shade_color
1384 dark
.mThreeDShadow
= NS_RGB(0x1f, 0x1f, 0x1f);
1385 dark
.mSidebarBorder
= NS_RGBA(0, 0, 0, 92);
1389 void nsLookAndFeel::ConfigureAndInitializeAltTheme() {
1390 const bool fellBackToDefaultTheme
= !ConfigureAltTheme();
1394 MaybeApplyAdwaitaOverrides();
1396 // Some of the alt theme colors we can grab from the system theme, if we fell
1397 // back to the default light / dark themes.
1398 if (fellBackToDefaultTheme
) {
1399 if (StaticPrefs::widget_gtk_alt_theme_selection()) {
1400 mAltTheme
.mSelectedText
= mSystemTheme
.mSelectedText
;
1403 if (StaticPrefs::widget_gtk_alt_theme_scrollbar_active() &&
1404 (!mAltTheme
.mIsDark
|| ShouldUseColorForActiveDarkScrollbarThumb(
1405 mSystemTheme
.mThemedScrollbarThumbActive
))) {
1406 mAltTheme
.mThemedScrollbarThumbActive
=
1407 mSystemTheme
.mThemedScrollbarThumbActive
;
1410 if (StaticPrefs::widget_gtk_alt_theme_accent()) {
1411 mAltTheme
.mAccent
= mSystemTheme
.mAccent
;
1415 // Right now we're using the opposite color-scheme theme, make sure to record
1417 mSystemThemeOverridden
= true;
1418 UpdateRoundedBottomCornerStyles();
1421 void nsLookAndFeel::ClearRoundedCornerProvider() {
1422 if (mRoundedCornerProvider
) {
1423 gtk_style_context_remove_provider_for_screen(
1424 gdk_screen_get_default(),
1425 GTK_STYLE_PROVIDER(mRoundedCornerProvider
.get()));
1426 mRoundedCornerProvider
= nullptr;
1430 void nsLookAndFeel::UpdateRoundedBottomCornerStyles() {
1431 ClearRoundedCornerProvider();
1432 if (!StaticPrefs::widget_gtk_rounded_bottom_corners_enabled()) {
1435 int32_t radius
= EffectiveTheme().mTitlebarRadius
;
1439 mRoundedCornerProvider
= dont_AddRef(gtk_css_provider_new());
1440 nsPrintfCString
string(
1441 "window.csd decoration {"
1442 "border-bottom-right-radius: %dpx;"
1443 "border-bottom-left-radius: %dpx;"
1446 GUniquePtr
<GError
> error
;
1447 if (!gtk_css_provider_load_from_data(mRoundedCornerProvider
.get(),
1448 string
.get(), string
.Length(),
1449 getter_Transfers(error
))) {
1450 NS_WARNING(nsPrintfCString("Failed to load provider: %s - %s\n",
1451 string
.get(), error
? error
->message
: nullptr)
1454 gtk_style_context_add_provider_for_screen(
1455 gdk_screen_get_default(),
1456 GTK_STYLE_PROVIDER(mRoundedCornerProvider
.get()),
1457 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
1460 Maybe
<ColorScheme
> nsLookAndFeel::ComputeColorSchemeSetting() {
1462 // Check the pref explicitly here. Usually this shouldn't be needed, but
1463 // since we can only load one GTK theme at a time, and the pref will
1464 // override the effective value that the rest of gecko assumes for the
1465 // "system" color scheme, we need to factor it in our GTK theme decisions.
1467 if (NS_SUCCEEDED(Preferences::GetInt("ui.systemUsesDarkTheme", &pref
))) {
1468 return Some(pref
? ColorScheme::Dark
: ColorScheme::Light
);
1472 if (!mDBusSettingsProxy
) {
1475 GUniquePtr
<GError
> error
;
1476 RefPtr
<GVariant
> variant
= dont_AddRef(g_dbus_proxy_call_sync(
1477 mDBusSettingsProxy
, "Read",
1478 g_variant_new("(ss)", "org.freedesktop.appearance", "color-scheme"),
1479 G_DBUS_CALL_FLAGS_NONE
,
1480 StaticPrefs::widget_gtk_settings_portal_timeout_ms(), nullptr,
1481 getter_Transfers(error
)));
1483 LOGLNF("color-scheme query error: %s\n", error
->message
);
1486 LOGLNF("color-scheme query result: %s\n", GVariantToString(variant
).get());
1487 variant
= dont_AddRef(g_variant_get_child_value(variant
, 0));
1488 while (variant
&& g_variant_is_of_type(variant
, G_VARIANT_TYPE_VARIANT
)) {
1489 // Unbox the return value.
1490 variant
= dont_AddRef(g_variant_get_variant(variant
));
1492 if (!variant
|| !g_variant_is_of_type(variant
, G_VARIANT_TYPE_UINT32
)) {
1493 MOZ_ASSERT(false, "Unexpected color-scheme query return value");
1496 switch (g_variant_get_uint32(variant
)) {
1498 MOZ_FALLTHROUGH_ASSERT("Unexpected color-scheme query return value");
1502 return Some(ColorScheme::Dark
);
1504 return Some(ColorScheme::Light
);
1509 void nsLookAndFeel::Initialize() {
1510 LOGLNF("nsLookAndFeel::Initialize");
1511 MOZ_DIAGNOSTIC_ASSERT(!mInitialized
);
1512 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
1513 "LookAndFeel init should be done on the main thread");
1515 mInitialized
= true;
1517 GtkSettings
* settings
= gtk_settings_get_default();
1518 if (MOZ_UNLIKELY(!settings
)) {
1519 NS_WARNING("EnsureInit: No settings");
1523 AutoRestore
<bool> restoreIgnoreSettings(sIgnoreChangedSettings
);
1524 sIgnoreChangedSettings
= true;
1526 // Our current theme may be different from the system theme if we're matching
1527 // the Firefox theme or using the alt theme intentionally due to the
1528 // color-scheme preference. Make sure to restore the original system theme.
1529 RestoreSystemTheme();
1531 // First initialize global settings.
1532 InitializeGlobalSettings();
1534 // Record our system theme settings now.
1535 mSystemTheme
.Init();
1537 // Find the alternative-scheme theme (light if the system theme is dark, or
1538 // vice versa), configure it and initialize it.
1539 ConfigureAndInitializeAltTheme();
1541 LOGLNF("System Theme: %s. Alt Theme: %s\n", mSystemTheme
.mName
.get(),
1542 mAltTheme
.mName
.get());
1544 // Go back to the system theme or keep the alt theme configured, depending on
1545 // Firefox theme or user color-scheme preference.
1546 ConfigureFinalEffectiveTheme();
1551 void nsLookAndFeel::OnColorSchemeSettingChanged() {
1552 if (NS_WARN_IF(mColorSchemePreference
== ComputeColorSchemeSetting())) {
1553 // We sometimes get duplicate color-scheme changes from dbus, avoid doing
1554 // extra work if not needed.
1560 void nsLookAndFeel::InitializeGlobalSettings() {
1561 GtkSettings
* settings
= gtk_settings_get_default();
1563 mColorSchemePreference
= ComputeColorSchemeSetting();
1565 gboolean enableAnimations
= false;
1566 g_object_get(settings
, "gtk-enable-animations", &enableAnimations
, nullptr);
1567 mPrefersReducedMotion
= !enableAnimations
;
1569 gint blink_time
= 0; // In milliseconds
1570 gint blink_timeout
= 0; // in seconds
1572 g_object_get(settings
, "gtk-cursor-blink-time", &blink_time
,
1573 "gtk-cursor-blink-timeout", &blink_timeout
, "gtk-cursor-blink",
1576 // https://docs.gtk.org/gtk3/property.Settings.gtk-cursor-blink-timeout.html:
1578 // Setting this to zero has the same effect as setting
1579 // GtkSettings:gtk-cursor-blink to FALSE.
1581 mCaretBlinkTime
= blink
&& blink_timeout
? (int32_t)blink_time
: 0;
1583 if (mCaretBlinkTime
) {
1584 // blink_time * 2 because blink count is a full blink cycle.
1586 std::max(1, int32_t(std::ceil(float(blink_timeout
* 1000) /
1587 (float(blink_time
) * 2.0f
))));
1589 mCaretBlinkCount
= -1;
1592 mCSDCloseButton
= false;
1593 mCSDMinimizeButton
= false;
1594 mCSDMaximizeButton
= false;
1595 mCSDCloseButtonPosition
= 0;
1596 mCSDMinimizeButtonPosition
= 0;
1597 mCSDMaximizeButtonPosition
= 0;
1599 // We need to initialize whole CSD config explicitly because it's queried
1600 // as -moz-gtk* media features.
1601 ButtonLayout buttonLayout
[TOOLBAR_BUTTONS
];
1603 size_t activeButtons
=
1604 GetGtkHeaderBarButtonLayout(Span(buttonLayout
), &mCSDReversedPlacement
);
1605 for (size_t i
= 0; i
< activeButtons
; i
++) {
1606 // We check if a button is represented on the right side of the tabbar.
1607 // Then we assign it a value from 3 to 5, instead of 0 to 2 when it is on
1609 const ButtonLayout
& layout
= buttonLayout
[i
];
1610 int32_t* pos
= nullptr;
1611 switch (layout
.mType
) {
1612 case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE
:
1613 mCSDMinimizeButton
= true;
1614 pos
= &mCSDMinimizeButtonPosition
;
1616 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
:
1617 mCSDMaximizeButton
= true;
1618 pos
= &mCSDMaximizeButtonPosition
;
1620 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE
:
1621 mCSDCloseButton
= true;
1622 pos
= &mCSDCloseButtonPosition
;
1633 struct actionMapping
{
1634 TitlebarAction action
;
1636 } ActionMapping
[] = {
1637 {TitlebarAction::None
, "none"},
1638 {TitlebarAction::WindowLower
, "lower"},
1639 {TitlebarAction::WindowMenu
, "menu"},
1640 {TitlebarAction::WindowMinimize
, "minimize"},
1641 {TitlebarAction::WindowMaximize
, "maximize"},
1642 {TitlebarAction::WindowMaximizeToggle
, "toggle-maximize"},
1645 auto GetWindowAction
= [&](const char* eventName
) -> TitlebarAction
{
1646 gchar
* action
= nullptr;
1647 g_object_get(settings
, eventName
, &action
, nullptr);
1649 return TitlebarAction::None
;
1651 auto free
= mozilla::MakeScopeExit([&] { g_free(action
); });
1652 for (auto const& mapping
: ActionMapping
) {
1653 if (!strncmp(action
, mapping
.name
, strlen(mapping
.name
))) {
1654 return mapping
.action
;
1657 return TitlebarAction::None
;
1660 mDoubleClickAction
= GetWindowAction("gtk-titlebar-double-click");
1661 mMiddleClickAction
= GetWindowAction("gtk-titlebar-middle-click");
1664 void nsLookAndFeel::ConfigureFinalEffectiveTheme() {
1665 MOZ_ASSERT(mSystemThemeOverridden
,
1666 "By this point, the alt theme should be configured");
1667 const bool shouldUseSystemTheme
= [&] {
1668 using ChromeSetting
= PreferenceSheet::ChromeColorSchemeSetting
;
1669 // NOTE: We can't call ColorSchemeForChrome directly because this might run
1670 // while we're computing it.
1671 switch (PreferenceSheet::ColorSchemeSettingForChrome()) {
1672 case ChromeSetting::Light
:
1673 return !mSystemTheme
.mIsDark
;
1674 case ChromeSetting::Dark
:
1675 return mSystemTheme
.mIsDark
;
1676 case ChromeSetting::System
:
1679 if (!mColorSchemePreference
) {
1682 const bool preferenceIsDark
= *mColorSchemePreference
== ColorScheme::Dark
;
1683 return preferenceIsDark
== mSystemTheme
.mIsDark
;
1686 const bool usingSystem
= !mSystemThemeOverridden
;
1687 LOGLNF("OverrideSystemThemeIfNeeded(matchesSystem=%d, usingSystem=%d)\n",
1688 shouldUseSystemTheme
, usingSystem
);
1690 if (shouldUseSystemTheme
) {
1691 RestoreSystemTheme();
1692 } else if (usingSystem
) {
1693 LOGLNF("Setting theme %s, %d\n", mAltTheme
.mName
.get(),
1694 mAltTheme
.mPreferDarkTheme
);
1696 GtkSettings
* settings
= gtk_settings_get_default();
1697 if (mSystemTheme
.mName
== mAltTheme
.mName
) {
1698 // Prefer setting only gtk-application-prefer-dark-theme, so we can still
1699 // get notified from notify::gtk-theme-name if the user changes the theme.
1700 g_object_set(settings
, "gtk-application-prefer-dark-theme",
1701 mAltTheme
.mPreferDarkTheme
, nullptr);
1703 g_object_set(settings
, "gtk-theme-name", mAltTheme
.mName
.get(),
1704 "gtk-application-prefer-dark-theme",
1705 mAltTheme
.mPreferDarkTheme
, nullptr);
1707 mSystemThemeOverridden
= true;
1708 UpdateRoundedBottomCornerStyles();
1713 static bool GetColorFromBackgroundImage(GtkStyleContext
* aStyle
,
1714 nscolor aForForegroundColor
,
1715 GtkStateFlags aState
, nscolor
* aColor
) {
1716 GValue value
= G_VALUE_INIT
;
1717 gtk_style_context_get_property(aStyle
, "background-image", aState
, &value
);
1718 auto cleanup
= MakeScopeExit([&] { g_value_unset(&value
); });
1719 if (GetColorFromImagePattern(&value
, aColor
)) {
1724 GdkRGBA light
, dark
;
1725 if (GetGradientColors(&value
, &light
, &dark
)) {
1726 nscolor l
= GDK_RGBA_TO_NS_RGBA(light
);
1727 nscolor d
= GDK_RGBA_TO_NS_RGBA(dark
);
1728 // Return the one with more contrast.
1729 // TODO(emilio): This could do interpolation or what not but seems
1731 if (NS_LUMINOSITY_DIFFERENCE(l
, aForForegroundColor
) >
1732 NS_LUMINOSITY_DIFFERENCE(d
, aForForegroundColor
)) {
1744 static nscolor
GetBackgroundColor(
1745 GtkStyleContext
* aStyle
, nscolor aForForegroundColor
,
1746 GtkStateFlags aState
= GTK_STATE_FLAG_NORMAL
,
1747 nscolor aOverBackgroundColor
= NS_TRANSPARENT
) {
1748 // Try to synthesize a color from a background-image.
1749 nscolor imageColor
= NS_TRANSPARENT
;
1750 if (GetColorFromBackgroundImage(aStyle
, aForForegroundColor
, aState
,
1752 if (NS_GET_A(imageColor
) == 255) {
1758 gtk_style_context_get_background_color(aStyle
, aState
, &gdkColor
);
1759 nscolor bgColor
= GDK_RGBA_TO_NS_RGBA(gdkColor
);
1760 // background-image paints over background-color.
1761 const nscolor finalColor
= NS_ComposeColors(bgColor
, imageColor
);
1762 if (finalColor
!= aOverBackgroundColor
) {
1765 return NS_TRANSPARENT
;
1768 static nscolor
GetTextColor(GtkStyleContext
* aStyle
,
1769 GtkStateFlags aState
= GTK_STATE_FLAG_NORMAL
) {
1771 gtk_style_context_get_color(aStyle
, aState
, &color
);
1772 return GDK_RGBA_TO_NS_RGBA(color
);
1775 using ColorPair
= nsLookAndFeel::ColorPair
;
1776 static ColorPair
GetColorPair(GtkStyleContext
* aStyle
,
1777 GtkStateFlags aState
= GTK_STATE_FLAG_NORMAL
) {
1779 result
.mFg
= GetTextColor(aStyle
, aState
);
1780 result
.mBg
= GetBackgroundColor(aStyle
, result
.mFg
, aState
);
1784 static bool GetNamedColorPair(GtkStyleContext
* aStyle
, const char* aBgName
,
1785 const char* aFgName
, ColorPair
* aPair
) {
1787 if (!gtk_style_context_lookup_color(aStyle
, aBgName
, &bg
) ||
1788 !gtk_style_context_lookup_color(aStyle
, aFgName
, &fg
)) {
1792 aPair
->mBg
= GDK_RGBA_TO_NS_RGBA(bg
);
1793 aPair
->mFg
= GDK_RGBA_TO_NS_RGBA(fg
);
1795 // If the colors are semi-transparent and the theme provides a
1796 // background color, blend with them to get the "final" color, see
1798 if (NS_GET_A(aPair
->mBg
) != 255 &&
1799 (gtk_style_context_lookup_color(aStyle
, "bg_color", &bg
) ||
1800 gtk_style_context_lookup_color(aStyle
, "theme_bg_color", &bg
))) {
1801 aPair
->mBg
= NS_ComposeColors(GDK_RGBA_TO_NS_RGBA(bg
), aPair
->mBg
);
1804 // A semi-transparent foreground color would be kinda silly, but is done
1806 if (NS_GET_A(aPair
->mFg
) != 255) {
1807 aPair
->mFg
= NS_ComposeColors(aPair
->mBg
, aPair
->mFg
);
1813 static void EnsureColorPairIsOpaque(ColorPair
& aPair
) {
1814 // Blend with white, ensuring the color is opaque, so that the UI doesn't have
1815 // to care about alpha.
1816 aPair
.mBg
= NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), aPair
.mBg
);
1817 aPair
.mFg
= NS_ComposeColors(aPair
.mBg
, aPair
.mFg
);
1820 static void PreferDarkerBackground(ColorPair
& aPair
) {
1821 // We use the darker one unless the foreground isn't really a color (is all
1822 // white / black / gray) and the background is, in which case we stick to what
1824 if (RelativeLuminanceUtils::Compute(aPair
.mBg
) >
1825 RelativeLuminanceUtils::Compute(aPair
.mFg
) &&
1826 (AnyColorChannelIsDifferent(aPair
.mFg
) ||
1827 !AnyColorChannelIsDifferent(aPair
.mBg
))) {
1828 std::swap(aPair
.mBg
, aPair
.mFg
);
1832 void nsLookAndFeel::PerThemeData::Init() {
1833 mName
= GetGtkTheme();
1836 if (mName
.EqualsLiteral("Adwaita") || mName
.EqualsLiteral("Adwaita-dark")) {
1837 return ThemeFamily::Adwaita
;
1839 if (mName
.EqualsLiteral("Breeze") || mName
.EqualsLiteral("Breeze-Dark")) {
1840 return ThemeFamily::Breeze
;
1842 if (StringBeginsWith(mName
, "Yaru"_ns
)) {
1843 return ThemeFamily::Yaru
;
1845 return ThemeFamily::Unknown
;
1848 GtkStyleContext
* style
;
1850 mHighContrast
= StaticPrefs::widget_content_gtk_high_contrast_enabled() &&
1851 mName
.Find("HighContrast"_ns
) >= 0;
1853 mPreferDarkTheme
= GetPreferDarkTheme();
1855 mIsDark
= GetThemeIsDark();
1858 // Some themes style the <trough>, while others style the <scrollbar>
1859 // itself, so we look at both and compose the colors.
1860 style
= GetStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL
);
1861 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
1862 mThemedScrollbar
= GDK_RGBA_TO_NS_RGBA(color
);
1863 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_BACKDROP
,
1865 mThemedScrollbarInactive
= GDK_RGBA_TO_NS_RGBA(color
);
1867 style
= GetStyleContext(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
);
1868 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
1870 NS_ComposeColors(mThemedScrollbar
, GDK_RGBA_TO_NS_RGBA(color
));
1871 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_BACKDROP
,
1873 mThemedScrollbarInactive
=
1874 NS_ComposeColors(mThemedScrollbarInactive
, GDK_RGBA_TO_NS_RGBA(color
));
1876 style
= GetStyleContext(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL
);
1877 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
1878 mThemedScrollbarThumb
= GDK_RGBA_TO_NS_RGBA(color
);
1879 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_PRELIGHT
,
1881 mThemedScrollbarThumbHover
= GDK_RGBA_TO_NS_RGBA(color
);
1882 gtk_style_context_get_background_color(
1883 style
, GtkStateFlags(GTK_STATE_FLAG_PRELIGHT
| GTK_STATE_FLAG_ACTIVE
),
1885 mThemedScrollbarThumbActive
= GDK_RGBA_TO_NS_RGBA(color
);
1886 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_BACKDROP
,
1888 mThemedScrollbarThumbInactive
= GDK_RGBA_TO_NS_RGBA(color
);
1890 // Make sure that the thumb is visible, at least.
1891 const bool fallbackToUnthemedColors
= [&] {
1892 if (!StaticPrefs::widget_gtk_theme_scrollbar_colors_enabled()) {
1896 if (!ShouldHonorThemeScrollbarColors()) {
1899 // If any of the scrollbar thumb colors are fully transparent, fall back to
1901 if (!NS_GET_A(mThemedScrollbarThumb
) ||
1902 !NS_GET_A(mThemedScrollbarThumbHover
) ||
1903 !NS_GET_A(mThemedScrollbarThumbActive
)) {
1906 // If the thumb and track are the same color and opaque, fall back to
1907 // non-native colors as well.
1908 if (mThemedScrollbar
== mThemedScrollbarThumb
&&
1909 NS_GET_A(mThemedScrollbar
) == 0xff) {
1915 if (fallbackToUnthemedColors
) {
1917 // Taken from Adwaita-dark.
1918 mThemedScrollbar
= NS_RGB(0x31, 0x31, 0x31);
1919 mThemedScrollbarInactive
= NS_RGB(0x2d, 0x2d, 0x2d);
1920 mThemedScrollbarThumb
= NS_RGB(0xa3, 0xa4, 0xa4);
1921 mThemedScrollbarThumbInactive
= NS_RGB(0x59, 0x5a, 0x5a);
1923 // Taken from Adwaita.
1924 mThemedScrollbar
= NS_RGB(0xce, 0xce, 0xce);
1925 mThemedScrollbarInactive
= NS_RGB(0xec, 0xed, 0xef);
1926 mThemedScrollbarThumb
= NS_RGB(0x82, 0x81, 0x7e);
1927 mThemedScrollbarThumbInactive
= NS_RGB(0xce, 0xcf, 0xce);
1930 mThemedScrollbarThumbHover
= ThemeColors::AdjustUnthemedScrollbarThumbColor(
1931 mThemedScrollbarThumb
, dom::ElementState::HOVER
);
1932 mThemedScrollbarThumbActive
=
1933 ThemeColors::AdjustUnthemedScrollbarThumbColor(
1934 mThemedScrollbarThumb
, dom::ElementState::ACTIVE
);
1937 // The label is not added to a parent widget, but shared for constructing
1938 // different style contexts. The node hierarchy is constructed only on
1939 // the label style context.
1940 GtkWidget
* labelWidget
= gtk_label_new("M");
1941 g_object_ref_sink(labelWidget
);
1944 style
= GetStyleContext(MOZ_GTK_WINDOW
);
1945 mWindow
= mDialog
= GetColorPair(style
);
1947 gtk_style_context_get_border_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
1948 mMozWindowActiveBorder
= GDK_RGBA_TO_NS_RGBA(color
);
1950 gtk_style_context_get_border_color(style
, GTK_STATE_FLAG_INSENSITIVE
, &color
);
1951 mMozWindowInactiveBorder
= GDK_RGBA_TO_NS_RGBA(color
);
1953 style
= GetStyleContext(MOZ_GTK_WINDOW_CONTAINER
);
1955 GtkStyleContext
* labelStyle
= CreateStyleForWidget(labelWidget
, style
);
1956 GetSystemFontInfo(labelStyle
, &mDefaultFontName
, &mDefaultFontStyle
);
1957 g_object_unref(labelStyle
);
1960 // tooltip foreground and background
1961 style
= GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL
);
1962 mInfo
.mFg
= GetTextColor(style
);
1963 style
= GetStyleContext(MOZ_GTK_TOOLTIP
);
1964 mInfo
.mBg
= GetBackgroundColor(style
, mInfo
.mFg
);
1966 style
= GetStyleContext(MOZ_GTK_MENUITEM
);
1968 GtkStyleContext
* accelStyle
=
1969 CreateStyleForWidget(gtk_accel_label_new("M"), style
);
1971 GetSystemFontInfo(accelStyle
, &mMenuFontName
, &mMenuFontStyle
);
1973 gtk_style_context_get_color(accelStyle
, GTK_STATE_FLAG_NORMAL
, &color
);
1974 mMenu
.mFg
= GetTextColor(accelStyle
);
1975 mGrayText
= GetTextColor(accelStyle
, GTK_STATE_FLAG_INSENSITIVE
);
1976 g_object_unref(accelStyle
);
1979 const auto effectiveTitlebarStyle
=
1980 HeaderBarShouldDrawContainer(MOZ_GTK_HEADER_BAR
) ? MOZ_GTK_HEADERBAR_FIXED
1981 : MOZ_GTK_HEADER_BAR
;
1982 style
= GetStyleContext(effectiveTitlebarStyle
);
1984 mTitlebar
= GetColorPair(style
, GTK_STATE_FLAG_NORMAL
);
1985 mTitlebarInactive
= GetColorPair(style
, GTK_STATE_FLAG_BACKDROP
);
1986 mTitlebarRadius
= IsSolidCSDStyleUsed() ? 0 : GetBorderRadius(style
);
1987 mTitlebarButtonSpacing
= moz_gtk_get_titlebar_button_spacing();
1990 // We special-case the header bar color in Adwaita, Yaru and Breeze to be the
1991 // titlebar color, because it looks better and matches what apps do by
1992 // default, see bug 1838460.
1994 // We only do this in the relevant desktop environments, however, since in
1995 // other cases we don't really know if the DE's titlebars are going to match.
1997 // For breeze, additionally we read the KDE colors directly, if available,
1998 // since these are user-configurable.
2000 // For most other themes or those in unknown DEs, we use the menubar colors.
2002 // FIXME(emilio): Can we do something a bit less special-case-y?
2003 const bool shouldUseTitlebarColorsForHeaderBar
= [&] {
2004 if (mFamily
== ThemeFamily::Adwaita
|| mFamily
== ThemeFamily::Yaru
) {
2005 return IsGnomeDesktopEnvironment();
2007 if (mFamily
== ThemeFamily::Breeze
) {
2008 return IsKdeDesktopEnvironment();
2013 if (shouldUseTitlebarColorsForHeaderBar
) {
2014 mHeaderBar
= mTitlebar
;
2015 mHeaderBarInactive
= mTitlebarInactive
;
2016 if (mFamily
== ThemeFamily::Breeze
) {
2017 GetNamedColorPair(style
, "theme_header_background_breeze",
2018 "theme_header_foreground_breeze", &mHeaderBar
);
2019 GetNamedColorPair(style
, "theme_header_background_backdrop_breeze",
2020 "theme_header_foreground_backdrop_breeze",
2021 &mHeaderBarInactive
);
2024 style
= GetStyleContext(MOZ_GTK_MENUBARITEM
);
2025 mHeaderBar
.mFg
= GetTextColor(style
);
2026 mHeaderBarInactive
.mFg
= GetTextColor(style
, GTK_STATE_FLAG_BACKDROP
);
2028 style
= GetStyleContext(MOZ_GTK_MENUBAR
);
2029 mHeaderBar
.mBg
= GetBackgroundColor(style
, mHeaderBar
.mFg
);
2030 mHeaderBarInactive
.mBg
= GetBackgroundColor(style
, mHeaderBarInactive
.mFg
,
2031 GTK_STATE_FLAG_BACKDROP
);
2034 style
= GetStyleContext(MOZ_GTK_MENUPOPUP
);
2036 nscolor color
= GetBackgroundColor(style
, mMenu
.mFg
);
2037 if (NS_GET_A(color
)) {
2040 // Some themes only style menupopups with the backdrop pseudo-class. Since a
2041 // context / popup menu always seems to match that, try that before giving
2043 color
= GetBackgroundColor(style
, mMenu
.mFg
, GTK_STATE_FLAG_BACKDROP
);
2044 if (NS_GET_A(color
)) {
2047 // If we get here we couldn't figure out the right color to use. Rather than
2048 // falling back to transparent, fall back to the window background.
2050 "Couldn't find menu background color, falling back to window "
2055 style
= GetStyleContext(MOZ_GTK_MENUITEM
);
2056 gtk_style_context_get_color(style
, GTK_STATE_FLAG_PRELIGHT
, &color
);
2057 mMenuHover
.mFg
= GDK_RGBA_TO_NS_RGBA(color
);
2058 mMenuHover
.mBg
= NS_ComposeColors(
2060 GetBackgroundColor(style
, mMenu
.mFg
, GTK_STATE_FLAG_PRELIGHT
, mMenu
.mBg
));
2062 GtkWidget
* parent
= gtk_fixed_new();
2063 GtkWidget
* window
= gtk_window_new(GTK_WINDOW_POPUP
);
2064 GtkWidget
* treeView
= gtk_tree_view_new();
2065 GtkWidget
* linkButton
= gtk_link_button_new("http://example.com/");
2066 GtkWidget
* menuBar
= gtk_menu_bar_new();
2067 GtkWidget
* menuBarItem
= gtk_menu_item_new();
2068 GtkWidget
* entry
= gtk_entry_new();
2069 GtkWidget
* textView
= gtk_text_view_new();
2071 gtk_container_add(GTK_CONTAINER(parent
), treeView
);
2072 gtk_container_add(GTK_CONTAINER(parent
), linkButton
);
2073 gtk_container_add(GTK_CONTAINER(parent
), menuBar
);
2074 gtk_menu_shell_append(GTK_MENU_SHELL(menuBar
), menuBarItem
);
2075 gtk_container_add(GTK_CONTAINER(window
), parent
);
2076 gtk_container_add(GTK_CONTAINER(parent
), entry
);
2077 gtk_container_add(GTK_CONTAINER(parent
), textView
);
2081 // If the text window background is translucent, then the background of
2082 // the textview root node is visible.
2083 style
= GetStyleContext(MOZ_GTK_TEXT_VIEW
);
2084 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
,
2087 style
= GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT
);
2088 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2089 ApplyColorOver(color
, &bgColor
);
2090 mField
.mBg
= GDK_RGBA_TO_NS_RGBA(bgColor
);
2091 gtk_style_context_get_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2092 mField
.mFg
= GDK_RGBA_TO_NS_RGBA(color
);
2095 // Selected text and background
2097 GtkStyleContext
* selectionStyle
=
2098 GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT_SELECTION
);
2099 auto GrabSelectionColors
= [&](GtkStyleContext
* style
) {
2100 gtk_style_context_get_background_color(
2102 static_cast<GtkStateFlags
>(GTK_STATE_FLAG_FOCUSED
|
2103 GTK_STATE_FLAG_SELECTED
),
2105 mSelectedText
.mBg
= GDK_RGBA_TO_NS_RGBA(color
);
2106 gtk_style_context_get_color(
2108 static_cast<GtkStateFlags
>(GTK_STATE_FLAG_FOCUSED
|
2109 GTK_STATE_FLAG_SELECTED
),
2111 mSelectedText
.mFg
= GDK_RGBA_TO_NS_RGBA(color
);
2113 GrabSelectionColors(selectionStyle
);
2114 if (mSelectedText
.mBg
== mSelectedText
.mFg
) {
2115 // Some old distros/themes don't properly use the .selection style, so
2116 // fall back to the regular text view style.
2117 GrabSelectionColors(style
);
2120 // Default selected item color is the selection background / foreground
2121 // colors, but we prefer named colors, as those are more general purpose
2122 // than the actual selection style, which might e.g. be too-transparent.
2124 // NOTE(emilio): It's unclear which one of the theme_selected_* or the
2125 // selected_* pairs should we prefer, in all themes that define both that
2126 // I've found, they're always the same.
2127 if (!GetNamedColorPair(style
, "selected_bg_color", "selected_fg_color",
2129 !GetNamedColorPair(style
, "theme_selected_bg_color",
2130 "theme_selected_fg_color", &mSelectedItem
)) {
2131 mSelectedItem
= mSelectedText
;
2134 EnsureColorPairIsOpaque(mSelectedItem
);
2136 // In a similar fashion, default accent color is the selected item/text
2137 // pair, but we also prefer named colors, if available.
2139 // accent_{bg,fg}_color is not _really_ a gtk3 thing (it's a gtk4 thing),
2140 // but if gtk 3 themes want to specify these we let them, see:
2142 // https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/named-colors.html#accent-colors
2143 if (!GetNamedColorPair(style
, "accent_bg_color", "accent_fg_color",
2145 mAccent
= mSelectedItem
;
2148 EnsureColorPairIsOpaque(mAccent
);
2149 PreferDarkerBackground(mAccent
);
2152 // Button text color
2153 style
= GetStyleContext(MOZ_GTK_BUTTON
);
2155 GtkStyleContext
* labelStyle
= CreateStyleForWidget(labelWidget
, style
);
2156 GetSystemFontInfo(labelStyle
, &mButtonFontName
, &mButtonFontStyle
);
2157 g_object_unref(labelStyle
);
2160 gtk_style_context_get_border_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2161 mButtonBorder
= GDK_RGBA_TO_NS_RGBA(color
);
2162 mButton
= GetColorPair(style
);
2163 mButtonHover
= GetColorPair(style
, GTK_STATE_FLAG_PRELIGHT
);
2164 mButtonActive
= GetColorPair(style
, GTK_STATE_FLAG_ACTIVE
);
2165 if (!NS_GET_A(mButtonHover
.mBg
)) {
2166 mButtonHover
.mBg
= mWindow
.mBg
;
2168 if (!NS_GET_A(mButtonActive
.mBg
)) {
2169 mButtonActive
.mBg
= mWindow
.mBg
;
2172 // Combobox text color
2173 style
= GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA
);
2174 gtk_style_context_get_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2175 mComboBoxText
= GDK_RGBA_TO_NS_RGBA(color
);
2177 // GTK's guide to fancy odd row background colors:
2178 // 1) Check if a theme explicitly defines an odd row color
2179 // 2) If not, check if it defines an even row color, and darken it
2180 // slightly by a hardcoded value (gtkstyle.c)
2181 // 3) If neither are defined, take the base background color and
2182 // darken that by a hardcoded value
2183 style
= GetStyleContext(MOZ_GTK_TREEVIEW
);
2185 // Get odd row background color
2186 gtk_style_context_save(style
);
2187 gtk_style_context_add_region(style
, GTK_STYLE_REGION_ROW
, GTK_REGION_ODD
);
2188 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2189 mOddCellBackground
= GDK_RGBA_TO_NS_RGBA(color
);
2190 gtk_style_context_restore(style
);
2192 // Column header colors
2193 style
= GetStyleContext(MOZ_GTK_TREE_HEADER_CELL
);
2194 mMozColHeader
= GetColorPair(style
, GTK_STATE_FLAG_NORMAL
);
2195 mMozColHeaderHover
= GetColorPair(style
, GTK_STATE_FLAG_NORMAL
);
2196 mMozColHeaderActive
= GetColorPair(style
, GTK_STATE_FLAG_ACTIVE
);
2198 // Compute cell highlight colors
2199 InitCellHighlightColors();
2201 // GtkFrame has a "border" subnode on which Adwaita draws the border.
2202 // Some themes do not draw on this node but draw a border on the widget
2203 // root node, so check the root node if no border is found on the border
2205 style
= GetStyleContext(MOZ_GTK_FRAME_BORDER
);
2206 bool themeUsesColors
=
2207 GetBorderColors(style
, &mThreeDHighlight
, &mThreeDShadow
);
2208 if (!themeUsesColors
) {
2209 style
= GetStyleContext(MOZ_GTK_FRAME
);
2210 GetBorderColors(style
, &mThreeDHighlight
, &mThreeDShadow
);
2212 mSidebarBorder
= mThreeDShadow
;
2214 // Some themes have a unified menu bar, and support window dragging on it
2215 gboolean supports_menubar_drag
= FALSE
;
2216 GParamSpec
* param_spec
= gtk_widget_class_find_style_property(
2217 GTK_WIDGET_GET_CLASS(menuBar
), "window-dragging");
2219 if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec
), G_TYPE_BOOLEAN
)) {
2220 gtk_widget_style_get(menuBar
, "window-dragging", &supports_menubar_drag
,
2224 mMenuSupportsDrag
= supports_menubar_drag
;
2226 // TODO: It returns wrong color for themes which
2227 // sets link color for GtkLabel only as we query
2228 // GtkLinkButton style here.
2229 style
= gtk_widget_get_style_context(linkButton
);
2230 gtk_style_context_get_color(style
, GTK_STATE_FLAG_LINK
, &color
);
2231 mNativeHyperLinkText
= GDK_RGBA_TO_NS_RGBA(color
);
2233 gtk_style_context_get_color(style
, GTK_STATE_FLAG_VISITED
, &color
);
2234 mNativeVisitedHyperLinkText
= GDK_RGBA_TO_NS_RGBA(color
);
2236 // invisible character styles
2238 g_object_get(entry
, "invisible-char", &value
, nullptr);
2239 mInvisibleCharacter
= char16_t(value
);
2242 gtk_widget_style_get(entry
, "cursor-aspect-ratio", &mCaretRatio
, nullptr);
2244 GetSystemFontInfo(gtk_widget_get_style_context(entry
), &mFieldFontName
,
2247 gtk_widget_destroy(window
);
2248 g_object_unref(labelWidget
);
2250 if (LOGLNF_ENABLED()) {
2251 LOGLNF("Initialized theme %s (%d)\n", mName
.get(), mPreferDarkTheme
);
2252 for (auto id
: MakeEnumeratedRange(ColorID::End
)) {
2254 nsresult rv
= GetColor(id
, color
);
2255 LOGLNF(" * color %d: pref=%s success=%d value=%x\n", int(id
),
2256 GetColorPrefName(id
), NS_SUCCEEDED(rv
),
2257 NS_SUCCEEDED(rv
) ? color
: 0);
2259 LOGLNF(" * titlebar-radius: %d\n", mTitlebarRadius
);
2264 char16_t
nsLookAndFeel::GetPasswordCharacterImpl() {
2266 return mSystemTheme
.mInvisibleCharacter
;
2269 bool nsLookAndFeel::GetEchoPasswordImpl() { return false; }
2271 bool nsLookAndFeel::GetDefaultDrawInTitlebar() { return sCSDAvailable
; }
2273 nsXPLookAndFeel::TitlebarAction
nsLookAndFeel::GetTitlebarAction(
2274 TitlebarEvent aEvent
) {
2275 return aEvent
== TitlebarEvent::Double_Click
? mDoubleClickAction
2276 : mMiddleClickAction
;
2279 void nsLookAndFeel::GetThemeInfo(nsACString
& aInfo
) {
2280 aInfo
.Append(mSystemTheme
.mName
);
2281 aInfo
.Append(" / ");
2282 aInfo
.Append(mAltTheme
.mName
);
2285 bool nsLookAndFeel::WidgetUsesImage(WidgetNodeType aNodeType
) {
2286 static constexpr GtkStateFlags sFlagsToCheck
[]{
2287 GTK_STATE_FLAG_NORMAL
, GTK_STATE_FLAG_PRELIGHT
,
2288 GtkStateFlags(GTK_STATE_FLAG_PRELIGHT
| GTK_STATE_FLAG_ACTIVE
),
2289 GTK_STATE_FLAG_BACKDROP
, GTK_STATE_FLAG_INSENSITIVE
};
2291 GtkStyleContext
* style
= GetStyleContext(aNodeType
);
2293 GValue value
= G_VALUE_INIT
;
2294 for (GtkStateFlags state
: sFlagsToCheck
) {
2295 gtk_style_context_get_property(style
, "background-image", state
, &value
);
2296 bool hasPattern
= G_VALUE_TYPE(&value
) == CAIRO_GOBJECT_TYPE_PATTERN
&&
2297 g_value_get_boxed(&value
);
2298 g_value_unset(&value
);
2306 nsresult
nsLookAndFeel::GetKeyboardLayoutImpl(nsACString
& aLayout
) {
2307 if (mozilla::widget::GdkIsX11Display()) {
2308 #if defined(MOZ_X11)
2309 Display
* display
= gdk_x11_get_default_xdisplay();
2311 return NS_ERROR_NOT_AVAILABLE
;
2313 XkbDescRec
* kbdDesc
= XkbAllocKeyboard();
2315 return NS_ERROR_NOT_AVAILABLE
;
2317 auto cleanup
= MakeScopeExit([&] { XkbFreeKeyboard(kbdDesc
, 0, true); });
2320 XkbGetState(display
, XkbUseCoreKbd
, &state
);
2321 uint32_t group
= state
.group
;
2323 XkbGetNames(display
, XkbGroupNamesMask
, kbdDesc
);
2325 if (!kbdDesc
->names
|| !kbdDesc
->names
->groups
[group
]) {
2326 return NS_ERROR_NOT_AVAILABLE
;
2329 char* layout
= XGetAtomName(display
, kbdDesc
->names
->groups
[group
]);
2331 aLayout
.Assign(layout
);
2334 #if defined(MOZ_WAYLAND)
2335 struct xkb_context
* context
= xkb_context_new(XKB_CONTEXT_NO_FLAGS
);
2337 return NS_ERROR_NOT_AVAILABLE
;
2339 auto cleanupContext
= MakeScopeExit([&] { xkb_context_unref(context
); });
2341 struct xkb_keymap
* keymap
= xkb_keymap_new_from_names(
2342 context
, nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS
);
2344 return NS_ERROR_NOT_AVAILABLE
;
2346 auto cleanupKeymap
= MakeScopeExit([&] { xkb_keymap_unref(keymap
); });
2348 const char* layout
= xkb_keymap_layout_get_name(keymap
, 0);
2351 aLayout
.Assign(layout
);
2359 void nsLookAndFeel::RecordLookAndFeelSpecificTelemetry() {
2360 // Gtk version we're on.
2362 version
.AppendPrintf("%d.%d", gtk_major_version
, gtk_minor_version
);
2363 Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_GTK_VERSION
, version
);
2365 // Whether the current Gtk theme has scrollbar buttons.
2366 bool hasScrollbarButtons
=
2367 GetInt(LookAndFeel::IntID::ScrollArrowStyle
) != eScrollArrow_None
;
2368 mozilla::Telemetry::ScalarSet(
2369 mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_HAS_SCROLLBAR_BUTTONS
,
2370 hasScrollbarButtons
);
2372 // Whether the current Gtk theme uses something other than a solid color
2373 // background for scrollbar parts.
2374 bool scrollbarUsesImage
= !ShouldHonorThemeScrollbarColors();
2375 mozilla::Telemetry::ScalarSet(
2376 mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_SCROLLBAR_USES_IMAGES
,
2377 scrollbarUsesImage
);
2380 bool nsLookAndFeel::ShouldHonorThemeScrollbarColors() {
2381 // If the Gtk theme uses anything other than solid color backgrounds for Gtk
2382 // scrollbar parts, this is a good indication that painting XUL scrollbar part
2383 // elements using colors extracted from the theme won't provide good results.
2384 return !WidgetUsesImage(MOZ_GTK_SCROLLBAR_VERTICAL
) &&
2385 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL
) &&
2386 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
) &&
2387 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL
);
2391 #undef LOGLNF_ENABLED