startplasma.cpp 15.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* This file is part of the KDE project
   Copyright (C) 2019 Aleix Pol Gonzalez <aleixpol@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include <QDir>
#include <QProcess>
#include <QStandardPaths>
#include <QTextStream>
#include <QDBusConnectionInterface>
25
#include <KConfig>
26
27
28
29
30
31
32
33
34
35
36
#include <KConfigGroup>

#include <unistd.h>

#include "startplasma.h"

QTextStream out(stderr);

void messageBox(const QString &text)
{
    out << text;
37
    runSync(QStringLiteral("xmessage"), {QStringLiteral("-geometry"), QStringLiteral("500x100"), text});
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
}

QStringList allServices(const QLatin1String& prefix)
{
    QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface();
    const QStringList services = bus->registeredServiceNames();
    QMap<QString, QStringList> servicesWithAliases;

    for (const QString &serviceName : services) {
        QDBusReply<QString> reply = bus->serviceOwner(serviceName);
        QString owner = reply;
        if (owner.isEmpty())
            owner = serviceName;
        servicesWithAliases[owner].append(serviceName);
    }

    QStringList names;
    for (auto it = servicesWithAliases.constBegin(); it != servicesWithAliases.constEnd(); ++it) {
        if (it.value().startsWith(prefix))
            names << it.value();
    }
    names.removeDuplicates();
    names.sort();
    return names;
}

int runSync(const QString& program, const QStringList &args, const QStringList &env)
{
    QProcess p;
    if (!env.isEmpty())
        p.setEnvironment(QProcess::systemEnvironment() << env);
    p.setProcessChannelMode(QProcess::ForwardedChannels);
    p.start(program, args);
71
//     qDebug() << "started..." << program << args;
72
    p.waitForFinished(-1);
73
    if (p.exitCode()) {
74
        qWarning() << program << args << "exited with code" << p.exitCode();
75
    }
76
77
78
79
80
81
82
83
84
85
86
    return p.exitCode();
}

void sourceFiles(const QStringList &files)
{
    QStringList filteredFiles;
    std::copy_if(files.begin(), files.end(), std::back_inserter(filteredFiles), [](const QString& i){ return QFileInfo(i).isReadable(); } );

    if (filteredFiles.isEmpty())
        return;

87
    filteredFiles.prepend(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR "/plasma-sourceenv.sh"));
88
89

    QProcess p;
90
    p.start(QStringLiteral("/bin/sh"), filteredFiles);
91
92
93
    p.waitForFinished(-1);

    const auto fullEnv = p.readAllStandardOutput();
94
    auto envs = fullEnv.split('\0');
95
96

    for (auto &env: envs) {
97
        if (env.startsWith("_=") || env.startsWith("SHLVL"))
98
99
            continue;

100
        const int idx = env.indexOf('=');
101
        if (Q_UNLIKELY(idx <= 0))
102
103
            continue;

104
        if (qgetenv(env.left(idx)) != env.mid(idx+1)) {
105
//             qDebug() << "setting..." << env.left(idx) << env.mid(idx+1) << "was" << qgetenv(env.left(idx));
106
107
            qputenv(env.left(idx), env.mid(idx+1));
        }
108
109
110
    }
}

111
void createConfigDirectory()
112
113
114
115
116
117
118
119
{
    const QString configDir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
    if (!QDir().mkpath(configDir))
        out << "Could not create config directory XDG_CONFIG_HOME: " << configDir << '\n';
}

void runStartupConfig()
{
120
121
122
123
124
125
126
127
128
129
130
131
132
133
    //export LC_* variables set by kcmshell5 formats into environment
    //so it can be picked up by QLocale and friends.
    KConfig config(QStringLiteral("plasma-localerc"));
    KConfigGroup formatsConfig = KConfigGroup(&config, "Formats");

    const auto lcValues = {
        "LANG", "LC_NUMERIC", "LC_MONETARY", "LC_MEASUREMENT", "LC_COLLATE", "LC_CTYPE"
    };
    for (auto lc : lcValues) {
        const QString value = formatsConfig.readEntry(lc, QString());
        if (!value.isEmpty()) {
            qputenv(lc, value.toUtf8());
        }
    }
134

135
    KConfigGroup languageConfig = KConfigGroup(&config, "Translations");
136
    const QString value = languageConfig.readEntry("LANGUAGE", QString());
137
138
    if (!value.isEmpty()) {
        qputenv("LANGUAGE", value.toUtf8());
139
140
    }

141
142
143
144
    if (!formatsConfig.hasKey("LANG") && !qEnvironmentVariableIsEmpty("LANG")) {
        formatsConfig.writeEntry("LANG", qgetenv("LANG"));
        formatsConfig.sync();
    }
145
146
147
148
}

void setupCursor(bool wayland)
{
149
    const KConfig cfg(QStringLiteral("kcminputrc"));
150
151
152
    const KConfigGroup inputCfg = cfg.group("Mouse");

    const auto kcminputrc_mouse_cursorsize = inputCfg.readEntry("cursorSize", QString());
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
153
    const auto kcminputrc_mouse_cursortheme = inputCfg.readEntry("cursorTheme", QStringLiteral("breeze_cursors"));
154
155
156
157
158
159
160
161
162
    if (!kcminputrc_mouse_cursortheme.isEmpty() || !kcminputrc_mouse_cursorsize.isEmpty()) {
#ifdef XCURSOR_PATH
        QByteArray path(XCURSOR_PATH);
        path.replace("$XCURSOR_PATH", qgetenv("XCURSOR_PATH"));
        qputenv("XCURSOR_PATH", path);
#endif
    }

    //TODO: consider linking directly
163
    const int applyMouseStatus = wayland ? 0 : runSync(QStringLiteral("kapplymousetheme"), { kcminputrc_mouse_cursortheme, kcminputrc_mouse_cursorsize });
164
165
166
    if (applyMouseStatus == 10) {
        qputenv("XCURSOR_THEME", "breeze_cursors");
    } else if (!kcminputrc_mouse_cursortheme.isEmpty()) {
167
        qputenv("XCURSOR_THEME", kcminputrc_mouse_cursortheme.toUtf8());
168
169
    }
    if (!kcminputrc_mouse_cursorsize.isEmpty()) {
170
        qputenv("XCURSOR_SIZE", kcminputrc_mouse_cursorsize.toUtf8());
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
    }
}

// Source scripts found in <config locations>/plasma-workspace/env/*.sh
// (where <config locations> correspond to the system and user's configuration
// directory.
//
// This is where you can define environment variables that will be available to
// all KDE programs, so this is where you can run agents using e.g. eval `ssh-agent`
// or eval `gpg-agent --daemon`.
// Note: if you do that, you should also put "ssh-agent -k" as a shutdown script
//
// (see end of this file).
// For anything else (that doesn't set env vars, or that needs a window manager),
// better use the Autostart folder.

void runEnvironmentScripts()
{
    QStringList scripts;
    const auto locations = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("plasma-workspace/env"), QStandardPaths::LocateDirectory);
    for (const QString & location : locations) {
        QDir dir(location);
        const auto dirScripts = dir.entryInfoList({QStringLiteral("*.sh")});
        for (const auto script : dirScripts) {
            scripts << script.absoluteFilePath();
        }
    }
    sourceFiles(scripts);
}


// Mark that full KDE session is running (e.g. Konqueror preloading works only
// with full KDE running). The KDE_FULL_SESSION property can be detected by
// any X client connected to the same X session, even if not launched
// directly from the KDE session but e.g. using "ssh -X", kdesu. $KDE_FULL_SESSION
// however guarantees that the application is launched in the same environment
// like the KDE session and that e.g. KDE utilities/libraries are available.
// KDE_FULL_SESSION property is also only available since KDE 3.5.5.
// The matching tests are:
//   For $KDE_FULL_SESSION:
//     if test -n "$KDE_FULL_SESSION"; then ... whatever
//   For KDE_FULL_SESSION property (on X11):
//     xprop -root | grep "^KDE_FULL_SESSION" >/dev/null 2>/dev/null
//     if test $? -eq 0; then ... whatever
//
// Additionally there is $KDE_SESSION_UID with the uid
// of the user running the KDE session. It should be rarely needed (e.g.
// after sudo to prevent desktop-wide functionality in the new user's kded).
//
// Since KDE4 there is also KDE_SESSION_VERSION, containing the major version number.
//

void setupPlasmaEnvironment()
{
225
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
226
227
228
    //Manually disable auto scaling because we are scaling above
    //otherwise apps that manually opt in for high DPI get auto scaled by the developer AND manually scaled by us
    qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0");
229
#endif
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246

    qputenv("KDE_FULL_SESSION", "true");
    qputenv("KDE_SESSION_VERSION", "5");
    qputenv("KDE_SESSION_UID", QByteArray::number(getuid()));
    qputenv("XDG_CURRENT_DESKTOP", "KDE");
}

void setupX11()
{
//     Set a left cursor instead of the standard X11 "X" cursor, since I've heard
//     from some users that they're confused and don't know what to do. This is
//     especially necessary on slow machines, where starting KDE takes one or two
//     minutes until anything appears on the screen.
//
//     If the user has overwritten fonts, the cursor font may be different now
//     so don't move this up.

247
248
249
    runSync(QStringLiteral("xsetroot"), {QStringLiteral("-cursor_name"), QStringLiteral("left_ptr")});
    runSync(QStringLiteral("xprop"), {QStringLiteral("-root"), QStringLiteral("-f"), QStringLiteral("KDE_FULL_SESSION"), QStringLiteral("8t"), QStringLiteral("-set"), QStringLiteral("KDE_FULL_SESSION"), QStringLiteral("true")});
    runSync(QStringLiteral("xprop"), {QStringLiteral("-root"), QStringLiteral("-f"), QStringLiteral("KDE_SESSION_VERSION"), QStringLiteral("32c"), QStringLiteral("-set"), QStringLiteral("KDE_SESSION_VERSION"), QStringLiteral("5")});
250
251
252
253
}

void cleanupX11()
{
254
255
    runSync(QStringLiteral("xprop"), { QStringLiteral("-root"), QStringLiteral("-remove"), QStringLiteral("KDE_FULL_SESSION") });
    runSync(QStringLiteral("xprop"), { QStringLiteral("-root"), QStringLiteral("-remove"), QStringLiteral("KDE_SESSION_VERSION") });
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
}

// TODO: Check if Necessary
void cleanupPlasmaEnvironment()
{
    qunsetenv("KDE_FULL_SESSION");
    qunsetenv("KDE_SESSION_VERSION");
    qunsetenv("KDE_SESSION_UID");
}

// kwin_wayland can possibly also start dbus-activated services which need env variables.
// In that case, the update in startplasma might be too late.
bool syncDBusEnvironment()
{
    int exitCode;
    // At this point all environment variables are set, let's send it to the DBus session server to update the activation environment
272
273
274
275
276
    if (!QStandardPaths::findExecutable(QStringLiteral("dbus-update-activation-environment")).isEmpty()) {
        exitCode = runSync(QStringLiteral("dbus-update-activation-environment"), { QStringLiteral("--systemd"), QStringLiteral("--all") });
    } else {
        exitCode = runSync(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR "/ksyncdbusenv"), {});
    }
277
278
279
280
281
    return exitCode == 0;
}

void setupFontDpi()
{
282
    KConfig cfg(QStringLiteral("kcmfonts"));
283
284
    KConfigGroup fontsCfg(&cfg, "General");

285
    if (!fontsCfg.hasKey("forceFontDPI")) {
286
287
288
289
        return;
    }

    //TODO port to c++?
290
    const QByteArray input = "Xft.dpi: " + QByteArray::number(fontsCfg.readEntry("forceFontDPI", 0));
291
    QProcess p;
292
    p.start(QStringLiteral("xrdb"), { QStringLiteral("-quiet"), QStringLiteral("-merge"), QStringLiteral("-nocpp") });
293
294
295
296
297
298
    p.setProcessChannelMode(QProcess::ForwardedChannels);
    p.write(input);
    p.closeWriteChannel();
    p.waitForFinished(-1);
}

299
static bool desktopLockedAtStart = false;
300
301
302

QProcess* setupKSplash()
{
303
    const auto dlstr = qgetenv("DESKTOP_LOCKED");
304
    desktopLockedAtStart = dlstr == "true" || dlstr == "1";
305
306
307
    qunsetenv("DESKTOP_LOCKED"); // Don't want it in the environment

    QProcess* p = nullptr;
308
309
    if (!desktopLockedAtStart) {
        const KConfig cfg(QStringLiteral("ksplashrc"));
310
        // the splashscreen and progress indicator
311
        KConfigGroup ksplashCfg = cfg.group("KSplash");
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
312
        if (ksplashCfg.readEntry("Engine", QStringLiteral("KSplashQML")) == QLatin1String("KSplashQML")) {
313
            p = new QProcess;
314
            p->start(QStringLiteral("ksplashqml"), { ksplashCfg.readEntry("Theme", QStringLiteral("Breeze")) });
315
316
317
318
319
320
321
322
323
        }
    }
    return p;
}


void setupGSLib()
// Get Ghostscript to look into user's KDE fonts dir for additional Fontmap
{
324
    const QByteArray usr_fdir = QFile::encodeName(QDir::home().absoluteFilePath(QStringLiteral(".fonts")));
325
326
327
328
329
330
331
332
333
334
335
    if (qEnvironmentVariableIsSet("GS_LIB")) {
        qputenv("GS_LIB", usr_fdir + ':' + qgetenv("GS_LIB"));
    } else {
        qputenv("GS_LIB", usr_fdir);
    }
}

bool startKDEInit()
{
    // We set LD_BIND_NOW to increase the efficiency of kdeinit.
    // kdeinit unsets this variable before loading applications.
336
    const int exitCode = runSync(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/start_kdeinit_wrapper"), { QStringLiteral("--kded"), QStringLiteral("+kcminit_startup") }, { QStringLiteral("LD_BIND_NOW=true") });
337
    if (exitCode != 0) {
338
        messageBox(QStringLiteral("startkde: Could not start kdeinit5. Check your installation."));
339
340
341
        return false;
    }

342
343
    OrgKdeKSplashInterface iface(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QDBusConnection::sessionBus());
    iface.setStage(QStringLiteral("kinit"));
344
345
346
    return true;
}

347
bool startKSMServer(bool wayland)
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
{
    // finally, give the session control to the session manager
    // see kdebase/ksmserver for the description of the rest of the startup sequence
    // if the KDEWM environment variable has been set, then it will be used as KDE's
    // window manager instead of kwin.
    // if KDEWM is not set, ksmserver will ensure kwin is started.
    // kwrapper5 is used to reduce startup time and memory usage
    // kwrapper5 does not return useful error codes such as the exit code of ksmserver.
    // We only check for 255 which means that the ksmserver process could not be
    // started, any problems thereafter, e.g. ksmserver failing to initialize,
    // will remain undetected.
    // If the session should be locked from the start (locked autologin),
    // lock now and do the rest of the KDE startup underneath the locker.


363
    QStringList ksmserverOptions;
364
365
366
    if (wayland) {
        ksmserverOptions << QStringLiteral("--no-lockscreen");
    } else {
367
368
369
        if (qEnvironmentVariableIsSet("KDEWM")) {
            ksmserverOptions << QStringLiteral("--windowmanager") << qEnvironmentVariable("KDEWM");
        }
370
371
372
        if (desktopLockedAtStart) {
            ksmserverOptions << QStringLiteral("--lockscreen");
        }
373
    }
374
375

    const auto exitCode = runSync(QStringLiteral(CMAKE_INSTALL_FULL_BINDIR "/plasma_session"), ksmserverOptions);
376
377
378

    if (exitCode == 255) {
        // Startup error
379
        messageBox(QStringLiteral("startkde: Could not start ksmserver. Check your installation.\n"));
380
381
382
383
384
385
386
        return false;
    }
    return true;
}

void waitForKonqi()
{
387
    const KConfig cfg(QStringLiteral("startkderc"));
388
    const KConfigGroup grp = cfg.group("WaitForDrKonqi");
389
390
391
392
393
394
395
396
397
398
399
400
401
    bool wait_drkonqi =  grp.readEntry("Enabled", true);
    if (wait_drkonqi) {
        // wait for remaining drkonqi instances with timeout (in seconds)
        const int wait_drkonqi_timeout = grp.readEntry("Timeout", 900) * 1000;
        QElapsedTimer wait_drkonqi_counter;
        wait_drkonqi_counter.start();
        QStringList services = allServices(QLatin1String("org.kde.drkonqi-"));
        while (!services.isEmpty()) {
            sleep(5);
            services = allServices(QLatin1String("org.kde.drkonqi-"));
            if (wait_drkonqi_counter.elapsed() >= wait_drkonqi_timeout) {
                // ask remaining drkonqis to die in a graceful way
                for (const auto &service: services) {
402
403
                    QDBusInterface iface(service, QStringLiteral("/MainApplication"));
                    iface.call(QStringLiteral("quit"));
404
405
406
407
408
409
                }
                break;
            }
        }
    }
}