Add -lm dependency for gettextlib to fix LTO build
[platform/upstream/gettext.git] / gettext-runtime / intl / langprefs.c
1 /* Determine the user's language preferences.
2    Copyright (C) 2004-2007, 2015 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 *
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 *
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 *
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
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 *
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 *
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 }