[Linux] fix and test light vs. dark theme detection (flutter/engine#32618)

This commit is contained in:
J-P Nurmi 2022-04-26 19:39:03 +02:00 committed by GitHub
parent 957aedc8b6
commit 050055f52c
15 changed files with 744 additions and 108 deletions

View File

@ -2088,6 +2088,9 @@ FILE: ../../../flutter/shell/platform/linux/fl_event_channel.cc
FILE: ../../../flutter/shell/platform/linux/fl_event_channel_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_gl_area.cc
FILE: ../../../flutter/shell/platform/linux/fl_gl_area.h
FILE: ../../../flutter/shell/platform/linux/fl_gnome_settings.cc
FILE: ../../../flutter/shell/platform/linux/fl_gnome_settings.h
FILE: ../../../flutter/shell/platform/linux/fl_gnome_settings_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec.cc
FILE: ../../../flutter/shell/platform/linux/fl_json_message_codec_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_json_method_codec.cc
@ -2137,8 +2140,11 @@ FILE: ../../../flutter/shell/platform/linux/fl_renderer_gl.cc
FILE: ../../../flutter/shell/platform/linux/fl_renderer_gl.h
FILE: ../../../flutter/shell/platform/linux/fl_renderer_headless.cc
FILE: ../../../flutter/shell/platform/linux/fl_renderer_headless.h
FILE: ../../../flutter/shell/platform/linux/fl_settings.cc
FILE: ../../../flutter/shell/platform/linux/fl_settings.h
FILE: ../../../flutter/shell/platform/linux/fl_settings_plugin.cc
FILE: ../../../flutter/shell/platform/linux/fl_settings_plugin.h
FILE: ../../../flutter/shell/platform/linux/fl_settings_plugin_test.cc
FILE: ../../../flutter/shell/platform/linux/fl_standard_message_codec.cc
FILE: ../../../flutter/shell/platform/linux/fl_standard_message_codec_private.h
FILE: ../../../flutter/shell/platform/linux/fl_standard_message_codec_test.cc

View File

@ -107,6 +107,7 @@ source_set("flutter_linux_sources") {
"fl_engine.cc",
"fl_event_channel.cc",
"fl_gl_area.cc",
"fl_gnome_settings.cc",
"fl_json_message_codec.cc",
"fl_json_method_codec.cc",
"fl_key_channel_responder.cc",
@ -128,6 +129,7 @@ source_set("flutter_linux_sources") {
"fl_renderer.cc",
"fl_renderer_gl.cc",
"fl_renderer_headless.cc",
"fl_settings.cc",
"fl_settings_plugin.cc",
"fl_standard_message_codec.cc",
"fl_standard_method_codec.cc",
@ -175,6 +177,13 @@ test_fixtures("flutter_linux_fixtures") {
fixtures = []
}
copy("flutter_linux_gschemas") {
testonly = true
sources = [ "testing/gschemas/ubuntu-20.04.compiled" ]
outputs = [ "$target_gen_dir/assets/{{source_name_part}}/gschemas.compiled" ]
}
executable("flutter_linux_unittests") {
testonly = true
@ -186,6 +195,7 @@ executable("flutter_linux_unittests") {
"fl_dart_project_test.cc",
"fl_engine_test.cc",
"fl_event_channel_test.cc",
"fl_gnome_settings_test.cc",
"fl_json_message_codec_test.cc",
"fl_json_method_codec_test.cc",
"fl_key_channel_responder_test.cc",
@ -197,6 +207,7 @@ executable("flutter_linux_unittests") {
"fl_method_response_test.cc",
"fl_pixel_buffer_texture_test.cc",
"fl_plugin_registrar_test.cc",
"fl_settings_plugin_test.cc",
"fl_standard_message_codec_test.cc",
"fl_standard_method_codec_test.cc",
"fl_string_codec_test.cc",
@ -210,6 +221,7 @@ executable("flutter_linux_unittests") {
"testing/mock_epoxy.cc",
"testing/mock_plugin_registrar.cc",
"testing/mock_renderer.cc",
"testing/mock_settings.cc",
"testing/mock_signal_handler.cc",
"testing/mock_text_input_plugin.cc",
"testing/mock_texture_registrar.cc",
@ -229,6 +241,7 @@ executable("flutter_linux_unittests") {
deps = [
":flutter_linux_fixtures",
":flutter_linux_gschemas",
":flutter_linux_sources",
"//flutter/runtime:libdart",
"//flutter/shell/platform/embedder:embedder_headers",

View File

@ -516,8 +516,9 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
setup_locales(self);
g_autoptr(FlSettings) settings = fl_settings_new();
self->settings_plugin = fl_settings_plugin_new(self->binary_messenger);
fl_settings_plugin_start(self->settings_plugin);
fl_settings_plugin_start(self->settings_plugin, settings);
result = self->embedder_api.UpdateSemanticsEnabled(self->engine, TRUE);
if (result != kSuccess) {

View File

@ -0,0 +1,160 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/linux/fl_gnome_settings.h"
#include <gio/gio.h>
#include <glib.h>
static constexpr char kDesktopInterfaceSchema[] = "org.gnome.desktop.interface";
static constexpr char kDesktopTextScalingFactorKey[] = "text-scaling-factor";
static constexpr char kDesktopClockFormatKey[] = "clock-format";
static constexpr char kDesktopGtkThemeKey[] = "gtk-theme";
static constexpr char kClockFormat12Hour[] = "12h";
static constexpr char kGtkThemeDarkSuffix[] = "-dark";
static constexpr char kInterfaceSettings[] = "interface-settings";
struct _FlGnomeSettings {
GObject parent_instance;
GSettings* interface_settings;
};
enum { PROP_0, PROP_INTERFACE_SETTINGS, PROP_LAST };
static void fl_gnome_settings_iface_init(FlSettingsInterface* iface);
G_DEFINE_TYPE_WITH_CODE(FlGnomeSettings,
fl_gnome_settings,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(fl_settings_get_type(),
fl_gnome_settings_iface_init))
static FlClockFormat fl_gnome_settings_get_clock_format(FlSettings* settings) {
FlGnomeSettings* self = FL_GNOME_SETTINGS(settings);
FlClockFormat clock_format = FL_CLOCK_FORMAT_24H;
if (self->interface_settings != nullptr) {
g_autofree gchar* value =
g_settings_get_string(self->interface_settings, kDesktopClockFormatKey);
if (g_strcmp0(value, kClockFormat12Hour) == 0) {
clock_format = FL_CLOCK_FORMAT_12H;
}
}
return clock_format;
}
static FlColorScheme fl_gnome_settings_get_color_scheme(FlSettings* settings) {
FlGnomeSettings* self = FL_GNOME_SETTINGS(settings);
FlColorScheme color_scheme = FL_COLOR_SCHEME_LIGHT;
if (self->interface_settings != nullptr) {
// check whether org.gnome.desktop.interface.gtk-theme ends with "-dark"
g_autofree gchar* value =
g_settings_get_string(self->interface_settings, kDesktopGtkThemeKey);
if (g_str_has_suffix(value, kGtkThemeDarkSuffix)) {
color_scheme = FL_COLOR_SCHEME_DARK;
}
}
return color_scheme;
}
static gdouble fl_gnome_settings_get_text_scaling_factor(FlSettings* settings) {
FlGnomeSettings* self = FL_GNOME_SETTINGS(settings);
gdouble scaling_factor = 1.0;
if (self->interface_settings != nullptr) {
scaling_factor = g_settings_get_double(self->interface_settings,
kDesktopTextScalingFactorKey);
}
return scaling_factor;
}
static void fl_gnome_settings_set_interface_settings(FlGnomeSettings* self,
GSettings* settings) {
g_return_if_fail(G_IS_SETTINGS(settings));
g_signal_connect_object(settings, "changed::clock-format",
G_CALLBACK(fl_settings_emit_changed), self,
G_CONNECT_SWAPPED);
g_signal_connect_object(settings, "changed::gtk-theme",
G_CALLBACK(fl_settings_emit_changed), self,
G_CONNECT_SWAPPED);
g_signal_connect_object(settings, "changed::text-scaling-factor",
G_CALLBACK(fl_settings_emit_changed), self,
G_CONNECT_SWAPPED);
self->interface_settings = G_SETTINGS(g_object_ref(settings));
}
static void fl_gnome_settings_set_property(GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec) {
FlGnomeSettings* self = FL_GNOME_SETTINGS(object);
switch (prop_id) {
case PROP_INTERFACE_SETTINGS:
fl_gnome_settings_set_interface_settings(
self, G_SETTINGS(g_value_get_object(value)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void fl_gnome_settings_dispose(GObject* object) {
FlGnomeSettings* self = FL_GNOME_SETTINGS(object);
g_clear_object(&self->interface_settings);
G_OBJECT_CLASS(fl_gnome_settings_parent_class)->dispose(object);
}
static void fl_gnome_settings_class_init(FlGnomeSettingsClass* klass) {
GObjectClass* object_class = G_OBJECT_CLASS(klass);
object_class->dispose = fl_gnome_settings_dispose;
object_class->set_property = fl_gnome_settings_set_property;
g_object_class_install_property(
object_class, PROP_INTERFACE_SETTINGS,
g_param_spec_object(
kInterfaceSettings, kInterfaceSettings, kDesktopInterfaceSchema,
g_settings_get_type(),
static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS)));
}
static void fl_gnome_settings_iface_init(FlSettingsInterface* iface) {
iface->get_clock_format = fl_gnome_settings_get_clock_format;
iface->get_color_scheme = fl_gnome_settings_get_color_scheme;
iface->get_text_scaling_factor = fl_gnome_settings_get_text_scaling_factor;
}
static void fl_gnome_settings_init(FlGnomeSettings* self) {}
static GSettings* create_settings(const gchar* schema_id) {
GSettings* settings = nullptr;
GSettingsSchemaSource* source = g_settings_schema_source_get_default();
if (source != nullptr) {
g_autoptr(GSettingsSchema) schema =
g_settings_schema_source_lookup(source, schema_id, TRUE);
if (schema != nullptr) {
settings = g_settings_new_full(schema, nullptr, nullptr);
}
}
return settings;
}
FlSettings* fl_gnome_settings_new() {
g_autoptr(GSettings) interface_settings =
create_settings(kDesktopInterfaceSchema);
return FL_SETTINGS(g_object_new(fl_gnome_settings_get_type(),
kInterfaceSettings, interface_settings,
nullptr));
}

View File

@ -0,0 +1,29 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_GNOME_SETTINGS_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_GNOME_SETTINGS_H_
#include "flutter/shell/platform/linux/fl_settings.h"
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(FlGnomeSettings,
fl_gnome_settings,
FL,
GNOME_SETTINGS,
GObject);
/**
* fl_gnome_settings_new:
*
* Creates a new settings instance for GNOME.
*
* Returns: a new #FlSettings.
*/
FlSettings* fl_gnome_settings_new();
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_GNOME_SETTINGS_H_

View File

@ -0,0 +1,112 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/linux/fl_gnome_settings.h"
#include "flutter/shell/platform/linux/testing/fl_test.h"
#include "flutter/shell/platform/linux/testing/mock_settings.h"
#include "flutter/shell/platform/linux/testing/mock_signal_handler.h"
#include "flutter/testing/testing.h"
#include <gio/gio.h>
#define G_SETTINGS_ENABLE_BACKEND
#include <gio/gsettingsbackend.h>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
class FlGnomeSettingsTest : public ::testing::Test {
protected:
void SetUp() override {
// force _g_io_modules_ensure_extension_points_registered() to get called
g_settings_backend_get_default();
}
};
static GSettings* create_settings(const gchar* name, const gchar* schema_id) {
g_autofree gchar* path =
g_build_filename(flutter::testing::GetFixturesPath(), name, nullptr);
g_autoptr(GSettingsSchemaSource) source =
g_settings_schema_source_new_from_directory(path, nullptr, false,
nullptr);
g_autoptr(GSettingsSchema) schema =
g_settings_schema_source_lookup(source, schema_id, false);
g_autoptr(GSettingsBackend) backend = g_memory_settings_backend_new();
return g_settings_new_full(schema, backend, nullptr);
}
TEST_F(FlGnomeSettingsTest, ClockFormat) {
g_autoptr(GSettings) interface_settings =
create_settings("ubuntu-20.04", "org.gnome.desktop.interface");
g_settings_set_string(interface_settings, "clock-format", "24h");
g_autoptr(FlSettings) settings = FL_SETTINGS(
g_object_new(fl_gnome_settings_get_type(), "interface_settings",
interface_settings, nullptr));
EXPECT_EQ(fl_settings_get_clock_format(settings), FL_CLOCK_FORMAT_24H);
flutter::testing::MockSignalHandler settings_changed(settings, "changed");
EXPECT_SIGNAL(settings_changed).Times(1);
g_settings_set_string(interface_settings, "clock-format", "12h");
EXPECT_EQ(fl_settings_get_clock_format(settings), FL_CLOCK_FORMAT_12H);
}
TEST_F(FlGnomeSettingsTest, GtkTheme) {
g_autoptr(GSettings) interface_settings =
create_settings("ubuntu-20.04", "org.gnome.desktop.interface");
g_settings_set_string(interface_settings, "gtk-theme", "Yaru");
g_autoptr(FlSettings) settings = FL_SETTINGS(
g_object_new(fl_gnome_settings_get_type(), "interface_settings",
interface_settings, nullptr));
EXPECT_EQ(fl_settings_get_color_scheme(settings), FL_COLOR_SCHEME_LIGHT);
flutter::testing::MockSignalHandler settings_changed(settings, "changed");
EXPECT_SIGNAL(settings_changed).Times(1);
g_settings_set_string(interface_settings, "gtk-theme", "Yaru-dark");
EXPECT_EQ(fl_settings_get_color_scheme(settings), FL_COLOR_SCHEME_DARK);
}
TEST_F(FlGnomeSettingsTest, TextScalingFactor) {
g_autoptr(GSettings) interface_settings =
create_settings("ubuntu-20.04", "org.gnome.desktop.interface");
g_settings_set_double(interface_settings, "text-scaling-factor", 1.0);
g_autoptr(FlSettings) settings = FL_SETTINGS(
g_object_new(fl_gnome_settings_get_type(), "interface_settings",
interface_settings, nullptr));
EXPECT_EQ(fl_settings_get_text_scaling_factor(settings), 1.0);
flutter::testing::MockSignalHandler settings_changed(settings, "changed");
EXPECT_SIGNAL(settings_changed).Times(1);
g_settings_set_double(interface_settings, "text-scaling-factor", 1.5);
EXPECT_EQ(fl_settings_get_text_scaling_factor(settings), 1.5);
}
TEST_F(FlGnomeSettingsTest, SignalHandlers) {
g_autoptr(GSettings) interface_settings =
create_settings("ubuntu-20.04", "org.gnome.desktop.interface");
g_autoptr(FlSettings) settings = FL_SETTINGS(
g_object_new(fl_gnome_settings_get_type(), "interface_settings",
interface_settings, nullptr));
flutter::testing::MockSignalHandler settings_changed(settings, "changed");
EXPECT_SIGNAL(settings_changed).Times(3);
g_settings_set_string(interface_settings, "clock-format", "12h");
g_settings_set_string(interface_settings, "gtk-theme", "Yaru-dark");
g_settings_set_double(interface_settings, "text-scaling-factor", 1.5);
EXPECT_SIGNAL(settings_changed).Times(0);
g_clear_object(&settings);
// destroyed FlSettings object must have disconnected its signal handlers
g_settings_set_string(interface_settings, "clock-format", "24h");
g_settings_set_string(interface_settings, "gtk-theme", "Yaru");
g_settings_set_double(interface_settings, "text-scaling-factor", 2.0);
}

View File

@ -0,0 +1,49 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/linux/fl_settings.h"
#include "flutter/shell/platform/linux/fl_gnome_settings.h"
G_DEFINE_INTERFACE(FlSettings, fl_settings, G_TYPE_OBJECT)
enum {
SIGNAL_CHANGED,
SIGNAL_LAST_SIGNAL,
};
static guint signals[SIGNAL_LAST_SIGNAL];
static void fl_settings_default_init(FlSettingsInterface* iface) {
/**
* FlSettings::changed:
* @settings: an #FlSettings
*
* This signal is emitted after the settings have been changed.
*/
signals[SIGNAL_CHANGED] =
g_signal_new("changed", G_TYPE_FROM_INTERFACE(iface), G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL, G_TYPE_NONE, 0);
}
FlClockFormat fl_settings_get_clock_format(FlSettings* self) {
return FL_SETTINGS_GET_IFACE(self)->get_clock_format(self);
}
FlColorScheme fl_settings_get_color_scheme(FlSettings* self) {
return FL_SETTINGS_GET_IFACE(self)->get_color_scheme(self);
}
gdouble fl_settings_get_text_scaling_factor(FlSettings* self) {
return FL_SETTINGS_GET_IFACE(self)->get_text_scaling_factor(self);
}
void fl_settings_emit_changed(FlSettings* self) {
g_return_if_fail(FL_IS_SETTINGS(self));
g_signal_emit(self, signals[SIGNAL_CHANGED], 0);
}
FlSettings* fl_settings_new() {
// TODO(jpnurmi): add support for other desktop environments
return FL_SETTINGS(fl_gnome_settings_new());
}

View File

@ -0,0 +1,106 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_H_
#include <glib-object.h>
G_BEGIN_DECLS
G_DECLARE_INTERFACE(FlSettings, fl_settings, FL, SETTINGS, GObject)
/**
* FlClockFormat:
* @FL_CLOCK_FORMAT_12H: 12-hour clock format.
* @FL_CLOCK_FORMAT_24H: 24-hour clock format.
*
* Available clock formats.
*/
typedef enum {
FL_CLOCK_FORMAT_12H,
FL_CLOCK_FORMAT_24H,
} FlClockFormat;
/**
* FlColorScheme:
* @FL_COLOR_SCHEME_LIGHT: Prefer light theme.
* @FL_COLOR_SCHEME_DARK: Prefer dark theme.
*
* Available color schemes.
*/
typedef enum {
FL_COLOR_SCHEME_LIGHT,
FL_COLOR_SCHEME_DARK,
} FlColorScheme;
/**
* FlSettings:
* #FlSettings is and object that provides desktop settings.
*/
struct _FlSettingsInterface {
GTypeInterface parent;
FlClockFormat (*get_clock_format)(FlSettings* settings);
FlColorScheme (*get_color_scheme)(FlSettings* settings);
gdouble (*get_text_scaling_factor)(FlSettings* settings);
};
/**
* fl_settings_new:
*
* Creates a new settings instance.
*
* Returns: a new #FlSettings.
*/
FlSettings* fl_settings_new();
/**
* fl_settings_get_clock_format:
* @settings: an #FlSettings.
*
* Whether the clock displays in 24-hour or 12-hour format.
*
* This corresponds to `org.gnome.desktop.interface.clock-format` in GNOME.
*
* Returns: an #FlClockFormat.
*/
FlClockFormat fl_settings_get_clock_format(FlSettings* settings);
/**
* fl_settings_get_color_scheme:
* @settings: an #FlSettings.
*
* The preferred color scheme for the user interface.
*
* This corresponds to `org.gnome.desktop.interface.color-scheme` in GNOME.
*
* Returns: an #FlColorScheme.
*/
FlColorScheme fl_settings_get_color_scheme(FlSettings* settings);
/**
* fl_settings_get_text_scaling_factor:
* @settings: an #FlSettings.
*
* Factor used to enlarge or reduce text display, without changing font size.
*
* This corresponds to `org.gnome.desktop.interface.text-scaling-factor` in
* GNOME.
*
* Returns: a floating point number.
*/
gdouble fl_settings_get_text_scaling_factor(FlSettings* settings);
/**
* fl_settings_emit_changed:
* @settings: an #FlSettings.
*
* Emits the "changed" signal. Used by FlSettings implementations to notify when
* the desktop settings have changed.
*/
void fl_settings_emit_changed(FlSettings* settings);
G_END_DECLS
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_H_

View File

@ -5,8 +5,6 @@
#include "flutter/shell/platform/linux/fl_settings_plugin.h"
#include <gmodule.h>
#include <gtk/gtk.h>
#include <math.h>
#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"
@ -18,98 +16,42 @@ static constexpr char kPlatformBrightnessKey[] = "platformBrightness";
static constexpr char kPlatformBrightnessLight[] = "light";
static constexpr char kPlatformBrightnessDark[] = "dark";
static constexpr char kDesktopInterfaceSchema[] = "org.gnome.desktop.interface";
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;
FlBasicMessageChannel* channel;
GSettings* interface_settings;
GArray* connections;
FlSettings* settings;
};
G_DEFINE_TYPE(FlSettingsPlugin, fl_settings_plugin, G_TYPE_OBJECT)
// 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 <https://www.w3.org/TR/WCAG20/#relativeluminancedef>.
static gdouble linearize_color_component(gdouble component) {
if (component <= 0.03928) {
return component / 12.92;
static const gchar* to_platform_brightness(FlColorScheme color_scheme) {
switch (color_scheme) {
case FL_COLOR_SCHEME_LIGHT:
return kPlatformBrightnessLight;
case FL_COLOR_SCHEME_DARK:
return kPlatformBrightnessDark;
default:
g_return_val_if_reached(nullptr);
}
return pow((component + 0.055) / 1.055, 2.4);
}
// See <https://en.wikipedia.org/wiki/Relative_luminance>.
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 <https://www.w3.org/TR/WCAG20/#contrast-ratiodef> and
// <https://material.io/go/design-theming#color-color-palette>.
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) {
return false;
}
GtkWidget* window = GTK_WIDGET(windows->data);
g_list_free(windows);
GdkRGBA text_color;
GtkStyleContext* style = gtk_widget_get_style_context(window);
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.
static void update_settings(FlSettingsPlugin* self) {
gdouble scaling_factor = 1.0;
gboolean always_use_24hr = FALSE;
const gchar* platform_brightness = kPlatformBrightnessLight;
if (self->interface_settings != nullptr) {
scaling_factor = g_settings_get_double(self->interface_settings,
kDesktopTextScalingFactorKey);
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()) {
platform_brightness = kPlatformBrightnessDark;
}
FlClockFormat clock_format = fl_settings_get_clock_format(self->settings);
FlColorScheme color_scheme = fl_settings_get_color_scheme(self->settings);
gdouble scaling_factor = fl_settings_get_text_scaling_factor(self->settings);
g_autoptr(FlValue) message = fl_value_new_map();
fl_value_set_string_take(message, kTextScaleFactorKey,
fl_value_new_float(scaling_factor));
fl_value_set_string_take(message, kAlwaysUse24HourFormatKey,
fl_value_new_bool(always_use_24hr));
fl_value_set_string_take(message, kPlatformBrightnessKey,
fl_value_new_string(platform_brightness));
fl_value_set_string_take(
message, kAlwaysUse24HourFormatKey,
fl_value_new_bool(clock_format == FL_CLOCK_FORMAT_24H));
fl_value_set_string_take(
message, kPlatformBrightnessKey,
fl_value_new_string(to_platform_brightness(color_scheme)));
fl_basic_message_channel_send(self->channel, message, nullptr, nullptr,
nullptr);
}
@ -117,13 +59,8 @@ static void update_settings(FlSettingsPlugin* self) {
static void fl_settings_plugin_dispose(GObject* object) {
FlSettingsPlugin* self = FL_SETTINGS_PLUGIN(object);
for (guint i = 0; i < self->connections->len; i += 1) {
g_signal_handler_disconnect(self->interface_settings,
g_array_index(self->connections, gulong, i));
}
g_array_unref(self->connections);
g_clear_object(&self->channel);
g_clear_object(&self->interface_settings);
g_clear_object(&self->settings);
G_OBJECT_CLASS(fl_settings_plugin_parent_class)->dispose(object);
}
@ -143,36 +80,17 @@ FlSettingsPlugin* fl_settings_plugin_new(FlBinaryMessenger* messenger) {
g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
self->channel = fl_basic_message_channel_new(messenger, kChannelName,
FL_MESSAGE_CODEC(codec));
self->connections = g_array_new(FALSE, FALSE, sizeof(gulong));
return self;
}
void fl_settings_plugin_start(FlSettingsPlugin* self) {
void fl_settings_plugin_start(FlSettingsPlugin* self, FlSettings* settings) {
g_return_if_fail(FL_IS_SETTINGS_PLUGIN(self));
g_return_if_fail(FL_IS_SETTINGS(settings));
// If we are on GNOME, get settings from GSettings.
GSettingsSchemaSource* source = g_settings_schema_source_get_default();
if (source != nullptr) {
g_autoptr(GSettingsSchema) schema =
g_settings_schema_source_lookup(source, kDesktopInterfaceSchema, FALSE);
if (schema != nullptr) {
self->interface_settings = g_settings_new_full(schema, nullptr, nullptr);
gulong new_connections[] = {
g_signal_connect_object(
self->interface_settings, "changed::text-scaling-factor",
G_CALLBACK(update_settings), self, G_CONNECT_SWAPPED),
g_signal_connect_object(
self->interface_settings, "changed::clock-format",
G_CALLBACK(update_settings), self, G_CONNECT_SWAPPED),
g_signal_connect_object(
self->interface_settings, "changed::gtk-theme",
G_CALLBACK(update_settings), self, G_CONNECT_SWAPPED),
};
g_array_append_vals(self->connections, new_connections,
sizeof(new_connections) / sizeof(gulong));
}
}
self->settings = FL_SETTINGS(g_object_ref(settings));
g_signal_connect_object(settings, "changed", G_CALLBACK(update_settings),
self, G_CONNECT_SWAPPED);
update_settings(self);
}

View File

@ -5,6 +5,7 @@
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PLUGIN_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PLUGIN_H_
#include "flutter/shell/platform/linux/fl_settings.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
G_BEGIN_DECLS
@ -38,7 +39,7 @@ FlSettingsPlugin* fl_settings_plugin_new(FlBinaryMessenger* messenger);
*
* Sends the current settings to the engine and updates when they change.
*/
void fl_settings_plugin_start(FlSettingsPlugin* plugin);
void fl_settings_plugin_start(FlSettingsPlugin* plugin, FlSettings* settings);
G_END_DECLS

View File

@ -0,0 +1,111 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/linux/fl_settings_plugin.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h"
#include "flutter/shell/platform/linux/testing/fl_test.h"
#include "flutter/shell/platform/linux/testing/mock_binary_messenger.h"
#include "flutter/shell/platform/linux/testing/mock_settings.h"
#include "flutter/testing/testing.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
MATCHER_P2(HasSetting, key, value, "") {
g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
g_autoptr(FlValue) message =
fl_message_codec_decode_message(FL_MESSAGE_CODEC(codec), arg, nullptr);
if (fl_value_equal(fl_value_lookup_string(message, key), value)) {
return true;
}
*result_listener << ::testing::PrintToString(message);
return false;
}
#define EXPECT_SETTING(mock, messenger, key, value) \
EXPECT_CALL(mock, fl_binary_messenger_send_on_channel( \
messenger, ::testing::StrEq("flutter/settings"), \
HasSetting(key, value), ::testing::A<GCancellable*>(), \
::testing::A<GAsyncReadyCallback>(), \
::testing::A<gpointer>()))
TEST(FlSettingsPluginTest, AlwaysUse24HourFormat) {
::testing::NiceMock<flutter::testing::MockSettings> settings;
::testing::NiceMock<flutter::testing::MockBinaryMessenger> mock_messenger;
g_autoptr(FlBinaryMessenger) messenger =
fl_binary_messenger_new_mock(&mock_messenger);
g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(messenger);
g_autoptr(FlValue) use_12h = fl_value_new_bool(false);
g_autoptr(FlValue) use_24h = fl_value_new_bool(true);
EXPECT_CALL(settings, fl_settings_get_clock_format(
::testing::Eq<FlSettings*>(settings)))
.WillOnce(::testing::Return(FL_CLOCK_FORMAT_12H))
.WillOnce(::testing::Return(FL_CLOCK_FORMAT_24H));
EXPECT_SETTING(mock_messenger, messenger, "alwaysUse24HourFormat", use_12h);
fl_settings_plugin_start(plugin, settings);
EXPECT_SETTING(mock_messenger, messenger, "alwaysUse24HourFormat", use_24h);
fl_settings_emit_changed(settings);
}
TEST(FlSettingsPluginTest, PlatformBrightness) {
::testing::NiceMock<flutter::testing::MockSettings> settings;
::testing::NiceMock<flutter::testing::MockBinaryMessenger> mock_messenger;
g_autoptr(FlBinaryMessenger) messenger =
fl_binary_messenger_new_mock(&mock_messenger);
g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(messenger);
g_autoptr(FlValue) light = fl_value_new_string("light");
g_autoptr(FlValue) dark = fl_value_new_string("dark");
EXPECT_CALL(settings, fl_settings_get_color_scheme(
::testing::Eq<FlSettings*>(settings)))
.WillOnce(::testing::Return(FL_COLOR_SCHEME_LIGHT))
.WillOnce(::testing::Return(FL_COLOR_SCHEME_DARK));
EXPECT_SETTING(mock_messenger, messenger, "platformBrightness", light);
fl_settings_plugin_start(plugin, settings);
EXPECT_SETTING(mock_messenger, messenger, "platformBrightness", dark);
fl_settings_emit_changed(settings);
}
TEST(FlSettingsPluginTest, TextScaleFactor) {
::testing::NiceMock<flutter::testing::MockSettings> settings;
::testing::NiceMock<flutter::testing::MockBinaryMessenger> mock_messenger;
g_autoptr(FlBinaryMessenger) messenger =
fl_binary_messenger_new_mock(&mock_messenger);
g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(messenger);
g_autoptr(FlValue) one = fl_value_new_float(1.0);
g_autoptr(FlValue) two = fl_value_new_float(2.0);
EXPECT_CALL(settings, fl_settings_get_text_scaling_factor(
::testing::Eq<FlSettings*>(settings)))
.WillOnce(::testing::Return(1.0))
.WillOnce(::testing::Return(2.0));
EXPECT_SETTING(mock_messenger, messenger, "textScaleFactor", one);
fl_settings_plugin_start(plugin, settings);
EXPECT_SETTING(mock_messenger, messenger, "textScaleFactor", two);
fl_settings_emit_changed(settings);
}

View File

@ -0,0 +1,21 @@
# gsettings-desktop-schemas
This directory contains a few variants of
[gsettings-desktop-schemas](https://packages.ubuntu.com/search?keywords=gsettings-desktop-schemas)
with different schemas for testing purposes.
- [`ubuntu-20.04.compiled`](https://packages.ubuntu.com/focal/gsettings-desktop-schemas)
### Add or update schemas
```bash
# download gsettings-desktop-schemas package
wget http://archive.ubuntu.com/ubuntu/pool/main/g/gsettings-desktop-schemas/gsettings-desktop-schemas_<version>.deb
# extract schema sources (/usr/share/glib-2.0/schemas/*.gschema.xml & .override)
ar x gsettings-desktop-schemas_<version>.deb
tar xf data.tar.zst
# compile schemas (/usr/share/glib-2.0/schemas/gschemas.compiled)
glib-compile-schemas --targetdir path/to/testing/gschemas usr/share/glib-2.0/schemas/
```

View File

@ -0,0 +1,70 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/linux/testing/mock_settings.h"
using namespace flutter::testing;
G_DECLARE_FINAL_TYPE(FlMockSettings,
fl_mock_settings,
FL,
MOCK_SETTINGS,
GObject)
struct _FlMockSettings {
GObject parent_instance;
MockSettings* mock;
};
static void fl_mock_settings_iface_init(FlSettingsInterface* iface);
#define FL_UNUSED(x) (void)x;
G_DEFINE_TYPE_WITH_CODE(FlMockSettings,
fl_mock_settings,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(fl_settings_get_type(),
fl_mock_settings_iface_init)
FL_UNUSED(FL_IS_MOCK_SETTINGS))
static void fl_mock_settings_class_init(FlMockSettingsClass* klass) {}
static FlClockFormat fl_mock_settings_get_clock_format(FlSettings* settings) {
FlMockSettings* self = FL_MOCK_SETTINGS(settings);
return self->mock->fl_settings_get_clock_format(settings);
}
static FlColorScheme fl_mock_settings_get_color_scheme(FlSettings* settings) {
FlMockSettings* self = FL_MOCK_SETTINGS(settings);
return self->mock->fl_settings_get_color_scheme(settings);
}
static gdouble fl_mock_settings_get_text_scaling_factor(FlSettings* settings) {
FlMockSettings* self = FL_MOCK_SETTINGS(settings);
return self->mock->fl_settings_get_text_scaling_factor(settings);
}
static void fl_mock_settings_iface_init(FlSettingsInterface* iface) {
iface->get_clock_format = fl_mock_settings_get_clock_format;
iface->get_color_scheme = fl_mock_settings_get_color_scheme;
iface->get_text_scaling_factor = fl_mock_settings_get_text_scaling_factor;
}
static void fl_mock_settings_init(FlMockSettings* self) {}
MockSettings::MockSettings()
: instance_(
FL_SETTINGS(g_object_new(fl_mock_settings_get_type(), nullptr))) {
FL_MOCK_SETTINGS(instance_)->mock = this;
}
MockSettings::~MockSettings() {
if (instance_ != nullptr) {
g_clear_object(&instance_);
}
}
MockSettings::operator FlSettings*() {
return instance_;
}

View File

@ -0,0 +1,39 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_SETTINGS_H_
#define FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_SETTINGS_H_
#include "flutter/shell/platform/linux/fl_settings.h"
#include "gmock/gmock.h"
namespace flutter {
namespace testing {
// Mock for FlSettings.
class MockSettings {
public:
MockSettings();
~MockSettings();
operator FlSettings*();
MOCK_METHOD1(fl_settings_get_clock_format,
FlClockFormat(FlSettings* settings));
MOCK_METHOD1(fl_settings_get_color_scheme,
FlColorScheme(FlSettings* settings));
MOCK_METHOD1(fl_settings_get_text_scaling_factor,
gdouble(FlSettings* settings));
private:
FlSettings* instance_ = nullptr;
};
} // namespace testing
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_SETTINGS_H_