[embedder][glfw] Add support for locales to glfw shell (flutter/engine#22657)

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 a 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.
This commit is contained in:
Andy Weiss 2020-11-24 14:23:12 -08:00 committed by GitHub
parent f611b85a5b
commit ebdd79df0d
8 changed files with 534 additions and 0 deletions

View File

@ -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) {

View File

@ -1298,6 +1298,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

View File

@ -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
@ -28,6 +30,8 @@ source_set("flutter_glfw_headers") {
}
source_set("flutter_glfw") {
public = [ "system_utils.h" ]
sources = [
"event_loop.cc",
"event_loop.h",
@ -41,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",
]
@ -71,6 +76,21 @@ source_set("flutter_glfw") {
}
}
test_fixtures("flutter_glfw_fixtures") {
fixtures = []
}
executable("flutter_glfw_unittests") {
testonly = true
sources = [ "system_utils_test.cc" ]
deps = [
":flutter_glfw",
":flutter_glfw_fixtures",
"//flutter/shell/platform/embedder:embedder_headers",
"//flutter/testing",
]
}
copy("publish_headers_glfw") {
sources = _public_headers
outputs = [ "$root_out_dir/{{source_file_part}}" ]

View File

@ -22,6 +22,7 @@
#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,
@ -690,6 +691,27 @@ static bool RunFlutterEngine(
return true;
}
// Passes locale information to the Flutter engine.
static void SetUpLocales(FlutterDesktopEngineState* state) {
std::vector<flutter::LanguageInfo> languages =
flutter::GetPreferredLanguageInfo();
std::vector<FlutterLocale> flutter_locales =
flutter::ConvertToFlutterLocale(languages);
// Convert the locale list to the locale pointer list that must be provided.
std::vector<const FlutterLocale*> 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, flutter_locale_list.data(),
flutter_locale_list.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 +735,8 @@ static void SetUpCommonEngineState(FlutterDesktopEngineState* state,
// System channel handler.
state->platform_handler = std::make_unique<flutter::PlatformHandler>(
state->internal_plugin_registrar->messenger(), window);
SetUpLocales(state);
}
bool FlutterDesktopInit() {

View File

@ -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 <cstdlib>
#include <sstream>
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 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 |= kTerritory;
}
if (!info.codeset.empty()) {
mask |= kCodeset;
}
if (!info.modifier.empty()) {
mask |= kModifier;
}
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<LanguageInfo>& 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 & kTerritory) {
variant.territory = info.territory;
}
if (i & kCodeset) {
variant.codeset = info.codeset;
}
if (i & kModifier) {
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<LanguageInfo> 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<LanguageInfo> languages;
std::string s;
while (getline(locales_stream, s, ':')) {
LanguageInfo info = ParseLocale(s);
AppendLocaleVariants(languages, info);
}
return languages;
}
std::vector<FlutterLocale> ConvertToFlutterLocale(
const std::vector<LanguageInfo>& languages) {
std::vector<FlutterLocale> 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

View File

@ -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 <string>
#include <vector>
#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<LanguageInfo> 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<FlutterLocale> ConvertToFlutterLocale(
const std::vector<LanguageInfo>& languages);
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_GLFW_SYSTEM_UTILS_H_

View File

@ -0,0 +1,292 @@
// 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 <cstdlib>
#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<LanguageInfo> SetAndRestoreLanguageAroundGettingLanguageInfo(
const char* language,
const char* lc_all,
const char* lc_messages,
const char* lang) {
std::vector<const char*> env_vars{
"LANGUAGE",
"LC_ALL",
"LC_MESSAGES",
"LANG",
};
std::map<const char*, const char*> new_values{
{env_vars[0], language},
{env_vars[1], lc_all},
{env_vars[2], lc_messages},
{env_vars[3], lang},
};
std::map<const char*, const char*> 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<LanguageInfo> languages = GetPreferredLanguageInfo();
for (auto [var, value] : prior_values) {
setenv(var, value, 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<LanguageInfo> 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<LanguageInfo> 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<LanguageInfo> 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<LanguageInfo> 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<LanguageInfo> 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<LanguageInfo> 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<LanguageInfo> languages =
SetAndRestoreLanguageAroundGettingLanguageInfo(nullptr, nullptr, nullptr,
lang);
EXPECT_EQ(languages.size(), 1UL);
EXPECT_STREQ(languages[0].language.c_str(), lang);
}
TEST(FlutterGlfwSystemUtilsTest, GetPreferredLanuageInfoEnvVariableOrdering5) {
std::vector<LanguageInfo> languages =
SetAndRestoreLanguageAroundGettingLanguageInfo(nullptr, nullptr, nullptr,
nullptr);
EXPECT_EQ(languages.size(), 1UL);
EXPECT_STREQ(languages[0].language.c_str(), "C");
}
TEST(FlutterGlfwSystemUtilsTest, ConvertToFlutterLocaleEmpty) {
std::vector<LanguageInfo> languages;
std::vector<FlutterLocale> locales = ConvertToFlutterLocale(languages);
EXPECT_TRUE(locales.empty());
}
TEST(FlutterGlfwSystemUtilsTest, ConvertToFlutterLocaleNonEmpty) {
std::vector<LanguageInfo> 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<FlutterLocale> 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

View File

@ -161,6 +161,7 @@ def RunCCTests(build_dir, filter):
if IsLinux():
RunEngineExecutable(build_dir, 'flutter_linux_unittests', filter, non_repeatable_shuffle_flags)
RunEngineExecutable(build_dir, 'flutter_glfw_unittests', filter, non_repeatable_shuffle_flags)
def RunEngineBenchmarks(build_dir, filter):