/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2014 Martin Gräßlin 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 "compositor_wayland.h" #include "core/outputbackend.h" #include "core/session.h" #include "effect/effecthandler.h" #include "inputmethod.h" #include "tabletmodemanager.h" #include "utils/realtime.h" #include "wayland/display.h" #include "wayland/seat.h" #include "wayland_server.h" #include "workspace.h" #if KWIN_BUILD_X11 #include "xwayland/xwayland.h" #include "xwayland/xwaylandlauncher.h" #endif // KDE #include #include #include #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include Q_IMPORT_PLUGIN(KWinIntegrationPlugin) #if KWIN_BUILD_GLOBALSHORTCUTS Q_IMPORT_PLUGIN(KGlobalAccelImpl) #endif Q_IMPORT_PLUGIN(KWindowSystemKWinPlugin) Q_IMPORT_PLUGIN(KWinIdleTimePoller) 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; } } //************************************ // 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) { effects->unloadAllEffects(); } #if KWIN_BUILD_X11 m_xwayland.reset(); #endif destroyColorManager(); destroyWorkspace(); destroyInputMethod(); destroyCompositor(); destroyInput(); delete WaylandServer::self(); } void ApplicationWayland::performStartup() { #if KWIN_BUILD_X11 if (m_startXWayland) { setOperationMode(OperationModeXwayland); setXwaylandScale(config()->group(QStringLiteral("Xwayland")).readEntry("Scale", 1.0)); } #endif createOptions(); if (!outputBackend()->initialize()) { std::exit(1); } createInput(); createInputMethod(); createTabletModeManager(); WaylandCompositor::create(); createWorkspace(); createColorManager(); createPlugins(); connect(Compositor::self(), &Compositor::sceneCreated, outputBackend(), &OutputBackend::sceneInitialized); connect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithScene, Qt::SingleShotConnection); } void ApplicationWayland::continueStartupWithScene() { // Note that we start accepting client connections after creating the Workspace. if (!waylandServer()->start()) { qFatal("Failed to initialze the Wayland server, exiting now"); } #if KWIN_BUILD_X11 if (operationMode() == OperationModeXwayland) { m_xwayland = std::make_unique(this); m_xwayland->xwaylandLauncher()->setListenFDs(m_xwaylandListenFds); m_xwayland->xwaylandLauncher()->setDisplayName(m_xwaylandDisplay); m_xwayland->xwaylandLauncher()->setXauthority(m_xwaylandXauthority); m_xwayland->init(); connect(m_xwayland.get(), &Xwl::Xwayland::started, this, &ApplicationWayland::applyXwaylandScale); } #endif 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())); } } 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(QStringLiteral("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::ForwardedChannels); p->setProcessEnvironment(processStartupEnvironment()); connect(p, qOverload(&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::ForwardedChannels); p->setProcessEnvironment(processStartupEnvironment()); p->setProgram(program); p->setArguments(arguments); p->startDetached(); p->deleteLater(); } } } #if KWIN_BUILD_X11 XwaylandInterface *ApplicationWayland::xwayland() const { return m_xwayland.get(); } #endif } // namespace int main(int argc, char *argv[]) { 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); // The shader (currently) causes a blocking disk flush on load and save of every QQuickWindow // Because it's on load, it will happen every time not just occasionally // The gains are minimal, disable until it's fixed QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache); KWin::ApplicationWayland a(argc, argv); // 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"); KSignalHandler::self()->watchSignal(SIGTERM); KSignalHandler::self()->watchSignal(SIGINT); KSignalHandler::self()->watchSignal(SIGHUP); QObject::connect(KSignalHandler::self(), &KSignalHandler::signalReceived, &a, &QCoreApplication::exit); KWin::Application::createAboutData(); KCrash::initialize(); #if KWIN_BUILD_X11 QCommandLineOption xwaylandOption(QStringLiteral("xwayland"), i18n("Start a rootless Xwayland server.")); #endif 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")); #if KWIN_BUILD_X11 QCommandLineOption x11DisplayOption(QStringLiteral("x11-display"), i18n("The X11 Display to use in windowed mode on platform X11."), QStringLiteral("display")); #endif 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); #if KWIN_BUILD_X11 parser.addOption(xwaylandOption); #endif parser.addOption(waylandSocketOption); parser.addOption(waylandSocketFdOption); parser.addOption(xwaylandListenFdOption); parser.addOption(xwaylandDisplayOption); parser.addOption(xwaylandXAuthorityOption); parser.addOption(replaceOption); #if KWIN_BUILD_X11 parser.addOption(x11DisplayOption); #endif 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; #if KWIN_BUILD_X11 } else if (parser.isSet(x11DisplayOption)) { backendType = BackendType::X11; #endif } else if (parser.isSet(waylandDisplayOption)) { backendType = BackendType::Wayland; } else if (parser.isSet(virtualFbOption)) { backendType = BackendType::Virtual; } else { if (qEnvironmentVariableIsSet("WAYLAND_DISPLAY")) { qInfo("No backend specified, automatically choosing Wayland because WAYLAND_DISPLAY is set"); backendType = BackendType::Wayland; } else if (qEnvironmentVariableIsSet("DISPLAY")) { qInfo("No backend specified, automatically choosing X11 because DISPLAY is set"); backendType = BackendType::X11; } else { qInfo("No backend specified, automatically choosing drm"); backendType = BackendType::Kms; } } if (parser.isSet(locale1Option)) { a.setFollowLocale1(true); } else { a.setFollowLocale1(a.config()->group(QStringLiteral("Wayland")).readEntry("FollowLocale1", false)); } 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); } 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(a.session())); break; case BackendType::Virtual: { auto outputBackend = std::make_unique(); for (int i = 0; i < outputCount; ++i) { outputBackend->addOutput(KWin::VirtualBackend::OutputInfo{ .geometry = QRect(QPoint(), initialWindowSize), .scale = outputScale, }); } a.setSession(KWin::Session::create(KWin::Session::Type::Noop)); a.setOutputBackend(std::move(outputBackend)); break; } #if KWIN_BUILD_X11 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::X11WindowedBackendOptions{ .display = display, .outputCount = outputCount, .outputScale = outputScale, .outputSize = initialWindowSize, })); break; } #endif 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::WaylandBackendOptions{ .socketName = socketName, .outputCount = outputCount, .outputScale = outputScale, .outputSize = initialWindowSize, })); break; } } KWin::WaylandServer *server = KWin::WaylandServer::create(); #if KWIN_BUILD_SCREENLOCKER if (parser.isSet(screenLockerOption)) { a.setInitiallyLocked(true); } else if (parser.isSet(noScreenLockerOption)) { a.setSupportsLockScreen(false); } #endif if (parser.isSet(noGlobalShortcutsOption)) { a.setSupportsGlobalShortcuts(false); } 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; } qInfo() << "Accepting client connections on sockets:" << server->display()->socketNames(); } if (!server->init()) { std::cerr << "FATAL ERROR: could not create Wayland server" << std::endl; return 1; } 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()); } a.setProcessStartupEnvironment(environment); #if KWIN_BUILD_X11 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)); } } } #endif a.setApplicationsToStart(parser.positionalArguments()); a.setInputMethodServerToStart(parser.value(inputMethodOption)); a.start(); return a.exec(); } #include "moc_main_wayland.cpp"