summaryrefslogtreecommitdiff
path: root/user/sddm/rootless-xorg.patch
diff options
context:
space:
mode:
Diffstat (limited to 'user/sddm/rootless-xorg.patch')
-rw-r--r--user/sddm/rootless-xorg.patch2012
1 files changed, 2012 insertions, 0 deletions
diff --git a/user/sddm/rootless-xorg.patch b/user/sddm/rootless-xorg.patch
new file mode 100644
index 000000000..99a2e189d
--- /dev/null
+++ b/user/sddm/rootless-xorg.patch
@@ -0,0 +1,2012 @@
+From 9881225a92ffe6cf3d4a84273192b08b2d2f6046 Mon Sep 17 00:00:00 2001
+From: Pier Luigi Fiorini <pierluigi.fiorini@liri.io>
+Date: Tue, 27 Aug 2019 22:31:00 +0200
+Subject: [PATCH] X11 display server without root privileges
+
+Introduce an alternative display server option to run the greeter with
+the X11 server as an unprivileged user.
+
+Root privileges are required by default, but this will change in the
+future.
+
+Greeter output is forwarded to the helper process, instead of saving it
+into a separate file like user sessions do.
+This means the greeter output is available in the journal with the
+daemon and helper logs.
+
+Display start and stop commands are executed as sddm user and this might
+break the workflow of our users.
+That is the reason why we still run Xorg as root for the greeter by
+default.
+
+X11 user session always spawn a new display server without root
+privileges.
+
+Closes: #246
+---
+ CMakeLists.txt | 1 +
+ data/man/sddm.conf.rst.in | 14 +-
+ src/auth/Auth.cpp | 24 ++-
+ src/auth/Auth.h | 14 ++
+ src/auth/AuthMessages.h | 1 +
+ src/common/Configuration.h | 3 +
+ src/common/Session.cpp | 10 ++
+ src/common/Session.h | 4 +
+ src/common/XAuth.cpp | 127 ++++++++++++++
+ src/common/XAuth.h | 55 ++++++
+ src/daemon/CMakeLists.txt | 6 +-
+ src/daemon/Display.cpp | 64 +++++--
+ src/daemon/Display.h | 11 ++
+ src/daemon/Greeter.cpp | 41 +++--
+ src/daemon/Greeter.h | 5 +
+ src/daemon/XorgDisplayServer.cpp | 78 ++-------
+ src/daemon/XorgDisplayServer.h | 10 +-
+ src/daemon/XorgUserDisplayServer.cpp | 102 +++++++++++
+ src/daemon/XorgUserDisplayServer.h | 53 ++++++
+ src/greeter/GreeterApp.cpp | 9 +
+ src/helper/Backend.cpp | 5 +
+ src/helper/Backend.h | 2 +
+ src/helper/CMakeLists.txt | 4 +
+ src/helper/HelperApp.cpp | 30 +++-
+ src/helper/HelperApp.h | 1 +
+ src/helper/UserSession.cpp | 202 +++++++++++++--------
+ src/helper/UserSession.h | 20 ++-
+ src/helper/backend/PamBackend.cpp | 6 +-
+ src/helper/xorguserhelper.cpp | 252 +++++++++++++++++++++++++++
+ src/helper/xorguserhelper.h | 64 +++++++
+ 30 files changed, 1036 insertions(+), 182 deletions(-)
+ create mode 100644 src/common/XAuth.cpp
+ create mode 100644 src/common/XAuth.h
+ create mode 100644 src/daemon/XorgUserDisplayServer.cpp
+ create mode 100644 src/daemon/XorgUserDisplayServer.h
+ create mode 100644 src/helper/xorguserhelper.cpp
+ create mode 100644 src/helper/xorguserhelper.h
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 9614b4e1e..41aee21d4 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -16,6 +16,7 @@ set(SDDM_VERSION_MAJOR 0)
+ set(SDDM_VERSION_MINOR 19)
+ set(SDDM_VERSION_PATCH 0)
+ set(SDDM_VERSION_STRING "${SDDM_VERSION_MAJOR}.${SDDM_VERSION_MINOR}.${SDDM_VERSION_PATCH}")
++add_compile_definitions("SDDM_VERSION=\"${SDDM_VERSION_STRING}\"")
+
+ # Set up packaging
+ set(CPACK_PACKAGE_NAME "sddm")
+diff --git a/data/man/sddm.conf.rst.in b/data/man/sddm.conf.rst.in
+index 1061540c0..960e65c68 100644
+--- a/data/man/sddm.conf.rst.in
++++ b/data/man/sddm.conf.rst.in
+@@ -6,7 +6,7 @@
+ sddm display manager configuration
+ ----------------------------------
+
+-:Date: August 2015
++:Date: March 2021
+ :Version: sddm @SDDM_VERSION_STRING@
+ :Manual section: 5
+ :Manual group: sddm
+@@ -36,6 +36,14 @@ OPTIONS
+
+ [General] section:
+
++`DisplayServer=`
++ Select the display server to use for the greeter.
++ Valid values are:
++ * `x11`: X server running as root.
++ * `x11-user`: X server running as unprivileged user.
++ Default value is "x11".
++ For `x11-user` you might need to configure Xorg.wrap(1).
++
+ `HaltCommand=`
+ Halt command.
+ Default value is "@HALT_COMMAND@".
+@@ -134,10 +142,14 @@ OPTIONS
+
+ `DisplayCommand=`
+ Path of script to execute when starting the display server.
++ The script will be executed as root when General.DisplayServer
++ is "x11", otherwise as sddm user.
+ Default value is "@DATA_INSTALL_DIR@/scripts/Xsetup".
+
+ `DisplayStopCommand=`
+ Path of script to execute when stopping the display server.
++ The script will be executed as root when General.DisplayServer
++ is "x11", otherwise as sddm user.
+ Default value is "@DATA_INSTALL_DIR@/scripts/Xstop".
+
+ `MinimumVT=`
+diff --git a/src/auth/Auth.cpp b/src/auth/Auth.cpp
+index 403186ee1..4450cc606 100644
+--- a/src/auth/Auth.cpp
++++ b/src/auth/Auth.cpp
+@@ -62,6 +62,7 @@ namespace SDDM {
+ AuthRequest *request { nullptr };
+ QProcess *child { nullptr };
+ QLocalSocket *socket { nullptr };
++ QString displayServerCmd;
+ QString sessionPath { };
+ QString user { };
+ QString cookie { };
+@@ -202,6 +203,15 @@ namespace SDDM {
+ str.send();
+ break;
+ }
++ case DISPLAY_SERVER_STARTED: {
++ QString displayName;
++ str >> displayName;
++ Q_EMIT auth->displayServerReady(displayName);
++ str.reset();
++ str << DISPLAY_SERVER_STARTED;
++ str.send();
++ break;
++ }
+ default: {
+ Q_EMIT auth->error(QStringLiteral("Auth: Unexpected value received: %1").arg(m), ERROR_INTERNAL);
+ }
+@@ -210,7 +220,9 @@ namespace SDDM {
+
+ void Auth::Private::childExited(int exitCode, QProcess::ExitStatus exitStatus) {
+ if (exitStatus != QProcess::NormalExit) {
+- qWarning("Auth: sddm-helper crashed (exit code %d)", exitCode);
++ qWarning("Auth: sddm-helper (%s) crashed (exit code %d)",
++ qPrintable(child->arguments().join(QLatin1Char(' '))),
++ HelperExitStatus(exitStatus));
+ Q_EMIT qobject_cast<Auth*>(parent())->error(child->errorString(), ERROR_INTERNAL);
+ }
+
+@@ -334,6 +346,14 @@ namespace SDDM {
+ }
+ }
+
++ void Auth::setDisplayServerCommand(const QString &command)
++ {
++ if (d->displayServerCmd != command) {
++ d->displayServerCmd = command;
++ Q_EMIT displayServerCommandChanged();
++ }
++ }
++
+ void Auth::setSession(const QString& path) {
+ if (path != d->sessionPath) {
+ d->sessionPath = path;
+@@ -361,6 +381,8 @@ namespace SDDM {
+ args << QStringLiteral("--user") << d->user;
+ if (d->autologin)
+ args << QStringLiteral("--autologin");
++ if (!d->displayServerCmd.isEmpty())
++ args << QStringLiteral("--display-server") << d->displayServerCmd;
+ if (d->greeter)
+ args << QStringLiteral("--greeter");
+ d->child->start(QStringLiteral("%1/sddm-helper").arg(QStringLiteral(LIBEXEC_INSTALL_DIR)), args);
+diff --git a/src/auth/Auth.h b/src/auth/Auth.h
+index f7cb8acd3..dc3df24d1 100644
+--- a/src/auth/Auth.h
++++ b/src/auth/Auth.h
+@@ -141,6 +141,12 @@ namespace SDDM {
+ */
+ void setUser(const QString &user);
+
++ /**
++ * Set the display server command to be started before the greeter.
++ * @param command Command of the display server to be started
++ */
++ void setDisplayServerCommand(const QString &command);
++
+ /**
+ * Set the session to be started after authenticating.
+ * @param path Path of the session executable to be started
+@@ -165,6 +171,7 @@ namespace SDDM {
+ void verboseChanged();
+ void cookieChanged();
+ void userChanged();
++ void displayServerCommandChanged();
+ void sessionChanged();
+ void requestChanged();
+
+@@ -186,6 +193,13 @@ namespace SDDM {
+ */
+ void sessionStarted(bool success, qint64 pid);
+
++ /**
++ * Emitted when the display server is ready.
++ *
++ * @param displayName display name
++ */
++ void displayServerReady(const QString &displayName);
++
+ /**
+ * Emitted when the helper quits, either after authentication or when the session ends.
+ * Or, when something goes wrong.
+diff --git a/src/auth/AuthMessages.h b/src/auth/AuthMessages.h
+index 3bc97b6ba..6aea7483f 100644
+--- a/src/auth/AuthMessages.h
++++ b/src/auth/AuthMessages.h
+@@ -97,6 +97,7 @@ namespace SDDM {
+ REQUEST,
+ AUTHENTICATED,
+ SESSION_STATUS,
++ DISPLAY_SERVER_STARTED,
+ MSG_LAST,
+ };
+
+diff --git a/src/common/Configuration.h b/src/common/Configuration.h
+index b79871988..47bfa2710 100644
+--- a/src/common/Configuration.h
++++ b/src/common/Configuration.h
+@@ -37,6 +37,9 @@ namespace SDDM {
+ enum NumState { NUM_NONE, NUM_SET_ON, NUM_SET_OFF };
+
+ // Name Type Default value Description
++ // TODO: Change default to x11-user in a future release
++ Entry(DisplayServer, QString, _S("x11"), _S("Which display server should be used.\n"
++ "Valid values are: x11, x11-user."));
+ Entry(HaltCommand, QString, _S(HALT_COMMAND), _S("Halt command"));
+ Entry(RebootCommand, QString, _S(REBOOT_COMMAND), _S("Reboot command"));
+ Entry(Numlock, NumState, NUM_NONE, _S("Initial NumLock state. Can be on, off or none.\n"
+diff --git a/src/common/Session.cpp b/src/common/Session.cpp
+index a026c1f87..1b932c57a 100644
+--- a/src/common/Session.cpp
++++ b/src/common/Session.cpp
+@@ -52,6 +52,16 @@ namespace SDDM {
+ return m_type;
+ }
+
++ int Session::vt() const
++ {
++ return m_vt;
++ }
++
++ void Session::setVt(int vt)
++ {
++ m_vt = vt;
++ }
++
+ QString Session::xdgSessionType() const
+ {
+ return m_xdgSessionType;
+diff --git a/src/common/Session.h b/src/common/Session.h
+index aa196e9c6..3abc993fb 100644
+--- a/src/common/Session.h
++++ b/src/common/Session.h
+@@ -43,6 +43,9 @@ namespace SDDM {
+
+ Type type() const;
+
++ int vt() const;
++ void setVt(int vt);
++
+ QString xdgSessionType() const;
+
+ QDir directory() const;
+@@ -70,6 +73,7 @@ namespace SDDM {
+ QProcessEnvironment parseEnv(const QString &list);
+ bool m_valid;
+ Type m_type;
++ int m_vt = 0;
+ QDir m_dir;
+ QString m_name;
+ QString m_fileName;
+diff --git a/src/common/XAuth.cpp b/src/common/XAuth.cpp
+new file mode 100644
+index 000000000..bc7b10caf
+--- /dev/null
++++ b/src/common/XAuth.cpp
+@@ -0,0 +1,127 @@
++/***************************************************************************
++* Copyright (c) 2021 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
++* Copyright (c) 2013 Abdurrahman AVCI <abdurrahmanavci@gmail.com>
++*
++* 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 <QDebug>
++#include <QDir>
++#include <QUuid>
++
++#include "Configuration.h"
++#include "Constants.h"
++#include "XAuth.h"
++
++#include <random>
++
++namespace SDDM {
++
++XAuth::XAuth()
++{
++ m_authDir = QStringLiteral(RUNTIME_DIR);
++}
++
++QString XAuth::authDirectory() const
++{
++ return m_authDir;
++}
++
++void XAuth::setAuthDirectory(const QString &path)
++{
++ if (m_setup) {
++ qWarning("Unable to set xauth directory after setup");
++ return;
++ }
++
++ m_authDir = path;
++}
++
++QString XAuth::authPath() const
++{
++ return m_authPath;
++}
++
++QString XAuth::cookie() const
++{
++ return m_cookie;
++}
++
++void XAuth::setup()
++{
++ if (m_setup)
++ return;
++
++ m_setup = true;
++
++ // Create directory if not existing
++ QDir().mkpath(m_authDir);
++
++ // Set path
++ m_authPath = QStringLiteral("%1/%2").arg(m_authDir).arg(QUuid::createUuid().toString(QUuid::WithoutBraces));
++ qDebug() << "Xauthority path:" << m_authPath;
++
++ // Generate cookie
++ std::random_device rd;
++ std::mt19937 gen(rd());
++ std::uniform_int_distribution<> dis(0, 15);
++
++ // Reseve 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] = QLatin1Char(digits[dis(gen)]);
++}
++
++bool XAuth::addCookie(const QString &display)
++{
++ if (!m_setup) {
++ qWarning("Please setup xauth before adding a cookie");
++ return false;
++ }
++
++ return XAuth::addCookieToFile(display, m_authPath, m_cookie);
++}
++
++bool XAuth::addCookieToFile(const QString &display, const QString &fileName,
++ const QString &cookie)
++{
++ qDebug() << "Adding cookie to" << fileName;
++
++ // Touch file
++ QFile file_handler(fileName);
++ file_handler.open(QIODevice::Append);
++ file_handler.close();
++
++ QString cmd = QStringLiteral("%1 -f %2 -q").arg(mainConfig.X11.XauthPath.get()).arg(fileName);
++
++ // Execute xauth
++ FILE *fp = ::popen(qPrintable(cmd), "w");
++
++ // Check file
++ if (!fp)
++ return false;
++ fprintf(fp, "remove %s\n", qPrintable(display));
++ fprintf(fp, "add %s . %s\n", qPrintable(display), qPrintable(cookie));
++ fprintf(fp, "exit\n");
++
++ // Close pipe
++ return pclose(fp) == 0;
++}
++
++} // namespace SDDM
+diff --git a/src/common/XAuth.h b/src/common/XAuth.h
+new file mode 100644
+index 000000000..3e80f4ead
+--- /dev/null
++++ b/src/common/XAuth.h
+@@ -0,0 +1,55 @@
++/***************************************************************************
++* Copyright (c) 2021 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
++* Copyright (c) 2013 Abdurrahman AVCI <abdurrahmanavci@gmail.com>
++*
++* 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_XAUTH_H
++#define SDDM_XAUTH_H
++
++#include <QString>
++
++namespace SDDM {
++
++class XAuth
++{
++public:
++ XAuth();
++
++ QString authDirectory() const;
++ void setAuthDirectory(const QString &path);
++
++ QString authPath() const;
++ QString cookie() const;
++
++ void setup();
++ bool addCookie(const QString &display);
++
++ static bool addCookieToFile(const QString &display,
++ const QString &fileName,
++ const QString &cookie);
++
++private:
++ bool m_setup = false;
++ QString m_authDir;
++ QString m_authPath;
++ QString m_cookie;
++};
++
++} // namespace SDDM
++
++#endif // SDDM_XAUTH_H
+diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt
+index 86d014bec..b411e42bd 100644
+--- a/src/daemon/CMakeLists.txt
++++ b/src/daemon/CMakeLists.txt
+@@ -13,6 +13,8 @@ set(DAEMON_SOURCES
+ ${CMAKE_SOURCE_DIR}/src/common/ThemeMetadata.cpp
+ ${CMAKE_SOURCE_DIR}/src/common/Session.cpp
+ ${CMAKE_SOURCE_DIR}/src/common/SocketWriter.cpp
++ ${CMAKE_SOURCE_DIR}/src/common/XAuth.cpp
++ ${CMAKE_SOURCE_DIR}/src/common/XAuth.h
+ ${CMAKE_SOURCE_DIR}/src/auth/Auth.cpp
+ ${CMAKE_SOURCE_DIR}/src/auth/AuthPrompt.cpp
+ ${CMAKE_SOURCE_DIR}/src/auth/AuthRequest.cpp
+@@ -22,13 +24,15 @@ set(DAEMON_SOURCES
+ DisplayManager.cpp
+ DisplayServer.cpp
+ LogindDBusTypes.cpp
+- XorgDisplayServer.cpp
+ Greeter.cpp
+ PowerManager.cpp
+ Seat.cpp
+ SeatManager.cpp
+ SignalHandler.cpp
+ SocketServer.cpp
++ XorgDisplayServer.cpp
++ XorgUserDisplayServer.cpp
++ XorgUserDisplayServer.h
+ )
+
+ # Different implementations of the VT switching code
+diff --git a/src/daemon/Display.cpp b/src/daemon/Display.cpp
+index 3c77454fc..26ca3bb60 100644
+--- a/src/daemon/Display.cpp
++++ b/src/daemon/Display.cpp
+@@ -25,6 +25,7 @@
+ #include "DaemonApp.h"
+ #include "DisplayManager.h"
+ #include "XorgDisplayServer.h"
++#include "XorgUserDisplayServer.h"
+ #include "Seat.h"
+ #include "SocketServer.h"
+ #include "Greeter.h"
+@@ -56,7 +57,6 @@
+ namespace SDDM {
+ Display::Display(Seat *parent) : QObject(parent),
+ m_auth(new Auth(this)),
+- m_displayServer(new XorgDisplayServer(this)),
+ m_seat(parent),
+ m_socketServer(new SocketServer(this)),
+ m_greeter(new Greeter(this)) {
+@@ -64,6 +64,32 @@ namespace SDDM {
+ // Allocate vt
+ m_terminalId = VirtualTerminal::setUpNewVt();
+
++ // Save display server type
++ const QString &displayServerType = mainConfig.DisplayServer.get().toLower();
++ if (displayServerType == QLatin1String("x11"))
++ m_displayServerType = X11DisplayServerType;
++ else if (displayServerType == QStringLiteral("x11-user"))
++ m_displayServerType = X11UserDisplayServerType;
++ else {
++ qWarning("\"%s\" is an invalid value for General.DisplayServer: fall back to \"x11\"",
++ qPrintable(displayServerType));
++ m_displayServerType = X11DisplayServerType;
++ }
++
++ // Create display server
++ switch (m_displayServerType) {
++ case X11DisplayServerType:
++ m_displayServer = new XorgDisplayServer(this);
++ break;
++ case X11UserDisplayServerType:
++ m_displayServer = new XorgUserDisplayServer(this);
++ m_greeter->setDisplayServerCommand(XorgUserDisplayServer::command(this));
++ break;
++ }
++
++ // Print what VT we are using for more information
++ qDebug("Using VT %d", m_terminalId);
++
+ // respond to authentication requests
+ m_auth->setVerbose(true);
+ connect(m_auth, &Auth::requestChanged, this, &Display::slotRequestChanged);
+@@ -89,6 +115,16 @@ namespace SDDM {
+ stop();
+ }
+
++ Display::DisplayServerType Display::displayServerType() const
++ {
++ return m_displayServerType;
++ }
++
++ DisplayServer *Display::displayServer() const
++ {
++ return m_displayServer;
++ }
++
+ QString Display::displayId() const {
+ return m_displayServer->display();
+ }
+@@ -181,7 +217,8 @@ namespace SDDM {
+
+ // set greeter params
+ m_greeter->setDisplay(this);
+- m_greeter->setAuthPath(qobject_cast<XorgDisplayServer *>(m_displayServer)->authPath());
++ if (qobject_cast<XorgDisplayServer *>(m_displayServer))
++ m_greeter->setAuthPath(qobject_cast<XorgDisplayServer *>(m_displayServer)->authPath());
+ m_greeter->setSocket(m_socketServer->socketAddress());
+ m_greeter->setTheme(findGreeterTheme());
+
+@@ -317,20 +354,16 @@ namespace SDDM {
+ // last session later, in slotAuthenticationFinished()
+ m_sessionName = session.fileName();
+
++ // New VT
++ m_lastSession.setVt(VirtualTerminal::setUpNewVt());
++
+ // some information
+ qDebug() << "Session" << m_sessionName << "selected, command:" << session.exec();
+
+ QProcessEnvironment env;
+ env.insert(session.additionalEnv());
+
+- if (seat()->name() == QLatin1String("seat0")) {
+- // Use the greeter VT, for wayland sessions the helper overwrites this
+- env.insert(QStringLiteral("XDG_VTNR"), QString::number(terminalId()));
+- }
+-
+ env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get());
+- if (session.xdgSessionType() == QLatin1String("x11"))
+- env.insert(QStringLiteral("DISPLAY"), name());
+ env.insert(QStringLiteral("XDG_SEAT_PATH"), daemonApp->displayManager()->seatPath(seat()->name()));
+ env.insert(QStringLiteral("XDG_SESSION_PATH"), daemonApp->displayManager()->sessionPath(QStringLiteral("Session%1").arg(daemonApp->newSessionId())));
+ env.insert(QStringLiteral("DESKTOP_SESSION"), session.desktopSession());
+@@ -338,10 +371,16 @@ namespace SDDM {
+ env.insert(QStringLiteral("XDG_SESSION_CLASS"), QStringLiteral("user"));
+ env.insert(QStringLiteral("XDG_SESSION_TYPE"), session.xdgSessionType());
+ env.insert(QStringLiteral("XDG_SEAT"), seat()->name());
++ env.insert(QStringLiteral("XDG_VTNR"), QString::number(m_lastSession.vt()));
+ env.insert(QStringLiteral("XDG_SESSION_DESKTOP"), session.desktopNames());
+
+ m_auth->insertEnvironment(env);
+
++ if (session.xdgSessionType() == QLatin1String("x11"))
++ m_auth->setDisplayServerCommand(XorgUserDisplayServer::command(this));
++ else
++ m_auth->setDisplayServerCommand(QStringLiteral());
++
+ m_auth->setUser(user);
+ if (m_reuseSessionId.isNull()) {
+ m_auth->setSession(session.exec());
+@@ -358,7 +397,8 @@ namespace SDDM {
+ manager.UnlockSession(m_reuseSessionId);
+ manager.ActivateSession(m_reuseSessionId);
+ } else {
+- m_auth->setCookie(qobject_cast<XorgDisplayServer *>(m_displayServer)->cookie());
++ if (qobject_cast<XorgDisplayServer *>(m_displayServer))
++ m_auth->setCookie(qobject_cast<XorgDisplayServer *>(m_displayServer)->cookie());
+ }
+
+ // save last user and last session
+@@ -411,6 +451,10 @@ namespace SDDM {
+ // greeter
+ if (status != Auth::HELPER_AUTH_ERROR)
+ stop();
++
++ // Start the greeter again as soon as the user session is closed
++ if (m_auth->user() != QLatin1String("sddm"))
++ m_greeter->start();
+ }
+
+ void Display::slotRequestChanged() {
+diff --git a/src/daemon/Display.h b/src/daemon/Display.h
+index 61dd9f630..a31542cda 100644
+--- a/src/daemon/Display.h
++++ b/src/daemon/Display.h
+@@ -41,9 +41,18 @@ namespace SDDM {
+ Q_OBJECT
+ Q_DISABLE_COPY(Display)
+ public:
++ enum DisplayServerType {
++ X11DisplayServerType,
++ X11UserDisplayServerType
++ };
++ Q_ENUM(DisplayServerType)
++
+ explicit Display(Seat *parent);
+ ~Display();
+
++ DisplayServerType displayServerType() const;
++ DisplayServer *displayServer() const;
++
+ QString displayId() const;
+ const int terminalId() const;
+
+@@ -76,6 +85,8 @@ namespace SDDM {
+ void startAuth(const QString &user, const QString &password,
+ const Session &session);
+
++ DisplayServerType m_displayServerType = X11DisplayServerType;
++
+ bool m_relogin { true };
+ bool m_started { false };
+
+diff --git a/src/daemon/Greeter.cpp b/src/daemon/Greeter.cpp
+index 436ecc3d5..158d8bbbe 100644
+--- a/src/daemon/Greeter.cpp
++++ b/src/daemon/Greeter.cpp
+@@ -27,6 +27,7 @@
+ #include "ThemeConfig.h"
+ #include "ThemeMetadata.h"
+ #include "Display.h"
++#include "XorgUserDisplayServer.h"
+
+ #include <QtCore/QDebug>
+ #include <QtCore/QProcess>
+@@ -71,6 +72,16 @@ namespace SDDM {
+ }
+ }
+
++ QString Greeter::displayServerCommand() const
++ {
++ return m_displayServerCmd;
++ }
++
++ void Greeter::setDisplayServerCommand(const QString &cmd)
++ {
++ m_displayServerCmd = cmd;
++ }
++
+ bool Greeter::start() {
+ // check flag
+ if (m_started)
+@@ -111,17 +122,16 @@ namespace SDDM {
+ // log message
+ qDebug() << "Greeter starting...";
+
+- // set process environment
++ args << QStringLiteral("--test-mode");
++
++ // set process environment
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ env.insert(QStringLiteral("DISPLAY"), m_display->name());
+ env.insert(QStringLiteral("XAUTHORITY"), m_authPath);
+ env.insert(QStringLiteral("XCURSOR_THEME"), xcursorTheme);
+- env.insert(QStringLiteral("QT_IM_MODULE"), mainConfig.InputMethod.get());
+ m_process->setProcessEnvironment(env);
+
+ // start greeter
+- if (daemonApp->testing())
+- args << QStringLiteral("--test-mode");
+ m_process->start(QStringLiteral("%1/sddm-greeter").arg(QStringLiteral(BIN_INSTALL_DIR)), args);
+
+ //if we fail to start bail immediately, and don't block in waitForStarted
+@@ -149,11 +159,12 @@ namespace SDDM {
+ m_auth->setVerbose(true);
+ connect(m_auth, &Auth::requestChanged, this, &Greeter::onRequestChanged);
+ connect(m_auth, &Auth::sessionStarted, this, &Greeter::onSessionStarted);
++ connect(m_auth, &Auth::displayServerReady, this, &Greeter::onDisplayServerReady);
+ connect(m_auth, &Auth::finished, this, &Greeter::onHelperFinished);
+ connect(m_auth, &Auth::info, this, &Greeter::authInfo);
+ connect(m_auth, &Auth::error, this, &Greeter::authError);
+
+- // greeter command
++ // command
+ QStringList cmd;
+ cmd << QStringLiteral("%1/sddm-greeter").arg(QStringLiteral(BIN_INSTALL_DIR))
+ << args;
+@@ -173,8 +184,6 @@ namespace SDDM {
+ }, sysenv, env);
+
+ env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get());
+- env.insert(QStringLiteral("DISPLAY"), m_display->name());
+- env.insert(QStringLiteral("XAUTHORITY"), m_authPath);
+ env.insert(QStringLiteral("XCURSOR_THEME"), xcursorTheme);
+ env.insert(QStringLiteral("XDG_SEAT"), m_display->seat()->name());
+ env.insert(QStringLiteral("XDG_SEAT_PATH"), daemonApp->displayManager()->seatPath(m_display->seat()->name()));
+@@ -183,11 +192,10 @@ namespace SDDM {
+ env.insert(QStringLiteral("XDG_VTNR"), QString::number(m_display->terminalId()));
+ env.insert(QStringLiteral("XDG_SESSION_CLASS"), QStringLiteral("greeter"));
+ env.insert(QStringLiteral("XDG_SESSION_TYPE"), m_display->sessionType());
+- env.insert(QStringLiteral("QT_IM_MODULE"), mainConfig.InputMethod.get());
+-
+- //some themes may use KDE components and that will automatically load KDE's crash handler which we don't want
+- //counterintuitively setting this env disables that handler
+- env.insert(QStringLiteral("KDE_DEBUG"), QStringLiteral("1"));
++ if (m_display->displayServerType() == Display::X11DisplayServerType) {
++ env.insert(QStringLiteral("DISPLAY"), m_display->name());
++ env.insert(QStringLiteral("XAUTHORITY"), m_authPath);
++ }
+ m_auth->insertEnvironment(env);
+
+ // log message
+@@ -195,6 +203,7 @@ namespace SDDM {
+
+ // start greeter
+ m_auth->setUser(QStringLiteral("sddm"));
++ m_auth->setDisplayServerCommand(m_displayServerCmd);
+ m_auth->setGreeter(true);
+ m_auth->setSession(cmd.join(QLatin1Char(' ')));
+ m_auth->start();
+@@ -261,6 +270,14 @@ namespace SDDM {
+ qDebug() << "Greeter session failed to start";
+ }
+
++ void Greeter::onDisplayServerReady(const QString &displayName)
++ {
++ auto *displayServer = m_display->displayServer();
++ auto *xorgUser = qobject_cast<XorgUserDisplayServer *>(displayServer);
++ if (xorgUser)
++ xorgUser->setDisplayName(displayName);
++ }
++
+ void Greeter::onHelperFinished(Auth::HelperExitStatus status) {
+ // reset flag
+ m_started = false;
+diff --git a/src/daemon/Greeter.h b/src/daemon/Greeter.h
+index 7391a3597..bf472375f 100644
+--- a/src/daemon/Greeter.h
++++ b/src/daemon/Greeter.h
+@@ -43,6 +43,9 @@ namespace SDDM {
+ void setSocket(const QString &socket);
+ void setTheme(const QString &theme);
+
++ QString displayServerCommand() const;
++ void setDisplayServerCommand(const QString &cmd);
++
+ public slots:
+ bool start();
+ void stop();
+@@ -51,6 +54,7 @@ namespace SDDM {
+ private slots:
+ void onRequestChanged();
+ void onSessionStarted(bool success);
++ void onDisplayServerReady(const QString &displayName);
+ void onHelperFinished(Auth::HelperExitStatus status);
+ void onReadyReadStandardOutput();
+ void onReadyReadStandardError();
+@@ -64,6 +68,7 @@ namespace SDDM {
+ QString m_authPath;
+ QString m_socket;
+ QString m_themePath;
++ QString m_displayServerCmd;
+ ThemeMetadata *m_metadata { nullptr };
+ ThemeConfig *m_themeConfig { nullptr };
+
+diff --git a/src/daemon/XorgDisplayServer.cpp b/src/daemon/XorgDisplayServer.cpp
+index 331adcda7..fc61ee2dd 100644
+--- a/src/daemon/XorgDisplayServer.cpp
++++ b/src/daemon/XorgDisplayServer.cpp
+@@ -41,31 +41,9 @@
+
+ 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)];
++ m_xauth.setAuthDirectory(QStringLiteral("."));
++ m_xauth.setup();
+ }
+
+ XorgDisplayServer::~XorgDisplayServer() {
+@@ -76,41 +54,16 @@ namespace SDDM {
+ return m_display;
+ }
+
+- const QString &XorgDisplayServer::authPath() const {
+- return m_authPath;
++ QString XorgDisplayServer::authPath() const {
++ return m_xauth.authPath();
+ }
+
+ QString XorgDisplayServer::sessionType() const {
+ return QStringLiteral("x11");
+ }
+
+- const QString &XorgDisplayServer::cookie() const {
+- return m_cookie;
+- }
+-
+- bool XorgDisplayServer::addCookie(const QString &file) {
+- // log message
+- qDebug() << "Adding cookie to" << file;
+-
+- // Touch file
+- QFile file_handler(file);
+- file_handler.open(QIODevice::Append);
+- file_handler.close();
+-
+- QString cmd = QStringLiteral("%1 -f %2 -q").arg(mainConfig.X11.XauthPath.get()).arg(file);
+-
+- // execute xauth
+- FILE *fp = popen(qPrintable(cmd), "w");
+-
+- // check file
+- if (!fp)
+- return false;
+- fprintf(fp, "remove %s\n", qPrintable(m_display));
+- fprintf(fp, "add %s . %s\n", qPrintable(m_display), qPrintable(m_cookie));
+- fprintf(fp, "exit\n");
+-
+- // close pipe
+- return pclose(fp) == 0;
++ QString XorgDisplayServer::cookie() const {
++ return m_xauth.cookie();
+ }
+
+ bool XorgDisplayServer::start() {
+@@ -136,7 +89,7 @@ namespace SDDM {
+ // 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(!addCookie(m_authPath)) {
++ if(!m_xauth.addCookie(m_display)) {
+ qCritical() << "Failed to write xauth file";
+ return false;
+ }
+@@ -159,18 +112,15 @@ namespace SDDM {
+ process->setProgram(mainConfig.X11.ServerPath.get());
+ args << mainConfig.X11.ServerArguments.get().split(QLatin1Char(' '), QString::SkipEmptyParts)
+ << QStringLiteral("-background") << QStringLiteral("none")
+- << QStringLiteral("-seat") << displayPtr()->seat()->name();
+-
+- if (displayPtr()->seat()->name() == QLatin1String("seat0")) {
+- args << QStringLiteral("vt%1").arg(displayPtr()->terminalId());
+- }
++ << 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_authPath
++ args << QStringLiteral("-auth") << m_xauth.authPath()
+ << QStringLiteral("-noreset")
+ << QStringLiteral("-displayfd") << QString::number(pipeFds[1]);
+
+@@ -222,13 +172,13 @@ namespace SDDM {
+ // 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(!addCookie(m_authPath)) {
++ if(!m_xauth.addCookie(m_display)) {
+ qCritical() << "Failed to write xauth file";
+ stop();
+ return false;
+ }
+ }
+- changeOwner(m_authPath);
++ changeOwner(m_xauth.authPath());
+
+ emit started();
+
+@@ -297,7 +247,7 @@ namespace SDDM {
+ displayStopScript = nullptr;
+
+ // remove authority file
+- QFile::remove(m_authPath);
++ QFile::remove(m_xauth.authPath());
+
+ // emit signal
+ emit stopped();
+@@ -316,7 +266,7 @@ namespace SDDM {
+ 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("XAUTHORITY"), m_xauth.authPath());
+ env.insert(QStringLiteral("SHELL"), QStringLiteral("/bin/sh"));
+ env.insert(QStringLiteral("XCURSOR_THEME"), mainConfig.Theme.CursorTheme.get());
+ setCursor->setProcessEnvironment(env);
+diff --git a/src/daemon/XorgDisplayServer.h b/src/daemon/XorgDisplayServer.h
+index e97a0b531..ebf189920 100644
+--- a/src/daemon/XorgDisplayServer.h
++++ b/src/daemon/XorgDisplayServer.h
+@@ -22,6 +22,7 @@
+ #define SDDM_XORGDISPLAYSERVER_H
+
+ #include "DisplayServer.h"
++#include "XAuth.h"
+
+ class QProcess;
+
+@@ -34,13 +35,11 @@ namespace SDDM {
+ ~XorgDisplayServer();
+
+ const QString &display() const;
+- const QString &authPath() const;
++ QString authPath() const;
+
+ QString sessionType() const;
+
+- const QString &cookie() const;
+-
+- bool addCookie(const QString &file);
++ QString cookie() const;
+
+ public slots:
+ bool start();
+@@ -49,8 +48,7 @@ namespace SDDM {
+ void setupDisplay();
+
+ private:
+- QString m_authPath;
+- QString m_cookie;
++ XAuth m_xauth;
+
+ QProcess *process { nullptr };
+
+diff --git a/src/daemon/XorgUserDisplayServer.cpp b/src/daemon/XorgUserDisplayServer.cpp
+new file mode 100644
+index 000000000..1e7c008a4
+--- /dev/null
++++ b/src/daemon/XorgUserDisplayServer.cpp
+@@ -0,0 +1,102 @@
++/***************************************************************************
++* Copyright (c) 2021 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
++*
++* 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 "Configuration.h"
++#include "DaemonApp.h"
++#include "Display.h"
++#include "Seat.h"
++#include "XorgUserDisplayServer.h"
++
++namespace SDDM {
++
++XorgUserDisplayServer::XorgUserDisplayServer(Display *parent)
++ : DisplayServer(parent)
++{
++}
++
++XorgUserDisplayServer::~XorgUserDisplayServer()
++{
++ stop();
++}
++
++QString XorgUserDisplayServer::sessionType() const
++{
++ return QStringLiteral("x11");
++}
++
++void XorgUserDisplayServer::setDisplayName(const QString &displayName)
++{
++ m_display = displayName;
++}
++
++QString XorgUserDisplayServer::command(Display *display)
++{
++ QStringList args;
++
++ if (daemonApp->testing()) {
++ args << mainConfig.X11.XephyrPath.get()
++ << QStringLiteral("-br")
++ << QStringLiteral("-screen") << QStringLiteral("800x600");
++ } else {
++ args << mainConfig.X11.ServerPath.get()
++ << mainConfig.X11.ServerArguments.get().split(QLatin1Char(' '), Qt::SkipEmptyParts)
++ << QStringLiteral("-background") << QStringLiteral("none")
++ << QStringLiteral("-seat") << display->seat()->name()
++ << QStringLiteral("-noreset")
++ << QStringLiteral("-keeptty")
++ << QStringLiteral("-novtswitch")
++ << QStringLiteral("-verbose") << QStringLiteral("3");
++ }
++
++ return args.join(QLatin1Char(' '));
++}
++
++bool XorgUserDisplayServer::start()
++{
++ // Check flag
++ if (m_started)
++ return false;
++
++ // Set flag
++ m_started = true;
++ emit started();
++
++ return true;
++}
++
++void XorgUserDisplayServer::stop()
++{
++ // Check flag
++ if (!m_started)
++ return;
++
++ // Reset flag
++ m_started = false;
++ emit stopped();
++}
++
++void XorgUserDisplayServer::finished()
++{
++}
++
++void XorgUserDisplayServer::setupDisplay()
++{
++}
++
++} // namespace SDDM
+diff --git a/src/daemon/XorgUserDisplayServer.h b/src/daemon/XorgUserDisplayServer.h
+new file mode 100644
+index 000000000..aa7cbe4a8
+--- /dev/null
++++ b/src/daemon/XorgUserDisplayServer.h
+@@ -0,0 +1,53 @@
++/***************************************************************************
++* Copyright (c) 2021 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
++*
++* 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_XORGUSERDISPLAYSERVER_H
++#define SDDM_XORGUSERDISPLAYSERVER_H
++
++#include "DisplayServer.h"
++#include "XAuth.h"
++
++class QProcess;
++
++namespace SDDM {
++
++class XorgUserDisplayServer : public DisplayServer
++{
++ Q_OBJECT
++ Q_DISABLE_COPY(XorgUserDisplayServer)
++public:
++ explicit XorgUserDisplayServer(Display *parent);
++ ~XorgUserDisplayServer();
++
++ QString sessionType() const;
++
++ void setDisplayName(const QString &displayName);
++
++ static QString command(Display *display);
++
++public Q_SLOTS:
++ bool start();
++ void stop();
++ void finished();
++ void setupDisplay();
++};
++
++} // namespace SDDM
++
++#endif // SDDM_XORGUSERDISPLAYSERVER_H
+diff --git a/src/greeter/GreeterApp.cpp b/src/greeter/GreeterApp.cpp
+index 01f53fafb..b7f740f41 100644
+--- a/src/greeter/GreeterApp.cpp
++++ b/src/greeter/GreeterApp.cpp
+@@ -337,6 +337,15 @@ int main(int argc, char **argv)
+ QSurfaceFormat::setDefaultFormat(format);
+ }
+
++ // Some themes may use KDE components and that will automatically load KDE's
++ // crash handler which we don't want counterintuitively setting this env
++ // disables that handler
++ qputenv("KDE_DEBUG", "1");
++
++ // Qt IM module
++ if (!SDDM::mainConfig.InputMethod.get().isEmpty())
++ qputenv("QT_IM_MODULE", SDDM::mainConfig.InputMethod.get().toLocal8Bit().constData());
++
+ QGuiApplication app(argc, argv);
+
+ QCommandLineParser parser;
+diff --git a/src/helper/Backend.cpp b/src/helper/Backend.cpp
+index a324b39fb..9a36a62ba 100644
+--- a/src/helper/Backend.cpp
++++ b/src/helper/Backend.cpp
+@@ -54,6 +54,11 @@ namespace SDDM {
+ m_autologin = on;
+ }
+
++ void Backend::setDisplayServer(bool on)
++ {
++ m_displayServer = on;
++ }
++
+ void Backend::setGreeter(bool on) {
+ m_greeter = on;
+ }
+diff --git a/src/helper/Backend.h b/src/helper/Backend.h
+index b790e0011..915d09ca8 100644
+--- a/src/helper/Backend.h
++++ b/src/helper/Backend.h
+@@ -36,6 +36,7 @@ namespace SDDM {
+ static Backend *get(HelperApp *parent);
+
+ void setAutologin(bool on = true);
++ void setDisplayServer(bool on = true);
+ void setGreeter(bool on = true);
+
+ public slots:
+@@ -50,6 +51,7 @@ namespace SDDM {
+ Backend(HelperApp *parent);
+ HelperApp *m_app;
+ bool m_autologin { false };
++ bool m_displayServer = false;
+ bool m_greeter { false };
+ };
+ }
+diff --git a/src/helper/CMakeLists.txt b/src/helper/CMakeLists.txt
+index 8914ea757..24f36eb97 100644
+--- a/src/helper/CMakeLists.txt
++++ b/src/helper/CMakeLists.txt
+@@ -10,9 +10,13 @@ set(HELPER_SOURCES
+ ${CMAKE_SOURCE_DIR}/src/common/Configuration.cpp
+ ${CMAKE_SOURCE_DIR}/src/common/ConfigReader.cpp
+ ${CMAKE_SOURCE_DIR}/src/common/SafeDataStream.cpp
++ ${CMAKE_SOURCE_DIR}/src/common/XAuth.cpp
++ ${CMAKE_SOURCE_DIR}/src/common/XAuth.h
+ Backend.cpp
+ HelperApp.cpp
+ UserSession.cpp
++ xorguserhelper.cpp
++ xorguserhelper.h
+ )
+
+ # Different implementations of the VT switching code
+diff --git a/src/helper/HelperApp.cpp b/src/helper/HelperApp.cpp
+index 3f92f1d8f..059e9c156 100644
+--- a/src/helper/HelperApp.cpp
++++ b/src/helper/HelperApp.cpp
+@@ -88,6 +88,16 @@ namespace SDDM {
+ m_user = args[pos + 1];
+ }
+
++ if ((pos = args.indexOf(QStringLiteral("--display-server"))) >= 0) {
++ if (pos >= args.length() - 1) {
++ qCritical() << "This application is not supposed to be executed manually";
++ exit(Auth::HELPER_OTHER_ERROR);
++ return;
++ }
++ m_session->setDisplayServerCommand(args[pos + 1]);
++ m_backend->setDisplayServer(true);
++ }
++
+ if ((pos = args.indexOf(QStringLiteral("--autologin"))) >= 0) {
+ m_backend->setAutologin(true);
+ }
+@@ -103,7 +113,7 @@ namespace SDDM {
+ }
+
+ connect(m_socket, &QLocalSocket::connected, this, &HelperApp::doAuth);
+- connect(m_session, QOverload<int>::of(&QProcess::finished), this, &HelperApp::sessionFinished);
++ connect(m_session, &UserSession::finished, this, &HelperApp::sessionFinished);
+ m_socket->connectToServer(server, QIODevice::ReadWrite | QIODevice::Unbuffered);
+ }
+
+@@ -131,11 +141,6 @@ namespace SDDM {
+
+ if (!m_session->path().isEmpty()) {
+ env.insert(m_session->processEnvironment());
+- // Allocate a new VT for the wayland session
+- if(env.value(QStringLiteral("XDG_SESSION_TYPE")) == QLatin1String("wayland")) {
+- int vtNumber = VirtualTerminal::setUpNewVt();
+- env.insert(QStringLiteral("XDG_VTNR"), QString::number(vtNumber));
+- }
+ m_session->setProcessEnvironment(env);
+
+ if (!m_backend->openSession()) {
+@@ -216,6 +221,19 @@ namespace SDDM {
+ }
+ }
+
++ void HelperApp::displayServerStarted(const QString &displayName)
++ {
++ Msg m = Msg::MSG_UNKNOWN;
++ SafeDataStream str(m_socket);
++ str << Msg::DISPLAY_SERVER_STARTED << displayName;
++ str.send();
++ str.receive();
++ str >> m;
++ if (m != DISPLAY_SERVER_STARTED) {
++ qCritical() << "Received a wrong opcode instead of DISPLAY_SERVER_STARTED:" << m;
++ }
++ }
++
+ UserSession *HelperApp::session() {
+ return m_session;
+ }
+diff --git a/src/helper/HelperApp.h b/src/helper/HelperApp.h
+index 632435ee5..1a75c2d67 100644
+--- a/src/helper/HelperApp.h
++++ b/src/helper/HelperApp.h
+@@ -48,6 +48,7 @@ namespace SDDM {
+ void error(const QString &message, Auth::Error type);
+ QProcessEnvironment authenticated(const QString &user);
+ void sessionOpened(bool success);
++ void displayServerStarted(const QString &displayName);
+
+ private slots:
+ void setUp();
+diff --git a/src/helper/UserSession.cpp b/src/helper/UserSession.cpp
+index 8e36f2113..f02a42c21 100644
+--- a/src/helper/UserSession.cpp
++++ b/src/helper/UserSession.cpp
+@@ -19,10 +19,14 @@
+ *
+ */
+
++#include <QSocketNotifier>
++
+ #include "Configuration.h"
+ #include "UserSession.h"
+ #include "HelperApp.h"
+ #include "VirtualTerminal.h"
++#include "XAuth.h"
++#include "xorguserhelper.h"
+
+ #include <sys/types.h>
+ #include <sys/ioctl.h>
+@@ -37,11 +41,19 @@
+
+ namespace SDDM {
+ UserSession::UserSession(HelperApp *parent)
+- : QProcess(parent) {
+- }
+-
+- UserSession::~UserSession() {
+-
++ : QObject(parent)
++ , m_process(new QProcess(this))
++ , m_xorgUser(new XOrgUserHelper(this))
++ {
++ connect(m_process, QOverload<int>::of(&QProcess::finished), this, &UserSession::finished);
++ connect(m_xorgUser, &XOrgUserHelper::displayChanged, this, [this, parent](const QString &display) {
++ auto env = processEnvironment();
++ env.insert(QStringLiteral("DISPLAY"), m_xorgUser->display());
++ env.insert(QStringLiteral("XAUTHORITY"), m_xorgUser->xauthPath());
++ setProcessEnvironment(env);
++
++ parent->displayServerStarted(display);
++ });
+ }
+
+ bool UserSession::start() {
+@@ -49,21 +61,68 @@ namespace SDDM {
+
+ setup();
+
+- if (env.value(QStringLiteral("XDG_SESSION_CLASS")) == QLatin1String("greeter")) {
+- QProcess::start(m_path);
+- } else if (env.value(QStringLiteral("XDG_SESSION_TYPE")) == QLatin1String("x11")) {
+- const QString cmd = QStringLiteral("%1 \"%2\"").arg(mainConfig.X11.SessionCommand.get()).arg(m_path);
+- qInfo() << "Starting:" << cmd;
+- QProcess::start(cmd);
++ if (!m_displayServerCmd.isEmpty()) {
++ m_xorgUser->setEnvironment(env);
++ if (!m_xorgUser->start(m_displayServerCmd))
++ return false;
++ }
++
++ if (env.value(QStringLiteral("XDG_SESSION_TYPE")) == QLatin1String("x11")) {
++ if (env.value(QStringLiteral("XDG_SESSION_CLASS")) == QLatin1String("greeter")) {
++ qInfo() << "Starting X11 greeter session:" << m_path;
++ auto args = QProcess::splitCommand(m_path);
++ const auto program = args.takeFirst();
++ m_process->start(program, args);
++ } else {
++ const QString cmd = QStringLiteral("%1 \"%2\"").arg(mainConfig.X11.SessionCommand.get()).arg(m_path);
++ qInfo() << "Starting X11 user session:" << cmd;
++ m_process->start(mainConfig.X11.SessionCommand.get(), QStringList{m_path});
++ }
+ } else if (env.value(QStringLiteral("XDG_SESSION_TYPE")) == QLatin1String("wayland")) {
+ const QString cmd = QStringLiteral("%1 %2").arg(mainConfig.Wayland.SessionCommand.get()).arg(m_path);
+- qInfo() << "Starting:" << cmd;
+- QProcess::start(cmd);
++ qInfo() << "Starting Wayland user session:" << cmd;
++ m_process->start(mainConfig.Wayland.SessionCommand.get(), QStringList{m_path});
+ } else {
+ qCritical() << "Unable to run user session: unknown session type";
+ }
+
+- return waitForStarted();
++ if (m_process->waitForStarted()) {
++ int vtNumber = processEnvironment().value(QStringLiteral("XDG_VTNR")).toInt();
++ VirtualTerminal::jumpToVt(vtNumber, true);
++ return true;
++ }
++
++ return false;
++ }
++
++ void UserSession::stop()
++ {
++ m_process->terminate();
++ if (!m_process->waitForFinished(5000))
++ m_process->kill();
++
++ if (!m_displayServerCmd.isEmpty())
++ m_xorgUser->stop();
++ }
++
++ QProcessEnvironment UserSession::processEnvironment() const
++ {
++ return m_process->processEnvironment();
++ }
++
++ void UserSession::setProcessEnvironment(const QProcessEnvironment &env)
++ {
++ m_process->setProcessEnvironment(env);
++ }
++
++ QString UserSession::displayServerCommand() const
++ {
++ return m_displayServerCmd;
++ }
++
++ void UserSession::setDisplayServerCommand(const QString &command)
++ {
++ m_displayServerCmd = command;
+ }
+
+ void UserSession::setPath(const QString& path) {
+@@ -74,13 +133,22 @@ namespace SDDM {
+ return m_path;
+ }
+
++ qint64 UserSession::processId() const
++ {
++ return m_process->processId();
++ }
++
+ void UserSession::setupChildProcess() {
+ // Session type
+ QString sessionType = processEnvironment().value(QStringLiteral("XDG_SESSION_TYPE"));
+-
+- // For Wayland sessions we leak the VT into the session as stdin so
+- // that it stays open without races
+- if (sessionType == QLatin1String("wayland")) {
++ QString sessionClass = processEnvironment().value(QStringLiteral("XDG_SESSION_CLASS"));
++ const bool hasDisplayServer = !m_displayServerCmd.isEmpty();
++ const bool x11UserSession = sessionType == QLatin1String("x11") && sessionClass == QLatin1String("user");
++ const bool waylandUserSession = sessionType == QLatin1String("wayland") && sessionClass == QLatin1String("user");
++
++ // When the display server is part of the session, we leak the VT into
++ // the session as stdin so that it stays open without races
++ if (hasDisplayServer || waylandUserSession) {
+ // open VT and get the fd
+ int vtNumber = processEnvironment().value(QStringLiteral("XDG_VTNR")).toInt();
+ QString ttyString = QStringLiteral("/dev/tty%1").arg(vtNumber);
+@@ -226,74 +294,56 @@ namespace SDDM {
+ qCritical() << "verify directory exist and has sufficient permissions";
+ exit(Auth::HELPER_OTHER_ERROR);
+ }
+- const QString homeDir = QString::fromLocal8Bit(pw.pw_dir);
+-
+- //we cannot use setStandardError file as this code is run in the child process
+- //we want to redirect after we setuid so that the log file is owned by the user
+-
+- // determine stderr log file based on session type
+- QString sessionLog = QStringLiteral("%1/%2")
+- .arg(homeDir)
+- .arg(sessionType == QLatin1String("x11")
+- ? mainConfig.X11.SessionLogFile.get()
+- : mainConfig.Wayland.SessionLogFile.get());
+-
+- // create the path
+- QFileInfo finfo(sessionLog);
+- QDir().mkpath(finfo.absolutePath());
+-
+- //swap the stderr pipe of this subprcess into a file
+- int fd = ::open(qPrintable(sessionLog), O_WRONLY | O_CREAT | O_TRUNC, 0600);
+- if (fd >= 0)
+- {
+- dup2 (fd, STDERR_FILENO);
+- ::close(fd);
+- } else {
+- qWarning() << "Could not open stderr to" << sessionLog;
+- }
+-
+- //redirect any stdout to /dev/null
+- fd = ::open("/dev/null", O_WRONLY);
+- if (fd >= 0)
+- {
+- dup2 (fd, STDOUT_FILENO);
+- ::close(fd);
+- } else {
+- qWarning() << "Could not redirect stdout";
+- }
+
+- // set X authority for X11 sessions only
+- if (sessionType != QLatin1String("x11"))
+- return;
+- QString cookie = qobject_cast<HelperApp*>(parent())->cookie();
+- if (!cookie.isEmpty()) {
+- QString file = processEnvironment().value(QStringLiteral("XAUTHORITY"));
+- QString display = processEnvironment().value(QStringLiteral("DISPLAY"));
+- qDebug() << "Adding cookie to" << file;
++ if (sessionClass != QLatin1String("greeter")) {
++ //we cannot use setStandardError file as this code is run in the child process
++ //we want to redirect after we setuid so that the log file is owned by the user
+
++ // determine stderr log file based on session type
++ QString sessionLog = QStringLiteral("%1/%2")
++ .arg(QString::fromLocal8Bit(pw.pw_dir))
++ .arg(sessionType == QLatin1String("x11")
++ ? mainConfig.X11.SessionLogFile.get()
++ : mainConfig.Wayland.SessionLogFile.get());
+
+ // create the path
+- QFileInfo finfo(file);
++ QFileInfo finfo(sessionLog);
+ QDir().mkpath(finfo.absolutePath());
+
+- QFile file_handler(file);
+- file_handler.open(QIODevice::Append);
+- file_handler.close();
++ //swap the stderr pipe of this subprcess into a file
++ int fd = ::open(qPrintable(sessionLog), O_WRONLY | O_CREAT | O_TRUNC, 0600);
++ if (fd >= 0)
++ {
++ dup2 (fd, STDERR_FILENO);
++ ::close(fd);
++ } else {
++ qWarning() << "Could not open stderr to" << sessionLog;
++ }
+
+- QString cmd = QStringLiteral("%1 -f %2 -q").arg(mainConfig.X11.XauthPath.get()).arg(file);
++ //redirect any stdout to /dev/null
++ fd = ::open("/dev/null", O_WRONLY);
++ if (fd >= 0)
++ {
++ dup2 (fd, STDOUT_FILENO);
++ ::close(fd);
++ } else {
++ qWarning() << "Could not redirect stdout";
++ }
++ }
+
+- // execute xauth
+- FILE *fp = popen(qPrintable(cmd), "w");
++ // set X authority for X11 sessions only
++ if (x11UserSession) {
++ QString cookie = qobject_cast<HelperApp*>(parent())->cookie();
++ if (!cookie.isEmpty()) {
++ QString file = processEnvironment().value(QStringLiteral("XAUTHORITY"));
++ QString display = processEnvironment().value(QStringLiteral("DISPLAY"));
+
+- // check file
+- if (!fp)
+- return;
+- fprintf(fp, "remove %s\n", qPrintable(display));
+- fprintf(fp, "add %s . %s\n", qPrintable(display), qPrintable(cookie));
+- fprintf(fp, "exit\n");
++ // Create the path
++ QFileInfo finfo(file);
++ QDir().mkpath(finfo.absolutePath());
+
+- // close pipe
+- pclose(fp);
++ XAuth::addCookieToFile(display, file, cookie);
++ }
+ }
+ }
+ }
+diff --git a/src/helper/UserSession.h b/src/helper/UserSession.h
+index c2383f068..11bff1d1b 100644
+--- a/src/helper/UserSession.h
++++ b/src/helper/UserSession.h
+@@ -23,41 +23,55 @@
+ #define SDDM_AUTH_SESSION_H
+
+ #include <QtCore/QObject>
+-#include <QtCore/QString>
+ #include <QtCore/QProcess>
+
+ namespace SDDM {
+ class HelperApp;
+- class UserSession : public QProcess
++ class XOrgUserHelper;
++ class UserSession : public QObject
+ {
+ Q_OBJECT
+ public:
+ explicit UserSession(HelperApp *parent);
+- virtual ~UserSession();
+
+ bool start();
++ void stop();
++
++ QProcessEnvironment processEnvironment() const;
++ void setProcessEnvironment(const QProcessEnvironment &env);
++
++ QString displayServerCommand() const;
++ void setDisplayServerCommand(const QString &command);
+
+ void setPath(const QString &path);
+ QString path() const;
+
+ /*!
+ \brief Sets m_cachedProcessId. Needed for getting the PID of a finished UserSession
+ and calling HelperApp::utmpLogout
+ \param pid The process ID
+ */
+ void setCachedProcessId(qint64 pid);
+
+ /*!
+ \brief Gets m_cachedProcessId
+ \return The cached process ID
+ */
+ qint64 cachedProcessId();
+
++ qint64 processId() const;
++
++ Q_SIGNALS:
++ void finished(int exitCode);
++
+ protected:
+ void setupChildProcess();
+
+ private:
+ QString m_path { };
+ qint64 m_cachedProcessId;
++ QProcess *m_process = nullptr;
++ XOrgUserHelper *m_xorgUser = nullptr;
++ QString m_displayServerCmd;
+ };
+ }
+
+diff --git a/src/helper/backend/PamBackend.cpp b/src/helper/backend/PamBackend.cpp
+index f86d77d63..c97056a66 100644
+--- a/src/helper/backend/PamBackend.cpp
++++ b/src/helper/backend/PamBackend.cpp
+@@ -248,7 +248,9 @@ namespace SDDM {
+ }
+
+ QProcessEnvironment sessionEnv = m_app->session()->processEnvironment();
+- if (sessionEnv.value(QStringLiteral("XDG_SESSION_TYPE")) == QLatin1String("x11")) {
++ const auto sessionType = sessionEnv.value(QStringLiteral("XDG_SESSION_TYPE"));
++ const auto sessionClass = sessionEnv.value(QStringLiteral("XDG_SESSION_CLASS"));
++ if (sessionType == QLatin1String("x11") && (sessionClass == QLatin1String("user") || !m_displayServer)) {
+ QString display = sessionEnv.value(QStringLiteral("DISPLAY"));
+ if (!display.isEmpty()) {
+ #ifdef PAM_XDISPLAY
+@@ -256,7 +258,7 @@ namespace SDDM {
+ #endif
+ m_pam->setItem(PAM_TTY, qPrintable(display));
+ }
+- } else if (sessionEnv.value(QStringLiteral("XDG_SESSION_TYPE")) == QLatin1String("wayland")) {
++ } else {
+ QString tty = QStringLiteral("/dev/tty%1").arg(sessionEnv.value(QStringLiteral("XDG_VTNR")));
+ m_pam->setItem(PAM_TTY, qPrintable(tty));
+ }
+diff --git a/src/helper/xorguserhelper.cpp b/src/helper/xorguserhelper.cpp
+new file mode 100644
+index 000000000..573ed101f
+--- /dev/null
++++ b/src/helper/xorguserhelper.cpp
+@@ -0,0 +1,252 @@
++/***************************************************************************
++* Copyright (c) 2021 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
++*
++* 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 <QCoreApplication>
++#include <QFile>
++#include <QStandardPaths>
++
++#include "Configuration.h"
++
++#include "xorguserhelper.h"
++
++#include <fcntl.h>
++#include <unistd.h>
++
++namespace SDDM {
++
++XOrgUserHelper::XOrgUserHelper(QObject *parent)
++ : QObject(parent)
++{
++}
++
++QProcessEnvironment XOrgUserHelper::environment() const
++{
++ return m_environment;
++}
++
++void XOrgUserHelper::setEnvironment(const QProcessEnvironment &env)
++{
++ m_environment = env;
++}
++
++QString XOrgUserHelper::display() const
++{
++ return m_display;
++}
++
++QString XOrgUserHelper::xauthPath() const
++{
++ return m_xauth.authPath();
++}
++
++bool XOrgUserHelper::start(const QString &cmd)
++{
++ // Create xauthority
++ m_xauth.setAuthDirectory(m_environment.value(QStringLiteral("XDG_RUNTIME_DIR")));
++ m_xauth.setup();
++
++ // Start server process
++ if (!startServer(cmd))
++ return false;
++
++ // Setup display
++ startDisplayCommand();
++
++ return true;
++}
++
++void XOrgUserHelper::stop()
++{
++ if (m_serverProcess) {
++ qInfo("Stopping server...");
++ m_serverProcess->terminate();
++ if (!m_serverProcess->waitForFinished(5000))
++ m_serverProcess->kill();
++ m_serverProcess->deleteLater();
++ m_serverProcess = nullptr;
++
++ displayFinished();
++ }
++}
++
++bool XOrgUserHelper::startProcess(const QString &cmd,
++ const QProcessEnvironment &env,
++ QProcess **p)
++{
++ auto args = QProcess::splitCommand(cmd);
++ const auto program = args.takeFirst();
++
++ // Make sure to forward the input of this process into the Xorg
++ // server, otherwise it will complain that only console users are allowed
++ auto *process = new QProcess(this);
++ process->setInputChannelMode(QProcess::ForwardedInputChannel);
++ process->setProcessChannelMode(QProcess::ForwardedChannels);
++ process->setProcessEnvironment(env);
++
++ connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
++ process, [](int exitCode, QProcess::ExitStatus exitStatus) {
++ if (exitCode != 0 || exitStatus != QProcess::NormalExit)
++ QCoreApplication::instance()->quit();
++ });
++
++ process->start(program, args);
++ if (!process->waitForStarted(10000)) {
++ qWarning("Failed to start \"%s\": %s",
++ qPrintable(cmd),
++ qPrintable(process->errorString()));
++ return false;
++ }
++
++ if (p)
++ *p = process;
++
++ return true;
++}
++
++bool XOrgUserHelper::startServer(const QString &cmd)
++{
++ QString serverCmd = cmd;
++
++ // Create pipe for communicating with X server
++ // 0 == read from X, 1 == write to X
++ int pipeFds[2];
++ if (::pipe(pipeFds) != 0) {
++ qCritical("Could not create pipe to start X server");
++ return false;
++ }
++
++ // Do not leak the read endpoint to the X server process
++ fcntl(pipeFds[0], F_SETFD, FD_CLOEXEC);
++
++ // Server environment
++ // Not setting XORG_RUN_AS_USER_OK=1 will make Xorg require root privileges
++ // under Fedora and all distros that use their patch.
++ // https://src.fedoraproject.org/rpms/xorg-x11-server/blob/rawhide/f/0001-Fedora-hack-Make-the-suid-root-wrapper-always-start-.patch
++ // https://fedoraproject.org/wiki/Changes/XorgWithoutRootRights
++ QProcessEnvironment serverEnv = m_environment;
++ serverEnv.insert(QStringLiteral("XORG_RUN_AS_USER_OK"), QStringLiteral("1"));
++
++ // Append xauth and display fd to the command
++ auto args = QStringList()
++ << QStringLiteral("-auth") << m_xauth.authPath()
++ << QStringLiteral("-displayfd") << QString::number(pipeFds[1]);
++
++ // Append VT from environment
++ args << QStringLiteral("vt%1").arg(serverEnv.value(QStringLiteral("XDG_VTNR")));
++
++ // Log to stdout
++ args << QStringLiteral("-logfile") << QStringLiteral("/dev/null");
++
++ // Command string
++ serverCmd += QLatin1Char(' ') + args.join(QLatin1Char(' '));
++
++ // Start the server process
++ qInfo("Running server: %s", qPrintable(serverCmd));
++ if (!startProcess(serverCmd, serverEnv, &m_serverProcess)) {
++ ::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]);
++
++ // Read the display number from the pipe
++ 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]);
++ return false;
++ }
++ displayNumber.prepend(QByteArray(":"));
++ displayNumber.remove(displayNumber.size() -1, 1); // trim trailing whitespace
++ m_display = QString::fromLocal8Bit(displayNumber);
++ qDebug("X11 display: %s", qPrintable(m_display));
++ Q_EMIT displayChanged(m_display);
++
++ // Generate xauthority file
++ // For the X server's copy, the display number doesn't matter.
++ // An empty file would result in no access control!
++ if (!m_xauth.addCookie(m_display)) {
++ qCritical("Failed to write xauth file");
++ return false;
++ }
++
++ // Close our pipe
++ ::close(pipeFds[0]);
++
++ return true;
++}
++
++void XOrgUserHelper::startDisplayCommand()
++{
++ auto env = QProcessEnvironment::systemEnvironment();
++ env.insert(QStringLiteral("DISPLAY"), m_display);
++ env.insert(QStringLiteral("XAUTHORITY"), m_xauth.authPath());
++
++ // Set cursor
++ qInfo("Setting default cursor...");
++ QProcess *setCursor = nullptr;
++ if (startProcess(QStringLiteral("xsetroot -cursor_name left_ptr"), env, &setCursor)) {
++ if (!setCursor->waitForFinished(1000)) {
++ qWarning() << "Could not setup default cursor";
++ setCursor->kill();
++ }
++ setCursor->deleteLater();
++ }
++
++ // Display setup script
++ auto cmd = mainConfig.X11.DisplayCommand.get();
++ qInfo("Running display setup script: %s", qPrintable(cmd));
++ QProcess *displayScript = nullptr;
++ if (startProcess(cmd, env, &displayScript)) {
++ if (!displayScript->waitForFinished(30000))
++ displayScript->kill();
++ displayScript->deleteLater();
++ }
++}
++
++void XOrgUserHelper::displayFinished()
++{
++ auto env = QProcessEnvironment::systemEnvironment();
++ env.insert(QStringLiteral("DISPLAY"), m_display);
++ env.insert(QStringLiteral("XAUTHORITY"), m_xauth.authPath());
++ env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("xcb"));
++
++ auto cmd = mainConfig.X11.DisplayStopCommand.get();
++ qInfo("Running display stop script: %s", qPrintable(cmd));
++ QProcess *displayStopScript = nullptr;
++ if (startProcess(cmd, env, &displayStopScript)) {
++ if (!displayStopScript->waitForFinished(5000))
++ displayStopScript->kill();
++ displayStopScript->deleteLater();
++ }
++
++ // Remove xauthority file
++ QFile::remove(m_xauth.authPath());
++}
++
++} // namespace SDDM
+diff --git a/src/helper/xorguserhelper.h b/src/helper/xorguserhelper.h
+new file mode 100644
+index 000000000..73536965a
+--- /dev/null
++++ b/src/helper/xorguserhelper.h
+@@ -0,0 +1,64 @@
++/***************************************************************************
++* Copyright (c) 2021 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
++*
++* 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 XORGUSERHELPER_H
++#define XORGUSERHELPER_H
++
++#include <QProcess>
++
++#include "XAuth.h"
++
++namespace SDDM {
++
++class XOrgUserHelper : public QObject
++{
++ Q_OBJECT
++ Q_PROPERTY(QString display READ display NOTIFY displayChanged)
++public:
++ explicit XOrgUserHelper(QObject *parent = nullptr);
++
++ QProcessEnvironment environment() const;
++ void setEnvironment(const QProcessEnvironment &env);
++
++ QString display() const;
++
++ QString xauthPath() const;
++
++ bool start(const QString &cmd);
++ void stop();
++
++Q_SIGNALS:
++ void displayChanged(const QString &display);
++
++private:
++ QString m_display = QStringLiteral(":0");
++ XAuth m_xauth;
++ QProcessEnvironment m_environment;
++ QProcess *m_serverProcess = nullptr;
++
++ bool startProcess(const QString &cmd, const QProcessEnvironment &env,
++ QProcess **p = nullptr);
++ bool startServer(const QString &cmd);
++ void startDisplayCommand();
++ void displayFinished();
++};
++
++} // namespace SDDM
++
++#endif // XORGUSERHELPER_H