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
41static 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 ( buff[0] != '\0' );
53 }
54 return false;
55}
56
57static const char *
58langid_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 */
165static const char *ui_langs[MAX_LANGUAGES];
166static int num_ui_langs = 0;
167
168static 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
175static BOOL CALLBACK
176enum_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
187static bool
188present_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
201namespace {
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
207struct InstallLocalized {
208 char lang[sizeof("lll_Ssss_CC_vvvvvvvv")];
209 bool install;
210};
211
212void 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
237extern "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, 0) != 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, 0) == 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: */
#define TRUE
#define FALSE
void * p
#define PRIMARYLANGID(lgid)
#define LANG_SPANISH
#define SAL_N_ELEMENTS(arr)
int i
int sprintf(char(&s)[N], char const *format, T &&... arguments)
#define CALLBACK
const wchar_t *typedef BOOL
Definition: regactivex.cxx:38
static bool present_in_ui_langs(const char *lang)
Definition: sellang.cxx:188
static int num_ui_langs
Definition: sellang.cxx:166
__declspec(dllexport) UINT __stdcall SelectLanguage(MSIHANDLE handle)
Definition: sellang.cxx:237
static BOOL CALLBACK enum_ui_lang_proc(LPTSTR language, LONG_PTR)
Definition: sellang.cxx:176
static void add_ui_lang(char const *lang)
Definition: sellang.cxx:168
static bool GetMsiPropA(MSIHANDLE hMSI, const char *pPropName, char **ppValue)
Definition: sellang.cxx:41
static const char * langid_to_string(LANGID langid)
Definition: sellang.cxx:58
static const char * ui_langs[MAX_LANGUAGES]
Definition: sellang.cxx:165
#define MAX_LANGUAGES
Definition: sellang.cxx:23