1 /* Determine the user's language preferences.
2 Copyright (C) 2004-2007, 2018-2019 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 <https://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_CFLOCALECOPYPREFERREDLANGUAGES || HAVE_CFPREFERENCESCOPYAPPVALUE
27 # include <string.h>
28 # if HAVE_CFLOCALECOPYPREFERREDLANGUAGES
29 # include <CoreFoundation/CFLocale.h>
30 # elif HAVE_CFPREFERENCESCOPYAPPVALUE
31 # include <CoreFoundation/CFPreferences.h>
32 # endif
33 # include <CoreFoundation/CFPropertyList.h>
34 # include <CoreFoundation/CFArray.h>
35 # include <CoreFoundation/CFString.h>
36 extern void _nl_locale_name_canonicalize (char *name);
37 #endif
38
39 #if defined _WIN32
40 # define WIN32_NATIVE
41 #endif
42
43 #ifdef WIN32_NATIVE
44 # define WIN32_LEAN_AND_MEAN
45 # include <windows.h>
46
47 # ifndef MUI_LANGUAGE_NAME
48 # define MUI_LANGUAGE_NAME 8
49 # endif
50 # ifndef STATUS_BUFFER_OVERFLOW
51 # define STATUS_BUFFER_OVERFLOW 0x80000005
52 # endif
53
54 extern void _nl_locale_name_canonicalize (char *name);
55 extern const char *_nl_locale_name_from_win32_LANGID (LANGID langid);
56 extern const char *_nl_locale_name_from_win32_LCID (LCID lcid);
57
58 /* Get the preferences list through the MUI APIs. This works on Windows Vista
59 and newer. */
60 static const char *
_nl_language_preferences_win32_mui(HMODULE kernel32)61 _nl_language_preferences_win32_mui (HMODULE kernel32)
62 {
63 /* DWORD GetUserPreferredUILanguages (ULONG dwFlags,
64 PULONG pulNumLanguages,
65 PWSTR pwszLanguagesBuffer,
66 PULONG pcchLanguagesBuffer); */
67 typedef DWORD (WINAPI *GetUserPreferredUILanguages_func) (ULONG, PULONG, PWSTR, PULONG);
68 GetUserPreferredUILanguages_func p_GetUserPreferredUILanguages;
69
70 p_GetUserPreferredUILanguages =
71 (GetUserPreferredUILanguages_func)
72 GetProcAddress (kernel32, "GetUserPreferredUILanguages");
73 if (p_GetUserPreferredUILanguages != NULL)
74 {
75 ULONG num_languages;
76 ULONG bufsize;
77 DWORD ret;
78
79 bufsize = 0;
80 ret = p_GetUserPreferredUILanguages (MUI_LANGUAGE_NAME,
81 &num_languages,
82 NULL, &bufsize);
83 if (ret == 0
84 && GetLastError () == STATUS_BUFFER_OVERFLOW
85 && bufsize > 0)
86 {
87 WCHAR *buffer = (WCHAR *) malloc (bufsize * sizeof (WCHAR));
88 if (buffer != NULL)
89 {
90 ret = p_GetUserPreferredUILanguages (MUI_LANGUAGE_NAME,
91 &num_languages,
92 buffer, &bufsize);
93 if (ret)
94 {
95 /* Convert the list from NUL-delimited WCHAR[] Win32 locale
96 names to colon-delimited char[] Unix locale names.
97 We assume that all these locale names are in ASCII,
98 nonempty and contain no colons. */
99 char *languages =
100 (char *) malloc (bufsize + num_languages * 10 + 1);
101 if (languages != NULL)
102 {
103 const WCHAR *p = buffer;
104 char *q = languages;
105 ULONG i;
106 for (i = 0; i < num_languages; i++)
107 {
108 char *q1;
109 char *q2;
110
111 q1 = q;
112 if (i > 0)
113 *q++ = ':';
114 q2 = q;
115 for (; *p != (WCHAR)'\0'; p++)
116 {
117 if ((unsigned char) *p != *p || *p == ':')
118 {
119 /* A non-ASCII character or a colon inside
120 the Win32 locale name! Punt. */
121 q = q1;
122 break;
123 }
124 *q++ = (unsigned char) *p;
125 }
126 if (q == q1)
127 /* An unexpected Win32 locale name occurred. */
128 break;
129 *q = '\0';
130 _nl_locale_name_canonicalize (q2);
131 q = q2 + strlen (q2);
132 p++;
133 }
134 *q = '\0';
135 if (q > languages)
136 {
137 free (buffer);
138 return languages;
139 }
140 free (languages);
141 }
142 }
143 free (buffer);
144 }
145 }
146 }
147 return NULL;
148 }
149
150 /* Get a preference. This works on Windows ME and newer. */
151 static const char *
_nl_language_preferences_win32_ME(HMODULE kernel32)152 _nl_language_preferences_win32_ME (HMODULE kernel32)
153 {
154 /* LANGID GetUserDefaultUILanguage (void); */
155 typedef LANGID (WINAPI *GetUserDefaultUILanguage_func) (void);
156 GetUserDefaultUILanguage_func p_GetUserDefaultUILanguage;
157
158 p_GetUserDefaultUILanguage =
159 (GetUserDefaultUILanguage_func)
160 GetProcAddress (kernel32, "GetUserDefaultUILanguage");
161 if (p_GetUserDefaultUILanguage != NULL)
162 return _nl_locale_name_from_win32_LANGID (p_GetUserDefaultUILanguage ());
163 return NULL;
164 }
165
166 /* Get a preference. This works on Windows 95 and newer. */
167 static const char *
_nl_language_preferences_win32_95()168 _nl_language_preferences_win32_95 ()
169 {
170 HKEY desktop_resource_locale_key;
171
172 if (RegOpenKeyExA (HKEY_CURRENT_USER,
173 "Control Panel\\Desktop\\ResourceLocale",
174 0, KEY_QUERY_VALUE, &desktop_resource_locale_key)
175 == NO_ERROR)
176 {
177 DWORD type;
178 BYTE data[8 + 1];
179 DWORD data_size = sizeof (data);
180 DWORD ret;
181
182 ret = RegQueryValueExA (desktop_resource_locale_key, NULL, NULL,
183 &type, data, &data_size);
184 RegCloseKey (desktop_resource_locale_key);
185
186 if (ret == NO_ERROR)
187 {
188 /* We expect a string, at most 8 bytes long, that parses as a
189 hexadecimal number. */
190 if (type == REG_SZ
191 && data_size <= sizeof (data)
192 && (data_size < sizeof (data)
193 || data[sizeof (data) - 1] == '\0'))
194 {
195 LCID lcid;
196 char *endp;
197 /* Ensure it's NUL terminated. */
198 if (data_size < sizeof (data))
199 data[data_size] = '\0';
200 /* Parse it as a hexadecimal number. */
201 lcid = strtoul ((char *) data, &endp, 16);
202 if (endp > (char *) data && *endp == '\0')
203 return _nl_locale_name_from_win32_LCID (lcid);
204 }
205 }
206 }
207 return NULL;
208 }
209
210 /* Get the system's preference. This can be used as a fallback. */
211 static BOOL CALLBACK
ret_first_language(HMODULE h,LPCSTR type,LPCSTR name,WORD lang,LONG_PTR param)212 ret_first_language (HMODULE h, LPCSTR type, LPCSTR name, WORD lang, LONG_PTR param)
213 {
214 *(const char **)param = _nl_locale_name_from_win32_LANGID (lang);
215 return FALSE;
216 }
217 static const char *
_nl_language_preferences_win32_system(HMODULE kernel32)218 _nl_language_preferences_win32_system (HMODULE kernel32)
219 {
220 const char *languages = NULL;
221 /* Ignore the warning on mingw here. mingw has a wrong definition of the last
222 parameter type of ENUMRESLANGPROC. */
223 EnumResourceLanguages (kernel32, RT_VERSION, MAKEINTRESOURCE (1),
224 ret_first_language, (LONG_PTR)&languages);
225 return languages;
226 }
227
228 #endif
229
230 /* Determine the user's language preferences, as a colon separated list of
231 locale names in XPG syntax
232 language[_territory][.codeset][@modifier]
233 The result must not be freed; it is statically allocated.
234 The LANGUAGE environment variable does not need to be considered; it is
235 already taken into account by the caller. */
236
237 const char *
_nl_language_preferences_default(void)238 _nl_language_preferences_default (void)
239 {
240 #if HAVE_CFLOCALECOPYPREFERREDLANGUAGES || HAVE_CFPREFERENCESCOPYAPPVALUE
241 /* MacOS X 10.4 or newer */
242 {
243 /* Cache the preferences list, since CoreFoundation calls are expensive. */
244 static const char *cached_languages;
245 static int cache_initialized;
246
247 if (!cache_initialized)
248 {
249 # if HAVE_CFLOCALECOPYPREFERREDLANGUAGES /* MacOS X 10.5 or newer */
250 CFArrayRef prefArray = CFLocaleCopyPreferredLanguages ();
251 # elif HAVE_CFPREFERENCESCOPYAPPVALUE /* MacOS X 10.4 or newer */
252 CFTypeRef preferences =
253 CFPreferencesCopyAppValue (CFSTR ("AppleLanguages"),
254 kCFPreferencesCurrentApplication);
255 if (preferences != NULL
256 && CFGetTypeID (preferences) == CFArrayGetTypeID ())
257 {
258 CFArrayRef prefArray = (CFArrayRef)preferences;
259 # endif
260
261 int n = CFArrayGetCount (prefArray);
262 char buf[256];
263 char buf2[256];
264 size_t size = 0;
265 int i;
266
267 for (i = 0; i < n; i++)
268 {
269 CFTypeRef element = CFArrayGetValueAtIndex (prefArray, i);
270 if (element != NULL
271 && CFGetTypeID (element) == CFStringGetTypeID ()
272 && CFStringGetCString ((CFStringRef)element,
273 buf, sizeof (buf),
274 kCFStringEncodingASCII))
275 {
276 strcpy (buf2, buf);
277 _nl_locale_name_canonicalize (buf);
278 size += strlen (buf) + 1;
279 /* Mac OS X 10.12 or newer returns an array of elements of
280 the form "ll-CC" or "ll-Scrp-CC" where ll is a language
281 code, CC is a country code, and Scrp (optional) is a
282 script code.
283 _nl_locale_name_canonicalize converts this to "ll_CC" or
284 "ll_Scrp_CC".
285 Sometimes ll and CC are unrelated, i.e. there is no
286 translation for "ll_CC" but one for "ll".
287 Similarly, in the case with a script, sometimes there is
288 no translation for "ll_Scrp_CC" but one for "ll_Scrp"
289 (after proper canonicalization).
290 Therefore, in the result, we return "ll_CC" followed
291 by "ll", or similarly for the case with a script. */
292 {
293 char *last_minus = strrchr (buf2, '-');
294 if (last_minus != NULL)
295 {
296 *last_minus = '\0';
297 _nl_locale_name_canonicalize (buf2);
298 size += strlen (buf2) + 1;
299 }
300 }
301 /* Most GNU programs use msgids in English and don't ship
302 an en.mo message catalog. Therefore when we see "en" or
303 "en-CC" in the preferences list, arrange for gettext()
304 to return the msgid, and ignore all further elements of
305 the preferences list. */
306 if (buf[0] == 'e' && buf[1] == 'n'
307 && (buf[2] == '\0' || buf[2] == '_'))
308 break;
309 }
310 else
311 break;
312 }
313 if (size > 0)
314 {
315 char *languages = (char *) malloc (size);
316
317 if (languages != NULL)
318 {
319 char *p = languages;
320
321 for (i = 0; i < n; i++)
322 {
323 CFTypeRef element =
324 CFArrayGetValueAtIndex (prefArray, i);
325 if (element != NULL
326 && CFGetTypeID (element) == CFStringGetTypeID ()
327 && CFStringGetCString ((CFStringRef)element,
328 buf, sizeof (buf),
329 kCFStringEncodingASCII))
330 {
331 strcpy (buf2, buf);
332 _nl_locale_name_canonicalize (buf);
333 strcpy (p, buf);
334 p += strlen (buf);
335 *p++ = ':';
336 {
337 char *last_minus = strrchr (buf2, '-');
338 if (last_minus != NULL)
339 {
340 *last_minus = '\0';
341 _nl_locale_name_canonicalize (buf2);
342 strcpy (p, buf2);
343 p += strlen (buf2);
344 *p++ = ':';
345 }
346 }
347 if (buf[0] == 'e' && buf[1] == 'n'
348 && (buf[2] == '\0' || buf[2] == '_'))
349 break;
350 }
351 else
352 break;
353 }
354 *--p = '\0';
355
356 cached_languages = languages;
357 }
358 }
359
360 # if HAVE_CFLOCALECOPYPREFERREDLANGUAGES /* MacOS X 10.5 or newer */
361 CFRelease (prefArray);
362 # elif HAVE_CFPREFERENCESCOPYAPPVALUE /* MacOS X 10.4 or newer */
363 }
364 # endif
365 cache_initialized = 1;
366 }
367 if (cached_languages != NULL)
368 return cached_languages;
369 }
370 #endif
371
372 #ifdef WIN32_NATIVE
373 {
374 /* Cache the preferences list, since computing it is expensive. */
375 static const char *cached_languages;
376 static int cache_initialized;
377
378 /* Activate the new code only when the GETTEXT_MUI environment variable is
379 set, for the time being, since the new code is not well tested. */
380 if (!cache_initialized && getenv ("GETTEXT_MUI") != NULL)
381 {
382 const char *languages = NULL;
383 HMODULE kernel32 = GetModuleHandle ("kernel32");
384
385 if (kernel32 != NULL)
386 languages = _nl_language_preferences_win32_mui (kernel32);
387
388 if (languages == NULL && kernel32 != NULL)
389 languages = _nl_language_preferences_win32_ME (kernel32);
390
391 if (languages == NULL)
392 languages = _nl_language_preferences_win32_95 ();
393
394 if (languages == NULL && kernel32 != NULL)
395 languages = _nl_language_preferences_win32_system (kernel32);
396
397 cached_languages = languages;
398 cache_initialized = 1;
399 }
400 if (cached_languages != NULL)
401 return cached_languages;
402 }
403 #endif
404
405 return NULL;
406 }
407