LibreOffice Module setup_native (master)  1
sellang.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  * Licensed to the Apache Software Foundation (ASF) under one or more
12  * contributor license agreements. See the NOTICE file distributed
13  * with this work for additional information regarding copyright
14  * ownership. The ASF licenses this file to you under the Apache
15  * License, Version 2.0 (the "License"); you may not use this file
16  * except in compliance with the License. You may obtain a copy of
17  * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 /* Currently the "all" installer has a bit over 100 UI languages, and
21  * I doubt it will grow a lot over that.
22  */
23 #define MAX_LANGUAGES 200
24 
25 #define WIN32_LEAN_AND_MEAN
26 #include <windows.h>
27 #include <msiquery.h>
28 #include <malloc.h>
29 
30 #include <cassert>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 
35 #include <sal/macros.h>
36 #include <systools/win32/uwinapi.h>
37 #include <algorithm>
38 
39 #include <spellchecker_selection.hxx>
40 
41 static bool GetMsiPropA( MSIHANDLE hMSI, const char* pPropName, char** ppValue )
42 {
43  DWORD sz = 0;
44  if ( MsiGetPropertyA( hMSI, pPropName, const_cast<char *>(""), &sz ) == ERROR_MORE_DATA ) {
45  sz++;
46  DWORD nbytes = sz * sizeof( char );
47  char* buff = static_cast<char*>( malloc( nbytes ) );
48  assert(buff); // Don't handle OOM conditions
49  ZeroMemory( buff, nbytes );
50  MsiGetPropertyA( hMSI, pPropName, buff, &sz );
51  *ppValue = buff;
52  return ( strlen(buff) > 0 );
53  }
54  return false;
55 }
56 
57 static const char *
58 langid_to_string( LANGID langid )
59 {
60  /* Map from LANGID to string. The languages below are now in
61  * alphabetical order of codes as in
62  * l10ntools/source/ulfconv/msi-encodinglist.txt. Only the
63  * language part is returned in the string.
64  */
65  switch (PRIMARYLANGID (langid)) {
66  case LANG_AFRIKAANS: return "af";
67  case LANG_AMHARIC: return "am";
68  case LANG_ARABIC: return "ar";
69  case LANG_ASSAMESE: return "as";
70  case LANG_BELARUSIAN: return "be";
71  case LANG_BULGARIAN: return "bg";
72  case LANG_BENGALI: return "bn";
73  case LANG_BRETON: return "br";
74  case LANG_CATALAN: return "ca";
75  case LANG_CZECH: return "cs";
76  case LANG_WELSH: return "cy";
77  case LANG_DANISH: return "da";
78  case LANG_GERMAN: return "de";
79  case LANG_GREEK: return "el";
80  case LANG_ENGLISH: return "en";
81  case LANG_SPANISH: return "es";
82  case LANG_ESTONIAN: return "et";
83  case LANG_BASQUE: return "eu";
84  case LANG_FARSI: return "fa";
85  case LANG_FINNISH: return "fi";
86  case LANG_FAEROESE: return "fo";
87  case LANG_FRENCH: return "fr";
88  case LANG_IRISH: return "ga";
89  case LANG_GALICIAN: return "gl";
90  case LANG_GUJARATI: return "gu";
91  case LANG_HEBREW: return "he";
92  case LANG_HINDI: return "hi";
93  case LANG_HUNGARIAN: return "hu";
94  case LANG_ARMENIAN: return "hy";
95  case LANG_INDONESIAN: return "id";
96  case LANG_ICELANDIC: return "is";
97  case LANG_ITALIAN: return "it";
98  case LANG_JAPANESE: return "ja";
99  case LANG_GEORGIAN: return "ka";
100  case LANG_KAZAK: return "kk";
101  case LANG_KHMER: return "km";
102  case LANG_KANNADA: return "kn";
103  case LANG_KOREAN: return "ko";
104  case LANG_KASHMIRI: return "ks";
105  case LANG_LAO: return "lo";
106  case LANG_LITHUANIAN: return "lt";
107  case LANG_LATVIAN: return "lv";
108  case LANG_MACEDONIAN: return "mk";
109  case LANG_MALAYALAM: return "ml";
110  case LANG_MONGOLIAN: return "mn";
111  case LANG_MARATHI: return "mr";
112  case LANG_MALAY: return "ms";
113  case LANG_MALTESE: return "mt";
114  case LANG_NEPALI: return "ne";
115  case LANG_DUTCH: return "nl";
116  case LANG_SOTHO: return "ns";
117  case LANG_ORIYA: return "or";
118  case LANG_PUNJABI: return "pa";
119  case LANG_POLISH: return "pl";
120  case LANG_PORTUGUESE: return "pt";
121  case LANG_ROMANSH: return "rm";
122  case LANG_ROMANIAN: return "ro";
123  case LANG_RUSSIAN: return "ru";
124  case LANG_KINYARWANDA: return "rw";
125  case LANG_SANSKRIT: return "sa";
126  case LANG_UPPER_SORBIAN: return "sb";
127  case LANG_SINDHI: return "sd";
128  case LANG_SLOVAK: return "sk";
129  case LANG_SLOVENIAN: return "sl";
130  case LANG_ALBANIAN: return "sq";
131  case LANG_SWEDISH: return "sv";
132  case LANG_SWAHILI: return "sw";
133  case LANG_TAMIL: return "ta";
134  case LANG_TELUGU: return "te";
135  case LANG_TAJIK: return "tg";
136  case LANG_THAI: return "th";
137  case LANG_TIGRIGNA: return "ti";
138  case LANG_TSWANA: return "tn";
139  case LANG_TURKISH: return "tr";
140  case LANG_TATAR: return "tt";
141  case LANG_UKRAINIAN: return "uk";
142  case LANG_URDU: return "ur";
143  case LANG_UZBEK: return "uz";
144  case LANG_VIETNAMESE: return "vi";
145  case LANG_XHOSA: return "xh";
146  case LANG_CHINESE: return "zh";
147  case LANG_ZULU: return "zu";
148  /* Special cases */
149  default:
150  switch (langid) {
151  case MAKELANGID(LANG_SERBIAN, 0x05): return "bs";
152  case MAKELANGID(LANG_SERBIAN, SUBLANG_DEFAULT): return "hr";
153  case MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL): return "nb";
154  case MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_NYNORSK): return "nn";
155  case MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_LATIN): return "sh";
156  case MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_CYRILLIC): return "sr";
157  default: return nullptr;
158  }
159  }
160 }
161 
162 /* Here we collect the UI languages present on the system;
163  * MAX_LANGUAGES is certainly enough for that
164  */
165 static const char *ui_langs[MAX_LANGUAGES];
166 static int num_ui_langs = 0;
167 
168 static void add_ui_lang(char const * lang)
169 {
170  if (lang != nullptr && num_ui_langs != SAL_N_ELEMENTS(ui_langs)) {
171  ui_langs[num_ui_langs++] = lang;
172  }
173 }
174 
175 static BOOL CALLBACK
176 enum_ui_lang_proc (LPTSTR language, LONG_PTR /* unused_lParam */)
177 {
178  long langid = strtol(language, nullptr, 16);
179  if (langid > 0xFFFF)
180  return TRUE;
181  add_ui_lang(langid_to_string(static_cast<LANGID>(langid)));
183  return FALSE;
184  return TRUE;
185 }
186 
187 static bool
188 present_in_ui_langs(const char *lang)
189 {
190  for (int i = 0; i < num_ui_langs; i++)
191  {
192  if (strchr (lang, '_') != nullptr)
193  if (memcmp (ui_langs[i], lang, std::min(strlen(ui_langs[i]), strlen(lang))) == 0)
194  return true;
195  if (strcmp (ui_langs[i], lang) == 0)
196  return true;
197  }
198  return false;
199 }
200 
201 namespace {
202 
203 /* TODO-BCP47: unlimit this, and if possible change from '_' to '-' separator
204  * to ease things. */
205 // XXX NOTE: the sizeof needs to follow what is accepted in
206 // setup_native/source/packinfo/spellchecker_selection.pl
207 struct InstallLocalized {
208  char lang[sizeof("lll_Ssss_CC_vvvvvvvv")];
209  bool install;
210 };
211 
212 void addMatchingDictionaries(
213  char const * lang, InstallLocalized * dicts, int ndicts)
214 {
215  for (int i = 0; i != SAL_N_ELEMENTS(setup_native::languageDictionaries);
216  ++i)
217  {
218  if (strcmp(lang, setup_native::languageDictionaries[i].language) == 0) {
219  for (char const * const * p = setup_native::languageDictionaries[i].
220  dictionaries;
221  *p != nullptr; ++p)
222  {
223  for (int j = 0; j != ndicts; ++j) {
224  if (_stricmp(*p, dicts[j].lang) == 0) {
225  dicts[j].install = true;
226  break;
227  }
228  }
229  }
230  break;
231  }
232  }
233 }
234 
235 }
236 
237 extern "C" __declspec(dllexport) UINT __stdcall SelectLanguage( MSIHANDLE handle )
238 {
239  char feature[100];
240  MSIHANDLE database, view, record;
241  DWORD length;
242  int nlangs = 0;
243  InstallLocalized langs[MAX_LANGUAGES];
244  int ndicts = 0;
245  InstallLocalized dicts[MAX_LANGUAGES];
246 
247  database = MsiGetActiveDatabase(handle);
248 
249  if (MsiDatabaseOpenViewA(database, "SELECT Feature from Feature WHERE Feature_Parent = 'gm_Langpack_Languageroot'", &view) != ERROR_SUCCESS) {
250  MsiCloseHandle(database);
251  return ERROR_SUCCESS;
252  }
253 
254  if (MsiViewExecute(view, NULL) != ERROR_SUCCESS) {
255  MsiCloseHandle(view);
256  MsiCloseHandle(database);
257  return ERROR_SUCCESS;
258  }
259 
260  while (nlangs < MAX_LANGUAGES &&
261  MsiViewFetch(view, &record) == ERROR_SUCCESS) {
262  length = sizeof(feature);
263  if (MsiRecordGetStringA(record, 1, feature, &length) != ERROR_SUCCESS) {
264  MsiCloseHandle(record);
265  MsiCloseHandle(view);
266  MsiCloseHandle(database);
267  return ERROR_SUCCESS;
268  }
269 
270  /* Keep track of what langpacks are included in this installer.
271  */
272  strcpy(langs[nlangs].lang, feature + strlen("gm_Langpack_r_"));
273  langs[nlangs].install = false;
274  ++nlangs;
275 
276  MsiCloseHandle(record);
277  }
278 
279  MsiCloseHandle(view);
280 
281  /* Keep track of what dictionaries are included in this installer:
282  */
283  if (MsiDatabaseOpenViewA(
284  database,
285  ("SELECT Feature from Feature WHERE"
286  " Feature_Parent = 'gm_Dictionaries'"),
287  &view)
288  == ERROR_SUCCESS)
289  {
290  if (MsiViewExecute(view, NULL) == ERROR_SUCCESS) {
291  while (ndicts < MAX_LANGUAGES &&
292  MsiViewFetch(view, &record) == ERROR_SUCCESS)
293  {
294  length = sizeof(feature);
295  if (MsiRecordGetStringA(record, 1, feature, &length)
296  == ERROR_SUCCESS)
297  {
298  if (strncmp(
299  feature, "gm_r_ex_Dictionary_",
300  strlen("gm_r_ex_Dictionary_"))
301  == 0)
302  {
303  strcpy(
304  dicts[ndicts].lang,
305  feature + strlen("gm_r_ex_Dictionary_"));
306  dicts[ndicts].install = false;
307  ++ndicts;
308  }
309  }
310  MsiCloseHandle(record);
311  }
312  }
313  MsiCloseHandle(view);
314  }
315 
316  /* Keep track of what UI languages are relevant, either the ones explicitly
317  * requested with the UI_LANGS property, or all available on the system:
318  */
319  char* pVal = nullptr;
320  if ( (GetMsiPropA( handle, "UI_LANGS", &pVal )) && pVal ) {
321  char *str_ptr;
322  str_ptr = strtok(pVal, ",");
323  for(; str_ptr != nullptr ;) {
324  add_ui_lang(str_ptr);
325  str_ptr = strtok(nullptr, ",");
326  }
327  } else {
328  add_ui_lang(langid_to_string(GetSystemDefaultUILanguage()));
329  add_ui_lang(langid_to_string(LANGIDFROMLCID(GetThreadLocale())));
330  //TODO: are the above two explicit additions necessary, or will
331  // those values always be included in the below EnumUILanguages
332  // anyway?
333  if (GetMsiPropA(handle, "ProductLanguage", &pVal))
334  {
335  // This addition might refer to a language without an installed system language pack
336  // If the installer is run in this language, then this language is likely needed
337  long langid = strtol(pVal, nullptr, 10);
338  if (langid > 0xFFFF)
339  return TRUE;
340  add_ui_lang(langid_to_string(static_cast<LANGID>(langid)));
341  }
342  EnumUILanguagesA(enum_ui_lang_proc, 0, 0);
343  }
344 
345  // If the set of langpacks that match any of the relevant UI languages is
346  // non-empty, select just those matching langpacks; otherwise, if an en_US
347  // langpack is included, select just that langpack (this happens if, e.g.,
348  // a multi-language en-US,de,es,fr,it,pt-BR installation set is installed on
349  // a Finnish Windows); otherwise, select all langpacks (this happens if,
350  // e.g., a single-language de installation set is installed on a Finnish
351  // Windows):
352  bool matches = false;
353  for (int i = 0; i < nlangs; i++) {
354  if (present_in_ui_langs(langs[i].lang)) {
355  langs[i].install = true;
356  matches = true;
357  }
358  }
359  if (!matches) {
360  for (int i = 0; i < nlangs; i++) {
361  if (strcmp(langs[i].lang, "en_US") == 0) {
362  langs[i].install = true;
363  matches = true;
364  break;
365  }
366  }
367  if (!matches) {
368  for (int i = 0; i < nlangs; i++) {
369  langs[i].install = true;
370  }
371  }
372  }
373 
374  for (int i = 0; i < nlangs; i++) {
375  if (langs[i].install) {
376  addMatchingDictionaries(langs[i].lang, dicts, ndicts);
377  } else {
378  sprintf(feature, "gm_Langpack_r_%s", langs[i].lang);
379  MsiSetFeatureStateA(handle, feature, INSTALLSTATE_ABSENT);
380  }
381  }
382 
383  // Select just those dictionaries that match any of the selected langpacks:
384  for (int i = 0; i != ndicts; ++i) {
385  if (!dicts[i].install) {
386  sprintf(feature, "gm_r_ex_Dictionary_%s", dicts[i].lang);
387  MsiSetFeatureStateA(handle, feature, INSTALLSTATE_ABSENT);
388  }
389  }
390 
391  MsiCloseHandle(database);
392 
393  return ERROR_SUCCESS;
394 }
395 
396 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
static bool GetMsiPropA(MSIHANDLE hMSI, const char *pPropName, char **ppValue)
Definition: sellang.cxx:41
static bool present_in_ui_langs(const char *lang)
Definition: sellang.cxx:188
return NULL
const BorderLinePrimitive2D *pCandidateB assert(pCandidateA)
const wchar_t *typedef BOOL
Definition: regactivex.cxx:38
length
static BOOL CALLBACK enum_ui_lang_proc(LPTSTR language, LONG_PTR)
Definition: sellang.cxx:176
__declspec(dllexport) UINT __stdcall SelectLanguage(MSIHANDLE handle)
Definition: sellang.cxx:237
#define SAL_N_ELEMENTS(arr)
static int num_ui_langs
Definition: sellang.cxx:166
#define TRUE
int i
exports com.sun.star. view
static const char * langid_to_string(LANGID langid)
Definition: sellang.cxx:58
#define PRIMARYLANGID(lgid)
#define LANG_SPANISH
exports com.sun.star. lang
void * p
#define MAX_LANGUAGES
Definition: sellang.cxx:23
#define FALSE
#define CALLBACK
static void add_ui_lang(char const *lang)
Definition: sellang.cxx:168
static const char * ui_langs[MAX_LANGUAGES]
Definition: sellang.cxx:165