diff --git a/main_wayland.cpp b/main_wayland.cpp index 1ad7dd167..e734b838a 100644 --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -1,678 +1,678 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin 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, see . *********************************************************************/ #include "main_wayland.h" #include "composite.h" #include "virtualkeyboard.h" #include "workspace.h" #include // kwin #include "platform.h" #include "effects.h" #include "tabletmodemanager.h" #include "wayland_server.h" #include "xwl/xwayland.h" // KWayland #include #include // KDE #include #include #include #include #include // Qt #include #include #include #include #include #include #include // system #if HAVE_SYS_PRCTL_H #include #endif #if HAVE_SYS_PROCCTL_H #include #endif #if HAVE_LIBCAP #include #endif #include #include #include namespace KWin { static void sighandler(int) { QApplication::exit(); } void disableDrKonqi() { KCrash::setDrKonqiEnabled(false); } // run immediately, before Q_CORE_STARTUP functions // that would enable drkonqi Q_CONSTRUCTOR_FUNCTION(disableDrKonqi) enum class RealTimeFlags { DontReset, ResetOnFork }; namespace { void gainRealTime(RealTimeFlags flags = RealTimeFlags::DontReset) { #if HAVE_SCHED_RESET_ON_FORK const int minPriority = sched_get_priority_min(SCHED_RR); struct sched_param sp; sp.sched_priority = minPriority; int policy = SCHED_RR; if (flags == RealTimeFlags::ResetOnFork) { policy |= SCHED_RESET_ON_FORK; } sched_setscheduler(0, policy, &sp); #else Q_UNUSED(flags); #endif } } //************************************ // ApplicationWayland //************************************ ApplicationWayland::ApplicationWayland(int &argc, char **argv) : ApplicationWaylandAbstract(OperationModeWaylandOnly, argc, argv) { } ApplicationWayland::~ApplicationWayland() { setTerminating(); if (!waylandServer()) { return; } - if (kwinApp()->platform()) { - kwinApp()->platform()->setOutputsEnabled(false); + if (auto *platform = kwinApp()->platform()) { + platform->prepareShutdown(); } // need to unload all effects prior to destroying X connection as they might do X calls if (effects) { static_cast(effects)->unloadAllEffects(); } if (m_xwayland) { // needs to be done before workspace gets destroyed m_xwayland->prepareDestroy(); } destroyWorkspace(); waylandServer()->dispatch(); if (QStyle *s = style()) { s->unpolish(this); } // kill Xwayland before terminating its connection delete m_xwayland; m_xwayland = nullptr; waylandServer()->terminateClientConnections(); destroyCompositor(); } void ApplicationWayland::performStartup() { if (m_startXWayland) { setOperationMode(OperationModeXwayland); } // first load options - done internally by a different thread createOptions(); waylandServer()->createInternalConnection(); // try creating the Wayland Backend createInput(); // now libinput thread has been created, adjust scheduler to not leak into other processes gainRealTime(RealTimeFlags::ResetOnFork); VirtualKeyboard::create(this); createBackend(); TabletModeManager::create(this); } void ApplicationWayland::createBackend() { connect(platform(), &Platform::screensQueried, this, &ApplicationWayland::continueStartupWithScreens); connect(platform(), &Platform::initFailed, this, [] () { std::cerr << "FATAL ERROR: backend failed to initialize, exiting now" << std::endl; QCoreApplication::exit(1); } ); platform()->init(); } void ApplicationWayland::continueStartupWithScreens() { disconnect(kwinApp()->platform(), &Platform::screensQueried, this, &ApplicationWayland::continueStartupWithScreens); createScreens(); WaylandCompositor::create(); connect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithScene); } void ApplicationWayland::finalizeStartup() { if (m_xwayland) { disconnect(m_xwayland, &Xwl::Xwayland::initialized, this, &ApplicationWayland::finalizeStartup); } startSession(); createWorkspace(); notifyKSplash(); } void ApplicationWayland::continueStartupWithScene() { disconnect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithScene); if (operationMode() == OperationModeWaylandOnly) { finalizeStartup(); return; } m_xwayland = new Xwl::Xwayland(this); connect(m_xwayland, &Xwl::Xwayland::criticalError, this, [](int code) { // we currently exit on Xwayland errors always directly // TODO: restart Xwayland std::cerr << "Xwayland had a critical error. Going to exit now." << std::endl; exit(code); }); connect(m_xwayland, &Xwl::Xwayland::initialized, this, &ApplicationWayland::finalizeStartup); m_xwayland->init(); } void ApplicationWayland::startSession() { if (!m_inputMethodServerToStart.isEmpty()) { int socket = dup(waylandServer()->createInputMethodConnection()); if (socket >= 0) { QProcessEnvironment environment = processStartupEnvironment(); environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland")); environment.remove("DISPLAY"); environment.remove("WAYLAND_DISPLAY"); QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); auto finishedSignal = static_cast(&QProcess::finished); connect(p, finishedSignal, this, [this, p] { if (waylandServer()) { waylandServer()->destroyInputMethodConnection(); } p->deleteLater(); } ); p->setProcessEnvironment(environment); p->start(m_inputMethodServerToStart); p->waitForStarted(); } } // start session if (!m_sessionArgument.isEmpty()) { QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); p->setProcessEnvironment(processStartupEnvironment()); auto finishedSignal = static_cast(&QProcess::finished); connect(p, finishedSignal, this, [](int code, QProcess::ExitStatus status) { 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->start(m_sessionArgument); } // start the applications passed to us as command line arguments if (!m_applicationsToStart.isEmpty()) { for (const QString &application: m_applicationsToStart) { // 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 Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); p->setProcessEnvironment(processStartupEnvironment()); p->start(application); } } } static const QString s_waylandPlugin = QStringLiteral("KWinWaylandWaylandBackend"); static const QString s_x11Plugin = QStringLiteral("KWinWaylandX11Backend"); static const QString s_fbdevPlugin = QStringLiteral("KWinWaylandFbdevBackend"); #if HAVE_DRM static const QString s_drmPlugin = QStringLiteral("KWinWaylandDrmBackend"); #endif #if HAVE_LIBHYBRIS static const QString s_hwcomposerPlugin = QStringLiteral("KWinWaylandHwcomposerBackend"); #endif static const QString s_virtualPlugin = QStringLiteral("KWinWaylandVirtualBackend"); static QString automaticBackendSelection() { if (qEnvironmentVariableIsSet("WAYLAND_DISPLAY")) { return s_waylandPlugin; } if (qEnvironmentVariableIsSet("DISPLAY")) { return s_x11Plugin; } #if HAVE_LIBHYBRIS if (qEnvironmentVariableIsSet("ANDROID_ROOT")) { return s_hwcomposerPlugin; } #endif #if HAVE_DRM return s_drmPlugin; #endif return s_fbdevPlugin; } static void disablePtrace() { #if HAVE_PR_SET_DUMPABLE // check whether we are running under a debugger const QFileInfo parent(QStringLiteral("/proc/%1/exe").arg(getppid())); if (parent.isSymLink() && (parent.symLinkTarget().endsWith(QLatin1String("/gdb")) || parent.symLinkTarget().endsWith(QLatin1String("/gdbserver")) || parent.symLinkTarget().endsWith(QLatin1String("/lldb-server")))) { // debugger, don't adjust return; } // disable ptrace in kwin_wayland prctl(PR_SET_DUMPABLE, 0); #endif #if HAVE_PROC_TRACE_CTL // FreeBSD's rudimentary procfs does not support /proc//exe // We could use the P_TRACED flag of the process to find out // if the process is being debugged ond FreeBSD. int mode = PROC_TRACE_CTL_DISABLE; procctl(P_PID, getpid(), PROC_TRACE_CTL, &mode); #endif } static void unsetDumpable(int sig) { #if HAVE_PR_SET_DUMPABLE prctl(PR_SET_DUMPABLE, 1); #endif signal(sig, SIG_IGN); raise(sig); return; } void dropNiceCapability() { #if HAVE_LIBCAP cap_t caps = cap_get_proc(); if (!caps) { return; } cap_value_t capList[] = { CAP_SYS_NICE }; if (cap_set_flag(caps, CAP_PERMITTED, 1, capList, CAP_CLEAR) == -1) { cap_free(caps); return; } if (cap_set_flag(caps, CAP_EFFECTIVE, 1, capList, CAP_CLEAR) == -1) { cap_free(caps); return; } cap_set_proc(caps); cap_free(caps); #endif } } // namespace int main(int argc, char * argv[]) { if (getuid() == 0) { std::cerr << "kwin_wayland does not support running as root." << std::endl; return 1; } KWin::disablePtrace(); KWin::Application::setupMalloc(); KWin::Application::setupLocalizedString(); KWin::gainRealTime(); KWin::dropNiceCapability(); if (signal(SIGTERM, KWin::sighandler) == SIG_IGN) signal(SIGTERM, SIG_IGN); if (signal(SIGINT, KWin::sighandler) == SIG_IGN) signal(SIGINT, SIG_IGN); if (signal(SIGHUP, KWin::sighandler) == SIG_IGN) signal(SIGHUP, SIG_IGN); signal(SIGABRT, KWin::unsetDumpable); signal(SIGSEGV, KWin::unsetDumpable); signal(SIGPIPE, SIG_IGN); // ensure that no thread takes SIGUSR sigset_t userSignals; sigemptyset(&userSignals); sigaddset(&userSignals, SIGUSR1); sigaddset(&userSignals, SIGUSR2); pthread_sigmask(SIG_BLOCK, &userSignals, nullptr); 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); qunsetenv("QT_DEVICE_PIXEL_RATIO"); qputenv("QT_IM_MODULE", "qtvirtualkeyboard"); qputenv("QSG_RENDER_LOOP", "basic"); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); KWin::ApplicationWayland a(argc, argv); a.setupTranslator(); // reset QT_QPA_PLATFORM to a sane value for any processes started from KWin setenv("QT_QPA_PLATFORM", "wayland", true); KWin::Application::createAboutData(); KQuickAddons::QtQuickSettings::init(); const auto availablePlugins = KPluginLoader::findPlugins(QStringLiteral("org.kde.kwin.waylandbackends")); auto hasPlugin = [&availablePlugins] (const QString &name) { return std::any_of(availablePlugins.begin(), availablePlugins.end(), [name] (const KPluginMetaData &plugin) { return plugin.pluginId() == name; } ); }; const bool hasSizeOption = hasPlugin(KWin::s_x11Plugin) || hasPlugin(KWin::s_virtualPlugin); const bool hasOutputCountOption = hasPlugin(KWin::s_x11Plugin); const bool hasX11Option = hasPlugin(KWin::s_x11Plugin); const bool hasVirtualOption = hasPlugin(KWin::s_virtualPlugin); const bool hasWaylandOption = hasPlugin(KWin::s_waylandPlugin); const bool hasFramebufferOption = hasPlugin(KWin::s_fbdevPlugin); #if HAVE_DRM const bool hasDrmOption = hasPlugin(KWin::s_drmPlugin); #endif #if HAVE_LIBHYBRIS const bool hasHwcomposerOption = hasPlugin(KWin::s_hwcomposerPlugin); #endif 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 framebufferOption(QStringLiteral("framebuffer"), i18n("Render to framebuffer.")); QCommandLineOption framebufferDeviceOption(QStringLiteral("fb-device"), i18n("The framebuffer device to render to."), QStringLiteral("fbdev")); 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)); QCommandLineParser parser; a.setupCommandLine(&parser); parser.addOption(xwaylandOption); parser.addOption(waylandSocketOption); if (hasX11Option) { parser.addOption(x11DisplayOption); } if (hasWaylandOption) { parser.addOption(waylandDisplayOption); } if (hasFramebufferOption) { parser.addOption(framebufferOption); parser.addOption(framebufferDeviceOption); } if (hasVirtualOption) { parser.addOption(virtualFbOption); } if (hasSizeOption) { parser.addOption(widthOption); parser.addOption(heightOption); parser.addOption(scaleOption); } if (hasOutputCountOption) { parser.addOption(outputCountOption); } #if HAVE_LIBHYBRIS QCommandLineOption hwcomposerOption(QStringLiteral("hwcomposer"), i18n("Use libhybris hwcomposer")); if (hasHwcomposerOption) { parser.addOption(hwcomposerOption); } #endif QCommandLineOption libinputOption(QStringLiteral("libinput"), i18n("Enable libinput support for input events processing. Note: never use in a nested session.")); parser.addOption(libinputOption); #if HAVE_DRM QCommandLineOption drmOption(QStringLiteral("drm"), i18n("Render through drm node.")); if (hasDrmOption) { parser.addOption(drmOption); } #endif QCommandLineOption inputMethodOption(QStringLiteral("inputmethod"), i18n("Input method that KWin starts."), QStringLiteral("path/to/imserver")); parser.addOption(inputMethodOption); QCommandLineOption listBackendsOption(QStringLiteral("list-backends"), i18n("List all available backends and quit.")); parser.addOption(listBackendsOption); 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); QCommandLineOption noGlobalShortcutsOption(QStringLiteral("no-global-shortcuts"), i18n("Starts the session without global shortcuts support.")); parser.addOption(noGlobalShortcutsOption); 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); #ifdef KWIN_BUILD_ACTIVITIES a.setUseKActivities(false); #endif if (parser.isSet(listBackendsOption)) { for (const auto &plugin: availablePlugins) { std::cout << std::setw(40) << std::left << qPrintable(plugin.name()) << qPrintable(plugin.description()) << std::endl; } return 0; } if (parser.isSet(exitWithSessionOption)) { a.setSessionArgument(parser.value(exitWithSessionOption)); } KWin::Application::setUseLibinput(parser.isSet(libinputOption)); QString pluginName; QSize initialWindowSize; QByteArray deviceIdentifier; int outputCount = 1; qreal outputScale = 1; #if HAVE_DRM if (hasDrmOption && parser.isSet(drmOption)) { pluginName = KWin::s_drmPlugin; } #endif if (hasSizeOption) { 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 < 1) { std::cerr << "FATAL ERROR incorrect value for scale" << std::endl; return 1; } outputScale = scale; initialWindowSize = QSize(width, height); } if (hasOutputCountOption) { bool ok = false; const int count = parser.value(outputCountOption).toInt(&ok); if (ok) { outputCount = qMax(1, count); } } if (hasX11Option && parser.isSet(x11DisplayOption)) { deviceIdentifier = parser.value(x11DisplayOption).toUtf8(); pluginName = KWin::s_x11Plugin; } else if (hasWaylandOption && parser.isSet(waylandDisplayOption)) { deviceIdentifier = parser.value(waylandDisplayOption).toUtf8(); pluginName = KWin::s_waylandPlugin; } if (hasFramebufferOption && parser.isSet(framebufferOption)) { pluginName = KWin::s_fbdevPlugin; deviceIdentifier = parser.value(framebufferDeviceOption).toUtf8(); } #if HAVE_LIBHYBRIS if (hasHwcomposerOption && parser.isSet(hwcomposerOption)) { pluginName = KWin::s_hwcomposerPlugin; } #endif if (hasVirtualOption && parser.isSet(virtualFbOption)) { pluginName = KWin::s_virtualPlugin; } if (pluginName.isEmpty()) { std::cerr << "No backend specified through command line argument, trying auto resolution" << std::endl; pluginName = KWin::automaticBackendSelection(); } auto pluginIt = std::find_if(availablePlugins.begin(), availablePlugins.end(), [&pluginName] (const KPluginMetaData &plugin) { return plugin.pluginId() == pluginName; } ); if (pluginIt == availablePlugins.end()) { std::cerr << "FATAL ERROR: could not find a backend" << std::endl; return 1; } // TODO: create backend without having the server running KWin::WaylandServer *server = KWin::WaylandServer::create(&a); KWin::WaylandServer::InitalizationFlags flags; if (parser.isSet(screenLockerOption)) { flags = KWin::WaylandServer::InitalizationFlag::LockScreen; } else if (parser.isSet(noScreenLockerOption)) { flags = KWin::WaylandServer::InitalizationFlag::NoLockScreenIntegration; } if (parser.isSet(noGlobalShortcutsOption)) { flags |= KWin::WaylandServer::InitalizationFlag::NoGlobalShortcuts; } if (!server->init(parser.value(waylandSocketOption).toUtf8(), flags)) { std::cerr << "FATAL ERROR: could not create Wayland server" << std::endl; return 1; } a.initPlatform(*pluginIt); if (!a.platform()) { std::cerr << "FATAL ERROR: could not instantiate a backend" << std::endl; return 1; } if (!deviceIdentifier.isEmpty()) { a.platform()->setDeviceIdentifier(deviceIdentifier); } if (initialWindowSize.isValid()) { a.platform()->setInitialWindowSize(initialWindowSize); } a.platform()->setInitialOutputScale(outputScale); a.platform()->setInitialOutputCount(outputCount); QObject::connect(&a, &KWin::Application::workspaceCreated, server, &KWin::WaylandServer::initWorkspace); environment.insert(QStringLiteral("WAYLAND_DISPLAY"), server->display()->socketName()); a.setProcessStartupEnvironment(environment); a.setStartXwayland(parser.isSet(xwaylandOption)); a.setApplicationsToStart(parser.positionalArguments()); a.setInputMethodServerToStart(parser.value(inputMethodOption)); a.start(); return a.exec(); } diff --git a/platform.cpp b/platform.cpp index 84a9e1044..05f2beba6 100644 --- a/platform.cpp +++ b/platform.cpp @@ -1,487 +1,492 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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, see . *********************************************************************/ #include "platform.h" #include #include "composite.h" #include "cursor.h" #include "effects.h" #include #include "overlaywindow.h" #include "outline.h" #include "pointer_input.h" #include "scene.h" #include "screenedge.h" #include "wayland_server.h" #include "colorcorrection/manager.h" #include namespace KWin { Platform::Platform(QObject *parent) : QObject(parent) , m_eglDisplay(EGL_NO_DISPLAY) { setSoftWareCursor(false); m_colorCorrect = new ColorCorrect::Manager(this); } Platform::~Platform() { if (m_eglDisplay != EGL_NO_DISPLAY) { eglTerminate(m_eglDisplay); } } QImage Platform::softwareCursor() const { return input()->pointer()->cursorImage(); } QPoint Platform::softwareCursorHotspot() const { return input()->pointer()->cursorHotSpot(); } PlatformCursorImage Platform::cursorImage() const { return PlatformCursorImage(softwareCursor(), softwareCursorHotspot()); } void Platform::hideCursor() { m_hideCursorCounter++; if (m_hideCursorCounter == 1) { doHideCursor(); } } void Platform::doHideCursor() { } void Platform::showCursor() { m_hideCursorCounter--; if (m_hideCursorCounter == 0) { doShowCursor(); } } void Platform::doShowCursor() { } Screens *Platform::createScreens(QObject *parent) { Q_UNUSED(parent) return nullptr; } OpenGLBackend *Platform::createOpenGLBackend() { return nullptr; } QPainterBackend *Platform::createQPainterBackend() { return nullptr; } +void Platform::prepareShutdown() +{ + setOutputsEnabled(false); +} + Edge *Platform::createScreenEdge(ScreenEdges *edges) { return new Edge(edges); } void Platform::createPlatformCursor(QObject *parent) { new InputRedirectionCursor(parent); } void Platform::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) { qCWarning(KWIN_CORE) << "This backend does not support configuration changes."; config->setFailed(); } void Platform::setSoftWareCursor(bool set) { if (qEnvironmentVariableIsSet("KWIN_FORCE_SW_CURSOR")) { set = true; } if (m_softWareCursor == set) { return; } m_softWareCursor = set; if (m_softWareCursor) { connect(Cursor::self(), &Cursor::posChanged, this, &Platform::triggerCursorRepaint); connect(this, &Platform::cursorChanged, this, &Platform::triggerCursorRepaint); } else { disconnect(Cursor::self(), &Cursor::posChanged, this, &Platform::triggerCursorRepaint); disconnect(this, &Platform::cursorChanged, this, &Platform::triggerCursorRepaint); } } void Platform::triggerCursorRepaint() { if (!Compositor::self()) { return; } Compositor::self()->addRepaint(m_cursor.lastRenderedGeometry); Compositor::self()->addRepaint(QRect(Cursor::pos() - softwareCursorHotspot(), softwareCursor().size())); } void Platform::markCursorAsRendered() { if (m_softWareCursor) { m_cursor.lastRenderedGeometry = QRect(Cursor::pos() - softwareCursorHotspot(), softwareCursor().size()); } if (input()->pointer()) { input()->pointer()->markCursorAsRendered(); } } void Platform::keyboardKeyPressed(quint32 key, quint32 time) { if (!input()) { return; } input()->processKeyboardKey(key, InputRedirection::KeyboardKeyPressed, time); } void Platform::keyboardKeyReleased(quint32 key, quint32 time) { if (!input()) { return; } input()->processKeyboardKey(key, InputRedirection::KeyboardKeyReleased, time); } void Platform::keyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { if (!input()) { return; } input()->processKeyboardModifiers(modsDepressed, modsLatched, modsLocked, group); } void Platform::keymapChange(int fd, uint32_t size) { if (!input()) { return; } input()->processKeymapChange(fd, size); } void Platform::pointerAxisHorizontal(qreal delta, quint32 time, qint32 discreteDelta, InputRedirection::PointerAxisSource source) { if (!input()) { return; } input()->processPointerAxis(InputRedirection::PointerAxisHorizontal, delta, discreteDelta, source, time); } void Platform::pointerAxisVertical(qreal delta, quint32 time, qint32 discreteDelta, InputRedirection::PointerAxisSource source) { if (!input()) { return; } input()->processPointerAxis(InputRedirection::PointerAxisVertical, delta, discreteDelta, source, time); } void Platform::pointerButtonPressed(quint32 button, quint32 time) { if (!input()) { return; } input()->processPointerButton(button, InputRedirection::PointerButtonPressed, time); } void Platform::pointerButtonReleased(quint32 button, quint32 time) { if (!input()) { return; } input()->processPointerButton(button, InputRedirection::PointerButtonReleased, time); } void Platform::pointerMotion(const QPointF &position, quint32 time) { if (!input()) { return; } input()->processPointerMotion(position, time); } void Platform::touchCancel() { if (!input()) { return; } input()->cancelTouch(); } void Platform::touchDown(qint32 id, const QPointF &pos, quint32 time) { if (!input()) { return; } input()->processTouchDown(id, pos, time); } void Platform::touchFrame() { if (!input()) { return; } input()->touchFrame(); } void Platform::touchMotion(qint32 id, const QPointF &pos, quint32 time) { if (!input()) { return; } input()->processTouchMotion(id, pos, time); } void Platform::touchUp(qint32 id, quint32 time) { if (!input()) { return; } input()->processTouchUp(id, time); } void Platform::processSwipeGestureBegin(int fingerCount, quint32 time) { if (!input()) { return; } input()->pointer()->processSwipeGestureBegin(fingerCount, time); } void Platform::processSwipeGestureUpdate(const QSizeF &delta, quint32 time) { if (!input()) { return; } input()->pointer()->processSwipeGestureUpdate(delta, time); } void Platform::processSwipeGestureEnd(quint32 time) { if (!input()) { return; } input()->pointer()->processSwipeGestureEnd(time); } void Platform::processSwipeGestureCancelled(quint32 time) { if (!input()) { return; } input()->pointer()->processSwipeGestureCancelled(time); } void Platform::processPinchGestureBegin(int fingerCount, quint32 time) { if (!input()) { return; } input()->pointer()->processPinchGestureBegin(fingerCount, time); } void Platform::processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) { if (!input()) { return; } input()->pointer()->processPinchGestureUpdate(scale, angleDelta, delta, time); } void Platform::processPinchGestureEnd(quint32 time) { if (!input()) { return; } input()->pointer()->processPinchGestureEnd(time); } void Platform::processPinchGestureCancelled(quint32 time) { if (!input()) { return; } input()->pointer()->processPinchGestureCancelled(time); } void Platform::repaint(const QRect &rect) { if (!Compositor::self()) { return; } Compositor::self()->addRepaint(rect); } void Platform::setReady(bool ready) { if (m_ready == ready) { return; } m_ready = ready; emit readyChanged(m_ready); } void Platform::warpPointer(const QPointF &globalPos) { Q_UNUSED(globalPos) } bool Platform::supportsQpaContext() const { if (Compositor *c = Compositor::self()) { return c->scene()->openGLPlatformInterfaceExtensions().contains(QByteArrayLiteral("EGL_KHR_surfaceless_context")); } return false; } EGLDisplay KWin::Platform::sceneEglDisplay() const { return m_eglDisplay; } void Platform::setSceneEglDisplay(EGLDisplay display) { m_eglDisplay = display; } QSize Platform::screenSize() const { return QSize(); } QVector Platform::screenGeometries() const { return QVector({QRect(QPoint(0, 0), screenSize())}); } QVector Platform::screenScales() const { return QVector({1}); } bool Platform::requiresCompositing() const { return true; } bool Platform::compositingPossible() const { return true; } QString Platform::compositingNotPossibleReason() const { return QString(); } bool Platform::openGLCompositingIsBroken() const { return false; } void Platform::createOpenGLSafePoint(OpenGLSafePoint safePoint) { Q_UNUSED(safePoint) } void Platform::startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName) { if (!input()) { callback(nullptr); return; } input()->startInteractiveWindowSelection(callback, cursorName); } void Platform::startInteractivePositionSelection(std::function callback) { if (!input()) { callback(QPoint(-1, -1)); return; } input()->startInteractivePositionSelection(callback); } void Platform::setupActionForGlobalAccel(QAction *action) { Q_UNUSED(action) } OverlayWindow *Platform::createOverlayWindow() { return nullptr; } void Platform::updateXTime() { } OutlineVisual *Platform::createOutline(Outline *outline) { if (Compositor::compositing()) { return new CompositedOutlineVisual(outline); } return nullptr; } Decoration::Renderer *Platform::createDecorationRenderer(Decoration::DecoratedClientImpl *client) { if (Compositor::self()->hasScene()) { return Compositor::self()->scene()->createDecorationRenderer(client); } return nullptr; } void Platform::invertScreen() { if (effects) { if (Effect *inverter = static_cast(effects)->provides(Effect::ScreenInversion)) { qCDebug(KWIN_CORE) << "inverting screen using Effect plugin"; QMetaObject::invokeMethod(inverter, "toggleScreenInversion", Qt::DirectConnection); } } } void Platform::createEffectsHandler(Compositor *compositor, Scene *scene) { new EffectsHandlerImpl(compositor, scene); } QString Platform::supportInformation() const { return QStringLiteral("Name: %1\n").arg(metaObject()->className()); } } diff --git a/platform.h b/platform.h index 97e059a45..a018725a7 100644 --- a/platform.h +++ b/platform.h @@ -1,555 +1,564 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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, see . *********************************************************************/ #ifndef KWIN_PLATFORM_H #define KWIN_PLATFORM_H #include #include #include #include #include "fixqopengl.h" #include "input.h" #include #include #include class QAction; namespace KWayland { namespace Server { class OutputConfigurationInterface; } } namespace KWin { namespace ColorCorrect { class Manager; } class AbstractOutput; class Edge; class Compositor; class OverlayWindow; class OpenGLBackend; class Outline; class OutlineVisual; class QPainterBackend; class Scene; class Screens; class ScreenEdges; class Toplevel; class WaylandCursorTheme; namespace Decoration { class Renderer; class DecoratedClientImpl; } class KWIN_EXPORT Outputs : public QVector { public: Outputs(){}; template Outputs(const QVector &other) { resize(other.size()); std::copy(other.constBegin(), other.constEnd(), begin()); } }; class KWIN_EXPORT Platform : public QObject { Q_OBJECT public: ~Platform() override; virtual void init() = 0; virtual Screens *createScreens(QObject *parent = nullptr); virtual OpenGLBackend *createOpenGLBackend(); virtual QPainterBackend *createQPainterBackend(); + + /** + * Informs the Platform that it is about to go down and shall do appropriate cleanup. + * Child classes can override this function but must call the parent implementation in + * the end. + */ + virtual void prepareShutdown(); + /** * Allows the platform to create a platform specific screen edge. * The default implementation creates a Edge. */ virtual Edge *createScreenEdge(ScreenEdges *parent); /** * Allows the platform to create a platform specific Cursor. * The default implementation creates an InputRedirectionCursor. */ virtual void createPlatformCursor(QObject *parent = nullptr); virtual void warpPointer(const QPointF &globalPos); /** * Whether our Compositing EGL display allows a surface less context * so that a sharing context could be created. */ virtual bool supportsQpaContext() const; /** * The EGLDisplay used by the compositing scene. */ EGLDisplay sceneEglDisplay() const; void setSceneEglDisplay(EGLDisplay display); /** * The EGLContext used by the compositing scene. */ virtual EGLContext sceneEglContext() const { return m_context; } /** * Sets the @p context used by the compositing scene. */ void setSceneEglContext(EGLContext context) { m_context = context; } /** * The first (in case of multiple) EGLSurface used by the compositing scene. */ EGLSurface sceneEglSurface() const { return m_surface; } /** * Sets the first @p surface used by the compositing scene. * @see sceneEglSurface */ void setSceneEglSurface(EGLSurface surface) { m_surface = surface; } /** * The EglConfig used by the compositing scene. */ EGLConfig sceneEglConfig() const { return m_eglConfig; } /** * Sets the @p config used by the compositing scene. * @see sceneEglConfig */ void setSceneEglConfig(EGLConfig config) { m_eglConfig = config; } /** * Implementing subclasses should provide a size in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns an invalid size. */ virtual QSize screenSize() const; /** * Implementing subclasses should provide all geometries in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns one QRect positioned at 0/0 with screenSize() as size. */ virtual QVector screenGeometries() const; /** * Implementing subclasses should provide all geometries in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns a screen with a scale of 1. */ virtual QVector screenScales() const; /** * Implement this method to receive configuration change requests through KWayland's * OutputManagement interface. * * Base implementation warns that the current backend does not implement this * functionality. */ virtual void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config); /** * Whether the Platform requires compositing for rendering. * Default implementation returns @c true. If the implementing Platform allows to be used * without compositing (e.g. rendering is done by the windowing system), re-implement this method. */ virtual bool requiresCompositing() const; /** * Whether Compositing is possible in the Platform. * Returning @c false in this method makes only sense if requiresCompositing returns @c false. * * The default implementation returns @c true. * @see requiresCompositing */ virtual bool compositingPossible() const; /** * Returns a user facing text explaining why compositing is not possible in case * compositingPossible returns @c false. * * The default implementation returns an empty string. * @see compositingPossible */ virtual QString compositingNotPossibleReason() const; /** * Whether OpenGL compositing is broken. * The Platform can implement this method if it is able to detect whether OpenGL compositing * broke (e.g. triggered a crash in a previous run). * * Default implementation returns @c false. * @see createOpenGLSafePoint */ virtual bool openGLCompositingIsBroken() const; enum class OpenGLSafePoint { PreInit, PostInit, PreFrame, PostFrame, PostLastGuardedFrame }; /** * This method is invoked before and after creating the OpenGL rendering Scene. * An implementing Platform can use it to detect crashes triggered by the OpenGL implementation. * This can be used for openGLCompositingIsBroken. * * The default implementation does nothing. * @see openGLCompositingIsBroken. */ virtual void createOpenGLSafePoint(OpenGLSafePoint safePoint); /** * Starts an interactive window selection process. * * Once the user selected a window the @p callback is invoked with the selected Toplevel as * argument. In case the user cancels the interactive window selection or selecting a window is currently * not possible (e.g. screen locked) the @p callback is invoked with a @c nullptr argument. * * During the interactive window selection the cursor is turned into a crosshair cursor unless * @p cursorName is provided. The argument @p cursorName is a QByteArray instead of Qt::CursorShape * to support the "pirate" cursor for kill window which is not wrapped by Qt::CursorShape. * * The default implementation forwards to InputRedirection. * * @param callback The function to invoke once the interactive window selection ends * @param cursorName The optional name of the cursor shape to use, default is crosshair */ virtual void startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName = QByteArray()); /** * Starts an interactive position selection process. * * Once the user selected a position on the screen the @p callback is invoked with * the selected point as argument. In case the user cancels the interactive position selection * or selecting a position is currently not possible (e.g. screen locked) the @p callback * is invoked with a point at @c -1 as x and y argument. * * During the interactive window selection the cursor is turned into a crosshair cursor. * * The default implementation forwards to InputRedirection. * * @param callback The function to invoke once the interactive position selection ends */ virtual void startInteractivePositionSelection(std::function callback); /** * Platform specific preparation for an @p action which is used for KGlobalAccel. * * A platform might need to do preparation for an @p action before * it can be used with KGlobalAccel. * * Code using KGlobalAccel should invoke this method for the @p action * prior to setting up any shortcuts and connections. * * The default implementation does nothing. * * @param action The action which will be used with KGlobalAccel. * @since 5.10 */ virtual void setupActionForGlobalAccel(QAction *action); bool usesSoftwareCursor() const { return m_softWareCursor; } QImage softwareCursor() const; QPoint softwareCursorHotspot() const; void markCursorAsRendered(); /** * Returns a PlatformCursorImage. By default this is created by softwareCursor and * softwareCursorHotspot. An implementing subclass can use this to provide a better * suited PlatformCursorImage. * * @see softwareCursor * @see softwareCursorHotspot * @since 5.9 */ virtual PlatformCursorImage cursorImage() const; /** * The Platform cursor image should be hidden. * @see showCursor * @see doHideCursor * @see isCursorHidden * @since 5.9 */ void hideCursor(); /** * The Platform cursor image should be shown again. * @see hideCursor * @see doShowCursor * @see isCursorHidden * @since 5.9 */ void showCursor(); /** * Whether the cursor is currently hidden. * @see showCursor * @see hideCursor * @since 5.9 */ bool isCursorHidden() const { return m_hideCursorCounter > 0; } bool isReady() const { return m_ready; } void setInitialWindowSize(const QSize &size) { m_initialWindowSize = size; } void setDeviceIdentifier(const QByteArray &identifier) { m_deviceIdentifier = identifier; } bool supportsPointerWarping() const { return m_pointerWarping; } bool areOutputsEnabled() const { return m_outputsEnabled; } void setOutputsEnabled(bool enabled) { m_outputsEnabled = enabled; } int initialOutputCount() const { return m_initialOutputCount; } void setInitialOutputCount(int count) { m_initialOutputCount = count; } qreal initialOutputScale() const { return m_initialOutputScale; } void setInitialOutputScale(qreal scale) { m_initialOutputScale = scale; } /** * Creates the OverlayWindow required for X11 based compositors. * Default implementation returns @c nullptr. */ virtual OverlayWindow *createOverlayWindow(); /** * Allows a platform to update the X11 timestamp. * Mostly for the X11 standalone platform to interact with QX11Info. * * Default implementation does nothing. This means code relying on the X timestamp being up to date, * might not be working. E.g. synced X11 window resizing */ virtual void updateXTime(); /** * Creates the OutlineVisual for the given @p outline. * Default implementation creates an OutlineVisual suited for composited usage. */ virtual OutlineVisual *createOutline(Outline *outline); /** * Creates the Decoration::Renderer for the given @p client. * * The default implementation creates a Renderer suited for the Compositor, @c nullptr if there is no Compositor. */ virtual Decoration::Renderer *createDecorationRenderer(Decoration::DecoratedClientImpl *client); /** * Platform specific way to invert the screen. * Default implementation invokes the invert effect */ virtual void invertScreen(); /** * Default implementation creates an EffectsHandlerImp; */ virtual void createEffectsHandler(Compositor *compositor, Scene *scene); + /** * The CompositingTypes supported by the Platform. * The first item should be the most preferred one. * @since 5.11 */ virtual QVector supportedCompositors() const = 0; /** * Whether gamma control is supported by the backend. * @since 5.12 */ bool supportsGammaControl() const { return m_supportsGammaControl; } ColorCorrect::Manager *colorCorrectManager() { return m_colorCorrect; } // outputs with connections (org_kde_kwin_outputdevice) virtual Outputs outputs() const { return Outputs(); } // actively compositing outputs (wl_output) virtual Outputs enabledOutputs() const { return Outputs(); } /** * A string of information to include in kwin debug output * It should not be translated. * * The base implementation prints the name. * @since 5.12 */ virtual QString supportInformation() const; /** * The compositor plugin which got selected from @link{supportedCompositors}. * Prior to selecting a compositor this returns @c NoCompositing. * * This method allows the platforms to limit the offerings in @link{supportedCompositors} * in case they do not support runtime compositor switching */ CompositingType selectedCompositor() const { return m_selectedCompositor; } /** * Used by Compositor to set the used compositor. */ void setSelectedCompositor(CompositingType type) { m_selectedCompositor = type; } public Q_SLOTS: void pointerMotion(const QPointF &position, quint32 time); void pointerButtonPressed(quint32 button, quint32 time); void pointerButtonReleased(quint32 button, quint32 time); void pointerAxisHorizontal(qreal delta, quint32 time, qint32 discreteDelta = 0, InputRedirection::PointerAxisSource source = InputRedirection::PointerAxisSourceUnknown); void pointerAxisVertical(qreal delta, quint32 time, qint32 discreteDelta = 0, InputRedirection::PointerAxisSource source = InputRedirection::PointerAxisSourceUnknown); void keyboardKeyPressed(quint32 key, quint32 time); void keyboardKeyReleased(quint32 key, quint32 time); void keyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group); void keymapChange(int fd, uint32_t size); void touchDown(qint32 id, const QPointF &pos, quint32 time); void touchUp(qint32 id, quint32 time); void touchMotion(qint32 id, const QPointF &pos, quint32 time); void touchCancel(); void touchFrame(); void processSwipeGestureBegin(int fingerCount, quint32 time); void processSwipeGestureUpdate(const QSizeF &delta, quint32 time); void processSwipeGestureEnd(quint32 time); void processSwipeGestureCancelled(quint32 time); void processPinchGestureBegin(int fingerCount, quint32 time); void processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time); void processPinchGestureEnd(quint32 time); void processPinchGestureCancelled(quint32 time); Q_SIGNALS: void screensQueried(); void initFailed(); void cursorChanged(); void readyChanged(bool); /** * Emitted by backends using a one screen (nested window) approach and when the size of that changes. */ void screenSizeChanged(); protected: explicit Platform(QObject *parent = nullptr); void setSoftWareCursor(bool set); void repaint(const QRect &rect); void setReady(bool ready); QSize initialWindowSize() const { return m_initialWindowSize; } QByteArray deviceIdentifier() const { return m_deviceIdentifier; } void setSupportsPointerWarping(bool set) { m_pointerWarping = set; } void setSupportsGammaControl(bool set) { m_supportsGammaControl = set; } /** * Actual platform specific way to hide the cursor. * Sub-classes need to implement if they support hiding the cursor. * * This method is invoked by hideCursor if the cursor needs to be hidden. * The default implementation does nothing. * * @see doShowCursor * @see hideCursor * @see showCursor */ virtual void doHideCursor(); /** * Actual platform specific way to show the cursor. * Sub-classes need to implement if they support showing the cursor. * * This method is invoked by showCursor if the cursor needs to be shown again. * * @see doShowCursor * @see hideCursor * @see showCursor */ virtual void doShowCursor(); private: void triggerCursorRepaint(); bool m_softWareCursor = false; struct { QRect lastRenderedGeometry; } m_cursor; bool m_ready = false; QSize m_initialWindowSize; QByteArray m_deviceIdentifier; bool m_pointerWarping = false; bool m_outputsEnabled = true; int m_initialOutputCount = 1; qreal m_initialOutputScale = 1; EGLDisplay m_eglDisplay; EGLConfig m_eglConfig = nullptr; EGLContext m_context = EGL_NO_CONTEXT; EGLSurface m_surface = EGL_NO_SURFACE; int m_hideCursorCounter = 0; ColorCorrect::Manager *m_colorCorrect = nullptr; bool m_supportsGammaControl = false; CompositingType m_selectedCompositor = NoCompositing; }; } Q_DECLARE_INTERFACE(KWin::Platform, "org.kde.kwin.Platform") #endif diff --git a/plugins/platforms/drm/drm_backend.cpp b/plugins/platforms/drm/drm_backend.cpp index 8883e68e0..431461e63 100644 --- a/plugins/platforms/drm/drm_backend.cpp +++ b/plugins/platforms/drm/drm_backend.cpp @@ -1,843 +1,847 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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, see . *********************************************************************/ #include "drm_backend.h" #include "drm_output.h" #include "drm_object_connector.h" #include "drm_object_crtc.h" #include "drm_object_plane.h" #include "composite.h" #include "cursor.h" #include "logging.h" #include "logind.h" #include "main.h" #include "scene_qpainter_drm_backend.h" #include "screens_drm.h" #include "udev.h" #include "wayland_server.h" #if HAVE_GBM #include "egl_gbm_backend.h" #include #endif #if HAVE_EGL_STREAMS #include "egl_stream_backend.h" #endif // KWayland #include #include // KF5 #include #include #include #include // Qt #include #include #include // system #include #include // drm #include #include #include #ifndef DRM_CAP_CURSOR_WIDTH #define DRM_CAP_CURSOR_WIDTH 0x8 #endif #ifndef DRM_CAP_CURSOR_HEIGHT #define DRM_CAP_CURSOR_HEIGHT 0x9 #endif #define KWIN_DRM_EVENT_CONTEXT_VERSION 2 namespace KWin { DrmBackend::DrmBackend(QObject *parent) : Platform(parent) , m_udev(new Udev) , m_udevMonitor(m_udev->monitor()) , m_dpmsFilter() { #if HAVE_EGL_STREAMS if (qEnvironmentVariableIsSet("KWIN_DRM_USE_EGL_STREAMS")) { m_useEglStreams = true; } #endif setSupportsGammaControl(true); } DrmBackend::~DrmBackend() { - writeOutputsConfiguration(); #if HAVE_GBM if (m_gbmDevice) { gbm_device_destroy(m_gbmDevice); } #endif if (m_fd >= 0) { // wait for pageflips while (m_pageFlipsPending != 0) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } - // we need to first remove all outputs - qDeleteAll(m_outputs); - m_outputs.clear(); - m_enabledOutputs.clear(); qDeleteAll(m_planes); qDeleteAll(m_crtcs); qDeleteAll(m_connectors); close(m_fd); } } void DrmBackend::init() { LogindIntegration *logind = LogindIntegration::self(); auto takeControl = [logind, this]() { if (logind->hasSessionControl()) { openDrm(); } else { logind->takeControl(); connect(logind, &LogindIntegration::hasSessionControlChanged, this, &DrmBackend::openDrm); } }; if (logind->isConnected()) { takeControl(); } else { connect(logind, &LogindIntegration::connectedChanged, this, takeControl); } } +void DrmBackend::prepareShutdown() +{ + writeOutputsConfiguration(); + for (DrmOutput *output : m_outputs) { + output->teardown(); + } + Platform::prepareShutdown(); +} + Outputs DrmBackend::outputs() const { return m_outputs; } Outputs DrmBackend::enabledOutputs() const { return m_enabledOutputs; } void DrmBackend::outputWentOff() { if (!m_dpmsFilter.isNull()) { // already another output is off return; } m_dpmsFilter.reset(new DpmsInputEventFilter(this)); input()->prependInputEventFilter(m_dpmsFilter.data()); } void DrmBackend::turnOutputsOn() { m_dpmsFilter.reset(); for (auto it = m_enabledOutputs.constBegin(), end = m_enabledOutputs.constEnd(); it != end; it++) { (*it)->updateDpms(KWayland::Server::OutputInterface::DpmsMode::On); } } void DrmBackend::checkOutputsAreOn() { if (m_dpmsFilter.isNull()) { // already disabled, all outputs are on return; } for (auto it = m_enabledOutputs.constBegin(), end = m_enabledOutputs.constEnd(); it != end; it++) { if (!(*it)->isDpmsEnabled()) { // dpms still disabled, need to keep the filter return; } } // all outputs are on, disable the filter m_dpmsFilter.reset(); } void DrmBackend::activate(bool active) { if (active) { qCDebug(KWIN_DRM) << "Activating session."; reactivate(); } else { qCDebug(KWIN_DRM) << "Deactivating session."; deactivate(); } } void DrmBackend::reactivate() { if (m_active) { return; } m_active = true; if (!usesSoftwareCursor()) { const QPoint cp = Cursor::pos() - softwareCursorHotspot(); for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; // only relevant in atomic mode o->m_modesetRequested = true; o->pageFlipped(); // TODO: Do we really need this? o->m_crtc->blank(); o->showCursor(); o->moveCursor(cp); } } // restart compositor m_pageFlipsPending = 0; if (Compositor *compositor = Compositor::self()) { compositor->bufferSwapComplete(); compositor->addRepaintFull(); } } void DrmBackend::deactivate() { if (!m_active) { return; } // block compositor if (m_pageFlipsPending == 0 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } // hide cursor and disable for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; o->hideCursor(); } m_active = false; } void DrmBackend::pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { Q_UNUSED(fd) Q_UNUSED(frame) Q_UNUSED(sec) Q_UNUSED(usec) auto output = reinterpret_cast(data); output->pageFlipped(); output->m_backend->m_pageFlipsPending--; if (output->m_backend->m_pageFlipsPending == 0) { // TODO: improve, this currently means we wait for all page flips or all outputs. // It would be better to driver the repaint per output if (output->m_dpmsAtomicOffPending) { output->m_modesetRequested = true; output->dpmsAtomicOff(); } if (Compositor::self()) { Compositor::self()->bufferSwapComplete(); } } } void DrmBackend::openDrm() { connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, this, &DrmBackend::activate); UdevDevice::Ptr device = m_udev->primaryGpu(); if (!device) { qCWarning(KWIN_DRM) << "Did not find a GPU"; return; } m_devNode = device->devNode(); int fd = LogindIntegration::self()->takeDevice(m_devNode.constData()); if (fd < 0) { qCWarning(KWIN_DRM) << "failed to open drm device at" << m_devNode; return; } m_fd = fd; m_active = true; QSocketNotifier *notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { if (!LogindIntegration::self()->isActiveSession()) { return; } drmEventContext e; memset(&e, 0, sizeof e); e.version = KWIN_DRM_EVENT_CONTEXT_VERSION; e.page_flip_handler = pageFlipHandler; drmHandleEvent(m_fd, &e); } ); m_drmId = device->sysNum(); // trying to activate Atomic Mode Setting (this means also Universal Planes) if (!qEnvironmentVariableIsSet("KWIN_DRM_NO_AMS")) { if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0) { qCDebug(KWIN_DRM) << "Using Atomic Mode Setting."; m_atomicModeSetting = true; DrmScopedPointer planeResources(drmModeGetPlaneResources(m_fd)); if (!planeResources) { qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode"; m_atomicModeSetting = false; } if (m_atomicModeSetting) { qCDebug(KWIN_DRM) << "Number of planes:" << planeResources->count_planes; // create the plane objects for (unsigned int i = 0; i < planeResources->count_planes; ++i) { drmModePlane *kplane = drmModeGetPlane(m_fd, planeResources->planes[i]); DrmPlane *p = new DrmPlane(kplane->plane_id, m_fd); if (p->atomicInit()) { m_planes << p; if (p->type() == DrmPlane::TypeIndex::Overlay) { m_overlayPlanes << p; } } else { delete p; } } if (m_planes.isEmpty()) { qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode"; m_atomicModeSetting = false; } } } else { qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode."; } } DrmScopedPointer resources(drmModeGetResources(m_fd)); drmModeRes *res = resources.data(); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; return; } for (int i = 0; i < res->count_connectors; ++i) { m_connectors << new DrmConnector(res->connectors[i], m_fd); } for (int i = 0; i < res->count_crtcs; ++i) { m_crtcs << new DrmCrtc(res->crtcs[i], this, i); } if (m_atomicModeSetting) { auto tryAtomicInit = [] (DrmObject *obj) -> bool { if (obj->atomicInit()) { return false; } else { delete obj; return true; } }; m_connectors.erase(std::remove_if(m_connectors.begin(), m_connectors.end(), tryAtomicInit), m_connectors.end()); m_crtcs.erase(std::remove_if(m_crtcs.begin(), m_crtcs.end(), tryAtomicInit), m_crtcs.end()); } initCursor(); updateOutputs(); if (m_outputs.isEmpty()) { qCWarning(KWIN_DRM) << "No outputs, cannot render, will terminate now"; emit initFailed(); return; } // setup udevMonitor if (m_udevMonitor) { m_udevMonitor->filterSubsystemDevType("drm"); const int fd = m_udevMonitor->fd(); if (fd != -1) { QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { auto device = m_udevMonitor->getDevice(); if (!device) { return; } if (device->sysNum() != m_drmId) { return; } if (device->hasProperty("HOTPLUG", "1")) { qCDebug(KWIN_DRM) << "Received hot plug event for monitored drm device"; updateOutputs(); updateCursor(); } } ); m_udevMonitor->enable(); } } setReady(true); } void DrmBackend::updateOutputs() { if (m_fd < 0) { return; } DrmScopedPointer resources(drmModeGetResources(m_fd)); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; return; } QVector connectedOutputs; QVector pendingConnectors; // split up connected connectors in already or not yet assigned ones for (DrmConnector *con : qAsConst(m_connectors)) { if (!con->isConnected()) { continue; } if (DrmOutput *o = findOutput(con->id())) { connectedOutputs << o; } else { pendingConnectors << con; } } // check for outputs which got removed auto it = m_outputs.begin(); while (it != m_outputs.end()) { if (connectedOutputs.contains(*it)) { it++; continue; } DrmOutput *removed = *it; it = m_outputs.erase(it); m_enabledOutputs.removeOne(removed); emit outputRemoved(removed); removed->teardown(); } // now check new connections for (DrmConnector *con : qAsConst(pendingConnectors)) { DrmScopedPointer connector(drmModeGetConnector(m_fd, con->id())); if (!connector) { continue; } if (connector->count_modes == 0) { continue; } bool outputDone = false; QVector encoders = con->encoders(); for (auto encId : qAsConst(encoders)) { DrmScopedPointer encoder(drmModeGetEncoder(m_fd, encId)); if (!encoder) { continue; } for (DrmCrtc *crtc : qAsConst(m_crtcs)) { if (!(encoder->possible_crtcs & (1 << crtc->resIndex()))) { continue; } // check if crtc isn't used yet -- currently we don't allow multiple outputs on one crtc (cloned mode) auto it = std::find_if(connectedOutputs.constBegin(), connectedOutputs.constEnd(), [crtc] (DrmOutput *o) { return o->m_crtc == crtc; } ); if (it != connectedOutputs.constEnd()) { continue; } // we found a suitable encoder+crtc // TODO: we could avoid these lib drm calls if we store all struct data in DrmCrtc and DrmConnector in the beginning DrmScopedPointer modeCrtc(drmModeGetCrtc(m_fd, crtc->id())); if (!modeCrtc) { continue; } DrmOutput *output = new DrmOutput(this); con->setOutput(output); output->m_conn = con; crtc->setOutput(output); output->m_crtc = crtc; connect(output, &DrmOutput::dpmsChanged, this, &DrmBackend::outputDpmsChanged); if (modeCrtc->mode_valid) { output->m_mode = modeCrtc->mode; } else { output->m_mode = connector->modes[0]; } qCDebug(KWIN_DRM) << "For new output use mode " << output->m_mode.name; if (!output->init(connector.data())) { qCWarning(KWIN_DRM) << "Failed to create output for connector " << con->id(); delete output; continue; } if (!output->initCursor(m_cursorSize)) { setSoftWareCursor(true); } qCDebug(KWIN_DRM) << "Found new output with uuid" << output->uuid(); connectedOutputs << output; emit outputAdded(output); outputDone = true; break; } if (outputDone) { break; } } } std::sort(connectedOutputs.begin(), connectedOutputs.end(), [] (DrmOutput *a, DrmOutput *b) { return a->m_conn->id() < b->m_conn->id(); }); m_outputs = connectedOutputs; m_enabledOutputs = connectedOutputs; readOutputsConfiguration(); if (!m_outputs.isEmpty()) { emit screensQueried(); } } void DrmBackend::readOutputsConfiguration() { if (m_outputs.isEmpty()) { return; } const QByteArray uuid = generateOutputConfigurationUuid(); const auto outputGroup = kwinApp()->config()->group("DrmOutputs"); const auto configGroup = outputGroup.group(uuid); // default position goes from left to right QPoint pos(0, 0); for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { qCDebug(KWIN_DRM) << "Reading output configuration for [" << uuid << "] ["<< (*it)->uuid() << "]"; const auto outputConfig = configGroup.group((*it)->uuid()); (*it)->setGlobalPos(outputConfig.readEntry("Position", pos)); // TODO: add mode if (outputConfig.hasKey("Scale")) (*it)->setScale(outputConfig.readEntry("Scale", 1.0)); pos.setX(pos.x() + (*it)->geometry().width()); } } void DrmBackend::writeOutputsConfiguration() { if (m_outputs.isEmpty()) { return; } const QByteArray uuid = generateOutputConfigurationUuid(); auto configGroup = KSharedConfig::openConfig()->group("DrmOutputs").group(uuid); // default position goes from left to right for (auto it = m_outputs.cbegin(); it != m_outputs.cend(); ++it) { qCDebug(KWIN_DRM) << "Writing output configuration for [" << uuid << "] ["<< (*it)->uuid() << "]"; auto outputConfig = configGroup.group((*it)->uuid()); outputConfig.writeEntry("Scale", (*it)->scale()); } } QByteArray DrmBackend::generateOutputConfigurationUuid() const { auto it = m_outputs.constBegin(); if (m_outputs.size() == 1) { // special case: one output return (*it)->uuid(); } QCryptographicHash hash(QCryptographicHash::Md5); for (; it != m_outputs.constEnd(); ++it) { hash.addData((*it)->uuid()); } return hash.result().toHex().left(10); } void DrmBackend::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) { const auto changes = config->changes(); bool countChanged = false; //process all non-disabling changes for (auto it = changes.begin(); it != changes.end(); it++) { KWayland::Server::OutputChangeSet *changeset = it.value(); auto drmoutput = findOutput(it.key()->uuid()); if (drmoutput == nullptr) { qCWarning(KWIN_DRM) << "Could NOT find DrmOutput matching " << it.key()->uuid(); continue; } if (changeset->enabledChanged() && changeset->enabled() == KWayland::Server::OutputDeviceInterface::Enablement::Enabled) { drmoutput->setEnabled(true); m_enabledOutputs << drmoutput; emit outputAdded(drmoutput); countChanged = true; } drmoutput->setChanges(changeset); } //process any disable requests for (auto it = changes.begin(); it != changes.end(); it++) { KWayland::Server::OutputChangeSet *changeset = it.value(); if (changeset->enabledChanged() && changeset->enabled() == KWayland::Server::OutputDeviceInterface::Enablement::Disabled) { if (m_enabledOutputs.count() == 1) { qCWarning(KWIN_DRM) << "Not disabling final screen" << it.key()->uuid(); continue; } auto drmoutput = findOutput(it.key()->uuid()); if (drmoutput == nullptr) { qCWarning(KWIN_DRM) << "Could NOT find DrmOutput matching " << it.key()->uuid(); continue; } drmoutput->setEnabled(false); m_enabledOutputs.removeOne(drmoutput); emit outputRemoved(drmoutput); countChanged = true; } } if (countChanged) { emit screensQueried(); } else { emit screens()->changed(); } config->setApplied(); } DrmOutput *DrmBackend::findOutput(quint32 connector) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [connector] (DrmOutput *o) { return o->m_conn->id() == connector; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } DrmOutput *DrmBackend::findOutput(const QByteArray &uuid) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [uuid] (DrmOutput *o) { return o->m_uuid == uuid; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } bool DrmBackend::present(DrmBuffer *buffer, DrmOutput *output) { if (!buffer || buffer->bufferId() == 0) { if (m_deleteBufferAfterPageFlip) { delete buffer; } return false; } if (output->present(buffer)) { m_pageFlipsPending++; if (m_pageFlipsPending == 1 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } return true; } else if (m_deleteBufferAfterPageFlip) { delete buffer; } return false; } void DrmBackend::initCursor() { #if HAVE_EGL_STREAMS // Hardware cursors aren't currently supported with EGLStream backend, // possibly an NVIDIA driver bug if (m_useEglStreams) { setSoftWareCursor(true); } #endif m_cursorEnabled = waylandServer()->seat()->hasPointer(); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::hasPointerChanged, this, [this] { m_cursorEnabled = waylandServer()->seat()->hasPointer(); if (usesSoftwareCursor()) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { if (m_cursorEnabled) { if (!(*it)->showCursor()) { setSoftWareCursor(true); } } else { (*it)->hideCursor(); } } } ); uint64_t capability = 0; QSize cursorSize; if (drmGetCap(m_fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) { cursorSize.setWidth(capability); } else { cursorSize.setWidth(64); } if (drmGetCap(m_fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) { cursorSize.setHeight(capability); } else { cursorSize.setHeight(64); } m_cursorSize = cursorSize; // now we have screens and can set cursors, so start tracking connect(this, &DrmBackend::cursorChanged, this, &DrmBackend::updateCursor); connect(Cursor::self(), &Cursor::posChanged, this, &DrmBackend::moveCursor); } void DrmBackend::setCursor() { if (m_cursorEnabled) { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { if (!(*it)->showCursor()) { setSoftWareCursor(true); } } } markCursorAsRendered(); } void DrmBackend::updateCursor() { if (usesSoftwareCursor()) { return; } if (isCursorHidden()) { return; } const QImage &cursorImage = softwareCursor(); if (cursorImage.isNull()) { doHideCursor(); return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->updateCursor(); } setCursor(); moveCursor(); } void DrmBackend::doShowCursor() { updateCursor(); } void DrmBackend::doHideCursor() { if (!m_cursorEnabled || usesSoftwareCursor()) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->hideCursor(); } } void DrmBackend::moveCursor() { if (!m_cursorEnabled || isCursorHidden() || usesSoftwareCursor()) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->moveCursor(Cursor::pos()); } } Screens *DrmBackend::createScreens(QObject *parent) { return new DrmScreens(this, parent); } QPainterBackend *DrmBackend::createQPainterBackend() { m_deleteBufferAfterPageFlip = false; return new DrmQPainterBackend(this); } OpenGLBackend *DrmBackend::createOpenGLBackend() { #if HAVE_EGL_STREAMS if (m_useEglStreams) { m_deleteBufferAfterPageFlip = false; return new EglStreamBackend(this); } #endif #if HAVE_GBM m_deleteBufferAfterPageFlip = true; return new EglGbmBackend(this); #else return Platform::createOpenGLBackend(); #endif } DrmDumbBuffer *DrmBackend::createBuffer(const QSize &size) { DrmDumbBuffer *b = new DrmDumbBuffer(m_fd, size); return b; } #if HAVE_GBM DrmSurfaceBuffer *DrmBackend::createBuffer(const std::shared_ptr &surface) { DrmSurfaceBuffer *b = new DrmSurfaceBuffer(m_fd, surface); return b; } #endif void DrmBackend::outputDpmsChanged() { if (m_enabledOutputs.isEmpty()) { return; } bool enabled = false; for (auto it = m_enabledOutputs.constBegin(); it != m_enabledOutputs.constEnd(); ++it) { enabled = enabled || (*it)->isDpmsEnabled(); } setOutputsEnabled(enabled); } QVector DrmBackend::supportedCompositors() const { if (selectedCompositor() != NoCompositing) { return {selectedCompositor()}; } #if HAVE_GBM return QVector{OpenGLCompositing, QPainterCompositing}; #elif HAVE_EGL_STREAMS return m_useEglStreams ? QVector{OpenGLCompositing, QPainterCompositing} : QVector{QPainterCompositing}; #else return QVector{QPainterCompositing}; #endif } QString DrmBackend::supportInformation() const { QString supportInfo; QDebug s(&supportInfo); s.nospace(); s << "Name: " << "DRM" << endl; s << "Active: " << m_active << endl; s << "Atomic Mode Setting: " << m_atomicModeSetting << endl; #if HAVE_EGL_STREAMS s << "Using EGL Streams: " << m_useEglStreams << endl; #endif return supportInfo; } } diff --git a/plugins/platforms/drm/drm_backend.h b/plugins/platforms/drm/drm_backend.h index 3bdb4fda7..7d7dd1c13 100644 --- a/plugins/platforms/drm/drm_backend.h +++ b/plugins/platforms/drm/drm_backend.h @@ -1,212 +1,214 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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, see . *********************************************************************/ #ifndef KWIN_DRM_BACKEND_H #define KWIN_DRM_BACKEND_H #include "platform.h" #include "input.h" #include "drm_buffer.h" #if HAVE_GBM #include "drm_buffer_gbm.h" #endif #include "drm_inputeventfilter.h" #include "drm_pointer.h" #include #include #include #include #include #include #include struct gbm_bo; struct gbm_device; struct gbm_surface; namespace KWayland { namespace Server { class OutputInterface; class OutputDeviceInterface; class OutputChangeSet; class OutputManagementInterface; } } namespace KWin { class Udev; class UdevMonitor; class DrmOutput; class DrmPlane; class DrmCrtc; class DrmConnector; class GbmSurface; class KWIN_EXPORT DrmBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "drm.json") public: explicit DrmBackend(QObject *parent = nullptr); ~DrmBackend() override; void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) override; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend *createQPainterBackend() override; OpenGLBackend* createOpenGLBackend() override; void init() override; + void prepareShutdown() override; + DrmDumbBuffer *createBuffer(const QSize &size); #if HAVE_GBM DrmSurfaceBuffer *createBuffer(const std::shared_ptr &surface); #endif bool present(DrmBuffer *buffer, DrmOutput *output); int fd() const { return m_fd; } Outputs outputs() const override; Outputs enabledOutputs() const override; QVector drmOutputs() const { return m_outputs; } QVector drmEnabledOutputs() const { return m_enabledOutputs; } QVector planes() const { return m_planes; } QVector overlayPlanes() const { return m_overlayPlanes; } void outputWentOff(); void checkOutputsAreOn(); // QPainter reuses buffers bool deleteBufferAfterPageFlip() const { return m_deleteBufferAfterPageFlip; } // returns use of AMS, default is not/legacy bool atomicModeSetting() const { return m_atomicModeSetting; } void setGbmDevice(gbm_device *device) { m_gbmDevice = device; } gbm_device *gbmDevice() const { return m_gbmDevice; } QByteArray devNode() const { return m_devNode; } #if HAVE_EGL_STREAMS bool useEglStreams() const { return m_useEglStreams; } #endif QVector supportedCompositors() const override; QString supportInformation() const override; public Q_SLOTS: void turnOutputsOn(); Q_SIGNALS: /** * Emitted whenever an output is removed/disabled */ void outputRemoved(KWin::DrmOutput *output); /** * Emitted whenever an output is added/enabled */ void outputAdded(KWin::DrmOutput *output); protected: void doHideCursor() override; void doShowCursor() override; private: static void pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data); void openDrm(); void activate(bool active); void reactivate(); void deactivate(); void updateOutputs(); void setCursor(); void updateCursor(); void moveCursor(); void initCursor(); void outputDpmsChanged(); void readOutputsConfiguration(); void writeOutputsConfiguration(); QByteArray generateOutputConfigurationUuid() const; DrmOutput *findOutput(quint32 connector); DrmOutput *findOutput(const QByteArray &uuid); QScopedPointer m_udev; QScopedPointer m_udevMonitor; int m_fd = -1; int m_drmId = 0; // all crtcs QVector m_crtcs; // all connectors QVector m_connectors; // active output pipelines (planes + crtc + encoder + connector) QVector m_outputs; // active and enabled pipelines (above + wl_output) QVector m_enabledOutputs; bool m_deleteBufferAfterPageFlip; bool m_atomicModeSetting = false; bool m_cursorEnabled = false; QSize m_cursorSize; int m_pageFlipsPending = 0; bool m_active = false; QByteArray m_devNode; #if HAVE_EGL_STREAMS bool m_useEglStreams = false; #endif // all available planes: primarys, cursors and overlays QVector m_planes; QVector m_overlayPlanes; QScopedPointer m_dpmsFilter; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; gbm_device *m_gbmDevice = nullptr; }; } #endif diff --git a/plugins/platforms/drm/drm_output.cpp b/plugins/platforms/drm/drm_output.cpp index fb2610093..c9ebfe5d4 100644 --- a/plugins/platforms/drm/drm_output.cpp +++ b/plugins/platforms/drm/drm_output.cpp @@ -1,1071 +1,1072 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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, see . *********************************************************************/ #include "drm_output.h" #include "drm_backend.h" #include "drm_object_plane.h" #include "drm_object_crtc.h" #include "drm_object_connector.h" #include "composite.h" #include "logind.h" #include "logging.h" #include "main.h" #include "orientation_sensor.h" #include "screens_drm.h" #include "wayland_server.h" // KWayland #include // KF5 #include #include #include // Qt #include #include #include // c++ #include // drm #include #include #include namespace KWin { DrmOutput::DrmOutput(DrmBackend *backend) : AbstractWaylandOutput(backend) , m_backend(backend) { } DrmOutput::~DrmOutput() { Q_ASSERT(!m_pageFlipPending); - if (!m_deleted) { - teardown(); - } + teardown(); } void DrmOutput::teardown() { + if (m_deleted) { + return; + } m_deleted = true; hideCursor(); m_crtc->blank(); if (m_primaryPlane) { // TODO: when having multiple planes, also clean up these m_primaryPlane->setOutput(nullptr); if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); } m_primaryPlane->setCurrent(nullptr); } m_crtc->setOutput(nullptr); m_conn->setOutput(nullptr); delete m_cursor[0]; delete m_cursor[1]; if (!m_pageFlipPending) { deleteLater(); } //else will be deleted in the page flip handler //this is needed so that the pageflipcallback handle isn't deleted } void DrmOutput::releaseGbm() { if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } if (m_primaryPlane && m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } } bool DrmOutput::hideCursor() { return drmModeSetCursor(m_backend->fd(), m_crtc->id(), 0, 0, 0) == 0; } bool DrmOutput::showCursor(DrmDumbBuffer *c) { const QSize &s = c->size(); return drmModeSetCursor(m_backend->fd(), m_crtc->id(), c->handle(), s.width(), s.height()) == 0; } bool DrmOutput::showCursor() { const bool ret = showCursor(m_cursor[m_cursorIndex]); if (!ret) { return ret; } if (m_hasNewCursor) { m_cursorIndex = (m_cursorIndex + 1) % 2; m_hasNewCursor = false; } return ret; } int orientationToRotation(Qt::ScreenOrientation orientation) { switch (orientation) { case Qt::PrimaryOrientation: case Qt::LandscapeOrientation: return 0; case Qt::InvertedPortraitOrientation: return 90; case Qt::InvertedLandscapeOrientation: return 180; case Qt::PortraitOrientation: return 270; } Q_UNREACHABLE(); return 0; } QMatrix4x4 DrmOutput::matrixDisplay(const QSize &s) const { QMatrix4x4 matrix; const int angle = orientationToRotation(orientation()); if (angle) { const QSize center = s / 2; matrix.translate(center.width(), center.height()); matrix.rotate(angle, 0, 0, 1); matrix.translate(-center.width(), -center.height()); } matrix.scale(scale()); return matrix; } void DrmOutput::updateCursor() { QImage cursorImage = m_backend->softwareCursor(); if (cursorImage.isNull()) { return; } m_hasNewCursor = true; QImage *c = m_cursor[m_cursorIndex]->image(); c->fill(Qt::transparent); QPainter p; p.begin(c); p.setWorldTransform(matrixDisplay(QSize(cursorImage.width(), cursorImage.height())).toTransform()); p.drawImage(QPoint(0, 0), cursorImage); p.end(); } void DrmOutput::moveCursor(const QPoint &globalPos) { const QMatrix4x4 hotspotMatrix = matrixDisplay(m_backend->softwareCursor().size()); QPoint p = globalPos - AbstractWaylandOutput::globalPos(); switch (orientation()) { case Qt::PrimaryOrientation: case Qt::LandscapeOrientation: break; case Qt::PortraitOrientation: p = QPoint(p.y(), pixelSize().height() - p.x()); break; case Qt::InvertedPortraitOrientation: p = QPoint(pixelSize().width() - p.y(), p.x()); break; case Qt::InvertedLandscapeOrientation: p = QPoint(pixelSize().width() - p.x(), pixelSize().height() - p.y()); break; } p *= scale(); p -= hotspotMatrix.map(m_backend->softwareCursorHotspot()); drmModeMoveCursor(m_backend->fd(), m_crtc->id(), p.x(), p.y()); } static QHash s_connectorNames = { {DRM_MODE_CONNECTOR_Unknown, QByteArrayLiteral("Unknown")}, {DRM_MODE_CONNECTOR_VGA, QByteArrayLiteral("VGA")}, {DRM_MODE_CONNECTOR_DVII, QByteArrayLiteral("DVI-I")}, {DRM_MODE_CONNECTOR_DVID, QByteArrayLiteral("DVI-D")}, {DRM_MODE_CONNECTOR_DVIA, QByteArrayLiteral("DVI-A")}, {DRM_MODE_CONNECTOR_Composite, QByteArrayLiteral("Composite")}, {DRM_MODE_CONNECTOR_SVIDEO, QByteArrayLiteral("SVIDEO")}, {DRM_MODE_CONNECTOR_LVDS, QByteArrayLiteral("LVDS")}, {DRM_MODE_CONNECTOR_Component, QByteArrayLiteral("Component")}, {DRM_MODE_CONNECTOR_9PinDIN, QByteArrayLiteral("DIN")}, {DRM_MODE_CONNECTOR_DisplayPort, QByteArrayLiteral("DP")}, {DRM_MODE_CONNECTOR_HDMIA, QByteArrayLiteral("HDMI-A")}, {DRM_MODE_CONNECTOR_HDMIB, QByteArrayLiteral("HDMI-B")}, {DRM_MODE_CONNECTOR_TV, QByteArrayLiteral("TV")}, {DRM_MODE_CONNECTOR_eDP, QByteArrayLiteral("eDP")}, {DRM_MODE_CONNECTOR_VIRTUAL, QByteArrayLiteral("Virtual")}, {DRM_MODE_CONNECTOR_DSI, QByteArrayLiteral("DSI")}, #ifdef DRM_MODE_CONNECTOR_DPI {DRM_MODE_CONNECTOR_DPI, QByteArrayLiteral("DPI")}, #endif }; namespace { quint64 refreshRateForMode(_drmModeModeInfo *m) { // Calculate higher precision (mHz) refresh rate // logic based on Weston, see compositor-drm.c quint64 refreshRate = (m->clock * 1000000LL / m->htotal + m->vtotal / 2) / m->vtotal; if (m->flags & DRM_MODE_FLAG_INTERLACE) { refreshRate *= 2; } if (m->flags & DRM_MODE_FLAG_DBLSCAN) { refreshRate /= 2; } if (m->vscan > 1) { refreshRate /= m->vscan; } return refreshRate; } } bool DrmOutput::init(drmModeConnector *connector) { initEdid(connector); initDpms(connector); initUuid(); if (m_backend->atomicModeSetting()) { if (!initPrimaryPlane()) { return false; } } else if (!m_crtc->blank()) { return false; } setInternal(connector->connector_type == DRM_MODE_CONNECTOR_LVDS || connector->connector_type == DRM_MODE_CONNECTOR_eDP); setDpmsSupported(true); if (isInternal()) { connect(kwinApp(), &Application::screensCreated, this, [this] { connect(screens()->orientationSensor(), &OrientationSensor::orientationChanged, this, &DrmOutput::automaticRotation); } ); } initOutputDevice(connector); setEnabled(true); return true; } void DrmOutput::initUuid() { QCryptographicHash hash(QCryptographicHash::Md5); hash.addData(QByteArray::number(m_conn->id())); hash.addData(m_edid.eisaId()); hash.addData(m_edid.monitorName()); hash.addData(m_edid.serialNumber()); m_uuid = hash.result().toHex().left(10); } void DrmOutput::initOutputDevice(drmModeConnector *connector) { QString manufacturer; if (!m_edid.eisaId().isEmpty()) { manufacturer = QString::fromLatin1(m_edid.eisaId()); } QString connectorName = s_connectorNames.value(connector->connector_type, QByteArrayLiteral("Unknown")); QString modelName; if (!m_edid.monitorName().isEmpty()) { QString m = QString::fromLatin1(m_edid.monitorName()); if (!m_edid.serialNumber().isEmpty()) { m.append('/'); m.append(QString::fromLatin1(m_edid.serialNumber())); } modelName = m; } else if (!m_edid.serialNumber().isEmpty()) { modelName = QString::fromLatin1(m_edid.serialNumber()); } else { modelName = i18n("unknown"); } const QString model = connectorName + QStringLiteral("-") + QString::number(connector->connector_type_id) + QStringLiteral("-") + modelName; // read in mode information QVector modes; for (int i = 0; i < connector->count_modes; ++i) { // TODO: in AMS here we could read and store for later every mode's blob_id // would simplify isCurrentMode(..) and presentAtomically(..) in case of mode set auto *m = &connector->modes[i]; KWayland::Server::OutputDeviceInterface::ModeFlags deviceflags; if (isCurrentMode(m)) { deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Current; } if (m->type & DRM_MODE_TYPE_PREFERRED) { deviceflags |= KWayland::Server::OutputDeviceInterface::ModeFlag::Preferred; } KWayland::Server::OutputDeviceInterface::Mode mode; mode.id = i; mode.size = QSize(m->hdisplay, m->vdisplay); mode.flags = deviceflags; mode.refreshRate = refreshRateForMode(m); modes << mode; } QSize physicalSize = !m_edid.physicalSize().isEmpty() ? m_edid.physicalSize() : QSize(connector->mmWidth, connector->mmHeight); // the size might be completely borked. E.g. Samsung SyncMaster 2494HS reports 160x90 while in truth it's 520x292 // as this information is used to calculate DPI info, it's going to result in everything being huge const QByteArray unknown = QByteArrayLiteral("unknown"); KConfigGroup group = kwinApp()->config()->group("EdidOverwrite").group(m_edid.eisaId().isEmpty() ? unknown : m_edid.eisaId()) .group(m_edid.monitorName().isEmpty() ? unknown : m_edid.monitorName()) .group(m_edid.serialNumber().isEmpty() ? unknown : m_edid.serialNumber()); if (group.hasKey("PhysicalSize")) { const QSize overwriteSize = group.readEntry("PhysicalSize", physicalSize); qCWarning(KWIN_DRM) << "Overwriting monitor physical size for" << m_edid.eisaId() << "/" << m_edid.monitorName() << "/" << m_edid.serialNumber() << " from " << physicalSize << "to " << overwriteSize; physicalSize = overwriteSize; } initWaylandOutputDevice(model, manufacturer, m_uuid, physicalSize, modes); } bool DrmOutput::isCurrentMode(const drmModeModeInfo *mode) const { return mode->clock == m_mode.clock && mode->hdisplay == m_mode.hdisplay && mode->hsync_start == m_mode.hsync_start && mode->hsync_end == m_mode.hsync_end && mode->htotal == m_mode.htotal && mode->hskew == m_mode.hskew && mode->vdisplay == m_mode.vdisplay && mode->vsync_start == m_mode.vsync_start && mode->vsync_end == m_mode.vsync_end && mode->vtotal == m_mode.vtotal && mode->vscan == m_mode.vscan && mode->vrefresh == m_mode.vrefresh && mode->flags == m_mode.flags && mode->type == m_mode.type && qstrcmp(mode->name, m_mode.name) == 0; } void DrmOutput::initEdid(drmModeConnector *connector) { DrmScopedPointer edid; for (int i = 0; i < connector->count_props; ++i) { DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if ((property->flags & DRM_MODE_PROP_BLOB) && qstrcmp(property->name, "EDID") == 0) { edid.reset(drmModeGetPropertyBlob(m_backend->fd(), connector->prop_values[i])); } } if (!edid) { return; } m_edid = Edid(edid->data, edid->length); if (!m_edid.isValid()) { qCWarning(KWIN_DRM, "Couldn't parse EDID for connector with id %d", m_conn->id()); } } bool DrmOutput::initPrimaryPlane() { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Primary) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_primaryPlane) { // Output already has a primary plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_primaryPlane = p; qCDebug(KWIN_DRM) << "Initialized primary plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } qCCritical(KWIN_DRM) << "Failed to initialize primary plane."; return false; } bool DrmOutput::initCursorPlane() // TODO: Add call in init (but needs layer support in general first) { for (int i = 0; i < m_backend->planes().size(); ++i) { DrmPlane* p = m_backend->planes()[i]; if (!p) { continue; } if (p->type() != DrmPlane::TypeIndex::Cursor) { continue; } if (p->output()) { // Plane already has an output continue; } if (m_cursorPlane) { // Output already has a cursor plane continue; } if (!p->isCrtcSupported(m_crtc->resIndex())) { continue; } p->setOutput(this); m_cursorPlane = p; qCDebug(KWIN_DRM) << "Initialized cursor plane" << p->id() << "on CRTC" << m_crtc->id(); return true; } return false; } bool DrmOutput::initCursor(const QSize &cursorSize) { auto createCursor = [this, cursorSize] (int index) { m_cursor[index] = m_backend->createBuffer(cursorSize); if (!m_cursor[index]->map(QImage::Format_ARGB32_Premultiplied)) { return false; } return true; }; if (!createCursor(0) || !createCursor(1)) { return false; } return true; } void DrmOutput::initDpms(drmModeConnector *connector) { for (int i = 0; i < connector->count_props; ++i) { DrmScopedPointer property(drmModeGetProperty(m_backend->fd(), connector->props[i])); if (!property) { continue; } if (qstrcmp(property->name, "DPMS") == 0) { m_dpms.swap(property); break; } } } static DrmOutput::DpmsMode fromWaylandDpmsMode(KWayland::Server::OutputInterface::DpmsMode wlMode) { using namespace KWayland::Server; switch (wlMode) { case OutputInterface::DpmsMode::On: return DrmOutput::DpmsMode::On; case OutputInterface::DpmsMode::Standby: return DrmOutput::DpmsMode::Standby; case OutputInterface::DpmsMode::Suspend: return DrmOutput::DpmsMode::Suspend; case OutputInterface::DpmsMode::Off: return DrmOutput::DpmsMode::Off; default: Q_UNREACHABLE(); } } static KWayland::Server::OutputInterface::DpmsMode toWaylandDpmsMode(DrmOutput::DpmsMode mode) { using namespace KWayland::Server; switch (mode) { case DrmOutput::DpmsMode::On: return OutputInterface::DpmsMode::On; case DrmOutput::DpmsMode::Standby: return OutputInterface::DpmsMode::Standby; case DrmOutput::DpmsMode::Suspend: return OutputInterface::DpmsMode::Suspend; case DrmOutput::DpmsMode::Off: return OutputInterface::DpmsMode::Off; default: Q_UNREACHABLE(); } } void DrmOutput::updateDpms(KWayland::Server::OutputInterface::DpmsMode mode) { if (m_dpms.isNull()) { return; } const auto drmMode = fromWaylandDpmsMode(mode); if (drmMode == m_dpmsModePending) { qCDebug(KWIN_DRM) << "New DPMS mode equals old mode. DPMS unchanged."; return; } m_dpmsModePending = drmMode; if (m_backend->atomicModeSetting()) { m_modesetRequested = true; if (drmMode == DpmsMode::On) { if (m_pageFlipPending) { m_pageFlipPending = false; Compositor::self()->bufferSwapComplete(); } dpmsOnHandler(); } else { m_dpmsAtomicOffPending = true; if (!m_pageFlipPending) { dpmsAtomicOff(); } } } else { if (drmModeConnectorSetProperty(m_backend->fd(), m_conn->id(), m_dpms->prop_id, uint64_t(drmMode)) < 0) { m_dpmsModePending = m_dpmsMode; qCWarning(KWIN_DRM) << "Setting DPMS failed"; return; } if (drmMode == DpmsMode::On) { dpmsOnHandler(); } else { dpmsOffHandler(); } m_dpmsMode = m_dpmsModePending; } } void DrmOutput::dpmsOnHandler() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to On."; auto wlOutput = waylandOutput(); if (wlOutput) { wlOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending)); } emit dpmsChanged(); m_backend->checkOutputsAreOn(); if (!m_backend->atomicModeSetting()) { m_crtc->blank(); } if (Compositor *compositor = Compositor::self()) { compositor->addRepaintFull(); } } void DrmOutput::dpmsOffHandler() { qCDebug(KWIN_DRM) << "DPMS mode set for output" << m_crtc->id() << "to Off."; auto wlOutput = waylandOutput(); if (wlOutput) { wlOutput->setDpmsMode(toWaylandDpmsMode(m_dpmsModePending)); } emit dpmsChanged(); m_backend->outputWentOff(); } void DrmOutput::transform(KWayland::Server::OutputDeviceInterface::Transform transform) { waylandOutputDevice()->setTransform(transform); using KWayland::Server::OutputDeviceInterface; using KWayland::Server::OutputInterface; auto wlOutput = waylandOutput(); switch (transform) { case OutputDeviceInterface::Transform::Normal: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate0); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Normal); } setOrientation(Qt::PrimaryOrientation); break; case OutputDeviceInterface::Transform::Rotated90: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate90); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Rotated90); } setOrientation(Qt::PortraitOrientation); break; case OutputDeviceInterface::Transform::Rotated180: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate180); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Rotated180); } setOrientation(Qt::InvertedLandscapeOrientation); break; case OutputDeviceInterface::Transform::Rotated270: if (m_primaryPlane) { m_primaryPlane->setTransformation(DrmPlane::Transformation::Rotate270); } if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Rotated270); } setOrientation(Qt::InvertedPortraitOrientation); break; case OutputDeviceInterface::Transform::Flipped: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped); } break; case OutputDeviceInterface::Transform::Flipped90: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped90); } break; case OutputDeviceInterface::Transform::Flipped180: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped180); } break; case OutputDeviceInterface::Transform::Flipped270: // TODO: what is this exactly? if (wlOutput) { wlOutput->setTransform(OutputInterface::Transform::Flipped270); } break; } m_modesetRequested = true; // the cursor might need to get rotated updateCursor(); showCursor(); // TODO: are these calls not enough in updateMode already? setWaylandMode(); } void DrmOutput::updateMode(int modeIndex) { // get all modes on the connector DrmScopedPointer connector(drmModeGetConnector(m_backend->fd(), m_conn->id())); if (connector->count_modes <= modeIndex) { // TODO: error? return; } if (isCurrentMode(&connector->modes[modeIndex])) { // nothing to do return; } m_mode = connector->modes[modeIndex]; m_modesetRequested = true; setWaylandMode(); } void DrmOutput::setWaylandMode() { AbstractWaylandOutput::setWaylandMode(QSize(m_mode.hdisplay, m_mode.vdisplay), refreshRateForMode(&m_mode)); } void DrmOutput::pageFlipped() { m_pageFlipPending = false; if (m_deleted) { deleteLater(); return; } if (!m_crtc) { return; } // Egl based surface buffers get destroyed, QPainter based dumb buffers not // TODO: split up DrmOutput in two for dumb and egl/gbm surface buffer compatible subclasses completely? if (m_backend->deleteBufferAfterPageFlip()) { if (m_backend->atomicModeSetting()) { if (!m_primaryPlane->next()) { // on manual vt switch // TODO: when we later use overlay planes it might happen, that we have a page flip with only // damage on one of these, and therefore the primary plane has no next buffer // -> Then we don't want to return here! if (m_primaryPlane->current()) { m_primaryPlane->current()->releaseGbm(); } return; } for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBufferWithDelete(); } m_nextPlanesFlipList.clear(); } else { if (!m_crtc->next()) { // on manual vt switch if (DrmBuffer *b = m_crtc->current()) { b->releaseGbm(); } } m_crtc->flipBuffer(); } } else { if (m_backend->atomicModeSetting()){ for (DrmPlane *p : m_nextPlanesFlipList) { p->flipBuffer(); } m_nextPlanesFlipList.clear(); } else { m_crtc->flipBuffer(); } m_crtc->flipBuffer(); } } bool DrmOutput::present(DrmBuffer *buffer) { if (m_backend->atomicModeSetting()) { return presentAtomically(buffer); } else { return presentLegacy(buffer); } } bool DrmOutput::dpmsAtomicOff() { m_dpmsAtomicOffPending = false; // TODO: With multiple planes: deactivate all of them here delete m_primaryPlane->next(); m_primaryPlane->setNext(nullptr); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { qCDebug(KWIN_DRM) << "Atomic test commit to Dpms Off failed. Aborting."; return false; } if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit to Dpms Off failed. This should have never happened! Aborting."; return false; } m_nextPlanesFlipList.clear(); dpmsOffHandler(); return true; } bool DrmOutput::presentAtomically(DrmBuffer *buffer) { if (!LogindIntegration::self()->isActiveSession()) { qCWarning(KWIN_DRM) << "Logind session not active."; return false; } if (m_pageFlipPending) { qCWarning(KWIN_DRM) << "Page not yet flipped."; return false; } #if HAVE_EGL_STREAMS if (m_backend->useEglStreams() && !m_modesetRequested) { // EglStreamBackend queues normal page flips through EGL, // modesets are still performed through DRM-KMS m_pageFlipPending = true; return true; } #endif m_primaryPlane->setNext(buffer); m_nextPlanesFlipList << m_primaryPlane; if (!doAtomicCommit(AtomicCommitMode::Test)) { //TODO: When we use planes for layered rendering, fallback to renderer instead. Also for direct scanout? //TODO: Probably should undo setNext and reset the flip list qCDebug(KWIN_DRM) << "Atomic test commit failed. Aborting present."; // go back to previous state if (m_lastWorkingState.valid) { m_mode = m_lastWorkingState.mode; setOrientation(m_lastWorkingState.orientation); setGlobalPos(m_lastWorkingState.globalPos); if (m_primaryPlane) { m_primaryPlane->setTransformation(m_lastWorkingState.planeTransformations); } m_modesetRequested = true; // the cursor might need to get rotated updateCursor(); showCursor(); // TODO: forward to OutputInterface and OutputDeviceInterface setWaylandMode(); emit screens()->changed(); } return false; } const bool wasModeset = m_modesetRequested; if (!doAtomicCommit(AtomicCommitMode::Real)) { qCDebug(KWIN_DRM) << "Atomic commit failed. This should have never happened! Aborting present."; //TODO: Probably should undo setNext and reset the flip list return false; } if (wasModeset) { // store current mode set as new good state m_lastWorkingState.mode = m_mode; m_lastWorkingState.orientation = orientation(); m_lastWorkingState.globalPos = globalPos(); if (m_primaryPlane) { m_lastWorkingState.planeTransformations = m_primaryPlane->transformation(); } m_lastWorkingState.valid = true; } m_pageFlipPending = true; return true; } bool DrmOutput::presentLegacy(DrmBuffer *buffer) { if (m_crtc->next()) { return false; } if (!LogindIntegration::self()->isActiveSession()) { m_crtc->setNext(buffer); return false; } if (m_dpmsMode != DpmsMode::On) { return false; } // Do we need to set a new mode first? if (!m_crtc->current() || m_crtc->current()->needsModeChange(buffer)) { if (!setModeLegacy(buffer)) { return false; } } const bool ok = drmModePageFlip(m_backend->fd(), m_crtc->id(), buffer->bufferId(), DRM_MODE_PAGE_FLIP_EVENT, this) == 0; if (ok) { m_crtc->setNext(buffer); } else { qCWarning(KWIN_DRM) << "Page flip failed:" << strerror(errno); } return ok; } bool DrmOutput::setModeLegacy(DrmBuffer *buffer) { uint32_t connId = m_conn->id(); if (drmModeSetCrtc(m_backend->fd(), m_crtc->id(), buffer->bufferId(), 0, 0, &connId, 1, &m_mode) == 0) { return true; } else { qCWarning(KWIN_DRM) << "Mode setting failed"; return false; } } bool DrmOutput::doAtomicCommit(AtomicCommitMode mode) { drmModeAtomicReq *req = drmModeAtomicAlloc(); auto errorHandler = [this, mode, req] () { if (mode == AtomicCommitMode::Test) { // TODO: when we later test overlay planes, make sure we change only the right stuff back } if (req) { drmModeAtomicFree(req); } if (m_dpmsMode != m_dpmsModePending) { qCWarning(KWIN_DRM) << "Setting DPMS failed"; m_dpmsModePending = m_dpmsMode; if (m_dpmsMode != DpmsMode::On) { dpmsOffHandler(); } } // TODO: see above, rework later for overlay planes! for (DrmPlane *p : m_nextPlanesFlipList) { p->setNext(nullptr); } m_nextPlanesFlipList.clear(); }; if (!req) { qCWarning(KWIN_DRM) << "DRM: couldn't allocate atomic request"; errorHandler(); return false; } uint32_t flags = 0; // Do we need to set a new mode? if (m_modesetRequested) { if (m_dpmsModePending == DpmsMode::On) { if (drmModeCreatePropertyBlob(m_backend->fd(), &m_mode, sizeof(m_mode), &m_blobId) != 0) { qCWarning(KWIN_DRM) << "Failed to create property blob"; errorHandler(); return false; } } if (!atomicReqModesetPopulate(req, m_dpmsModePending == DpmsMode::On)){ qCWarning(KWIN_DRM) << "Failed to populate Atomic Modeset"; errorHandler(); return false; } flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; } if (mode == AtomicCommitMode::Real) { if (m_dpmsModePending == DpmsMode::On) { if (!(flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { // TODO: Evaluating this condition should only be necessary, as long as we expect older kernels than 4.10. flags |= DRM_MODE_ATOMIC_NONBLOCK; } #if HAVE_EGL_STREAMS if (!m_backend->useEglStreams()) // EglStreamBackend uses the NV_output_drm_flip_event EGL extension // to register the flip event through eglStreamConsumerAcquireAttribNV #endif flags |= DRM_MODE_PAGE_FLIP_EVENT; } } else { flags |= DRM_MODE_ATOMIC_TEST_ONLY; } bool ret = true; // TODO: Make sure when we use more than one plane at a time, that we go through this list in the right order. for (int i = m_nextPlanesFlipList.size() - 1; 0 <= i; i-- ) { DrmPlane *p = m_nextPlanesFlipList[i]; ret &= p->atomicPopulate(req); } if (!ret) { qCWarning(KWIN_DRM) << "Failed to populate atomic planes. Abort atomic commit!"; errorHandler(); return false; } if (drmModeAtomicCommit(m_backend->fd(), req, flags, this)) { qCWarning(KWIN_DRM) << "Atomic request failed to commit:" << strerror(errno); errorHandler(); return false; } if (mode == AtomicCommitMode::Real && (flags & DRM_MODE_ATOMIC_ALLOW_MODESET)) { qCDebug(KWIN_DRM) << "Atomic Modeset successful."; m_modesetRequested = false; m_dpmsMode = m_dpmsModePending; } drmModeAtomicFree(req); return true; } bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable) { if (enable) { m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), m_mode.hdisplay << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), m_mode.vdisplay << 16); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), m_mode.hdisplay); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), m_mode.vdisplay); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), m_crtc->id()); } else { if (m_backend->deleteBufferAfterPageFlip()) { delete m_primaryPlane->current(); delete m_primaryPlane->next(); } m_primaryPlane->setCurrent(nullptr); m_primaryPlane->setNext(nullptr); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), 0); m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), 0); } m_conn->setValue(int(DrmConnector::PropertyIndex::CrtcId), enable ? m_crtc->id() : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::ModeId), enable ? m_blobId : 0); m_crtc->setValue(int(DrmCrtc::PropertyIndex::Active), enable); bool ret = true; ret &= m_conn->atomicPopulate(req); ret &= m_crtc->atomicPopulate(req); return ret; } bool DrmOutput::supportsTransformations() const { if (!m_primaryPlane) { return false; } const auto transformations = m_primaryPlane->supportedTransformations(); return transformations.testFlag(DrmPlane::Transformation::Rotate90) || transformations.testFlag(DrmPlane::Transformation::Rotate180) || transformations.testFlag(DrmPlane::Transformation::Rotate270); } void DrmOutput::automaticRotation() { if (!m_primaryPlane) { return; } const auto supportedTransformations = m_primaryPlane->supportedTransformations(); const auto requestedTransformation = screens()->orientationSensor()->orientation(); using KWayland::Server::OutputDeviceInterface; OutputDeviceInterface::Transform newTransformation = OutputDeviceInterface::Transform::Normal; switch (requestedTransformation) { case OrientationSensor::Orientation::TopUp: newTransformation = OutputDeviceInterface::Transform::Normal; break; case OrientationSensor::Orientation::TopDown: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate180)) { return; } newTransformation = OutputDeviceInterface::Transform::Rotated180; break; case OrientationSensor::Orientation::LeftUp: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate90)) { return; } newTransformation = OutputDeviceInterface::Transform::Rotated90; break; case OrientationSensor::Orientation::RightUp: if (!supportedTransformations.testFlag(DrmPlane::Transformation::Rotate270)) { return; } newTransformation = OutputDeviceInterface::Transform::Rotated270; break; case OrientationSensor::Orientation::FaceUp: case OrientationSensor::Orientation::FaceDown: case OrientationSensor::Orientation::Undefined: // unsupported return; } transform(newTransformation); emit screens()->changed(); } int DrmOutput::gammaRampSize() const { return m_crtc->gammaRampSize(); } bool DrmOutput::setGammaRamp(const GammaRamp &gamma) { return m_crtc->setGammaRamp(gamma); } }