Use Windows Display Language (flutter/engine#43341)

Get the Windows Display Language for locale selection instead of the
preferred languages.

https://github.com/flutter/flutter/issues/129786

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide] and the [C++,
Objective-C, Java style guides].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making or feature I am
adding, or Hixie said the PR is test-exempt. See [testing the engine]
for instructions on writing and running engine tests.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [ ] I signed the [CLA].
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#overview
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[C++, Objective-C, Java style guides]:
https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
[testing the engine]:
https://github.com/flutter/flutter/wiki/Testing-the-engine
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat

---------

Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com>
This commit is contained in:
yaakovschectman
2023-07-06 07:49:50 -04:00
committed by GitHub
parent 550d013ccf
commit 017941c97d
12 changed files with 65 additions and 179 deletions

View File

@@ -3235,8 +3235,6 @@ ORIGIN: ../../../flutter/shell/platform/windows/windows_lifecycle_manager.cc + .
ORIGIN: ../../../flutter/shell/platform/windows/windows_lifecycle_manager.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/windows/windows_proc_table.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/windows/windows_proc_table.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/windows/windows_registry.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/windows/windows_registry.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/windows/windowsx_shim.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/profiling/sampling_profiler.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/profiling/sampling_profiler.h + ../../../flutter/LICENSE
@@ -5939,8 +5937,6 @@ FILE: ../../../flutter/shell/platform/windows/windows_lifecycle_manager.cc
FILE: ../../../flutter/shell/platform/windows/windows_lifecycle_manager.h
FILE: ../../../flutter/shell/platform/windows/windows_proc_table.cc
FILE: ../../../flutter/shell/platform/windows/windows_proc_table.h
FILE: ../../../flutter/shell/platform/windows/windows_registry.cc
FILE: ../../../flutter/shell/platform/windows/windows_registry.h
FILE: ../../../flutter/shell/platform/windows/windowsx_shim.h
FILE: ../../../flutter/shell/profiling/sampling_profiler.cc
FILE: ../../../flutter/shell/profiling/sampling_profiler.h

View File

@@ -107,8 +107,6 @@ source_set("flutter_windows_source") {
"windows_lifecycle_manager.h",
"windows_proc_table.cc",
"windows_proc_table.h",
"windows_registry.cc",
"windows_registry.h",
"windowsx_shim.h",
]

View File

@@ -156,12 +156,9 @@ FlutterLocale CovertToFlutterLocale(const LanguageInfo& info) {
} // namespace
FlutterWindowsEngine::FlutterWindowsEngine(
const FlutterProjectBundle& project,
std::unique_ptr<WindowsRegistry> registry)
FlutterWindowsEngine::FlutterWindowsEngine(const FlutterProjectBundle& project)
: project_(std::make_unique<FlutterProjectBundle>(project)),
aot_data_(nullptr, nullptr),
windows_registry_(std::move(registry)),
lifecycle_manager_(std::make_unique<WindowsLifecycleManager>(this)) {
embedder_api_.struct_size = sizeof(FlutterEngineProcTable);
FlutterEngineGetProcAddresses(&embedder_api_);
@@ -572,7 +569,7 @@ void FlutterWindowsEngine::SetLifecycleState(flutter::AppLifecycleState state) {
void FlutterWindowsEngine::SendSystemLocales() {
std::vector<LanguageInfo> languages =
GetPreferredLanguageInfo(*windows_registry_);
GetPreferredLanguageInfo(windows_proc_table_);
std::vector<FlutterLocale> flutter_locales;
flutter_locales.reserve(languages.size());
for (const auto& info : languages) {

View File

@@ -37,7 +37,7 @@
#include "flutter/shell/platform/windows/window_proc_delegate_manager.h"
#include "flutter/shell/platform/windows/window_state.h"
#include "flutter/shell/platform/windows/windows_lifecycle_manager.h"
#include "flutter/shell/platform/windows/windows_registry.h"
#include "flutter/shell/platform/windows/windows_proc_table.h"
#include "third_party/rapidjson/include/rapidjson/document.h"
namespace flutter {
@@ -77,13 +77,8 @@ static void WindowsPlatformThreadPrioritySetter(
// run in headless mode.
class FlutterWindowsEngine {
public:
// Creates a new Flutter engine with an injectible windows registry.
FlutterWindowsEngine(const FlutterProjectBundle& project,
std::unique_ptr<WindowsRegistry> windows_registry);
// Creates a new Flutter engine object configured to run |project|.
explicit FlutterWindowsEngine(const FlutterProjectBundle& project)
: FlutterWindowsEngine(project, std::make_unique<WindowsRegistry>()) {}
explicit FlutterWindowsEngine(const FlutterProjectBundle& project);
virtual ~FlutterWindowsEngine();
@@ -405,12 +400,11 @@ class FlutterWindowsEngine {
// The on frame drawn callback.
fml::closure next_frame_callback_;
// Wrapper providing Windows registry access.
std::unique_ptr<WindowsRegistry> windows_registry_;
// Handler for top level window messages.
std::unique_ptr<WindowsLifecycleManager> lifecycle_manager_;
WindowsProcTable windows_proc_table_;
FML_DISALLOW_COPY_AND_ASSIGN(FlutterWindowsEngine);
};

View File

@@ -13,8 +13,9 @@
namespace flutter {
std::vector<LanguageInfo> GetPreferredLanguageInfo(
const WindowsRegistry& registry) {
std::vector<std::wstring> languages = GetPreferredLanguages(registry);
const WindowsProcTable& windows_proc_table) {
std::vector<std::wstring> languages =
GetPreferredLanguages(windows_proc_table);
std::vector<LanguageInfo> language_info;
language_info.reserve(languages.size());
@@ -24,63 +25,29 @@ std::vector<LanguageInfo> GetPreferredLanguageInfo(
return language_info;
}
std::wstring GetPreferredLanguagesFromRegistry(const WindowsRegistry& registry,
ULONG buffer_size) {
std::wstring buffer(buffer_size, '\0');
if (registry.GetRegistryValue(HKEY_CURRENT_USER, kGetPreferredLanguageRegKey,
kGetPreferredLanguageRegValue,
RRF_RT_REG_MULTI_SZ, NULL, buffer.data(),
&buffer_size) != ERROR_SUCCESS) {
return std::wstring();
}
return buffer;
}
std::wstring GetPreferredLanguagesFromMUI() {
ULONG buffer_size;
std::wstring GetPreferredLanguagesFromMUI(
const WindowsProcTable& windows_proc_table) {
ULONG buffer_size = 0;
ULONG count = 0;
DWORD flags = MUI_LANGUAGE_NAME | MUI_UI_FALLBACK;
if (!GetThreadPreferredUILanguages(flags, &count, nullptr, &buffer_size)) {
if (!windows_proc_table.GetThreadPreferredUILanguages(flags, &count, nullptr,
&buffer_size)) {
return std::wstring();
}
std::wstring buffer(buffer_size, '\0');
if (!GetThreadPreferredUILanguages(flags, &count, buffer.data(),
&buffer_size)) {
if (!windows_proc_table.GetThreadPreferredUILanguages(
flags, &count, buffer.data(), &buffer_size)) {
return std::wstring();
}
return buffer;
}
std::vector<std::wstring> GetPreferredLanguages(
const WindowsRegistry& registry) {
const WindowsProcTable& windows_proc_table) {
std::vector<std::wstring> languages;
BOOL languages_from_registry = TRUE;
ULONG buffer_size = 0;
ULONG count = 0;
DWORD flags = MUI_LANGUAGE_NAME | MUI_UI_FALLBACK;
// Determine where languages are defined and get buffer length
if (registry.GetRegistryValue(HKEY_CURRENT_USER, kGetPreferredLanguageRegKey,
kGetPreferredLanguageRegValue,
RRF_RT_REG_MULTI_SZ, NULL, NULL,
&buffer_size) != ERROR_SUCCESS) {
languages_from_registry = FALSE;
}
// Multi-string must be at least 3-long if non-empty,
// as a multi-string is terminated with 2 nulls.
//
// See:
// https://learn.microsoft.com/windows/win32/sysinfo/registry-value-types
if (languages_from_registry && buffer_size < 3) {
languages_from_registry = FALSE;
}
// Initialize the buffer
std::wstring buffer =
languages_from_registry
? GetPreferredLanguagesFromRegistry(registry, buffer_size)
: GetPreferredLanguagesFromMUI();
std::wstring buffer = GetPreferredLanguagesFromMUI(windows_proc_table);
// Extract the individual languages from the buffer.
size_t start = 0;

View File

@@ -10,7 +10,7 @@
#include <string>
#include <vector>
#include "flutter/shell/platform/windows/windows_registry.h"
#include "flutter/shell/platform/windows/windows_proc_table.h"
namespace flutter {
@@ -29,20 +29,17 @@ struct LanguageInfo {
// Returns the list of user-preferred languages, in preference order,
// parsed into LanguageInfo structures.
std::vector<LanguageInfo> GetPreferredLanguageInfo(
const WindowsRegistry& registry);
// Retrieve the preferred languages from the registry.
std::wstring GetPreferredLanguagesFromRegistry(const WindowsRegistry& registry,
ULONG buffer_size);
const WindowsProcTable& windows_proc_table);
// Retrieve the preferred languages from the MUI API.
std::wstring GetPreferredLanguagesFromMUI();
std::wstring GetPreferredLanguagesFromMUI(
const WindowsProcTable& windows_proc_table);
// 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<std::wstring> GetPreferredLanguages(
const WindowsRegistry& registry);
const WindowsProcTable& windows_proc_table);
// Parses a Windows language name into its components.
LanguageInfo ParseLanguageName(std::wstring language_name);

View File

@@ -7,46 +7,16 @@
#include "flutter/fml/macros.h"
#include "flutter/shell/platform/windows/system_utils.h"
#include "flutter/shell/platform/windows/testing/mock_windows_proc_table.h"
#include "gtest/gtest.h"
namespace flutter {
namespace testing {
class MockWindowsRegistry : public WindowsRegistry {
public:
MockWindowsRegistry() = default;
virtual ~MockWindowsRegistry() = default;
virtual LSTATUS GetRegistryValue(HKEY hkey,
LPCWSTR key,
LPCWSTR value,
DWORD flags,
LPDWORD type,
PVOID data,
LPDWORD data_size) const {
using namespace std::string_literals;
static const std::wstring locales =
L"en-US\0zh-Hans-CN\0ja\0zh-Hant-TW\0he\0\0"s;
static DWORD locales_len = locales.size() * sizeof(wchar_t);
if (data != nullptr) {
if (*data_size < locales_len) {
return ERROR_MORE_DATA;
}
std::memcpy(data, locales.data(), locales_len);
*data_size = locales_len;
} else if (data_size != NULL) {
*data_size = locales_len;
}
return ERROR_SUCCESS;
}
private:
FML_DISALLOW_COPY_AND_ASSIGN(MockWindowsRegistry);
};
TEST(SystemUtils, GetPreferredLanguageInfo) {
WindowsRegistry registry;
std::vector<LanguageInfo> languages = GetPreferredLanguageInfo(registry);
WindowsProcTable proc_table;
std::vector<LanguageInfo> languages =
GetPreferredLanguageInfo(WindowsProcTable());
// There should be at least one language.
ASSERT_GE(languages.size(), 1);
// The info should have a valid languge.
@@ -54,24 +24,30 @@ TEST(SystemUtils, GetPreferredLanguageInfo) {
}
TEST(SystemUtils, GetPreferredLanguages) {
WindowsRegistry registry;
std::vector<std::wstring> languages = GetPreferredLanguages(registry);
MockWindowsProcTable proc_table;
ON_CALL(proc_table, GetThreadPreferredUILanguages)
.WillByDefault(
[](DWORD flags, PULONG count, PZZWSTR languages, PULONG size) {
// Languages string ends in a double-null.
static const wchar_t lang[] = L"en-US\0";
static const size_t lang_len = sizeof(lang) / sizeof(wchar_t);
static const int cnt = 1;
if (languages == nullptr) {
*size = lang_len;
*count = cnt;
} else if (*size >= lang_len) {
memcpy(languages, lang, lang_len * sizeof(wchar_t));
}
return TRUE;
});
std::vector<std::wstring> languages = GetPreferredLanguages(proc_table);
// 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 mock results
MockWindowsRegistry mock_registry;
languages = GetPreferredLanguages(mock_registry);
ASSERT_EQ(languages.size(), 5);
ASSERT_EQ(languages[0], std::wstring(L"en-US"));
ASSERT_EQ(languages[1], std::wstring(L"zh-Hans-CN"));
ASSERT_EQ(languages[2], std::wstring(L"ja"));
ASSERT_EQ(languages[3], std::wstring(L"zh-Hant-TW"));
ASSERT_EQ(languages[4], std::wstring(L"he"));
EXPECT_EQ(languages[0], L"en-US");
}
TEST(SystemUtils, ParseLanguageNameGeneric) {

View File

@@ -21,6 +21,9 @@ class MockWindowsProcTable : public WindowsProcTable {
MOCK_METHOD2(GetPointerType,
BOOL(UINT32 pointer_id, POINTER_INPUT_TYPE* pointer_type));
MOCK_CONST_METHOD4(GetThreadPreferredUILanguages,
LRESULT(DWORD, PULONG, PZZWSTR, PULONG));
private:
FML_DISALLOW_COPY_AND_ASSIGN(MockWindowsProcTable);
};

View File

@@ -25,4 +25,11 @@ BOOL WindowsProcTable::GetPointerType(UINT32 pointer_id,
return get_pointer_type_.value()(pointer_id, pointer_type);
}
LRESULT WindowsProcTable::GetThreadPreferredUILanguages(DWORD flags,
PULONG count,
PZZWSTR languages,
PULONG length) const {
return ::GetThreadPreferredUILanguages(flags, count, languages, length);
}
} // namespace flutter

View File

@@ -13,7 +13,7 @@
namespace flutter {
// Lookup table for Windows APIs that aren't available on all versions of
// Windows.
// Windows, or for mocking Windows API calls.
class WindowsProcTable {
public:
WindowsProcTable();
@@ -26,6 +26,15 @@ class WindowsProcTable {
virtual BOOL GetPointerType(UINT32 pointer_id,
POINTER_INPUT_TYPE* pointer_type);
// Get the preferred languages for the thread, and optionally the process,
// and system, in that order, depending on the flags.
// See
// https://learn.microsoft.com/windows/win32/api/winnls/nf-winnls-getthreadpreferreduilanguages
virtual LRESULT GetThreadPreferredUILanguages(DWORD flags,
PULONG count,
PZZWSTR languages,
PULONG length) const;
private:
using GetPointerType_ = BOOL __stdcall(UINT32 pointerId,
POINTER_INPUT_TYPE* pointerType);

View File

@@ -1,19 +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/windows/windows_registry.h"
namespace flutter {
LSTATUS WindowsRegistry::GetRegistryValue(HKEY hkey,
LPCWSTR key,
LPCWSTR value,
DWORD flags,
LPDWORD type,
PVOID data,
LPDWORD data_size) const {
return RegGetValue(hkey, key, value, flags, type, data, data_size);
}
} // namespace flutter

View File

@@ -1,39 +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_WINDOWS_WINDOWS_REGISTRY_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_REGISTRY_H_
#include <Windows.h>
#include "flutter/fml/macros.h"
namespace flutter {
/// A utility class to encapsulate interaction with the Windows registry.
/// By encapsulating this in a class, we can mock out this functionality
/// for unit testing.
class WindowsRegistry {
public:
WindowsRegistry() = default;
virtual ~WindowsRegistry() = default;
// Parameters and return values of this method match those of RegGetValue
// See:
// https://learn.microsoft.com/windows/win32/api/winreg/nf-winreg-reggetvaluew
virtual LSTATUS GetRegistryValue(HKEY hkey,
LPCWSTR key,
LPCWSTR value,
DWORD flags,
LPDWORD type,
PVOID data,
LPDWORD data_size) const;
private:
FML_DISALLOW_COPY_AND_ASSIGN(WindowsRegistry);
};
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_REGISTRY_H_