1 /* Determine the user's language preferences.
2 Copyright (C) 2004-2007 Free Software Foundation, Inc.
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as published by
6 the Free Software Foundation; either version 2.1 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
16
17 /* Written by Bruno Haible <bruno@clisp.org>.
18 Win32 code originally by Michele Cicciotti <hackbunny@reactos.com>. */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include <stdlib.h>
25
26 #if HAVE_CFPREFERENCESCOPYAPPVALUE
27 # include <string.h>
28 # include <CoreFoundation/CFPreferences.h>
29 # include <CoreFoundation/CFPropertyList.h>
30 # include <CoreFoundation/CFArray.h>
31 # include <CoreFoundation/CFString.h>
32 extern void _nl_locale_name_canonicalize (char *name);
33 #endif
34
35 #if defined _WIN32 || defined __WIN32__
36 # define WIN32_NATIVE
37 #endif
38
39 #ifdef WIN32_NATIVE
40 # define WIN32_LEAN_AND_MEAN
41 # include <windows.h>
42
43 # ifndef MUI_LANGUAGE_NAME
44 # define MUI_LANGUAGE_NAME 8
45 # endif
46 # ifndef STATUS_BUFFER_OVERFLOW
47 # define STATUS_BUFFER_OVERFLOW 0x80000005
48 # endif
49
50 extern void _nl_locale_name_canonicalize (char *name);
51 extern const char *_nl_locale_name_from_win32_LANGID (LANGID langid);
52 extern const char *_nl_locale_name_from_win32_LCID (LCID lcid);
53
54 /* Get the preferences list through the MUI APIs. This works on Windows Vista
55 and newer. */
56 static const char *
_nl_language_preferences_win32_mui(HMODULE kernel32)57 _nl_language_preferences_win32_mui (HMODULE kernel32)
58 {
59 /* DWORD GetUserPreferredUILanguages (ULONG dwFlags,
60 PULONG pulNumLanguages,
61 PWSTR pwszLanguagesBuffer,
62 PULONG pcchLanguagesBuffer); */
63 typedef DWORD (WINAPI *GetUserPreferredUILanguages_func) (ULONG, PULONG, PWSTR, PULONG);
64 GetUserPreferredUILanguages_func p_GetUserPreferredUILanguages;
65
66 p_GetUserPreferredUILanguages =
67 (GetUserPreferredUILanguages_func)
68 GetProcAddress (kernel32, "GetUserPreferredUILanguages");
69 if (p_GetUserPreferredUILanguages != NULL)
70 {
71 ULONG num_languages;
72 ULONG bufsize;
73 DWORD ret;
74
75 bufsize = 0;
76 ret = p_GetUserPreferredUILanguages (MUI_LANGUAGE_NAME,
77 &num_languages,
78 NULL, &bufsize);
79 if (ret == 0
80 && GetLastError () == STATUS_BUFFER_OVERFLOW
81 && bufsize > 0)
82 {
83 WCHAR *buffer = (WCHAR *) malloc (bufsize * sizeof (WCHAR));
84 if (buffer != NULL)
85 {
86 ret = p_GetUserPreferredUILanguages (MUI_LANGUAGE_NAME,
87 &num_languages,
88 buffer, &bufsize);
89 if (ret)
90 {
91 /* Convert the list from NUL-delimited WCHAR[] Win32 locale
92 names to colon-delimited char[] Unix locale names.
93 We assume that all these locale names are in ASCII,
94 nonempty and contain no colons. */
95 char *languages =
96 (char *) malloc (bufsize + num_languages * 10 + 1);
97 if (languages != NULL)
98 {
99 const WCHAR *p = buffer;
100 char *q = languages;
101 ULONG i;
102 for (i = 0; i < num_languages; i++)
103 {
104 char *q1;
105 char *q2;
106
107 q1 = q;
108 if (i > 0)
109 *q++ = ':';
110 q2 = q;
111 for (; *p != (WCHAR)'\0'; p++)
112 {
113 if ((unsigned char) *p != *p || *p == ':')
114 {
115 /* A non-ASCII character or a colon inside
116 the Win32 locale name! Punt. */
117 q = q1;
118 break;
119 }
120 *q++ = (unsigned char) *p;
121 }
122 if (q == q1)
123 /* An unexpected Win32 locale name occurred. */
124 break;
125 *q = '\0';
126 _nl_locale_name_canonicalize (q2);
127 q = q2 + strlen (q2);
128 p++;
129 }
130 *q = '\0';
131 if (q > languages)
132 {
133 free (buffer);
134 return languages;
135 }
136 free (languages);
137 }
138 }
139 free (buffer);
140 }
141 }
142 }
143 return NULL;
144 }
145
146 /* Get a preference. This works on Windows ME and newer. */
147 static const char *
_nl_language_preferences_win32_ME(HMODULE kernel32)148 _nl_language_preferences_win32_ME (HMODULE kernel32)
149 {
150 /* LANGID GetUserDefaultUILanguage (void); */
151 typedef LANGID (WINAPI *GetUserDefaultUILanguage_func) (void);
152 GetUserDefaultUILanguage_func p_GetUserDefaultUILanguage;
153
154 p_GetUserDefaultUILanguage =
155 (GetUserDefaultUILanguage_func)
156 GetProcAddress (kernel32, "GetUserDefaultUILanguage");
157 if (p_GetUserDefaultUILanguage != NULL)
158 return _nl_locale_name_from_win32_LANGID (p_GetUserDefaultUILanguage ());
159 return NULL;
160 }
161
162 /* Get a preference. This works on Windows 95 and newer. */
163 static const char *
_nl_language_preferences_win32_95()164 _nl_language_preferences_win32_95 ()
165 {
166 HKEY desktop_resource_locale_key;
167
168 if (RegOpenKeyExA (HKEY_CURRENT_USER,
169 "Control Panel\\Desktop\\ResourceLocale",
170 0, KEY_QUERY_VALUE, &desktop_resource_locale_key)
171 == NO_ERROR)
172 {
173 DWORD type;
174 char data[8 + 1];
175 DWORD data_size = sizeof (data);
176 DWORD ret;
177
178 ret = RegQueryValueExA (desktop_resource_locale_key, NULL, NULL,
179 &type, data, &data_size);
180 RegCloseKey (desktop_resource_locale_key);
181
182 if (ret == NO_ERROR)
183 {
184 /* We expect a string, at most 8 bytes long, that parses as a
185 hexadecimal number. */
186 if (type == REG_SZ
187 && data_size <= sizeof (data)
188 && (data_size < sizeof (data)
189 || data[sizeof (data) - 1] == '\0'))
190 {
191 LCID lcid;
192 char *endp;
193 /* Ensure it's NUL terminated. */
194 if (data_size < sizeof (data))
195 data[data_size] = '\0';
196 /* Parse it as a hexadecimal number. */
197 lcid = strtoul (data, &endp, 16);
198 if (endp > data && *endp == '\0')
199 return _nl_locale_name_from_win32_LCID (lcid);
200 }
201 }
202 }
203 return NULL;
204 }
205
206 /* Get the system's preference. This can be used as a fallback. */
207 static BOOL CALLBACK
ret_first_language(HMODULE h,LPCSTR type,LPCSTR name,WORD lang,LONG_PTR param)208 ret_first_language (HMODULE h, LPCSTR type, LPCSTR name, WORD lang, LONG_PTR param)
209 {
210 *(const char **)param = _nl_locale_name_from_win32_LANGID (lang);
211 return FALSE;
212 }
213 static const char *
_nl_language_preferences_win32_system(HMODULE kernel32)214 _nl_language_preferences_win32_system (HMODULE kernel32)
215 {
216 const char *languages = NULL;
217 /* Ignore the warning on mingw here. mingw has a wrong definition of the last
218 parameter type of ENUMRESLANGPROC. */
219 EnumResourceLanguages (kernel32, RT_VERSION, MAKEINTRESOURCE (1),
220 ret_first_language, (LONG_PTR)&languages);
221 return languages;
222 }
223
224 #endif
225
226 /* Determine the user's language preferences, as a colon separated list of
227 locale names in XPG syntax
228 language[_territory][.codeset][@modifier]
229 The result must not be freed; it is statically allocated.
230 The LANGUAGE environment variable does not need to be considered; it is
231 already taken into account by the caller. */
232
233 const char *
_nl_language_preferences_default(void)234 _nl_language_preferences_default (void)
235 {
236 #if HAVE_CFPREFERENCESCOPYAPPVALUE /* MacOS X 10.2 or newer */
237 {
238 /* Cache the preferences list, since CoreFoundation calls are expensive. */
239 static const char *cached_languages;
240 static int cache_initialized;
241
242 if (!cache_initialized)
243 {
244 CFTypeRef preferences =
245 CFPreferencesCopyAppValue (CFSTR ("AppleLanguages"),
246 kCFPreferencesCurrentApplication);
247 if (preferences != NULL
248 && CFGetTypeID (preferences) == CFArrayGetTypeID ())
249 {
250 CFArrayRef prefArray = (CFArrayRef)preferences;
251 int n = CFArrayGetCount (prefArray);
252 char buf[256];
253 size_t size = 0;
254 int i;
255
256 for (i = 0; i < n; i++)
257 {
258 CFTypeRef element = CFArrayGetValueAtIndex (prefArray, i);
259 if (element != NULL
260 && CFGetTypeID (element) == CFStringGetTypeID ()
261 && CFStringGetCString ((CFStringRef)element,
262 buf, sizeof (buf),
263 kCFStringEncodingASCII))
264 {
265 _nl_locale_name_canonicalize (buf);
266 size += strlen (buf) + 1;
267 /* Most GNU programs use msgids in English and don't ship
268 an en.mo message catalog. Therefore when we see "en"
269 in the preferences list, arrange for gettext() to
270 return the msgid, and ignore all further elements of
271 the preferences list. */
272 if (strcmp (buf, "en") == 0)
273 break;
274 }
275 else
276 break;
277 }
278 if (size > 0)
279 {
280 char *languages = (char *) malloc (size);
281
282 if (languages != NULL)
283 {
284 char *p = languages;
285
286 for (i = 0; i < n; i++)
287 {
288 CFTypeRef element =
289 CFArrayGetValueAtIndex (prefArray, i);
290 if (element != NULL
291 && CFGetTypeID (element) == CFStringGetTypeID ()
292 && CFStringGetCString ((CFStringRef)element,
293 buf, sizeof (buf),
294 kCFStringEncodingASCII))
295 {
296 _nl_locale_name_canonicalize (buf);
297 strcpy (p, buf);
298 p += strlen (buf);
299 *p++ = ':';
300 if (strcmp (buf, "en") == 0)
301 break;
302 }
303 else
304 break;
305 }
306 *--p = '\0';
307
308 cached_languages = languages;
309 }
310 }
311 }
312 cache_initialized = 1;
313 }
314 if (cached_languages != NULL)
315 return cached_languages;
316 }
317 #endif
318
319 #ifdef WIN32_NATIVE
320 {
321 /* Cache the preferences list, since computing it is expensive. */
322 static const char *cached_languages;
323 static int cache_initialized;
324
325 /* Activate the new code only when the GETTEXT_MUI environment variable is
326 set, for the time being, since the new code is not well tested. */
327 if (!cache_initialized && getenv ("GETTEXT_MUI") != NULL)
328 {
329 const char *languages = NULL;
330 HMODULE kernel32 = GetModuleHandle ("kernel32");
331
332 if (kernel32 != NULL)
333 languages = _nl_language_preferences_win32_mui (kernel32);
334
335 if (languages == NULL && kernel32 != NULL)
336 languages = _nl_language_preferences_win32_ME (kernel32);
337
338 if (languages == NULL)
339 languages = _nl_language_preferences_win32_95 ();
340
341 if (languages == NULL && kernel32 != NULL)
342 languages = _nl_language_preferences_win32_system (kernel32);
343
344 cached_languages = languages;
345 cache_initialized = 1;
346 }
347 if (cached_languages != NULL)
348 return cached_languages;
349 }
350 #endif
351
352 return NULL;
353 }
354