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