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/glean/WidgetGtkMetrics.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 UnboxVariant(RefPtr
<GVariant
>& aVariant
) {
118 while (aVariant
&& g_variant_is_of_type(aVariant
, G_VARIANT_TYPE_VARIANT
)) {
119 // Unbox the return value.
120 aVariant
= dont_AddRef(g_variant_get_variant(aVariant
));
124 static void settings_changed_signal_cb(GDBusProxy
* proxy
, gchar
* sender_name
,
125 gchar
* signal_name
, GVariant
* parameters
,
126 gpointer user_data
) {
127 LOGLNF("Settings Change sender=%s signal=%s params=%s\n", sender_name
,
128 signal_name
, GVariantToString(parameters
).get());
129 if (strcmp(signal_name
, "SettingChanged")) {
131 nsPrintfCString("Unknown change signal for settings: %s", signal_name
)
135 RefPtr
<GVariant
> ns
= dont_AddRef(g_variant_get_child_value(parameters
, 0));
136 RefPtr
<GVariant
> key
= dont_AddRef(g_variant_get_child_value(parameters
, 1));
137 RefPtr
<GVariant
> value
=
138 dont_AddRef(g_variant_get_child_value(parameters
, 2));
139 // Third parameter is the value, but we don't care about it.
140 if (!ns
|| !key
|| !value
||
141 !g_variant_is_of_type(ns
, G_VARIANT_TYPE_STRING
) ||
142 !g_variant_is_of_type(key
, G_VARIANT_TYPE_STRING
)) {
143 MOZ_ASSERT(false, "Unexpected setting change signal parameters");
147 auto* lnf
= static_cast<nsLookAndFeel
*>(user_data
);
148 auto nsStr
= GVariantGetString(ns
);
149 if (!nsStr
.Equals("org.freedesktop.appearance"_ns
)) {
155 auto keyStr
= GVariantGetString(key
);
156 if (lnf
->RecomputeDBusAppearanceSetting(keyStr
, value
)) {
161 bool nsLookAndFeel::RecomputeDBusAppearanceSetting(const nsACString
& aKey
,
163 LOGLNF("RecomputeDBusAppearanceSetting(%s, %s)",
164 PromiseFlatCString(aKey
).get(), GVariantToString(aValue
).get());
165 if (aKey
.EqualsLiteral("contrast")) {
166 const bool old
= mDBusSettings
.mPrefersContrast
;
167 mDBusSettings
.mPrefersContrast
= g_variant_get_uint32(aValue
) == 1;
168 return mDBusSettings
.mPrefersContrast
!= old
;
170 if (aKey
.EqualsLiteral("color-scheme")) {
171 const auto old
= mDBusSettings
.mColorScheme
;
172 mDBusSettings
.mColorScheme
= [&] {
173 switch (g_variant_get_uint32(aValue
)) {
175 MOZ_FALLTHROUGH_ASSERT("Unexpected color-scheme query return value");
179 return Some(ColorScheme::Dark
);
181 return Some(ColorScheme::Light
);
183 return Maybe
<ColorScheme
>{};
185 return mDBusSettings
.mColorScheme
!= old
;
187 if (aKey
.EqualsLiteral("accent-color")) {
188 auto old
= mDBusSettings
.mAccentColor
;
189 mDBusSettings
.mAccentColor
.mBg
= mDBusSettings
.mAccentColor
.mFg
=
191 gdouble r
= -1.0, g
= -1.0, b
= -1.0;
192 g_variant_get(aValue
, "(ddd)", &r
, &g
, &b
);
193 if (r
>= 0.0f
&& g
>= 0.0f
&& b
>= 0.0f
) {
194 mDBusSettings
.mAccentColor
.mBg
= gfx::sRGBColor(r
, g
, b
, 1.0).ToABGR();
195 mDBusSettings
.mAccentColor
.mFg
=
196 ThemeColors::ComputeCustomAccentForeground(
197 mDBusSettings
.mAccentColor
.mBg
);
199 return mDBusSettings
.mAccentColor
!= old
;
204 bool nsLookAndFeel::RecomputeDBusSettings() {
205 if (!mDBusSettingsProxy
) {
209 GVariantBuilder namespacesBuilder
;
210 g_variant_builder_init(&namespacesBuilder
, G_VARIANT_TYPE("as"));
211 g_variant_builder_add(&namespacesBuilder
, "s", "org.freedesktop.appearance");
213 GUniquePtr
<GError
> error
;
214 RefPtr
<GVariant
> variant
= dont_AddRef(g_dbus_proxy_call_sync(
215 mDBusSettingsProxy
, "ReadAll", g_variant_new("(as)", &namespacesBuilder
),
216 G_DBUS_CALL_FLAGS_NONE
,
217 StaticPrefs::widget_gtk_settings_portal_timeout_ms(), nullptr,
218 getter_Transfers(error
)));
220 LOGLNF("dbus settings query error: %s\n", error
->message
);
224 LOGLNF("dbus settings query result: %s\n", GVariantToString(variant
).get());
225 variant
= dont_AddRef(g_variant_get_child_value(variant
, 0));
226 UnboxVariant(variant
);
227 LOGLNF("dbus settings query result after unbox: %s\n",
228 GVariantToString(variant
).get());
229 if (!variant
|| !g_variant_is_of_type(variant
, G_VARIANT_TYPE_DICTIONARY
)) {
230 MOZ_ASSERT(false, "Unexpected dbus settings query return value");
234 bool changed
= false;
235 // We expect one dictionary with (right now) one namespace for appearance,
236 // with another dictionary inside for the actual values.
239 GVariantIter outerIter
;
240 GVariantIter
* innerIter
;
241 g_variant_iter_init(&outerIter
, variant
);
242 while (g_variant_iter_loop(&outerIter
, "{sa{sv}}", &ns
, &innerIter
)) {
243 LOGLNF("Got namespace %s", ns
);
244 if (!strcmp(ns
, "org.freedesktop.appearance")) {
245 gchar
* appearanceKey
;
246 GVariant
* innerValue
;
247 while (g_variant_iter_loop(innerIter
, "{sv}", &appearanceKey
,
249 LOGLNF(" > %s: %s", appearanceKey
,
250 GVariantToString(innerValue
).get());
251 changed
|= RecomputeDBusAppearanceSetting(
252 nsDependentCString(appearanceKey
), innerValue
);
260 void nsLookAndFeel::WatchDBus() {
261 LOGLNF("nsLookAndFeel::WatchDBus");
262 GUniquePtr
<GError
> error
;
263 mDBusSettingsProxy
= dont_AddRef(g_dbus_proxy_new_for_bus_sync(
264 G_BUS_TYPE_SESSION
, G_DBUS_PROXY_FLAGS_NONE
, nullptr,
265 "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop",
266 "org.freedesktop.portal.Settings", nullptr, getter_Transfers(error
)));
267 if (!mDBusSettingsProxy
) {
268 LOGLNF("Can't create DBus proxy for settings: %s\n", error
->message
);
272 g_signal_connect(mDBusSettingsProxy
, "g-signal",
273 G_CALLBACK(settings_changed_signal_cb
), this);
275 // DBus interface was started after L&F init so we need to load our settings
276 // from DBus explicitly.
277 if (RecomputeDBusSettings()) {
282 void nsLookAndFeel::UnwatchDBus() {
283 if (!mDBusSettingsProxy
) {
286 LOGLNF("nsLookAndFeel::UnwatchDBus");
287 g_signal_handlers_disconnect_by_func(
288 mDBusSettingsProxy
, FuncToGpointer(settings_changed_signal_cb
), this);
289 mDBusSettingsProxy
= nullptr;
292 nsLookAndFeel::nsLookAndFeel() {
293 static constexpr nsLiteralCString kObservedSettings
[] = {
294 // Affects system font sizes.
295 "notify::gtk-xft-dpi"_ns
,
296 // Affects mSystemTheme and mAltTheme as expected.
297 "notify::gtk-theme-name"_ns
,
299 "notify::gtk-font-name"_ns
,
300 // prefers-reduced-motion
301 "notify::gtk-enable-animations"_ns
,
302 // CSD media queries, etc.
303 "notify::gtk-decoration-layout"_ns
,
304 // Text resolution affects system font and widget sizes.
305 "notify::resolution"_ns
,
306 // These three Affect mCaretBlinkTime
307 "notify::gtk-cursor-blink"_ns
,
308 "notify::gtk-cursor-blink-time"_ns
,
309 "notify::gtk-cursor-blink-timeout"_ns
,
310 // Affects SelectTextfieldsOnKeyFocus
311 "notify::gtk-entry-select-on-focus"_ns
,
312 // Affects ScrollToClick
313 "notify::gtk-primary-button-warps-slider"_ns
,
314 // Affects SubmenuDelay
315 "notify::gtk-menu-popup-delay"_ns
,
316 // Affects DragThresholdX/Y
317 "notify::gtk-dnd-drag-threshold"_ns
,
318 // Affects titlebar actions loaded at moz_gtk_refresh().
319 "notify::gtk-titlebar-double-click"_ns
,
320 "notify::gtk-titlebar-middle-click"_ns
,
323 GtkSettings
* settings
= gtk_settings_get_default();
324 for (const auto& setting
: kObservedSettings
) {
325 g_signal_connect_after(settings
, setting
.get(),
326 G_CALLBACK(settings_changed_cb
), nullptr);
330 nsWindow::GetSystemGtkWindowDecoration() != nsWindow::GTK_DECORATION_NONE
;
332 if (ShouldUsePortal(PortalKind::Settings
)) {
333 mDBusID
= g_bus_watch_name(
334 G_BUS_TYPE_SESSION
, "org.freedesktop.portal.Desktop",
335 G_BUS_NAME_WATCHER_FLAGS_AUTO_START
,
336 [](GDBusConnection
*, const gchar
*, const gchar
*,
337 gpointer data
) -> void {
338 auto* lnf
= static_cast<nsLookAndFeel
*>(data
);
341 [](GDBusConnection
*, const gchar
*, gpointer data
) -> void {
342 auto* lnf
= static_cast<nsLookAndFeel
*>(data
);
347 if (IsKdeDesktopEnvironment()) {
348 GUniquePtr
<gchar
> path(
349 g_strconcat(g_get_user_config_dir(), "/gtk-3.0/colors.css", NULL
));
350 mKdeColors
= dont_AddRef(g_file_new_for_path(path
.get()));
351 mKdeColorsMonitor
= dont_AddRef(
352 g_file_monitor_file(mKdeColors
.get(), G_FILE_MONITOR_NONE
, NULL
, NULL
));
353 if (mKdeColorsMonitor
) {
354 g_signal_connect(mKdeColorsMonitor
.get(), "changed",
355 G_CALLBACK(settings_changed_cb
), NULL
);
360 nsLookAndFeel::~nsLookAndFeel() {
361 ClearRoundedCornerProvider();
363 g_bus_unwatch_name(mDBusID
);
367 g_signal_handlers_disconnect_by_func(
368 gtk_settings_get_default(), FuncToGpointer(settings_changed_cb
), nullptr);
372 static void DumpStyleContext(GtkStyleContext
* aStyle
) {
373 static auto sGtkStyleContextToString
=
374 reinterpret_cast<char* (*)(GtkStyleContext
*, gint
)>(
375 dlsym(RTLD_DEFAULT
, "gtk_style_context_to_string"));
376 char* str
= sGtkStyleContextToString(aStyle
, ~0);
379 str
= gtk_widget_path_to_string(gtk_style_context_get_path(aStyle
));
385 // Modifies color |*aDest| as if a pattern of color |aSource| was painted with
386 // CAIRO_OPERATOR_OVER to a surface with color |*aDest|.
387 static void ApplyColorOver(const GdkRGBA
& aSource
, GdkRGBA
* aDest
) {
388 gdouble sourceCoef
= aSource
.alpha
;
389 gdouble destCoef
= aDest
->alpha
* (1.0 - sourceCoef
);
390 gdouble resultAlpha
= sourceCoef
+ destCoef
;
391 if (resultAlpha
!= 0.0) { // don't divide by zero
392 destCoef
/= resultAlpha
;
393 sourceCoef
/= resultAlpha
;
394 aDest
->red
= sourceCoef
* aSource
.red
+ destCoef
* aDest
->red
;
395 aDest
->green
= sourceCoef
* aSource
.green
+ destCoef
* aDest
->green
;
396 aDest
->blue
= sourceCoef
* aSource
.blue
+ destCoef
* aDest
->blue
;
397 aDest
->alpha
= resultAlpha
;
401 static void GetLightAndDarkness(const GdkRGBA
& aColor
, double* aLightness
,
403 double sum
= aColor
.red
+ aColor
.green
+ aColor
.blue
;
404 *aLightness
= sum
* aColor
.alpha
;
405 *aDarkness
= (3.0 - sum
) * aColor
.alpha
;
408 static bool GetGradientColors(const GValue
* aValue
, GdkRGBA
* aLightColor
,
409 GdkRGBA
* aDarkColor
) {
410 if (!G_TYPE_CHECK_VALUE_TYPE(aValue
, CAIRO_GOBJECT_TYPE_PATTERN
)) {
414 auto pattern
= static_cast<cairo_pattern_t
*>(g_value_get_boxed(aValue
));
419 // Just picking the lightest and darkest colors as simple samples rather
420 // than trying to blend, which could get messy if there are many stops.
421 if (CAIRO_STATUS_SUCCESS
!=
422 cairo_pattern_get_color_stop_rgba(pattern
, 0, nullptr, &aDarkColor
->red
,
423 &aDarkColor
->green
, &aDarkColor
->blue
,
424 &aDarkColor
->alpha
)) {
428 double maxLightness
, maxDarkness
;
429 GetLightAndDarkness(*aDarkColor
, &maxLightness
, &maxDarkness
);
430 *aLightColor
= *aDarkColor
;
434 CAIRO_STATUS_SUCCESS
==
435 cairo_pattern_get_color_stop_rgba(pattern
, index
, nullptr, &stop
.red
,
436 &stop
.green
, &stop
.blue
, &stop
.alpha
);
438 double lightness
, darkness
;
439 GetLightAndDarkness(stop
, &lightness
, &darkness
);
440 if (lightness
> maxLightness
) {
441 maxLightness
= lightness
;
444 if (darkness
> maxDarkness
) {
445 maxDarkness
= darkness
;
453 static bool GetColorFromImagePattern(const GValue
* aValue
, nscolor
* aColor
) {
454 if (!G_TYPE_CHECK_VALUE_TYPE(aValue
, CAIRO_GOBJECT_TYPE_PATTERN
)) {
458 auto* pattern
= static_cast<cairo_pattern_t
*>(g_value_get_boxed(aValue
));
463 cairo_surface_t
* surface
;
464 if (cairo_pattern_get_surface(pattern
, &surface
) != CAIRO_STATUS_SUCCESS
) {
468 cairo_format_t format
= cairo_image_surface_get_format(surface
);
469 if (format
== CAIRO_FORMAT_INVALID
) {
472 int width
= cairo_image_surface_get_width(surface
);
473 int height
= cairo_image_surface_get_height(surface
);
474 int stride
= cairo_image_surface_get_stride(surface
);
475 if (!width
|| !height
) {
479 // Guesstimate the central pixel would have a sensible color.
483 unsigned char* data
= cairo_image_surface_get_data(surface
);
485 // Most (all?) GTK images / patterns / etc use ARGB32.
486 case CAIRO_FORMAT_ARGB32
: {
487 size_t offset
= x
* 4 + y
* stride
;
488 uint32_t* pixel
= reinterpret_cast<uint32_t*>(data
+ offset
);
489 *aColor
= gfx::sRGBColor::UnusualFromARGB(*pixel
).ToABGR();
499 static bool GetUnicoBorderGradientColors(GtkStyleContext
* aContext
,
500 GdkRGBA
* aLightColor
,
501 GdkRGBA
* aDarkColor
) {
502 // Ubuntu 12.04 has GTK engine Unico-1.0.2, which overrides render_frame,
503 // providing its own border code. Ubuntu 14.04 has
504 // Unico-1.0.3+14.04.20140109, which does not override render_frame, and
505 // so does not need special attention. The earlier Unico can be detected
506 // by the -unico-border-gradient style property it registers.
507 // gtk_style_properties_lookup_property() is checked first to avoid the
508 // warning from gtk_style_context_get_property() when the property does
509 // not exist. (gtk_render_frame() of GTK+ 3.16 no longer uses the
511 const char* propertyName
= "-unico-border-gradient";
512 if (!gtk_style_properties_lookup_property(propertyName
, nullptr, nullptr))
515 // -unico-border-gradient is used only when the CSS node's engine is Unico.
516 GtkThemingEngine
* engine
;
517 GtkStateFlags state
= gtk_style_context_get_state(aContext
);
518 gtk_style_context_get(aContext
, state
, "engine", &engine
, nullptr);
519 if (strcmp(g_type_name(G_TYPE_FROM_INSTANCE(engine
)), "UnicoEngine") != 0)
522 // draw_border() of Unico engine uses -unico-border-gradient
523 // in preference to border-color.
524 GValue value
= G_VALUE_INIT
;
525 gtk_style_context_get_property(aContext
, propertyName
, state
, &value
);
527 bool result
= GetGradientColors(&value
, aLightColor
, aDarkColor
);
529 g_value_unset(&value
);
533 // Sets |aLightColor| and |aDarkColor| to colors from |aContext|. Returns
534 // true if |aContext| uses these colors to render a visible border.
535 // If returning false, then the colors returned are a fallback from the
536 // border-color value even though |aContext| does not use these colors to
538 static bool GetBorderColors(GtkStyleContext
* aContext
, GdkRGBA
* aLightColor
,
539 GdkRGBA
* aDarkColor
) {
540 // Determine whether the border on this style context is visible.
541 GtkStateFlags state
= gtk_style_context_get_state(aContext
);
542 GtkBorderStyle borderStyle
;
543 gtk_style_context_get(aContext
, state
, GTK_STYLE_PROPERTY_BORDER_STYLE
,
544 &borderStyle
, nullptr);
545 bool visible
= borderStyle
!= GTK_BORDER_STYLE_NONE
&&
546 borderStyle
!= GTK_BORDER_STYLE_HIDDEN
;
548 // GTK has an initial value of zero for border-widths, and so themes
549 // need to explicitly set border-widths to make borders visible.
551 gtk_style_context_get_border(aContext
, state
, &border
);
552 visible
= border
.top
!= 0 || border
.right
!= 0 || border
.bottom
!= 0 ||
557 GetUnicoBorderGradientColors(aContext
, aLightColor
, aDarkColor
))
560 // The initial value for the border-color is the foreground color, and so
561 // this will usually return a color distinct from the background even if
562 // there is no visible border detected.
563 gtk_style_context_get_border_color(aContext
, state
, aDarkColor
);
564 // TODO GTK3 - update aLightColor
565 // for GTK_BORDER_STYLE_INSET/OUTSET/GROVE/RIDGE border styles.
566 // https://bugzilla.mozilla.org/show_bug.cgi?id=978172#c25
567 *aLightColor
= *aDarkColor
;
571 static bool GetBorderColors(GtkStyleContext
* aContext
, nscolor
* aLightColor
,
572 nscolor
* aDarkColor
) {
573 GdkRGBA lightColor
, darkColor
;
574 bool ret
= GetBorderColors(aContext
, &lightColor
, &darkColor
);
575 *aLightColor
= GDK_RGBA_TO_NS_RGBA(lightColor
);
576 *aDarkColor
= GDK_RGBA_TO_NS_RGBA(darkColor
);
580 // Finds ideal cell highlight colors used for unfocused+selected cells distinct
581 // from both Highlight, used as focused+selected background, and the listbox
582 // background which is assumed to be similar to -moz-field
583 void nsLookAndFeel::PerThemeData::InitCellHighlightColors() {
584 int32_t minLuminosityDifference
= NS_SUFFICIENT_LUMINOSITY_DIFFERENCE_BG
;
585 int32_t backLuminosityDifference
=
586 NS_LUMINOSITY_DIFFERENCE(mWindow
.mBg
, mField
.mBg
);
587 if (backLuminosityDifference
>= minLuminosityDifference
) {
588 mCellHighlight
= mWindow
;
592 uint16_t hue
, sat
, luminance
;
594 mCellHighlight
= mField
;
596 NS_RGB2HSV(mCellHighlight
.mBg
, hue
, sat
, luminance
, alpha
);
599 // Lighten the color if the color is very dark
600 if (luminance
<= step
) {
603 // Darken it if it is very light
604 else if (luminance
>= 255 - step
) {
607 // Otherwise, compute what works best depending on the text luminance.
609 uint16_t textHue
, textSat
, textLuminance
;
611 NS_RGB2HSV(mCellHighlight
.mFg
, textHue
, textSat
, textLuminance
, textAlpha
);
612 // Text is darker than background, use a lighter shade
613 if (textLuminance
< luminance
) {
616 // Otherwise, use a darker shade
621 NS_HSV2RGB(mCellHighlight
.mBg
, hue
, sat
, luminance
, alpha
);
624 void nsLookAndFeel::NativeInit() { EnsureInit(); }
626 void nsLookAndFeel::RefreshImpl() {
627 mInitialized
= false;
630 nsXPLookAndFeel::RefreshImpl();
633 nsresult
nsLookAndFeel::NativeGetColor(ColorID aID
, ColorScheme aScheme
,
638 aScheme
== ColorScheme::Light
? LightTheme() : DarkTheme();
639 return theme
.GetColor(aID
, aColor
);
642 static bool ShouldUseColorForActiveDarkScrollbarThumb(nscolor aColor
) {
643 auto IsDifferentEnough
= [](int32_t aChannel
, int32_t aOtherChannel
) {
644 return std::abs(aChannel
- aOtherChannel
) > 10;
646 return IsDifferentEnough(NS_GET_R(aColor
), NS_GET_G(aColor
)) ||
647 IsDifferentEnough(NS_GET_R(aColor
), NS_GET_B(aColor
));
650 static bool ShouldUseThemedScrollbarColor(StyleSystemColor aID
, nscolor aColor
,
655 if (StaticPrefs::widget_non_native_theme_scrollbar_dark_themed()) {
658 return aID
== StyleSystemColor::ThemedScrollbarThumbActive
&&
659 StaticPrefs::widget_non_native_theme_scrollbar_active_always_themed();
662 nsresult
nsLookAndFeel::PerThemeData::GetColor(ColorID aID
,
663 nscolor
& aColor
) const {
664 nsresult res
= NS_OK
;
667 // These colors don't seem to be used for anything anymore in Mozilla
668 // The CSS2 colors below are used.
669 case ColorID::Appworkspace
: // MDI background color
670 case ColorID::Background
: // desktop background
671 case ColorID::Window
:
672 case ColorID::Windowframe
:
673 case ColorID::MozCombobox
:
674 aColor
= mWindow
.mBg
;
676 case ColorID::Windowtext
:
677 aColor
= mWindow
.mFg
;
679 case ColorID::MozDialog
:
680 aColor
= mDialog
.mBg
;
682 case ColorID::MozDialogtext
:
683 aColor
= mDialog
.mFg
;
685 case ColorID::IMESelectedRawTextBackground
:
686 case ColorID::IMESelectedConvertedTextBackground
:
687 case ColorID::Highlight
: // preference selected item,
688 aColor
= mSelectedText
.mBg
;
690 case ColorID::Highlighttext
:
691 if (NS_GET_A(mSelectedText
.mBg
) < 155) {
692 aColor
= NS_SAME_AS_FOREGROUND_COLOR
;
696 case ColorID::IMESelectedRawTextForeground
:
697 case ColorID::IMESelectedConvertedTextForeground
:
698 aColor
= mSelectedText
.mFg
;
700 case ColorID::Selecteditem
:
701 aColor
= mSelectedItem
.mBg
;
703 case ColorID::Selecteditemtext
:
704 aColor
= mSelectedItem
.mFg
;
706 case ColorID::Accentcolor
:
707 aColor
= mAccent
.mBg
;
709 case ColorID::Accentcolortext
:
710 aColor
= mAccent
.mFg
;
712 case ColorID::MozCellhighlight
:
713 aColor
= mCellHighlight
.mBg
;
715 case ColorID::MozCellhighlighttext
:
716 aColor
= mCellHighlight
.mFg
;
718 case ColorID::IMERawInputBackground
:
719 case ColorID::IMEConvertedTextBackground
:
720 aColor
= NS_TRANSPARENT
;
722 case ColorID::IMERawInputForeground
:
723 case ColorID::IMEConvertedTextForeground
:
724 aColor
= NS_SAME_AS_FOREGROUND_COLOR
;
726 case ColorID::IMERawInputUnderline
:
727 case ColorID::IMEConvertedTextUnderline
:
728 aColor
= NS_SAME_AS_FOREGROUND_COLOR
;
730 case ColorID::IMESelectedRawTextUnderline
:
731 case ColorID::IMESelectedConvertedTextUnderline
:
732 aColor
= NS_TRANSPARENT
;
734 case ColorID::Scrollbar
:
735 aColor
= mThemedScrollbar
;
737 case ColorID::ThemedScrollbar
:
738 aColor
= mThemedScrollbar
;
739 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
740 return NS_ERROR_FAILURE
;
743 case ColorID::ThemedScrollbarInactive
:
744 aColor
= mThemedScrollbarInactive
;
745 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
746 return NS_ERROR_FAILURE
;
749 case ColorID::ThemedScrollbarThumb
:
750 aColor
= mThemedScrollbarThumb
;
751 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
752 return NS_ERROR_FAILURE
;
755 case ColorID::ThemedScrollbarThumbHover
:
756 aColor
= mThemedScrollbarThumbHover
;
757 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
758 return NS_ERROR_FAILURE
;
761 case ColorID::ThemedScrollbarThumbActive
:
762 aColor
= mThemedScrollbarThumbActive
;
763 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
764 return NS_ERROR_FAILURE
;
767 case ColorID::ThemedScrollbarThumbInactive
:
768 aColor
= mThemedScrollbarThumbInactive
;
769 if (!ShouldUseThemedScrollbarColor(aID
, aColor
, mIsDark
)) {
770 return NS_ERROR_FAILURE
;
774 // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
775 case ColorID::Activeborder
:
776 // active window border
777 aColor
= mMozWindowActiveBorder
;
779 case ColorID::Inactiveborder
:
780 // inactive window border
781 aColor
= mMozWindowInactiveBorder
;
783 case ColorID::Graytext
: // disabled text in windows, menus, etc.
786 case ColorID::Activecaption
:
787 aColor
= mTitlebar
.mBg
;
789 case ColorID::Captiontext
: // text in active window caption (titlebar)
790 aColor
= mTitlebar
.mFg
;
792 case ColorID::Inactivecaption
:
793 // inactive window caption
794 aColor
= mTitlebarInactive
.mBg
;
796 case ColorID::Inactivecaptiontext
:
797 aColor
= mTitlebarInactive
.mFg
;
799 case ColorID::Infobackground
:
802 case ColorID::Infotext
:
808 case ColorID::Menutext
:
811 case ColorID::MozHeaderbar
:
812 aColor
= mHeaderBar
.mBg
;
814 case ColorID::MozHeaderbartext
:
815 aColor
= mHeaderBar
.mFg
;
817 case ColorID::MozHeaderbarinactive
:
818 aColor
= mHeaderBarInactive
.mBg
;
820 case ColorID::MozHeaderbarinactivetext
:
821 aColor
= mHeaderBarInactive
.mFg
;
823 case ColorID::Threedface
:
824 case ColorID::Buttonface
:
825 case ColorID::MozButtondisabledface
:
827 aColor
= mWindow
.mBg
;
830 case ColorID::Buttontext
:
831 // text on push buttons
832 aColor
= mButton
.mFg
;
835 case ColorID::Buttonhighlight
:
836 // 3-D highlighted edge color
837 case ColorID::Threedhighlight
:
838 // 3-D highlighted outer edge color
839 aColor
= mThreeDHighlight
;
842 case ColorID::Buttonshadow
:
843 // 3-D shadow edge color
844 case ColorID::Threedshadow
:
845 // 3-D shadow inner edge color
846 aColor
= mThreeDShadow
;
848 case ColorID::Buttonborder
:
849 aColor
= mButtonBorder
;
851 case ColorID::Threedlightshadow
:
852 case ColorID::MozDisabledfield
:
853 aColor
= mIsDark
? *GenericDarkColor(aID
) : NS_RGB(0xE0, 0xE0, 0xE0);
855 case ColorID::Threeddarkshadow
:
856 aColor
= mIsDark
? *GenericDarkColor(aID
) : NS_RGB(0xDC, 0xDC, 0xDC);
859 case ColorID::MozEventreerow
:
863 case ColorID::Fieldtext
:
866 case ColorID::MozSidebar
:
867 aColor
= mSidebar
.mBg
;
869 case ColorID::MozSidebartext
:
870 aColor
= mSidebar
.mFg
;
872 case ColorID::MozSidebarborder
:
873 aColor
= mSidebarBorder
;
875 case ColorID::MozButtonhoverface
:
876 aColor
= mButtonHover
.mBg
;
878 case ColorID::MozButtonhovertext
:
879 aColor
= mButtonHover
.mFg
;
881 case ColorID::MozButtonactiveface
:
882 aColor
= mButtonActive
.mBg
;
884 case ColorID::MozButtonactivetext
:
885 aColor
= mButtonActive
.mFg
;
887 case ColorID::MozMenuhover
:
888 aColor
= mMenuHover
.mBg
;
890 case ColorID::MozMenuhovertext
:
891 aColor
= mMenuHover
.mFg
;
893 case ColorID::MozMenuhoverdisabled
:
894 aColor
= NS_TRANSPARENT
;
896 case ColorID::MozOddtreerow
:
897 aColor
= mOddCellBackground
;
899 case ColorID::MozNativehyperlinktext
:
900 aColor
= mNativeHyperLinkText
;
902 case ColorID::MozNativevisitedhyperlinktext
:
903 aColor
= mNativeVisitedHyperLinkText
;
905 case ColorID::MozComboboxtext
:
906 aColor
= mComboBoxText
;
908 case ColorID::MozColheader
:
909 aColor
= mMozColHeader
.mBg
;
911 case ColorID::MozColheadertext
:
912 aColor
= mMozColHeader
.mFg
;
914 case ColorID::MozColheaderhover
:
915 aColor
= mMozColHeaderHover
.mBg
;
917 case ColorID::MozColheaderhovertext
:
918 aColor
= mMozColHeaderHover
.mFg
;
920 case ColorID::MozColheaderactive
:
921 aColor
= mMozColHeaderActive
.mBg
;
923 case ColorID::MozColheaderactivetext
:
924 aColor
= mMozColHeaderActive
.mFg
;
926 case ColorID::SpellCheckerUnderline
:
928 case ColorID::Marktext
:
929 case ColorID::MozAutofillBackground
:
930 case ColorID::TargetTextBackground
:
931 case ColorID::TargetTextForeground
:
932 aColor
= GetStandinForNativeColor(
933 aID
, mIsDark
? ColorScheme::Dark
: ColorScheme::Light
);
936 /* default color is BLACK */
938 res
= NS_ERROR_FAILURE
;
945 static int32_t CheckWidgetStyle(GtkWidget
* aWidget
, const char* aStyle
,
947 gboolean value
= FALSE
;
948 gtk_widget_style_get(aWidget
, aStyle
, &value
, nullptr);
949 return value
? aResult
: 0;
952 static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle(
953 GtkWidget
* aWidget
) {
954 if (!aWidget
) return mozilla::LookAndFeel::eScrollArrowStyle_Single
;
956 return CheckWidgetStyle(aWidget
, "has-backward-stepper",
957 mozilla::LookAndFeel::eScrollArrow_StartBackward
) |
958 CheckWidgetStyle(aWidget
, "has-forward-stepper",
959 mozilla::LookAndFeel::eScrollArrow_EndForward
) |
960 CheckWidgetStyle(aWidget
, "has-secondary-backward-stepper",
961 mozilla::LookAndFeel::eScrollArrow_EndBackward
) |
962 CheckWidgetStyle(aWidget
, "has-secondary-forward-stepper",
963 mozilla::LookAndFeel::eScrollArrow_StartForward
);
966 nsresult
nsLookAndFeel::NativeGetInt(IntID aID
, int32_t& aResult
) {
967 nsresult res
= NS_OK
;
969 // We use delayed initialization by EnsureInit() here
970 // to make sure mozilla::Preferences is available (Bug 115807).
971 // IntID::UseAccessibilityTheme is requested before user preferences
972 // are read, and so EnsureInit(), which depends on preference values,
973 // is deliberately delayed until required.
975 case IntID::ScrollButtonLeftMouseButtonAction
:
978 case IntID::ScrollButtonMiddleMouseButtonAction
:
981 case IntID::ScrollButtonRightMouseButtonAction
:
984 case IntID::CaretBlinkTime
:
986 aResult
= mCaretBlinkTime
;
988 case IntID::CaretBlinkCount
:
990 aResult
= mCaretBlinkCount
;
992 case IntID::CaretWidth
:
995 case IntID::SelectTextfieldsOnKeyFocus
: {
996 GtkSettings
* settings
;
997 gboolean select_on_focus
;
999 settings
= gtk_settings_get_default();
1000 g_object_get(settings
, "gtk-entry-select-on-focus", &select_on_focus
,
1003 if (select_on_focus
)
1009 case IntID::ScrollToClick
: {
1010 GtkSettings
* settings
;
1011 gboolean warps_slider
= FALSE
;
1013 settings
= gtk_settings_get_default();
1014 if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings
),
1015 "gtk-primary-button-warps-slider")) {
1016 g_object_get(settings
, "gtk-primary-button-warps-slider", &warps_slider
,
1025 case IntID::SubmenuDelay
: {
1026 GtkSettings
* settings
;
1029 settings
= gtk_settings_get_default();
1030 g_object_get(settings
, "gtk-menu-popup-delay", &delay
, nullptr);
1031 aResult
= (int32_t)delay
;
1034 case IntID::MenusCanOverlapOSBar
:
1037 case IntID::SkipNavigatingDisabledMenuItem
:
1040 case IntID::DragThresholdX
:
1041 case IntID::DragThresholdY
: {
1043 g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold",
1044 &threshold
, nullptr);
1046 aResult
= threshold
;
1048 case IntID::ScrollArrowStyle
: {
1049 GtkWidget
* scrollbar
= GetWidget(MOZ_GTK_SCROLLBAR_VERTICAL
);
1050 aResult
= ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar
);
1053 case IntID::TreeOpenDelay
:
1056 case IntID::TreeCloseDelay
:
1059 case IntID::TreeLazyScrollDelay
:
1062 case IntID::TreeScrollDelay
:
1065 case IntID::TreeScrollLinesMax
:
1068 case IntID::AlertNotificationOrigin
:
1069 aResult
= NS_ALERT_TOP
;
1071 case IntID::IMERawInputUnderlineStyle
:
1072 case IntID::IMEConvertedTextUnderlineStyle
:
1073 aResult
= static_cast<int32_t>(StyleTextDecorationStyle::Solid
);
1075 case IntID::IMESelectedRawTextUnderlineStyle
:
1076 case IntID::IMESelectedConvertedTextUnderline
:
1077 aResult
= static_cast<int32_t>(StyleTextDecorationStyle::None
);
1079 case IntID::SpellCheckerUnderlineStyle
:
1080 aResult
= static_cast<int32_t>(StyleTextDecorationStyle::Wavy
);
1082 case IntID::MenuBarDrag
:
1084 aResult
= mSystemTheme
.mMenuSupportsDrag
;
1086 case IntID::ScrollbarButtonAutoRepeatBehavior
:
1089 case IntID::SwipeAnimationEnabled
:
1092 case IntID::ContextMenuOffsetVertical
:
1093 case IntID::ContextMenuOffsetHorizontal
:
1096 case IntID::GTKCSDAvailable
:
1097 aResult
= sCSDAvailable
;
1099 case IntID::GTKCSDTransparencyAvailable
: {
1100 auto* screen
= gdk_screen_get_default();
1101 aResult
= gdk_screen_get_rgba_visual(screen
) &&
1102 gdk_screen_is_composited(screen
);
1105 case IntID::GTKCSDMaximizeButton
:
1107 aResult
= mCSDMaximizeButton
;
1109 case IntID::GTKCSDMinimizeButton
:
1111 aResult
= mCSDMinimizeButton
;
1113 case IntID::GTKCSDCloseButton
:
1115 aResult
= mCSDCloseButton
;
1117 case IntID::GTKCSDReversedPlacement
:
1119 aResult
= mCSDReversedPlacement
;
1121 case IntID::PrefersReducedMotion
: {
1123 aResult
= mPrefersReducedMotion
;
1126 case IntID::SystemUsesDarkTheme
: {
1128 if (mColorSchemePreference
) {
1129 aResult
= *mColorSchemePreference
== ColorScheme::Dark
;
1131 aResult
= mSystemTheme
.mIsDark
;
1135 case IntID::GTKCSDMaximizeButtonPosition
:
1136 aResult
= mCSDMaximizeButtonPosition
;
1138 case IntID::GTKCSDMinimizeButtonPosition
:
1139 aResult
= mCSDMinimizeButtonPosition
;
1141 case IntID::GTKCSDCloseButtonPosition
:
1142 aResult
= mCSDCloseButtonPosition
;
1144 case IntID::GTKThemeFamily
: {
1146 aResult
= int32_t(EffectiveTheme().mFamily
);
1149 case IntID::UseAccessibilityTheme
:
1150 // If high contrast is enabled, enable prefers-reduced-transparency media
1151 // query as well as there is no dedicated option.
1152 case IntID::PrefersReducedTransparency
:
1154 aResult
= mDBusSettings
.mPrefersContrast
|| mSystemTheme
.mHighContrast
;
1156 case IntID::InvertedColors
:
1157 // No GTK API for checking if inverted colors is enabled
1160 case IntID::TitlebarRadius
: {
1162 aResult
= EffectiveTheme().mTitlebarRadius
;
1165 case IntID::TitlebarButtonSpacing
: {
1167 aResult
= EffectiveTheme().mTitlebarButtonSpacing
;
1170 case IntID::AllowOverlayScrollbarsOverlap
: {
1174 case IntID::ScrollbarFadeBeginDelay
: {
1178 case IntID::ScrollbarFadeDuration
: {
1182 case IntID::ScrollbarDisplayOnMouseMove
: {
1186 case IntID::PanelAnimations
:
1187 aResult
= [&]() -> bool {
1188 if (!sCSDAvailable
) {
1189 // Disabled on systems without CSD, see bug 1385079.
1192 if (GdkIsWaylandDisplay()) {
1193 // Disabled on wayland, see bug 1800442 and bug 1800368.
1199 case IntID::UseOverlayScrollbars
: {
1200 aResult
= StaticPrefs::widget_gtk_overlay_scrollbars_enabled();
1203 case IntID::HideCursorWhileTyping
: {
1204 aResult
= StaticPrefs::widget_gtk_hide_pointer_while_typing_enabled();
1207 case IntID::TouchDeviceSupportPresent
:
1208 aResult
= widget::WidgetUtilsGTK::IsTouchDeviceSupportPresent();
1212 res
= NS_ERROR_FAILURE
;
1218 nsresult
nsLookAndFeel::NativeGetFloat(FloatID aID
, float& aResult
) {
1219 nsresult rv
= NS_OK
;
1221 case FloatID::IMEUnderlineRelativeSize
:
1224 case FloatID::SpellCheckerUnderlineRelativeSize
:
1227 case FloatID::CaretAspectRatio
:
1229 aResult
= mSystemTheme
.mCaretRatio
;
1231 case FloatID::TextScaleFactor
:
1232 aResult
= gfxPlatformGtk::GetFontScaleFactor();
1236 rv
= NS_ERROR_FAILURE
;
1241 static void GetSystemFontInfo(GtkStyleContext
* aStyle
, nsString
* aFontName
,
1242 gfxFontStyle
* aFontStyle
) {
1243 aFontStyle
->style
= FontSlantStyle::NORMAL
;
1246 // https://git.gnome.org/browse/gtk+/tree/gtk/gtkwidget.c?h=3.22.19#n10333
1247 PangoFontDescription
* desc
;
1248 gtk_style_context_get(aStyle
, gtk_style_context_get_state(aStyle
), "font",
1251 aFontStyle
->systemFont
= true;
1253 constexpr auto quote
= u
"\""_ns
;
1254 NS_ConvertUTF8toUTF16
family(pango_font_description_get_family(desc
));
1255 *aFontName
= quote
+ family
+ quote
;
1257 aFontStyle
->weight
=
1258 FontWeight::FromInt(pango_font_description_get_weight(desc
));
1260 // FIXME: Set aFontStyle->stretch correctly!
1261 aFontStyle
->stretch
= FontStretch::NORMAL
;
1263 float size
= float(pango_font_description_get_size(desc
)) / PANGO_SCALE
;
1265 // |size| is now either pixels or pango-points (not Mozilla-points!)
1267 if (!pango_font_description_get_size_is_absolute(desc
)) {
1268 // |size| is in pango-points, so convert to pixels.
1269 size
*= float(gfxPlatformGtk::GetFontScaleDPI()) / POINTS_PER_INCH_FLOAT
;
1272 // |size| is now pixels but not scaled for the hidpi displays,
1273 aFontStyle
->size
= size
;
1275 pango_font_description_free(desc
);
1278 bool nsLookAndFeel::NativeGetFont(FontID aID
, nsString
& aFontName
,
1279 gfxFontStyle
& aFontStyle
) {
1280 return mSystemTheme
.GetFont(aID
, aFontName
, aFontStyle
);
1283 bool nsLookAndFeel::PerThemeData::GetFont(FontID aID
, nsString
& aFontName
,
1284 gfxFontStyle
& aFontStyle
) const {
1286 case FontID::Menu
: // css2
1287 case FontID::MozPullDownMenu
: // css3
1288 aFontName
= mMenuFontName
;
1289 aFontStyle
= mMenuFontStyle
;
1292 case FontID::MozField
: // css3
1293 case FontID::MozList
: // css3
1294 aFontName
= mFieldFontName
;
1295 aFontStyle
= mFieldFontStyle
;
1298 case FontID::MozButton
: // css3
1299 aFontName
= mButtonFontName
;
1300 aFontStyle
= mButtonFontStyle
;
1303 case FontID::Caption
: // css2
1304 case FontID::Icon
: // css2
1305 case FontID::MessageBox
: // css2
1306 case FontID::SmallCaption
: // css2
1307 case FontID::StatusBar
: // css2
1309 aFontName
= mDefaultFontName
;
1310 aFontStyle
= mDefaultFontStyle
;
1314 // Convert GDK pixels to CSS pixels.
1315 // When "layout.css.devPixelsPerPx" > 0, this is not a direct conversion.
1316 // The difference produces a scaling of system fonts in proportion with
1317 // other scaling from the change in CSS pixel sizes.
1318 aFontStyle
.size
/= LookAndFeel::GetTextScaleFactor();
1322 static nsCString
GetGtkSettingsStringKey(const char* aKey
) {
1323 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
1325 GtkSettings
* settings
= gtk_settings_get_default();
1326 char* value
= nullptr;
1327 g_object_get(settings
, aKey
, &value
, nullptr);
1335 static nsCString
GetGtkTheme() {
1336 auto theme
= GetGtkSettingsStringKey("gtk-theme-name");
1337 if (theme
.IsEmpty()) {
1338 theme
.AssignLiteral("Adwaita");
1343 static bool GetPreferDarkTheme() {
1344 GtkSettings
* settings
= gtk_settings_get_default();
1345 gboolean preferDarkTheme
= FALSE
;
1346 g_object_get(settings
, "gtk-application-prefer-dark-theme", &preferDarkTheme
,
1348 return preferDarkTheme
== TRUE
;
1351 // It seems GTK doesn't have an API to query if the current theme is "light" or
1352 // "dark", so we synthesize it from the CSS2 Window/WindowText colors instead,
1353 // by comparing their luminosity.
1354 static bool GetThemeIsDark() {
1356 GtkStyleContext
* style
= GetStyleContext(MOZ_GTK_WINDOW
);
1357 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &bg
);
1358 gtk_style_context_get_color(style
, GTK_STATE_FLAG_NORMAL
, &fg
);
1359 return RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(bg
)) <
1360 RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(fg
));
1363 void nsLookAndFeel::RestoreSystemTheme() {
1364 LOGLNF("RestoreSystemTheme(%s, %d, %d)\n", mSystemTheme
.mName
.get(),
1365 mSystemTheme
.mPreferDarkTheme
, mSystemThemeOverridden
);
1367 if (!mSystemThemeOverridden
) {
1371 // Available on Gtk 3.20+.
1372 static auto sGtkSettingsResetProperty
=
1373 (void (*)(GtkSettings
*, const gchar
*))dlsym(
1374 RTLD_DEFAULT
, "gtk_settings_reset_property");
1376 GtkSettings
* settings
= gtk_settings_get_default();
1377 if (sGtkSettingsResetProperty
) {
1378 sGtkSettingsResetProperty(settings
, "gtk-theme-name");
1379 sGtkSettingsResetProperty(settings
, "gtk-application-prefer-dark-theme");
1381 g_object_set(settings
, "gtk-theme-name", mSystemTheme
.mName
.get(),
1382 "gtk-application-prefer-dark-theme",
1383 mSystemTheme
.mPreferDarkTheme
, nullptr);
1385 mSystemThemeOverridden
= false;
1386 UpdateRoundedBottomCornerStyles();
1390 static bool AnyColorChannelIsDifferent(nscolor aColor
) {
1391 return NS_GET_R(aColor
) != NS_GET_G(aColor
) ||
1392 NS_GET_R(aColor
) != NS_GET_B(aColor
);
1395 bool nsLookAndFeel::ConfigureAltTheme() {
1396 GtkSettings
* settings
= gtk_settings_get_default();
1397 // Toggling gtk-application-prefer-dark-theme is not enough generally to
1398 // switch from dark to light theme. If the theme didn't change, and we have
1399 // a dark theme, try to first remove -Dark{,er,est} from the theme name to
1400 // find the light variant.
1401 if (mSystemTheme
.mIsDark
) {
1402 nsCString potentialLightThemeName
= mSystemTheme
.mName
;
1404 constexpr nsLiteralCString kSubstringsToRemove
[] = {
1405 "-darkest"_ns
, "-darker"_ns
, "-dark"_ns
,
1406 "-Darkest"_ns
, "-Darker"_ns
, "-Dark"_ns
,
1407 "_darkest"_ns
, "_darker"_ns
, "_dark"_ns
,
1408 "_Darkest"_ns
, "_Darker"_ns
, "_Dark"_ns
,
1412 for (const auto& s
: kSubstringsToRemove
) {
1413 potentialLightThemeName
= mSystemTheme
.mName
;
1414 potentialLightThemeName
.ReplaceSubstring(s
, ""_ns
);
1415 if (potentialLightThemeName
.Length() != mSystemTheme
.mName
.Length()) {
1421 LOGLNF(" found potential light variant of %s: %s",
1422 mSystemTheme
.mName
.get(), potentialLightThemeName
.get());
1423 g_object_set(settings
, "gtk-theme-name", potentialLightThemeName
.get(),
1424 "gtk-application-prefer-dark-theme", !mSystemTheme
.mIsDark
,
1428 if (!GetThemeIsDark()) {
1429 return true; // Success!
1434 LOGLNF(" toggling gtk-application-prefer-dark-theme");
1435 g_object_set(settings
, "gtk-application-prefer-dark-theme",
1436 !mSystemTheme
.mIsDark
, nullptr);
1438 if (mSystemTheme
.mIsDark
!= GetThemeIsDark()) {
1439 return true; // Success!
1442 LOGLNF(" didn't work, falling back to default theme");
1443 // If the theme still didn't change enough, fall back to Adwaita with the
1444 // appropriate color preference.
1445 g_object_set(settings
, "gtk-theme-name", "Adwaita",
1446 "gtk-application-prefer-dark-theme", !mSystemTheme
.mIsDark
,
1450 // If it _still_ didn't change enough, and we're looking for a dark theme,
1451 // try to set Adwaita-dark as a theme name. This might be needed in older GTK
1453 if (!mSystemTheme
.mIsDark
&& !GetThemeIsDark()) {
1454 LOGLNF(" last resort Adwaita-dark fallback");
1455 g_object_set(settings
, "gtk-theme-name", "Adwaita-dark", nullptr);
1462 // We override some adwaita colors from GTK3 to LibAdwaita, see:
1463 // https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/named-colors.html
1464 void nsLookAndFeel::MaybeApplyAdwaitaOverrides() {
1465 auto& dark
= mSystemTheme
.mIsDark
? mSystemTheme
: mAltTheme
;
1466 auto& light
= mSystemTheme
.mIsDark
? mAltTheme
: mSystemTheme
;
1468 // Unconditional special case for Adwaita-dark: In GTK3 we don't have more
1469 // proper accent colors, so we use the selected background colors. Those
1470 // colors, however, don't have much contrast in dark mode (see bug 1741293).
1471 if (dark
.mFamily
== ThemeFamily::Adwaita
) {
1472 if (mDBusSettings
.HasAccentColor()) {
1473 dark
.mAccent
= mDBusSettings
.mAccentColor
;
1474 dark
.mSelectedItem
= dark
.mMenuHover
= dark
.mAccent
;
1475 dark
.mNativeHyperLinkText
= dark
.mNativeVisitedHyperLinkText
=
1478 dark
.mAccent
= {NS_RGB(0x35, 0x84, 0xe4), NS_RGB(0xff, 0xff, 0xff)};
1480 dark
.mSelectedText
= dark
.mAccent
;
1483 if (light
.mFamily
== ThemeFamily::Adwaita
) {
1484 if (mDBusSettings
.HasAccentColor()) {
1485 light
.mAccent
= mDBusSettings
.mAccentColor
;
1486 light
.mSelectedItem
= light
.mMenuHover
= light
.mAccent
;
1487 light
.mNativeHyperLinkText
= light
.mNativeVisitedHyperLinkText
=
1490 light
.mAccent
= {NS_RGB(0x35, 0x84, 0xe4), NS_RGB(0xff, 0xff, 0xff)};
1492 light
.mSelectedText
= light
.mAccent
;
1495 if (!StaticPrefs::widget_gtk_libadwaita_colors_enabled()) {
1499 if (light
.mFamily
== ThemeFamily::Adwaita
) {
1500 // #323232 is rgba(0,0,0,.8) over #fafafa.
1502 light
.mDialog
= {NS_RGB(0xfa, 0xfa, 0xfa), NS_RGB(0x32, 0x32, 0x32)};
1503 light
.mField
= {NS_RGB(0xff, 0xff, 0xff), NS_RGB(0x32, 0x32, 0x32)};
1505 // We use the sidebar colors for the headerbar in light mode background
1506 // because it creates much better contrast. GTK headerbar colors are white,
1507 // and meant to "blend" with the contents otherwise.
1508 // #2f2f2f is rgba(0,0,0,.8) over #ebebeb.
1509 light
.mSidebar
= light
.mHeaderBar
=
1510 light
.mTitlebar
= {NS_RGB(0xeb, 0xeb, 0xeb), NS_RGB(0x2f, 0x2f, 0x2f)};
1511 light
.mHeaderBarInactive
= light
.mTitlebarInactive
= {
1512 NS_RGB(0xf2, 0xf2, 0xf2), NS_RGB(0x2f, 0x2f, 0x2f)};
1513 light
.mThreeDShadow
= NS_RGB(0xe0, 0xe0, 0xe0);
1514 light
.mSidebarBorder
= NS_RGBA(0, 0, 0, 18);
1517 if (dark
.mFamily
== ThemeFamily::Adwaita
) {
1518 dark
.mWindow
= {NS_RGB(0x24, 0x24, 0x24), NS_RGB(0xff, 0xff, 0xff)};
1519 dark
.mDialog
= {NS_RGB(0x38, 0x38, 0x38), NS_RGB(0xff, 0xff, 0xff)};
1520 dark
.mField
= {NS_RGB(0x3a, 0x3a, 0x3a), NS_RGB(0xff, 0xff, 0xff)};
1521 dark
.mSidebar
= dark
.mHeaderBar
=
1522 dark
.mTitlebar
= {NS_RGB(0x30, 0x30, 0x30), NS_RGB(0xff, 0xff, 0xff)};
1523 dark
.mHeaderBarInactive
= dark
.mTitlebarInactive
= {
1524 NS_RGB(0x24, 0x24, 0x24), NS_RGB(0xff, 0xff, 0xff)};
1525 // headerbar_shade_color
1526 dark
.mThreeDShadow
= NS_RGB(0x1f, 0x1f, 0x1f);
1527 dark
.mSidebarBorder
= NS_RGBA(0, 0, 0, 92);
1531 void nsLookAndFeel::ConfigureAndInitializeAltTheme() {
1532 const bool fellBackToDefaultTheme
= !ConfigureAltTheme();
1536 MaybeApplyAdwaitaOverrides();
1538 // Some of the alt theme colors we can grab from the system theme, if we fell
1539 // back to the default light / dark themes.
1540 if (fellBackToDefaultTheme
) {
1541 if (StaticPrefs::widget_gtk_alt_theme_selection()) {
1542 mAltTheme
.mSelectedText
= mSystemTheme
.mSelectedText
;
1545 if (StaticPrefs::widget_gtk_alt_theme_scrollbar_active() &&
1546 (!mAltTheme
.mIsDark
|| ShouldUseColorForActiveDarkScrollbarThumb(
1547 mSystemTheme
.mThemedScrollbarThumbActive
))) {
1548 mAltTheme
.mThemedScrollbarThumbActive
=
1549 mSystemTheme
.mThemedScrollbarThumbActive
;
1552 if (StaticPrefs::widget_gtk_alt_theme_accent()) {
1553 mAltTheme
.mAccent
= mSystemTheme
.mAccent
;
1557 // Right now we're using the opposite color-scheme theme, make sure to record
1559 mSystemThemeOverridden
= true;
1560 UpdateRoundedBottomCornerStyles();
1563 void nsLookAndFeel::ClearRoundedCornerProvider() {
1564 if (!mRoundedCornerProvider
) {
1567 gtk_style_context_remove_provider_for_screen(
1568 gdk_screen_get_default(),
1569 GTK_STYLE_PROVIDER(mRoundedCornerProvider
.get()));
1570 mRoundedCornerProvider
= nullptr;
1573 void nsLookAndFeel::UpdateRoundedBottomCornerStyles() {
1574 ClearRoundedCornerProvider();
1575 if (!StaticPrefs::widget_gtk_rounded_bottom_corners_enabled()) {
1578 int32_t radius
= EffectiveTheme().mTitlebarRadius
;
1582 mRoundedCornerProvider
= dont_AddRef(gtk_css_provider_new());
1583 nsPrintfCString
string(
1584 "window.csd decoration {"
1585 "border-bottom-right-radius: %dpx;"
1586 "border-bottom-left-radius: %dpx;"
1589 GUniquePtr
<GError
> error
;
1590 if (!gtk_css_provider_load_from_data(mRoundedCornerProvider
.get(),
1591 string
.get(), string
.Length(),
1592 getter_Transfers(error
))) {
1593 NS_WARNING(nsPrintfCString("Failed to load provider: %s - %s\n",
1594 string
.get(), error
? error
->message
: nullptr)
1597 gtk_style_context_add_provider_for_screen(
1598 gdk_screen_get_default(),
1599 GTK_STYLE_PROVIDER(mRoundedCornerProvider
.get()),
1600 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
1603 Maybe
<ColorScheme
> nsLookAndFeel::ComputeColorSchemeSetting() {
1605 // Check the pref explicitly here. Usually this shouldn't be needed, but
1606 // since we can only load one GTK theme at a time, and the pref will
1607 // override the effective value that the rest of gecko assumes for the
1608 // "system" color scheme, we need to factor it in our GTK theme decisions.
1610 if (NS_SUCCEEDED(Preferences::GetInt("ui.systemUsesDarkTheme", &pref
))) {
1611 return Some(pref
? ColorScheme::Dark
: ColorScheme::Light
);
1615 return mDBusSettings
.mColorScheme
;
1618 void nsLookAndFeel::Initialize() {
1619 LOGLNF("nsLookAndFeel::Initialize");
1620 MOZ_DIAGNOSTIC_ASSERT(!mInitialized
);
1621 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
1622 "LookAndFeel init should be done on the main thread");
1624 mInitialized
= true;
1626 GtkSettings
* settings
= gtk_settings_get_default();
1627 if (MOZ_UNLIKELY(!settings
)) {
1628 NS_WARNING("EnsureInit: No settings");
1632 AutoRestore
<bool> restoreIgnoreSettings(sIgnoreChangedSettings
);
1633 sIgnoreChangedSettings
= true;
1635 // Our current theme may be different from the system theme if we're matching
1636 // the Firefox theme or using the alt theme intentionally due to the
1637 // color-scheme preference. Make sure to restore the original system theme.
1638 RestoreSystemTheme();
1640 // First initialize global settings.
1641 InitializeGlobalSettings();
1643 // Record our system theme settings now.
1644 mSystemTheme
.Init();
1646 // Find the alternative-scheme theme (light if the system theme is dark, or
1647 // vice versa), configure it and initialize it.
1648 ConfigureAndInitializeAltTheme();
1650 LOGLNF("System Theme: %s. Alt Theme: %s\n", mSystemTheme
.mName
.get(),
1651 mAltTheme
.mName
.get());
1653 // Go back to the system theme or keep the alt theme configured, depending on
1654 // Firefox theme or user color-scheme preference.
1655 ConfigureFinalEffectiveTheme();
1660 void nsLookAndFeel::InitializeGlobalSettings() {
1661 GtkSettings
* settings
= gtk_settings_get_default();
1663 mColorSchemePreference
= ComputeColorSchemeSetting();
1665 gboolean enableAnimations
= false;
1666 g_object_get(settings
, "gtk-enable-animations", &enableAnimations
, nullptr);
1667 mPrefersReducedMotion
= !enableAnimations
;
1669 gint blink_time
= 0; // In milliseconds
1670 gint blink_timeout
= 0; // in seconds
1672 g_object_get(settings
, "gtk-cursor-blink-time", &blink_time
,
1673 "gtk-cursor-blink-timeout", &blink_timeout
, "gtk-cursor-blink",
1676 // https://docs.gtk.org/gtk3/property.Settings.gtk-cursor-blink-timeout.html:
1678 // Setting this to zero has the same effect as setting
1679 // GtkSettings:gtk-cursor-blink to FALSE.
1681 mCaretBlinkTime
= blink
&& blink_timeout
? (int32_t)blink_time
: 0;
1683 if (mCaretBlinkTime
) {
1684 // blink_time * 2 because blink count is a full blink cycle.
1686 std::max(1, int32_t(std::ceil(float(blink_timeout
* 1000) /
1687 (float(blink_time
) * 2.0f
))));
1689 mCaretBlinkCount
= -1;
1692 mCSDCloseButton
= false;
1693 mCSDMinimizeButton
= false;
1694 mCSDMaximizeButton
= false;
1695 mCSDCloseButtonPosition
= 0;
1696 mCSDMinimizeButtonPosition
= 0;
1697 mCSDMaximizeButtonPosition
= 0;
1699 // We need to initialize whole CSD config explicitly because it's queried
1700 // as -moz-gtk* media features.
1701 ButtonLayout buttonLayout
[TOOLBAR_BUTTONS
];
1703 size_t activeButtons
=
1704 GetGtkHeaderBarButtonLayout(Span(buttonLayout
), &mCSDReversedPlacement
);
1705 for (size_t i
= 0; i
< activeButtons
; i
++) {
1706 // We check if a button is represented on the right side of the tabbar.
1707 // Then we assign it a value from 3 to 5, instead of 0 to 2 when it is on
1709 const ButtonLayout
& layout
= buttonLayout
[i
];
1710 int32_t* pos
= nullptr;
1711 switch (layout
.mType
) {
1712 case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE
:
1713 mCSDMinimizeButton
= true;
1714 pos
= &mCSDMinimizeButtonPosition
;
1716 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
:
1717 mCSDMaximizeButton
= true;
1718 pos
= &mCSDMaximizeButtonPosition
;
1720 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE
:
1721 mCSDCloseButton
= true;
1722 pos
= &mCSDCloseButtonPosition
;
1733 struct actionMapping
{
1734 TitlebarAction action
;
1736 } ActionMapping
[] = {
1737 {TitlebarAction::None
, "none"},
1738 {TitlebarAction::WindowLower
, "lower"},
1739 {TitlebarAction::WindowMenu
, "menu"},
1740 {TitlebarAction::WindowMinimize
, "minimize"},
1741 {TitlebarAction::WindowMaximize
, "maximize"},
1742 {TitlebarAction::WindowMaximizeToggle
, "toggle-maximize"},
1745 auto GetWindowAction
= [&](const char* eventName
) -> TitlebarAction
{
1746 gchar
* action
= nullptr;
1747 g_object_get(settings
, eventName
, &action
, nullptr);
1749 return TitlebarAction::None
;
1751 auto free
= mozilla::MakeScopeExit([&] { g_free(action
); });
1752 for (auto const& mapping
: ActionMapping
) {
1753 if (!strncmp(action
, mapping
.name
, strlen(mapping
.name
))) {
1754 return mapping
.action
;
1757 return TitlebarAction::None
;
1760 mDoubleClickAction
= GetWindowAction("gtk-titlebar-double-click");
1761 mMiddleClickAction
= GetWindowAction("gtk-titlebar-middle-click");
1764 void nsLookAndFeel::ConfigureFinalEffectiveTheme() {
1765 MOZ_ASSERT(mSystemThemeOverridden
,
1766 "By this point, the alt theme should be configured");
1767 const bool shouldUseSystemTheme
= [&] {
1768 using ChromeSetting
= PreferenceSheet::ChromeColorSchemeSetting
;
1769 // NOTE: We can't call ColorSchemeForChrome directly because this might run
1770 // while we're computing it.
1771 switch (PreferenceSheet::ColorSchemeSettingForChrome()) {
1772 case ChromeSetting::Light
:
1773 return !mSystemTheme
.mIsDark
;
1774 case ChromeSetting::Dark
:
1775 return mSystemTheme
.mIsDark
;
1776 case ChromeSetting::System
:
1779 if (!mColorSchemePreference
) {
1782 const bool preferenceIsDark
= *mColorSchemePreference
== ColorScheme::Dark
;
1783 return preferenceIsDark
== mSystemTheme
.mIsDark
;
1786 const bool usingSystem
= !mSystemThemeOverridden
;
1787 LOGLNF("OverrideSystemThemeIfNeeded(matchesSystem=%d, usingSystem=%d)\n",
1788 shouldUseSystemTheme
, usingSystem
);
1790 if (shouldUseSystemTheme
) {
1791 RestoreSystemTheme();
1792 } else if (usingSystem
) {
1793 LOGLNF("Setting theme %s, %d\n", mAltTheme
.mName
.get(),
1794 mAltTheme
.mPreferDarkTheme
);
1796 GtkSettings
* settings
= gtk_settings_get_default();
1797 if (mSystemTheme
.mName
== mAltTheme
.mName
) {
1798 // Prefer setting only gtk-application-prefer-dark-theme, so we can still
1799 // get notified from notify::gtk-theme-name if the user changes the theme.
1800 g_object_set(settings
, "gtk-application-prefer-dark-theme",
1801 mAltTheme
.mPreferDarkTheme
, nullptr);
1803 g_object_set(settings
, "gtk-theme-name", mAltTheme
.mName
.get(),
1804 "gtk-application-prefer-dark-theme",
1805 mAltTheme
.mPreferDarkTheme
, nullptr);
1807 mSystemThemeOverridden
= true;
1808 UpdateRoundedBottomCornerStyles();
1813 static bool GetColorFromBackgroundImage(GtkStyleContext
* aStyle
,
1814 nscolor aForForegroundColor
,
1815 GtkStateFlags aState
, nscolor
* aColor
) {
1816 GValue value
= G_VALUE_INIT
;
1817 gtk_style_context_get_property(aStyle
, "background-image", aState
, &value
);
1818 auto cleanup
= MakeScopeExit([&] { g_value_unset(&value
); });
1819 if (GetColorFromImagePattern(&value
, aColor
)) {
1824 GdkRGBA light
, dark
;
1825 if (GetGradientColors(&value
, &light
, &dark
)) {
1826 nscolor l
= GDK_RGBA_TO_NS_RGBA(light
);
1827 nscolor d
= GDK_RGBA_TO_NS_RGBA(dark
);
1828 // Return the one with more contrast.
1829 // TODO(emilio): This could do interpolation or what not but seems
1831 if (NS_LUMINOSITY_DIFFERENCE(l
, aForForegroundColor
) >
1832 NS_LUMINOSITY_DIFFERENCE(d
, aForForegroundColor
)) {
1844 static nscolor
GetBackgroundColor(
1845 GtkStyleContext
* aStyle
, nscolor aForForegroundColor
,
1846 GtkStateFlags aState
= GTK_STATE_FLAG_NORMAL
,
1847 nscolor aOverBackgroundColor
= NS_TRANSPARENT
) {
1848 // Try to synthesize a color from a background-image.
1849 nscolor imageColor
= NS_TRANSPARENT
;
1850 if (GetColorFromBackgroundImage(aStyle
, aForForegroundColor
, aState
,
1852 if (NS_GET_A(imageColor
) == 255) {
1858 gtk_style_context_get_background_color(aStyle
, aState
, &gdkColor
);
1859 nscolor bgColor
= GDK_RGBA_TO_NS_RGBA(gdkColor
);
1860 // background-image paints over background-color.
1861 const nscolor finalColor
= NS_ComposeColors(bgColor
, imageColor
);
1862 if (finalColor
!= aOverBackgroundColor
) {
1865 return NS_TRANSPARENT
;
1868 static nscolor
GetTextColor(GtkStyleContext
* aStyle
,
1869 GtkStateFlags aState
= GTK_STATE_FLAG_NORMAL
) {
1871 gtk_style_context_get_color(aStyle
, aState
, &color
);
1872 return GDK_RGBA_TO_NS_RGBA(color
);
1875 using ColorPair
= nsLookAndFeel::ColorPair
;
1876 static ColorPair
GetColorPair(GtkStyleContext
* aStyle
,
1877 GtkStateFlags aState
= GTK_STATE_FLAG_NORMAL
) {
1879 result
.mFg
= GetTextColor(aStyle
, aState
);
1880 result
.mBg
= GetBackgroundColor(aStyle
, result
.mFg
, aState
);
1884 static bool GetNamedColorPair(GtkStyleContext
* aStyle
, const char* aBgName
,
1885 const char* aFgName
, ColorPair
* aPair
) {
1887 if (!gtk_style_context_lookup_color(aStyle
, aBgName
, &bg
) ||
1888 !gtk_style_context_lookup_color(aStyle
, aFgName
, &fg
)) {
1892 aPair
->mBg
= GDK_RGBA_TO_NS_RGBA(bg
);
1893 aPair
->mFg
= GDK_RGBA_TO_NS_RGBA(fg
);
1895 // If the colors are semi-transparent and the theme provides a
1896 // background color, blend with them to get the "final" color, see
1898 if (NS_GET_A(aPair
->mBg
) != 255 &&
1899 (gtk_style_context_lookup_color(aStyle
, "bg_color", &bg
) ||
1900 gtk_style_context_lookup_color(aStyle
, "theme_bg_color", &bg
))) {
1901 aPair
->mBg
= NS_ComposeColors(GDK_RGBA_TO_NS_RGBA(bg
), aPair
->mBg
);
1904 // A semi-transparent foreground color would be kinda silly, but is done
1906 if (NS_GET_A(aPair
->mFg
) != 255) {
1907 aPair
->mFg
= NS_ComposeColors(aPair
->mBg
, aPair
->mFg
);
1913 static void EnsureColorPairIsOpaque(ColorPair
& aPair
) {
1914 // Blend with white, ensuring the color is opaque, so that the UI doesn't have
1915 // to care about alpha.
1916 aPair
.mBg
= NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), aPair
.mBg
);
1917 aPair
.mFg
= NS_ComposeColors(aPair
.mBg
, aPair
.mFg
);
1920 static void PreferDarkerBackground(ColorPair
& aPair
) {
1921 // We use the darker one unless the foreground isn't really a color (is all
1922 // white / black / gray) and the background is, in which case we stick to what
1924 if (RelativeLuminanceUtils::Compute(aPair
.mBg
) >
1925 RelativeLuminanceUtils::Compute(aPair
.mFg
) &&
1926 (AnyColorChannelIsDifferent(aPair
.mFg
) ||
1927 !AnyColorChannelIsDifferent(aPair
.mBg
))) {
1928 std::swap(aPair
.mBg
, aPair
.mFg
);
1932 void nsLookAndFeel::PerThemeData::Init() {
1933 mName
= GetGtkTheme();
1936 if (mName
.EqualsLiteral("Adwaita") || mName
.EqualsLiteral("Adwaita-dark")) {
1937 return ThemeFamily::Adwaita
;
1939 if (mName
.EqualsLiteral("Breeze") || mName
.EqualsLiteral("Breeze-Dark")) {
1940 return ThemeFamily::Breeze
;
1942 if (StringBeginsWith(mName
, "Yaru"_ns
)) {
1943 return ThemeFamily::Yaru
;
1945 return ThemeFamily::Unknown
;
1948 GtkStyleContext
* style
;
1950 mHighContrast
= StaticPrefs::widget_content_gtk_high_contrast_enabled() &&
1951 mName
.Find("HighContrast"_ns
) >= 0;
1953 mPreferDarkTheme
= GetPreferDarkTheme();
1955 mIsDark
= GetThemeIsDark();
1958 // Some themes style the <trough>, while others style the <scrollbar>
1959 // itself, so we look at both and compose the colors.
1960 style
= GetStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL
);
1961 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
1962 mThemedScrollbar
= GDK_RGBA_TO_NS_RGBA(color
);
1963 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_BACKDROP
,
1965 mThemedScrollbarInactive
= GDK_RGBA_TO_NS_RGBA(color
);
1967 style
= GetStyleContext(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
);
1968 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
1970 NS_ComposeColors(mThemedScrollbar
, GDK_RGBA_TO_NS_RGBA(color
));
1971 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_BACKDROP
,
1973 mThemedScrollbarInactive
=
1974 NS_ComposeColors(mThemedScrollbarInactive
, GDK_RGBA_TO_NS_RGBA(color
));
1976 style
= GetStyleContext(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL
);
1977 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
1978 mThemedScrollbarThumb
= GDK_RGBA_TO_NS_RGBA(color
);
1979 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_PRELIGHT
,
1981 mThemedScrollbarThumbHover
= GDK_RGBA_TO_NS_RGBA(color
);
1982 gtk_style_context_get_background_color(
1983 style
, GtkStateFlags(GTK_STATE_FLAG_PRELIGHT
| GTK_STATE_FLAG_ACTIVE
),
1985 mThemedScrollbarThumbActive
= GDK_RGBA_TO_NS_RGBA(color
);
1986 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_BACKDROP
,
1988 mThemedScrollbarThumbInactive
= GDK_RGBA_TO_NS_RGBA(color
);
1990 // Make sure that the thumb is visible, at least.
1991 const bool fallbackToUnthemedColors
= [&] {
1992 if (!StaticPrefs::widget_gtk_theme_scrollbar_colors_enabled()) {
1996 if (!ShouldHonorThemeScrollbarColors()) {
1999 // If any of the scrollbar thumb colors are fully transparent, fall back to
2001 if (!NS_GET_A(mThemedScrollbarThumb
) ||
2002 !NS_GET_A(mThemedScrollbarThumbHover
) ||
2003 !NS_GET_A(mThemedScrollbarThumbActive
)) {
2006 // If the thumb and track are the same color and opaque, fall back to
2007 // non-native colors as well.
2008 if (mThemedScrollbar
== mThemedScrollbarThumb
&&
2009 NS_GET_A(mThemedScrollbar
) == 0xff) {
2015 if (fallbackToUnthemedColors
) {
2017 // Taken from Adwaita-dark.
2018 mThemedScrollbar
= NS_RGB(0x31, 0x31, 0x31);
2019 mThemedScrollbarInactive
= NS_RGB(0x2d, 0x2d, 0x2d);
2020 mThemedScrollbarThumb
= NS_RGB(0xa3, 0xa4, 0xa4);
2021 mThemedScrollbarThumbInactive
= NS_RGB(0x59, 0x5a, 0x5a);
2023 // Taken from Adwaita.
2024 mThemedScrollbar
= NS_RGB(0xce, 0xce, 0xce);
2025 mThemedScrollbarInactive
= NS_RGB(0xec, 0xed, 0xef);
2026 mThemedScrollbarThumb
= NS_RGB(0x82, 0x81, 0x7e);
2027 mThemedScrollbarThumbInactive
= NS_RGB(0xce, 0xcf, 0xce);
2030 mThemedScrollbarThumbHover
= ThemeColors::AdjustUnthemedScrollbarThumbColor(
2031 mThemedScrollbarThumb
, dom::ElementState::HOVER
);
2032 mThemedScrollbarThumbActive
=
2033 ThemeColors::AdjustUnthemedScrollbarThumbColor(
2034 mThemedScrollbarThumb
, dom::ElementState::ACTIVE
);
2037 // The label is not added to a parent widget, but shared for constructing
2038 // different style contexts. The node hierarchy is constructed only on
2039 // the label style context.
2040 GtkWidget
* labelWidget
= gtk_label_new("M");
2041 g_object_ref_sink(labelWidget
);
2044 style
= GetStyleContext(MOZ_GTK_WINDOW
);
2045 mWindow
= mDialog
= GetColorPair(style
);
2047 gtk_style_context_get_border_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2048 mMozWindowActiveBorder
= GDK_RGBA_TO_NS_RGBA(color
);
2050 gtk_style_context_get_border_color(style
, GTK_STATE_FLAG_INSENSITIVE
, &color
);
2051 mMozWindowInactiveBorder
= GDK_RGBA_TO_NS_RGBA(color
);
2053 style
= GetStyleContext(MOZ_GTK_WINDOW_CONTAINER
);
2055 GtkStyleContext
* labelStyle
= CreateStyleForWidget(labelWidget
, style
);
2056 GetSystemFontInfo(labelStyle
, &mDefaultFontName
, &mDefaultFontStyle
);
2057 g_object_unref(labelStyle
);
2060 // tooltip foreground and background
2061 style
= GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL
);
2062 mInfo
.mFg
= GetTextColor(style
);
2063 style
= GetStyleContext(MOZ_GTK_TOOLTIP
);
2064 mInfo
.mBg
= GetBackgroundColor(style
, mInfo
.mFg
);
2066 style
= GetStyleContext(MOZ_GTK_MENUITEM
);
2068 GtkStyleContext
* accelStyle
=
2069 CreateStyleForWidget(gtk_accel_label_new("M"), style
);
2071 GetSystemFontInfo(accelStyle
, &mMenuFontName
, &mMenuFontStyle
);
2073 gtk_style_context_get_color(accelStyle
, GTK_STATE_FLAG_NORMAL
, &color
);
2074 mMenu
.mFg
= GetTextColor(accelStyle
);
2075 mGrayText
= GetTextColor(accelStyle
, GTK_STATE_FLAG_INSENSITIVE
);
2076 g_object_unref(accelStyle
);
2079 const auto effectiveTitlebarStyle
=
2080 HeaderBarShouldDrawContainer(MOZ_GTK_HEADER_BAR
) ? MOZ_GTK_HEADERBAR_FIXED
2081 : MOZ_GTK_HEADER_BAR
;
2082 style
= GetStyleContext(effectiveTitlebarStyle
);
2084 mTitlebar
= GetColorPair(style
, GTK_STATE_FLAG_NORMAL
);
2085 mTitlebarInactive
= GetColorPair(style
, GTK_STATE_FLAG_BACKDROP
);
2086 mTitlebarRadius
= IsSolidCSDStyleUsed() ? 0 : GetBorderRadius(style
);
2087 mTitlebarButtonSpacing
= moz_gtk_get_titlebar_button_spacing();
2090 // We special-case the header bar color in Adwaita, Yaru and Breeze to be the
2091 // titlebar color, because it looks better and matches what apps do by
2092 // default, see bug 1838460.
2094 // We only do this in the relevant desktop environments, however, since in
2095 // other cases we don't really know if the DE's titlebars are going to match.
2097 // For breeze, additionally we read the KDE colors directly, if available,
2098 // since these are user-configurable.
2100 // For most other themes or those in unknown DEs, we use the menubar colors.
2102 // FIXME(emilio): Can we do something a bit less special-case-y?
2103 const bool shouldUseTitlebarColorsForHeaderBar
= [&] {
2104 if (mFamily
== ThemeFamily::Adwaita
|| mFamily
== ThemeFamily::Yaru
) {
2105 return IsGnomeDesktopEnvironment();
2107 if (mFamily
== ThemeFamily::Breeze
) {
2108 return IsKdeDesktopEnvironment();
2113 if (shouldUseTitlebarColorsForHeaderBar
) {
2114 mHeaderBar
= mTitlebar
;
2115 mHeaderBarInactive
= mTitlebarInactive
;
2116 if (mFamily
== ThemeFamily::Breeze
) {
2117 GetNamedColorPair(style
, "theme_header_background_breeze",
2118 "theme_header_foreground_breeze", &mHeaderBar
);
2119 GetNamedColorPair(style
, "theme_header_background_backdrop_breeze",
2120 "theme_header_foreground_backdrop_breeze",
2121 &mHeaderBarInactive
);
2124 style
= GetStyleContext(MOZ_GTK_MENUBARITEM
);
2125 mHeaderBar
.mFg
= GetTextColor(style
);
2126 mHeaderBarInactive
.mFg
= GetTextColor(style
, GTK_STATE_FLAG_BACKDROP
);
2128 style
= GetStyleContext(MOZ_GTK_MENUBAR
);
2129 mHeaderBar
.mBg
= GetBackgroundColor(style
, mHeaderBar
.mFg
);
2130 mHeaderBarInactive
.mBg
= GetBackgroundColor(style
, mHeaderBarInactive
.mFg
,
2131 GTK_STATE_FLAG_BACKDROP
);
2134 style
= GetStyleContext(MOZ_GTK_MENUPOPUP
);
2136 nscolor color
= GetBackgroundColor(style
, mMenu
.mFg
);
2137 if (NS_GET_A(color
)) {
2140 // Some themes only style menupopups with the backdrop pseudo-class. Since a
2141 // context / popup menu always seems to match that, try that before giving
2143 color
= GetBackgroundColor(style
, mMenu
.mFg
, GTK_STATE_FLAG_BACKDROP
);
2144 if (NS_GET_A(color
)) {
2147 // If we get here we couldn't figure out the right color to use. Rather than
2148 // falling back to transparent, fall back to the window background.
2150 "Couldn't find menu background color, falling back to window "
2155 style
= GetStyleContext(MOZ_GTK_MENUITEM
);
2156 gtk_style_context_get_color(style
, GTK_STATE_FLAG_PRELIGHT
, &color
);
2157 mMenuHover
.mFg
= GDK_RGBA_TO_NS_RGBA(color
);
2158 mMenuHover
.mBg
= NS_ComposeColors(
2160 GetBackgroundColor(style
, mMenu
.mFg
, GTK_STATE_FLAG_PRELIGHT
, mMenu
.mBg
));
2162 GtkWidget
* parent
= gtk_fixed_new();
2163 GtkWidget
* window
= gtk_window_new(GTK_WINDOW_POPUP
);
2164 GtkWidget
* treeView
= gtk_tree_view_new();
2165 GtkWidget
* linkButton
= gtk_link_button_new("http://example.com/");
2166 GtkWidget
* menuBar
= gtk_menu_bar_new();
2167 GtkWidget
* menuBarItem
= gtk_menu_item_new();
2168 GtkWidget
* entry
= gtk_entry_new();
2169 GtkWidget
* textView
= gtk_text_view_new();
2171 gtk_container_add(GTK_CONTAINER(parent
), treeView
);
2172 gtk_container_add(GTK_CONTAINER(parent
), linkButton
);
2173 gtk_container_add(GTK_CONTAINER(parent
), menuBar
);
2174 gtk_menu_shell_append(GTK_MENU_SHELL(menuBar
), menuBarItem
);
2175 gtk_container_add(GTK_CONTAINER(window
), parent
);
2176 gtk_container_add(GTK_CONTAINER(parent
), entry
);
2177 gtk_container_add(GTK_CONTAINER(parent
), textView
);
2181 // If the text window background is translucent, then the background of
2182 // the textview root node is visible.
2183 style
= GetStyleContext(MOZ_GTK_TEXT_VIEW
);
2184 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
,
2187 style
= GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT
);
2188 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2189 ApplyColorOver(color
, &bgColor
);
2190 mField
.mBg
= GDK_RGBA_TO_NS_RGBA(bgColor
);
2191 gtk_style_context_get_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2192 mField
.mFg
= GDK_RGBA_TO_NS_RGBA(color
);
2195 // Selected text and background
2197 GtkStyleContext
* selectionStyle
=
2198 GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT_SELECTION
);
2199 auto GrabSelectionColors
= [&](GtkStyleContext
* style
) {
2200 gtk_style_context_get_background_color(
2202 static_cast<GtkStateFlags
>(GTK_STATE_FLAG_FOCUSED
|
2203 GTK_STATE_FLAG_SELECTED
),
2205 mSelectedText
.mBg
= GDK_RGBA_TO_NS_RGBA(color
);
2206 gtk_style_context_get_color(
2208 static_cast<GtkStateFlags
>(GTK_STATE_FLAG_FOCUSED
|
2209 GTK_STATE_FLAG_SELECTED
),
2211 mSelectedText
.mFg
= GDK_RGBA_TO_NS_RGBA(color
);
2213 GrabSelectionColors(selectionStyle
);
2214 if (mSelectedText
.mBg
== mSelectedText
.mFg
) {
2215 // Some old distros/themes don't properly use the .selection style, so
2216 // fall back to the regular text view style.
2217 GrabSelectionColors(style
);
2220 // Default selected item color is the selection background / foreground
2221 // colors, but we prefer named colors, as those are more general purpose
2222 // than the actual selection style, which might e.g. be too-transparent.
2224 // NOTE(emilio): It's unclear which one of the theme_selected_* or the
2225 // selected_* pairs should we prefer, in all themes that define both that
2226 // I've found, they're always the same.
2227 if (!GetNamedColorPair(style
, "selected_bg_color", "selected_fg_color",
2229 !GetNamedColorPair(style
, "theme_selected_bg_color",
2230 "theme_selected_fg_color", &mSelectedItem
)) {
2231 mSelectedItem
= mSelectedText
;
2234 EnsureColorPairIsOpaque(mSelectedItem
);
2236 // In a similar fashion, default accent color is the selected item/text
2237 // pair, but we also prefer named colors, if available.
2239 // accent_{bg,fg}_color is not _really_ a gtk3 thing (it's a gtk4 thing),
2240 // but if gtk 3 themes want to specify these we let them, see:
2242 // https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/named-colors.html#accent-colors
2243 if (!GetNamedColorPair(style
, "accent_bg_color", "accent_fg_color",
2245 mAccent
= mSelectedItem
;
2248 EnsureColorPairIsOpaque(mAccent
);
2249 PreferDarkerBackground(mAccent
);
2252 // Button text color
2253 style
= GetStyleContext(MOZ_GTK_BUTTON
);
2255 GtkStyleContext
* labelStyle
= CreateStyleForWidget(labelWidget
, style
);
2256 GetSystemFontInfo(labelStyle
, &mButtonFontName
, &mButtonFontStyle
);
2257 g_object_unref(labelStyle
);
2260 gtk_style_context_get_border_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2261 mButtonBorder
= GDK_RGBA_TO_NS_RGBA(color
);
2262 mButton
= GetColorPair(style
);
2263 mButtonHover
= GetColorPair(style
, GTK_STATE_FLAG_PRELIGHT
);
2264 mButtonActive
= GetColorPair(style
, GTK_STATE_FLAG_ACTIVE
);
2265 if (!NS_GET_A(mButtonHover
.mBg
)) {
2266 mButtonHover
.mBg
= mWindow
.mBg
;
2268 if (!NS_GET_A(mButtonActive
.mBg
)) {
2269 mButtonActive
.mBg
= mWindow
.mBg
;
2272 // Combobox text color
2273 style
= GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA
);
2274 gtk_style_context_get_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2275 mComboBoxText
= GDK_RGBA_TO_NS_RGBA(color
);
2277 // GTK's guide to fancy odd row background colors:
2278 // 1) Check if a theme explicitly defines an odd row color
2279 // 2) If not, check if it defines an even row color, and darken it
2280 // slightly by a hardcoded value (gtkstyle.c)
2281 // 3) If neither are defined, take the base background color and
2282 // darken that by a hardcoded value
2283 style
= GetStyleContext(MOZ_GTK_TREEVIEW
);
2285 // Get odd row background color
2286 gtk_style_context_save(style
);
2287 gtk_style_context_add_region(style
, GTK_STYLE_REGION_ROW
, GTK_REGION_ODD
);
2288 gtk_style_context_get_background_color(style
, GTK_STATE_FLAG_NORMAL
, &color
);
2289 mOddCellBackground
= GDK_RGBA_TO_NS_RGBA(color
);
2290 gtk_style_context_restore(style
);
2292 // Column header colors
2293 style
= GetStyleContext(MOZ_GTK_TREE_HEADER_CELL
);
2294 mMozColHeader
= GetColorPair(style
, GTK_STATE_FLAG_NORMAL
);
2295 mMozColHeaderHover
= GetColorPair(style
, GTK_STATE_FLAG_NORMAL
);
2296 mMozColHeaderActive
= GetColorPair(style
, GTK_STATE_FLAG_ACTIVE
);
2298 // Compute cell highlight colors
2299 InitCellHighlightColors();
2301 // GtkFrame has a "border" subnode on which Adwaita draws the border.
2302 // Some themes do not draw on this node but draw a border on the widget
2303 // root node, so check the root node if no border is found on the border
2305 style
= GetStyleContext(MOZ_GTK_FRAME_BORDER
);
2306 bool themeUsesColors
=
2307 GetBorderColors(style
, &mThreeDHighlight
, &mThreeDShadow
);
2308 if (!themeUsesColors
) {
2309 style
= GetStyleContext(MOZ_GTK_FRAME
);
2310 GetBorderColors(style
, &mThreeDHighlight
, &mThreeDShadow
);
2312 mSidebarBorder
= mThreeDShadow
;
2314 // Some themes have a unified menu bar, and support window dragging on it
2315 gboolean supports_menubar_drag
= FALSE
;
2316 GParamSpec
* param_spec
= gtk_widget_class_find_style_property(
2317 GTK_WIDGET_GET_CLASS(menuBar
), "window-dragging");
2319 if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec
), G_TYPE_BOOLEAN
)) {
2320 gtk_widget_style_get(menuBar
, "window-dragging", &supports_menubar_drag
,
2324 mMenuSupportsDrag
= supports_menubar_drag
;
2326 // TODO: It returns wrong color for themes which
2327 // sets link color for GtkLabel only as we query
2328 // GtkLinkButton style here.
2329 style
= gtk_widget_get_style_context(linkButton
);
2330 gtk_style_context_get_color(style
, GTK_STATE_FLAG_LINK
, &color
);
2331 mNativeHyperLinkText
= GDK_RGBA_TO_NS_RGBA(color
);
2333 gtk_style_context_get_color(style
, GTK_STATE_FLAG_VISITED
, &color
);
2334 mNativeVisitedHyperLinkText
= GDK_RGBA_TO_NS_RGBA(color
);
2336 // invisible character styles
2338 g_object_get(entry
, "invisible-char", &value
, nullptr);
2339 mInvisibleCharacter
= char16_t(value
);
2342 gtk_widget_style_get(entry
, "cursor-aspect-ratio", &mCaretRatio
, nullptr);
2344 GetSystemFontInfo(gtk_widget_get_style_context(entry
), &mFieldFontName
,
2347 gtk_widget_destroy(window
);
2348 g_object_unref(labelWidget
);
2350 if (LOGLNF_ENABLED()) {
2351 LOGLNF("Initialized theme %s (%d)\n", mName
.get(), mPreferDarkTheme
);
2352 for (auto id
: MakeEnumeratedRange(ColorID::End
)) {
2354 nsresult rv
= GetColor(id
, color
);
2355 LOGLNF(" * color %d: pref=%s success=%d value=%x\n", int(id
),
2356 GetColorPrefName(id
), NS_SUCCEEDED(rv
),
2357 NS_SUCCEEDED(rv
) ? color
: 0);
2359 LOGLNF(" * titlebar-radius: %d\n", mTitlebarRadius
);
2364 char16_t
nsLookAndFeel::GetPasswordCharacterImpl() {
2366 return mSystemTheme
.mInvisibleCharacter
;
2369 bool nsLookAndFeel::GetEchoPasswordImpl() { return false; }
2371 bool nsLookAndFeel::GetDefaultDrawInTitlebar() { return sCSDAvailable
; }
2373 nsXPLookAndFeel::TitlebarAction
nsLookAndFeel::GetTitlebarAction(
2374 TitlebarEvent aEvent
) {
2375 return aEvent
== TitlebarEvent::Double_Click
? mDoubleClickAction
2376 : mMiddleClickAction
;
2379 void nsLookAndFeel::GetThemeInfo(nsACString
& aInfo
) {
2380 aInfo
.Append(mSystemTheme
.mName
);
2381 aInfo
.Append(" / ");
2382 aInfo
.Append(mAltTheme
.mName
);
2385 bool nsLookAndFeel::WidgetUsesImage(WidgetNodeType aNodeType
) {
2386 static constexpr GtkStateFlags sFlagsToCheck
[]{
2387 GTK_STATE_FLAG_NORMAL
, GTK_STATE_FLAG_PRELIGHT
,
2388 GtkStateFlags(GTK_STATE_FLAG_PRELIGHT
| GTK_STATE_FLAG_ACTIVE
),
2389 GTK_STATE_FLAG_BACKDROP
, GTK_STATE_FLAG_INSENSITIVE
};
2391 GtkStyleContext
* style
= GetStyleContext(aNodeType
);
2393 GValue value
= G_VALUE_INIT
;
2394 for (GtkStateFlags state
: sFlagsToCheck
) {
2395 gtk_style_context_get_property(style
, "background-image", state
, &value
);
2396 bool hasPattern
= G_VALUE_TYPE(&value
) == CAIRO_GOBJECT_TYPE_PATTERN
&&
2397 g_value_get_boxed(&value
);
2398 g_value_unset(&value
);
2406 nsresult
nsLookAndFeel::GetKeyboardLayoutImpl(nsACString
& aLayout
) {
2407 if (mozilla::widget::GdkIsX11Display()) {
2408 #if defined(MOZ_X11)
2409 Display
* display
= gdk_x11_get_default_xdisplay();
2411 return NS_ERROR_NOT_AVAILABLE
;
2413 XkbDescRec
* kbdDesc
= XkbAllocKeyboard();
2415 return NS_ERROR_NOT_AVAILABLE
;
2417 auto cleanup
= MakeScopeExit([&] { XkbFreeKeyboard(kbdDesc
, 0, true); });
2420 XkbGetState(display
, XkbUseCoreKbd
, &state
);
2421 uint32_t group
= state
.group
;
2423 XkbGetNames(display
, XkbGroupNamesMask
, kbdDesc
);
2425 if (!kbdDesc
->names
|| !kbdDesc
->names
->groups
[group
]) {
2426 return NS_ERROR_NOT_AVAILABLE
;
2429 char* layout
= XGetAtomName(display
, kbdDesc
->names
->groups
[group
]);
2431 aLayout
.Assign(layout
);
2434 #if defined(MOZ_WAYLAND)
2435 struct xkb_context
* context
= xkb_context_new(XKB_CONTEXT_NO_FLAGS
);
2437 return NS_ERROR_NOT_AVAILABLE
;
2439 auto cleanupContext
= MakeScopeExit([&] { xkb_context_unref(context
); });
2441 struct xkb_keymap
* keymap
= xkb_keymap_new_from_names(
2442 context
, nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS
);
2444 return NS_ERROR_NOT_AVAILABLE
;
2446 auto cleanupKeymap
= MakeScopeExit([&] { xkb_keymap_unref(keymap
); });
2448 const char* layout
= xkb_keymap_layout_get_name(keymap
, 0);
2451 aLayout
.Assign(layout
);
2459 void nsLookAndFeel::RecordLookAndFeelSpecificTelemetry() {
2460 // Gtk version we're on.
2462 version
.AppendPrintf("%d.%d", gtk_major_version
, gtk_minor_version
);
2463 glean::widget::gtk_version
.Set(version
);
2466 bool nsLookAndFeel::ShouldHonorThemeScrollbarColors() {
2467 // If the Gtk theme uses anything other than solid color backgrounds for Gtk
2468 // scrollbar parts, this is a good indication that painting XUL scrollbar part
2469 // elements using colors extracted from the theme won't provide good results.
2470 return !WidgetUsesImage(MOZ_GTK_SCROLLBAR_VERTICAL
) &&
2471 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL
) &&
2472 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
) &&
2473 !WidgetUsesImage(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL
);
2477 #undef LOGLNF_ENABLED