From 2cd66cb145d2635f5c565bfcba1249f43b40cbf5 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 12 Aug 2020 12:50:00 -0700 Subject: [PATCH 1/3] Send locale information in the Windows embedding Queries the system list of user-preferred languages, and sends it to the engine just after starting it up. Windows portion of https://github.com/flutter/flutter/issues/45152 --- ci/licenses_golden/licenses_flutter | 3 + shell/platform/windows/BUILD.gn | 3 + .../windows/flutter_windows_engine.cc | 40 ++++++++ .../platform/windows/flutter_windows_engine.h | 6 ++ shell/platform/windows/system_utils.cc | 94 +++++++++++++++++++ shell/platform/windows/system_utils.h | 36 +++++++ .../windows/system_utils_unittests.cc | 75 +++++++++++++++ 7 files changed, 257 insertions(+) create mode 100644 shell/platform/windows/system_utils.cc create mode 100644 shell/platform/windows/system_utils.h create mode 100644 shell/platform/windows/system_utils_unittests.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 5bb1a51273993..f86c4e659babf 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1355,6 +1355,9 @@ FILE: ../../../flutter/shell/platform/windows/public/flutter_windows.h FILE: ../../../flutter/shell/platform/windows/string_conversion.cc FILE: ../../../flutter/shell/platform/windows/string_conversion.h FILE: ../../../flutter/shell/platform/windows/string_conversion_unittests.cc +FILE: ../../../flutter/shell/platform/windows/system_utils.cc +FILE: ../../../flutter/shell/platform/windows/system_utils.h +FILE: ../../../flutter/shell/platform/windows/system_utils_unittests.cc FILE: ../../../flutter/shell/platform/windows/text_input_plugin.cc FILE: ../../../flutter/shell/platform/windows/text_input_plugin.h FILE: ../../../flutter/shell/platform/windows/win32_dpi_utils.cc diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 87bcde9ab7d4c..8b5bd3e8fe0ae 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -54,6 +54,8 @@ source_set("flutter_windows_source") { "keyboard_hook_handler.h", "string_conversion.cc", "string_conversion.h", + "system_utils.cc", + "system_utils.h", "text_input_plugin.cc", "text_input_plugin.h", "win32_dpi_utils.cc", @@ -118,6 +120,7 @@ executable("flutter_windows_unittests") { sources = [ "string_conversion_unittests.cc", + "system_utils_unittests.cc", "testing/win32_flutter_window_test.cc", "testing/win32_flutter_window_test.h", "testing/win32_window_test.cc", diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index 957c9432d8cb2..772a80d861ebe 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -6,9 +6,12 @@ #include #include +#include #include "flutter/shell/platform/common/cpp/path_utils.h" #include "flutter/shell/platform/windows/flutter_windows_view.h" +#include "flutter/shell/platform/windows/string_conversion.h" +#include "flutter/shell/platform/windows/system_utils.h" namespace flutter { @@ -69,6 +72,21 @@ static FlutterDesktopMessage ConvertToDesktopMessage( return message; } +// Converts a LanguageInfo struct to a FlutterLocale struct. |info| must outlive +// the returned value, since the returned FlutterLocale has pointers into it. +FlutterLocale CovertToFlutterLocale(const LanguageInfo& info) { + FlutterLocale locale = {}; + locale.struct_size = sizeof(FlutterLocale); + locale.language_code = info.language.c_str(); + if (!info.region.empty()) { + locale.country_code = info.region.c_str(); + } + if (!info.script.empty()) { + locale.script_code = info.script.c_str(); + } + return locale; +} + } // namespace FlutterWindowsEngine::FlutterWindowsEngine(const FlutterProjectBundle& project) @@ -173,6 +191,8 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) { } plugin_registrar_->messenger->engine = engine_; + SendSystemSettings(); + return true; } @@ -213,4 +233,24 @@ void FlutterWindowsEngine::HandlePlatformMessage( message, [this] {}, [this] {}); } +void FlutterWindowsEngine::SendSystemSettings() { + std::vector languages = GetPreferredLanguageInfo(); + std::vector flutter_locales; + flutter_locales.reserve(languages.size()); + for (const auto& info : languages) { + flutter_locales.push_back(CovertToFlutterLocale(info)); + } + // 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; }); + FlutterEngineUpdateLocales(engine_, flutter_locale_list.data(), + flutter_locale_list.size()); + + // TODO: Send 'flutter/settings' channel settings here as well. +} + } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h index 61784a76f8083..2dc72098cf4cd 100644 --- a/shell/platform/windows/flutter_windows_engine.h +++ b/shell/platform/windows/flutter_windows_engine.h @@ -67,6 +67,12 @@ class FlutterWindowsEngine { void HandlePlatformMessage(const FlutterPlatformMessage*); private: + // Sends system settings (e.g., locale) to the engine. + // + // Should be called just after the engine is run, and after any relevant + // system changes. + void SendSystemSettings(); + // The handle to the embedder.h engine instance. FLUTTER_API_SYMBOL(FlutterEngine) engine_ = nullptr; diff --git a/shell/platform/windows/system_utils.cc b/shell/platform/windows/system_utils.cc new file mode 100644 index 0000000000000..a22f225ecd4d0 --- /dev/null +++ b/shell/platform/windows/system_utils.cc @@ -0,0 +1,94 @@ +// 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/windows/system_utils.h" + +#include + +#include + +#include "flutter/shell/platform/windows/string_conversion.h" + +namespace flutter { + +std::vector GetPreferredLanguageInfo() { + std::vector languages = GetPreferredLanguages(); + std::vector language_info; + language_info.reserve(languages.size()); + + for (auto language : languages) { + language_info.push_back(ParseLanguageName(language)); + } + return language_info; +} + +std::vector GetPreferredLanguages() { + std::vector languages; + DWORD flags = MUI_LANGUAGE_NAME | MUI_UI_FALLBACK; + + // Get buffer length. + ULONG count = 0; + ULONG buffer_size = 0; + if (!::GetThreadPreferredUILanguages(flags, &count, nullptr, &buffer_size)) { + return languages; + } + + // Get the list of null-separated languages. + std::wstring buffer; + buffer.reserve(buffer_size); + if (!::GetThreadPreferredUILanguages(flags, &count, buffer.data(), + &buffer_size)) { + return languages; + } + + // Extract the individual languages from the buffer. + size_t start = 0; + while (true) { + // The buffer is terminated by an empty string (i.e., a double null). + if (buffer[start] == L'\0') { + break; + } + // Read the next null-terminated language. + std::wstring language(buffer.c_str() + start); + if (language.size() == 0) { + break; + } + languages.push_back(language); + // Skip past that language and its terminating null in the buffer. + start += language.size() + 1; + } + return languages; +} + +LanguageInfo ParseLanguageName(std::wstring language_name) { + LanguageInfo info; + + // Split by '-', discarding any suplemental language info (-x-foo). + std::vector components; + std::istringstream stream(Utf8FromUtf16(language_name)); + std::string component; + while (getline(stream, component, '-')) { + if (component == "x") { + break; + } + components.push_back(component); + } + + // Determine which components are which. + info.language = components[0]; + if (components.size() == 3) { + info.script = components[1]; + info.region = components[2]; + } else if (components.size() == 2) { + // A script code will always be four characters long. + if (components[1].size() == 4) { + info.script = components[1]; + } else { + info.region = components[1]; + } + } + return info; +} + +} // namespace flutter diff --git a/shell/platform/windows/system_utils.h b/shell/platform/windows/system_utils.h new file mode 100644 index 0000000000000..2585d4e8d5241 --- /dev/null +++ b/shell/platform/windows/system_utils.h @@ -0,0 +1,36 @@ +// 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. + +// This file contains utilities for system-level information/settings. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_SYSTEM_UTILS_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_SYSTEM_UTILS_H_ + +#include +#include + +namespace flutter { + +// Components of a system language/locale. +struct LanguageInfo { + std::string language; + std::string region; + std::string script; +}; + +// Returns the list of user-preferred languages, in preference order, +// parsed into LanguageInfo structures. +std::vector GetPreferredLanguageInfo(); + +// Returns the list of user-preferred languages, in preference order. +// The language names are as described at: +// https://docs.microsoft.com/en-us/windows/win32/intl/language-names +std::vector GetPreferredLanguages(); + +// Parses a Windows language name into its components. +LanguageInfo ParseLanguageName(std::wstring language_name); + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_SYSTEM_UTILS_H_ diff --git a/shell/platform/windows/system_utils_unittests.cc b/shell/platform/windows/system_utils_unittests.cc new file mode 100644 index 0000000000000..d784d5027645f --- /dev/null +++ b/shell/platform/windows/system_utils_unittests.cc @@ -0,0 +1,75 @@ +// 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 + +#include "flutter/shell/platform/windows/system_utils.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +TEST(SystemUtils, GetPreferredLanguageInfo) { + std::vector languages = GetPreferredLanguageInfo(); + // There should be at least one language. + ASSERT_GE(languages.size(), 1); + // The info should have a valid languge. + EXPECT_GE(languages[0].language.size(), 2); +} + +TEST(SystemUtils, GetPreferredLanguages) { + std::vector languages = GetPreferredLanguages(); + // There should be at least one language. + ASSERT_GE(languages.size(), 1); + // The language should be non-empty. + EXPECT_FALSE(languages[0].empty()); + // There should not be a trailing null from the parsing step. + EXPECT_EQ(languages[0].size(), wcslen(languages[0].c_str())); +} + +TEST(SystemUtils, ParseLanguageNameGeneric) { + LanguageInfo info = ParseLanguageName(L"en"); + EXPECT_EQ(info.language, "en"); + EXPECT_TRUE(info.region.empty()); + EXPECT_TRUE(info.script.empty()); +} + +TEST(SystemUtils, ParseLanguageNameWithRegion) { + LanguageInfo info = ParseLanguageName(L"hu-HU"); + EXPECT_EQ(info.language, "hu"); + EXPECT_EQ(info.region, "HU"); + EXPECT_TRUE(info.script.empty()); +} + +TEST(SystemUtils, ParseLanguageNameWithScript) { + LanguageInfo info = ParseLanguageName(L"us-Latn"); + EXPECT_EQ(info.language, "us"); + EXPECT_TRUE(info.region.empty()); + EXPECT_EQ(info.script, "Latn"); +} + +TEST(SystemUtils, ParseLanguageNameWithRegionAndScript) { + LanguageInfo info = ParseLanguageName(L"uz-Latn-UZ"); + EXPECT_EQ(info.language, "uz"); + EXPECT_EQ(info.region, "UZ"); + EXPECT_EQ(info.script, "Latn"); +} + +TEST(SystemUtils, ParseLanguageNameWithSuplementalLanguage) { + LanguageInfo info = ParseLanguageName(L"en-US-x-fabricam"); + EXPECT_EQ(info.language, "en"); + EXPECT_EQ(info.region, "US"); + EXPECT_TRUE(info.script.empty()); +} + +// Ensure that ISO 639-2/T codes are handled. +TEST(SystemUtils, ParseLanguageNameWithThreeCharacterLanguage) { + LanguageInfo info = ParseLanguageName(L"ale-ZZ"); + EXPECT_EQ(info.language, "ale"); + EXPECT_EQ(info.region, "ZZ"); + EXPECT_TRUE(info.script.empty()); +} + +} // namespace testing +} // namespace flutter From 15b20864202159f0d1e6ebe6295f1abc9fbc4c58 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 13 Aug 2020 10:58:57 -0700 Subject: [PATCH 2/3] Fix buffer creation --- shell/platform/windows/system_utils.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shell/platform/windows/system_utils.cc b/shell/platform/windows/system_utils.cc index a22f225ecd4d0..a198523bf536b 100644 --- a/shell/platform/windows/system_utils.cc +++ b/shell/platform/windows/system_utils.cc @@ -35,8 +35,7 @@ std::vector GetPreferredLanguages() { } // Get the list of null-separated languages. - std::wstring buffer; - buffer.reserve(buffer_size); + std::wstring buffer(buffer_size, '\0'); if (!::GetThreadPreferredUILanguages(flags, &count, buffer.data(), &buffer_size)) { return languages; From 7e1782742254eee5587e858e993b61b527846c83 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Sun, 16 Aug 2020 14:37:44 -0700 Subject: [PATCH 3/3] Rename implementation to _win32 --- ci/licenses_golden/licenses_flutter | 2 +- shell/platform/windows/BUILD.gn | 2 +- .../platform/windows/{system_utils.cc => system_utils_win32.cc} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename shell/platform/windows/{system_utils.cc => system_utils_win32.cc} (100%) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index f86c4e659babf..743940e8bdcae 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1355,9 +1355,9 @@ FILE: ../../../flutter/shell/platform/windows/public/flutter_windows.h FILE: ../../../flutter/shell/platform/windows/string_conversion.cc FILE: ../../../flutter/shell/platform/windows/string_conversion.h FILE: ../../../flutter/shell/platform/windows/string_conversion_unittests.cc -FILE: ../../../flutter/shell/platform/windows/system_utils.cc FILE: ../../../flutter/shell/platform/windows/system_utils.h FILE: ../../../flutter/shell/platform/windows/system_utils_unittests.cc +FILE: ../../../flutter/shell/platform/windows/system_utils_win32.cc FILE: ../../../flutter/shell/platform/windows/text_input_plugin.cc FILE: ../../../flutter/shell/platform/windows/text_input_plugin.h FILE: ../../../flutter/shell/platform/windows/win32_dpi_utils.cc diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 8b5bd3e8fe0ae..ab8e703f73bcc 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -54,8 +54,8 @@ source_set("flutter_windows_source") { "keyboard_hook_handler.h", "string_conversion.cc", "string_conversion.h", - "system_utils.cc", "system_utils.h", + "system_utils_win32.cc", "text_input_plugin.cc", "text_input_plugin.h", "win32_dpi_utils.cc", diff --git a/shell/platform/windows/system_utils.cc b/shell/platform/windows/system_utils_win32.cc similarity index 100% rename from shell/platform/windows/system_utils.cc rename to shell/platform/windows/system_utils_win32.cc