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/LocaleService.h"
9 #include <Carbon/Carbon.h>
10 
11 using namespace mozilla::intl;
12 
LocaleChangedNotificationCallback(CFNotificationCenterRef center,void * observer,CFStringRef name,const void * object,CFDictionaryRef userInfo)13 static void LocaleChangedNotificationCallback(CFNotificationCenterRef center,
14                                               void* observer, CFStringRef name,
15                                               const void* object,
16                                               CFDictionaryRef userInfo) {
17   if (!::CFEqual(name, kCFLocaleCurrentLocaleDidChangeNotification)) {
18     return;
19   }
20   static_cast<OSPreferences*>(observer)->Refresh();
21 }
22 
OSPreferences()23 OSPreferences::OSPreferences() {
24   ::CFNotificationCenterAddObserver(
25       ::CFNotificationCenterGetLocalCenter(), this,
26       LocaleChangedNotificationCallback,
27       kCFLocaleCurrentLocaleDidChangeNotification, 0,
28       CFNotificationSuspensionBehaviorDeliverImmediately);
29 }
30 
ReadSystemLocales(nsTArray<nsCString> & aLocaleList)31 bool OSPreferences::ReadSystemLocales(nsTArray<nsCString>& aLocaleList) {
32   MOZ_ASSERT(aLocaleList.IsEmpty());
33 
34   CFArrayRef langs = ::CFLocaleCopyPreferredLanguages();
35   for (CFIndex i = 0; i < ::CFArrayGetCount(langs); i++) {
36     CFStringRef lang = (CFStringRef)::CFArrayGetValueAtIndex(langs, i);
37 
38     AutoTArray<UniChar, 32> buffer;
39     int size = ::CFStringGetLength(lang);
40     buffer.SetLength(size);
41 
42     CFRange range = ::CFRangeMake(0, size);
43     ::CFStringGetCharacters(lang, range, buffer.Elements());
44 
45     // Convert the locale string to the format that Mozilla expects
46     NS_LossyConvertUTF16toASCII locale(
47         reinterpret_cast<const char16_t*>(buffer.Elements()), buffer.Length());
48 
49     if (CanonicalizeLanguageTag(locale)) {
50       aLocaleList.AppendElement(locale);
51     }
52   }
53 
54   ::CFRelease(langs);
55 
56   return !aLocaleList.IsEmpty();
57 }
58 
ReadRegionalPrefsLocales(nsTArray<nsCString> & aLocaleList)59 bool OSPreferences::ReadRegionalPrefsLocales(nsTArray<nsCString>& aLocaleList) {
60   // For now we're just taking System Locales since we don't know of any better
61   // API for regional prefs.
62   return ReadSystemLocales(aLocaleList);
63 }
64 
ToCFDateFormatterStyle(OSPreferences::DateTimeFormatStyle aFormatStyle)65 static CFDateFormatterStyle ToCFDateFormatterStyle(
66     OSPreferences::DateTimeFormatStyle aFormatStyle) {
67   switch (aFormatStyle) {
68     case OSPreferences::DateTimeFormatStyle::None:
69       return kCFDateFormatterNoStyle;
70     case OSPreferences::DateTimeFormatStyle::Short:
71       return kCFDateFormatterShortStyle;
72     case OSPreferences::DateTimeFormatStyle::Medium:
73       return kCFDateFormatterMediumStyle;
74     case OSPreferences::DateTimeFormatStyle::Long:
75       return kCFDateFormatterLongStyle;
76     case OSPreferences::DateTimeFormatStyle::Full:
77       return kCFDateFormatterFullStyle;
78     case OSPreferences::DateTimeFormatStyle::Invalid:
79       MOZ_ASSERT_UNREACHABLE("invalid time format");
80       return kCFDateFormatterNoStyle;
81   }
82 }
83 
84 // Given an 8-bit Gecko string, create a corresponding CFLocale;
85 // if aLocale is empty, returns a copy of the system's current locale.
86 // May return null on failure.
87 // Follows Core Foundation's Create rule, so the caller is responsible to
88 // release the returned reference.
CreateCFLocaleFor(const nsACString & aLocale)89 static CFLocaleRef CreateCFLocaleFor(const nsACString& aLocale) {
90   nsAutoCString reqLocale;
91   nsAutoCString systemLocale;
92 
93   OSPreferences::GetInstance()->GetSystemLocale(systemLocale);
94 
95   if (aLocale.IsEmpty()) {
96     LocaleService::GetInstance()->GetAppLocaleAsBCP47(reqLocale);
97   } else {
98     reqLocale.Assign(aLocale);
99   }
100 
101   bool match = LocaleService::LanguagesMatch(reqLocale, systemLocale);
102   if (match) {
103     return ::CFLocaleCopyCurrent();
104   }
105 
106   CFStringRef identifier = CFStringCreateWithBytesNoCopy(
107       kCFAllocatorDefault, (const uint8_t*)reqLocale.BeginReading(),
108       reqLocale.Length(), kCFStringEncodingASCII, false, kCFAllocatorNull);
109   if (!identifier) {
110     return nullptr;
111   }
112   CFLocaleRef locale = CFLocaleCreate(kCFAllocatorDefault, identifier);
113   CFRelease(identifier);
114   return locale;
115 }
116 
117 /**
118  * Cocoa API maps nicely to our four styles of date/time.
119  *
120  * The only caveat is that Cocoa takes regional preferences modifications
121  * into account only when we pass an empty string as a locale.
122  *
123  * In all other cases it will return the default pattern for a given locale.
124  */
ReadDateTimePattern(DateTimeFormatStyle aDateStyle,DateTimeFormatStyle aTimeStyle,const nsACString & aLocale,nsACString & aRetVal)125 bool OSPreferences::ReadDateTimePattern(DateTimeFormatStyle aDateStyle,
126                                         DateTimeFormatStyle aTimeStyle,
127                                         const nsACString& aLocale,
128                                         nsACString& aRetVal) {
129   CFLocaleRef locale = CreateCFLocaleFor(aLocale);
130   if (!locale) {
131     return false;
132   }
133 
134   CFDateFormatterRef formatter = CFDateFormatterCreate(
135       kCFAllocatorDefault, locale, ToCFDateFormatterStyle(aDateStyle),
136       ToCFDateFormatterStyle(aTimeStyle));
137   if (!formatter) {
138     return false;
139   }
140   CFStringRef format = CFDateFormatterGetFormat(formatter);
141   CFRelease(locale);
142 
143   CFRange range = CFRangeMake(0, CFStringGetLength(format));
144   nsAutoString str;
145   str.SetLength(range.length);
146   CFStringGetCharacters(format, range,
147                         reinterpret_cast<UniChar*>(str.BeginWriting()));
148   CFRelease(formatter);
149 
150   aRetVal = NS_ConvertUTF16toUTF8(str);
151   return true;
152 }
153 
RemoveObservers()154 void OSPreferences::RemoveObservers() {
155   ::CFNotificationCenterRemoveObserver(
156       ::CFNotificationCenterGetLocalCenter(), this,
157       kCTFontManagerRegisteredFontsChangedNotification, 0);
158 }
159