OSPreferences_win.cpp (11278B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "OSPreferences.h" 8 #include "mozilla/intl/Locale.h" 9 #include "mozilla/intl/LocaleService.h" 10 #include "nsReadableUtils.h" 11 12 #include <windows.h> 13 14 #ifndef __MINGW32__ // WinRT headers not yet supported by MinGW 15 # include <roapi.h> 16 # include <wrl.h> 17 # include <Windows.System.UserProfile.h> 18 19 using namespace Microsoft::WRL; 20 using namespace Microsoft::WRL::Wrappers; 21 using namespace ABI::Windows::Foundation::Collections; 22 using namespace ABI::Windows::System::UserProfile; 23 #endif 24 25 using namespace mozilla::intl; 26 27 OSPreferences::OSPreferences() {} 28 29 bool OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList) { 30 MOZ_ASSERT(aLocaleList.IsEmpty()); 31 32 #ifndef __MINGW32__ 33 // Try to get language list from GlobalizationPreferences; if this fails, 34 // we'll fall back to GetUserPreferredUILanguages. 35 // Per MSDN, these APIs are not available prior to Win8. 36 ComPtr<IGlobalizationPreferencesStatics> globalizationPrefs; 37 ComPtr<IVectorView<HSTRING>> languages; 38 uint32_t count; 39 if (SUCCEEDED(RoGetActivationFactory( 40 HStringReference( 41 RuntimeClass_Windows_System_UserProfile_GlobalizationPreferences) 42 .Get(), 43 IID_PPV_ARGS(&globalizationPrefs))) && 44 SUCCEEDED(globalizationPrefs->get_Languages(&languages)) && 45 SUCCEEDED(languages->get_Size(&count))) { 46 for (uint32_t i = 0; i < count; ++i) { 47 HString lang; 48 if (SUCCEEDED(languages->GetAt(i, lang.GetAddressOf()))) { 49 unsigned int length; 50 const wchar_t* text = lang.GetRawBuffer(&length); 51 NS_LossyConvertUTF16toASCII loc(text, length); 52 if (CanonicalizeLanguageTag(loc)) { 53 if (!loc.Contains('-')) { 54 // DirectWrite font-name code doesn't like to be given a bare 55 // language code with no region subtag, but the 56 // GlobalizationPreferences API may give us one (e.g. "ja"). 57 // So if there's no hyphen in the string at this point, we use 58 // AddLikelySubtags to get a suitable region code to 59 // go with it. 60 Locale locale; 61 auto result = LocaleParser::TryParse(loc, locale); 62 if (result.isOk() && locale.AddLikelySubtags().isOk() && 63 locale.Region().Present()) { 64 loc.Append('-'); 65 loc.Append(locale.Region().Span()); 66 } 67 } 68 aLocaleList.AppendElement(loc); 69 } 70 } 71 } 72 } 73 #endif 74 75 // Per MSDN, GetUserPreferredUILanguages is available from Vista onwards, 76 // so we can use it unconditionally (although it may not work well!) 77 if (aLocaleList.IsEmpty()) { 78 // Note that according to the questioner at 79 // https://stackoverflow.com/questions/52849233/getuserpreferreduilanguages-never-returns-more-than-two-languages, 80 // this may not always return the full list of languages we'd expect. 81 // We should always get at least the first-preference lang, though. 82 ULONG numLanguages = 0; 83 DWORD cchLanguagesBuffer = 0; 84 if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages, nullptr, 85 &cchLanguagesBuffer)) { 86 return false; 87 } 88 89 AutoTArray<WCHAR, 64> locBuffer; 90 locBuffer.SetCapacity(cchLanguagesBuffer); 91 if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages, 92 locBuffer.Elements(), 93 &cchLanguagesBuffer)) { 94 return false; 95 } 96 97 const WCHAR* start = locBuffer.Elements(); 98 const WCHAR* bufEnd = start + cchLanguagesBuffer; 99 while (bufEnd - start > 1 && *start) { 100 const WCHAR* end = start + 1; 101 while (bufEnd - end > 1 && *end) { 102 end++; 103 } 104 NS_LossyConvertUTF16toASCII loc(start, end - start); 105 if (CanonicalizeLanguageTag(loc)) { 106 aLocaleList.AppendElement(loc); 107 } 108 start = end + 1; 109 } 110 } 111 112 return !aLocaleList.IsEmpty(); 113 } 114 115 bool OSPreferences::ReadRegionalPrefsLocales(nsTArray<nsCString>& aLocaleList) { 116 MOZ_ASSERT(aLocaleList.IsEmpty()); 117 118 WCHAR locale[LOCALE_NAME_MAX_LENGTH]; 119 if (NS_WARN_IF(!LCIDToLocaleName(LOCALE_USER_DEFAULT, locale, 120 LOCALE_NAME_MAX_LENGTH, 0))) { 121 return false; 122 } 123 124 NS_LossyConvertUTF16toASCII loc(locale); 125 126 if (CanonicalizeLanguageTag(loc)) { 127 aLocaleList.AppendElement(loc); 128 return true; 129 } 130 return false; 131 } 132 133 static LCTYPE ToDateLCType(OSPreferences::DateTimeFormatStyle aFormatStyle) { 134 switch (aFormatStyle) { 135 case OSPreferences::DateTimeFormatStyle::None: 136 return LOCALE_SLONGDATE; 137 case OSPreferences::DateTimeFormatStyle::Short: 138 return LOCALE_SSHORTDATE; 139 case OSPreferences::DateTimeFormatStyle::Medium: 140 return LOCALE_SSHORTDATE; 141 case OSPreferences::DateTimeFormatStyle::Long: 142 return LOCALE_SLONGDATE; 143 case OSPreferences::DateTimeFormatStyle::Full: 144 return LOCALE_SLONGDATE; 145 case OSPreferences::DateTimeFormatStyle::Invalid: 146 default: 147 MOZ_ASSERT_UNREACHABLE("invalid date format"); 148 return LOCALE_SLONGDATE; 149 } 150 } 151 152 static LCTYPE ToTimeLCType(OSPreferences::DateTimeFormatStyle aFormatStyle) { 153 switch (aFormatStyle) { 154 case OSPreferences::DateTimeFormatStyle::None: 155 return LOCALE_STIMEFORMAT; 156 case OSPreferences::DateTimeFormatStyle::Short: 157 return LOCALE_SSHORTTIME; 158 case OSPreferences::DateTimeFormatStyle::Medium: 159 return LOCALE_SSHORTTIME; 160 case OSPreferences::DateTimeFormatStyle::Long: 161 return LOCALE_STIMEFORMAT; 162 case OSPreferences::DateTimeFormatStyle::Full: 163 return LOCALE_STIMEFORMAT; 164 case OSPreferences::DateTimeFormatStyle::Invalid: 165 default: 166 MOZ_ASSERT_UNREACHABLE("invalid time format"); 167 return LOCALE_STIMEFORMAT; 168 } 169 } 170 171 /** 172 * Windows API includes regional preferences from the user only 173 * if we pass empty locale string or if the locale string matches 174 * the current locale. 175 * 176 * Since Windows API only allows us to retrieve two options - short/long 177 * we map it to our four options as: 178 * 179 * short -> short 180 * medium -> short 181 * long -> long 182 * full -> long 183 * 184 * In order to produce a single date/time format, we use CLDR pattern 185 * for combined date/time string, since Windows API does not provide an 186 * option for this. 187 */ 188 bool OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle, 189 DateTimeFormatStyle aTimeStyle, 190 const nsACString& aLocale, 191 nsACString& aRetVal) { 192 nsAutoString localeName; 193 CopyASCIItoUTF16(aLocale, localeName); 194 195 bool isDate = aDateStyle != DateTimeFormatStyle::None && 196 aDateStyle != DateTimeFormatStyle::Invalid; 197 bool isTime = aTimeStyle != DateTimeFormatStyle::None && 198 aTimeStyle != DateTimeFormatStyle::Invalid; 199 200 // If both date and time are wanted, we'll initially read them into a 201 // local string, and then insert them into the overall date+time pattern; 202 nsAutoString str; 203 if (isDate && isTime) { 204 if (!GetDateTimeConnectorPattern(aLocale, aRetVal)) { 205 NS_WARNING("failed to get date/time connector"); 206 aRetVal.AssignLiteral("{1} {0}"); 207 } 208 } else if (!isDate && !isTime) { 209 aRetVal.Truncate(0); 210 return true; 211 } 212 213 if (isDate) { 214 LCTYPE lcType = ToDateLCType(aDateStyle); 215 size_t len = GetLocaleInfoEx( 216 reinterpret_cast<const wchar_t*>(localeName.BeginReading()), lcType, 217 nullptr, 0); 218 if (len == 0) { 219 return false; 220 } 221 222 // We're doing it to ensure the terminator will fit when Windows writes the 223 // data to its output buffer. See bug 1358159 for details. 224 str.SetLength(len); 225 GetLocaleInfoEx(reinterpret_cast<const wchar_t*>(localeName.BeginReading()), 226 lcType, (WCHAR*)str.BeginWriting(), len); 227 str.SetLength(len - 1); // -1 because len counts the null terminator 228 229 // Windows uses "ddd" and "dddd" for abbreviated and full day names 230 // respectively, 231 // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317787(v=vs.85).aspx 232 // but in a CLDR/ICU-style pattern these should be "EEE" and "EEEE". 233 // http://userguide.icu-project.org/formatparse/datetime 234 // So we fix that up here. 235 nsAString::const_iterator start, pos, end; 236 start = str.BeginReading(pos); 237 str.EndReading(end); 238 if (FindInReadable(u"dddd"_ns, pos, end)) { 239 str.ReplaceLiteral(pos - start, 4, u"EEEE"); 240 } else { 241 pos = start; 242 if (FindInReadable(u"ddd"_ns, pos, end)) { 243 str.ReplaceLiteral(pos - start, 3, u"EEE"); 244 } 245 } 246 247 // Also, Windows uses lowercase "g" or "gg" for era, but ICU wants uppercase 248 // "G" (it would interpret "g" as "modified Julian day"!). So fix that. 249 int32_t index = str.FindChar('g'); 250 if (index >= 0) { 251 str.Replace(index, 1, 'G'); 252 // If it was a double "gg", just drop the second one. 253 index++; 254 if (str.CharAt(index) == 'g') { 255 str.Cut(index, 1); 256 } 257 } 258 259 // If time was also requested, we need to substitute the date pattern from 260 // Windows into the date+time format that we have in aRetVal. 261 if (isTime) { 262 nsACString::const_iterator start, pos, end; 263 start = aRetVal.BeginReading(pos); 264 aRetVal.EndReading(end); 265 if (FindInReadable("{1}"_ns, pos, end)) { 266 aRetVal.Replace(pos - start, 3, NS_ConvertUTF16toUTF8(str)); 267 } 268 } else { 269 aRetVal = NS_ConvertUTF16toUTF8(str); 270 } 271 } 272 273 if (isTime) { 274 LCTYPE lcType = ToTimeLCType(aTimeStyle); 275 size_t len = GetLocaleInfoEx( 276 reinterpret_cast<const wchar_t*>(localeName.BeginReading()), lcType, 277 nullptr, 0); 278 if (len == 0) { 279 return false; 280 } 281 282 // We're doing it to ensure the terminator will fit when Windows writes the 283 // data to its output buffer. See bug 1358159 for details. 284 str.SetLength(len); 285 GetLocaleInfoEx(reinterpret_cast<const wchar_t*>(localeName.BeginReading()), 286 lcType, (WCHAR*)str.BeginWriting(), len); 287 str.SetLength(len - 1); 288 289 // Windows uses "t" or "tt" for a "time marker" (am/pm indicator), 290 // https://msdn.microsoft.com/en-us/library/windows/desktop/dd318148(v=vs.85).aspx 291 // but in a CLDR/ICU-style pattern that should be "a". 292 // http://userguide.icu-project.org/formatparse/datetime 293 // So we fix that up here. 294 int32_t index = str.FindChar('t'); 295 if (index >= 0) { 296 str.Replace(index, 1, 'a'); 297 index++; 298 if (str.CharAt(index) == 't') { 299 str.Cut(index, 1); 300 } 301 } 302 303 if (isDate) { 304 nsACString::const_iterator start, pos, end; 305 start = aRetVal.BeginReading(pos); 306 aRetVal.EndReading(end); 307 if (FindInReadable("{0}"_ns, pos, end)) { 308 aRetVal.Replace(pos - start, 3, NS_ConvertUTF16toUTF8(str)); 309 } 310 } else { 311 aRetVal = NS_ConvertUTF16toUTF8(str); 312 } 313 } 314 315 return true; 316 } 317 318 void OSPreferences::RemoveObservers() {}