/*
    KWin - the KDE window manager
    This file is part of the KDE project.

    SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>

    SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "main_wayland.h"

#include <config-kwin.h>

#include "backends/drm/drm_backend.h"
#include "backends/virtual/virtual_backend.h"
#include "backends/wayland/wayland_backend.h"
#include "backends/x11/windowed/x11_windowed_backend.h"
#include "composite.h"
#include "core/outputbackend.h"
#include "core/session.h"
#include "effects.h"
#include "inputmethod.h"
#include "tabletmodemanager.h"
#include "utils/realtime.h"
#include "wayland/display.h"
#include "wayland/seat_interface.h"
#include "wayland_server.h"
#include "workspace.h"
#include "xwayland/xwayland.h"
#include "xwayland/xwaylandlauncher.h"

// KDE
#include <KCrash>
#include <KDesktopFile>
#include <KLocalizedString>
#include <KShell>
#include <KSignalHandler>

// Qt
#include <QCommandLineParser>
#include <QDBusInterface>
#include <QDebug>
#include <QFileInfo>
#include <QProcess>
#include <QWindow>
#include <QDir>
#include <QRegularExpression>
#include <qplatformdefs.h>

#include <sched.h>
#include <sys/resource.h>

#include <iomanip>
#include <iostream>

#if defined(QT_NO_DEBUG)
#define LOG_FILE_COUNT_MAX 2
#else
#define LOG_FILE_COUNT_MAX 4
#endif
#define LOG_FILE_SIZE_MAX 20*1024*1024
#define LOG_FILE_PREFIX "kwin"
#define LOG_FILE_PATH "/.log/"

Q_IMPORT_PLUGIN(KWinIntegrationPlugin)
Q_IMPORT_PLUGIN(KGlobalAccelImpl)
Q_IMPORT_PLUGIN(KWindowSystemKWinPlugin)
Q_IMPORT_PLUGIN(KWinIdleTimePoller)
#if PipeWire_FOUND
Q_IMPORT_PLUGIN(ScreencastManagerFactory)
#endif

namespace KWin
{

static rlimit originalNofileLimit = {
    .rlim_cur = 0,
    .rlim_max = 0,
};

static bool bumpNofileLimit()
{
    if (getrlimit(RLIMIT_NOFILE, &originalNofileLimit) == -1) {
        std::cerr << "Failed to bump RLIMIT_NOFILE limit, getrlimit() failed: " << strerror(errno) << std::endl;
        return false;
    }

    rlimit limit = originalNofileLimit;
    limit.rlim_cur = limit.rlim_max;

    if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
        std::cerr << "Failed to bump RLIMIT_NOFILE limit, setrlimit() failed: " << strerror(errno) << std::endl;
        return false;
    }

    return true;
}

static void restoreNofileLimit()
{
    if (setrlimit(RLIMIT_NOFILE, &originalNofileLimit) == -1) {
        std::cerr << "Failed to restore RLIMIT_NOFILE limit, legacy apps might be broken" << std::endl;
    }
}

void disableDrKonqi()
{
    KCrash::setDrKonqiEnabled(false);
}
// run immediately, before Q_CORE_STARTUP functions
// that would enable drkonqi
Q_CONSTRUCTOR_FUNCTION(disableDrKonqi)

//************************************
// ApplicationWayland
//************************************

ApplicationWayland::ApplicationWayland(int &argc, char **argv)
    : Application(OperationModeWaylandOnly, argc, argv)
{
}

ApplicationWayland::~ApplicationWayland()
{
    setTerminating();
    if (!waylandServer()) {
        return;
    }

    destroyPlugins();

    // need to unload all effects prior to destroying X connection as they might do X calls
    if (effects) {
        static_cast<EffectsHandlerImpl *>(effects)->unloadAllEffects();
    }
    m_xwayland.reset();
    destroyColorManager();
    destroyWorkspace();

    destroyInputMethod();
    destroyCompositor();
    destroyInput();
}

void ApplicationWayland::performStartup()
{
    if (m_startXWayland) {
        setOperationMode(OperationModeXwayland);
        setXwaylandScale(config()->group("Xwayland").readEntry("Scale", 1.0));
    }
    // first load options - done internally by a different thread
    createOptions();

    if (!outputBackend()->initialize()) {
        std::exit(1);
    }

    createInput();
    createInputMethod();
    createTabletModeManager();

    WaylandCompositor::create();

    connect(Compositor::self(), &Compositor::sceneCreated, outputBackend(), &OutputBackend::sceneInitialized);
    connect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithScene);
}

void ApplicationWayland::continueStartupWithScene()
{
    disconnect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithScene);

    // Note that we start accepting client connections after creating the Workspace.
    createWorkspace();
    createColorManager();
    createPlugins();

    if (!waylandServer()->start()) {
        qFatal("Failed to initialze the Wayland server, exiting now");
    }

    if (operationMode() == OperationModeWaylandOnly) {
        finalizeStartup();
        return;
    }

    m_xwayland = std::make_unique<Xwl::Xwayland>(this);
    m_xwayland->xwaylandLauncher()->setListenFDs(m_xwaylandListenFds);
    m_xwayland->xwaylandLauncher()->setDisplayName(m_xwaylandDisplay);
    m_xwayland->xwaylandLauncher()->setXauthority(m_xwaylandXauthority);
    connect(m_xwayland.get(), &Xwl::Xwayland::errorOccurred, this, &ApplicationWayland::finalizeStartup);
    connect(m_xwayland.get(), &Xwl::Xwayland::started, this, &ApplicationWayland::finalizeStartup);
    m_xwayland->start();
}

void ApplicationWayland::finalizeStartup()
{
    if (m_xwayland) {
        disconnect(m_xwayland.get(), &Xwl::Xwayland::errorOccurred, this, &ApplicationWayland::finalizeStartup);
        disconnect(m_xwayland.get(), &Xwl::Xwayland::started, this, &ApplicationWayland::finalizeStartup);
    }
    startSession();
    notifyStarted();
}

void ApplicationWayland::refreshSettings(const KConfigGroup &group, const QByteArrayList &names)
{
    if (group.name() == "Wayland" && names.contains("InputMethod")) {
        KDesktopFile file(group.readPathEntry("InputMethod", QString()));
        kwinApp()->inputMethod()->setInputMethodCommand(file.desktopGroup().readEntry("Exec", QString()));
    }

    if (m_startXWayland && group.name() == "Xwayland" && names.contains("Scale")) {
        setXwaylandScale(group.readEntry("Scale", 1.0));
    }
}

void ApplicationWayland::startSession()
{
    KSharedConfig::Ptr kwinSettings = kwinApp()->config();
    m_settingsWatcher = KConfigWatcher::create(kwinSettings);
    connect(m_settingsWatcher.data(), &KConfigWatcher::configChanged, this, &ApplicationWayland::refreshSettings);

    if (!m_inputMethodServerToStart.isEmpty()) {
        kwinApp()->inputMethod()->setInputMethodCommand(m_inputMethodServerToStart);
    } else {
        refreshSettings(kwinSettings->group("Wayland"), {"InputMethod"});
    }

    // start session
    if (!m_sessionArgument.isEmpty()) {
        QStringList arguments = KShell::splitArgs(m_sessionArgument);
        if (!arguments.isEmpty()) {
            QString program = arguments.takeFirst();
            QProcess *p = new QProcess(this);
            p->setProcessChannelMode(QProcess::ForwardedErrorChannel);
            p->setProcessEnvironment(processStartupEnvironment());
            connect(p, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, [p](int code, QProcess::ExitStatus status) {
                p->deleteLater();
                if (status == QProcess::CrashExit) {
                    qWarning() << "Session process has crashed";
                    QCoreApplication::exit(-1);
                    return;
                }

                if (code) {
                    qWarning() << "Session process exited with code" << code;
                }

                QCoreApplication::exit(code);
            });
            p->setProgram(program);
            p->setArguments(arguments);
            p->start();
        } else {
            qWarning("Failed to launch the session process: %s is an invalid command",
                     qPrintable(m_sessionArgument));
        }
    }
    // start the applications passed to us as command line arguments
    if (!m_applicationsToStart.isEmpty()) {
        for (const QString &application : std::as_const(m_applicationsToStart)) {
            QStringList arguments = KShell::splitArgs(application);
            if (arguments.isEmpty()) {
                qWarning("Failed to launch application: %s is an invalid command",
                         qPrintable(application));
                continue;
            }
            QString program = arguments.takeFirst();
            // note: this will kill the started process when we exit
            // this is going to happen anyway as we are the wayland and X server the app connects to
            QProcess *p = new QProcess(this);
            p->setProcessChannelMode(QProcess::ForwardedErrorChannel);
            p->setProcessEnvironment(processStartupEnvironment());
            p->setProgram(program);
            p->setArguments(arguments);
            p->startDetached();
            p->deleteLater();
        }
    }
}

XwaylandInterface *ApplicationWayland::xwayland() const
{
    return m_xwayland.get();
}

} // namespace

/*
* find next log file, and do some check.
*/
static int checkLogFile(int index) {
    int logFileIndex = index;
    for (size_t i = 0; i < LOG_FILE_COUNT_MAX; i++) {
        logFileIndex = (logFileIndex + 1) % LOG_FILE_COUNT_MAX;
        QString logFilePath = QDir::homePath() + LOG_FILE_PATH + "/" + QStringLiteral("%1_%2.log").arg(LOG_FILE_PREFIX).arg(logFileIndex);
        if (!QFile::exists(logFilePath))
            break;
        QFile temp(logFilePath);
        if (temp.size() < LOG_FILE_SIZE_MAX)
            break;
        if ((LOG_FILE_COUNT_MAX - 1) == i) {
            // here is the next log file.
            logFileIndex = (logFileIndex + 1) % LOG_FILE_COUNT_MAX;
            QString nextLogFilePath = QDir::homePath() + LOG_FILE_PATH + "/" + QStringLiteral("%1_%2.log").arg(LOG_FILE_PREFIX).arg(logFileIndex);

            // prepare rotate to next log file, so we should log some info in prev log file to make a better understanding.
            FILE* tailFile = fopen(logFilePath.toLocal8Bit().constData(), "a+");
            if (tailFile) {
                fprintf(tailFile, "to be continue, please check:\r\n\t[%s]", nextLogFilePath.toStdString().c_str());
                fclose(tailFile);
            }

            // and make next log file be clear
            QFile nextLogFile(nextLogFilePath);
            nextLogFile.remove();
        }
    }
    return logFileIndex;
}

static void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QByteArray localMsg = msg.toLocal8Bit();

    QDateTime dateTime = QDateTime::currentDateTime();
    QString strFormat = "yyMMdd hh:mm:ss.zzz";
    QString strDateTime = dateTime.toString(strFormat);

    FILE *log_file = nullptr;
    QString logFilePath;
    int fileSize;
    static int currentLogFileIndex = -1;
    QDir dir;
    bool flag = 0;

    logFilePath = QDir::homePath() + LOG_FILE_PATH;
    if (dir.mkpath(logFilePath)) {
        flag = 1;
    }
    if (flag) {
        if (-1 == currentLogFileIndex)
            currentLogFileIndex = checkLogFile(currentLogFileIndex);
        logFilePath = logFilePath + "/" + QStringLiteral("%1_%2.log").arg(LOG_FILE_PREFIX).arg(currentLogFileIndex);
        log_file = fopen(logFilePath.toLocal8Bit().constData(), "a+");
    }

    QString src = QString(context.file ? context.file : "").section('/', -1);
    QRegularExpression re("(\\w([\\S]*?)[\\s]?)(\\()");
    QRegularExpressionMatch match = re.match(context.function);
    QString func = match.hasMatch() ? match.captured(1) : "";
    QString exinfo = src.isEmpty() ? QString() : QStringLiteral("(%1:%2 %3)").arg(src).arg(context.line).arg(func);
    switch (type) {
    case QtDebugMsg:
        if (!log_file) {
            break;
        }
        fprintf(log_file, "Debug: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str());
        break;
    case QtInfoMsg:
        fprintf(log_file? log_file: stdout, "Info: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str());
        break;
    case QtWarningMsg:
        fprintf(log_file? log_file: stderr, "Warning: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str());
        break;
    case QtCriticalMsg:
        fprintf(log_file? log_file: stderr, "Critical: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str());
        break;
    case QtFatalMsg:
        fprintf(log_file? log_file: stderr, "Fatal: %s: %s %s\n", strDateTime.toStdString().c_str(), localMsg.constData(), exinfo.toStdString().c_str());
        break;
    }

    if (log_file) {
        fileSize = ftell(log_file);
        fclose(log_file);
        if (fileSize >= LOG_FILE_SIZE_MAX)
            currentLogFileIndex = checkLogFile(currentLogFileIndex);
    }
}

int main(int argc, char *argv[])
{
    qDebug() << "==================kwin_wayland main in before message handler===========================";
    qInstallMessageHandler(messageOutput);
    qDebug() << "==================kwin_wayland main in===========================";

    KWin::Application::setupMalloc();
    KWin::Application::setupLocalizedString();
    KWin::gainRealTime();

    signal(SIGPIPE, SIG_IGN);

    // It's easy to exceed the file descriptor limit because many things are backed using fds
    // nowadays, e.g. dmabufs, shm buffers, etc. Bump the RLIMIT_NOFILE limit to handle that.
    // Some apps may still use select(), so we reset the limit to its original value in fork().
    if (KWin::bumpNofileLimit()) {
        pthread_atfork(nullptr, nullptr, KWin::restoreNofileLimit);
    }

    QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();

    // enforce our internal qpa plugin, unfortunately command line switch has precedence
    setenv("QT_QPA_PLATFORM", "wayland-org.kde.kwin.qpa", true);
    // ukui-session set QT_QPA_PLATFORMTHEME=ukui in X11 first, then start kwin_x11
    // but kwin_wayland starts earlier than ukui-session, so we need to set it again
    setenv("QT_QPA_PLATFORMTHEME", "ukui", true);

    qunsetenv("QT_DEVICE_PIXEL_RATIO");
    qputenv("QSG_RENDER_LOOP", "basic");
    QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
    KWin::ApplicationWayland a(argc, argv);
    a.setupTranslator();
    // reset QT_QPA_PLATFORM so we don't propagate it to our children (e.g. apps launched from the overview effect)
    qunsetenv("QT_QPA_PLATFORM");
    qunsetenv("QT_QPA_PLATFORMTHEME");

#if !defined(QT_NO_DEBUG)
    QStringList newLibPaths(a.applicationDirPath());
    const QStringList oldLibPaths = QCoreApplication::libraryPaths();
    if (newLibPaths.contains("/usr/local/bin")) {
        // when do “make install”, we need add plugins directory before normal qt plugins directory.
        // we can get arch lib directory name from qt plugins directory and do a replace
        // from "/usr/lib/x86_64-linux-gnu/qt5/plugins" to "/usr/local/lib/x86_64-linux-gnu/plugins"
        // then kwin will load the right plugins.
        QRegularExpression re("^/usr/lib/(.*)/qt.*$");
        QStringList plugins = oldLibPaths.filter(QRegularExpression("^/usr/lib/"))
                                         .replaceInStrings(re, "/usr/local/lib/\\1/plugins");
        newLibPaths.append(plugins);
    }
    newLibPaths.append(oldLibPaths);
    QCoreApplication::setLibraryPaths(newLibPaths);
    qDebug() << "debug mode:";
    qDebug() << "   set application dir path to be the first of libraryPaths,";
    qDebug() << "   so we can be happy to debug kwin plugins.";
    qDebug() << "   libraryPaths:" << newLibPaths;
#endif

    KSignalHandler::self()->watchSignal(SIGTERM);
    KSignalHandler::self()->watchSignal(SIGINT);
    KSignalHandler::self()->watchSignal(SIGHUP);
    QObject::connect(KSignalHandler::self(), &KSignalHandler::signalReceived,
                     &a, &QCoreApplication::exit);

    KWin::Application::createAboutData();

    QCommandLineOption xwaylandOption(QStringLiteral("xwayland"),
                                      i18n("Start a rootless Xwayland server."));
    QCommandLineOption waylandSocketOption(QStringList{QStringLiteral("s"), QStringLiteral("socket")},
                                           i18n("Name of the Wayland socket to listen on. If not set \"wayland-0\" is used."),
                                           QStringLiteral("socket"));
    QCommandLineOption x11DisplayOption(QStringLiteral("x11-display"),
                                        i18n("The X11 Display to use in windowed mode on platform X11."),
                                        QStringLiteral("display"));
    QCommandLineOption waylandDisplayOption(QStringLiteral("wayland-display"),
                                            i18n("The Wayland Display to use in windowed mode on platform Wayland."),
                                            QStringLiteral("display"));
    QCommandLineOption virtualFbOption(QStringLiteral("virtual"), i18n("Render to a virtual framebuffer."));
    QCommandLineOption widthOption(QStringLiteral("width"),
                                   i18n("The width for windowed mode. Default width is 1024."),
                                   QStringLiteral("width"));
    widthOption.setDefaultValue(QString::number(1024));
    QCommandLineOption heightOption(QStringLiteral("height"),
                                    i18n("The height for windowed mode. Default height is 768."),
                                    QStringLiteral("height"));
    heightOption.setDefaultValue(QString::number(768));

    QCommandLineOption scaleOption(QStringLiteral("scale"),
                                   i18n("The scale for windowed mode. Default value is 1."),
                                   QStringLiteral("scale"));
    scaleOption.setDefaultValue(QString::number(1));

    QCommandLineOption outputCountOption(QStringLiteral("output-count"),
                                         i18n("The number of windows to open as outputs in windowed mode. Default value is 1"),
                                         QStringLiteral("count"));
    outputCountOption.setDefaultValue(QString::number(1));

    QCommandLineOption waylandSocketFdOption(QStringLiteral("wayland-fd"),
                                             i18n("Wayland socket to use for incoming connections. This can be combined with --socket to name the socket"),
                                             QStringLiteral("wayland-fd"));

    QCommandLineOption xwaylandListenFdOption(QStringLiteral("xwayland-fd"),
                                              i18n("XWayland socket to use for Xwayland's incoming connections. This can be set multiple times"),
                                              QStringLiteral("xwayland-fds"));

    QCommandLineOption xwaylandDisplayOption(QStringLiteral("xwayland-display"),
                                             i18n("Name of the xwayland display that has been pre-set up"),
                                             "xwayland-display");

    QCommandLineOption xwaylandXAuthorityOption(QStringLiteral("xwayland-xauthority"),
                                                i18n("Name of the xauthority file "),
                                                "xwayland-xauthority");

    QCommandLineOption replaceOption(QStringLiteral("replace"),
                                     i18n("Exits this instance so it can be restarted by kwin_wayland_wrapper."));

    QCommandLineOption drmOption(QStringLiteral("drm"), i18n("Render through drm node."));
    QCommandLineOption locale1Option(QStringLiteral("locale1"), i18n("Extract locale information from locale1 rather than the user's configuration"));

    QCommandLineParser parser;
    a.setupCommandLine(&parser);
    parser.addOption(xwaylandOption);
    parser.addOption(waylandSocketOption);
    parser.addOption(waylandSocketFdOption);
    parser.addOption(xwaylandListenFdOption);
    parser.addOption(xwaylandDisplayOption);
    parser.addOption(xwaylandXAuthorityOption);
    parser.addOption(replaceOption);
    parser.addOption(x11DisplayOption);
    parser.addOption(waylandDisplayOption);
    parser.addOption(virtualFbOption);
    parser.addOption(widthOption);
    parser.addOption(heightOption);
    parser.addOption(scaleOption);
    parser.addOption(outputCountOption);
    parser.addOption(drmOption);
    parser.addOption(locale1Option);

    QCommandLineOption inputMethodOption(QStringLiteral("inputmethod"),
                                         i18n("Input method that KWin starts."),
                                         QStringLiteral("path/to/imserver"));
    parser.addOption(inputMethodOption);

#if KWIN_BUILD_SCREENLOCKER
    QCommandLineOption screenLockerOption(QStringLiteral("lockscreen"),
                                          i18n("Starts the session in locked mode."));
    parser.addOption(screenLockerOption);

    QCommandLineOption noScreenLockerOption(QStringLiteral("no-lockscreen"),
                                            i18n("Starts the session without lock screen support."));
    parser.addOption(noScreenLockerOption);
#endif

    QCommandLineOption noGlobalShortcutsOption(QStringLiteral("no-global-shortcuts"),
                                               i18n("Starts the session without global shortcuts support."));
    parser.addOption(noGlobalShortcutsOption);

#if KWIN_BUILD_ACTIVITIES
    QCommandLineOption noActivitiesOption(QStringLiteral("no-kactivities"),
                                          i18n("Disable KActivities integration."));
    parser.addOption(noActivitiesOption);
#endif

    QCommandLineOption exitWithSessionOption(QStringLiteral("exit-with-session"),
                                             i18n("Exit after the session application, which is started by KWin, closed."),
                                             QStringLiteral("/path/to/session"));
    parser.addOption(exitWithSessionOption);

    parser.addPositionalArgument(QStringLiteral("applications"),
                                 i18n("Applications to start once Wayland and Xwayland server are started"),
                                 QStringLiteral("[/path/to/application...]"));

    parser.process(a);
    a.processCommandLine(&parser);

#if KWIN_BUILD_ACTIVITIES
    if (parser.isSet(noActivitiesOption)) {
        a.setUseKActivities(false);
    }
#endif

    if (parser.isSet(replaceOption)) {
        QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), QStringLiteral("/KWin"),
                                                          QStringLiteral("org.kde.KWin"), QStringLiteral("replace"));
        QDBusConnection::sessionBus().call(msg, QDBus::NoBlock);
        return 0;
    }

    if (parser.isSet(exitWithSessionOption)) {
        a.setSessionArgument(parser.value(exitWithSessionOption));
    }

    enum class BackendType {
        Kms,
        X11,
        Wayland,
        Virtual,
    };

    BackendType backendType;
    QString pluginName;
    QSize initialWindowSize;
    int outputCount = 1;
    qreal outputScale = 1;

    // Decide what backend to use.
    if (parser.isSet(drmOption)) {
        backendType = BackendType::Kms;
    } else if (parser.isSet(x11DisplayOption)) {
        backendType = BackendType::X11;
    } else if (parser.isSet(waylandDisplayOption)) {
        backendType = BackendType::Wayland;
    } else if (parser.isSet(virtualFbOption)) {
        backendType = BackendType::Virtual;
    } else {
        if (qEnvironmentVariableIsSet("WAYLAND_DISPLAY")) {
            qWarning("No backend specified, automatically choosing Wayland because WAYLAND_DISPLAY is set");
            backendType = BackendType::Wayland;
        } else if (qEnvironmentVariableIsSet("DISPLAY")) {
            qWarning("No backend specified, automatically choosing X11 because DISPLAY is set");
            backendType = BackendType::X11;
        } else {
            qWarning("No backend specified, automatically choosing drm");
            backendType = BackendType::Kms;
        }
    }

    if (parser.isSet(locale1Option)) {
        a.setFollowLocale1(true);
    }

    bool ok = false;
    const int width = parser.value(widthOption).toInt(&ok);
    if (!ok) {
        std::cerr << "FATAL ERROR incorrect value for width" << std::endl;
        return 1;
    }
    const int height = parser.value(heightOption).toInt(&ok);
    if (!ok) {
        std::cerr << "FATAL ERROR incorrect value for height" << std::endl;
        return 1;
    }
    const qreal scale = parser.value(scaleOption).toDouble(&ok);
    if (!ok || scale <= 0) {
        std::cerr << "FATAL ERROR incorrect value for scale" << std::endl;
        return 1;
    }

    outputScale = scale;
    initialWindowSize = QSize(width, height);

    const int count = parser.value(outputCountOption).toInt(&ok);
    if (ok) {
        outputCount = std::max(1, count);
    }

    // TODO: create backend without having the server running
    KWin::WaylandServer *server = KWin::WaylandServer::create(&a);

    KWin::WaylandServer::InitializationFlags flags;
#if KWIN_BUILD_SCREENLOCKER
    if (parser.isSet(screenLockerOption)) {
        flags = KWin::WaylandServer::InitializationFlag::LockScreen;
    } else if (parser.isSet(noScreenLockerOption)) {
        flags = KWin::WaylandServer::InitializationFlag::NoLockScreenIntegration;
    }
#endif
    if (parser.isSet(noGlobalShortcutsOption)) {
        flags |= KWin::WaylandServer::InitializationFlag::NoGlobalShortcuts;
    }

    const QString socketName = parser.value(waylandSocketOption);
    if (parser.isSet(waylandSocketFdOption)) {
        bool ok;
        int fd = parser.value(waylandSocketFdOption).toInt(&ok);
        if (ok) {
            // make sure we don't leak this FD to children
            fcntl(fd, F_SETFD, FD_CLOEXEC);
            server->display()->addSocketFileDescriptor(fd, socketName);
        } else {
            std::cerr << "FATAL ERROR: could not parse socket FD" << std::endl;
            return 1;
        }
    } else {
        // socketName empty is fine here, addSocketName will automatically pick one
        if (!server->display()->addSocketName(socketName)) {
            std::cerr << "FATAL ERROR: could not add wayland socket " << qPrintable(socketName) << std::endl;
            return 1;
        }
    }

    if (!server->init(flags)) {
        std::cerr << "FATAL ERROR: could not create Wayland server" << std::endl;
        return 1;
    }

    switch (backendType) {
    case BackendType::Kms:
        a.setSession(KWin::Session::create());
        if (!a.session()) {
            std::cerr << "FATAl ERROR: could not acquire a session" << std::endl;
            return 1;
        }
        a.setOutputBackend(std::make_unique<KWin::DrmBackend>(a.session()));
        break;
    case BackendType::Virtual: {
        auto outputBackend = std::make_unique<KWin::VirtualBackend>();
        for (int i = 0; i < outputCount; ++i) {
            outputBackend->addOutput(initialWindowSize, outputScale);
        }
        a.setSession(KWin::Session::create(KWin::Session::Type::Noop));
        a.setOutputBackend(std::move(outputBackend));
        break;
    }
    case BackendType::X11: {
        QString display = parser.value(x11DisplayOption);
        if (display.isEmpty()) {
            display = qgetenv("DISPLAY");
        }
        a.setSession(KWin::Session::create(KWin::Session::Type::Noop));
        a.setOutputBackend(std::make_unique<KWin::X11WindowedBackend>(KWin::X11WindowedBackendOptions{
            .display = display,
            .outputCount = outputCount,
            .outputScale = outputScale,
            .outputSize = initialWindowSize,
        }));
        break;
    }
    case BackendType::Wayland: {
        QString socketName = parser.value(waylandDisplayOption);
        if (socketName.isEmpty()) {
            socketName = qgetenv("WAYLAND_DISPLAY");
        }
        a.setSession(KWin::Session::create(KWin::Session::Type::Noop));
        a.setOutputBackend(std::make_unique<KWin::Wayland::WaylandBackend>(KWin::Wayland::WaylandBackendOptions{
            .socketName = socketName,
            .outputCount = outputCount,
            .outputScale = outputScale,
            .outputSize = initialWindowSize,
        }));
        break;
    }
    }

    QObject::connect(&a, &KWin::Application::workspaceCreated, server, &KWin::WaylandServer::initWorkspace);
    if (!server->socketName().isEmpty()) {
        environment.insert(QStringLiteral("WAYLAND_DISPLAY"), server->socketName());
        qputenv("WAYLAND_DISPLAY", server->socketName().toUtf8());
    }
    if (!qEnvironmentVariableIsSet("XCURSOR_SIZE")) {
        auto config = KSharedConfig::openConfig(QStringLiteral("kcminputrc"));
        KConfigGroup mouseConfig = config->group("Mouse");
        const uint themeSize = mouseConfig.readEntry("cursorSize", 24);
        environment.insert(QStringLiteral("XCURSOR_SIZE"), QString::number(themeSize));
    }
    a.setProcessStartupEnvironment(environment);

    if (parser.isSet(xwaylandOption)) {
        a.setStartXwayland(true);

        if (parser.isSet(xwaylandListenFdOption)) {
            const QStringList fdStrings = parser.values(xwaylandListenFdOption);
            for (const QString &fdString : fdStrings) {
                bool ok;
                int fd = fdString.toInt(&ok);
                if (ok) {
                    // make sure we don't leak this FD to children
                    fcntl(fd, F_SETFD, FD_CLOEXEC);
                    a.addXwaylandSocketFileDescriptor(fd);
                }
            }
            if (parser.isSet(xwaylandDisplayOption)) {
                a.setXwaylandDisplay(parser.value(xwaylandDisplayOption));
            } else {
                std::cerr << "Using xwayland-fd without xwayland-display is undefined" << std::endl;
                return 1;
            }
            if (parser.isSet(xwaylandXAuthorityOption)) {
                a.setXwaylandXauthority(parser.value(xwaylandXAuthorityOption));
            }
        }
    }

    a.setApplicationsToStart(parser.positionalArguments());
    a.setInputMethodServerToStart(parser.value(inputMethodOption));
    a.start();

    return a.exec();
}
