Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / widget / gtk / nsLookAndFeel.cpp
blob89877ad5d665eff97acf141d7eb4abd5a40ac16e
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/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"
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 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")) {
130 NS_WARNING(
131 nsPrintfCString("Unknown change signal for settings: %s", signal_name)
132 .get());
133 return;
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");
144 return;
147 auto* lnf = static_cast<nsLookAndFeel*>(user_data);
148 auto nsStr = GVariantGetString(ns);
149 if (!nsStr.Equals("org.freedesktop.appearance"_ns)) {
150 return;
153 UnboxVariant(value);
155 auto keyStr = GVariantGetString(key);
156 if (lnf->RecomputeDBusAppearanceSetting(keyStr, value)) {
157 OnSettingsChange();
161 bool nsLookAndFeel::RecomputeDBusAppearanceSetting(const nsACString& aKey,
162 GVariant* aValue) {
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)) {
174 default:
175 MOZ_FALLTHROUGH_ASSERT("Unexpected color-scheme query return value");
176 case 0:
177 break;
178 case 1:
179 return Some(ColorScheme::Dark);
180 case 2:
181 return Some(ColorScheme::Light);
183 return Maybe<ColorScheme>{};
184 }();
185 return mDBusSettings.mColorScheme != old;
187 if (aKey.EqualsLiteral("accent-color")) {
188 auto old = mDBusSettings.mAccentColor;
189 mDBusSettings.mAccentColor.mBg = mDBusSettings.mAccentColor.mFg =
190 NS_TRANSPARENT;
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;
201 return false;
204 bool nsLookAndFeel::RecomputeDBusSettings() {
205 if (!mDBusSettingsProxy) {
206 return false;
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)));
219 if (!variant) {
220 LOGLNF("dbus settings query error: %s\n", error->message);
221 return false;
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");
231 return false;
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.
238 gchar* ns;
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,
248 &innerValue)) {
249 LOGLNF(" > %s: %s", appearanceKey,
250 GVariantToString(innerValue).get());
251 changed |= RecomputeDBusAppearanceSetting(
252 nsDependentCString(appearanceKey), innerValue);
257 return changed;
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);
269 return;
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()) {
278 OnSettingsChange();
282 void nsLookAndFeel::UnwatchDBus() {
283 if (!mDBusSettingsProxy) {
284 return;
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,
298 // System fonts?
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);
329 sCSDAvailable =
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);
339 lnf->WatchDBus();
341 [](GDBusConnection*, const gchar*, gpointer data) -> void {
342 auto* lnf = static_cast<nsLookAndFeel*>(data);
343 lnf->UnwatchDBus();
345 this, nullptr);
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();
362 if (mDBusID) {
363 g_bus_unwatch_name(mDBusID);
364 mDBusID = 0;
366 UnwatchDBus();
367 g_signal_handlers_disconnect_by_func(
368 gtk_settings_get_default(), FuncToGpointer(settings_changed_cb), nullptr);
371 #if 0
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);
377 printf("%s\n", str);
378 g_free(str);
379 str = gtk_widget_path_to_string(gtk_style_context_get_path(aStyle));
380 printf("%s\n", str);
381 g_free(str);
383 #endif
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,
402 double* aDarkness) {
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)) {
411 return false;
414 auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
415 if (!pattern) {
416 return false;
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)) {
425 return false;
428 double maxLightness, maxDarkness;
429 GetLightAndDarkness(*aDarkColor, &maxLightness, &maxDarkness);
430 *aLightColor = *aDarkColor;
432 GdkRGBA stop;
433 for (int index = 1;
434 CAIRO_STATUS_SUCCESS ==
435 cairo_pattern_get_color_stop_rgba(pattern, index, nullptr, &stop.red,
436 &stop.green, &stop.blue, &stop.alpha);
437 ++index) {
438 double lightness, darkness;
439 GetLightAndDarkness(stop, &lightness, &darkness);
440 if (lightness > maxLightness) {
441 maxLightness = lightness;
442 *aLightColor = stop;
444 if (darkness > maxDarkness) {
445 maxDarkness = darkness;
446 *aDarkColor = stop;
450 return true;
453 static bool GetColorFromImagePattern(const GValue* aValue, nscolor* aColor) {
454 if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN)) {
455 return false;
458 auto* pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
459 if (!pattern) {
460 return false;
463 cairo_surface_t* surface;
464 if (cairo_pattern_get_surface(pattern, &surface) != CAIRO_STATUS_SUCCESS) {
465 return false;
468 cairo_format_t format = cairo_image_surface_get_format(surface);
469 if (format == CAIRO_FORMAT_INVALID) {
470 return false;
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) {
476 return false;
479 // Guesstimate the central pixel would have a sensible color.
480 int x = width / 2;
481 int y = height / 2;
483 unsigned char* data = cairo_image_surface_get_data(surface);
484 switch (format) {
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();
490 return true;
492 default:
493 break;
496 return false;
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
510 // engine.)
511 const char* propertyName = "-unico-border-gradient";
512 if (!gtk_style_properties_lookup_property(propertyName, nullptr, nullptr))
513 return false;
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)
520 return false;
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);
530 return result;
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
537 // render a border.
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;
547 if (visible) {
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.
550 GtkBorder border;
551 gtk_style_context_get_border(aContext, state, &border);
552 visible = border.top != 0 || border.right != 0 || border.bottom != 0 ||
553 border.left != 0;
556 if (visible &&
557 GetUnicoBorderGradientColors(aContext, aLightColor, aDarkColor))
558 return true;
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;
568 return visible;
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);
577 return ret;
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;
589 return;
592 uint16_t hue, sat, luminance;
593 uint8_t alpha;
594 mCellHighlight = mField;
596 NS_RGB2HSV(mCellHighlight.mBg, hue, sat, luminance, alpha);
598 uint16_t step = 30;
599 // Lighten the color if the color is very dark
600 if (luminance <= step) {
601 luminance += step;
603 // Darken it if it is very light
604 else if (luminance >= 255 - step) {
605 luminance -= step;
607 // Otherwise, compute what works best depending on the text luminance.
608 else {
609 uint16_t textHue, textSat, textLuminance;
610 uint8_t textAlpha;
611 NS_RGB2HSV(mCellHighlight.mFg, textHue, textSat, textLuminance, textAlpha);
612 // Text is darker than background, use a lighter shade
613 if (textLuminance < luminance) {
614 luminance += step;
616 // Otherwise, use a darker shade
617 else {
618 luminance -= step;
621 NS_HSV2RGB(mCellHighlight.mBg, hue, sat, luminance, alpha);
624 void nsLookAndFeel::NativeInit() { EnsureInit(); }
626 void nsLookAndFeel::RefreshImpl() {
627 mInitialized = false;
628 moz_gtk_refresh();
630 nsXPLookAndFeel::RefreshImpl();
633 nsresult nsLookAndFeel::NativeGetColor(ColorID aID, ColorScheme aScheme,
634 nscolor& aColor) {
635 EnsureInit();
637 const auto& theme =
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,
651 bool aIsDark) {
652 if (!aIsDark) {
653 return true;
655 if (StaticPrefs::widget_non_native_theme_scrollbar_dark_themed()) {
656 return true;
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;
666 switch (aID) {
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;
675 break;
676 case ColorID::Windowtext:
677 aColor = mWindow.mFg;
678 break;
679 case ColorID::MozDialog:
680 aColor = mDialog.mBg;
681 break;
682 case ColorID::MozDialogtext:
683 aColor = mDialog.mFg;
684 break;
685 case ColorID::IMESelectedRawTextBackground:
686 case ColorID::IMESelectedConvertedTextBackground:
687 case ColorID::Highlight: // preference selected item,
688 aColor = mSelectedText.mBg;
689 break;
690 case ColorID::Highlighttext:
691 if (NS_GET_A(mSelectedText.mBg) < 155) {
692 aColor = NS_SAME_AS_FOREGROUND_COLOR;
693 break;
695 [[fallthrough]];
696 case ColorID::IMESelectedRawTextForeground:
697 case ColorID::IMESelectedConvertedTextForeground:
698 aColor = mSelectedText.mFg;
699 break;
700 case ColorID::Selecteditem:
701 aColor = mSelectedItem.mBg;
702 break;
703 case ColorID::Selecteditemtext:
704 aColor = mSelectedItem.mFg;
705 break;
706 case ColorID::Accentcolor:
707 aColor = mAccent.mBg;
708 break;
709 case ColorID::Accentcolortext:
710 aColor = mAccent.mFg;
711 break;
712 case ColorID::MozCellhighlight:
713 aColor = mCellHighlight.mBg;
714 break;
715 case ColorID::MozCellhighlighttext:
716 aColor = mCellHighlight.mFg;
717 break;
718 case ColorID::IMERawInputBackground:
719 case ColorID::IMEConvertedTextBackground:
720 aColor = NS_TRANSPARENT;
721 break;
722 case ColorID::IMERawInputForeground:
723 case ColorID::IMEConvertedTextForeground:
724 aColor = NS_SAME_AS_FOREGROUND_COLOR;
725 break;
726 case ColorID::IMERawInputUnderline:
727 case ColorID::IMEConvertedTextUnderline:
728 aColor = NS_SAME_AS_FOREGROUND_COLOR;
729 break;
730 case ColorID::IMESelectedRawTextUnderline:
731 case ColorID::IMESelectedConvertedTextUnderline:
732 aColor = NS_TRANSPARENT;
733 break;
734 case ColorID::Scrollbar:
735 aColor = mThemedScrollbar;
736 break;
737 case ColorID::ThemedScrollbar:
738 aColor = mThemedScrollbar;
739 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
740 return NS_ERROR_FAILURE;
742 break;
743 case ColorID::ThemedScrollbarInactive:
744 aColor = mThemedScrollbarInactive;
745 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
746 return NS_ERROR_FAILURE;
748 break;
749 case ColorID::ThemedScrollbarThumb:
750 aColor = mThemedScrollbarThumb;
751 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
752 return NS_ERROR_FAILURE;
754 break;
755 case ColorID::ThemedScrollbarThumbHover:
756 aColor = mThemedScrollbarThumbHover;
757 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
758 return NS_ERROR_FAILURE;
760 break;
761 case ColorID::ThemedScrollbarThumbActive:
762 aColor = mThemedScrollbarThumbActive;
763 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
764 return NS_ERROR_FAILURE;
766 break;
767 case ColorID::ThemedScrollbarThumbInactive:
768 aColor = mThemedScrollbarThumbInactive;
769 if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
770 return NS_ERROR_FAILURE;
772 break;
774 // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
775 case ColorID::Activeborder:
776 // active window border
777 aColor = mMozWindowActiveBorder;
778 break;
779 case ColorID::Inactiveborder:
780 // inactive window border
781 aColor = mMozWindowInactiveBorder;
782 break;
783 case ColorID::Graytext: // disabled text in windows, menus, etc.
784 aColor = mGrayText;
785 break;
786 case ColorID::Activecaption:
787 aColor = mTitlebar.mBg;
788 break;
789 case ColorID::Captiontext: // text in active window caption (titlebar)
790 aColor = mTitlebar.mFg;
791 break;
792 case ColorID::Inactivecaption:
793 // inactive window caption
794 aColor = mTitlebarInactive.mBg;
795 break;
796 case ColorID::Inactivecaptiontext:
797 aColor = mTitlebarInactive.mFg;
798 break;
799 case ColorID::Infobackground:
800 aColor = mInfo.mBg;
801 break;
802 case ColorID::Infotext:
803 aColor = mInfo.mFg;
804 break;
805 case ColorID::Menu:
806 aColor = mMenu.mBg;
807 break;
808 case ColorID::Menutext:
809 aColor = mMenu.mFg;
810 break;
811 case ColorID::MozHeaderbar:
812 aColor = mHeaderBar.mBg;
813 break;
814 case ColorID::MozHeaderbartext:
815 aColor = mHeaderBar.mFg;
816 break;
817 case ColorID::MozHeaderbarinactive:
818 aColor = mHeaderBarInactive.mBg;
819 break;
820 case ColorID::MozHeaderbarinactivetext:
821 aColor = mHeaderBarInactive.mFg;
822 break;
823 case ColorID::Threedface:
824 case ColorID::Buttonface:
825 case ColorID::MozButtondisabledface:
826 // 3-D face color
827 aColor = mWindow.mBg;
828 break;
830 case ColorID::Buttontext:
831 // text on push buttons
832 aColor = mButton.mFg;
833 break;
835 case ColorID::Buttonhighlight:
836 // 3-D highlighted edge color
837 case ColorID::Threedhighlight:
838 // 3-D highlighted outer edge color
839 aColor = mThreeDHighlight;
840 break;
842 case ColorID::Buttonshadow:
843 // 3-D shadow edge color
844 case ColorID::Threedshadow:
845 // 3-D shadow inner edge color
846 aColor = mThreeDShadow;
847 break;
848 case ColorID::Buttonborder:
849 aColor = mButtonBorder;
850 break;
851 case ColorID::Threedlightshadow:
852 case ColorID::MozDisabledfield:
853 aColor = mIsDark ? *GenericDarkColor(aID) : NS_RGB(0xE0, 0xE0, 0xE0);
854 break;
855 case ColorID::Threeddarkshadow:
856 aColor = mIsDark ? *GenericDarkColor(aID) : NS_RGB(0xDC, 0xDC, 0xDC);
857 break;
859 case ColorID::MozEventreerow:
860 case ColorID::Field:
861 aColor = mField.mBg;
862 break;
863 case ColorID::Fieldtext:
864 aColor = mField.mFg;
865 break;
866 case ColorID::MozSidebar:
867 aColor = mSidebar.mBg;
868 break;
869 case ColorID::MozSidebartext:
870 aColor = mSidebar.mFg;
871 break;
872 case ColorID::MozSidebarborder:
873 aColor = mSidebarBorder;
874 break;
875 case ColorID::MozButtonhoverface:
876 aColor = mButtonHover.mBg;
877 break;
878 case ColorID::MozButtonhovertext:
879 aColor = mButtonHover.mFg;
880 break;
881 case ColorID::MozButtonactiveface:
882 aColor = mButtonActive.mBg;
883 break;
884 case ColorID::MozButtonactivetext:
885 aColor = mButtonActive.mFg;
886 break;
887 case ColorID::MozMenuhover:
888 aColor = mMenuHover.mBg;
889 break;
890 case ColorID::MozMenuhovertext:
891 aColor = mMenuHover.mFg;
892 break;
893 case ColorID::MozMenuhoverdisabled:
894 aColor = NS_TRANSPARENT;
895 break;
896 case ColorID::MozOddtreerow:
897 aColor = mOddCellBackground;
898 break;
899 case ColorID::MozNativehyperlinktext:
900 aColor = mNativeHyperLinkText;
901 break;
902 case ColorID::MozNativevisitedhyperlinktext:
903 aColor = mNativeVisitedHyperLinkText;
904 break;
905 case ColorID::MozComboboxtext:
906 aColor = mComboBoxText;
907 break;
908 case ColorID::MozColheader:
909 aColor = mMozColHeader.mBg;
910 break;
911 case ColorID::MozColheadertext:
912 aColor = mMozColHeader.mFg;
913 break;
914 case ColorID::MozColheaderhover:
915 aColor = mMozColHeaderHover.mBg;
916 break;
917 case ColorID::MozColheaderhovertext:
918 aColor = mMozColHeaderHover.mFg;
919 break;
920 case ColorID::MozColheaderactive:
921 aColor = mMozColHeaderActive.mBg;
922 break;
923 case ColorID::MozColheaderactivetext:
924 aColor = mMozColHeaderActive.mFg;
925 break;
926 case ColorID::SpellCheckerUnderline:
927 case ColorID::Mark:
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);
934 break;
935 default:
936 /* default color is BLACK */
937 aColor = 0;
938 res = NS_ERROR_FAILURE;
939 break;
942 return res;
945 static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle,
946 int32_t aResult) {
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.
974 switch (aID) {
975 case IntID::ScrollButtonLeftMouseButtonAction:
976 aResult = 0;
977 break;
978 case IntID::ScrollButtonMiddleMouseButtonAction:
979 aResult = 1;
980 break;
981 case IntID::ScrollButtonRightMouseButtonAction:
982 aResult = 2;
983 break;
984 case IntID::CaretBlinkTime:
985 EnsureInit();
986 aResult = mCaretBlinkTime;
987 break;
988 case IntID::CaretBlinkCount:
989 EnsureInit();
990 aResult = mCaretBlinkCount;
991 break;
992 case IntID::CaretWidth:
993 aResult = 1;
994 break;
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,
1001 nullptr);
1003 if (select_on_focus)
1004 aResult = 1;
1005 else
1006 aResult = 0;
1008 } break;
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,
1017 nullptr);
1020 if (warps_slider)
1021 aResult = 1;
1022 else
1023 aResult = 0;
1024 } break;
1025 case IntID::SubmenuDelay: {
1026 GtkSettings* settings;
1027 gint delay;
1029 settings = gtk_settings_get_default();
1030 g_object_get(settings, "gtk-menu-popup-delay", &delay, nullptr);
1031 aResult = (int32_t)delay;
1032 break;
1034 case IntID::MenusCanOverlapOSBar:
1035 aResult = 0;
1036 break;
1037 case IntID::SkipNavigatingDisabledMenuItem:
1038 aResult = 1;
1039 break;
1040 case IntID::DragThresholdX:
1041 case IntID::DragThresholdY: {
1042 gint threshold = 0;
1043 g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold",
1044 &threshold, nullptr);
1046 aResult = threshold;
1047 } break;
1048 case IntID::ScrollArrowStyle: {
1049 GtkWidget* scrollbar = GetWidget(MOZ_GTK_SCROLLBAR_VERTICAL);
1050 aResult = ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar);
1051 break;
1053 case IntID::TreeOpenDelay:
1054 aResult = 1000;
1055 break;
1056 case IntID::TreeCloseDelay:
1057 aResult = 1000;
1058 break;
1059 case IntID::TreeLazyScrollDelay:
1060 aResult = 150;
1061 break;
1062 case IntID::TreeScrollDelay:
1063 aResult = 100;
1064 break;
1065 case IntID::TreeScrollLinesMax:
1066 aResult = 3;
1067 break;
1068 case IntID::AlertNotificationOrigin:
1069 aResult = NS_ALERT_TOP;
1070 break;
1071 case IntID::IMERawInputUnderlineStyle:
1072 case IntID::IMEConvertedTextUnderlineStyle:
1073 aResult = static_cast<int32_t>(StyleTextDecorationStyle::Solid);
1074 break;
1075 case IntID::IMESelectedRawTextUnderlineStyle:
1076 case IntID::IMESelectedConvertedTextUnderline:
1077 aResult = static_cast<int32_t>(StyleTextDecorationStyle::None);
1078 break;
1079 case IntID::SpellCheckerUnderlineStyle:
1080 aResult = static_cast<int32_t>(StyleTextDecorationStyle::Wavy);
1081 break;
1082 case IntID::MenuBarDrag:
1083 EnsureInit();
1084 aResult = mSystemTheme.mMenuSupportsDrag;
1085 break;
1086 case IntID::ScrollbarButtonAutoRepeatBehavior:
1087 aResult = 1;
1088 break;
1089 case IntID::SwipeAnimationEnabled:
1090 aResult = 1;
1091 break;
1092 case IntID::ContextMenuOffsetVertical:
1093 case IntID::ContextMenuOffsetHorizontal:
1094 aResult = 2;
1095 break;
1096 case IntID::GTKCSDAvailable:
1097 aResult = sCSDAvailable;
1098 break;
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);
1103 break;
1105 case IntID::GTKCSDMaximizeButton:
1106 EnsureInit();
1107 aResult = mCSDMaximizeButton;
1108 break;
1109 case IntID::GTKCSDMinimizeButton:
1110 EnsureInit();
1111 aResult = mCSDMinimizeButton;
1112 break;
1113 case IntID::GTKCSDCloseButton:
1114 EnsureInit();
1115 aResult = mCSDCloseButton;
1116 break;
1117 case IntID::GTKCSDReversedPlacement:
1118 EnsureInit();
1119 aResult = mCSDReversedPlacement;
1120 break;
1121 case IntID::PrefersReducedMotion: {
1122 EnsureInit();
1123 aResult = mPrefersReducedMotion;
1124 break;
1126 case IntID::SystemUsesDarkTheme: {
1127 EnsureInit();
1128 if (mColorSchemePreference) {
1129 aResult = *mColorSchemePreference == ColorScheme::Dark;
1130 } else {
1131 aResult = mSystemTheme.mIsDark;
1133 break;
1135 case IntID::GTKCSDMaximizeButtonPosition:
1136 aResult = mCSDMaximizeButtonPosition;
1137 break;
1138 case IntID::GTKCSDMinimizeButtonPosition:
1139 aResult = mCSDMinimizeButtonPosition;
1140 break;
1141 case IntID::GTKCSDCloseButtonPosition:
1142 aResult = mCSDCloseButtonPosition;
1143 break;
1144 case IntID::GTKThemeFamily: {
1145 EnsureInit();
1146 aResult = int32_t(EffectiveTheme().mFamily);
1147 break;
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:
1153 EnsureInit();
1154 aResult = mDBusSettings.mPrefersContrast || mSystemTheme.mHighContrast;
1155 break;
1156 case IntID::InvertedColors:
1157 // No GTK API for checking if inverted colors is enabled
1158 aResult = 0;
1159 break;
1160 case IntID::TitlebarRadius: {
1161 EnsureInit();
1162 aResult = EffectiveTheme().mTitlebarRadius;
1163 break;
1165 case IntID::TitlebarButtonSpacing: {
1166 EnsureInit();
1167 aResult = EffectiveTheme().mTitlebarButtonSpacing;
1168 break;
1170 case IntID::AllowOverlayScrollbarsOverlap: {
1171 aResult = 1;
1172 break;
1174 case IntID::ScrollbarFadeBeginDelay: {
1175 aResult = 1000;
1176 break;
1178 case IntID::ScrollbarFadeDuration: {
1179 aResult = 400;
1180 break;
1182 case IntID::ScrollbarDisplayOnMouseMove: {
1183 aResult = 1;
1184 break;
1186 case IntID::PanelAnimations:
1187 aResult = [&]() -> bool {
1188 if (!sCSDAvailable) {
1189 // Disabled on systems without CSD, see bug 1385079.
1190 return false;
1192 if (GdkIsWaylandDisplay()) {
1193 // Disabled on wayland, see bug 1800442 and bug 1800368.
1194 return false;
1196 return true;
1197 }();
1198 break;
1199 case IntID::UseOverlayScrollbars: {
1200 aResult = StaticPrefs::widget_gtk_overlay_scrollbars_enabled();
1201 break;
1203 case IntID::HideCursorWhileTyping: {
1204 aResult = StaticPrefs::widget_gtk_hide_pointer_while_typing_enabled();
1205 break;
1207 case IntID::TouchDeviceSupportPresent:
1208 aResult = widget::WidgetUtilsGTK::IsTouchDeviceSupportPresent();
1209 break;
1210 default:
1211 aResult = 0;
1212 res = NS_ERROR_FAILURE;
1215 return res;
1218 nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
1219 nsresult rv = NS_OK;
1220 switch (aID) {
1221 case FloatID::IMEUnderlineRelativeSize:
1222 aResult = 1.0f;
1223 break;
1224 case FloatID::SpellCheckerUnderlineRelativeSize:
1225 aResult = 1.0f;
1226 break;
1227 case FloatID::CaretAspectRatio:
1228 EnsureInit();
1229 aResult = mSystemTheme.mCaretRatio;
1230 break;
1231 case FloatID::TextScaleFactor:
1232 aResult = gfxPlatformGtk::GetFontScaleFactor();
1233 break;
1234 default:
1235 aResult = -1.0;
1236 rv = NS_ERROR_FAILURE;
1238 return rv;
1241 static void GetSystemFontInfo(GtkStyleContext* aStyle, nsString* aFontName,
1242 gfxFontStyle* aFontStyle) {
1243 aFontStyle->style = FontSlantStyle::NORMAL;
1245 // As in
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",
1249 &desc, nullptr);
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 {
1285 switch (aID) {
1286 case FontID::Menu: // css2
1287 case FontID::MozPullDownMenu: // css3
1288 aFontName = mMenuFontName;
1289 aFontStyle = mMenuFontStyle;
1290 break;
1292 case FontID::MozField: // css3
1293 case FontID::MozList: // css3
1294 aFontName = mFieldFontName;
1295 aFontStyle = mFieldFontStyle;
1296 break;
1298 case FontID::MozButton: // css3
1299 aFontName = mButtonFontName;
1300 aFontStyle = mButtonFontStyle;
1301 break;
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
1308 default:
1309 aFontName = mDefaultFontName;
1310 aFontStyle = mDefaultFontStyle;
1311 break;
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();
1319 return true;
1322 static nsCString GetGtkSettingsStringKey(const char* aKey) {
1323 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
1324 nsCString ret;
1325 GtkSettings* settings = gtk_settings_get_default();
1326 char* value = nullptr;
1327 g_object_get(settings, aKey, &value, nullptr);
1328 if (value) {
1329 ret.Assign(value);
1330 g_free(value);
1332 return ret;
1335 static nsCString GetGtkTheme() {
1336 auto theme = GetGtkSettingsStringKey("gtk-theme-name");
1337 if (theme.IsEmpty()) {
1338 theme.AssignLiteral("Adwaita");
1340 return theme;
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,
1347 nullptr);
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() {
1355 GdkRGBA bg, fg;
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) {
1368 return;
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");
1380 } else {
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();
1387 moz_gtk_refresh();
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;
1403 // clang-format off
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,
1410 // clang-format on
1411 bool found = false;
1412 for (const auto& s : kSubstringsToRemove) {
1413 potentialLightThemeName = mSystemTheme.mName;
1414 potentialLightThemeName.ReplaceSubstring(s, ""_ns);
1415 if (potentialLightThemeName.Length() != mSystemTheme.mName.Length()) {
1416 found = true;
1417 break;
1420 if (found) {
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,
1425 nullptr);
1426 moz_gtk_refresh();
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);
1437 moz_gtk_refresh();
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,
1447 nullptr);
1448 moz_gtk_refresh();
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
1452 // versions.
1453 if (!mSystemTheme.mIsDark && !GetThemeIsDark()) {
1454 LOGLNF(" last resort Adwaita-dark fallback");
1455 g_object_set(settings, "gtk-theme-name", "Adwaita-dark", nullptr);
1456 moz_gtk_refresh();
1459 return false;
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 =
1476 dark.mAccent.mBg;
1477 } else {
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 =
1488 light.mAccent.mBg;
1489 } else {
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()) {
1496 return;
1499 if (light.mFamily == ThemeFamily::Adwaita) {
1500 // #323232 is rgba(0,0,0,.8) over #fafafa.
1501 light.mWindow =
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();
1534 mAltTheme.Init();
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
1558 // it.
1559 mSystemThemeOverridden = true;
1560 UpdateRoundedBottomCornerStyles();
1563 void nsLookAndFeel::ClearRoundedCornerProvider() {
1564 if (!mRoundedCornerProvider) {
1565 return;
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()) {
1576 return;
1578 int32_t radius = EffectiveTheme().mTitlebarRadius;
1579 if (!radius) {
1580 return;
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;"
1587 "}\n",
1588 radius, radius);
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)
1595 .get());
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.
1609 int32_t pref = 0;
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");
1629 return;
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();
1657 RecordTelemetry();
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
1671 gboolean blink;
1672 g_object_get(settings, "gtk-cursor-blink-time", &blink_time,
1673 "gtk-cursor-blink-timeout", &blink_timeout, "gtk-cursor-blink",
1674 &blink, nullptr);
1675 // From
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.
1685 mCaretBlinkCount =
1686 std::max(1, int32_t(std::ceil(float(blink_timeout * 1000) /
1687 (float(blink_time) * 2.0f))));
1688 } else {
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
1708 // the left side.
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;
1715 break;
1716 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
1717 mCSDMaximizeButton = true;
1718 pos = &mCSDMaximizeButtonPosition;
1719 break;
1720 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
1721 mCSDCloseButton = true;
1722 pos = &mCSDCloseButtonPosition;
1723 break;
1724 default:
1725 break;
1728 if (pos) {
1729 *pos = i;
1733 struct actionMapping {
1734 TitlebarAction action;
1735 char name[100];
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);
1748 if (!action) {
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:
1777 break;
1779 if (!mColorSchemePreference) {
1780 return true;
1782 const bool preferenceIsDark = *mColorSchemePreference == ColorScheme::Dark;
1783 return preferenceIsDark == mSystemTheme.mIsDark;
1784 }();
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);
1802 } else {
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();
1809 moz_gtk_refresh();
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)) {
1820 return true;
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
1830 // overkill.
1831 if (NS_LUMINOSITY_DIFFERENCE(l, aForForegroundColor) >
1832 NS_LUMINOSITY_DIFFERENCE(d, aForForegroundColor)) {
1833 *aColor = l;
1834 } else {
1835 *aColor = d;
1837 return true;
1841 return false;
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,
1851 &imageColor)) {
1852 if (NS_GET_A(imageColor) == 255) {
1853 return imageColor;
1857 GdkRGBA gdkColor;
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) {
1863 return finalColor;
1865 return NS_TRANSPARENT;
1868 static nscolor GetTextColor(GtkStyleContext* aStyle,
1869 GtkStateFlags aState = GTK_STATE_FLAG_NORMAL) {
1870 GdkRGBA color;
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) {
1878 ColorPair result;
1879 result.mFg = GetTextColor(aStyle, aState);
1880 result.mBg = GetBackgroundColor(aStyle, result.mFg, aState);
1881 return result;
1884 static bool GetNamedColorPair(GtkStyleContext* aStyle, const char* aBgName,
1885 const char* aFgName, ColorPair* aPair) {
1886 GdkRGBA bg, fg;
1887 if (!gtk_style_context_lookup_color(aStyle, aBgName, &bg) ||
1888 !gtk_style_context_lookup_color(aStyle, aFgName, &fg)) {
1889 return false;
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
1897 // bug 1717077.
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
1905 // for symmetry.
1906 if (NS_GET_A(aPair->mFg) != 255) {
1907 aPair->mFg = NS_ComposeColors(aPair->mBg, aPair->mFg);
1910 return true;
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
1923 // we have.
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();
1935 mFamily = [&] {
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;
1946 }();
1948 GtkStyleContext* style;
1950 mHighContrast = StaticPrefs::widget_content_gtk_high_contrast_enabled() &&
1951 mName.Find("HighContrast"_ns) >= 0;
1953 mPreferDarkTheme = GetPreferDarkTheme();
1955 mIsDark = GetThemeIsDark();
1957 GdkRGBA color;
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,
1964 &color);
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);
1969 mThemedScrollbar =
1970 NS_ComposeColors(mThemedScrollbar, GDK_RGBA_TO_NS_RGBA(color));
1971 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
1972 &color);
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,
1980 &color);
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),
1984 &color);
1985 mThemedScrollbarThumbActive = GDK_RGBA_TO_NS_RGBA(color);
1986 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
1987 &color);
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()) {
1993 return true;
1996 if (!ShouldHonorThemeScrollbarColors()) {
1997 return true;
1999 // If any of the scrollbar thumb colors are fully transparent, fall back to
2000 // non-native ones.
2001 if (!NS_GET_A(mThemedScrollbarThumb) ||
2002 !NS_GET_A(mThemedScrollbarThumbHover) ||
2003 !NS_GET_A(mThemedScrollbarThumbActive)) {
2004 return true;
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) {
2010 return true;
2012 return false;
2013 }();
2015 if (fallbackToUnthemedColors) {
2016 if (mIsDark) {
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);
2022 } else {
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);
2043 // Window colors
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();
2110 return false;
2111 }();
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);
2123 } else {
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);
2135 mMenu.mBg = [&] {
2136 nscolor color = GetBackgroundColor(style, mMenu.mFg);
2137 if (NS_GET_A(color)) {
2138 return 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
2142 // up.
2143 color = GetBackgroundColor(style, mMenu.mFg, GTK_STATE_FLAG_BACKDROP);
2144 if (NS_GET_A(color)) {
2145 return 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.
2149 NS_WARNING(
2150 "Couldn't find menu background color, falling back to window "
2151 "background");
2152 return mWindow.mBg;
2153 }();
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(
2159 mMenu.mBg,
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);
2179 // Text colors
2180 GdkRGBA bgColor;
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,
2185 &bgColor);
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);
2193 mSidebar = mField;
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(
2201 style,
2202 static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
2203 GTK_STATE_FLAG_SELECTED),
2204 &color);
2205 mSelectedText.mBg = GDK_RGBA_TO_NS_RGBA(color);
2206 gtk_style_context_get_color(
2207 style,
2208 static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
2209 GTK_STATE_FLAG_SELECTED),
2210 &color);
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",
2228 &mSelectedItem) &&
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",
2244 &mAccent)) {
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
2304 // node.
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");
2318 if (param_spec) {
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,
2321 nullptr);
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
2337 guint value;
2338 g_object_get(entry, "invisible-char", &value, nullptr);
2339 mInvisibleCharacter = char16_t(value);
2341 // caret styles
2342 gtk_widget_style_get(entry, "cursor-aspect-ratio", &mCaretRatio, nullptr);
2344 GetSystemFontInfo(gtk_widget_get_style_context(entry), &mFieldFontName,
2345 &mFieldFontStyle);
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)) {
2353 nscolor color;
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);
2363 // virtual
2364 char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
2365 EnsureInit();
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);
2399 if (hasPattern) {
2400 return true;
2403 return false;
2406 nsresult nsLookAndFeel::GetKeyboardLayoutImpl(nsACString& aLayout) {
2407 if (mozilla::widget::GdkIsX11Display()) {
2408 #if defined(MOZ_X11)
2409 Display* display = gdk_x11_get_default_xdisplay();
2410 if (!display) {
2411 return NS_ERROR_NOT_AVAILABLE;
2413 XkbDescRec* kbdDesc = XkbAllocKeyboard();
2414 if (!kbdDesc) {
2415 return NS_ERROR_NOT_AVAILABLE;
2417 auto cleanup = MakeScopeExit([&] { XkbFreeKeyboard(kbdDesc, 0, true); });
2419 XkbStateRec state;
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);
2432 #endif
2433 } else {
2434 #if defined(MOZ_WAYLAND)
2435 struct xkb_context* context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
2436 if (!context) {
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);
2443 if (!keymap) {
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);
2450 if (layout) {
2451 aLayout.Assign(layout);
2453 #endif
2456 return NS_OK;
2459 void nsLookAndFeel::RecordLookAndFeelSpecificTelemetry() {
2460 // Gtk version we're on.
2461 nsCString version;
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);
2476 #undef LOGLNF
2477 #undef LOGLNF_ENABLED