From 023b9e6131b1809168b327af659f2b87db8cb4a6 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 10 Apr 2021 18:11:38 +0200 Subject: [PATCH 1/3] [Linux] revise dark theme detection Check whether the window text color is light or dark, or whether the theme name contains 'dark' (fallback). This detects dark mode also for other themes than the dark variants of Yaru & Adwaita. --- shell/platform/linux/fl_settings_plugin.cc | 29 +++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/shell/platform/linux/fl_settings_plugin.cc b/shell/platform/linux/fl_settings_plugin.cc index 446ae9751c96c..e15a2321ecd24 100644 --- a/shell/platform/linux/fl_settings_plugin.cc +++ b/shell/platform/linux/fl_settings_plugin.cc @@ -4,6 +4,8 @@ #include "flutter/shell/platform/linux/fl_settings_plugin.h" +#include +#include #include #include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h" @@ -32,6 +34,26 @@ struct _FlSettingsPlugin { G_DEFINE_TYPE(FlSettingsPlugin, fl_settings_plugin, G_TYPE_OBJECT) +static bool is_dark_theme(GSettings* interface_settings) { + // GTK doesn't have a specific flag for dark themes, so we check if the + // window text color is light or dark + GtkApplication* app = GTK_APPLICATION(g_application_get_default()); + GtkWidget* window = GTK_WIDGET(gtk_application_get_active_window(app)); + + if (G_UNLIKELY(window == nullptr)) { + // As a fallback, check if the theme name contains 'dark' + g_autofree gchar* gtk_theme = + g_settings_get_string(interface_settings, kDesktopGtkThemeKey); + g_autofree gchar* gtk_theme_down = g_utf8_strdown(gtk_theme, -1); + return strstr(gtk_theme_down, "dark") != nullptr; + } + + GdkRGBA color; + GtkStyleContext* style = gtk_widget_get_style_context(window); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); + return color.red > 0.5 && color.blue > 0.5 && color.green > 0.5; +} + // Sends the current settings to the Flutter engine. static void update_settings(FlSettingsPlugin* self) { gdouble scaling_factor = 1.0; @@ -45,12 +67,7 @@ static void update_settings(FlSettingsPlugin* self) { g_settings_get_string(self->interface_settings, kDesktopClockFormatKey); always_use_24hr = g_strcmp0(clock_format, kClockFormat24Hour) == 0; - // GTK doesn't have a specific flag for dark themes, so we have some - // hard-coded themes for Ubuntu (Yaru) and GNOME (Adwaita). - g_autofree gchar* gtk_theme = - g_settings_get_string(self->interface_settings, kDesktopGtkThemeKey); - if (g_strcmp0(gtk_theme, "Yaru-dark") == 0 || - g_strcmp0(gtk_theme, "Adwaita-dark") == 0) { + if (is_dark_theme(self->interface_settings)) { platform_brightness = kPlatformBrightnessDark; } } From b9c06bc42655ccd6585da2af2d7e10ecad886890 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sun, 11 Apr 2021 17:49:41 +0200 Subject: [PATCH 2/3] Cleanup - don't require GtkApp - gtk_window_list_toplevels() is fine - windowless fallback is not needed because fl_engine_start() is called from fl_view_realize() --- shell/platform/linux/fl_settings_plugin.cc | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/shell/platform/linux/fl_settings_plugin.cc b/shell/platform/linux/fl_settings_plugin.cc index e15a2321ecd24..eadc1a19aa4e7 100644 --- a/shell/platform/linux/fl_settings_plugin.cc +++ b/shell/platform/linux/fl_settings_plugin.cc @@ -19,7 +19,6 @@ static constexpr char kPlatformBrightnessLight[] = "light"; static constexpr char kPlatformBrightnessDark[] = "dark"; static constexpr char kDesktopInterfaceSchema[] = "org.gnome.desktop.interface"; -static constexpr char kDesktopGtkThemeKey[] = "gtk-theme"; static constexpr char kDesktopTextScalingFactorKey[] = "text-scaling-factor"; static constexpr char kDesktopClockFormatKey[] = "clock-format"; static constexpr char kClockFormat24Hour[] = "24h"; @@ -34,24 +33,25 @@ struct _FlSettingsPlugin { G_DEFINE_TYPE(FlSettingsPlugin, fl_settings_plugin, G_TYPE_OBJECT) -static bool is_dark_theme(GSettings* interface_settings) { +static bool is_dark_color(GdkRGBA* color) { + return color->red < 0.5 && color->green < 0.5 && color->blue < 0.5; +} + +static bool is_dark_theme() { // GTK doesn't have a specific flag for dark themes, so we check if the - // window text color is light or dark - GtkApplication* app = GTK_APPLICATION(g_application_get_default()); - GtkWidget* window = GTK_WIDGET(gtk_application_get_active_window(app)); - - if (G_UNLIKELY(window == nullptr)) { - // As a fallback, check if the theme name contains 'dark' - g_autofree gchar* gtk_theme = - g_settings_get_string(interface_settings, kDesktopGtkThemeKey); - g_autofree gchar* gtk_theme_down = g_utf8_strdown(gtk_theme, -1); - return strstr(gtk_theme_down, "dark") != nullptr; + // style text color is light or dark + GList* windows = gtk_window_list_toplevels(); + if (windows == nullptr) { + return false; } + GtkWidget* window = GTK_WIDGET(windows->data); + g_list_free(windows); + GdkRGBA color; GtkStyleContext* style = gtk_widget_get_style_context(window); gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); - return color.red > 0.5 && color.blue > 0.5 && color.green > 0.5; + return !is_dark_color(&color); } // Sends the current settings to the Flutter engine. @@ -66,10 +66,10 @@ static void update_settings(FlSettingsPlugin* self) { g_autofree gchar* clock_format = g_settings_get_string(self->interface_settings, kDesktopClockFormatKey); always_use_24hr = g_strcmp0(clock_format, kClockFormat24Hour) == 0; + } - if (is_dark_theme(self->interface_settings)) { - platform_brightness = kPlatformBrightnessDark; - } + if (is_dark_theme()) { + platform_brightness = kPlatformBrightnessDark; } g_autoptr(FlValue) message = fl_value_new_map(); From 0ab457825de3473d3f0f78ca9836c4d441624f5b Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 29 Apr 2021 19:41:34 +0200 Subject: [PATCH 3/3] Resolve review comments --- shell/platform/linux/fl_settings_plugin.cc | 42 +++++++++++++++++----- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/shell/platform/linux/fl_settings_plugin.cc b/shell/platform/linux/fl_settings_plugin.cc index eadc1a19aa4e7..db1896108aad1 100644 --- a/shell/platform/linux/fl_settings_plugin.cc +++ b/shell/platform/linux/fl_settings_plugin.cc @@ -6,7 +6,7 @@ #include #include -#include +#include #include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" @@ -23,6 +23,8 @@ static constexpr char kDesktopTextScalingFactorKey[] = "text-scaling-factor"; static constexpr char kDesktopClockFormatKey[] = "clock-format"; static constexpr char kClockFormat24Hour[] = "24h"; +enum class Brightness { Light, Dark }; + struct _FlSettingsPlugin { GObject parent_instance; @@ -33,25 +35,49 @@ struct _FlSettingsPlugin { G_DEFINE_TYPE(FlSettingsPlugin, fl_settings_plugin, G_TYPE_OBJECT) -static bool is_dark_color(GdkRGBA* color) { - return color->red < 0.5 && color->green < 0.5 && color->blue < 0.5; +// The color brightness calculation has been adapted from theme_data.dart: +// https://github.com/flutter/flutter/blob/8fe4cc79648a952f9c7e49a5248756c2ff98fa3b/packages/flutter/lib/src/material/theme_data.dart#L1470-L1488 + +// See . +static gdouble linearize_color_component(gdouble component) { + if (component <= 0.03928) + return component / 12.92; + return pow((component + 0.055) / 1.055, 2.4); +} + +// See . +gdouble compute_luminance(GdkRGBA* color) { + gdouble r = linearize_color_component(color->red); + gdouble g = linearize_color_component(color->green); + gdouble b = linearize_color_component(color->blue); + return 0.2126 * r + 0.7152 * g + 0.0722 * b; +} + +static Brightness estimate_brightness_for_color(GdkRGBA* color) { + gdouble relative_luminance = compute_luminance(color); + + // See and + // . + const gdouble kThreshold = 0.15; + if ((relative_luminance + 0.05) * (relative_luminance + 0.05) > kThreshold) + return Brightness::Light; + return Brightness::Dark; } static bool is_dark_theme() { // GTK doesn't have a specific flag for dark themes, so we check if the // style text color is light or dark GList* windows = gtk_window_list_toplevels(); - if (windows == nullptr) { + if (windows == nullptr) return false; - } GtkWidget* window = GTK_WIDGET(windows->data); g_list_free(windows); - GdkRGBA color; + GdkRGBA text_color; GtkStyleContext* style = gtk_widget_get_style_context(window); - gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); - return !is_dark_color(&color); + gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &text_color); + return estimate_brightness_for_color(&text_color) == Brightness::Light; } // Sends the current settings to the Flutter engine.