File indexing completed on 2024-04-21 16:21:52

0001 /*
0002     ksmserver - the KDE session management server
0003 
0004     SPDX-FileCopyrightText: 2000 Matthias Ettrich <ettrich@kde.org>
0005 
0006     SPDX-License-Identifier: MIT
0007 */
0008 
0009 #include <config-ksmserver.h>
0010 #include <config-workspace.h>
0011 #include <errno.h>
0012 #include <fcntl.h>
0013 #include <fixx11h.h>
0014 #include <stdlib.h>
0015 #include <string.h>
0016 #include <unistd.h>
0017 
0018 #include <KMessageBox>
0019 
0020 #include "server.h"
0021 #include <KLocalizedString>
0022 #include <KRuntimePlatform>
0023 #include <KSharedConfig>
0024 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0025 #include <private/qtx11extras_p.h>
0026 #else
0027 #include <QX11Info>
0028 #endif
0029 #include <kconfig.h>
0030 #include <kconfiggroup.h>
0031 #include <kdbusservice.h>
0032 #include <kmanagerselection.h>
0033 #include <ksmserver_debug.h>
0034 #include <kwindowsystem.h>
0035 
0036 #include <QApplication>
0037 #include <QCommandLineParser>
0038 #include <QFile>
0039 #include <QQuickWindow>
0040 #include <X11/extensions/Xrender.h>
0041 
0042 static const char version[] = "0.4";
0043 
0044 Display *dpy = nullptr;
0045 Colormap colormap = 0;
0046 Visual *visual = nullptr;
0047 
0048 extern KSMServer *the_server;
0049 
0050 void IoErrorHandler(IceConn iceConn)
0051 {
0052     the_server->ioError(iceConn);
0053 }
0054 
0055 bool writeTest(QByteArray path)
0056 {
0057     path += "/XXXXXX";
0058     int fd = mkstemp(path.data());
0059     if (fd == -1)
0060         return false;
0061     if (write(fd, "Hello World\n", 12) == -1) {
0062         int save_errno = errno;
0063         close(fd);
0064         unlink(path.data());
0065         errno = save_errno;
0066         return false;
0067     }
0068     close(fd);
0069     unlink(path.data());
0070     return true;
0071 }
0072 
0073 void checkComposite()
0074 {
0075     if (qgetenv("KDE_SKIP_ARGB_VISUALS") == "1")
0076         return;
0077     // thanks to zack rusin and frederik for pointing me in the right direction
0078     // for the following bits of X11 code
0079     dpy = XOpenDisplay(nullptr); // open default display
0080     if (!dpy) {
0081         qCCritical(KSMSERVER) << "Cannot connect to the X server";
0082         return;
0083     }
0084 
0085     int screen = DefaultScreen(dpy);
0086     int eventBase, errorBase;
0087 
0088     if (XRenderQueryExtension(dpy, &eventBase, &errorBase)) {
0089         int nvi;
0090         XVisualInfo templ;
0091         templ.screen = screen;
0092         templ.depth = 32;
0093         templ.c_class = TrueColor;
0094         XVisualInfo *xvi = XGetVisualInfo(dpy, VisualScreenMask | VisualDepthMask | VisualClassMask, &templ, &nvi);
0095         for (int i = 0; i < nvi; ++i) {
0096             XRenderPictFormat *format = XRenderFindVisualFormat(dpy, xvi[i].visual);
0097             if (format->type == PictTypeDirect && format->direct.alphaMask) {
0098                 visual = xvi[i].visual;
0099                 colormap = XCreateColormap(dpy, RootWindow(dpy, screen), visual, AllocNone);
0100 
0101                 XFree(xvi);
0102                 return;
0103             }
0104         }
0105 
0106         XFree(xvi);
0107     }
0108     XCloseDisplay(dpy);
0109     dpy = nullptr;
0110 }
0111 
0112 void sanity_check(int argc, char *argv[])
0113 {
0114     QString msg;
0115     QByteArray path = qgetenv("HOME");
0116     const QByteArray readOnly = qgetenv("KDE_HOME_READONLY");
0117     if (path.isEmpty()) {
0118         msg = i18n("$HOME not set!");
0119     }
0120     if (msg.isEmpty() && access(path.data(), W_OK)) {
0121         if (errno == ENOENT)
0122             msg = i18n("$HOME directory (%1) does not exist.", QFile::decodeName(path));
0123         else if (readOnly.isEmpty())
0124             msg = i18n("No write access to $HOME directory (%1).", QFile::decodeName(path));
0125     }
0126     if (msg.isEmpty() && access(path.data(), R_OK)) {
0127         if (errno == ENOENT)
0128             msg = i18n("$HOME directory (%1) does not exist.", QFile::decodeName(path));
0129         else
0130             msg = i18n("No read access to $HOME directory (%1).", QFile::decodeName(path));
0131     }
0132     if (msg.isEmpty() && readOnly.isEmpty() && !writeTest(path)) {
0133         if (errno == ENOSPC)
0134             msg = i18n("$HOME directory (%1) is out of disk space.", QFile::decodeName(path));
0135         else
0136             msg = i18n(
0137                 "Writing to the $HOME directory (%2) failed with "
0138                 "the error '%1'",
0139                 QString::fromLocal8Bit(strerror(errno)),
0140                 QFile::decodeName(path));
0141     }
0142     if (msg.isEmpty()) {
0143         path = getenv("ICEAUTHORITY");
0144         if (path.isEmpty()) {
0145             path = qgetenv("HOME");
0146             path += "/.ICEauthority";
0147         }
0148 
0149         if (access(path.data(), W_OK) && (errno != ENOENT))
0150             msg = i18n("No write access to '%1'.", QFile::decodeName(path));
0151         else if (access(path.data(), R_OK) && (errno != ENOENT))
0152             msg = i18n("No read access to '%1'.", QFile::decodeName(path));
0153     }
0154     if (msg.isEmpty()) {
0155         path = getenv("KDETMP");
0156         if (path.isEmpty())
0157             path = "/tmp";
0158         if (!writeTest(path)) {
0159             if (errno == ENOSPC)
0160                 msg = i18n("Temp directory (%1) is out of disk space.", QFile::decodeName(path));
0161             else
0162                 msg = i18n(
0163                     "Writing to the temp directory (%2) failed with\n    "
0164                     "the error '%1'",
0165                     QString::fromLocal8Bit(strerror(errno)),
0166                     QFile::decodeName(path));
0167         }
0168     }
0169     if (msg.isEmpty() && (path != "/tmp")) {
0170         path = "/tmp";
0171         if (!writeTest(path)) {
0172             if (errno == ENOSPC)
0173                 msg = i18n("Temp directory (%1) is out of disk space.", QFile::decodeName(path));
0174             else
0175                 msg = i18n(
0176                     "Writing to the temp directory (%2) failed with\n    "
0177                     "the error '%1'",
0178                     QString::fromLocal8Bit(strerror(errno)),
0179                     QFile::decodeName(path));
0180         }
0181     }
0182     if (msg.isEmpty()) {
0183         path += "/.ICE-unix";
0184         if (access(path.data(), W_OK) && (errno != ENOENT))
0185             msg = i18n("No write access to '%1'.", QFile::decodeName(path));
0186         else if (access(path.data(), R_OK) && (errno != ENOENT))
0187             msg = i18n("No read access to '%1'.", QFile::decodeName(path));
0188     }
0189     if (!msg.isEmpty()) {
0190         const QString msg_pre = i18n(
0191                                     "The following installation problem was detected\n"
0192                                     "while trying to start Plasma:")
0193             + QStringLiteral("\n\n    ");
0194         const QString msg_post = i18n("\n\nPlasma is unable to start.\n");
0195         fputs(msg_pre.toUtf8().constData(), stderr);
0196         fprintf(stderr, "%s", msg.toUtf8().constData());
0197         fputs(msg_post.toUtf8().constData(), stderr);
0198 
0199         QApplication a(argc, argv);
0200         const QString qmsg = msg_pre + msg + msg_post;
0201         KMessageBox::error(nullptr, qmsg, i18n("Plasma Workspace installation problem!"));
0202         exit(255);
0203     }
0204 }
0205 
0206 int main(int argc, char *argv[])
0207 {
0208     sanity_check(argc, argv);
0209 
0210     putenv((char *)"SESSION_MANAGER=");
0211     checkComposite();
0212 
0213     // force xcb QPA plugin as ksmserver is very X11 specific
0214     const QByteArray origQpaPlatform = qgetenv("QT_QPA_PLATFORM");
0215     qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("xcb"));
0216 
0217     QQuickWindow::setDefaultAlphaBuffer(true);
0218     QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
0219     QApplication *a = new QApplication(argc, argv);
0220 
0221     // now the QPA platform is set, unset variable again to not launch apps with incorrect environment
0222     if (origQpaPlatform.isEmpty()) {
0223         qunsetenv("QT_QPA_PLATFORM");
0224     } else {
0225         qputenv("QT_QPA_PLATFORM", origQpaPlatform);
0226     }
0227 
0228     QApplication::setApplicationName(QStringLiteral("ksmserver"));
0229     QApplication::setApplicationVersion(QString::fromLatin1(version));
0230     QApplication::setOrganizationDomain(QStringLiteral("kde.org"));
0231 
0232     fcntl(ConnectionNumber(QX11Info::display()), F_SETFD, 1);
0233 
0234     a->setQuitOnLastWindowClosed(false); // #169486
0235 
0236     QCommandLineParser parser;
0237     parser.setApplicationDescription(i18n("The reliable Plasma session manager that talks the standard X11R6 \nsession management protocol (XSMP)."));
0238     parser.addHelpOption();
0239     parser.addVersionOption();
0240 
0241     QCommandLineOption restoreOption(QStringList() << QStringLiteral("r") << QStringLiteral("restore"), i18n("Restores the saved user session if available"));
0242     parser.addOption(restoreOption);
0243 
0244     QCommandLineOption nolocalOption(QStringLiteral("nolocal"), i18n("Also allow remote connections"));
0245     parser.addOption(nolocalOption);
0246 
0247     QCommandLineOption lockscreenOption(QStringLiteral("lockscreen"), i18n("Starts the session in locked mode"));
0248     parser.addOption(lockscreenOption);
0249 
0250     QCommandLineOption noLockscreenOption(QStringLiteral("no-lockscreen"),
0251                                           i18n("Starts without lock screen support. Only needed if other component provides the lock screen."));
0252     parser.addOption(noLockscreenOption);
0253 
0254     parser.process(*a);
0255 
0256     bool only_local = !parser.isSet(nolocalOption);
0257 #ifndef HAVE__ICETRANSNOLISTEN
0258     /* this seems strange, but the default is only_local, so if !only_local
0259      * the option --nolocal was given, and we warn (the option --nolocal
0260      * does nothing on this platform, as here the default is reversed)
0261      */
0262     if (!only_local) {
0263         qCWarning(KSMSERVER, "--nolocal is not supported on your platform. Sorry.");
0264     }
0265     only_local = false;
0266 #endif
0267 
0268     KSMServer::InitFlags flags = KSMServer::InitFlag::None;
0269     if (only_local) {
0270         flags |= KSMServer::InitFlag::OnlyLocal;
0271     }
0272     if (parser.isSet(lockscreenOption)) {
0273         flags |= KSMServer::InitFlag::ImmediateLockScreen;
0274     }
0275     if (parser.isSet(noLockscreenOption)) {
0276         flags |= KSMServer::InitFlag::NoLockScreen;
0277     }
0278 
0279     // we use the session_type here as ksmserver is already forced as X above
0280     // in wayland, kwin manages the lock screen
0281     if (qgetenv("XDG_SESSION_TYPE") == QByteArrayLiteral("wayland")) {
0282         flags |= KSMServer::InitFlag::NoLockScreen;
0283     }
0284 
0285     KSMServer *server = new KSMServer(flags);
0286 
0287     // for the KDE-already-running check in startkde
0288     KSelectionOwner kde_running("_KDE_RUNNING", 0);
0289     kde_running.claim(false);
0290 
0291     IceSetIOErrorHandler(IoErrorHandler);
0292 
0293     KConfigGroup config(KSharedConfig::openConfig(), "General");
0294 
0295     QString loginMode = config.readEntry("loginMode", "restorePreviousLogout");
0296 
0297     // we don't need session restoring in Plasma Mobile
0298     if (KRuntimePlatform::runtimePlatform().contains(QStringLiteral("phone"))) {
0299         loginMode = QStringLiteral("emptySession");
0300     }
0301 
0302     if (parser.isSet(restoreOption))
0303         server->setRestoreSession(QStringLiteral(SESSION_BY_USER));
0304     else if (loginMode == QLatin1String("restorePreviousLogout"))
0305         server->setRestoreSession(QStringLiteral(SESSION_PREVIOUS_LOGOUT));
0306     else if (loginMode == QLatin1String("restoreSavedSession"))
0307         server->setRestoreSession(QStringLiteral(SESSION_BY_USER));
0308     else
0309         server->startDefaultSession();
0310 
0311     KDBusService service(KDBusService::Unique);
0312 
0313     server->setupShortcuts();
0314     int ret = a->exec();
0315     kde_running.release(); // needs to be done before QApplication destruction
0316     delete a;
0317     return ret;
0318 }