diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5b8ea946f..6a6b95245 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,3 +3,4 @@ configure_file("common/Constants.h.in" "common/Constants.h" IMMEDIATE @ONLY) add_subdirectory(daemon) add_subdirectory(greeter) add_subdirectory(helper) +add_subdirectory(x11helper) diff --git a/src/common/Configuration.h b/src/common/Configuration.h index 3b894ad07..a9152f59e 100644 --- a/src/common/Configuration.h +++ b/src/common/Configuration.h @@ -43,6 +43,8 @@ namespace SDDM { "If property is set to none, numlock won't be changed\n" "NOTE: Currently ignored if autologin is enabled.")); Entry(InputMethod, QString, QStringLiteral("qtvirtualkeyboard"), _S("Input method module")); + Entry(DisplayServer, QString, _S("x11"), _S("Which display server should be used.\n" + "Valid values are: x11, x11-user.")); Entry(Namespaces, QStringList, QStringList(), _S("Comma-separated list of Linux namespaces for user session to enter")); Entry(DisplayServer, QString, _S("x11"), _S("Which display server should be used.\n" "NOTE: Wayland support is currently considered experimental.\n" diff --git a/src/common/x11helperprotocol.h b/src/common/x11helperprotocol.h new file mode 100644 index 000000000..145ec9398 --- /dev/null +++ b/src/common/x11helperprotocol.h @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2019 Pier Luigi Fiorini + * + * 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. + ***************************************************************************/ + +#ifndef SDDM_X11HELPERPROTOCOL_H +#define SDDM_X11HELPERPROTOCOL_H + +#include + +namespace SDDM { + +enum class X11HelperMessage { + Unknown, + Started, + Last +}; + +inline QDataStream &operator>>(QDataStream &s, X11HelperMessage &m) +{ + qint32 what; + s >> what; + if (what < qint32(X11HelperMessage::Unknown) || what >= qint32(X11HelperMessage::Last)) { + s.setStatus(QDataStream::ReadCorruptData); + return s; + } + m = X11HelperMessage(what); + return s; +} + +inline QDataStream &operator<<(QDataStream &s, const X11HelperMessage &m) +{ + s << qint32(m); + return s; +} + +} // namespace SDDM + +#endif // SDDM_X11HELPERPROTOCOL_H diff --git a/src/daemon/Display.cpp b/src/daemon/Display.cpp index 72cc46ef5..6def9b3aa 100644 --- a/src/daemon/Display.cpp +++ b/src/daemon/Display.cpp @@ -147,9 +147,6 @@ namespace SDDM { if (m_started) return; - // setup display - m_displayServer->setupDisplay(); - // log message qDebug() << "Display server started."; diff --git a/src/daemon/DisplayServer.h b/src/daemon/DisplayServer.h index 3f72913b0..45e098f3e 100644 --- a/src/daemon/DisplayServer.h +++ b/src/daemon/DisplayServer.h @@ -44,7 +44,6 @@ namespace SDDM { virtual bool start() = 0; virtual void stop() = 0; virtual void finished() = 0; - virtual void setupDisplay() = 0; signals: void started(); diff --git a/src/daemon/XorgDisplayServer.cpp b/src/daemon/XorgDisplayServer.cpp index e7eb342cb..b6cd5c750 100644 --- a/src/daemon/XorgDisplayServer.cpp +++ b/src/daemon/XorgDisplayServer.cpp @@ -1,5 +1,5 @@ /*************************************************************************** -* Copyright (c) 2014 Pier Luigi Fiorini +* Copyright (c) 2014-2019 Pier Luigi Fiorini * Copyright (c) 2013 Abdurrahman AVCI * * This program is free software; you can redistribute it and/or modify @@ -18,19 +18,24 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ***************************************************************************/ +#include +#include +#include +#include + #include "XorgDisplayServer.h" #include "Configuration.h" +#include "Constants.h" #include "DaemonApp.h" #include "Display.h" +#include "SafeDataStream.h" #include "SignalHandler.h" #include "Seat.h" +#include "x11helperprotocol.h" #include -#include -#include #include -#include #include @@ -41,31 +46,8 @@ namespace SDDM { XorgDisplayServer::XorgDisplayServer(Display *parent) : DisplayServer(parent) { - // get auth directory - QString authDir = QStringLiteral(RUNTIME_DIR); - - // use "." as authdir in test mode - if (daemonApp->testing()) - authDir = QStringLiteral("."); - - // create auth dir if not existing - QDir().mkpath(authDir); - - // set auth path - m_authPath = QStringLiteral("%1/%2").arg(authDir).arg(QUuid::createUuid().toString()); - - // generate cookie - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> dis(0, 15); - - // resever 32 bytes - m_cookie.reserve(32); - - // create a random hexadecimal number - const char *digits = "0123456789abcdef"; - for (int i = 0; i < 32; ++i) - m_cookie[i] = digits[dis(gen)]; + createAuthFile(); + changeOwner(m_authPath); } XorgDisplayServer::~XorgDisplayServer() { @@ -88,6 +70,7 @@ namespace SDDM { return m_cookie; } +<<<<<<< HEAD bool XorgDisplayServer::addCookie(const QString &file) { // log message qDebug() << "Adding cookie to" << file; @@ -113,11 +96,14 @@ namespace SDDM { return pclose(fp) == 0; } +======= +>>>>>>> b1ba4e7 (WIP Xorg user session) bool XorgDisplayServer::start() { // check flag if (m_started) return false; +<<<<<<< HEAD // create process process = new QProcess(this); @@ -200,33 +186,73 @@ namespace SDDM { // 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]); - 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]); +======= + // Start socket server + if (m_socketServer) + m_socketServer->deleteLater(); + m_socketServer = new QLocalServer(this); + connect(m_socketServer, &QLocalServer::newConnection, + this, &XorgDisplayServer::handleNewConnection); + m_socketServer->listen(QStringLiteral("sddm-x11-helper-%1").arg(QUuid::createUuid().toString().replace(QRegExp(QStringLiteral("[{}]")), QString()))); + changeOwner(m_socketServer->fullServerName()); + + // Command arguments + QStringList args; + args << QStringLiteral("--seat") << displayPtr()->seat()->name() + << QStringLiteral("--vt") << QString::number(displayPtr()->terminalId()) + << QStringLiteral("--socket") << m_socketServer->fullServerName() + << QStringLiteral("--auth") << m_authPath; + if (daemonApp->testing()) + args << QStringLiteral("--test-mode"); +>>>>>>> b1ba4e7 (WIP Xorg user session) + + qInfo("Starting X11 server..."); + + // Run helper + if (mainConfig.DisplayServer.get() == QStringLiteral("x11")) { + if (m_process) + m_process->deleteLater(); + m_process = new QProcess(this); + connect(m_process, QOverload::of(&QProcess::finished), + this, &XorgDisplayServer::finished); + + m_process->start(QStringLiteral(LIBEXEC_INSTALL_DIR "/sddm-x11-helper"), args); + if (!m_process->waitForStarted()) { + qCritical("Failed to start display server"); 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]); - - emit started(); + } else if (mainConfig.DisplayServer.get() == QStringLiteral("x11-user")) { + if (m_auth) + m_auth->deleteLater(); + m_auth = new Auth(this); + m_auth->setVerbose(true); + m_auth->setUser(QStringLiteral("sddm")); + m_auth->setGreeter(true); + connect(m_auth, &Auth::requestChanged, this, &XorgDisplayServer::onRequestChanged); + connect(m_auth, &Auth::sessionStarted, this, &XorgDisplayServer::onSessionStarted); + connect(m_auth, &Auth::finished, this, &XorgDisplayServer::onHelperFinished); + connect(m_auth, &Auth::info, this, &XorgDisplayServer::authInfo); + connect(m_auth, &Auth::error, this, &XorgDisplayServer::authError); + + QStringList cmd; + cmd << QStringLiteral(LIBEXEC_INSTALL_DIR "/sddm-x11-helper") + << args; + + QProcessEnvironment env; + env.insert(QStringLiteral("XDG_SEAT"), displayPtr()->seat()->name()); + env.insert(QStringLiteral("XDG_SEAT_PATH"), daemonApp->displayManager()->seatPath(displayPtr()->seat()->name())); + env.insert(QStringLiteral("XDG_SESSION_PATH"), daemonApp->displayManager()->sessionPath(QStringLiteral("Session%1").arg(daemonApp->newSessionId()))); + env.insert(QStringLiteral("XDG_VTNR"), QString::number(displayPtr()->terminalId())); + env.insert(QStringLiteral("XDG_SESSION_CLASS"), QStringLiteral("greeter")); + env.insert(QStringLiteral("XDG_SESSION_TYPE"), QStringLiteral("x11")); + env.insert(QStringLiteral("XORG_RUN_AS_USER_OK"), QStringLiteral("1")); + m_auth->insertEnvironment(env); + + m_auth->setSession(cmd.join(QLatin1Char(' '))); + m_auth->start(); } +<<<<<<< HEAD // 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")) { @@ -241,6 +267,8 @@ namespace SDDM { m_started = true; // return success +======= +>>>>>>> b1ba4e7 (WIP Xorg user session) return true; } @@ -250,14 +278,16 @@ namespace SDDM { return; // log message - qDebug() << "Display server stopping..."; + qDebug() << "Stopping X11 server"; - // terminate process - process->terminate(); + if (m_process) { + // terminate process + m_process->terminate(); - // wait for finished - if (!process->waitForFinished(5000)) - process->kill(); + // wait for finished + if (!m_process->waitForFinished(5000)) + m_process->kill(); + } } void XorgDisplayServer::finished() { @@ -269,98 +299,137 @@ namespace SDDM { m_started = false; // log message - qDebug() << "Display server stopped."; - - QString displayStopCommand = 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; - displayStopScript->start(displayStopCommand); + qInfo("X11 server stopped"); - // wait for finished - if (!displayStopScript->waitForFinished(5000)) - displayStopScript->kill(); - - // clean up the script process - displayStopScript->deleteLater(); - displayStopScript = nullptr; + // Stop socket server + if (m_socketServer) { + m_socketServer->close(); + m_socketServer->deleteLater(); + m_socketServer = nullptr; + } // clean up - process->deleteLater(); - process = nullptr; + if (m_process) { + m_process->deleteLater(); + m_process = nullptr; + } - // remove authority file - QFile::remove(m_authPath); + // Remove authority file + if (!m_authPath.isEmpty() && QFile::exists(m_authPath)) + QFile::remove(m_authPath); // emit signal emit stopped(); } - void XorgDisplayServer::setupDisplay() { - QString displayCommand = mainConfig.X11.DisplayCommand.get(); - - // create cursor setup process - QProcess *setCursor = new QProcess(); - // create display setup script process - QProcess *displayScript = 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("XAUTHORITY"), m_authPath); - env.insert(QStringLiteral("SHELL"), QStringLiteral("/bin/sh")); - env.insert(QStringLiteral("XCURSOR_THEME"), mainConfig.Theme.CursorTheme.get()); - setCursor->setProcessEnvironment(env); - displayScript->setProcessEnvironment(env); - - qDebug() << "Setting default cursor"; - setCursor->start(QStringLiteral("xsetroot -cursor_name left_ptr")); - - // delete setCursor on finish - connect(setCursor, QOverload::of(&QProcess::finished), setCursor, &QProcess::deleteLater); - - // wait for finished - if (!setCursor->waitForFinished(1000)) { - qWarning() << "Could not setup default cursor"; - setCursor->kill(); - } + void XorgDisplayServer::createAuthFile() + { + // Create auth directory + QString authDir = QStringLiteral(RUNTIME_DIR); + if (daemonApp->testing()) + authDir = QStringLiteral("."); + QDir().mkpath(authDir); + + // Set auth path + m_authPath = QStringLiteral("%1/%2").arg(authDir).arg(QUuid::createUuid().toString()); + + // Generate cookie + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, 15); + + // Reseve 32 bytes + m_cookie.reserve(32); - // start display setup script - qDebug() << "Running display setup script " << displayCommand; - displayScript->start(displayCommand); + // Create a random hexadecimal number + const char *digits = "0123456789abcdef"; + for (int i = 0; i < 32; ++i) + m_cookie[i] = digits[dis(gen)]; + } - // delete displayScript on finish - connect(displayScript, QOverload::of(&QProcess::finished), displayScript, &QProcess::deleteLater); + void XorgDisplayServer::addCookie(const QString &fileName) + { + qDebug() << "Adding cookie to" << fileName; - // wait for finished - if (!displayScript->waitForFinished(30000)) - displayScript->kill(); + // Touch file + QFile fileHandler(fileName); + fileHandler.open(QIODevice::Append); + fileHandler.close(); - // reload config if needed - mainConfig.load(); + // Run xauth + QString cmd = QStringLiteral("%1 -f %2 -q").arg(mainConfig.X11.XauthPath.get()).arg(fileName); + FILE *fp = popen(qPrintable(cmd), "w"); + if (!fp) + return; + fprintf(fp, "remove %s\n", qPrintable(m_display)); + fprintf(fp, "add %s . %s\n", qPrintable(m_display), qPrintable(m_cookie)); + fprintf(fp, "exit\n"); + pclose(fp); } - void XorgDisplayServer::changeOwner(const QString &fileName) { - // change the owner and group of the auth file to the sddm user + 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 (pw) { if (chown(qPrintable(fileName), pw->pw_uid, pw->pw_gid) == -1) qWarning() << "Failed to change owner of the auth file."; + } else { + qWarning() << "Failed to find the sddm user. Owner of the auth file will not be changed."; + } + } + + void XorgDisplayServer::handleNewConnection() + { + while (m_socketServer->hasPendingConnections()) { + X11HelperMessage m = X11HelperMessage::Unknown; + QLocalSocket *socket = m_socketServer->nextPendingConnection(); + SafeDataStream str(socket); + str.receive(); + str >> m; + + if (m == X11HelperMessage::Started) { + str >> m_display; + + qInfo("X11 server started"); + + // Add cookie to auth file + addCookie(m_authPath); + + m_started = true; + emit started(); + } } } + + void XorgDisplayServer::onRequestChanged() + { + m_auth->request()->setFinishAutomatically(true); + } + + void XorgDisplayServer::onSessionStarted(bool success) + { + if (!success) + qWarning("X11 server failed to start"); + } + + void XorgDisplayServer::onHelperFinished(Auth::HelperExitStatus status) + { + finished(); + + m_auth->deleteLater(); + m_auth = nullptr; + } + + void XorgDisplayServer::authInfo(const QString &message, Auth::Info info) + { + Q_UNUSED(info) + qDebug("Message from X11 server: %s", qPrintable(message)); + } + + void XorgDisplayServer::authError(const QString &message, Auth::Error error) + { + Q_UNUSED(error) + qWarning("Error from X11 server: %s", qPrintable(message)); + } } diff --git a/src/daemon/XorgDisplayServer.h b/src/daemon/XorgDisplayServer.h index e97a0b531..d81e081fb 100644 --- a/src/daemon/XorgDisplayServer.h +++ b/src/daemon/XorgDisplayServer.h @@ -21,9 +21,11 @@ #ifndef SDDM_XORGDISPLAYSERVER_H #define SDDM_XORGDISPLAYSERVER_H +#include "Auth.h" #include "DisplayServer.h" class QProcess; +class QLocalServer; namespace SDDM { class XorgDisplayServer : public DisplayServer { @@ -46,15 +48,25 @@ namespace SDDM { bool start(); void stop(); void finished(); - void setupDisplay(); private: + QLocalServer *m_socketServer = nullptr; + QProcess *m_process = nullptr; + Auth *m_auth = nullptr; + QString m_authPath; QString m_cookie; - QProcess *process { nullptr }; - + void createAuthFile(); void changeOwner(const QString &fileName); + + private slots: + void handleNewConnection(); + void onRequestChanged(); + void onSessionStarted(bool success); + void onHelperFinished(Auth::HelperExitStatus status); + void authInfo(const QString &message, Auth::Info info); + void authError(const QString &message, Auth::Error error); }; } diff --git a/src/x11helper/CMakeLists.txt b/src/x11helper/CMakeLists.txt new file mode 100644 index 000000000..25c056b26 --- /dev/null +++ b/src/x11helper/CMakeLists.txt @@ -0,0 +1,17 @@ +include_directories( + "${CMAKE_SOURCE_DIR}/src/common" + "${CMAKE_BINARY_DIR}/src/common" +) + +set(SOURCES + ${CMAKE_SOURCE_DIR}/src/common/Configuration.cpp + ${CMAKE_SOURCE_DIR}/src/common/ConfigReader.cpp + ${CMAKE_SOURCE_DIR}/src/common/SafeDataStream.cpp + main.cpp + runner.cpp + runner.h +) + +add_executable(sddm-x11-helper ${SOURCES}) +target_link_libraries(sddm-x11-helper Qt5::Core Qt5::Network) +install(TARGETS sddm-x11-helper DESTINATION "${CMAKE_INSTALL_LIBEXECDIR}") diff --git a/src/x11helper/main.cpp b/src/x11helper/main.cpp new file mode 100644 index 000000000..a2865f0a9 --- /dev/null +++ b/src/x11helper/main.cpp @@ -0,0 +1,100 @@ +/*************************************************************************** +* Copyright (C) 2019 Pier Luigi Fiorini +* +* 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 +#include + +#include "runner.h" + +#define TR(x) QT_TRANSLATE_NOOP("Command line parser", QStringLiteral(x)) + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + app.setApplicationName(QStringLiteral("sddm-x11-helper")); + app.setOrganizationName(QStringLiteral("SDDM")); + + QCommandLineParser parser; + parser.setApplicationDescription(TR("SDDM X11 Helper")); + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption socketOption(QStringLiteral("socket"), + TR("Socket name"), + TR("name")); + parser.addOption(socketOption); + + QCommandLineOption testModeOption(QStringLiteral("test-mode"), + TR("Enable test mode")); + parser.addOption(testModeOption); + + QCommandLineOption seatOption(QStringLiteral("seat"), + TR("Seat name"), + TR("seat")); + parser.addOption(seatOption); + + QCommandLineOption vtOption(QStringLiteral("vt"), + TR("Terminal identifier"), + TR("number")); + parser.addOption(vtOption); + + QCommandLineOption authFileOption(QStringLiteral("auth"), + TR("Auth file path"), + TR("path")); + parser.addOption(authFileOption); + + parser.process(app); + + if (!parser.isSet(socketOption)) { + qCritical("Please specify a socket name with --socket"); + return 127; + } + if (!parser.isSet(seatOption)) { + qCritical("Please specify a seat name with --seat"); + return 127; + } + if (!parser.isSet(vtOption)) { + qCritical("Please specify a terminal identifier with --vt"); + return 127; + } + if (!parser.isSet(authFileOption)) { + qCritical("Please specify an auth file with --auth"); + return 127; + } + + bool vtOptionValid = false; + int vt = parser.value(vtOption).toInt(&vtOptionValid); + if (!vtOptionValid) { + qCritical("Terminal identifier must be a number"); + return 127; + } + + auto *runner = new Runner(); + runner->setSocketName(parser.value(socketOption)); + runner->setTestMode(parser.isSet(testModeOption)); + runner->setSeat(parser.value(seatOption)); + runner->setTerminalId(vt); + runner->setAuthPath(parser.value(authFileOption)); + if (!runner->start()) { + runner->stop(); + return 1; + } + + return app.exec(); +} diff --git a/src/x11helper/runner.cpp b/src/x11helper/runner.cpp new file mode 100644 index 000000000..b2772b378 --- /dev/null +++ b/src/x11helper/runner.cpp @@ -0,0 +1,313 @@ +/*************************************************************************** +* Copyright (C) 2019 Pier Luigi Fiorini +* +* 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 +#include +#include +#include +#include +#include + +#include "runner.h" + +#include "Configuration.h" +#include "Constants.h" +#include "SafeDataStream.h" +#include "x11helperprotocol.h" + +#include + +Runner::Runner(QObject *parent) + : QObject(parent) + , m_socket(new QLocalSocket(this)) +{ + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, + this, &Runner::deleteLater); +} + +Runner::~Runner() +{ + m_socket->close(); + + stop(); +} + +QString Runner::socketName() const +{ + return m_socketName; +} + +void Runner::setSocketName(const QString &name) +{ + if (!m_socketName.isEmpty()) { + qWarning("Cannot set socket name after initialization"); + return; + } + + m_socketName = name; + + m_socket->connectToServer(m_socketName, QIODevice::ReadWrite | QIODevice::Unbuffered); + if (!m_socket->waitForConnected(2500)) + qCritical("Failed to connect to the daemon: %s", qPrintable(m_socket->errorString())); +} + +bool Runner::isTestMode() const +{ + return m_testMode; +} + +void Runner::setTestMode(bool testMode) +{ + m_testMode = testMode; +} + +QString Runner::display() const +{ + return m_display; +} + +QString Runner::seat() const +{ + return m_seat; +} + +void Runner::setSeat(const QString &seat) +{ + if (!m_seat.isEmpty()) { + qWarning("Cannot set seat name after initialization"); + return; + } + + m_seat = seat; +} + +int Runner::terminalId() const +{ + return m_terminalId; +} + +void Runner::setTerminalId(int id) +{ + m_terminalId = id; +} + +QString Runner::authPath() const +{ + return m_authPath; +} + +void Runner::setAuthPath(const QString &path) +{ + if (!m_authPath.isEmpty()) { + qWarning("Cannot set auth file path after initialization"); + return; + } + + m_authPath = path; +} + +bool Runner::start() +{ + if (m_process) + m_process->deleteLater(); + + m_process = new QProcess(this); + connect(m_process, &QProcess::started, + this, &Runner::started); + connect(m_process, QOverload::of(&QProcess::finished), + this, &Runner::finished); + + if (m_testMode) { + QStringList args; + QDir x11socketDir(QStringLiteral("/tmp/.X11-unix")); + int display = 100; + while (x11socketDir.exists(QStringLiteral("X%1").arg(display))) { + ++display; + } + m_display = QStringLiteral(":%1").arg(display); + args << m_display << QStringLiteral("-ac") + << QStringLiteral("-br") + << QStringLiteral("-noreset") + << QStringLiteral("-screen") << QStringLiteral("800x600"); + m_process->start(SDDM::mainConfig.X11.XephyrPath.get(), args); + if (!m_process->waitForStarted()) { + qCritical("Failed to start X11 server process"); + return false; + } + } else { + // Set process environment + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(QStringLiteral("XCURSOR_THEME"), SDDM::mainConfig.Theme.CursorTheme.get()); + m_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"); + return false; + } + + // Start display server + QStringList args = SDDM::mainConfig.X11.ServerArguments.get().split(QLatin1Char(' '), QString::SkipEmptyParts); + args << QStringLiteral("-auth") << m_authPath + << QStringLiteral("-background") << QStringLiteral("none") + << QStringLiteral("-noreset") + << QStringLiteral("-keeptty") + << QStringLiteral("-displayfd") << QString::number(pipeFds[1]) + << QStringLiteral("-seat") << m_seat; + if (m_seat == QLatin1String("seat0")) + args << QStringLiteral("vt%1").arg(m_terminalId); + qDebug() << "Running:" + << qPrintable(SDDM::mainConfig.X11.ServerPath.get()) + << qPrintable(args.join(QLatin1Char(' '))); + m_process->start(SDDM::mainConfig.X11.ServerPath.get(), args); + if (!m_process->waitForStarted()) { + qCritical("Failed to start X11 server process"); + 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 X11 server"); + close(pipeFds[0]); + 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]); + return false; + } + displayNumber.prepend(QByteArray(":")); + displayNumber.remove(displayNumber.size() -1, 1); // trim trailing whitespace + m_display = QString::fromLocal8Bit(displayNumber); + close(pipeFds[0]); + } + + // Send it back to the daemon + SDDM::SafeDataStream str(m_socket); + str << SDDM::X11HelperMessage::Started << m_display; + str.send(); + + return true; +} + +void Runner::stop() +{ + // Terminate process + if (m_process) { + m_process->terminate(); + if (!m_process->waitForFinished(5000)) + m_process->kill(); + } +} + +void Runner::started() +{ + if (m_display.isEmpty()) + return; + + QString displayCommand = SDDM::mainConfig.X11.DisplayCommand.get(); + + // Create cursor setup process + QProcess *setCursor = new QProcess(); + + // Create display setup script process + QProcess *displayScript = new QProcess(); + + // Set process environment + QProcessEnvironment env; + env.insert(QStringLiteral("DISPLAY"), m_display); + env.insert(QStringLiteral("HOME"), QStringLiteral("/")); + env.insert(QStringLiteral("PATH"), SDDM::mainConfig.Users.DefaultPath.get()); + env.insert(QStringLiteral("XAUTHORITY"), m_authPath); + env.insert(QStringLiteral("SHELL"), QStringLiteral("/bin/sh")); + env.insert(QStringLiteral("XCURSOR_THEME"), SDDM::mainConfig.Theme.CursorTheme.get()); + setCursor->setProcessEnvironment(env); + displayScript->setProcessEnvironment(env); + + qDebug() << "Setting default cursor"; + setCursor->start(QStringLiteral("xsetroot -cursor_name left_ptr")); + + // Delete setCursor on finish + connect(setCursor, QOverload::of(&QProcess::finished), + setCursor, &QProcess::deleteLater); + + // Wait for finished + if (!setCursor->waitForFinished(1000)) { + qWarning() << "Could not set default cursor"; + setCursor->kill(); + } + + // Start display setup script + qDebug() << "Running display setup script:" << displayCommand; + displayScript->start(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 + SDDM::mainConfig.load(); +} + +void Runner::finished() +{ + if (!m_display.isEmpty()) { + QString displayStopCommand = SDDM::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"), SDDM::mainConfig.Users.DefaultPath.get()); + env.insert(QStringLiteral("SHELL"), QStringLiteral("/bin/sh")); + displayStopScript->setProcessEnvironment(env); + + // Start display stop script + qDebug() << "Running display stop script:" << displayStopCommand; + displayStopScript->start(displayStopCommand); + + // Wait for finished + if (!displayStopScript->waitForFinished(5000)) + displayStopScript->kill(); + + // Clean up the script process + displayStopScript->deleteLater(); + displayStopScript = nullptr; + } + + // Clean up + m_process->deleteLater(); + m_process = nullptr; +} diff --git a/src/x11helper/runner.h b/src/x11helper/runner.h new file mode 100644 index 000000000..0cc2f4918 --- /dev/null +++ b/src/x11helper/runner.h @@ -0,0 +1,71 @@ +/*************************************************************************** +* Copyright (C) 2019 Pier Luigi Fiorini +* +* 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. +***************************************************************************/ + +#ifndef SDDMX11HELPER_RUNNER_H +#define SDDMX11HELPER_RUNNER_H + +#include + +class QLocalSocket; +class QProcess; + +class Runner : public QObject +{ + Q_OBJECT +public: + explicit Runner(QObject *parent = nullptr); + ~Runner(); + + QString socketName() const; + void setSocketName(const QString &name); + + bool isTestMode() const; + void setTestMode(bool testMode); + + QString display() const; + + QString seat() const; + void setSeat(const QString &seat); + + int terminalId() const; + void setTerminalId(int id); + + QString authPath() const; + void setAuthPath(const QString &path); + + bool start(); + void stop(); + +private: + QString m_socketName; + bool m_testMode = false; + QString m_display; + QString m_seat; + int m_terminalId = 0; + QString m_authPath; + + QLocalSocket *m_socket = nullptr; + QProcess *m_process = nullptr; + +private slots: + void started(); + void finished(); +}; + +#endif // SDDMX11HELPER_RUNNER_H