summaryrefslogtreecommitdiffstats
path: root/intl/locale/windows/OSPreferences_win.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'intl/locale/windows/OSPreferences_win.cpp')
-rw-r--r--intl/locale/windows/OSPreferences_win.cpp321
1 files changed, 321 insertions, 0 deletions
diff --git a/intl/locale/windows/OSPreferences_win.cpp b/intl/locale/windows/OSPreferences_win.cpp
new file mode 100644
index 0000000000..e1093f3a3a
--- /dev/null
+++ b/intl/locale/windows/OSPreferences_win.cpp
@@ -0,0 +1,321 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OSPreferences.h"
+#include "mozilla/intl/Locale.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsReadableUtils.h"
+
+#include <windows.h>
+
+#ifndef __MINGW32__ // WinRT headers not yet supported by MinGW
+# include <roapi.h>
+# include <wrl.h>
+# include <Windows.System.UserProfile.h>
+
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows::Foundation::Collections;
+using namespace ABI::Windows::System::UserProfile;
+#endif
+
+using namespace mozilla::intl;
+
+OSPreferences::OSPreferences() {}
+
+bool OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList) {
+ MOZ_ASSERT(aLocaleList.IsEmpty());
+
+#ifndef __MINGW32__
+ if (IsWin8OrLater()) {
+ // Try to get language list from GlobalizationPreferences; if this fails,
+ // we'll fall back to GetUserPreferredUILanguages.
+ // Per MSDN, these APIs are not available prior to Win8.
+ ComPtr<IGlobalizationPreferencesStatics> globalizationPrefs;
+ ComPtr<IVectorView<HSTRING>> languages;
+ uint32_t count;
+ if (SUCCEEDED(RoGetActivationFactory(
+ HStringReference(
+ RuntimeClass_Windows_System_UserProfile_GlobalizationPreferences)
+ .Get(),
+ IID_PPV_ARGS(&globalizationPrefs))) &&
+ SUCCEEDED(globalizationPrefs->get_Languages(&languages)) &&
+ SUCCEEDED(languages->get_Size(&count))) {
+ for (uint32_t i = 0; i < count; ++i) {
+ HString lang;
+ if (SUCCEEDED(languages->GetAt(i, lang.GetAddressOf()))) {
+ unsigned int length;
+ const wchar_t* text = lang.GetRawBuffer(&length);
+ NS_LossyConvertUTF16toASCII loc(text, length);
+ if (CanonicalizeLanguageTag(loc)) {
+ if (!loc.Contains('-')) {
+ // DirectWrite font-name code doesn't like to be given a bare
+ // language code with no region subtag, but the
+ // GlobalizationPreferences API may give us one (e.g. "ja").
+ // So if there's no hyphen in the string at this point, we use
+ // AddLikelySubtags to get a suitable region code to
+ // go with it.
+ Locale locale;
+ auto result = LocaleParser::TryParse(loc, locale);
+ if (result.isOk() && locale.AddLikelySubtags().isOk() &&
+ locale.Region().Present()) {
+ loc.Append('-');
+ loc.Append(locale.Region().Span());
+ }
+ }
+ aLocaleList.AppendElement(loc);
+ }
+ }
+ }
+ }
+ }
+#endif
+
+ // Per MSDN, GetUserPreferredUILanguages is available from Vista onwards,
+ // so we can use it unconditionally (although it may not work well!)
+ if (aLocaleList.IsEmpty()) {
+ // Note that according to the questioner at
+ // https://stackoverflow.com/questions/52849233/getuserpreferreduilanguages-never-returns-more-than-two-languages,
+ // this may not always return the full list of languages we'd expect.
+ // We should always get at least the first-preference lang, though.
+ ULONG numLanguages = 0;
+ DWORD cchLanguagesBuffer = 0;
+ if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages, nullptr,
+ &cchLanguagesBuffer)) {
+ return false;
+ }
+
+ AutoTArray<WCHAR, 64> locBuffer;
+ locBuffer.SetCapacity(cchLanguagesBuffer);
+ if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
+ locBuffer.Elements(),
+ &cchLanguagesBuffer)) {
+ return false;
+ }
+
+ const WCHAR* start = locBuffer.Elements();
+ const WCHAR* bufEnd = start + cchLanguagesBuffer;
+ while (bufEnd - start > 1 && *start) {
+ const WCHAR* end = start + 1;
+ while (bufEnd - end > 1 && *end) {
+ end++;
+ }
+ NS_LossyConvertUTF16toASCII loc(start, end - start);
+ if (CanonicalizeLanguageTag(loc)) {
+ aLocaleList.AppendElement(loc);
+ }
+ start = end + 1;
+ }
+ }
+
+ return !aLocaleList.IsEmpty();
+}
+
+bool OSPreferences::ReadRegionalPrefsLocales(nsTArray<nsCString>& aLocaleList) {
+ MOZ_ASSERT(aLocaleList.IsEmpty());
+
+ WCHAR locale[LOCALE_NAME_MAX_LENGTH];
+ if (NS_WARN_IF(!LCIDToLocaleName(LOCALE_USER_DEFAULT, locale,
+ LOCALE_NAME_MAX_LENGTH, 0))) {
+ return false;
+ }
+
+ NS_LossyConvertUTF16toASCII loc(locale);
+
+ if (CanonicalizeLanguageTag(loc)) {
+ aLocaleList.AppendElement(loc);
+ return true;
+ }
+ return false;
+}
+
+static LCTYPE ToDateLCType(OSPreferences::DateTimeFormatStyle aFormatStyle) {
+ switch (aFormatStyle) {
+ case OSPreferences::DateTimeFormatStyle::None:
+ return LOCALE_SLONGDATE;
+ case OSPreferences::DateTimeFormatStyle::Short:
+ return LOCALE_SSHORTDATE;
+ case OSPreferences::DateTimeFormatStyle::Medium:
+ return LOCALE_SSHORTDATE;
+ case OSPreferences::DateTimeFormatStyle::Long:
+ return LOCALE_SLONGDATE;
+ case OSPreferences::DateTimeFormatStyle::Full:
+ return LOCALE_SLONGDATE;
+ case OSPreferences::DateTimeFormatStyle::Invalid:
+ default:
+ MOZ_ASSERT_UNREACHABLE("invalid date format");
+ return LOCALE_SLONGDATE;
+ }
+}
+
+static LCTYPE ToTimeLCType(OSPreferences::DateTimeFormatStyle aFormatStyle) {
+ switch (aFormatStyle) {
+ case OSPreferences::DateTimeFormatStyle::None:
+ return LOCALE_STIMEFORMAT;
+ case OSPreferences::DateTimeFormatStyle::Short:
+ return LOCALE_SSHORTTIME;
+ case OSPreferences::DateTimeFormatStyle::Medium:
+ return LOCALE_SSHORTTIME;
+ case OSPreferences::DateTimeFormatStyle::Long:
+ return LOCALE_STIMEFORMAT;
+ case OSPreferences::DateTimeFormatStyle::Full:
+ return LOCALE_STIMEFORMAT;
+ case OSPreferences::DateTimeFormatStyle::Invalid:
+ default:
+ MOZ_ASSERT_UNREACHABLE("invalid time format");
+ return LOCALE_STIMEFORMAT;
+ }
+}
+
+/**
+ * Windows API includes regional preferences from the user only
+ * if we pass empty locale string or if the locale string matches
+ * the current locale.
+ *
+ * Since Windows API only allows us to retrieve two options - short/long
+ * we map it to our four options as:
+ *
+ * short -> short
+ * medium -> short
+ * long -> long
+ * full -> long
+ *
+ * In order to produce a single date/time format, we use CLDR pattern
+ * for combined date/time string, since Windows API does not provide an
+ * option for this.
+ */
+bool OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
+ DateTimeFormatStyle aTimeStyle,
+ const nsACString& aLocale,
+ nsACString& aRetVal) {
+ nsAutoString localeName;
+ CopyASCIItoUTF16(aLocale, localeName);
+
+ bool isDate = aDateStyle != DateTimeFormatStyle::None &&
+ aDateStyle != DateTimeFormatStyle::Invalid;
+ bool isTime = aTimeStyle != DateTimeFormatStyle::None &&
+ aTimeStyle != DateTimeFormatStyle::Invalid;
+
+ // If both date and time are wanted, we'll initially read them into a
+ // local string, and then insert them into the overall date+time pattern;
+ nsAutoString str;
+ if (isDate && isTime) {
+ if (!GetDateTimeConnectorPattern(aLocale, aRetVal)) {
+ NS_WARNING("failed to get date/time connector");
+ aRetVal.AssignLiteral("{1} {0}");
+ }
+ } else if (!isDate && !isTime) {
+ aRetVal.Truncate(0);
+ return true;
+ }
+
+ if (isDate) {
+ LCTYPE lcType = ToDateLCType(aDateStyle);
+ size_t len = GetLocaleInfoEx(
+ reinterpret_cast<const wchar_t*>(localeName.BeginReading()), lcType,
+ nullptr, 0);
+ if (len == 0) {
+ return false;
+ }
+
+ // We're doing it to ensure the terminator will fit when Windows writes the
+ // data to its output buffer. See bug 1358159 for details.
+ str.SetLength(len);
+ GetLocaleInfoEx(reinterpret_cast<const wchar_t*>(localeName.BeginReading()),
+ lcType, (WCHAR*)str.BeginWriting(), len);
+ str.SetLength(len - 1); // -1 because len counts the null terminator
+
+ // Windows uses "ddd" and "dddd" for abbreviated and full day names
+ // respectively,
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317787(v=vs.85).aspx
+ // but in a CLDR/ICU-style pattern these should be "EEE" and "EEEE".
+ // http://userguide.icu-project.org/formatparse/datetime
+ // So we fix that up here.
+ nsAString::const_iterator start, pos, end;
+ start = str.BeginReading(pos);
+ str.EndReading(end);
+ if (FindInReadable(u"dddd"_ns, pos, end)) {
+ str.ReplaceLiteral(pos - start, 4, u"EEEE");
+ } else {
+ pos = start;
+ if (FindInReadable(u"ddd"_ns, pos, end)) {
+ str.ReplaceLiteral(pos - start, 3, u"EEE");
+ }
+ }
+
+ // Also, Windows uses lowercase "g" or "gg" for era, but ICU wants uppercase
+ // "G" (it would interpret "g" as "modified Julian day"!). So fix that.
+ int32_t index = str.FindChar('g');
+ if (index >= 0) {
+ str.Replace(index, 1, 'G');
+ // If it was a double "gg", just drop the second one.
+ index++;
+ if (str.CharAt(index) == 'g') {
+ str.Cut(index, 1);
+ }
+ }
+
+ // If time was also requested, we need to substitute the date pattern from
+ // Windows into the date+time format that we have in aRetVal.
+ if (isTime) {
+ nsACString::const_iterator start, pos, end;
+ start = aRetVal.BeginReading(pos);
+ aRetVal.EndReading(end);
+ if (FindInReadable("{1}"_ns, pos, end)) {
+ aRetVal.Replace(pos - start, 3, NS_ConvertUTF16toUTF8(str));
+ }
+ } else {
+ aRetVal = NS_ConvertUTF16toUTF8(str);
+ }
+ }
+
+ if (isTime) {
+ LCTYPE lcType = ToTimeLCType(aTimeStyle);
+ size_t len = GetLocaleInfoEx(
+ reinterpret_cast<const wchar_t*>(localeName.BeginReading()), lcType,
+ nullptr, 0);
+ if (len == 0) {
+ return false;
+ }
+
+ // We're doing it to ensure the terminator will fit when Windows writes the
+ // data to its output buffer. See bug 1358159 for details.
+ str.SetLength(len);
+ GetLocaleInfoEx(reinterpret_cast<const wchar_t*>(localeName.BeginReading()),
+ lcType, (WCHAR*)str.BeginWriting(), len);
+ str.SetLength(len - 1);
+
+ // Windows uses "t" or "tt" for a "time marker" (am/pm indicator),
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd318148(v=vs.85).aspx
+ // but in a CLDR/ICU-style pattern that should be "a".
+ // http://userguide.icu-project.org/formatparse/datetime
+ // So we fix that up here.
+ int32_t index = str.FindChar('t');
+ if (index >= 0) {
+ str.Replace(index, 1, 'a');
+ index++;
+ if (str.CharAt(index) == 't') {
+ str.Cut(index, 1);
+ }
+ }
+
+ if (isDate) {
+ nsACString::const_iterator start, pos, end;
+ start = aRetVal.BeginReading(pos);
+ aRetVal.EndReading(end);
+ if (FindInReadable("{0}"_ns, pos, end)) {
+ aRetVal.Replace(pos - start, 3, NS_ConvertUTF16toUTF8(str));
+ }
+ } else {
+ aRetVal = NS_ConvertUTF16toUTF8(str);
+ }
+ }
+
+ return true;
+}
+
+void OSPreferences::RemoveObservers() {}