Coverage Report

Created: 2026-10-14 09:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/app/salplug.cxx
Line
Count
Source
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
// The 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/DesktopType.hxx>
42
#include <vcl/svapp.hxx>
43
44
#if HAVE_FEATURE_UI
45
#if USING_X11
46
#define UNIX_DESKTOP_DETECT 1
47
#else
48
#define UNIX_DESKTOP_DETECT 0
49
#endif
50
#endif
51
52
#if defined(DISABLE_DYNLOADING) || !HAVE_FEATURE_UI
53
#define STATIC_SAL_INSTANCE 1
54
#include <staticsalinstance.hxx>
55
#else
56
#define STATIC_SAL_INSTANCE 0
57
#include <osl/module.hxx>
58
#endif
59
60
#if defined(iOS)
61
#include <premac.h>
62
#include <UIKit/UIKit.h>
63
#include <postmac.h>
64
65
#elif defined(ANDROID)
66
#include <android/androidinst.hxx>
67
#endif
68
69
#if defined(_WIN32)
70
#include <o3tl/char16_t2wchar_t.hxx>
71
#include <salframe.hxx>
72
#include <Windows.h>
73
#else
74
#include <unistd.h>
75
#endif
76
77
#if ENABLE_HEADLESS
78
#include <headless/svpinst.hxx>
79
#include <unx/gendata.hxx>
80
#endif
81
82
namespace {
83
84
#if ENABLE_HEADLESS
85
SalInstance* svp_create_SalInstance()
86
101
{
87
101
    return new SvpSalInstance(std::make_unique<SvpSalYieldMutex>(), new GenericUnixSalData);
88
101
}
89
#endif
90
91
#if HAVE_FEATURE_UI
92
93
#if !STATIC_SAL_INSTANCE
94
oslModule pCloseModule = nullptr;
95
96
extern "C" typedef SalInstance* (*salFactoryProc)();
97
98
SalInstance* tryInstance( const OUString& rModuleBase, bool bForce = false )
99
{
100
#if ENABLE_HEADLESS
101
    if (rModuleBase == "svp")
102
        return svp_create_SalInstance();
103
#endif
104
105
    SalInstance* pInst = nullptr;
106
    OUString aUsedModuleBase(rModuleBase);
107
    if (aUsedModuleBase == "kde5")
108
        aUsedModuleBase = "kf5";
109
    OUString aModule(
110
#ifdef SAL_DLLPREFIX
111
            SAL_DLLPREFIX
112
#endif
113
            "vclplug_" + aUsedModuleBase + "lo" SAL_DLLEXTENSION );
114
115
    osl::Module aMod;
116
    if (aMod.loadRelative(reinterpret_cast<oslGenericFunction>(&tryInstance), aModule, SAL_LOADMODULE_GLOBAL))
117
    {
118
        salFactoryProc aProc = reinterpret_cast<salFactoryProc>(aMod.getFunctionSymbol("create_SalInstance"));
119
        if (aProc)
120
        {
121
            pInst = aProc();
122
            SAL_INFO(
123
                "vcl.plugadapt",
124
                "sal plugin " << aModule << " produced instance " << pInst);
125
            if (pInst)
126
            {
127
                pCloseModule = static_cast<oslModule>(aMod);
128
                aMod.release();
129
130
                /*
131
                 * Recent GTK+ versions load their modules with RTLD_LOCAL, so we can
132
                 * not access the 'gnome_accessibility_module_shutdown' anymore.
133
                 * So make sure libgtk+ & co are still mapped into memory when
134
                 * atk-bridge's atexit handler gets called.
135
                 */
136
                if (aUsedModuleBase == "gtk4" || aUsedModuleBase == "gtk3" ||
137
                    aUsedModuleBase == "gtk3_kde5" || aUsedModuleBase == "kf5" ||
138
                    aUsedModuleBase == "kf6" ||
139
                    aUsedModuleBase == "qt5" || aUsedModuleBase == "qt6" ||
140
                    aUsedModuleBase == "win")
141
                {
142
                    pCloseModule = nullptr;
143
                }
144
            }
145
        }
146
        else
147
        {
148
            SAL_WARN(
149
                "vcl.plugadapt",
150
                "could not load symbol create_SalInstance from shared object "
151
                    << aModule);
152
        }
153
    }
154
    else if (bForce)
155
    {
156
        SAL_WARN("vcl.plugadapt", "could not load shared object " << aModule);
157
    }
158
    else
159
    {
160
        SAL_INFO("vcl.plugadapt", "could not load shared object " << aModule);
161
    }
162
163
    // coverity[leaked_storage] - this is on purpose
164
    return pInst;
165
}
166
#endif // !STATIC_SAL_INSTANCE
167
168
#if UNIX_DESKTOP_DETECT
169
170
#if !STATIC_SAL_INSTANCE
171
std::vector<OUString> autodetect_plugin_list()
172
{
173
    const DesktopType eDesktop = get_desktop_environment();
174
#if ENABLE_HEADLESS
175
    // no server at all: dummy plugin
176
    if (eDesktop == DesktopType::Headless)
177
        return { u"svp"_ustr };
178
#endif
179
180
    std::vector<OUString> aPlugins;
181
    if (eDesktop == DesktopType::LXQt || eDesktop == DesktopType::Plasma5 || eDesktop == DesktopType::Plasma6)
182
    {
183
#if ENABLE_KF6
184
        if (eDesktop == DesktopType::Plasma6)
185
            aPlugins.push_back(u"kf6"_ustr);
186
#endif
187
#if ENABLE_KF5
188
        aPlugins.push_back(u"kf5"_ustr);
189
#endif
190
#if ENABLE_GTK3_KDE5
191
        aPlugins.push_back(u"gtk3_kde5"_ustr);
192
#endif
193
    }
194
195
#if ENABLE_GTK3
196
    aPlugins.push_back(u"gtk3"_ustr);
197
#endif
198
#if ENABLE_GEN
199
    aPlugins.push_back(u"gen"_ustr);
200
#endif
201
202
    return aPlugins;
203
}
204
#endif // !STATIC_SAL_INSTANCE
205
#endif // UNIX_DESKTOP_DETECT
206
207
#endif // HAVE_FEATURE_UI
208
209
// HACK to obtain Application::IsHeadlessModeEnabled early on, before
210
// Application::EnableHeadlessMode has potentially been called:
211
bool IsHeadlessModeRequested()
212
0
{
213
0
    if (Application::IsHeadlessModeEnabled()) {
214
0
        return true;
215
0
    }
216
0
    sal_uInt32 n = rtl_getAppCommandArgCount();
217
0
    for (sal_uInt32 i = 0; i < n; ++i) {
218
0
        OUString arg;
219
0
        rtl_getAppCommandArg(i, &arg.pData);
220
0
        if ( arg == "--headless" || arg == "-headless" ) {
221
0
            return true;
222
0
        }
223
0
    }
224
0
    return false;
225
0
}
226
227
} // anonymous namespace
228
229
SalInstance *CreateSalInstance()
230
101
{
231
101
    OUString aUsePlugin;
232
101
    rtl::Bootstrap::get(u"SAL_USE_VCLPLUGIN"_ustr, aUsePlugin);
233
101
    SAL_INFO_IF(!aUsePlugin.isEmpty(), "vcl.plugadapt", "Requested VCL plugin: " << aUsePlugin);
234
235
101
    if (Application::IsBitmapRendering() || (aUsePlugin.isEmpty() && IsHeadlessModeRequested()))
236
0
        aUsePlugin = "svp";
237
238
101
    if (aUsePlugin == "svp")
239
101
    {
240
101
        Application::EnableBitmapRendering();
241
101
#if ENABLE_HEADLESS
242
101
        return svp_create_SalInstance();
243
#else
244
        aUsePlugin.clear();
245
#endif
246
101
    }
247
248
0
#if STATIC_SAL_INSTANCE
249
0
    return create_SalInstance();
250
251
#else // !STATIC_SAL_INSTANCE
252
253
    if( !aUsePlugin.isEmpty() )
254
    {
255
        if (SalInstance* pInst = tryInstance(aUsePlugin, true))
256
            return pInst;
257
    }
258
259
#if UNIX_DESKTOP_DETECT
260
    const std::vector<OUString> aPluginList = autodetect_plugin_list();
261
    for (const OUString& rPlugin : aPluginList)
262
    {
263
        SalInstance* pInst = tryInstance(rPlugin);
264
        SAL_INFO_IF(pInst, "vcl.plugadapt", "plugin autodetection: " << rPlugin);
265
        if (pInst)
266
            return pInst;
267
    }
268
#endif
269
270
    // fallback, try everything
271
    static constexpr OUString aPlugins[] = {
272
#if defined(MACOSX)
273
        u"osx"_ustr,
274
#endif
275
#if ENABLE_WIN
276
        u"win"_ustr,
277
#endif
278
#if ENABLE_GTK3
279
        u"gtk3"_ustr,
280
#endif
281
#if ENABLE_KF5
282
        u"kf5"_ustr,
283
#endif
284
#if ENABLE_GTK3_KDE5
285
        u"gtk3_kde5"_ustr,
286
#endif
287
#if ENABLE_GEN
288
        u"gen"_ustr,
289
#endif
290
#if ENABLE_QT5
291
        u"qt5"_ustr,
292
#endif
293
#if ENABLE_KF6
294
        u"kf6"_ustr,
295
#endif
296
#if ENABLE_QT6
297
        u"qt6"_ustr,
298
#endif
299
    };
300
301
    for (const OUString& rPlugin : aPlugins)
302
    {
303
        if (SalInstance* pInst = tryInstance(rPlugin))
304
            return pInst;
305
    }
306
307
    std::fprintf(stderr, "no suitable windowing system found, exiting.\n");
308
    _exit(1);
309
#endif // !STATIC_SAL_INSTANCE
310
101
}
311
312
void DestroySalInstance( SalInstance *pInst )
313
0
{
314
0
    delete pInst;
315
#if !STATIC_SAL_INSTANCE
316
    if( pCloseModule )
317
        osl_unloadModule( pCloseModule );
318
#endif
319
0
}
320
321
void SalAbort( const OUString& rErrorText, bool bDumpCore )
322
0
{
323
0
    if (GetSalInstance())
324
0
        GetSalInstance()->BeforeAbort();
325
326
#if defined _WIN32
327
    (void)bDumpCore;
328
    if( rErrorText.isEmpty() )
329
    {
330
        // make sure crash reporter is triggered
331
        RaiseException( 0, EXCEPTION_NONCONTINUABLE, 0, nullptr );
332
        FatalAppExitW( 0, L"Application Error" );
333
    }
334
    else
335
    {
336
        CrashReporter::addKeyValue("AbortMessage", rErrorText, CrashReporter::Write);
337
        // make sure crash reporter is triggered
338
        RaiseException( 0, EXCEPTION_NONCONTINUABLE, 0, nullptr );
339
        FatalAppExitW( 0, o3tl::toW(rErrorText.getStr()) );
340
    }
341
#else // !_WIN32
342
#if defined ANDROID
343
    OUString aError(rErrorText.isEmpty() ? "Unspecified application error" : rErrorText);
344
    LOGE("SalAbort: '%s'", OUStringToOString(aError, osl_getThreadTextEncoding()).getStr());
345
#elif defined(iOS)
346
    NSLog(@"SalAbort: %s", OUStringToOString(rErrorText, osl_getThreadTextEncoding()).getStr());
347
#else
348
0
    if( rErrorText.isEmpty() )
349
0
        std::fprintf( stderr, "Unspecified Application Error\n" );
350
0
    else
351
0
    {
352
0
        CrashReporter::addKeyValue(u"AbortMessage"_ustr, rErrorText, CrashReporter::Write);
353
0
        std::fprintf( stderr, "%s\n", OUStringToOString(rErrorText, osl_getThreadTextEncoding()).getStr() );
354
0
    }
355
0
#endif
356
0
    if( bDumpCore )
357
0
        abort();
358
0
    else
359
0
        _exit(1);
360
0
#endif // !_WIN32
361
0
}
362
363
DesktopType SalGetDesktopEnvironment()
364
0
{
365
0
#if !HAVE_FEATURE_UI
366
0
    return DesktopType::Headless;
367
#elif defined(_WIN32)
368
    return DesktopType::Windows;
369
#elif defined(MACOSX)
370
    return DesktopType::macOS;
371
#elif defined(__EMSCRIPTEN__)
372
    return DesktopType::WASM;
373
#elif defined(ANDROID)
374
    return DesktopType::Android;
375
#elif defined(iOS)
376
    return DesktopType::iOS;
377
#elif UNIX_DESKTOP_DETECT
378
    static DesktopType eDesktop = get_desktop_environment();
379
    return eDesktop;
380
#else
381
    return DesktopType::Unknown;
382
#endif
383
0
}
384
385
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */