xref: /core/vcl/source/app/salplug.cxx (revision fa8db25a)
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 #include <osl/module.hxx>
21 
22 #include <rtl/bootstrap.hxx>
23 #include <rtl/process.h>
24 #include <sal/log.hxx>
25 
26 #include <salframe.hxx>
27 #include <salinst.hxx>
28 #include <config_vclplug.h>
29 #include <desktop/crashreport.hxx>
30 
31 #ifndef _WIN32
32 #include <headless/svpinst.hxx>
33 #include <printerinfomanager.hxx>
34 #include <unx/desktops.hxx>
35 
36 #include <unistd.h>
37 #else
38 #include <saldatabasic.hxx>
39 #include <o3tl/char16_t2wchar_t.hxx>
40 #include <Windows.h>
41 #endif
42 
43 #include <cstdio>
44 
45 #ifdef ANDROID
46 #include <android/androidinst.hxx>
47 #endif
48 
49 #if USING_X11
50 #define DESKTOPDETECT
51 #endif
52 #if ENABLE_HEADLESS
53 #define HEADLESS_VCLPLUG
54 #endif
55 
56 extern "C" {
57 typedef SalInstance*(*salFactoryProc)();
58 }
59 
60 namespace {
61 
62 #ifndef DISABLE_DYNLOADING
63 oslModule pCloseModule = nullptr;
64 #endif
65 
66 SalInstance* tryInstance( const OUString& rModuleBase, bool bForce = false )
67 {
68 #ifdef DISABLE_DYNLOADING
69     (void)rModuleBase;
70     (void)bForce;
71     return create_SalInstance();
72 #else // !DISABLE_DYNLOADING
73 #ifdef HEADLESS_VCLPLUG
74     if (rModuleBase == "svp")
75         return svp_create_SalInstance();
76 #endif
77 
78     SalInstance* pInst = nullptr;
79     OUString aUsedModuleBase(rModuleBase);
80     if (aUsedModuleBase == "kde5")
81         aUsedModuleBase = "kf5";
82     OUString aModule(
83 #ifdef SAL_DLLPREFIX
84             SAL_DLLPREFIX
85 #endif
86             "vclplug_" + aUsedModuleBase + "lo" SAL_DLLEXTENSION );
87 
88     osl::Module aMod;
89     if (aMod.loadRelative(reinterpret_cast<oslGenericFunction>(&tryInstance), aModule, SAL_LOADMODULE_GLOBAL))
90     {
91         salFactoryProc aProc = reinterpret_cast<salFactoryProc>(aMod.getFunctionSymbol("create_SalInstance"));
92         if (aProc)
93         {
94             pInst = aProc();
95             SAL_INFO(
96                 "vcl.plugadapt",
97                 "sal plugin " << aModule << " produced instance " << pInst);
98             if (pInst)
99             {
100                 pCloseModule = static_cast<oslModule>(aMod);
101                 aMod.release();
102 
103                 /*
104                  * Recent GTK+ versions load their modules with RTLD_LOCAL, so we can
105                  * not access the 'gnome_accessibility_module_shutdown' anymore.
106                  * So make sure libgtk+ & co are still mapped into memory when
107                  * atk-bridge's atexit handler gets called.
108                  */
109                 if (aUsedModuleBase == "gtk4" || aUsedModuleBase == "gtk3" ||
110                     aUsedModuleBase == "gtk3_kde5" || aUsedModuleBase == "win")
111                 {
112                     pCloseModule = nullptr;
113                 }
114             }
115         }
116         else
117         {
118             SAL_WARN(
119                 "vcl.plugadapt",
120                 "could not load symbol create_SalInstance from shared object "
121                     << aModule);
122         }
123     }
124     else if (bForce)
125     {
126         SAL_WARN("vcl.plugadapt", "could not load shared object " << aModule);
127     }
128     else
129     {
130         SAL_INFO("vcl.plugadapt", "could not load shared object " << aModule);
131     }
132 
133     // coverity[leaked_storage] - this is on purpose
134     return pInst;
135 #endif // !DISABLE_DYNLOADING
136 }
137 
138 #ifdef DESKTOPDETECT
139 #ifndef DISABLE_DYNLOADING
140 extern "C" typedef DesktopType Fn_get_desktop_environment();
141 #endif
142 
143 DesktopType lcl_get_desktop_environment()
144 {
145     DesktopType ret = DESKTOP_UNKNOWN;
146 #ifdef DISABLE_DYNLOADING
147     ret = get_desktop_environment();
148 #else
149     OUString aModule(DESKTOP_DETECTOR_DLL_NAME);
150     oslModule aMod = osl_loadModuleRelative(
151         reinterpret_cast< oslGenericFunction >( &tryInstance ), aModule.pData,
152         SAL_LOADMODULE_DEFAULT );
153     if( aMod )
154     {
155         Fn_get_desktop_environment * pSym
156             = reinterpret_cast<Fn_get_desktop_environment *>(
157                 osl_getAsciiFunctionSymbol(aMod, "get_desktop_environment"));
158         if( pSym )
159             ret = pSym();
160     }
161     osl_unloadModule( aMod );
162 #endif
163     return ret;
164 }
165 
166 SalInstance* autodetect_plugin()
167 {
168 #ifdef DISABLE_DYNLOADING
169     return nullptr;
170 #else // !DISABLE_DYNLOADING
171     static const char* const pKDEFallbackList[] =
172     {
173 #if ENABLE_KF5
174         "kf5",
175 #endif
176 #if ENABLE_GTK3_KDE5
177         "gtk3_kde5",
178 #endif
179         "gtk3", "gen", nullptr
180     };
181 
182     static const char* const pStandardFallbackList[] =
183     {
184         "gtk3", "gen", nullptr
185     };
186 
187 #ifdef HEADLESS_VCLPLUG
188     static const char* const pHeadlessFallbackList[] =
189     {
190         "svp", nullptr
191     };
192 #endif
193 
194     SalInstance* pInst = nullptr;
195     DesktopType desktop = lcl_get_desktop_environment();
196     const char * const * pList = pStandardFallbackList;
197     int nListEntry = 0;
198 
199 #ifdef HEADLESS_VCLPLUG
200     // no server at all: dummy plugin
201     if ( desktop == DESKTOP_NONE )
202         pList = pHeadlessFallbackList;
203     else
204 #endif
205         if ( desktop == DESKTOP_GNOME ||
206               desktop == DESKTOP_UNITY ||
207               desktop == DESKTOP_XFCE  ||
208               desktop == DESKTOP_MATE )
209         pList = pStandardFallbackList;
210     else if (desktop == DESKTOP_PLASMA5 || desktop == DESKTOP_LXQT)
211         pList = pKDEFallbackList;
212 
213     while( pList[nListEntry] && pInst == nullptr )
214     {
215         OUString aTry( OUString::createFromAscii( pList[nListEntry] ) );
216         pInst = tryInstance( aTry );
217         SAL_INFO_IF(
218             pInst, "vcl.plugadapt",
219             "plugin autodetection: " << pList[nListEntry]);
220         nListEntry++;
221     }
222     return pInst;
223 #endif // !DISABLE_DYNLOADING
224 }
225 #endif // DESKTOPDETECT
226 
227 #ifdef HEADLESS_VCLPLUG
228 // HACK to obtain Application::IsHeadlessModeEnabled early on, before
229 // Application::EnableHeadlessMode has potentially been called:
230 bool IsHeadlessModeRequested()
231 {
232     if (Application::IsHeadlessModeEnabled()) {
233         return true;
234     }
235     sal_uInt32 n = rtl_getAppCommandArgCount();
236     for (sal_uInt32 i = 0; i < n; ++i) {
237         OUString arg;
238         rtl_getAppCommandArg(i, &arg.pData);
239         if ( arg == "--headless" || arg == "-headless" ) {
240             return true;
241         }
242     }
243     return false;
244 }
245 #endif
246 
247 } // anonymous namespace
248 
249 SalInstance *CreateSalInstance()
250 {
251     SalInstance *pInst = nullptr;
252     OUString aUsePlugin;
253     rtl::Bootstrap::get("SAL_USE_VCLPLUGIN", aUsePlugin);
254     SAL_INFO_IF(!aUsePlugin.isEmpty(), "vcl", "Requested VCL plugin: " << aUsePlugin);
255 #ifdef HEADLESS_VCLPLUG
256     if (Application::IsBitmapRendering() || (aUsePlugin.isEmpty() && IsHeadlessModeRequested()))
257         aUsePlugin = "svp";
258 #endif
259 
260     if (aUsePlugin == "svp")
261     {
262         Application::EnableBitmapRendering();
263 #ifndef HEADLESS_VCLPLUG
264         aUsePlugin.clear();
265 #endif
266     }
267 
268     if( !aUsePlugin.isEmpty() )
269         pInst = tryInstance( aUsePlugin, true );
270 
271 #ifdef DESKTOPDETECT
272     if( ! pInst )
273         pInst = autodetect_plugin();
274 #endif
275 
276     // fallback, try everything
277     static const char* const pPlugin[] = {
278 #ifdef _WIN32
279         "win"
280 #else
281 #ifdef MACOSX
282         "osx"
283 #else
284         "gtk3", "kf5", "gen"
285 #endif
286 #endif
287      };
288 
289     for ( int i = 0; !pInst && i != SAL_N_ELEMENTS(pPlugin); ++i )
290         pInst = tryInstance( OUString::createFromAscii( pPlugin[ i ] ) );
291 
292     if( ! pInst )
293     {
294         std::fprintf( stderr, "no suitable windowing system found, exiting.\n" );
295         _exit( 1 );
296     }
297 
298     // acquire SolarMutex
299     pInst->AcquireYieldMutex();
300 
301     return pInst;
302 }
303 
304 void DestroySalInstance( SalInstance *pInst )
305 {
306     // release SolarMutex
307     pInst->ReleaseYieldMutexAll();
308 
309     delete pInst;
310 #ifndef DISABLE_DYNLOADING
311     if( pCloseModule )
312         osl_unloadModule( pCloseModule );
313 #endif
314 }
315 
316 void SalAbort( const OUString& rErrorText, bool bDumpCore )
317 {
318     if (GetSalData()->m_pInstance)
319         GetSalData()->m_pInstance->BeforeAbort(rErrorText, bDumpCore);
320 
321 #if defined _WIN32
322     (void) bDumpCore;
323     if( rErrorText.isEmpty() )
324     {
325         // make sure crash reporter is triggered
326         RaiseException( 0, EXCEPTION_NONCONTINUABLE, 0, nullptr );
327         FatalAppExitW( 0, L"Application Error" );
328     }
329     else
330     {
331         CrashReporter::addKeyValue("AbortMessage", rErrorText, CrashReporter::Write);
332         // make sure crash reporter is triggered
333         RaiseException( 0, EXCEPTION_NONCONTINUABLE, 0, nullptr );
334         FatalAppExitW( 0, o3tl::toW(rErrorText.getStr()) );
335     }
336 #else
337 #if defined ANDROID
338     OUString aError(rErrorText.isEmpty() ? "Unspecified application error" : rErrorText);
339     LOGE("SalAbort: '%s'", OUStringToOString(aError, osl_getThreadTextEncoding()).getStr());
340 #else
341     if( rErrorText.isEmpty() )
342         std::fprintf( stderr, "Unspecified Application Error\n" );
343     else
344     {
345         CrashReporter::addKeyValue("AbortMessage", rErrorText, CrashReporter::Write);
346         std::fprintf( stderr, "%s\n", OUStringToOString(rErrorText, osl_getThreadTextEncoding()).getStr() );
347     }
348 #endif
349     if( bDumpCore )
350         abort();
351     else
352         _exit(1);
353 #endif
354 }
355 
356 const OUString& SalGetDesktopEnvironment()
357 {
358 #ifdef _WIN32
359     static OUString aDesktopEnvironment( "Windows" );
360 #elif defined(MACOSX)
361     static OUString aDesktopEnvironment( "MacOSX" );
362 #elif defined(EMSCRIPTEN)
363     static OUString aDesktopEnvironment("WASM");
364 #elif defined(ANDROID)
365     static OUString aDesktopEnvironment("android");
366 #elif USING_X11
367     // Order to match desktops.hxx' DesktopType
368     static const char * const desktop_strings[] = {
369         "none", "unknown", "GNOME", "UNITY",
370         "XFCE", "MATE", "PLASMA5", "LXQT" };
371     static OUString aDesktopEnvironment;
372     if( aDesktopEnvironment.isEmpty())
373     {
374         aDesktopEnvironment = OUString::createFromAscii(
375             desktop_strings[lcl_get_desktop_environment()]);
376     }
377 #else
378     static OUString aDesktopEnvironment("unknown");
379 #endif
380     return aDesktopEnvironment;
381 }
382 
383 SalData::SalData() :
384     m_pInstance(nullptr),
385     m_pPIManager(nullptr)
386 {
387 }
388 
389 SalData::~SalData() COVERITY_NOEXCEPT_FALSE
390 {
391 #if (defined UNX && !defined MACOSX)
392     psp::PrinterInfoManager::release();
393 #endif
394 }
395 
396 #ifdef _WIN32
397 bool HasAtHook()
398 {
399     BOOL bIsRunning = FALSE;
400     // pvParam must be BOOL
401     return SystemParametersInfoW(SPI_GETSCREENREADER, 0, &bIsRunning, 0)
402         && bIsRunning;
403 }
404 #endif
405 
406 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
407