From efc980a4975a9e80dae3edbb9040a3e6e113d523 Mon Sep 17 00:00:00 2001 From: Andy Weiss Date: Fri, 20 Nov 2020 14:34:32 -0800 Subject: [PATCH 1/9] Add support for locales to glfw shell The other linux shell (and all the other embedding) have support for getting the locales from the system and sending them over the flutter/localization channel. The glfw shell does not have that which is causing us to crash on an assert now that Locale is no longer nullable in Platform. This adds a similar approach to what is going on over in the other linux shell to this one. I haven't added tests or anything yet, just wanted to get this out there to see if this is reasonable. --- shell/platform/glfw/flutter_glfw.cc | 90 +++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/shell/platform/glfw/flutter_glfw.cc b/shell/platform/glfw/flutter_glfw.cc index ee26ea2452f4a..a600841e0c287 100644 --- a/shell/platform/glfw/flutter_glfw.cc +++ b/shell/platform/glfw/flutter_glfw.cc @@ -12,6 +12,7 @@ #include #include #include +#include #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h" #include "flutter/shell/platform/common/cpp/incoming_message_dispatcher.h" @@ -690,6 +691,93 @@ static bool RunFlutterEngine( return true; } +const char* GetLocaleStringFromEnvironment() { + const char* retval; + retval = getenv("LANGUAGE"); + if ((retval != NULL) && (retval[0] != '\0')) { + return retval; + } + retval = getenv("LC_ALL"); + if ((retval != NULL) && (retval[0] != '\0')) { + return retval; + } + retval = getenv("LC_MESSAGES"); + if ((retval != NULL) && (retval[0] != '\0')) { + return retval; + } + retval = getenv("LANG"); + if ((retval != NULL) && (retval[0] != '\0')) { + return retval; + } + + return NULL; +} + +// Parse a locale into its components. +static void ParseLocale(const std::string& locale, + std::string* language, + std::string* territory, + std::string* codeset, + std::string* modifier) { + // #include + // Locales are in the form "language[_territory][.codeset][@modifier]" + std::string::size_type end = locale.size(); + std::string::size_type modifier_pos = locale.rfind('@'); + if (modifier_pos != std::string::npos) { + *modifier = locale.substr(modifier_pos + 1, end - modifier_pos - 1); + end = modifier_pos; + } else { + *modifier = nullptr; + } + + std::string::size_type codeset_pos = locale.rfind('.'); + if (codeset_pos != std::string::npos) { + *codeset = locale.substr(codeset_pos + 1, end - codeset_pos - 1); + end = codeset_pos; + } else { + *codeset = nullptr; + } + + std::string::size_type territory_pos = locale.rfind('_'); + if (territory_pos != std::string::npos) { + *territory = locale.substr(territory_pos + 1, end - territory_pos - 1); + end = territory_pos; + } else { + *territory = nullptr; + } + + *language = local.substr(0, end); +} + +static void SetUpLocales(FlutterDesktopEngineState* state) { + const char* locale_string; + locale_string = GetLocaleStringFromEnvironment(); + if (!locale_string) { + locale_string = "C"; + } + std::istringstream locales_stream(locale_string); + std::vector> locales; + std::string s; + while (getline(locales_stream, s, ':')) { + locales.push_back(std::make_unique()); + std::string language, territory, codeset, modifier; + ParseLocale(s, &language, &territory, &codeset, &modifier); + locales.back()->struct_size = sizeof(FlutterLocale); + locales.back()->language_code = language.c_str(); + locales.back()->country_code = territory.c_str(); + locales.back()->script_code = codeset.c_str(); + locales.back()->variant_code = modifier.c_str(); + } + FlutterLocale** locales_array = + reinterpret_cast(&locales[0]); + FlutterEngineResult result = FlutterEngineUpdateLocales( + state->flutter_engine, const_cast(locales_array), + locales.size()); + if (result != kSuccess) { + std::cerr << "Failed to set up Flutter locales." << std::endl; + } +} + // Populates |state|'s helper object fields that are common to normal and // headless mode. // @@ -713,6 +801,8 @@ static void SetUpCommonEngineState(FlutterDesktopEngineState* state, // System channel handler. state->platform_handler = std::make_unique( state->internal_plugin_registrar->messenger(), window); + + SetUpLocales(state); } bool FlutterDesktopInit() { From d3c3dab28bb6e207d76ff6f2db0da0d4873850ad Mon Sep 17 00:00:00 2001 From: Andy Weiss Date: Sun, 22 Nov 2020 14:54:29 -0800 Subject: [PATCH 2/9] Add tests for glfw locales --- shell/platform/glfw/BUILD.gn | 6 ++++++ shell/platform/glfw/flutter_glfw_test.cc | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 shell/platform/glfw/flutter_glfw_test.cc diff --git a/shell/platform/glfw/BUILD.gn b/shell/platform/glfw/BUILD.gn index b0e65d6328cbf..3a84f5c4d8d89 100644 --- a/shell/platform/glfw/BUILD.gn +++ b/shell/platform/glfw/BUILD.gn @@ -71,6 +71,12 @@ source_set("flutter_glfw") { } } +executable("flutter_glfw_tests") { + testonly = true + deps = [":flutter_glfw"] + sources = ["flutter_glfw_test.cc"] +} + copy("publish_headers_glfw") { sources = _public_headers outputs = [ "$root_out_dir/{{source_file_part}}" ] diff --git a/shell/platform/glfw/flutter_glfw_test.cc b/shell/platform/glfw/flutter_glfw_test.cc new file mode 100644 index 0000000000000..cef6493f6fe70 --- /dev/null +++ b/shell/platform/glfw/flutter_glfw_test.cc @@ -0,0 +1,22 @@ +// 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/glfw/public/flutter_glfw.h" + +#include "gtest/gtest.h" + +Test(FlutterGlfwTest, CreateWindow) { + FlutterDesktopInit(); + FlutterDesktopWindowProperties window_properties; + window_properties.title = "foo"; + window_properties.width = 100; + window_properties.height = 100; + FlutterDesktopEngineProperties engine_properties; + engine_properties.assets_path = ""; + engine_properties.icu_data_path = ""; + auto ref = FlutterDesktopCreateWindow(window_properties, engine_properties); + EXPECT_NE(ref, nullptr); + FlutterDesktopDestroyWindow(ref); + FlutterDesktopTerminate(); +} From 85117d5d4e59a364db0530b9921ffb9f87e741eb Mon Sep 17 00:00:00 2001 From: Andy Weiss Date: Sun, 22 Nov 2020 14:54:29 -0800 Subject: [PATCH 3/9] Add tests for glfw locales --- BUILD.gn | 5 + shell/platform/glfw/BUILD.gn | 17 ++- shell/platform/glfw/flutter_glfw.cc | 86 ++++++++---- shell/platform/glfw/flutter_glfw_private.h | 30 +++++ shell/platform/glfw/flutter_glfw_test.cc | 144 +++++++++++++++++++-- testing/run_tests.py | 1 + 6 files changed, 240 insertions(+), 43 deletions(-) create mode 100644 shell/platform/glfw/flutter_glfw_private.h diff --git a/BUILD.gn b/BUILD.gn index e280ae9019179..75a0036ccac39 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -4,6 +4,7 @@ import("//flutter/common/config.gni") import("//flutter/shell/platform/config.gni") +import("//flutter/shell/platform/glfw/config.gni") import("//flutter/testing/testing.gni") # Whether to build the dartdevc sdk, libraries, and source files @@ -140,6 +141,10 @@ group("flutter") { if (is_linux) { public_deps += [ "//flutter/shell/platform/linux:flutter_linux_unittests" ] + if (build_glfw_shell) { + public_deps += + [ "//flutter/shell/platform/glfw:flutter_glfw_unittests" ] + } } if (is_mac) { diff --git a/shell/platform/glfw/BUILD.gn b/shell/platform/glfw/BUILD.gn index 3a84f5c4d8d89..52b81472dd288 100644 --- a/shell/platform/glfw/BUILD.gn +++ b/shell/platform/glfw/BUILD.gn @@ -2,6 +2,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//flutter/testing/testing.gni") + _public_headers = [ "public/flutter_glfw.h" ] # Any files that are built by clients (client_wrapper code, library headers for @@ -32,6 +34,7 @@ source_set("flutter_glfw") { "event_loop.cc", "event_loop.h", "flutter_glfw.cc", + "flutter_glfw_private.h", "glfw_event_loop.cc", "glfw_event_loop.h", "headless_event_loop.cc", @@ -71,9 +74,19 @@ source_set("flutter_glfw") { } } -executable("flutter_glfw_tests") { +test_fixtures("flutter_glfw_fixtures") { + fixtures = [] +} + +executable("flutter_glfw_unittests") { testonly = true - deps = [":flutter_glfw"] + deps = [ + ":flutter_glfw_fixtures", + ":flutter_glfw_headers", + ":flutter_glfw", + "//flutter/shell/platform/embedder:embedder_headers", + "//flutter/testing", + ] sources = ["flutter_glfw_test.cc"] } diff --git a/shell/platform/glfw/flutter_glfw.cc b/shell/platform/glfw/flutter_glfw.cc index a600841e0c287..76dd48865b18d 100644 --- a/shell/platform/glfw/flutter_glfw.cc +++ b/shell/platform/glfw/flutter_glfw.cc @@ -12,12 +12,14 @@ #include #include #include +#include #include #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h" #include "flutter/shell/platform/common/cpp/incoming_message_dispatcher.h" #include "flutter/shell/platform/common/cpp/path_utils.h" #include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/glfw/flutter_glfw_private.h" #include "flutter/shell/platform/glfw/glfw_event_loop.h" #include "flutter/shell/platform/glfw/headless_event_loop.h" #include "flutter/shell/platform/glfw/key_event_handler.h" @@ -691,21 +693,24 @@ static bool RunFlutterEngine( return true; } -const char* GetLocaleStringFromEnvironment() { +const char* GetLocaleStringFromEnvironmentVariables(const char* language, + const char* lc_all, + const char* lc_messages, + const char* lang) { const char* retval; - retval = getenv("LANGUAGE"); + retval = language; if ((retval != NULL) && (retval[0] != '\0')) { return retval; } - retval = getenv("LC_ALL"); + retval = lc_all; if ((retval != NULL) && (retval[0] != '\0')) { return retval; } - retval = getenv("LC_MESSAGES"); + retval = lc_messages; if ((retval != NULL) && (retval[0] != '\0')) { return retval; } - retval = getenv("LANG"); + retval = lang; if ((retval != NULL) && (retval[0] != '\0')) { return retval; } @@ -713,46 +718,45 @@ const char* GetLocaleStringFromEnvironment() { return NULL; } +const char* GetLocaleStringFromEnvironment() { + return GetLocaleStringFromEnvironmentVariables( + getenv("LANGUAGE"), getenv("LC_ALL"), getenv("LC_MESSAGES"), + getenv("LANG")); +} + // Parse a locale into its components. -static void ParseLocale(const std::string& locale, - std::string* language, - std::string* territory, - std::string* codeset, - std::string* modifier) { - // #include +void ParseLocale(const std::string& locale, + std::string* language, + std::string* territory, + std::string* codeset, + std::string* modifier) { // Locales are in the form "language[_territory][.codeset][@modifier]" std::string::size_type end = locale.size(); std::string::size_type modifier_pos = locale.rfind('@'); if (modifier_pos != std::string::npos) { *modifier = locale.substr(modifier_pos + 1, end - modifier_pos - 1); end = modifier_pos; - } else { - *modifier = nullptr; } std::string::size_type codeset_pos = locale.rfind('.'); if (codeset_pos != std::string::npos) { *codeset = locale.substr(codeset_pos + 1, end - codeset_pos - 1); end = codeset_pos; - } else { - *codeset = nullptr; } std::string::size_type territory_pos = locale.rfind('_'); if (territory_pos != std::string::npos) { *territory = locale.substr(territory_pos + 1, end - territory_pos - 1); end = territory_pos; - } else { - *territory = nullptr; } - *language = local.substr(0, end); + *language = locale.substr(0, end); } -static void SetUpLocales(FlutterDesktopEngineState* state) { - const char* locale_string; - locale_string = GetLocaleStringFromEnvironment(); - if (!locale_string) { +std::vector> GetLocales( + const char* locale_string, + std::list& locale_storage) { + if (!locale_string || locale_string[0] == '\0') { locale_string = "C"; } std::istringstream locales_stream(locale_string); @@ -762,12 +766,40 @@ static void SetUpLocales(FlutterDesktopEngineState* state) { locales.push_back(std::make_unique()); std::string language, territory, codeset, modifier; ParseLocale(s, &language, &territory, &codeset, &modifier); - locales.back()->struct_size = sizeof(FlutterLocale); - locales.back()->language_code = language.c_str(); - locales.back()->country_code = territory.c_str(); - locales.back()->script_code = codeset.c_str(); - locales.back()->variant_code = modifier.c_str(); + FlutterLocale* locale = locales.back().get(); + locale->struct_size = sizeof(FlutterLocale); + if (!language.empty()) { + locale_storage.push_back(language); + locale->language_code = locale_storage.back().c_str(); + } + if (!territory.empty()) { + locale_storage.push_back(territory); + locale->country_code = locale_storage.back().c_str(); + } + if (!codeset.empty()) { + locale_storage.push_back(codeset); + locale->script_code = locale_storage.back().c_str(); + } + if (!modifier.empty()) { + locale_storage.push_back(modifier); + locale->variant_code = locale_storage.back().c_str(); + } } + return locales; +} + +// Returns parsed locales fetched from the environment. +std::vector> GetLocalesFromEnvironment( + std::list& locale_storage) { + return GetLocales(GetLocaleStringFromEnvironment(), locale_storage); +} + +// Passes locale information to the Flutter engine. +static void SetUpLocales(FlutterDesktopEngineState* state) { + // Helper to extend lifetime of the strings passed to Flutter. + std::list locale_storage; + std::vector> locales = + GetLocalesFromEnvironment(locale_storage); FlutterLocale** locales_array = reinterpret_cast(&locales[0]); FlutterEngineResult result = FlutterEngineUpdateLocales( diff --git a/shell/platform/glfw/flutter_glfw_private.h b/shell/platform/glfw/flutter_glfw_private.h new file mode 100644 index 0000000000000..eb606ba823fb6 --- /dev/null +++ b/shell/platform/glfw/flutter_glfw_private.h @@ -0,0 +1,30 @@ +// 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_GLFW_FLUTTER_GLFW_PRIVATE_H_ +#define FLUTTER_SHELL_PLATFORM_GLFW_FLUTTER_GLFW_PRIVATE_H_ + +#include +#include +#include +#include + +#include "flutter/shell/platform/embedder/embedder.h" + +const char* GetLocaleStringFromEnvironmentVariables(const char* language, + const char* lc_all, + const char* lc_messages, + const char* lang); + +void ParseLocale(const std::string& locale, + std::string* language, + std::string* territory, + std::string* codeset, + std::string* modifier); + +std::vector> GetLocales( + const char* locale_string, + std::list& locale_storage); + +#endif // FLUTTER_SHELL_PLATFORM_GLFW_FLUTTER_GLFW_PRIVATE_H_ diff --git a/shell/platform/glfw/flutter_glfw_test.cc b/shell/platform/glfw/flutter_glfw_test.cc index cef6493f6fe70..a10b0e863540b 100644 --- a/shell/platform/glfw/flutter_glfw_test.cc +++ b/shell/platform/glfw/flutter_glfw_test.cc @@ -2,21 +2,137 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/shell/platform/glfw/public/flutter_glfw.h" +#include "flutter/shell/platform/glfw/flutter_glfw_private.h" #include "gtest/gtest.h" -Test(FlutterGlfwTest, CreateWindow) { - FlutterDesktopInit(); - FlutterDesktopWindowProperties window_properties; - window_properties.title = "foo"; - window_properties.width = 100; - window_properties.height = 100; - FlutterDesktopEngineProperties engine_properties; - engine_properties.assets_path = ""; - engine_properties.icu_data_path = ""; - auto ref = FlutterDesktopCreateWindow(window_properties, engine_properties); - EXPECT_NE(ref, nullptr); - FlutterDesktopDestroyWindow(ref); - FlutterDesktopTerminate(); +TEST(FlutterGlfwTest, GetLocaleStringFromEnvironment) { + EXPECT_EQ( + GetLocaleStringFromEnvironmentVariables("sv:de", NULL, NULL, "sv_SE"), + "sv:de"); + EXPECT_EQ( + GetLocaleStringFromEnvironmentVariables(NULL, "en_EN", NULL, "sv_SE"), + "en_EN"); + EXPECT_EQ( + GetLocaleStringFromEnvironmentVariables(NULL, NULL, "de_DE", "sv_SE"), + "de_DE"); + EXPECT_EQ(GetLocaleStringFromEnvironmentVariables(NULL, NULL, NULL, "sv_SE"), + "sv_SE"); + EXPECT_EQ(GetLocaleStringFromEnvironmentVariables(NULL, NULL, NULL, NULL), + nullptr); +} + +TEST(FlutterGlfwTest, ParseLocaleSimple) { + std::string s = "en"; + std::string language, territory, codeset, modifier; + ParseLocale(s, &language, &territory, &codeset, &modifier); + EXPECT_EQ(language, "en"); + EXPECT_EQ(territory, ""); + EXPECT_EQ(codeset, ""); + EXPECT_EQ(modifier, ""); +} + +TEST(FlutterGlfwTest, ParseLocaleWithTerritory) { + std::string s = "en_GB"; + std::string language, territory, codeset, modifier; + ParseLocale(s, &language, &territory, &codeset, &modifier); + EXPECT_EQ(language, "en"); + EXPECT_EQ(territory, "GB"); + EXPECT_EQ(codeset, ""); + EXPECT_EQ(modifier, ""); +} + +TEST(FlutterGlfwTest, ParseLocaleWithCodeset) { + std::string s = "zh_CN.UTF-8"; + std::string language, territory, codeset, modifier; + ParseLocale(s, &language, &territory, &codeset, &modifier); + EXPECT_EQ(language, "zh"); + EXPECT_EQ(territory, "CN"); + EXPECT_EQ(codeset, "UTF-8"); + EXPECT_EQ(modifier, ""); +} + +TEST(FlutterGlfwTest, ParseLocaleFull) { + std::string s = "en_GB.ISO-8859-1@euro"; + std::string language, territory, codeset, modifier; + ParseLocale(s, &language, &territory, &codeset, &modifier); + EXPECT_EQ(language, "en"); + EXPECT_EQ(territory, "GB"); + EXPECT_EQ(codeset, "ISO-8859-1"); + EXPECT_EQ(modifier, "euro"); +} + +TEST(FlutterGlfwTest, GetLocalesSimple) { + const char* locale_string = "en_US"; + std::list locale_storage; + + std::vector> locales = + GetLocales(locale_string, locale_storage); + + EXPECT_EQ(locales.size(), 1UL); + + EXPECT_STREQ(locales[0]->language_code, "en"); + EXPECT_STREQ(locales[0]->country_code, "US"); + EXPECT_STREQ(locales[0]->script_code, nullptr); + EXPECT_STREQ(locales[0]->variant_code, nullptr); +} + +TEST(FlutterGlfwTest, GetLocalesFull) { + const char* locale_string = "en_GB.ISO-8859-1@euro:en_US:sv:zh_CN.UTF-8"; + std::list locale_storage; + + std::vector> locales = + GetLocales(locale_string, locale_storage); + + EXPECT_EQ(locales.size(), 4UL); + + EXPECT_STREQ(locales[0]->language_code, "en"); + EXPECT_STREQ(locales[0]->country_code, "GB"); + EXPECT_STREQ(locales[0]->script_code, "ISO-8859-1"); + EXPECT_STREQ(locales[0]->variant_code, "euro"); + + EXPECT_STREQ(locales[1]->language_code, "en"); + EXPECT_STREQ(locales[1]->country_code, "US"); + EXPECT_STREQ(locales[1]->script_code, nullptr); + EXPECT_STREQ(locales[1]->variant_code, nullptr); + + EXPECT_STREQ(locales[2]->language_code, "sv"); + EXPECT_STREQ(locales[2]->country_code, nullptr); + EXPECT_STREQ(locales[2]->script_code, nullptr); + EXPECT_STREQ(locales[2]->variant_code, nullptr); + + EXPECT_STREQ(locales[3]->language_code, "zh"); + EXPECT_STREQ(locales[3]->country_code, "CN"); + EXPECT_STREQ(locales[3]->script_code, "UTF-8"); + EXPECT_STREQ(locales[3]->variant_code, nullptr); +} + +TEST(FlutterGlfwTest, GetLocalesEmpty) { + const char* locale_string = ""; + std::list locale_storage; + + std::vector> locales = + GetLocales(locale_string, locale_storage); + + EXPECT_EQ(locales.size(), 1UL); + + EXPECT_STREQ(locales[0]->language_code, "C"); + EXPECT_STREQ(locales[0]->country_code, nullptr); + EXPECT_STREQ(locales[0]->script_code, nullptr); + EXPECT_STREQ(locales[0]->variant_code, nullptr); +} + +TEST(FlutterGlfwTest, GetLocalesNull) { + const char* locale_string; + std::list locale_storage; + + std::vector> locales = + GetLocales(locale_string, locale_storage); + + EXPECT_EQ(locales.size(), 1UL); + + EXPECT_STREQ(locales[0]->language_code, "C"); + EXPECT_STREQ(locales[0]->country_code, nullptr); + EXPECT_STREQ(locales[0]->script_code, nullptr); + EXPECT_STREQ(locales[0]->variant_code, nullptr); } diff --git a/testing/run_tests.py b/testing/run_tests.py index 8bdd114f43988..47b7986724360 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -158,6 +158,7 @@ def RunCCTests(build_dir, filter): if IsLinux(): RunEngineExecutable(build_dir, 'flutter_linux_unittests', filter, shuffle_flags) + RunEngineExecutable(build_dir, 'flutter_glfw_unittests', filter, shuffle_flags) def RunEngineBenchmarks(build_dir, filter): From ebd9dbdc11030bef2ea894530f63ff89650bedb8 Mon Sep 17 00:00:00 2001 From: Andy Weiss Date: Mon, 23 Nov 2020 00:23:41 -0800 Subject: [PATCH 4/9] Fix licenses --- ci/licenses_golden/licenses_flutter | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index d7eb1dac4dc2a..2b4fa2005de27 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1284,6 +1284,8 @@ FILE: ../../../flutter/shell/platform/glfw/client_wrapper/plugin_registrar_glfw_ FILE: ../../../flutter/shell/platform/glfw/event_loop.cc FILE: ../../../flutter/shell/platform/glfw/event_loop.h FILE: ../../../flutter/shell/platform/glfw/flutter_glfw.cc +FILE: ../../../flutter/shell/platform/glfw/flutter_glfw_private.h +FILE: ../../../flutter/shell/platform/glfw/flutter_glfw_test.cc FILE: ../../../flutter/shell/platform/glfw/glfw_event_loop.cc FILE: ../../../flutter/shell/platform/glfw/glfw_event_loop.h FILE: ../../../flutter/shell/platform/glfw/headless_event_loop.cc From 7fbf3ace7cc991a85166b3f22e13c468d0c055e9 Mon Sep 17 00:00:00 2001 From: Andy Weiss Date: Mon, 23 Nov 2020 13:52:14 -0800 Subject: [PATCH 5/9] Move locale parsing to a system_utils.h, reduce exposed functions and use environment variables in the tests --- ci/licenses_golden/licenses_flutter | 5 +- shell/platform/glfw/BUILD.gn | 7 +- shell/platform/glfw/flutter_glfw.cc | 126 +------- shell/platform/glfw/flutter_glfw_private.h | 30 -- shell/platform/glfw/flutter_glfw_test.cc | 138 --------- shell/platform/glfw/system_utils.cc | 154 ++++++++++ shell/platform/glfw/system_utils.h | 35 +++ shell/platform/glfw/system_utils_test.cc | 321 +++++++++++++++++++++ 8 files changed, 531 insertions(+), 285 deletions(-) delete mode 100644 shell/platform/glfw/flutter_glfw_private.h delete mode 100644 shell/platform/glfw/flutter_glfw_test.cc create mode 100644 shell/platform/glfw/system_utils.cc create mode 100644 shell/platform/glfw/system_utils.h create mode 100644 shell/platform/glfw/system_utils_test.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 2b4fa2005de27..66b17b09f7bf9 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1284,8 +1284,6 @@ FILE: ../../../flutter/shell/platform/glfw/client_wrapper/plugin_registrar_glfw_ FILE: ../../../flutter/shell/platform/glfw/event_loop.cc FILE: ../../../flutter/shell/platform/glfw/event_loop.h FILE: ../../../flutter/shell/platform/glfw/flutter_glfw.cc -FILE: ../../../flutter/shell/platform/glfw/flutter_glfw_private.h -FILE: ../../../flutter/shell/platform/glfw/flutter_glfw_test.cc FILE: ../../../flutter/shell/platform/glfw/glfw_event_loop.cc FILE: ../../../flutter/shell/platform/glfw/glfw_event_loop.h FILE: ../../../flutter/shell/platform/glfw/headless_event_loop.cc @@ -1296,6 +1294,9 @@ FILE: ../../../flutter/shell/platform/glfw/keyboard_hook_handler.h FILE: ../../../flutter/shell/platform/glfw/platform_handler.cc FILE: ../../../flutter/shell/platform/glfw/platform_handler.h FILE: ../../../flutter/shell/platform/glfw/public/flutter_glfw.h +FILE: ../../../flutter/shell/platform/glfw/system_utils.cc +FILE: ../../../flutter/shell/platform/glfw/system_utils.h +FILE: ../../../flutter/shell/platform/glfw/system_utils_test.cc FILE: ../../../flutter/shell/platform/glfw/text_input_plugin.cc FILE: ../../../flutter/shell/platform/glfw/text_input_plugin.h FILE: ../../../flutter/shell/platform/linux/egl_utils.cc diff --git a/shell/platform/glfw/BUILD.gn b/shell/platform/glfw/BUILD.gn index 52b81472dd288..28cbefd904c18 100644 --- a/shell/platform/glfw/BUILD.gn +++ b/shell/platform/glfw/BUILD.gn @@ -30,11 +30,12 @@ source_set("flutter_glfw_headers") { } source_set("flutter_glfw") { + public = _public_headers + ["system_utils.h"] + sources = [ "event_loop.cc", "event_loop.h", "flutter_glfw.cc", - "flutter_glfw_private.h", "glfw_event_loop.cc", "glfw_event_loop.h", "headless_event_loop.cc", @@ -44,6 +45,7 @@ source_set("flutter_glfw") { "keyboard_hook_handler.h", "platform_handler.cc", "platform_handler.h", + "system_utils.cc", "text_input_plugin.cc", "text_input_plugin.h", ] @@ -80,14 +82,13 @@ test_fixtures("flutter_glfw_fixtures") { executable("flutter_glfw_unittests") { testonly = true + sources = ["system_utils_test.cc"] deps = [ ":flutter_glfw_fixtures", - ":flutter_glfw_headers", ":flutter_glfw", "//flutter/shell/platform/embedder:embedder_headers", "//flutter/testing", ] - sources = ["flutter_glfw_test.cc"] } copy("publish_headers_glfw") { diff --git a/shell/platform/glfw/flutter_glfw.cc b/shell/platform/glfw/flutter_glfw.cc index 76dd48865b18d..423e5a80b6f31 100644 --- a/shell/platform/glfw/flutter_glfw.cc +++ b/shell/platform/glfw/flutter_glfw.cc @@ -12,19 +12,17 @@ #include #include #include -#include -#include #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h" #include "flutter/shell/platform/common/cpp/incoming_message_dispatcher.h" #include "flutter/shell/platform/common/cpp/path_utils.h" #include "flutter/shell/platform/embedder/embedder.h" -#include "flutter/shell/platform/glfw/flutter_glfw_private.h" #include "flutter/shell/platform/glfw/glfw_event_loop.h" #include "flutter/shell/platform/glfw/headless_event_loop.h" #include "flutter/shell/platform/glfw/key_event_handler.h" #include "flutter/shell/platform/glfw/keyboard_hook_handler.h" #include "flutter/shell/platform/glfw/platform_handler.h" +#include "flutter/shell/platform/glfw/system_utils.h" #include "flutter/shell/platform/glfw/text_input_plugin.h" // GLFW_TRUE & GLFW_FALSE are introduced since libglfw-3.3, @@ -693,118 +691,22 @@ static bool RunFlutterEngine( return true; } -const char* GetLocaleStringFromEnvironmentVariables(const char* language, - const char* lc_all, - const char* lc_messages, - const char* lang) { - const char* retval; - retval = language; - if ((retval != NULL) && (retval[0] != '\0')) { - return retval; - } - retval = lc_all; - if ((retval != NULL) && (retval[0] != '\0')) { - return retval; - } - retval = lc_messages; - if ((retval != NULL) && (retval[0] != '\0')) { - return retval; - } - retval = lang; - if ((retval != NULL) && (retval[0] != '\0')) { - return retval; - } - - return NULL; -} - -const char* GetLocaleStringFromEnvironment() { - return GetLocaleStringFromEnvironmentVariables( - getenv("LANGUAGE"), getenv("LC_ALL"), getenv("LC_MESSAGES"), - getenv("LANG")); -} - -// Parse a locale into its components. -void ParseLocale(const std::string& locale, - std::string* language, - std::string* territory, - std::string* codeset, - std::string* modifier) { - // Locales are in the form "language[_territory][.codeset][@modifier]" - std::string::size_type end = locale.size(); - std::string::size_type modifier_pos = locale.rfind('@'); - if (modifier_pos != std::string::npos) { - *modifier = locale.substr(modifier_pos + 1, end - modifier_pos - 1); - end = modifier_pos; - } - - std::string::size_type codeset_pos = locale.rfind('.'); - if (codeset_pos != std::string::npos) { - *codeset = locale.substr(codeset_pos + 1, end - codeset_pos - 1); - end = codeset_pos; - } - - std::string::size_type territory_pos = locale.rfind('_'); - if (territory_pos != std::string::npos) { - *territory = locale.substr(territory_pos + 1, end - territory_pos - 1); - end = territory_pos; - } - - *language = locale.substr(0, end); -} - -std::vector> GetLocales( - const char* locale_string, - std::list& locale_storage) { - if (!locale_string || locale_string[0] == '\0') { - locale_string = "C"; - } - std::istringstream locales_stream(locale_string); - std::vector> locales; - std::string s; - while (getline(locales_stream, s, ':')) { - locales.push_back(std::make_unique()); - std::string language, territory, codeset, modifier; - ParseLocale(s, &language, &territory, &codeset, &modifier); - FlutterLocale* locale = locales.back().get(); - locale->struct_size = sizeof(FlutterLocale); - if (!language.empty()) { - locale_storage.push_back(language); - locale->language_code = locale_storage.back().c_str(); - } - if (!territory.empty()) { - locale_storage.push_back(territory); - locale->country_code = locale_storage.back().c_str(); - } - if (!codeset.empty()) { - locale_storage.push_back(codeset); - locale->script_code = locale_storage.back().c_str(); - } - if (!modifier.empty()) { - locale_storage.push_back(modifier); - locale->variant_code = locale_storage.back().c_str(); - } - } - return locales; -} - -// Returns parsed locales fetched from the environment. -std::vector> GetLocalesFromEnvironment( - std::list& locale_storage) { - return GetLocales(GetLocaleStringFromEnvironment(), locale_storage); -} - // Passes locale information to the Flutter engine. static void SetUpLocales(FlutterDesktopEngineState* state) { - // Helper to extend lifetime of the strings passed to Flutter. - std::list locale_storage; - std::vector> locales = - GetLocalesFromEnvironment(locale_storage); - FlutterLocale** locales_array = - reinterpret_cast(&locales[0]); + std::vector languages = + flutter::GetPreferredLanguageInfo(); + std::vector flutter_locales = + flutter::ConvertToFlutterLocale(languages); + // Convert the locale list to the locale pointer list that must be provided. + std::vector flutter_locale_list; + flutter_locale_list.reserve(flutter_locales.size()); + std::transform( + flutter_locales.begin(), flutter_locales.end(), + std::back_inserter(flutter_locale_list), + [](const auto& arg) -> const auto* { return &arg; }); FlutterEngineResult result = FlutterEngineUpdateLocales( - state->flutter_engine, const_cast(locales_array), - locales.size()); + state->flutter_engine, flutter_locale_list.data(), + flutter_locale_list.size()); if (result != kSuccess) { std::cerr << "Failed to set up Flutter locales." << std::endl; } diff --git a/shell/platform/glfw/flutter_glfw_private.h b/shell/platform/glfw/flutter_glfw_private.h deleted file mode 100644 index eb606ba823fb6..0000000000000 --- a/shell/platform/glfw/flutter_glfw_private.h +++ /dev/null @@ -1,30 +0,0 @@ -// 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_GLFW_FLUTTER_GLFW_PRIVATE_H_ -#define FLUTTER_SHELL_PLATFORM_GLFW_FLUTTER_GLFW_PRIVATE_H_ - -#include -#include -#include -#include - -#include "flutter/shell/platform/embedder/embedder.h" - -const char* GetLocaleStringFromEnvironmentVariables(const char* language, - const char* lc_all, - const char* lc_messages, - const char* lang); - -void ParseLocale(const std::string& locale, - std::string* language, - std::string* territory, - std::string* codeset, - std::string* modifier); - -std::vector> GetLocales( - const char* locale_string, - std::list& locale_storage); - -#endif // FLUTTER_SHELL_PLATFORM_GLFW_FLUTTER_GLFW_PRIVATE_H_ diff --git a/shell/platform/glfw/flutter_glfw_test.cc b/shell/platform/glfw/flutter_glfw_test.cc deleted file mode 100644 index a10b0e863540b..0000000000000 --- a/shell/platform/glfw/flutter_glfw_test.cc +++ /dev/null @@ -1,138 +0,0 @@ -// 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/glfw/flutter_glfw_private.h" - -#include "gtest/gtest.h" - -TEST(FlutterGlfwTest, GetLocaleStringFromEnvironment) { - EXPECT_EQ( - GetLocaleStringFromEnvironmentVariables("sv:de", NULL, NULL, "sv_SE"), - "sv:de"); - EXPECT_EQ( - GetLocaleStringFromEnvironmentVariables(NULL, "en_EN", NULL, "sv_SE"), - "en_EN"); - EXPECT_EQ( - GetLocaleStringFromEnvironmentVariables(NULL, NULL, "de_DE", "sv_SE"), - "de_DE"); - EXPECT_EQ(GetLocaleStringFromEnvironmentVariables(NULL, NULL, NULL, "sv_SE"), - "sv_SE"); - EXPECT_EQ(GetLocaleStringFromEnvironmentVariables(NULL, NULL, NULL, NULL), - nullptr); -} - -TEST(FlutterGlfwTest, ParseLocaleSimple) { - std::string s = "en"; - std::string language, territory, codeset, modifier; - ParseLocale(s, &language, &territory, &codeset, &modifier); - EXPECT_EQ(language, "en"); - EXPECT_EQ(territory, ""); - EXPECT_EQ(codeset, ""); - EXPECT_EQ(modifier, ""); -} - -TEST(FlutterGlfwTest, ParseLocaleWithTerritory) { - std::string s = "en_GB"; - std::string language, territory, codeset, modifier; - ParseLocale(s, &language, &territory, &codeset, &modifier); - EXPECT_EQ(language, "en"); - EXPECT_EQ(territory, "GB"); - EXPECT_EQ(codeset, ""); - EXPECT_EQ(modifier, ""); -} - -TEST(FlutterGlfwTest, ParseLocaleWithCodeset) { - std::string s = "zh_CN.UTF-8"; - std::string language, territory, codeset, modifier; - ParseLocale(s, &language, &territory, &codeset, &modifier); - EXPECT_EQ(language, "zh"); - EXPECT_EQ(territory, "CN"); - EXPECT_EQ(codeset, "UTF-8"); - EXPECT_EQ(modifier, ""); -} - -TEST(FlutterGlfwTest, ParseLocaleFull) { - std::string s = "en_GB.ISO-8859-1@euro"; - std::string language, territory, codeset, modifier; - ParseLocale(s, &language, &territory, &codeset, &modifier); - EXPECT_EQ(language, "en"); - EXPECT_EQ(territory, "GB"); - EXPECT_EQ(codeset, "ISO-8859-1"); - EXPECT_EQ(modifier, "euro"); -} - -TEST(FlutterGlfwTest, GetLocalesSimple) { - const char* locale_string = "en_US"; - std::list locale_storage; - - std::vector> locales = - GetLocales(locale_string, locale_storage); - - EXPECT_EQ(locales.size(), 1UL); - - EXPECT_STREQ(locales[0]->language_code, "en"); - EXPECT_STREQ(locales[0]->country_code, "US"); - EXPECT_STREQ(locales[0]->script_code, nullptr); - EXPECT_STREQ(locales[0]->variant_code, nullptr); -} - -TEST(FlutterGlfwTest, GetLocalesFull) { - const char* locale_string = "en_GB.ISO-8859-1@euro:en_US:sv:zh_CN.UTF-8"; - std::list locale_storage; - - std::vector> locales = - GetLocales(locale_string, locale_storage); - - EXPECT_EQ(locales.size(), 4UL); - - EXPECT_STREQ(locales[0]->language_code, "en"); - EXPECT_STREQ(locales[0]->country_code, "GB"); - EXPECT_STREQ(locales[0]->script_code, "ISO-8859-1"); - EXPECT_STREQ(locales[0]->variant_code, "euro"); - - EXPECT_STREQ(locales[1]->language_code, "en"); - EXPECT_STREQ(locales[1]->country_code, "US"); - EXPECT_STREQ(locales[1]->script_code, nullptr); - EXPECT_STREQ(locales[1]->variant_code, nullptr); - - EXPECT_STREQ(locales[2]->language_code, "sv"); - EXPECT_STREQ(locales[2]->country_code, nullptr); - EXPECT_STREQ(locales[2]->script_code, nullptr); - EXPECT_STREQ(locales[2]->variant_code, nullptr); - - EXPECT_STREQ(locales[3]->language_code, "zh"); - EXPECT_STREQ(locales[3]->country_code, "CN"); - EXPECT_STREQ(locales[3]->script_code, "UTF-8"); - EXPECT_STREQ(locales[3]->variant_code, nullptr); -} - -TEST(FlutterGlfwTest, GetLocalesEmpty) { - const char* locale_string = ""; - std::list locale_storage; - - std::vector> locales = - GetLocales(locale_string, locale_storage); - - EXPECT_EQ(locales.size(), 1UL); - - EXPECT_STREQ(locales[0]->language_code, "C"); - EXPECT_STREQ(locales[0]->country_code, nullptr); - EXPECT_STREQ(locales[0]->script_code, nullptr); - EXPECT_STREQ(locales[0]->variant_code, nullptr); -} - -TEST(FlutterGlfwTest, GetLocalesNull) { - const char* locale_string; - std::list locale_storage; - - std::vector> locales = - GetLocales(locale_string, locale_storage); - - EXPECT_EQ(locales.size(), 1UL); - - EXPECT_STREQ(locales[0]->language_code, "C"); - EXPECT_STREQ(locales[0]->country_code, nullptr); - EXPECT_STREQ(locales[0]->script_code, nullptr); - EXPECT_STREQ(locales[0]->variant_code, nullptr); -} diff --git a/shell/platform/glfw/system_utils.cc b/shell/platform/glfw/system_utils.cc new file mode 100644 index 0000000000000..9bbaddc43b8ac --- /dev/null +++ b/shell/platform/glfw/system_utils.cc @@ -0,0 +1,154 @@ +// 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/glfw/system_utils.h" + +#include +#include + +namespace flutter { + +namespace { + +const char* GetLocaleStringFromEnvironment() { + const char* retval; + retval = getenv("LANGUAGE"); + if ((retval != NULL) && (retval[0] != '\0')) { + return retval; + } + retval = getenv("LC_ALL"); + if ((retval != NULL) && (retval[0] != '\0')) { + return retval; + } + retval = getenv("LC_MESSAGES"); + if ((retval != NULL) && (retval[0] != '\0')) { + return retval; + } + retval = getenv("LANG"); + if ((retval != NULL) && (retval[0] != '\0')) { + return retval; + } + + return NULL; +} + +// The least specific to most specific components of a locale. +enum class Component { + Codeset = 1 << 0, + Territory = 1 << 1, + Modifier = 1 << 2, +}; + +// Construct a mask indicating which of the components in |info| are set. +int ComputeVariantMask(const LanguageInfo& info) { + int mask = 0; + if (!info.territory.empty()) { + mask |= static_cast(Component::Territory); + } + if (!info.codeset.empty()) { + mask |= static_cast(Component::Codeset); + } + if (!info.modifier.empty()) { + mask |= static_cast(Component::Modifier); + } + return mask; +} + +// Appends most specific to least specific variants of |info| to |languages|. +// For example, "de_DE@euro" would append "de_DE@euro", "de@euro", "de_DE", +// and "de". +void AppendLocaleVariants(std::vector& languages, + LanguageInfo info) { + int mask = ComputeVariantMask(info); + for (int i = mask; i >= 0; --i) { + if ((i & ~mask) == 0) { + LanguageInfo variant; + variant.language = info.language; + + if (i & static_cast(Component::Territory)) { + variant.territory = info.territory; + } + if (i & static_cast(Component::Codeset)) { + variant.codeset = info.codeset; + } + if (i & static_cast(Component::Modifier)) { + variant.modifier = info.modifier; + } + languages.push_back(variant); + } + } +} + +// Parses a locale into its components. +LanguageInfo ParseLocale(const std::string& locale) { + // Locales are of the form "language[_territory][.codeset][@modifier]" + LanguageInfo result; + std::string::size_type end = locale.size(); + std::string::size_type modifier_pos = locale.rfind('@'); + if (modifier_pos != std::string::npos) { + result.modifier = locale.substr(modifier_pos + 1, end - modifier_pos - 1); + end = modifier_pos; + } + + std::string::size_type codeset_pos = locale.rfind('.', end); + if (codeset_pos != std::string::npos) { + result.codeset = locale.substr(codeset_pos + 1, end - codeset_pos - 1); + end = codeset_pos; + } + + std::string::size_type territory_pos = locale.rfind('_', end); + if (territory_pos != std::string::npos) { + result.territory = + locale.substr(territory_pos + 1, end - territory_pos - 1); + end = territory_pos; + } + + result.language = locale.substr(0, end); + + return result; +} + +} // namespace + +std::vector GetPreferredLanguageInfo() { + const char* locale_string; + locale_string = GetLocaleStringFromEnvironment(); + if (!locale_string || locale_string[0] == '\0') { + // This is the default locale if none is specified according to ISO C. + locale_string = "C"; + } + std::istringstream locales_stream(locale_string); + std::vector languages; + std::string s; + while (getline(locales_stream, s, ':')) { + LanguageInfo info = ParseLocale(s); + AppendLocaleVariants(languages, info); + } + return languages; +} + +std::vector ConvertToFlutterLocale( + const std::vector& languages) { + std::vector flutter_locales; + flutter_locales.reserve(languages.size()); + for (const auto& info : languages) { + FlutterLocale locale = {}; + locale.struct_size = sizeof(FlutterLocale); + locale.language_code = info.language.c_str(); + if (!info.territory.empty()) { + locale.country_code = info.territory.c_str(); + } + if (!info.codeset.empty()) { + locale.script_code = info.codeset.c_str(); + } + if (!info.modifier.empty()) { + locale.variant_code = info.modifier.c_str(); + } + flutter_locales.push_back(locale); + } + + return flutter_locales; +} + +} // namespace flutter diff --git a/shell/platform/glfw/system_utils.h b/shell/platform/glfw/system_utils.h new file mode 100644 index 0000000000000..5f287088d7cea --- /dev/null +++ b/shell/platform/glfw/system_utils.h @@ -0,0 +1,35 @@ +// 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_GLFW_SYSTEM_UTILS_H_ +#define FLUTTER_SHELL_PLATFORM_GLFW_SYSTEM_UTILS_H_ + +#include +#include + +#include "flutter/shell/platform/embedder/embedder.h" + +namespace flutter { + +// Components of a system language/locale. +struct LanguageInfo { + std::string language; + std::string territory; + std::string codeset; + std::string modifier; +}; + +// Returns the list of user-preferred languages, in preference order, +// parsed into LanguageInfo structures. +std::vector GetPreferredLanguageInfo(); + +// Converts a vector of LanguageInfo structs to a vector of FlutterLocale +// structs. |languages| must outlive the returned value, since the returned +// elements have pointers into it. +std::vector ConvertToFlutterLocale( + const std::vector& languages); + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_GLFW_SYSTEM_UTILS_H_ diff --git a/shell/platform/glfw/system_utils_test.cc b/shell/platform/glfw/system_utils_test.cc new file mode 100644 index 0000000000000..22c567e5933aa --- /dev/null +++ b/shell/platform/glfw/system_utils_test.cc @@ -0,0 +1,321 @@ +// 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/glfw/system_utils.h" + +#include + +#include "gtest/gtest.h" + +namespace flutter { +namespace { + +// This is a helper for setting up the different environment variables to +// specific strings, calling GetPreferredLanguageInfo, and then restoring those +// environment variables to any previously existing values. +std::vector SetAndRestoreLanguageAroundGettingLanguageInfo( + const char* language, + const char* lc_all, + const char* lc_messages, + const char* lang) { + std::string old_language; + const char* language_env = getenv("LANGUAGE"); + bool language_was_set = language_env != nullptr; + if (language_was_set) { + old_language = std::string(language_env); + } + std::string old_lc_all; + const char* lc_all_env = getenv("LC_ALL"); + bool lc_all_was_set = lc_all_env != nullptr; + if (lc_all_was_set) { + old_lc_all = std::string(lc_all_env); + } + std::string old_lc_messages; + const char* lc_messages_env = getenv("LC_MESSAGES"); + bool lc_messages_was_set = lc_messages_env != nullptr; + if (lc_messages_was_set) { + old_lc_messages = std::string(lc_messages_env); + } + std::string old_lang; + const char* lang_env = getenv("LANG"); + bool lang_was_set = lang_env != nullptr; + if (lang_was_set) { + old_lang = std::string(lang_env); + } + + if (language != nullptr) { + setenv("LANGUAGE", language, 1); + } else if (language_was_set) { + unsetenv("LANGUAGE"); + } + if (lc_all != nullptr) { + setenv("LC_ALL", lc_all, 1); + } else if (lc_all_was_set) { + unsetenv("LC_ALL"); + } + if (lc_messages != nullptr) { + setenv("LC_MESSAGES", lc_messages, 1); + } else if (lc_messages_was_set) { + unsetenv("LC_MESSAGES"); + } + if (lang != nullptr) { + setenv("LANG", lang, 1); + } else if (lang_was_set) { + unsetenv("LANG"); + } + + std::vector languages = GetPreferredLanguageInfo(); + + if (language_was_set) { + setenv("LANGUAGE", old_language.c_str(), 1); + } + if (lc_all_was_set) { + setenv("LC_ALL", old_lc_all.c_str(), 1); + } + if (lc_messages_was_set) { + setenv("LC_MESSAGES", old_lc_messages.c_str(), 1); + } + if (lang_was_set) { + setenv("LANG", old_lang.c_str(), 1); + } + + return languages; +} + +TEST(FlutterGlfwSystemUtilsTest, GetPreferredLanuageInfoFull) { + const char* locale_string = "en_GB.ISO-8859-1@euro:en_US:sv:zh_CN.UTF-8"; + + std::vector languages = + SetAndRestoreLanguageAroundGettingLanguageInfo(locale_string, nullptr, + nullptr, nullptr); + + EXPECT_EQ(languages.size(), 15UL); + + EXPECT_STREQ(languages[0].language.c_str(), "en"); + EXPECT_STREQ(languages[0].territory.c_str(), "GB"); + EXPECT_STREQ(languages[0].codeset.c_str(), "ISO-8859-1"); + EXPECT_STREQ(languages[0].modifier.c_str(), "euro"); + + EXPECT_STREQ(languages[1].language.c_str(), "en"); + EXPECT_STREQ(languages[1].territory.c_str(), "GB"); + EXPECT_STREQ(languages[1].codeset.c_str(), ""); + EXPECT_STREQ(languages[1].modifier.c_str(), "euro"); + + EXPECT_STREQ(languages[2].language.c_str(), "en"); + EXPECT_STREQ(languages[2].territory.c_str(), ""); + EXPECT_STREQ(languages[2].codeset.c_str(), "ISO-8859-1"); + EXPECT_STREQ(languages[2].modifier.c_str(), "euro"); + + EXPECT_STREQ(languages[3].language.c_str(), "en"); + EXPECT_STREQ(languages[3].territory.c_str(), ""); + EXPECT_STREQ(languages[3].codeset.c_str(), ""); + EXPECT_STREQ(languages[3].modifier.c_str(), "euro"); + + EXPECT_STREQ(languages[4].language.c_str(), "en"); + EXPECT_STREQ(languages[4].territory.c_str(), "GB"); + EXPECT_STREQ(languages[4].codeset.c_str(), "ISO-8859-1"); + EXPECT_STREQ(languages[4].modifier.c_str(), ""); + + EXPECT_STREQ(languages[5].language.c_str(), "en"); + EXPECT_STREQ(languages[5].territory.c_str(), "GB"); + EXPECT_STREQ(languages[5].codeset.c_str(), ""); + EXPECT_STREQ(languages[5].modifier.c_str(), ""); + + EXPECT_STREQ(languages[6].language.c_str(), "en"); + EXPECT_STREQ(languages[6].territory.c_str(), ""); + EXPECT_STREQ(languages[6].codeset.c_str(), "ISO-8859-1"); + EXPECT_STREQ(languages[6].modifier.c_str(), ""); + + EXPECT_STREQ(languages[7].language.c_str(), "en"); + EXPECT_STREQ(languages[7].territory.c_str(), ""); + EXPECT_STREQ(languages[7].codeset.c_str(), ""); + EXPECT_STREQ(languages[7].modifier.c_str(), ""); + + EXPECT_STREQ(languages[8].language.c_str(), "en"); + EXPECT_STREQ(languages[8].territory.c_str(), "US"); + EXPECT_STREQ(languages[8].codeset.c_str(), ""); + EXPECT_STREQ(languages[8].modifier.c_str(), ""); + + EXPECT_STREQ(languages[9].language.c_str(), "en"); + EXPECT_STREQ(languages[9].territory.c_str(), ""); + EXPECT_STREQ(languages[9].codeset.c_str(), ""); + EXPECT_STREQ(languages[9].modifier.c_str(), ""); + + EXPECT_STREQ(languages[10].language.c_str(), "sv"); + EXPECT_STREQ(languages[10].territory.c_str(), ""); + EXPECT_STREQ(languages[10].codeset.c_str(), ""); + EXPECT_STREQ(languages[10].modifier.c_str(), ""); + + EXPECT_STREQ(languages[11].language.c_str(), "zh"); + EXPECT_STREQ(languages[11].territory.c_str(), "CN"); + EXPECT_STREQ(languages[11].codeset.c_str(), "UTF-8"); + EXPECT_STREQ(languages[11].modifier.c_str(), ""); + + EXPECT_STREQ(languages[12].language.c_str(), "zh"); + EXPECT_STREQ(languages[12].territory.c_str(), "CN"); + EXPECT_STREQ(languages[12].codeset.c_str(), ""); + EXPECT_STREQ(languages[12].modifier.c_str(), ""); + + EXPECT_STREQ(languages[13].language.c_str(), "zh"); + EXPECT_STREQ(languages[13].territory.c_str(), ""); + EXPECT_STREQ(languages[13].codeset.c_str(), "UTF-8"); + EXPECT_STREQ(languages[13].modifier.c_str(), ""); + + EXPECT_STREQ(languages[14].language.c_str(), "zh"); + EXPECT_STREQ(languages[14].territory.c_str(), ""); + EXPECT_STREQ(languages[14].codeset.c_str(), ""); + EXPECT_STREQ(languages[14].modifier.c_str(), ""); +} + +TEST(FlutterGlfwSystemUtilsTest, GetPreferredLanuageInfoWeird) { + const char* locale_string = "tt_RU@iqtelif.UTF-8"; + std::vector languages = + SetAndRestoreLanguageAroundGettingLanguageInfo(locale_string, nullptr, + nullptr, nullptr); + + EXPECT_EQ(languages.size(), 4UL); + + EXPECT_STREQ(languages[0].language.c_str(), "tt"); + EXPECT_STREQ(languages[0].territory.c_str(), "RU"); + EXPECT_STREQ(languages[0].codeset.c_str(), ""); + EXPECT_STREQ(languages[0].modifier.c_str(), "iqtelif.UTF-8"); + + EXPECT_STREQ(languages[1].language.c_str(), "tt"); + EXPECT_STREQ(languages[1].territory.c_str(), ""); + EXPECT_STREQ(languages[1].codeset.c_str(), ""); + EXPECT_STREQ(languages[1].modifier.c_str(), "iqtelif.UTF-8"); + + EXPECT_STREQ(languages[2].language.c_str(), "tt"); + EXPECT_STREQ(languages[2].territory.c_str(), "RU"); + EXPECT_STREQ(languages[2].codeset.c_str(), ""); + EXPECT_STREQ(languages[2].modifier.c_str(), ""); + + EXPECT_STREQ(languages[3].language.c_str(), "tt"); + EXPECT_STREQ(languages[3].territory.c_str(), ""); + EXPECT_STREQ(languages[3].codeset.c_str(), ""); + EXPECT_STREQ(languages[3].modifier.c_str(), ""); +} + +TEST(FlutterGlfwSystemUtilsTest, GetPreferredLanuageInfoEmpty) { + const char* locale_string = ""; + std::vector languages = + SetAndRestoreLanguageAroundGettingLanguageInfo( + locale_string, locale_string, locale_string, locale_string); + + EXPECT_EQ(languages.size(), 1UL); + + EXPECT_STREQ(languages[0].language.c_str(), "C"); + EXPECT_TRUE(languages[0].territory.empty()); + EXPECT_TRUE(languages[0].codeset.empty()); + EXPECT_TRUE(languages[0].modifier.empty()); +} + +TEST(FlutterGlfwSystemUtilsTest, GetPreferredLanuageInfoEnvVariableOrdering1) { + const char* language = "de"; + const char* lc_all = "en"; + const char* lc_messages = "zh"; + const char* lang = "tt"; + + std::vector languages = + SetAndRestoreLanguageAroundGettingLanguageInfo(language, lc_all, + lc_messages, lang); + + EXPECT_EQ(languages.size(), 1UL); + EXPECT_STREQ(languages[0].language.c_str(), language); +} + +TEST(FlutterGlfwSystemUtilsTest, GetPreferredLanuageInfoEnvVariableOrdering2) { + const char* lc_all = "en"; + const char* lc_messages = "zh"; + const char* lang = "tt"; + + std::vector languages = + SetAndRestoreLanguageAroundGettingLanguageInfo(nullptr, lc_all, + lc_messages, lang); + + EXPECT_EQ(languages.size(), 1UL); + EXPECT_STREQ(languages[0].language.c_str(), lc_all); +} + +TEST(FlutterGlfwSystemUtilsTest, GetPreferredLanuageInfoEnvVariableOrdering3) { + const char* lc_messages = "zh"; + const char* lang = "tt"; + + std::vector languages = + SetAndRestoreLanguageAroundGettingLanguageInfo(nullptr, nullptr, + lc_messages, lang); + + EXPECT_EQ(languages.size(), 1UL); + EXPECT_STREQ(languages[0].language.c_str(), lc_messages); +} + +TEST(FlutterGlfwSystemUtilsTest, GetPreferredLanuageInfoEnvVariableOrdering4) { + const char* lang = "tt"; + + std::vector languages = + SetAndRestoreLanguageAroundGettingLanguageInfo(nullptr, nullptr, nullptr, + lang); + + EXPECT_EQ(languages.size(), 1UL); + EXPECT_STREQ(languages[0].language.c_str(), lang); +} + +TEST(FlutterGlfwSystemUtilsTest, GetPreferredLanuageInfoEnvVariableOrdering5) { + std::vector languages = + SetAndRestoreLanguageAroundGettingLanguageInfo(nullptr, nullptr, nullptr, + nullptr); + + EXPECT_EQ(languages.size(), 1UL); + EXPECT_STREQ(languages[0].language.c_str(), "C"); +} + +TEST(FlutterGlfwSystemUtilsTest, ConvertToFlutterLocaleEmpty) { + std::vector languages; + + std::vector locales = ConvertToFlutterLocale(languages); + + EXPECT_TRUE(locales.empty()); +} + +TEST(FlutterGlfwSystemUtilsTest, ConvertToFlutterLocaleNonEmpty) { + std::vector languages; + languages.push_back(LanguageInfo{"en", "US", "", ""}); + languages.push_back(LanguageInfo{"tt", "RU", "", "iqtelif.UTF-8"}); + languages.push_back(LanguageInfo{"sv", "", "", ""}); + languages.push_back(LanguageInfo{"de", "DE", "UTF-8", "euro"}); + languages.push_back(LanguageInfo{"zh", "CN", "UTF-8", ""}); + + std::vector locales = ConvertToFlutterLocale(languages); + + EXPECT_EQ(locales.size(), 5UL); + + EXPECT_EQ(locales[0].struct_size, sizeof(FlutterLocale)); + EXPECT_STREQ(locales[0].language_code, "en"); + EXPECT_STREQ(locales[0].country_code, "US"); + EXPECT_EQ(locales[0].script_code, nullptr); + EXPECT_EQ(locales[0].variant_code, nullptr); + + EXPECT_STREQ(locales[1].language_code, "tt"); + EXPECT_STREQ(locales[1].country_code, "RU"); + EXPECT_EQ(locales[1].script_code, nullptr); + EXPECT_STREQ(locales[1].variant_code, "iqtelif.UTF-8"); + + EXPECT_STREQ(locales[2].language_code, "sv"); + EXPECT_EQ(locales[2].country_code, nullptr); + EXPECT_EQ(locales[2].script_code, nullptr); + EXPECT_EQ(locales[2].variant_code, nullptr); + + EXPECT_STREQ(locales[3].language_code, "de"); + EXPECT_STREQ(locales[3].country_code, "DE"); + EXPECT_STREQ(locales[3].script_code, "UTF-8"); + EXPECT_STREQ(locales[3].variant_code, "euro"); + + EXPECT_STREQ(locales[4].language_code, "zh"); + EXPECT_STREQ(locales[4].country_code, "CN"); + EXPECT_STREQ(locales[4].script_code, "UTF-8"); + EXPECT_EQ(locales[4].variant_code, nullptr); +} + +} // namespace +} // namespace flutter From a4fc6db263930689c1fe9ef01b02a6fcc0a1cf92 Mon Sep 17 00:00:00 2001 From: Andy Weiss Date: Mon, 23 Nov 2020 13:55:29 -0800 Subject: [PATCH 6/9] Change public to only be the new header which is exposed for tests --- shell/platform/glfw/BUILD.gn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/glfw/BUILD.gn b/shell/platform/glfw/BUILD.gn index 28cbefd904c18..3065d01677473 100644 --- a/shell/platform/glfw/BUILD.gn +++ b/shell/platform/glfw/BUILD.gn @@ -30,7 +30,7 @@ source_set("flutter_glfw_headers") { } source_set("flutter_glfw") { - public = _public_headers + ["system_utils.h"] + public = ["system_utils.h"] sources = [ "event_loop.cc", From 656bfa61c4e0d45f07007ece5a72937361ce98c4 Mon Sep 17 00:00:00 2001 From: Andy Weiss Date: Mon, 23 Nov 2020 14:01:42 -0800 Subject: [PATCH 7/9] gn format --- shell/platform/glfw/BUILD.gn | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/platform/glfw/BUILD.gn b/shell/platform/glfw/BUILD.gn index 3065d01677473..21ea61c7316a7 100644 --- a/shell/platform/glfw/BUILD.gn +++ b/shell/platform/glfw/BUILD.gn @@ -30,7 +30,7 @@ source_set("flutter_glfw_headers") { } source_set("flutter_glfw") { - public = ["system_utils.h"] + public = [ "system_utils.h" ] sources = [ "event_loop.cc", @@ -82,10 +82,10 @@ test_fixtures("flutter_glfw_fixtures") { executable("flutter_glfw_unittests") { testonly = true - sources = ["system_utils_test.cc"] + sources = [ "system_utils_test.cc" ] deps = [ - ":flutter_glfw_fixtures", ":flutter_glfw", + ":flutter_glfw_fixtures", "//flutter/shell/platform/embedder:embedder_headers", "//flutter/testing", ] From 2d0a87ff93d6689cc7c483d073a76df93b998f46 Mon Sep 17 00:00:00 2001 From: Andy Weiss Date: Mon, 23 Nov 2020 14:05:47 -0800 Subject: [PATCH 8/9] use a regular enum instead of enum class to avoid noise static_cast --- shell/platform/glfw/system_utils.cc | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/shell/platform/glfw/system_utils.cc b/shell/platform/glfw/system_utils.cc index 9bbaddc43b8ac..ed4ca2da66384 100644 --- a/shell/platform/glfw/system_utils.cc +++ b/shell/platform/glfw/system_utils.cc @@ -34,23 +34,23 @@ const char* GetLocaleStringFromEnvironment() { } // The least specific to most specific components of a locale. -enum class Component { - Codeset = 1 << 0, - Territory = 1 << 1, - Modifier = 1 << 2, +enum Component { + kCodeset = 1 << 0, + kTerritory = 1 << 1, + kModifier = 1 << 2, }; // Construct a mask indicating which of the components in |info| are set. int ComputeVariantMask(const LanguageInfo& info) { int mask = 0; if (!info.territory.empty()) { - mask |= static_cast(Component::Territory); + mask |= kTerritory; } if (!info.codeset.empty()) { - mask |= static_cast(Component::Codeset); + mask |= kCodeset; } if (!info.modifier.empty()) { - mask |= static_cast(Component::Modifier); + mask |= kModifier; } return mask; } @@ -66,13 +66,13 @@ void AppendLocaleVariants(std::vector& languages, LanguageInfo variant; variant.language = info.language; - if (i & static_cast(Component::Territory)) { + if (i & kTerritory) { variant.territory = info.territory; } - if (i & static_cast(Component::Codeset)) { + if (i & kCodeset) { variant.codeset = info.codeset; } - if (i & static_cast(Component::Modifier)) { + if (i & kModifier) { variant.modifier = info.modifier; } languages.push_back(variant); From d38ec352b47cac2bb403a74d15f0caeb9dc92ab8 Mon Sep 17 00:00:00 2001 From: Andy Weiss Date: Tue, 24 Nov 2020 13:28:19 -0800 Subject: [PATCH 9/9] Use maps to cleanup test environment variable setup/cleanup code --- shell/platform/glfw/system_utils_test.cc | 81 ++++++++---------------- 1 file changed, 26 insertions(+), 55 deletions(-) diff --git a/shell/platform/glfw/system_utils_test.cc b/shell/platform/glfw/system_utils_test.cc index 22c567e5933aa..7f0ddd8ff2006 100644 --- a/shell/platform/glfw/system_utils_test.cc +++ b/shell/platform/glfw/system_utils_test.cc @@ -19,65 +19,36 @@ std::vector SetAndRestoreLanguageAroundGettingLanguageInfo( const char* lc_all, const char* lc_messages, const char* lang) { - std::string old_language; - const char* language_env = getenv("LANGUAGE"); - bool language_was_set = language_env != nullptr; - if (language_was_set) { - old_language = std::string(language_env); - } - std::string old_lc_all; - const char* lc_all_env = getenv("LC_ALL"); - bool lc_all_was_set = lc_all_env != nullptr; - if (lc_all_was_set) { - old_lc_all = std::string(lc_all_env); - } - std::string old_lc_messages; - const char* lc_messages_env = getenv("LC_MESSAGES"); - bool lc_messages_was_set = lc_messages_env != nullptr; - if (lc_messages_was_set) { - old_lc_messages = std::string(lc_messages_env); - } - std::string old_lang; - const char* lang_env = getenv("LANG"); - bool lang_was_set = lang_env != nullptr; - if (lang_was_set) { - old_lang = std::string(lang_env); - } - - if (language != nullptr) { - setenv("LANGUAGE", language, 1); - } else if (language_was_set) { - unsetenv("LANGUAGE"); - } - if (lc_all != nullptr) { - setenv("LC_ALL", lc_all, 1); - } else if (lc_all_was_set) { - unsetenv("LC_ALL"); - } - if (lc_messages != nullptr) { - setenv("LC_MESSAGES", lc_messages, 1); - } else if (lc_messages_was_set) { - unsetenv("LC_MESSAGES"); - } - if (lang != nullptr) { - setenv("LANG", lang, 1); - } else if (lang_was_set) { - unsetenv("LANG"); + std::vector env_vars{ + "LANGUAGE", + "LC_ALL", + "LC_MESSAGES", + "LANG", + }; + std::map new_values{ + {env_vars[0], language}, + {env_vars[1], lc_all}, + {env_vars[2], lc_messages}, + {env_vars[3], lang}, + }; + std::map prior_values; + for (auto var : env_vars) { + const char* value = getenv(var); + if (value != nullptr) { + prior_values.emplace(var, value); + } + const char* new_value = new_values.at(var); + if (new_value != nullptr) { + setenv(var, new_value, 1); + } else { + unsetenv(var); + } } std::vector languages = GetPreferredLanguageInfo(); - if (language_was_set) { - setenv("LANGUAGE", old_language.c_str(), 1); - } - if (lc_all_was_set) { - setenv("LC_ALL", old_lc_all.c_str(), 1); - } - if (lc_messages_was_set) { - setenv("LC_MESSAGES", old_lc_messages.c_str(), 1); - } - if (lang_was_set) { - setenv("LANG", old_lang.c_str(), 1); + for (auto [var, value] : prior_values) { + setenv(var, value, 1); } return languages;