/*************************************************************************** * Copyright (c) 2014 Pier Luigi Fiorini * Copyright (c) 2013 Abdurrahman AVCI * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ***************************************************************************/ #include "XorgDisplayServer.h" #include "Configuration.h" #include "DaemonApp.h" #include "Display.h" #include "Seat.h" #include #include #include #include #include #include #include #include #include namespace SDDM { XorgDisplayServer::XorgDisplayServer(Display *parent) : DisplayServer(parent) { if (daemonApp->testing()) m_xauth.setAuthDirectory(QStringLiteral(".")); m_xauth.setup(); } XorgDisplayServer::~XorgDisplayServer() { stop(); } const QString &XorgDisplayServer::display() const { return m_display; } QString XorgDisplayServer::authPath() const { return m_xauth.authPath(); } QString XorgDisplayServer::sessionType() const { return QStringLiteral("x11"); } const QByteArray XorgDisplayServer::cookie() const { return m_xauth.cookie(); } bool XorgDisplayServer::start() { // check flag if (m_started) return false; if (process) { qCritical() << "Tried to start Xorg before previous instance exited"; return false; } // create process process = new QProcess(this); // delete process on finish connect(process, QOverload::of(&QProcess::finished), this, &XorgDisplayServer::finished); // log message qDebug() << "Display server starting..."; // generate auth file. // For the X server's copy, the display number doesn't matter. // An empty file would result in no access control! m_display = QStringLiteral(":0"); if(!m_xauth.addCookie(m_display)) { qCritical() << "Failed to write xauth file"; return false; } // set process environment QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert(QStringLiteral("XCURSOR_THEME"), mainConfig.Theme.CursorTheme.get()); QString xcursorSize = mainConfig.Theme.CursorSize.get(); if (!xcursorSize.isEmpty()) env.insert(QStringLiteral("XCURSOR_SIZE"), xcursorSize); process->setProcessEnvironment(env); //create pipe for communicating with X server //0 == read from X, 1== write to from X int pipeFds[2]; if (pipe(pipeFds) != 0) { qCritical("Could not create pipe to start X server"); } // start display server QStringList args; if (!daemonApp->testing()) { process->setProgram(mainConfig.X11.ServerPath.get()); args << mainConfig.X11.ServerArguments.get().split(QLatin1Char(' '), Qt::SkipEmptyParts) << QStringLiteral("-background") << QStringLiteral("none") << QStringLiteral("-seat") << displayPtr()->seat()->name() << QStringLiteral("vt%1").arg(displayPtr()->terminalId()); } else { process->setProgram(mainConfig.X11.XephyrPath.get()); args << QStringLiteral("-br") << QStringLiteral("-screen") << QStringLiteral("800x600"); } args << QStringLiteral("-auth") << m_xauth.authPath() << QStringLiteral("-noreset") << QStringLiteral("-displayfd") << QString::number(pipeFds[1]); process->setArguments(args); qDebug() << "Running:" << qPrintable(process->program()) << qPrintable(process->arguments().join(QLatin1Char(' '))); process->start(); // wait for display server to start if (!process->waitForStarted()) { // log message qCritical() << "Failed to start display server process."; // return fail close(pipeFds[0]); return false; } // close the other side of pipe in our process, otherwise reading // from it may stuck even X server exit. close(pipeFds[1]); QFile readPipe; if (!readPipe.open(pipeFds[0], QIODevice::ReadOnly)) { qCritical("Failed to open pipe to start X Server"); close(pipeFds[0]); stop(); return false; } QByteArray displayNumber = readPipe.readLine(); if (displayNumber.size() < 2) { // X server gave nothing (or a whitespace). qCritical("Failed to read display number from pipe"); close(pipeFds[0]); stop(); return false; } displayNumber.prepend(QByteArray(":")); displayNumber.remove(displayNumber.size() -1, 1); // trim trailing whitespace m_display = QString::fromLocal8Bit(displayNumber); // close our pipe close(pipeFds[0]); // The file is also used by the greeter, which does care about the // display number. Write the proper entry, if it's different. if(m_display != QStringLiteral(":0")) { if(!m_xauth.addCookie(m_display)) { qCritical() << "Failed to write xauth file"; stop(); return false; } } changeOwner(m_xauth.authPath()); emit started(); // set flag m_started = true; // return success return true; } void XorgDisplayServer::stop() { if (!process) return; // log message qDebug() << "Display server stopping..."; // terminate process process->terminate(); // wait for finished if (!process->waitForFinished(5000)) { process->kill(); process->waitForFinished(25000); } } void XorgDisplayServer::finished() { // clean up if (process) { process->deleteLater(); process = nullptr; } // check flag if (!m_started) return; // reset flag m_started = false; // log message qDebug() << "Display server stopped."; QStringList displayStopCommand = QProcess::splitCommand(mainConfig.X11.DisplayStopCommand.get()); // create display setup script process QProcess *displayStopScript = new QProcess(); // set process environment QProcessEnvironment env; env.insert(QStringLiteral("DISPLAY"), m_display); env.insert(QStringLiteral("HOME"), QStringLiteral("/")); env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get()); env.insert(QStringLiteral("SHELL"), QStringLiteral("/bin/sh")); displayStopScript->setProcessEnvironment(env); // start display stop script qDebug() << "Running display stop script " << displayStopCommand; const auto program = displayStopCommand.takeFirst(); displayStopScript->start(program, displayStopCommand); // delete displayStopScript on finish connect(displayStopScript, QOverload::of(&QProcess::finished), displayStopScript, &QProcess::deleteLater); // wait for finished if (!displayStopScript->waitForFinished(5000)) displayStopScript->kill(); // emit signal emit stopped(); } void XorgDisplayServer::setupDisplay() { // create cursor setup process QProcess *setCursor = new QProcess(); // create display setup script process QProcess *displayScript = new QProcess(); const QString xcursorTheme = mainConfig.Theme.CursorTheme.get(), xcursorSize = mainConfig.Theme.CursorSize.get(); // set process environment QProcessEnvironment env; env.insert(QStringLiteral("DISPLAY"), m_display); env.insert(QStringLiteral("HOME"), QStringLiteral("/")); env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get()); env.insert(QStringLiteral("XAUTHORITY"), m_xauth.authPath()); env.insert(QStringLiteral("SHELL"), QStringLiteral("/bin/sh")); if (!xcursorTheme.isEmpty()) env.insert(QStringLiteral("XCURSOR_THEME"), xcursorTheme); if (!xcursorSize.isEmpty()) env.insert(QStringLiteral("XCURSOR_SIZE"), xcursorSize); setCursor->setProcessEnvironment(env); displayScript->setProcessEnvironment(env); qDebug() << "Setting default cursor"; setCursor->start(QStringLiteral("xsetroot"), { QStringLiteral("-cursor_name"), QStringLiteral("left_ptr") }); // delete setCursor on finish connect(setCursor, QOverload::of(&QProcess::finished), setCursor, &QProcess::deleteLater); // wait for finished if (!setCursor->waitForFinished(5000)) { qWarning() << "Could not setup default cursor"; setCursor->kill(); } // Unlike libXcursor, xcb-util-cursor no longer looks at XCURSOR_*. Set the resources. if (!xcursorTheme.isEmpty() || !xcursorSize.isEmpty()) { QProcess xrdbProcess; xrdbProcess.setProcessEnvironment(env); xrdbProcess.start(QStringLiteral("xrdb"), QStringList{QStringLiteral("-nocpp"), QStringLiteral("-merge")}); if (!xcursorTheme.isEmpty()) xrdbProcess.write(QStringLiteral("Xcursor.theme: %1\n").arg(xcursorTheme).toUtf8()); if (!xcursorSize.isEmpty()) xrdbProcess.write(QStringLiteral("Xcursor.size: %1\n").arg(xcursorSize).toUtf8()); xrdbProcess.closeWriteChannel(); if (!xrdbProcess.waitForFinished(5000)) { qDebug() << "Could not set Xcursor resources" << xrdbProcess.error(); xrdbProcess.kill(); xrdbProcess.waitForFinished(-1); } } // start display setup script qDebug() << "Running display setup script " << mainConfig.X11.DisplayCommand.get(); QStringList displayCommand = QProcess::splitCommand(mainConfig.X11.DisplayCommand.get()); const QString program = displayCommand.takeFirst(); displayScript->start(program, displayCommand); // delete displayScript on finish connect(displayScript, QOverload::of(&QProcess::finished), displayScript, &QProcess::deleteLater); // wait for finished if (!displayScript->waitForFinished(30000)) displayScript->kill(); // reload config if needed mainConfig.load(); } void XorgDisplayServer::changeOwner(const QString &fileName) { // change the owner and group of the auth file to the sddm user struct passwd *pw = getpwnam("sddm"); if (!pw) qWarning() << "Failed to find the sddm user. Owner of the auth file will not be changed."; else { if (chown(qPrintable(fileName), pw->pw_uid, pw->pw_gid) == -1) qWarning() << "Failed to change owner of the auth file."; } } }