Get user's preferred languages from registry#36536
Get user's preferred languages from registry#36536yaakovschectman merged 26 commits intoflutter:mainfrom
Conversation
43b991b to
0bdfb5c
Compare
cbracken
left a comment
There was a problem hiding this comment.
We should make this code testable now that we're touching it.
If we haven't already, it might be worth writing a small Registry class that wraps the registry calls, so we can use a fake in the tests.
We keep hitting the issue of Win32 C API calls in tests. It might be worth biting the bullet and creating a Win32API struct with trampoline functions for these like we do with the embedder API in the engine. @loic-sharma may have thoughts on that.
| &buffer_size) != S_OK) { | ||
| return languages; | ||
| } | ||
| for (int i = 0; i < buffer_size; i++) { |
There was a problem hiding this comment.
engine/fml/platform/win/wstring_conversion.h
Lines 15 to 17 in 68f5308
There was a problem hiding this comment.
str_buffer contains a multi-string delimited by nulls, so I believe we would need a for loop here no matter what. We could call this function on each substring of the multi-string and increment the counter by its length instead of iterating over each character, but I do not see any benefit to this.
| } | ||
|
|
||
| // Get the list of null-separated languages. | ||
| // Mutli-string must be at least 3-long if non-empty, |
There was a problem hiding this comment.
| // Mutli-string must be at least 3-long if non-empty, | |
| // Multi-string must be at least 3-long if non-empty, |
| // Determine where languages are defined and get buffer length | ||
| if (RegGetValueA(HKEY_CURRENT_USER, | ||
| "Control panel\\International\\User Profile", "Languages", | ||
| RRF_RT_REG_MULTI_SZ, NULL, NULL, &buffer_size) != S_OK) { |
There was a problem hiding this comment.
S_OK is technically correct but ERROR_SUCCESS would be more idiomatic: https://learn.microsoft.com/windows/win32/api/winreg/nf-winreg-reggetvaluea#return-value
| // Mutli-string must be at least 3-long if non-empty, | ||
| // as a multi-string is terminated with 2 nulls. | ||
| // | ||
| // See: https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types |
There was a problem hiding this comment.
Microsoft docs are localized only if you remove the language code:
| // See: https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types | |
| // See: https://learn.microsoft.com/windows/win32/sysinfo/registry-value-types |
| if (!::GetThreadPreferredUILanguages(flags, &count, nullptr, &buffer_size)) { | ||
| return languages; | ||
| // Determine where languages are defined and get buffer length | ||
| if (RegGetValueA(HKEY_CURRENT_USER, |
There was a problem hiding this comment.
Is using RegGetValueA instead of RegGetValue intentional? If no, prefer RegGetValue. That might have the added benefit of removing the str_buffer to buffer conversion below.
That sounds great to me! What are y'alls' thoughts on using |
|
| LPDWORD sizeData) const { | ||
| static const wchar_t* locales = | ||
| L"en-US\0zh-Hans-CN\0ja\0zh-Hant-TW\0he\0\0"; | ||
| static DWORD locales_len = 35; |
There was a problem hiding this comment.
Is there any way to get the length of locales programmatically?
https://stackoverflow.com/questions/2853615/get-length-of-wchar-t-in-c
There was a problem hiding this comment.
The issue here is that it's not just a string, it's a multistring, which means it contains null delimiters in the middle that string length methods will treat as a terminator and return a bad value for. We could write our own method that counts until it reaches a double null, though I don't know how much use it would get outside of this test.
There was a problem hiding this comment.
Is the incoming data a string of locales delimited by \0?
There was a problem hiding this comment.
Yes. Each locale is delimited by \0 with a double \0 at the end.
There was a problem hiding this comment.
Lol, I wonder why they would do that 🤷♂️ . Anyway, the change LGTM now 👍 .
There was a problem hiding this comment.
That's how the Windows registry stores all multi strings, or at least how it represents them when you request the data.
There was a problem hiding this comment.
You should be able to do this as follows:
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 const DWORD locales_len = locales.size();There was a problem hiding this comment.
That said -- the documentation for the pcbData param to GetRegValue function states this value should be in bytes, so we should really return:
static const DWORD locales_len = locales.size() * sizeof(wchar_t);which in this case works out to 140.
Looks like you're already doing that multiplication below.
| return languages; | ||
| // Determine where languages are defined and get buffer length | ||
| if (registry.GetRegistryValue(HKEY_CURRENT_USER, | ||
| L"Control panel\\International\\User Profile", |
There was a problem hiding this comment.
Looks like these strings gets referenced twice
L"Control panel\International\User Profile",
L"Languages"
Should we make these constants? It might help improve readability 🤷♂️
| // The on frame drawn callback. | ||
| fml::closure next_frame_callback_; | ||
|
|
||
| // A handle to a registry for registry values |
There was a problem hiding this comment.
| // A handle to a registry for registry values | |
| // A handle to a registry for registry values. |
There was a problem hiding this comment.
Even briefer, and a bit more correct, since technically this is a pointer and not a handle (which, while being a type of void* has a very specific meaning in Windows):
| // A handle to a registry for registry values | |
| // Wrapper providing Windows registry access. |
|
|
||
| #include "flutter/shell/platform/windows/flutter_windows_engine.h" | ||
|
|
||
| #include <string> |
There was a problem hiding this comment.
Are the changes in this file necessary or can they be reverted?
| #include <string> | ||
| #include <vector> |
There was a problem hiding this comment.
Are these includes necessary?
| #include <string> | |
| #include <vector> |
| void UpdateAccessibilityFeatures(FlutterAccessibilityFeature flags); | ||
|
|
||
| // Allow setting the Windows Registry | ||
| void SetWindowsRegistry(const WindowsRegistry& windows_registry); |
There was a problem hiding this comment.
Is this used? If no, please removing it as it's dead code. If yes, should this be a std::unique_ptr that's moved by the caller to avoid a copy?
| PVOID data, | ||
| LPDWORD sizeData) const; | ||
|
|
||
| private: |
There was a problem hiding this comment.
| private: | |
| private: | |
| FML_DISALLOW_COPY_AND_ASSIGN(WindowsRegistry); |
|
Gold has detected about 52 new digest(s) on patchset 16. |
|
Gold has detected about 2 new digest(s) on patchset 16. |
| LPDWORD type, | ||
| PVOID data, | ||
| LPDWORD dataSize) const { | ||
| return RegGetValue(hkey, key, value, flags, type, data, dataSize); |
There was a problem hiding this comment.
| return RegGetValue(hkey, key, value, flags, type, data, dataSize); | |
| return RegGetValue(hkey, key, value, flags, type, data, data_size); |
| DWORD flags, | ||
| LPDWORD type, | ||
| PVOID data, | ||
| LPDWORD sizeData) const; |
There was a problem hiding this comment.
| LPDWORD sizeData) const; | |
| LPDWORD data_size) const; |
|
|
||
| class WindowsRegistry { | ||
| public: | ||
| explicit WindowsRegistry(); |
There was a problem hiding this comment.
explicit isn't needed here since there's no parameter to be eligible for implicit conversion (and because copy-initialization is already prevented by the disallow copy & assign macro, which already prevents something like WindowsRegistry r = {}).
| explicit WindowsRegistry(); | |
| WindowsRegistry() = default; |
| class WindowsRegistry { | ||
| public: | ||
| explicit WindowsRegistry(); | ||
| virtual ~WindowsRegistry(); |
There was a problem hiding this comment.
| virtual ~WindowsRegistry(); | |
| virtual ~WindowsRegistry() = default; |
|
|
||
| namespace flutter { | ||
|
|
||
| WindowsRegistry::WindowsRegistry() {} |
There was a problem hiding this comment.
You can delete this if you apply the default suggestion below. Sadly I'm not GitHub-suggestion-savvy enough to know how to delete two lines in a suggested change :/
Co-authored-by: Chris Bracken <[email protected]>
Co-authored-by: Chris Bracken <[email protected]>
| DWORD flags, | ||
| LPDWORD type, | ||
| PVOID data, | ||
| LPDWORD sizeData) const { |
There was a problem hiding this comment.
| LPDWORD sizeData) const { | |
| LPDWORD data_size) const { |
| L"en-US\0zh-Hans-CN\0ja\0zh-Hant-TW\0he\0\0"; | ||
| static DWORD locales_len = 35; | ||
| if (data != nullptr) { | ||
| if (*sizeData < locales_len) { |
There was a problem hiding this comment.
| if (*sizeData < locales_len) { | |
| if (*data_size < locales_len) { |
| if (*sizeData < locales_len) { | ||
| return ERROR_MORE_DATA; | ||
| } | ||
| memcpy(data, locales, locales_len * sizeof(wchar_t)); |
There was a problem hiding this comment.
Prefix as std::memcpy and make sure to #include <cstring>, where it's defined.
| memcpy(data, locales, locales_len * sizeof(wchar_t)); | ||
| *sizeData = locales_len * sizeof(wchar_t); | ||
| } else if (sizeData != NULL) { | ||
| *sizeData = locales_len * sizeof(wchar_t); |
There was a problem hiding this comment.
Same name fix here, but I have no idea how to do multiline suggestions in github :(
| : project_(std::make_unique<FlutterProjectBundle>(project)), | ||
| aot_data_(nullptr, nullptr) { | ||
| aot_data_(nullptr, nullptr), | ||
| windows_registry_(std::make_unique<WindowsRegistry>()) { |
There was a problem hiding this comment.
In unit tests, I think you'll want to inject the registry, so you'll want to add a std::unique_ptr<WindowsRegistry> registry param, then move the unique the the construction site rather than in the initialiser list.
There was a problem hiding this comment.
This was my rationale for including the SetWindowsRegistry method originally. Can parameters have default arguments that are not constant expressions, or would we need to include this new argument anywhere a FlutterWindowsEngine is constructed?
There was a problem hiding this comment.
You'd need to include this wherever it's constructed, though other than unit tests, I suspect that should be very rare, possibly just one place. An alternative would be to add a second constructor that takes the registry and use that one in unit tests where it matters.
Since semantically, this isn't something we ever expect to mutate on an existing engine, I think constructor injection is preferrable.
cbracken
left a comment
There was a problem hiding this comment.
Overall, looks good barring mostly style-guide-ish nits. Thanks so much for factoring this out. This is great!
|
CC @thkim1011 - this PR is probably of interest |

The function currently used in
GetPreferredLanguageson Windows does not necessarily return all the languages included in the list of preferred languages set in the user's settings. By attempting to get these values from the registry first, we can obtain all these languages in order. These are eventually parsed and used as thePlatformConfiguration's locales.flutter/flutter#112717
Pre-launch Checklist
writing and running engine tests.
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.