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:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
Reference in New Issue
Block a user