Backed out changeset 82c7a343a1a1 (bug 1474705) for causing linting opt failures...
[gecko.git] / widget / gtk / nsLookAndFeel.cpp
blob1e73b09a25303b3a62987a29a74752720113b12d
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
3 */
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/. */
8 // for strtod()
9 #include <stdlib.h>
10 #include <dlfcn.h>
12 #include "nsLookAndFeel.h"
14 #include <gtk/gtk.h>
15 #include <gdk/gdk.h>
17 #include <pango/pango.h>
18 #include <pango/pango-fontmap.h>
19 #include <fontconfig/fontconfig.h>
21 #include "GRefPtr.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"
39 #include "nsString.h"
40 #include "nsStyleConsts.h"
41 #include "gfxFontConstants.h"
42 #include "WidgetUtils.h"
43 #include "nsWindow.h"
45 #include "mozilla/gfx/2D.h"
47 #include <cairo-gobject.h>
48 #include <dlfcn.h>
49 #include "WidgetStyleCache.h"
50 #include "prenv.h"
51 #include "nsCSSColorUtils.h"
52 #include "mozilla/Preferences.h"
54 #ifdef MOZ_X11
55 # include <X11/XKBlib.h>
56 #endif
58 #ifdef MOZ_WAYLAND
59 # include <xkbcommon/xkbcommon.h>
60 #endif
62 using namespace mozilla;
63 using namespace mozilla::widget;
65 #ifdef MOZ_LOGGING
66 # include "mozilla/Logging.h"
67 # include "nsTArray.h"
68 # include "Units.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)
72 #else
73 # define LOGLNF(args)
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) {
87 return;
89 // TODO: We could be more granular here, but for now assume everything
90 // changed.
91 LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::StyleAndLayout);
92 widget::IMContextWrapper::OnThemeChanged();
95 static void settings_changed_cb(GtkSettings*, GParamSpec*, void*) {
96 OnSettingsChange();
99 static bool sCSDAvailable;
101 static nsCString GVariantToString(GVariant* aVariant) {
102 nsCString ret;
103 gchar* s = g_variant_print(aVariant, TRUE);
104 if (s) {
105 ret.Assign(s);
106 g_free(s);
108 return ret;
111 static nsDependentCString GVariantGetString(GVariant* aVariant) {
112 gsize len = 0;
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");
124 return;
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");
132 return;
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);
154 } else {
155 LOGLNF("Can't create DBus proxy for settings: %s\n", error->message);
156 return;
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,
180 // System fonts?
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);
211 sCSDAvailable =
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);
221 lnf->WatchDBus();
223 [](GDBusConnection*, const gchar*, gpointer data) -> void {
224 auto* lnf = static_cast<nsLookAndFeel*>(data);
225 lnf->UnwatchDBus();
227 this, nullptr);
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();
244 if (mDBusID) {
245 g_bus_unwatch_name(mDBusID);
246 mDBusID = 0;
248 UnwatchDBus();
249 g_signal_handlers_disconnect_by_func(
250 gtk_settings_get_default(), FuncToGpointer(settings_changed_cb), nullptr);
253 #if 0
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);
259 printf("%s\n", str);
260 g_free(str);
261 str = gtk_widget_path_to_string(gtk_style_context_get_path(aStyle));
262 printf("%s\n", str);
263 g_free(str);
265 #endif
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,
284 double* aDarkness) {
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)) {
293 return false;
296 auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
297 if (!pattern) {
298 return false;
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)) {
307 return false;
310 double maxLightness, maxDarkness;
311 GetLightAndDarkness(*aDarkColor, &maxLightness, &maxDarkness);
312 *aLightColor = *aDarkColor;
314 GdkRGBA stop;
315 for (int index = 1;
316 CAIRO_STATUS_SUCCESS ==
317 cairo_pattern_get_color_stop_rgba(pattern, index, nullptr, &stop.red,
318 &stop.green, &stop.blue, &stop.alpha);
319 ++index) {
320 double lightness, darkness;
321 GetLightAndDarkness(stop, &lightness, &darkness);
322 if (lightness > maxLightness) {
323 maxLightness = lightness;
324 *aLightColor = stop;
326 if (darkness > maxDarkness) {
327 maxDarkness = darkness;
328 *aDarkColor = stop;
332 return true;
335 static bool GetColorFromImagePattern(const GValue* aValue, nscolor* aColor) {
336 if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN)) {
337 return false;
340 auto* pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
341 if (!pattern) {
342 return false;
345 cairo_surface_t* surface;
346 if (cairo_pattern_get_surface(pattern, &surface) != CAIRO_STATUS_SUCCESS) {
347 return false;
350 cairo_format_t format = cairo_image_surface_get_format(surface);
351 if (format == CAIRO_FORMAT_INVALID) {
352 return false;
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) {
358 return false;
361 // Guesstimate the central pixel would have a sensible color.
362 int x = width / 2;
363 int y = height / 2;
365 unsigned char* data = cairo_image_surface_get_data(surface);
366 switch (format) {
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();
372 return true;
374 default:
375 break;
378 return false;
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
392 // engine.)
393 const char* propertyName = "-unico-border-gradient";
394 if (!gtk_style_properties_lookup_property(propertyName, nullptr, nullptr))
395 return false;
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)
402 return false;
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);
412 return result;
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
419 // render a border.
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;
429 if (visible) {
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.
432 GtkBorder border;
433 gtk_style_context_get_border(aContext, state, &border);
434 visible = border.top != 0 || border.right != 0 || border.bottom != 0 ||
435 border.left != 0;
438 if (visible &&
439 GetUnicoBorderGradientColors(aContext, aLightColor, aDarkColor))
440 return true;
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;
450 return visible;
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);
459 return ret;
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;
471 return;
474 uint16_t hue, sat, luminance;
475 uint8_t alpha;
476 mCellHighlight = mField;
478 NS_RGB2HSV(mCellHighlight.mBg, hue, sat, luminance, alpha);
480 uint16_t step = 30;
481 // Lighten the color if the color is very dark
482 if (luminance <= step) {
483 luminance += step;
485 // Darken it if it is very light
486 else if (luminance >= 255 - step) {
487 luminance -= step;
489 // Otherwise, compute what works best depending on the text luminance.
490 else {
491 uint16_t textHue, textSat, textLuminance;
492 uint8_t textAlpha;
493 NS_RGB2HSV(mCellHighlight.mFg, textHue, textSat, textLuminance, textAlpha);
494 // Text is darker than background, use a lighter shade
495 if (textLuminance < luminance) {
496 luminance += step;
498 // Otherwise, use a darker shade
499 else {
500 luminance -= step;
503 NS_HSV2RGB(mCellHighlight.mBg, hue, sat, luminance, alpha);
506 void nsLookAndFeel::NativeInit() { EnsureInit(); }
508 void nsLookAndFeel::RefreshImpl() {
509 mInitialized = false;
510 moz_gtk_refresh();
512 nsXPLookAndFeel::RefreshImpl();
515 nsresult nsLookAndFeel::NativeGetColor(ColorID aID, ColorScheme aScheme,
516 nscolor& aColor) {
517 EnsureInit();
519 const auto& theme =
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,
533 bool aIsDark) {
534 if (!aIsDark) {
535 return true;
537 if (StaticPrefs::widget_non_native_theme_scrollbar_dark_themed()) {
538 return true;
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;
548 switch (aID) {
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;
557 break;
558 case ColorID::Windowtext:
559 aColor = mWindow.mFg;
560 break;
561 case ColorID::MozDialog:
562 aColor = mDialog.mBg;
563 break;
564 case ColorID::MozDialogtext:
565 aColor = mDialog.mFg;
566 break;
567 case ColorID::IMESelectedRawTextBackground:
568 case ColorID::IMESelectedConvertedTextBackground:
569 case ColorID::Highlight: // preference selected item,
570 aColor = mSelectedText.mBg;
571 break;
572 case ColorID::Highlighttext:
573 if (NS_GET_A(mSelectedText.mBg) < 155) {
574 aColor = NS_SAME_AS_FOREGROUND_COLOR;
575 break;
577 [[fallthrough]];
578 case ColorID::IMESelectedRawTextForeground:
579 case ColorID::IMESelectedConvertedTextForeground:
580 aColor = mSelectedText.mFg;
581 break;
582 case ColorID::Selecteditem:
583 aColor = mSelectedItem.mBg;
584 break;
585 case ColorID::Selecteditemtext:
586 aColor = mSelectedItem.mFg;
587 break;
588 case ColorID::Accentcolor:
589 aColor = mAccent.mBg;
590 break;
591 case ColorID::Accentcolortext:
592 aColor = mAccent.mFg;
593 break;
594 case ColorID::MozCellhighlight:
595 aColor = mCellHighlight.mBg;
596 break;
597 case ColorID::MozCellhighlighttext:
598 aColor = mCellHighlight.mFg;
599 break;
600 case ColorID::IMERawInputBackground:
601 case ColorID::IMEConvertedTextBackground:
602 aColor = NS_TRANSPARENT;
603 break;
604 case ColorID::IMERawInputForeground:
605 case ColorID::IMEConvertedTextForeground:
606 aColor = NS_SAME_AS_FOREGROUND_COLOR;
607 break;
608 case ColorID::IMERawInputUnderline:
609 case ColorID::IMEConvertedTextUnderline:
610 aColor = NS_SAME_AS_FOREGROUND_COLOR;
611 break;
612 case ColorID::IMESelectedRawTextUnderline:
613 case ColorID::IMESelectedConvertedTextUnderline:
614 aColor = NS_TRANSPARENT;
615 break;
616 case ColorID::Scrollbar:
617 aColor = mThemedScrollbar;
618 break;
619 case ColorID::ThemedScrollbar:
620 aColor = mThemedScrollbar;
621 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
622 return NS_ERROR_FAILURE;
624 break;
625 case ColorID::ThemedScrollbarInactive:
626 aColor = mThemedScrollbarInactive;
627 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
628 return NS_ERROR_FAILURE;
630 break;
631 case ColorID::ThemedScrollbarThumb:
632 aColor = mThemedScrollbarThumb;
633 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
634 return NS_ERROR_FAILURE;
636 break;
637 case ColorID::ThemedScrollbarThumbHover:
638 aColor = mThemedScrollbarThumbHover;
639 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
640 return NS_ERROR_FAILURE;
642 break;
643 case ColorID::ThemedScrollbarThumbActive:
644 aColor = mThemedScrollbarThumbActive;
645 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
646 return NS_ERROR_FAILURE;
648 break;
649 case ColorID::ThemedScrollbarThumbInactive:
650 aColor = mThemedScrollbarThumbInactive;
651 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
652 return NS_ERROR_FAILURE;
654 break;
656 // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
657 case ColorID::Activeborder:
658 // active window border
659 aColor = mMozWindowActiveBorder;
660 break;
661 case ColorID::Inactiveborder:
662 // inactive window border
663 aColor = mMozWindowInactiveBorder;
664 break;
665 case ColorID::Graytext: // disabled text in windows, menus, etc.
666 aColor = mGrayText;
667 break;
668 case ColorID::Activecaption:
669 aColor = mTitlebar.mBg;
670 break;
671 case ColorID::Captiontext: // text in active window caption (titlebar)
672 aColor = mTitlebar.mFg;
673 break;
674 case ColorID::Inactivecaption:
675 // inactive window caption
676 aColor = mTitlebarInactive.mBg;
677 break;
678 case ColorID::Inactivecaptiontext:
679 aColor = mTitlebarInactive.mFg;
680 break;
681 case ColorID::Infobackground:
682 aColor = mInfo.mBg;
683 break;
684 case ColorID::Infotext:
685 aColor = mInfo.mFg;
686 break;
687 case ColorID::Menu:
688 aColor = mMenu.mBg;
689 break;
690 case ColorID::Menutext:
691 aColor = mMenu.mFg;
692 break;
693 case ColorID::MozHeaderbar:
694 aColor = mHeaderBar.mBg;
695 break;
696 case ColorID::MozHeaderbartext:
697 aColor = mHeaderBar.mFg;
698 break;
699 case ColorID::MozHeaderbarinactive:
700 aColor = mHeaderBarInactive.mBg;
701 break;
702 case ColorID::MozHeaderbarinactivetext:
703 aColor = mHeaderBarInactive.mFg;
704 break;
705 case ColorID::Threedface:
706 case ColorID::Buttonface:
707 case ColorID::MozButtondisabledface:
708 // 3-D face color
709 aColor = mWindow.mBg;
710 break;
712 case ColorID::Buttontext:
713 // text on push buttons
714 aColor = mButton.mFg;
715 break;
717 case ColorID::Buttonhighlight:
718 // 3-D highlighted edge color
719 case ColorID::Threedhighlight:
720 // 3-D highlighted outer edge color
721 aColor = mThreeDHighlight;
722 break;
724 case ColorID::Buttonshadow:
725 // 3-D shadow edge color
726 case ColorID::Threedshadow:
727 // 3-D shadow inner edge color
728 aColor = mThreeDShadow;
729 break;
730 case ColorID::Buttonborder:
731 aColor = mButtonBorder;
732 break;
733 case ColorID::Threedlightshadow:
734 case ColorID::MozDisabledfield:
735 aColor = mIsDark ? *GenericDarkColor(aID) : NS_RGB(0xE0, 0xE0, 0xE0);
736 break;
737 case ColorID::Threeddarkshadow:
738 aColor = mIsDark ? *GenericDarkColor(aID) : NS_RGB(0xDC, 0xDC, 0xDC);
739 break;
741 case ColorID::MozEventreerow:
742 case ColorID::Field:
743 aColor = mField.mBg;
744 break;
745 case ColorID::Fieldtext:
746 aColor = mField.mFg;
747 break;
748 case ColorID::MozSidebar:
749 aColor = mSidebar.mBg;
750 break;
751 case ColorID::MozSidebartext:
752 aColor = mSidebar.mFg;
753 break;
754 case ColorID::MozSidebarborder:
755 aColor = mSidebarBorder;
756 break;
757 case ColorID::MozButtonhoverface:
758 aColor = mButtonHover.mBg;
759 break;
760 case ColorID::MozButtonhovertext:
761 aColor = mButtonHover.mFg;
762 break;
763 case ColorID::MozButtonactiveface:
764 aColor = mButtonActive.mBg;
765 break;
766 case ColorID::MozButtonactivetext:
767 aColor = mButtonActive.mFg;
768 break;
769 case ColorID::MozMenuhover:
770 aColor = mMenuHover.mBg;
771 break;
772 case ColorID::MozMenuhovertext:
773 aColor = mMenuHover.mFg;
774 break;
775 case ColorID::MozMenuhoverdisabled:
776 aColor = NS_TRANSPARENT;
777 break;
778 case ColorID::MozOddtreerow:
779 aColor = mOddCellBackground;
780 break;
781 case ColorID::MozNativehyperlinktext:
782 aColor = mNativeHyperLinkText;
783 break;
784 case ColorID::MozNativevisitedhyperlinktext:
785 aColor = mNativeVisitedHyperLinkText;
786 break;
787 case ColorID::MozComboboxtext:
788 aColor = mComboBoxText;
789 break;
790 case ColorID::MozColheader:
791 aColor = mMozColHeader.mBg;
792 break;
793 case ColorID::MozColheadertext:
794 aColor = mMozColHeader.mFg;
795 break;
796 case ColorID::MozColheaderhover:
797 aColor = mMozColHeaderHover.mBg;
798 break;
799 case ColorID::MozColheaderhovertext:
800 aColor = mMozColHeaderHover.mFg;
801 break;
802 case ColorID::MozColheaderactive:
803 aColor = mMozColHeaderActive.mBg;
804 break;
805 case ColorID::MozColheaderactivetext:
806 aColor = mMozColHeaderActive.mFg;
807 break;
808 case ColorID::SpellCheckerUnderline:
809 case ColorID::Mark:
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);
816 break;
817 default:
818 /* default color is BLACK */
819 aColor = 0;
820 res = NS_ERROR_FAILURE;
821 break;
824 return res;
827 static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle,
828 int32_t aResult) {
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.
856 switch (aID) {
857 case IntID::ScrollButtonLeftMouseButtonAction:
858 aResult = 0;
859 break;
860 case IntID::ScrollButtonMiddleMouseButtonAction:
861 aResult = 1;
862 break;
863 case IntID::ScrollButtonRightMouseButtonAction:
864 aResult = 2;
865 break;
866 case IntID::CaretBlinkTime:
867 EnsureInit();
868 aResult = mCaretBlinkTime;
869 break;
870 case IntID::CaretBlinkCount:
871 EnsureInit();
872 aResult = mCaretBlinkCount;
873 break;
874 case IntID::CaretWidth:
875 aResult = 1;
876 break;
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,
883 nullptr);
885 if (select_on_focus)
886 aResult = 1;
887 else
888 aResult = 0;
890 } break;
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,
899 nullptr);
902 if (warps_slider)
903 aResult = 1;
904 else
905 aResult = 0;
906 } break;
907 case IntID::SubmenuDelay: {
908 GtkSettings* settings;
909 gint delay;
911 settings = gtk_settings_get_default();
912 g_object_get(settings, "gtk-menu-popup-delay", &delay, nullptr);
913 aResult = (int32_t)delay;
914 break;
916 case IntID::MenusCanOverlapOSBar:
917 aResult = 0;
918 break;
919 case IntID::SkipNavigatingDisabledMenuItem:
920 aResult = 1;
921 break;
922 case IntID::DragThresholdX:
923 case IntID::DragThresholdY: {
924 gint threshold = 0;
925 g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold",
926 &threshold, nullptr);
928 aResult = threshold;
929 } break;
930 case IntID::ScrollArrowStyle: {
931 GtkWidget* scrollbar = GetWidget(MOZ_GTK_SCROLLBAR_VERTICAL);
932 aResult = ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar);
933 break;
935 case IntID::TreeOpenDelay:
936 aResult = 1000;
937 break;
938 case IntID::TreeCloseDelay:
939 aResult = 1000;
940 break;
941 case IntID::TreeLazyScrollDelay:
942 aResult = 150;
943 break;
944 case IntID::TreeScrollDelay:
945 aResult = 100;
946 break;
947 case IntID::TreeScrollLinesMax:
948 aResult = 3;
949 break;
950 case IntID::AlertNotificationOrigin:
951 aResult = NS_ALERT_TOP;
952 break;
953 case IntID::IMERawInputUnderlineStyle:
954 case IntID::IMEConvertedTextUnderlineStyle:
955 aResult = static_cast<int32_t>(StyleTextDecorationStyle::Solid);
956 break;
957 case IntID::IMESelectedRawTextUnderlineStyle:
958 case IntID::IMESelectedConvertedTextUnderline:
959 aResult = static_cast<int32_t>(StyleTextDecorationStyle::None);
960 break;
961 case IntID::SpellCheckerUnderlineStyle:
962 aResult = static_cast<int32_t>(StyleTextDecorationStyle::Wavy);
963 break;
964 case IntID::MenuBarDrag:
965 EnsureInit();
966 aResult = mSystemTheme.mMenuSupportsDrag;
967 break;
968 case IntID::ScrollbarButtonAutoRepeatBehavior:
969 aResult = 1;
970 break;
971 case IntID::SwipeAnimationEnabled:
972 aResult = 1;
973 break;
974 case IntID::ContextMenuOffsetVertical:
975 case IntID::ContextMenuOffsetHorizontal:
976 aResult = 2;
977 break;
978 case IntID::GTKCSDAvailable:
979 aResult = sCSDAvailable;
980 break;
981 case IntID::GTKCSDMaximizeButton:
982 EnsureInit();
983 aResult = mCSDMaximizeButton;
984 break;
985 case IntID::GTKCSDMinimizeButton:
986 EnsureInit();
987 aResult = mCSDMinimizeButton;
988 break;
989 case IntID::GTKCSDCloseButton:
990 EnsureInit();
991 aResult = mCSDCloseButton;
992 break;
993 case IntID::GTKCSDReversedPlacement:
994 EnsureInit();
995 aResult = mCSDReversedPlacement;
996 break;
997 case IntID::PrefersReducedMotion: {
998 EnsureInit();
999 aResult = mPrefersReducedMotion;
1000 break;
1002 case IntID::SystemUsesDarkTheme: {
1003 EnsureInit();
1004 if (mColorSchemePreference) {
1005 aResult = *mColorSchemePreference == ColorScheme::Dark;
1006 } else {
1007 aResult = mSystemTheme.mIsDark;
1009 break;
1011 case IntID::GTKCSDMaximizeButtonPosition:
1012 aResult = mCSDMaximizeButtonPosition;
1013 break;
1014 case IntID::GTKCSDMinimizeButtonPosition:
1015 aResult = mCSDMinimizeButtonPosition;
1016 break;
1017 case IntID::GTKCSDCloseButtonPosition:
1018 aResult = mCSDCloseButtonPosition;
1019 break;
1020 case IntID::GTKThemeFamily: {
1021 EnsureInit();
1022 aResult = int32_t(EffectiveTheme().mFamily);
1023 break;
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:
1029 EnsureInit();
1030 aResult = mSystemTheme.mHighContrast;
1031 break;
1032 case IntID::InvertedColors:
1033 // No GTK API for checking if inverted colors is enabled
1034 aResult = 0;
1035 break;
1036 case IntID::TitlebarRadius: {
1037 EnsureInit();
1038 aResult = EffectiveTheme().mTitlebarRadius;
1039 break;
1041 case IntID::TitlebarButtonSpacing: {
1042 EnsureInit();
1043 aResult = EffectiveTheme().mTitlebarButtonSpacing;
1044 break;
1046 case IntID::AllowOverlayScrollbarsOverlap: {
1047 aResult = 1;
1048 break;
1050 case IntID::ScrollbarFadeBeginDelay: {
1051 aResult = 1000;
1052 break;
1054 case IntID::ScrollbarFadeDuration: {
1055 aResult = 400;
1056 break;
1058 case IntID::ScrollbarDisplayOnMouseMove: {
1059 aResult = 1;
1060 break;
1062 case IntID::PanelAnimations:
1063 aResult = [&]() -> bool {
1064 if (!sCSDAvailable) {
1065 // Disabled on systems without CSD, see bug 1385079.
1066 return false;
1068 if (GdkIsWaylandDisplay()) {
1069 // Disabled on wayland, see bug 1800442 and bug 1800368.
1070 return false;
1072 return true;
1073 }();
1074 break;
1075 case IntID::UseOverlayScrollbars: {
1076 aResult = StaticPrefs::widget_gtk_overlay_scrollbars_enabled();
1077 break;
1079 case IntID::HideCursorWhileTyping: {
1080 aResult = StaticPrefs::widget_gtk_hide_pointer_while_typing_enabled();
1081 break;
1083 case IntID::TouchDeviceSupportPresent:
1084 aResult = widget::WidgetUtilsGTK::IsTouchDeviceSupportPresent();
1085 break;
1086 default:
1087 aResult = 0;
1088 res = NS_ERROR_FAILURE;
1091 return res;
1094 nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
1095 nsresult rv = NS_OK;
1096 switch (aID) {
1097 case FloatID::IMEUnderlineRelativeSize:
1098 aResult = 1.0f;
1099 break;
1100 case FloatID::SpellCheckerUnderlineRelativeSize:
1101 aResult = 1.0f;
1102 break;
1103 case FloatID::CaretAspectRatio:
1104 EnsureInit();
1105 aResult = mSystemTheme.mCaretRatio;
1106 break;
1107 case FloatID::TextScaleFactor:
1108 aResult = gfxPlatformGtk::GetFontScaleFactor();
1109 break;
1110 default:
1111 aResult = -1.0;
1112 rv = NS_ERROR_FAILURE;
1114 return rv;
1117 static void GetSystemFontInfo(GtkStyleContext* aStyle, nsString* aFontName,
1118 gfxFontStyle* aFontStyle) {
1119 aFontStyle->style = FontSlantStyle::NORMAL;
1121 // As in
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",
1125 &desc, nullptr);
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 {
1161 switch (aID) {
1162 case FontID::Menu: // css2
1163 case FontID::MozPullDownMenu: // css3
1164 aFontName = mMenuFontName;
1165 aFontStyle = mMenuFontStyle;
1166 break;
1168 case FontID::MozField: // css3
1169 case FontID::MozList: // css3
1170 aFontName = mFieldFontName;
1171 aFontStyle = mFieldFontStyle;
1172 break;
1174 case FontID::MozButton: // css3
1175 aFontName = mButtonFontName;
1176 aFontStyle = mButtonFontStyle;
1177 break;
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
1184 default:
1185 aFontName = mDefaultFontName;
1186 aFontStyle = mDefaultFontStyle;
1187 break;
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();
1195 return true;
1198 static nsCString GetGtkSettingsStringKey(const char* aKey) {
1199 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
1200 nsCString ret;
1201 GtkSettings* settings = gtk_settings_get_default();
1202 char* value = nullptr;
1203 g_object_get(settings, aKey, &value, nullptr);
1204 if (value) {
1205 ret.Assign(value);
1206 g_free(value);
1208 return ret;
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,
1219 nullptr);
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() {
1227 GdkRGBA bg, fg;
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) {
1240 return;
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");
1252 } else {
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();
1259 moz_gtk_refresh();
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;
1275 // clang-format off
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,
1282 // clang-format on
1283 bool found = false;
1284 for (const auto& s : kSubstringsToRemove) {
1285 potentialLightThemeName = mSystemTheme.mName;
1286 potentialLightThemeName.ReplaceSubstring(s, ""_ns);
1287 if (potentialLightThemeName.Length() != mSystemTheme.mName.Length()) {
1288 found = true;
1289 break;
1292 if (found) {
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,
1297 nullptr);
1298 moz_gtk_refresh();
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);
1309 moz_gtk_refresh();
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,
1319 nullptr);
1320 moz_gtk_refresh();
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
1324 // versions.
1325 if (!mSystemTheme.mIsDark && !GetThemeIsDark()) {
1326 LOGLNF(" last resort Adwaita-dark fallback");
1327 g_object_set(settings, "gtk-theme-name", "Adwaita-dark", nullptr);
1328 moz_gtk_refresh();
1331 return false;
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()) {
1354 return;
1357 if (light.mFamily == ThemeFamily::Adwaita) {
1358 // #323232 is rgba(0,0,0,.8) over #fafafa.
1359 light.mWindow =
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();
1392 mAltTheme.Init();
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
1416 // it.
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()) {
1433 return;
1435 int32_t radius = EffectiveTheme().mTitlebarRadius;
1436 if (!radius) {
1437 return;
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;"
1444 "}\n",
1445 radius, radius);
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)
1452 .get());
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.
1466 int32_t pref = 0;
1467 if (NS_SUCCEEDED(Preferences::GetInt("ui.systemUsesDarkTheme", &pref))) {
1468 return Some(pref ? ColorScheme::Dark : ColorScheme::Light);
1472 if (!mDBusSettingsProxy) {
1473 return Nothing();
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)));
1482 if (!variant) {
1483 LOGLNF("color-scheme query error: %s\n", error->message);
1484 return Nothing();
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");
1494 return Nothing();
1496 switch (g_variant_get_uint32(variant)) {
1497 default:
1498 MOZ_FALLTHROUGH_ASSERT("Unexpected color-scheme query return value");
1499 case 0:
1500 break;
1501 case 1:
1502 return Some(ColorScheme::Dark);
1503 case 2:
1504 return Some(ColorScheme::Light);
1506 return Nothing();
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");
1520 return;
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();
1548 RecordTelemetry();
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.
1555 return;
1557 OnSettingsChange();
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
1571 gboolean blink;
1572 g_object_get(settings, "gtk-cursor-blink-time", &blink_time,
1573 "gtk-cursor-blink-timeout", &blink_timeout, "gtk-cursor-blink",
1574 &blink, nullptr);
1575 // From
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.
1585 mCaretBlinkCount =
1586 std::max(1, int32_t(std::ceil(float(blink_timeout * 1000) /
1587 (float(blink_time) * 2.0f))));
1588 } else {
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
1608 // the left side.
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;
1615 break;
1616 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
1617 mCSDMaximizeButton = true;
1618 pos = &mCSDMaximizeButtonPosition;
1619 break;
1620 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
1621 mCSDCloseButton = true;
1622 pos = &mCSDCloseButtonPosition;
1623 break;
1624 default:
1625 break;
1628 if (pos) {
1629 *pos = i;
1633 struct actionMapping {
1634 TitlebarAction action;
1635 char name[100];
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);
1648 if (!action) {
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:
1677 break;
1679 if (!mColorSchemePreference) {
1680 return true;
1682 const bool preferenceIsDark = *mColorSchemePreference == ColorScheme::Dark;
1683 return preferenceIsDark == mSystemTheme.mIsDark;
1684 }();
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);
1702 } else {
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();
1709 moz_gtk_refresh();
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)) {
1720 return true;
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
1730 // overkill.
1731 if (NS_LUMINOSITY_DIFFERENCE(l, aForForegroundColor) >
1732 NS_LUMINOSITY_DIFFERENCE(d, aForForegroundColor)) {
1733 *aColor = l;
1734 } else {
1735 *aColor = d;
1737 return true;
1741 return false;
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,
1751 &imageColor)) {
1752 if (NS_GET_A(imageColor) == 255) {
1753 return imageColor;
1757 GdkRGBA gdkColor;
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) {
1763 return finalColor;
1765 return NS_TRANSPARENT;
1768 static nscolor GetTextColor(GtkStyleContext* aStyle,
1769 GtkStateFlags aState = GTK_STATE_FLAG_NORMAL) {
1770 GdkRGBA color;
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) {
1778 ColorPair result;
1779 result.mFg = GetTextColor(aStyle, aState);
1780 result.mBg = GetBackgroundColor(aStyle, result.mFg, aState);
1781 return result;
1784 static bool GetNamedColorPair(GtkStyleContext* aStyle, const char* aBgName,
1785 const char* aFgName, ColorPair* aPair) {
1786 GdkRGBA bg, fg;
1787 if (!gtk_style_context_lookup_color(aStyle, aBgName, &bg) ||
1788 !gtk_style_context_lookup_color(aStyle, aFgName, &fg)) {
1789 return false;
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
1797 // bug 1717077.
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
1805 // for symmetry.
1806 if (NS_GET_A(aPair->mFg) != 255) {
1807 aPair->mFg = NS_ComposeColors(aPair->mBg, aPair->mFg);
1810 return true;
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
1823 // we have.
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();
1835 mFamily = [&] {
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;
1846 }();
1848 GtkStyleContext* style;
1850 mHighContrast = StaticPrefs::widget_content_gtk_high_contrast_enabled() &&
1851 mName.Find("HighContrast"_ns) >= 0;
1853 mPreferDarkTheme = GetPreferDarkTheme();
1855 mIsDark = GetThemeIsDark();
1857 GdkRGBA color;
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,
1864 &color);
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);
1869 mThemedScrollbar =
1870 NS_ComposeColors(mThemedScrollbar, GDK_RGBA_TO_NS_RGBA(color));
1871 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
1872 &color);
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,
1880 &color);
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),
1884 &color);
1885 mThemedScrollbarThumbActive = GDK_RGBA_TO_NS_RGBA(color);
1886 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
1887 &color);
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()) {
1893 return true;
1896 if (!ShouldHonorThemeScrollbarColors()) {
1897 return true;
1899 // If any of the scrollbar thumb colors are fully transparent, fall back to
1900 // non-native ones.
1901 if (!NS_GET_A(mThemedScrollbarThumb) ||
1902 !NS_GET_A(mThemedScrollbarThumbHover) ||
1903 !NS_GET_A(mThemedScrollbarThumbActive)) {
1904 return true;
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) {
1910 return true;
1912 return false;
1913 }();
1915 if (fallbackToUnthemedColors) {
1916 if (mIsDark) {
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);
1922 } else {
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);
1943 // Window colors
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();
2010 return false;
2011 }();
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);
2023 } else {
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);
2035 mMenu.mBg = [&] {
2036 nscolor color = GetBackgroundColor(style, mMenu.mFg);
2037 if (NS_GET_A(color)) {
2038 return 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
2042 // up.
2043 color = GetBackgroundColor(style, mMenu.mFg, GTK_STATE_FLAG_BACKDROP);
2044 if (NS_GET_A(color)) {
2045 return 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.
2049 NS_WARNING(
2050 "Couldn't find menu background color, falling back to window "
2051 "background");
2052 return mWindow.mBg;
2053 }();
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(
2059 mMenu.mBg,
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);
2079 // Text colors
2080 GdkRGBA bgColor;
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,
2085 &bgColor);
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);
2093 mSidebar = mField;
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(
2101 style,
2102 static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
2103 GTK_STATE_FLAG_SELECTED),
2104 &color);
2105 mSelectedText.mBg = GDK_RGBA_TO_NS_RGBA(color);
2106 gtk_style_context_get_color(
2107 style,
2108 static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
2109 GTK_STATE_FLAG_SELECTED),
2110 &color);
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",
2128 &mSelectedItem) &&
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",
2144 &mAccent)) {
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
2204 // node.
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");
2218 if (param_spec) {
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,
2221 nullptr);
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
2237 guint value;
2238 g_object_get(entry, "invisible-char", &value, nullptr);
2239 mInvisibleCharacter = char16_t(value);
2241 // caret styles
2242 gtk_widget_style_get(entry, "cursor-aspect-ratio", &mCaretRatio, nullptr);
2244 GetSystemFontInfo(gtk_widget_get_style_context(entry), &mFieldFontName,
2245 &mFieldFontStyle);
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)) {
2253 nscolor color;
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);
2263 // virtual
2264 char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
2265 EnsureInit();
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);
2299 if (hasPattern) {
2300 return true;
2303 return false;
2306 nsresult nsLookAndFeel::GetKeyboardLayoutImpl(nsACString& aLayout) {
2307 if (mozilla::widget::GdkIsX11Display()) {
2308 #if defined(MOZ_X11)
2309 Display* display = gdk_x11_get_default_xdisplay();
2310 if (!display) {
2311 return NS_ERROR_NOT_AVAILABLE;
2313 XkbDescRec* kbdDesc = XkbAllocKeyboard();
2314 if (!kbdDesc) {
2315 return NS_ERROR_NOT_AVAILABLE;
2317 auto cleanup = MakeScopeExit([&] { XkbFreeKeyboard(kbdDesc, 0, true); });
2319 XkbStateRec state;
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);
2332 #endif
2333 } else {
2334 #if defined(MOZ_WAYLAND)
2335 struct xkb_context* context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
2336 if (!context) {
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);
2343 if (!keymap) {
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);
2350 if (layout) {
2351 aLayout.Assign(layout);
2353 #endif
2356 return NS_OK;
2359 void nsLookAndFeel::RecordLookAndFeelSpecificTelemetry() {
2360 // Gtk version we're on.
2361 nsString version;
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);
2390 #undef LOGLNF
2391 #undef LOGLNF_ENABLED