summaryrefslogtreecommitdiffstats
path: root/setup_native/source/win32/customactions/sellang
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /setup_native/source/win32/customactions/sellang
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'setup_native/source/win32/customactions/sellang')
-rw-r--r--setup_native/source/win32/customactions/sellang/sellang.cxx387
-rw-r--r--setup_native/source/win32/customactions/sellang/sellang.def4
-rw-r--r--setup_native/source/win32/customactions/sellang/sorttree.cxx55
3 files changed, 446 insertions, 0 deletions
diff --git a/setup_native/source/win32/customactions/sellang/sellang.cxx b/setup_native/source/win32/customactions/sellang/sellang.cxx
new file mode 100644
index 000000000..3154e1190
--- /dev/null
+++ b/setup_native/source/win32/customactions/sellang/sellang.cxx
@@ -0,0 +1,387 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/* Currently the "all" installer has a bit over 100 UI languages, and
+ * I doubt it will grow a lot over that.
+ */
+#define MAX_LANGUAGES 200
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <msiquery.h>
+#include <malloc.h>
+
+#include <cassert>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sal/macros.h>
+#include <systools/win32/uwinapi.h>
+#include <algorithm>
+
+#include <spellchecker_selection.hxx>
+
+static bool GetMsiPropA( MSIHANDLE hMSI, const char* pPropName, char** ppValue )
+{
+ DWORD sz = 0;
+ if ( MsiGetPropertyA( hMSI, pPropName, const_cast<char *>(""), &sz ) == ERROR_MORE_DATA ) {
+ sz++;
+ DWORD nbytes = sz * sizeof( char );
+ char* buff = static_cast<char*>( malloc( nbytes ) );
+ assert(buff); // Don't handle OOM conditions
+ ZeroMemory( buff, nbytes );
+ MsiGetPropertyA( hMSI, pPropName, buff, &sz );
+ *ppValue = buff;
+ return ( strlen(buff) > 0 );
+ }
+ return false;
+}
+
+static const char *
+langid_to_string( LANGID langid )
+{
+ /* Map from LANGID to string. The languages below are now in
+ * alphabetical order of codes as in
+ * l10ntools/source/ulfconv/msi-encodinglist.txt. Only the
+ * language part is returned in the string.
+ */
+ switch (PRIMARYLANGID (langid)) {
+ case LANG_AFRIKAANS: return "af";
+ case LANG_AMHARIC: return "am";
+ case LANG_ARABIC: return "ar";
+ case LANG_ASSAMESE: return "as";
+ case LANG_BELARUSIAN: return "be";
+ case LANG_BULGARIAN: return "bg";
+ case LANG_BENGALI: return "bn";
+ case LANG_BRETON: return "br";
+ case LANG_CATALAN: return "ca";
+ case LANG_CZECH: return "cs";
+ case LANG_WELSH: return "cy";
+ case LANG_DANISH: return "da";
+ case LANG_GERMAN: return "de";
+ case LANG_GREEK: return "el";
+ case LANG_ENGLISH: return "en";
+ case LANG_SPANISH: return "es";
+ case LANG_ESTONIAN: return "et";
+ case LANG_BASQUE: return "eu";
+ case LANG_FARSI: return "fa";
+ case LANG_FINNISH: return "fi";
+ case LANG_FAEROESE: return "fo";
+ case LANG_FRENCH: return "fr";
+ case LANG_IRISH: return "ga";
+ case LANG_GALICIAN: return "gl";
+ case LANG_GUJARATI: return "gu";
+ case LANG_HEBREW: return "he";
+ case LANG_HINDI: return "hi";
+ case LANG_HUNGARIAN: return "hu";
+ case LANG_ARMENIAN: return "hy";
+ case LANG_INDONESIAN: return "id";
+ case LANG_ICELANDIC: return "is";
+ case LANG_ITALIAN: return "it";
+ case LANG_JAPANESE: return "ja";
+ case LANG_GEORGIAN: return "ka";
+ case LANG_KAZAK: return "kk";
+ case LANG_KHMER: return "km";
+ case LANG_KANNADA: return "kn";
+ case LANG_KOREAN: return "ko";
+ case LANG_KASHMIRI: return "ks";
+ case LANG_LAO: return "lo";
+ case LANG_LITHUANIAN: return "lt";
+ case LANG_LATVIAN: return "lv";
+ case LANG_MACEDONIAN: return "mk";
+ case LANG_MALAYALAM: return "ml";
+ case LANG_MONGOLIAN: return "mn";
+ case LANG_MARATHI: return "mr";
+ case LANG_MALAY: return "ms";
+ case LANG_MALTESE: return "mt";
+ case LANG_NEPALI: return "ne";
+ case LANG_DUTCH: return "nl";
+ case LANG_SOTHO: return "ns";
+ case LANG_ORIYA: return "or";
+ case LANG_PUNJABI: return "pa";
+ case LANG_POLISH: return "pl";
+ case LANG_PORTUGUESE: return "pt";
+ case LANG_ROMANSH: return "rm";
+ case LANG_ROMANIAN: return "ro";
+ case LANG_RUSSIAN: return "ru";
+ case LANG_KINYARWANDA: return "rw";
+ case LANG_SANSKRIT: return "sa";
+ case LANG_UPPER_SORBIAN: return "sb";
+ case LANG_SINDHI: return "sd";
+ case LANG_SLOVAK: return "sk";
+ case LANG_SLOVENIAN: return "sl";
+ case LANG_ALBANIAN: return "sq";
+ case LANG_SWEDISH: return "sv";
+ case LANG_SWAHILI: return "sw";
+ case LANG_TAMIL: return "ta";
+ case LANG_TELUGU: return "te";
+ case LANG_TAJIK: return "tg";
+ case LANG_THAI: return "th";
+ case LANG_TIGRIGNA: return "ti";
+ case LANG_TSWANA: return "tn";
+ case LANG_TURKISH: return "tr";
+ case LANG_TATAR: return "tt";
+ case LANG_UKRAINIAN: return "uk";
+ case LANG_URDU: return "ur";
+ case LANG_UZBEK: return "uz";
+ case LANG_VIETNAMESE: return "vi";
+ case LANG_XHOSA: return "xh";
+ case LANG_CHINESE: return "zh";
+ case LANG_ZULU: return "zu";
+ /* Special cases */
+ default:
+ switch (langid) {
+ case MAKELANGID(LANG_SERBIAN, 0x05): return "bs";
+ case MAKELANGID(LANG_SERBIAN, SUBLANG_DEFAULT): return "hr";
+ case MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL): return "nb";
+ case MAKELANGID(LANG_NORWEGIAN, SUBLANG_NORWEGIAN_NYNORSK): return "nn";
+ case MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_LATIN): return "sh";
+ case MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_CYRILLIC): return "sr";
+ default: return nullptr;
+ }
+ }
+}
+
+/* Here we collect the UI languages present on the system;
+ * MAX_LANGUAGES is certainly enough for that
+ */
+static const char *ui_langs[MAX_LANGUAGES];
+static int num_ui_langs = 0;
+
+static void add_ui_lang(char const * lang)
+{
+ if (lang != nullptr && num_ui_langs != SAL_N_ELEMENTS(ui_langs)) {
+ ui_langs[num_ui_langs++] = lang;
+ }
+}
+
+static BOOL CALLBACK
+enum_ui_lang_proc (LPTSTR language, LONG_PTR /* unused_lParam */)
+{
+ long langid = strtol(language, nullptr, 16);
+ if (langid > 0xFFFF)
+ return TRUE;
+ add_ui_lang(langid_to_string(static_cast<LANGID>(langid)));
+ if (num_ui_langs == SAL_N_ELEMENTS(ui_langs) )
+ return FALSE;
+ return TRUE;
+}
+
+static bool
+present_in_ui_langs(const char *lang)
+{
+ for (int i = 0; i < num_ui_langs; i++)
+ {
+ if (strchr (lang, '_') != nullptr)
+ if (memcmp (ui_langs[i], lang, std::min(strlen(ui_langs[i]), strlen(lang))) == 0)
+ return true;
+ if (strcmp (ui_langs[i], lang) == 0)
+ return true;
+ }
+ return false;
+}
+
+namespace {
+
+/* TODO-BCP47: unlimit this, and if possible change from '_' to '-' separator
+ * to ease things. */
+// XXX NOTE: the sizeof needs to follow what is accepted in
+// setup_native/source/packinfo/spellchecker_selection.pl
+struct InstallLocalized {
+ char lang[sizeof("lll_Ssss_CC_vvvvvvvv")];
+ bool install;
+};
+
+void addMatchingDictionaries(
+ char const * lang, InstallLocalized * dicts, int ndicts)
+{
+ for (int i = 0; i != SAL_N_ELEMENTS(setup_native::languageDictionaries);
+ ++i)
+ {
+ if (strcmp(lang, setup_native::languageDictionaries[i].language) == 0) {
+ for (char const * const * p = setup_native::languageDictionaries[i].
+ dictionaries;
+ *p != nullptr; ++p)
+ {
+ for (int j = 0; j != ndicts; ++j) {
+ if (_stricmp(*p, dicts[j].lang) == 0) {
+ dicts[j].install = true;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+}
+
+}
+
+extern "C" __declspec(dllexport) UINT __stdcall SelectLanguage( MSIHANDLE handle )
+{
+ char feature[100];
+ MSIHANDLE database, view, record;
+ DWORD length;
+ int nlangs = 0;
+ InstallLocalized langs[MAX_LANGUAGES];
+ int ndicts = 0;
+ InstallLocalized dicts[MAX_LANGUAGES];
+
+ database = MsiGetActiveDatabase(handle);
+
+ if (MsiDatabaseOpenViewA(database, "SELECT Feature from Feature WHERE Feature_Parent = 'gm_Langpack_Languageroot'", &view) != ERROR_SUCCESS) {
+ MsiCloseHandle(database);
+ return ERROR_SUCCESS;
+ }
+
+ if (MsiViewExecute(view, NULL) != ERROR_SUCCESS) {
+ MsiCloseHandle(view);
+ MsiCloseHandle(database);
+ return ERROR_SUCCESS;
+ }
+
+ while (nlangs < MAX_LANGUAGES &&
+ MsiViewFetch(view, &record) == ERROR_SUCCESS) {
+ length = sizeof(feature);
+ if (MsiRecordGetStringA(record, 1, feature, &length) != ERROR_SUCCESS) {
+ MsiCloseHandle(record);
+ MsiCloseHandle(view);
+ MsiCloseHandle(database);
+ return ERROR_SUCCESS;
+ }
+
+ /* Keep track of what langpacks are included in this installer.
+ */
+ strcpy(langs[nlangs].lang, feature + strlen("gm_Langpack_r_"));
+ langs[nlangs].install = false;
+ ++nlangs;
+
+ MsiCloseHandle(record);
+ }
+
+ MsiCloseHandle(view);
+
+ /* Keep track of what dictionaries are included in this installer:
+ */
+ if (MsiDatabaseOpenViewA(
+ database,
+ ("SELECT Feature from Feature WHERE"
+ " Feature_Parent = 'gm_Dictionaries'"),
+ &view)
+ == ERROR_SUCCESS)
+ {
+ if (MsiViewExecute(view, NULL) == ERROR_SUCCESS) {
+ while (ndicts < MAX_LANGUAGES &&
+ MsiViewFetch(view, &record) == ERROR_SUCCESS)
+ {
+ length = sizeof(feature);
+ if (MsiRecordGetStringA(record, 1, feature, &length)
+ == ERROR_SUCCESS)
+ {
+ if (strncmp(
+ feature, "gm_r_ex_Dictionary_",
+ strlen("gm_r_ex_Dictionary_"))
+ == 0)
+ {
+ strcpy(
+ dicts[ndicts].lang,
+ feature + strlen("gm_r_ex_Dictionary_"));
+ dicts[ndicts].install = false;
+ ++ndicts;
+ }
+ }
+ MsiCloseHandle(record);
+ }
+ }
+ MsiCloseHandle(view);
+ }
+
+ /* Keep track of what UI languages are relevant, either the ones explicitly
+ * requested with the UI_LANGS property, or all available on the system:
+ */
+ char* pVal = nullptr;
+ if ( (GetMsiPropA( handle, "UI_LANGS", &pVal )) && pVal ) {
+ char *str_ptr;
+ str_ptr = strtok(pVal, ",");
+ for(; str_ptr != nullptr ;) {
+ add_ui_lang(str_ptr);
+ str_ptr = strtok(nullptr, ",");
+ }
+ } else {
+ add_ui_lang(langid_to_string(GetSystemDefaultUILanguage()));
+ add_ui_lang(langid_to_string(LANGIDFROMLCID(GetThreadLocale())));
+ //TODO: are the above two explicit additions necessary, or will
+ // those values always be included in the below EnumUILanguages
+ // anyway?
+ EnumUILanguagesA(enum_ui_lang_proc, 0, 0);
+ }
+
+ // If the set of langpacks that match any of the relevant UI languages is
+ // non-empty, select just those matching langpacks; otherwise, if an en_US
+ // langpack is included, select just that langpack (this happens if, e.g.,
+ // a multi-language en-US,de,es,fr,it,pt-BR installation set is installed on
+ // a Finnish Windows); otherwise, select all langpacks (this happens if,
+ // e.g., a single-language de installation set is installed on a Finnish
+ // Windows):
+ bool matches = false;
+ for (int i = 0; i < nlangs; i++) {
+ if (present_in_ui_langs(langs[i].lang)) {
+ langs[i].install = true;
+ matches = true;
+ }
+ }
+ if (!matches) {
+ for (int i = 0; i < nlangs; i++) {
+ if (strcmp(langs[i].lang, "en_US") == 0) {
+ langs[i].install = true;
+ matches = true;
+ break;
+ }
+ }
+ if (!matches) {
+ for (int i = 0; i < nlangs; i++) {
+ langs[i].install = true;
+ }
+ }
+ }
+
+ for (int i = 0; i < nlangs; i++) {
+ if (langs[i].install) {
+ addMatchingDictionaries(langs[i].lang, dicts, ndicts);
+ } else {
+ sprintf(feature, "gm_Langpack_r_%s", langs[i].lang);
+ MsiSetFeatureStateA(handle, feature, INSTALLSTATE_ABSENT);
+ }
+ }
+
+ // Select just those dictionaries that match any of the selected langpacks:
+ for (int i = 0; i != ndicts; ++i) {
+ if (!dicts[i].install) {
+ sprintf(feature, "gm_r_ex_Dictionary_%s", dicts[i].lang);
+ MsiSetFeatureStateA(handle, feature, INSTALLSTATE_ABSENT);
+ }
+ }
+
+ MsiCloseHandle(database);
+
+ return ERROR_SUCCESS;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/setup_native/source/win32/customactions/sellang/sellang.def b/setup_native/source/win32/customactions/sellang/sellang.def
new file mode 100644
index 000000000..8d3c4d44c
--- /dev/null
+++ b/setup_native/source/win32/customactions/sellang/sellang.def
@@ -0,0 +1,4 @@
+LIBRARY "sellangmsi.dll"
+EXPORTS
+ SelectLanguage
+ SortTree \ No newline at end of file
diff --git a/setup_native/source/win32/customactions/sellang/sorttree.cxx b/setup_native/source/win32/customactions/sellang/sorttree.cxx
new file mode 100644
index 000000000..cb44675b9
--- /dev/null
+++ b/setup_native/source/win32/customactions/sellang/sorttree.cxx
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <msi.h>
+#include <commctrl.h>
+
+extern "C" __declspec(dllexport) UINT __stdcall SortTree(MSIHANDLE)
+{
+ // Sort items (languages) in SelectionTree control, fdo#46355
+
+ HWND hwndMSI = FindWindowW(L"MsiDialogCloseClass", nullptr);
+ if (hwndMSI == nullptr)
+ {
+ OutputDebugStringW(L"SortTree: MsiDialogCloseClass not found\n");
+ return ERROR_SUCCESS;
+ }
+ HWND hwndTV = FindWindowExW(hwndMSI, nullptr, L"SysTreeView32", nullptr);
+ if (hwndTV == nullptr)
+ {
+ OutputDebugStringW(L"SortTree: SysTreeView32 not found\n");
+ return ERROR_SUCCESS;
+ }
+ HTREEITEM optional = TreeView_GetRoot(hwndTV);
+ if (optional == nullptr)
+ {
+ OutputDebugStringW(L"SortTree: Optional Components branch not found\n");
+ return ERROR_SUCCESS;
+ }
+ HTREEITEM dicts = TreeView_GetChild(hwndTV, optional);
+ if (dicts == nullptr)
+ {
+ OutputDebugStringW(L"SortTree: Dictionaries branch not found\n");
+ return ERROR_SUCCESS;
+ }
+ TreeView_SortChildren(hwndTV, dicts, TRUE);
+ HTREEITEM langs = TreeView_GetNextSibling(hwndTV, optional);
+ if (langs == nullptr)
+ {
+ OutputDebugStringW(L"SortTree: Additional UI Languages branch not found\n");
+ return ERROR_SUCCESS;
+ }
+ TreeView_SortChildren(hwndTV, langs, TRUE);
+
+ return ERROR_SUCCESS;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */