/* -*- 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 "nsReadableUtils.h" #include #ifndef __MINGW32__ // WinRT headers not yet supported by MinGW # include # include # include 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& aLocaleList) { MOZ_ASSERT(aLocaleList.IsEmpty()); #ifndef __MINGW32__ // 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 globalizationPrefs; ComPtr> 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 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& 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(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(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(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(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() {}