diff --git a/src/waylandintegration.cpp b/src/waylandintegration.cpp index a1005f8..cac8701 100644 --- a/src/waylandintegration.cpp +++ b/src/waylandintegration.cpp @@ -1,546 +1,546 @@ /* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Jan Grulich */ #include "waylandintegration.h" #include "waylandintegration_p.h" #include "screencaststream.h" #include #include #include #include #include #include #include // KWayland #include #include #include #include #include // system #include #include Q_LOGGING_CATEGORY(XdgDesktopPortalKdeWaylandIntegration, "xdp-kde-wayland-integration") Q_GLOBAL_STATIC(WaylandIntegration::WaylandIntegrationPrivate, globalWaylandIntegration) void WaylandIntegration::init() { globalWaylandIntegration->initDrm(); globalWaylandIntegration->initWayland(); } bool WaylandIntegration::isEGLInitialized() { return globalWaylandIntegration->isEGLInitialized(); } bool WaylandIntegration::startStreaming(const WaylandOutput &output) { return globalWaylandIntegration->startStreaming(output); } void WaylandIntegration::stopStreaming() { globalWaylandIntegration->stopStreaming(); } QMap WaylandIntegration::screens() { return globalWaylandIntegration->screens(); } QVariant WaylandIntegration::streams() { return globalWaylandIntegration->streams(); } WaylandIntegration::WaylandIntegration * WaylandIntegration::waylandIntegration() { return globalWaylandIntegration; } static const char * formatGLError(GLenum err) { switch(err) { case GL_NO_ERROR: return "GL_NO_ERROR"; case GL_INVALID_ENUM: return "GL_INVALID_ENUM"; case GL_INVALID_VALUE: return "GL_INVALID_VALUE"; case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION"; case GL_STACK_OVERFLOW: return "GL_STACK_OVERFLOW"; case GL_STACK_UNDERFLOW: return "GL_STACK_UNDERFLOW"; case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY"; default: return (QLatin1String("0x") + QString::number(err, 16)).toLocal8Bit().constData(); } } // Thank you kscreen void WaylandIntegration::WaylandOutput::setOutputType(const QString &type) { const auto embedded = { QLatin1String("LVDS"), QLatin1String("IDP"), QLatin1String("EDP"), QLatin1String("LCD") }; for (const QLatin1String &pre : embedded) { if (type.toUpper().startsWith(pre)) { m_outputType = OutputType::Laptop; return; } } if (type.contains(QLatin1String("VGA")) || type.contains(QLatin1String("DVI")) || type.contains(QLatin1String("HDMI")) || type.contains(QLatin1String("Panel")) || type.contains(QLatin1String("DisplayPort")) || type.startsWith(QLatin1String("DP")) || type.contains(QLatin1String("unknown"))) { m_outputType = OutputType::Monitor; } else if (type.contains(QLatin1String("TV"))) { m_outputType = OutputType::Television; } else { m_outputType = OutputType::Monitor; } } const QDBusArgument &operator >> (const QDBusArgument &arg, WaylandIntegration::WaylandIntegrationPrivate::Stream &stream) { arg.beginStructure(); arg >> stream.nodeId; arg.beginMap(); while (!arg.atEnd()) { QString key; QVariant map; arg.beginMapEntry(); arg >> key >> map; arg.endMapEntry(); stream.map.insert(key, map); } arg.endMap(); arg.endStructure(); return arg; } const QDBusArgument &operator << (QDBusArgument &arg, const WaylandIntegration::WaylandIntegrationPrivate::Stream &stream) { arg.beginStructure(); arg << stream.nodeId; arg << stream.map; arg.endStructure(); return arg; } Q_DECLARE_METATYPE(WaylandIntegration::WaylandIntegrationPrivate::Stream) Q_DECLARE_METATYPE(WaylandIntegration::WaylandIntegrationPrivate::Streams) WaylandIntegration::WaylandIntegrationPrivate::WaylandIntegrationPrivate() : WaylandIntegration() , m_eglInitialized(false) , m_registryInitialized(false) , m_connection(nullptr) , m_queue(nullptr) , m_registry(nullptr) , m_remoteAccessManager(nullptr) { qDBusRegisterMetaType(); qDBusRegisterMetaType(); } WaylandIntegration::WaylandIntegrationPrivate::~WaylandIntegrationPrivate() { if (m_remoteAccessManager) { m_remoteAccessManager->destroy(); } - if (m_drmFd) { + if (m_gbmDevice) { gbm_device_destroy(m_gbmDevice); } } bool WaylandIntegration::WaylandIntegrationPrivate::isEGLInitialized() const { return m_eglInitialized; } void WaylandIntegration::WaylandIntegrationPrivate::bindOutput(int outputName, int outputVersion) { KWayland::Client::Output *output = new KWayland::Client::Output(this); output->setup(m_registry->bindOutput(outputName, outputVersion)); m_bindOutputs << output; } bool WaylandIntegration::WaylandIntegrationPrivate::startStreaming(const WaylandOutput &output) { m_stream = new ScreenCastStream(output.resolution()); m_stream->init(); connect(m_stream, &ScreenCastStream::startStreaming, this, [this, output] { m_streamingEnabled = true; bindOutput(output.waylandOutputName(), output.waylandOutputVersion()); }); connect(m_stream, &ScreenCastStream::stopStreaming, this, &WaylandIntegrationPrivate::stopStreaming); bool streamReady = false; QEventLoop loop; connect(m_stream, &ScreenCastStream::streamReady, this, [&loop, &streamReady] { loop.quit(); streamReady = true; }); // HACK wait for stream to be ready QTimer::singleShot(3000, &loop, &QEventLoop::quit); loop.exec(); disconnect(m_stream, &ScreenCastStream::streamReady, this, nullptr); if (!streamReady) { if (m_stream) { delete m_stream; m_stream = nullptr; } return false; } // TODO support multiple outputs if (m_registry->hasInterface(KWayland::Client::Registry::Interface::RemoteAccessManager)) { KWayland::Client::Registry::AnnouncedInterface interface = m_registry->interface(KWayland::Client::Registry::Interface::RemoteAccessManager); if (!interface.name && !interface.version) { qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to start streaming: remote access manager interface is not initialized yet"; return false; } m_remoteAccessManager = m_registry->createRemoteAccessManager(interface.name, interface.version); connect(m_remoteAccessManager, &KWayland::Client::RemoteAccessManager::bufferReady, this, [this] (const void *output, const KWayland::Client::RemoteBuffer * rbuf) { Q_UNUSED(output); connect(rbuf, &KWayland::Client::RemoteBuffer::parametersObtained, this, [this, rbuf] { processBuffer(rbuf); }); }); m_output = output.waylandOutputName(); return true; } if (m_stream) { delete m_stream; m_stream = nullptr; } qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to start streaming: no remote access manager interface"; return false; } void WaylandIntegration::WaylandIntegrationPrivate::stopStreaming() { if (m_streamingEnabled) { m_streamingEnabled = false; // First unbound outputs and destroy remote access manager so we no longer receive buffers if (m_remoteAccessManager) { m_remoteAccessManager->release(); m_remoteAccessManager->destroy(); } qDeleteAll(m_bindOutputs); m_bindOutputs.clear(); if (m_stream) { delete m_stream; m_stream = nullptr; } } } QMap WaylandIntegration::WaylandIntegrationPrivate::screens() { return m_outputMap; } QVariant WaylandIntegration::WaylandIntegrationPrivate::streams() { Stream stream; stream.nodeId = m_stream->nodeId(); stream.map = QVariantMap({{QLatin1String("size"), m_outputMap.value(m_output).resolution()}}); return QVariant::fromValue({stream}); } void WaylandIntegration::WaylandIntegrationPrivate::initDrm() { m_drmFd = open("/dev/dri/renderD128", O_RDWR); if (m_drmFd == -1) { qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Cannot open render node: " << strerror(errno); return; } m_gbmDevice = gbm_create_device(m_drmFd); if (!m_gbmDevice) { qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Cannot create GBM device: " << strerror(errno); } initEGL(); } void WaylandIntegration::WaylandIntegrationPrivate::initEGL() { // Get the list of client extensions const char* clientExtensionsCString = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); const QByteArray clientExtensionsString = QByteArray::fromRawData(clientExtensionsCString, qstrlen(clientExtensionsCString)); if (clientExtensionsString.isEmpty()) { // If eglQueryString() returned NULL, the implementation doesn't support // EGL_EXT_client_extensions. Expect an EGL_BAD_DISPLAY error. qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "No client extensions defined! " << formatGLError(eglGetError()); return; } m_egl.extensions = clientExtensionsString.split(' '); // Use eglGetPlatformDisplayEXT() to get the display pointer // if the implementation supports it. if (!m_egl.extensions.contains(QByteArrayLiteral("EGL_EXT_platform_base")) || !m_egl.extensions.contains(QByteArrayLiteral("EGL_MESA_platform_gbm"))) { qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "One of required EGL extensions is missing"; return; } m_egl.display = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, m_gbmDevice, nullptr); if (m_egl.display == EGL_NO_DISPLAY) { qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Error during obtaining EGL display: " << formatGLError(eglGetError()); return; } EGLint major, minor; if (eglInitialize(m_egl.display, &major, &minor) == EGL_FALSE) { qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Error during eglInitialize: " << formatGLError(eglGetError()); return; } if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "bind OpenGL API failed"; return; } m_egl.context = eglCreateContext(m_egl.display, nullptr, EGL_NO_CONTEXT, nullptr); if (m_egl.context == EGL_NO_CONTEXT) { qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Couldn't create EGL context: " << formatGLError(eglGetError()); return; } qCDebug(XdgDesktopPortalKdeWaylandIntegration) << "Egl initialization succeeded"; qCDebug(XdgDesktopPortalKdeWaylandIntegration) << QStringLiteral("EGL version: %1.%2").arg(major).arg(minor); m_eglInitialized = true; } void WaylandIntegration::WaylandIntegrationPrivate::initWayland() { m_thread = new QThread(this); m_connection = new KWayland::Client::ConnectionThread; connect(m_connection, &KWayland::Client::ConnectionThread::connected, this, &WaylandIntegrationPrivate::setupRegistry, Qt::QueuedConnection); connect(m_connection, &KWayland::Client::ConnectionThread::connectionDied, this, [this] { if (m_queue) { delete m_queue; m_queue = nullptr; } m_connection->deleteLater(); m_connection = nullptr; if (m_thread) { m_thread->quit(); if (!m_thread->wait(3000)) { m_thread->terminate(); m_thread->wait(); } delete m_thread; m_thread = nullptr; } }); connect(m_connection, &KWayland::Client::ConnectionThread::failed, this, [this] { m_thread->quit(); m_thread->wait(); }); m_thread->start(); m_connection->moveToThread(m_thread); m_connection->initConnection(); } void WaylandIntegration::WaylandIntegrationPrivate::addOutput(quint32 name, quint32 version) { KWayland::Client::Output *output = new KWayland::Client::Output(this); output->setup(m_registry->bindOutput(name, version)); connect(output, &KWayland::Client::Output::changed, this, [this, name, version, output] () { qCDebug(XdgDesktopPortalKdeWaylandIntegration) << "Adding output:"; qCDebug(XdgDesktopPortalKdeWaylandIntegration) << " manufacturer: " << output->manufacturer(); qCDebug(XdgDesktopPortalKdeWaylandIntegration) << " model: " << output->model(); qCDebug(XdgDesktopPortalKdeWaylandIntegration) << " resolution: " << output->pixelSize(); WaylandOutput portalOutput; portalOutput.setManufacturer(output->manufacturer()); portalOutput.setModel(output->model()); portalOutput.setOutputType(output->model()); portalOutput.setResolution(output->pixelSize()); portalOutput.setWaylandOutputName(name); portalOutput.setWaylandOutputVersion(version); m_outputMap.insert(name, portalOutput); delete output; }); } void WaylandIntegration::WaylandIntegrationPrivate::removeOutput(quint32 name) { WaylandOutput output = m_outputMap.take(name); qCDebug(XdgDesktopPortalKdeWaylandIntegration) << "Removing output:"; qCDebug(XdgDesktopPortalKdeWaylandIntegration) << " manufacturer: " << output.manufacturer(); qCDebug(XdgDesktopPortalKdeWaylandIntegration) << " model: " << output.model(); } void WaylandIntegration::WaylandIntegrationPrivate::processBuffer(const KWayland::Client::RemoteBuffer* rbuf) { QScopedPointer guard(rbuf); auto gbmHandle = rbuf->fd(); auto width = rbuf->width(); auto height = rbuf->height(); auto stride = rbuf->stride(); auto format = rbuf->format(); qCDebug(XdgDesktopPortalKdeWaylandIntegration) << QStringLiteral("Incoming GBM fd %1, %2x%3, stride %4, fourcc 0x%5").arg(gbmHandle).arg(width).arg(height).arg(stride).arg(QString::number(format, 16)); if (!m_streamingEnabled) { qCDebug(XdgDesktopPortalKdeWaylandIntegration) << "Streaming is disabled"; close(gbmHandle); return; } if (m_lastFrameTime.isValid() && m_lastFrameTime.msecsTo(QDateTime::currentDateTime()) < (1000 / m_stream->framerate())) { close(gbmHandle); return; } if (!gbm_device_is_format_supported(m_gbmDevice, format, GBM_BO_USE_SCANOUT)) { qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: GBM format is not supported by device!"; close(gbmHandle); return; } // import GBM buffer that was passed from KWin gbm_import_fd_data importInfo = {gbmHandle, width, height, stride, format}; gbm_bo *imported = gbm_bo_import(m_gbmDevice, GBM_BO_IMPORT_FD, &importInfo, GBM_BO_USE_SCANOUT); if (!imported) { qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: Cannot import passed GBM fd - " << strerror(errno); close(gbmHandle); return; } // bind context to render thread eglMakeCurrent(m_egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, m_egl.context); // create EGL image from imported BO EGLImageKHR image = eglCreateImageKHR(m_egl.display, nullptr, EGL_NATIVE_PIXMAP_KHR, imported, nullptr); // We can already close gbm handle gbm_bo_destroy(imported); close(gbmHandle); if (image == EGL_NO_IMAGE_KHR) { qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: Error creating EGLImageKHR - " << formatGLError(glGetError()); return; } // create GL 2D texture for framebuffer GLuint texture; glGenTextures(1, &texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, texture); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); // bind framebuffer to copy pixels from GLuint framebuffer; glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: glCheckFramebufferStatus failed - " << formatGLError(glGetError()); glDeleteTextures(1, &texture); glDeleteFramebuffers(1, &framebuffer); eglDestroyImageKHR(m_egl.display, image); return; } auto capture = new QImage(QSize(width, height), QImage::Format_RGBA8888); glViewport(0, 0, width, height); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, capture->bits()); if (m_stream->recordFrame(capture->bits())) { m_lastFrameTime = QDateTime::currentDateTime(); } glDeleteTextures(1, &texture); glDeleteFramebuffers(1, &framebuffer); eglDestroyImageKHR(m_egl.display, image); delete capture; } void WaylandIntegration::WaylandIntegrationPrivate::setupRegistry() { m_queue = new KWayland::Client::EventQueue(this); m_queue->setup(m_connection); m_registry = new KWayland::Client::Registry(this); connect(m_registry, &KWayland::Client::Registry::outputAnnounced, this, &WaylandIntegrationPrivate::addOutput); connect(m_registry, &KWayland::Client::Registry::outputRemoved, this, &WaylandIntegrationPrivate::removeOutput); connect(m_registry, &KWayland::Client::Registry::interfacesAnnounced, this, [this] { m_registryInitialized = true; qCDebug(XdgDesktopPortalKdeWaylandIntegration) << "Registry initialized"; }); m_registry->create(m_connection); m_registry->setEventQueue(m_queue); m_registry->setup(); }