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