diff --git a/src/screencaststream.cpp b/src/screencaststream.cpp index 0c0504d..08ea93a 100644 --- a/src/screencaststream.cpp +++ b/src/screencaststream.cpp @@ -1,549 +1,600 @@ /* * Copyright © 2018-2020 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 "screencaststream.h" -#include "waylandintegration.h" #include #include #include #include #include Q_LOGGING_CATEGORY(XdgDesktopPortalKdeScreenCastStream, "xdp-kde-screencast-stream") class PwFraction { public: int num; int denom; }; // Stolen from mutter #define MAX_TERMS 30 #define MIN_DIVISOR 1.0e-10 #define MAX_ERROR 1.0e-20 #define PROP_RANGE(min, max) 2, (min), (max) #define BITS_PER_PIXEL 4 static int greatestCommonDivisor(int a, int b) { while (b != 0) { int temp = a; a = b; b = temp % b; } return qAbs(a); } static PwFraction pipewireFractionFromDouble(double src) { double V, F; /* double being converted */ int N, D; /* will contain the result */ int A; /* current term in continued fraction */ int64_t N1, D1; /* numerator, denominator of last approx */ int64_t N2, D2; /* numerator, denominator of previous approx */ int i; int gcd; bool negative = false; /* initialize fraction being converted */ F = src; if (F < 0.0) { F = -F; negative = true; } V = F; /* initialize fractions with 1/0, 0/1 */ N1 = 1; D1 = 0; N2 = 0; D2 = 1; N = 1; D = 1; for (i = 0; i < MAX_TERMS; ++i) { /* get next term */ A = (int) F; /* no floor() needed, F is always >= 0 */ /* get new divisor */ F = F - A; /* calculate new fraction in temp */ N2 = N1 * A + N2; D2 = D1 * A + D2; /* guard against overflow */ if (N2 > INT_MAX || D2 > INT_MAX) break; N = N2; D = D2; /* save last two fractions */ N2 = N1; D2 = D1; N1 = N; D1 = D; /* quit if dividing by zero or close enough to target */ if (F < MIN_DIVISOR || fabs (V - ((double) N) / D) < MAX_ERROR) break; /* Take reciprocal */ F = 1 / F; } /* fix for overflow */ if (D == 0) { N = INT_MAX; D = 1; } /* fix for negative */ if (negative) N = -N; /* simplify */ gcd = greatestCommonDivisor(N, D); if (gcd) { N /= gcd; D /= gcd; } PwFraction fraction; fraction.num = N; fraction.denom = D; return fraction; } #if PW_CHECK_VERSION(0, 2, 90) void ScreenCastStream::onCoreError(void *data, uint32_t id, int seq, int res, const char *message) { Q_UNUSED(seq) ScreenCastStream *pw = static_cast(data); qCWarning(XdgDesktopPortalKdeScreenCastStream) << "PipeWire remote error: " << message; if (id == PW_ID_CORE) { if (res == -EPIPE) { Q_EMIT pw->stopStreaming(); } } } #else void ScreenCastStream::onStateChanged(void *data, pw_remote_state old, pw_remote_state state, const char *error) { Q_UNUSED(old); ScreenCastStream *pw = static_cast(data); switch (state) { case PW_REMOTE_STATE_ERROR: // TODO notify error qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Remote error: " << error; break; case PW_REMOTE_STATE_CONNECTED: // TODO notify error qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Remote state: " << pw_remote_state_as_string(state); pw->pwStream = pw->createStream(); if (!pw->pwStream) { Q_EMIT pw->stopStreaming(); } break; default: qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Remote state: " << pw_remote_state_as_string(state); break; } } #endif void ScreenCastStream::onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message) { Q_UNUSED(old) ScreenCastStream *pw = static_cast(data); #if PW_CHECK_VERSION(0, 2, 90) switch (state) { case PW_STREAM_STATE_ERROR: qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Stream error: " << error_message; break; case PW_STREAM_STATE_PAUSED: qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); if (pw->nodeId() == 0 && pw->pwStream) { pw->pwNodeId = pw_stream_get_node_id(pw->pwStream); Q_EMIT pw->streamReady(pw->nodeId()); } if (WaylandIntegration::isStreamingEnabled()) { Q_EMIT pw->stopStreaming(); } break; case PW_STREAM_STATE_STREAMING: qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); Q_EMIT pw->startStreaming(); break; case PW_STREAM_STATE_UNCONNECTED: case PW_STREAM_STATE_CONNECTING: qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); break; } #else switch (state) { case PW_STREAM_STATE_ERROR: qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Stream error: " << error_message; break; case PW_STREAM_STATE_CONFIGURE: qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); Q_EMIT pw->streamReady((uint)pw_stream_get_node_id(pw->pwStream)); break; case PW_STREAM_STATE_UNCONNECTED: case PW_STREAM_STATE_CONNECTING: case PW_STREAM_STATE_READY: case PW_STREAM_STATE_PAUSED: qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); Q_EMIT pw->stopStreaming(); break; case PW_STREAM_STATE_STREAMING: qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); Q_EMIT pw->startStreaming(); break; } #endif } #if PW_CHECK_VERSION(0, 2, 90) void ScreenCastStream::onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format) #else void ScreenCastStream::onStreamFormatChanged(void *data, const struct spa_pod *format) #endif { qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream format changed"; ScreenCastStream *pw = static_cast(data); uint8_t paramsBuffer[1024]; int32_t width, height, stride, size; struct spa_pod_builder pod_builder; const struct spa_pod *params[1]; #if PW_CHECK_VERSION(0, 2, 90) if (!format || id != SPA_PARAM_Format) { #else if (!format) { pw_stream_finish_format(pw->pwStream, 0, nullptr, 0); #endif return; } #if PW_CHECK_VERSION(0, 2, 90) spa_format_video_raw_parse (format, &pw->videoFormat); #else spa_format_video_raw_parse (format, &pw->videoFormat, &pw->pwType->format_video); #endif width = pw->videoFormat.size.width; height =pw->videoFormat.size.height; stride = SPA_ROUND_UP_N (width * BITS_PER_PIXEL, 4); size = height * stride; pod_builder = SPA_POD_BUILDER_INIT (paramsBuffer, sizeof (paramsBuffer)); #if PW_CHECK_VERSION(0, 2, 90) params[0] = (spa_pod*) spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 2, 16), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int (1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride), SPA_PARAM_BUFFERS_align, SPA_POD_Int(16)); pw_stream_update_params(pw->pwStream, params, 1); #else params[0] = (spa_pod*) spa_pod_builder_object(&pod_builder, pw->pwCoreType->param.idBuffers, pw->pwCoreType->param_buffers.Buffers, ":", pw->pwCoreType->param_buffers.size, "i", size, ":", pw->pwCoreType->param_buffers.stride, "i", stride, ":", pw->pwCoreType->param_buffers.buffers, "iru", 16, PROP_RANGE (2, 16), ":", pw->pwCoreType->param_buffers.align, "i", 16); pw_stream_finish_format (pw->pwStream, 0, params, 1); #endif } ScreenCastStream::ScreenCastStream(const QSize &resolution, QObject *parent) : QObject(parent) , resolution(resolution) { #if PW_CHECK_VERSION(0, 2, 90) pwCoreEvents.version = PW_VERSION_CORE_EVENTS; pwCoreEvents.error = &onCoreError; pwStreamEvents.version = PW_VERSION_STREAM_EVENTS; pwStreamEvents.state_changed = &onStreamStateChanged; pwStreamEvents.param_changed = &onStreamParamChanged; #else // initialize event handlers, remote end and stream-related pwRemoteEvents.version = PW_VERSION_REMOTE_EVENTS; pwRemoteEvents.state_changed = &onStateChanged; pwStreamEvents.version = PW_VERSION_STREAM_EVENTS; pwStreamEvents.state_changed = &onStreamStateChanged; pwStreamEvents.format_changed = &onStreamFormatChanged; #endif } ScreenCastStream::~ScreenCastStream() { if (pwMainLoop) { pw_thread_loop_stop(pwMainLoop); } #if !PW_CHECK_VERSION(0, 2, 90) if (pwType) { delete pwType; } #endif if (pwStream) { pw_stream_destroy(pwStream); } #if !PW_CHECK_VERSION(0, 2, 90) if (pwRemote) { pw_remote_destroy(pwRemote); } #endif #if PW_CHECK_VERSION(0, 2, 90) if (pwCore) { pw_core_disconnect(pwCore); } if (pwContext) { pw_context_destroy(pwContext); } #else if (pwCore) { pw_core_destroy(pwCore); } #endif if (pwMainLoop) { pw_thread_loop_destroy(pwMainLoop); } #if !PW_CHECK_VERSION(0, 2, 90) if (pwLoop) { pw_loop_leave(pwLoop); pw_loop_destroy(pwLoop); } #endif } void ScreenCastStream::init() { pw_init(nullptr, nullptr); #if PW_CHECK_VERSION(0, 2, 90) pwMainLoop = pw_thread_loop_new("pipewire-main-loop", nullptr); pwContext = pw_context_new(pw_thread_loop_get_loop(pwMainLoop), nullptr, 0); if (!pwContext) { qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to create PipeWire context"; return; } pwCore = pw_context_connect(pwContext, nullptr, 0); if (!pwCore) { qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to connect PipeWire context"; return; } pw_core_add_listener(pwCore, &coreListener, &pwCoreEvents, this); pwStream = createStream(); if (!pwStream) { qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to create PipeWire stream"; return; } #else pwLoop = pw_loop_new(nullptr); pwMainLoop = pw_thread_loop_new(pwLoop, "pipewire-main-loop"); pwCore = pw_core_new(pwLoop, nullptr); pwCoreType = pw_core_get_type(pwCore); pwRemote = pw_remote_new(pwCore, nullptr, 0); initializePwTypes(); pw_remote_add_listener(pwRemote, &remoteListener, &pwRemoteEvents, this); pw_remote_connect(pwRemote); #endif if (pw_thread_loop_start(pwMainLoop) < 0) { qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to start main PipeWire loop"; return; } } uint ScreenCastStream::framerate() { if (pwStream) { return videoFormat.max_framerate.num / videoFormat.max_framerate.denom; } return 0; } uint ScreenCastStream::nodeId() { #if PW_CHECK_VERSION(0, 2, 90) return pwNodeId; #else if (pwStream) { return (uint)pw_stream_get_node_id(pwStream); } return 0; #endif } pw_stream *ScreenCastStream::createStream() { #if !PW_CHECK_VERSION(0, 2, 90) if (pw_remote_get_state(pwRemote, nullptr) != PW_REMOTE_STATE_CONNECTED) { qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Cannot create pipewire stream"; return nullptr; } #endif uint8_t buffer[1024]; spa_pod_builder podBuilder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const float frameRate = 25; spa_fraction maxFramerate; spa_fraction minFramerate; const spa_pod *params[1]; #if PW_CHECK_VERSION(0, 2, 90) auto stream = pw_stream_new(pwCore, "kwin-screen-cast", nullptr); #else auto stream = pw_stream_new(pwRemote, "kwin-screen-cast", nullptr); #endif PwFraction fraction = pipewireFractionFromDouble(frameRate); minFramerate = SPA_FRACTION(1, 1); maxFramerate = SPA_FRACTION((uint32_t)fraction.num, (uint32_t)fraction.denom); spa_rectangle minResolution = SPA_RECTANGLE(1, 1); spa_rectangle maxResolution = SPA_RECTANGLE((uint32_t)resolution.width(), (uint32_t)resolution.height()); spa_fraction paramFraction = SPA_FRACTION(0, 1); #if PW_CHECK_VERSION(0, 2, 90) params[0] = (spa_pod*)spa_pod_builder_add_object(&podBuilder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGBx), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&maxResolution, &minResolution, &maxResolution), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(¶mFraction), SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&maxFramerate, &minFramerate, &maxFramerate)); #else params[0] = (spa_pod*)spa_pod_builder_object(&podBuilder, pwCoreType->param.idEnumFormat, pwCoreType->spa_format, "I", pwType->media_type.video, "I", pwType->media_subtype.raw, ":", pwType->format_video.format, "I", pwType->video_format.RGBx, ":", pwType->format_video.size, "Rru", &maxResolution, SPA_POD_PROP_MIN_MAX(&minResolution, &maxResolution), ":", pwType->format_video.framerate, "F", ¶mFraction, ":", pwType->format_video.max_framerate, "Fru", &maxFramerate, PROP_RANGE (&minFramerate, &maxFramerate)); #endif pw_stream_add_listener(stream, &streamListener, &pwStreamEvents, this); auto flags = static_cast(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_MAP_BUFFERS); #if PW_CHECK_VERSION(0, 2, 90) if (pw_stream_connect(stream, PW_DIRECTION_OUTPUT, SPA_ID_INVALID, flags, params, 1) != 0) { #else if (pw_stream_connect(stream, PW_DIRECTION_OUTPUT, nullptr, flags, params, 1) != 0) { #endif qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Could not connect to stream"; return nullptr; } return stream; } -bool ScreenCastStream::recordFrame(uint8_t *screenData) +bool ScreenCastStream::recordFrame(gbm_bo *bo, quint32 width, quint32 height, quint32 stride) { struct pw_buffer *buffer; struct spa_buffer *spa_buffer; uint8_t *data = nullptr; if (!(buffer = pw_stream_dequeue_buffer(pwStream))) { + qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: couldn't obtain PipeWire buffer"; return false; } spa_buffer = buffer->buffer; if (!(data = (uint8_t *) spa_buffer->datas[0].data)) { + qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: invalid buffer data"; return false; } - memcpy(data, screenData, BITS_PER_PIXEL * videoFormat.size.height * videoFormat.size.width * sizeof(uint8_t)); + const quint32 destStride = SPA_ROUND_UP_N(videoFormat.size.width * BITS_PER_PIXEL, 4); + const quint32 destSize = BITS_PER_PIXEL * width * height * sizeof(uint8_t); + const quint32 srcSize = spa_buffer->datas[0].maxsize; + + if (destSize != srcSize || stride != destStride) { + qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: different stride"; + return false; + } + + // bind context to render thread + eglMakeCurrent(WaylandIntegration::egl().display, EGL_NO_SURFACE, EGL_NO_SURFACE, WaylandIntegration::egl().context); + + // create EGL image from imported BO + EGLImageKHR image = eglCreateImageKHR(WaylandIntegration::egl().display, nullptr, EGL_NATIVE_PIXMAP_KHR, bo, nullptr); + + if (image == EGL_NO_IMAGE_KHR) { + qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: Error creating EGLImageKHR - " << WaylandIntegration::formatGLError(glGetError()); + return false; + } + + // 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(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: glCheckFramebufferStatus failed - " << WaylandIntegration::formatGLError(glGetError()); + glDeleteTextures(1, &texture); + glDeleteFramebuffers(1, &framebuffer); + eglDestroyImageKHR(WaylandIntegration::egl().display, image); + return false; + } + + glViewport(0, 0, width, height); + + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + + glDeleteTextures(1, &texture); + glDeleteFramebuffers(1, &framebuffer); + eglDestroyImageKHR(WaylandIntegration::egl().display, image); spa_buffer->datas[0].chunk->offset = 0; spa_buffer->datas[0].chunk->size = spa_buffer->datas[0].maxsize; - spa_buffer->datas[0].chunk->stride = SPA_ROUND_UP_N (videoFormat.size.width * BITS_PER_PIXEL, 4); + spa_buffer->datas[0].chunk->stride = destStride; pw_stream_queue_buffer(pwStream, buffer); return true; } void ScreenCastStream::removeStream() { // FIXME destroying streams seems to be crashing, Mutter also doesn't remove them, maybe Pipewire does this automatically // pw_stream_destroy(pwStream); // pwStream = nullptr; pw_stream_disconnect(pwStream); } #if !PW_CHECK_VERSION(0, 2, 90) void ScreenCastStream::initializePwTypes() { // raw C-like ScreenCastStream type map auto map = pwCoreType->map; pwType = new PwType(); spa_type_media_type_map(map, &pwType->media_type); spa_type_media_subtype_map(map, &pwType->media_subtype); spa_type_format_video_map (map, &pwType->format_video); spa_type_video_format_map (map, &pwType->video_format); } #endif diff --git a/src/screencaststream.h b/src/screencaststream.h index 0bf4d82..fdf1dd8 100644 --- a/src/screencaststream.h +++ b/src/screencaststream.h @@ -1,123 +1,125 @@ /* * Copyright © 2018-2020 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 */ #ifndef SCREEN_CAST_STREAM_H #define SCREEN_CAST_STREAM_H +#include "waylandintegration.h" + #include #include #include #include #include #include #if PW_CHECK_VERSION(0, 2, 90) #include #endif #if !PW_CHECK_VERSION(0, 2, 90) class PwType { public: spa_type_media_type media_type; spa_type_media_subtype media_subtype; spa_type_format_video format_video; spa_type_video_format video_format; }; #endif class ScreenCastStream : public QObject { Q_OBJECT public: explicit ScreenCastStream(const QSize &resolution, QObject *parent = nullptr); ~ScreenCastStream(); #if PW_CHECK_VERSION(0, 2, 90) static void onCoreError(void *data, uint32_t id, int seq, int res, const char *message); static void onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format); #else static void onStateChanged(void *data, pw_remote_state old, pw_remote_state state, const char *error); static void onStreamFormatChanged(void *data, const struct spa_pod *format); #endif static void onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message); static void onStreamProcess(void *data); // Public void init(); uint framerate(); uint nodeId(); // Public because we need access from static functions pw_stream *createStream(); void removeStream(); public Q_SLOTS: - bool recordFrame(uint8_t *screenData); + bool recordFrame(gbm_bo *bo, quint32 width, quint32 height, quint32 stride); Q_SIGNALS: void streamReady(uint nodeId); void startStreaming(); void stopStreaming(); #if !PW_CHECK_VERSION(0, 2, 90) private: void initializePwTypes(); #endif public: #if PW_CHECK_VERSION(0, 2, 90) struct pw_context *pwContext = nullptr; struct pw_core *pwCore = nullptr; struct pw_stream *pwStream = nullptr; struct pw_thread_loop *pwMainLoop = nullptr; spa_hook coreListener; spa_hook streamListener; // event handlers pw_core_events pwCoreEvents = {}; pw_stream_events pwStreamEvents = {}; uint32_t pwNodeId = 0; #else pw_core *pwCore = nullptr; pw_loop *pwLoop = nullptr; pw_thread_loop *pwMainLoop = nullptr; pw_stream *pwStream = nullptr; pw_remote *pwRemote = nullptr; pw_type *pwCoreType = nullptr; PwType *pwType = nullptr; spa_hook remoteListener; spa_hook streamListener; // event handlers pw_remote_events pwRemoteEvents = {}; pw_stream_events pwStreamEvents = {}; #endif QSize resolution; spa_video_info_raw videoFormat; }; #endif // SCREEN_CAST_STREAM_H diff --git a/src/waylandintegration.cpp b/src/waylandintegration.cpp index 5a1f750..27334cb 100644 --- a/src/waylandintegration.cpp +++ b/src/waylandintegration.cpp @@ -1,669 +1,633 @@ /* * 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 #include // KWayland #include #include #include #include #include #include // system #include #include Q_LOGGING_CATEGORY(XdgDesktopPortalKdeWaylandIntegration, "xdp-kde-wayland-integration") Q_GLOBAL_STATIC(WaylandIntegration::WaylandIntegrationPrivate, globalWaylandIntegration) void WaylandIntegration::authenticate() { globalWaylandIntegration->authenticate(); } void WaylandIntegration::init() { globalWaylandIntegration->initDrm(); globalWaylandIntegration->initWayland(); } bool WaylandIntegration::isEGLInitialized() { return globalWaylandIntegration->isEGLInitialized(); } bool WaylandIntegration::isStreamingEnabled() { return globalWaylandIntegration->isStreamingEnabled(); } void WaylandIntegration::startStreamingInput() { globalWaylandIntegration->startStreamingInput(); } bool WaylandIntegration::startStreaming(quint32 outputName) { return globalWaylandIntegration->startStreaming(outputName); } void WaylandIntegration::stopStreaming() { globalWaylandIntegration->stopStreaming(); } void WaylandIntegration::requestPointerButtonPress(quint32 linuxButton) { globalWaylandIntegration->requestPointerButtonPress(linuxButton); } void WaylandIntegration::requestPointerButtonRelease(quint32 linuxButton) { globalWaylandIntegration->requestPointerButtonRelease(linuxButton); } void WaylandIntegration::requestPointerMotion(const QSizeF &delta) { globalWaylandIntegration->requestPointerMotion(delta); } void WaylandIntegration::requestPointerMotionAbsolute(const QPointF &pos) { globalWaylandIntegration->requestPointerMotionAbsolute(pos); } void WaylandIntegration::requestPointerAxisDiscrete(Qt::Orientation axis, qreal delta) { globalWaylandIntegration->requestPointerAxisDiscrete(axis, delta); } void WaylandIntegration::requestKeyboardKeycode(int keycode, bool state) { globalWaylandIntegration->requestKeyboardKeycode(keycode, state); } +WaylandIntegration::EGLStruct WaylandIntegration::egl() +{ + return globalWaylandIntegration->egl(); +} + QMap WaylandIntegration::screens() { return globalWaylandIntegration->screens(); } QVariant WaylandIntegration::streams() { return globalWaylandIntegration->streams(); } WaylandIntegration::WaylandIntegration * WaylandIntegration::waylandIntegration() { return globalWaylandIntegration; } -static const char * formatGLError(GLenum err) +const char * WaylandIntegration::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_waylandAuthenticationRequested(false) , m_connection(nullptr) , m_queue(nullptr) , m_fakeInput(nullptr) , m_registry(nullptr) , m_remoteAccessManager(nullptr) { qDBusRegisterMetaType(); qDBusRegisterMetaType(); } WaylandIntegration::WaylandIntegrationPrivate::~WaylandIntegrationPrivate() { if (m_remoteAccessManager) { m_remoteAccessManager->destroy(); } if (m_gbmDevice) { gbm_device_destroy(m_gbmDevice); } } bool WaylandIntegration::WaylandIntegrationPrivate::isEGLInitialized() const { return m_eglInitialized; } bool WaylandIntegration::WaylandIntegrationPrivate::isStreamingEnabled() const { return m_streamingEnabled; } 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; } void WaylandIntegration::WaylandIntegrationPrivate::startStreamingInput() { m_streamInput = true; } bool WaylandIntegration::WaylandIntegrationPrivate::startStreaming(quint32 outputName) { WaylandOutput output = m_outputMap.value(outputName); m_streamedScreenPosition = output.globalPosition(); m_stream = new ScreenCastStream(output.resolution()); m_stream->init(); connect(m_stream, &ScreenCastStream::startStreaming, this, [this, output] { m_streamingEnabled = true; startStreamingInput(); 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() { m_streamInput = false; 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; } } } void WaylandIntegration::WaylandIntegrationPrivate::requestPointerButtonPress(quint32 linuxButton) { if (m_streamInput && m_fakeInput) { m_fakeInput->requestPointerButtonPress(linuxButton); } } void WaylandIntegration::WaylandIntegrationPrivate::requestPointerButtonRelease(quint32 linuxButton) { if (m_streamInput && m_fakeInput) { m_fakeInput->requestPointerButtonRelease(linuxButton); } } void WaylandIntegration::WaylandIntegrationPrivate::requestPointerMotion(const QSizeF &delta) { if (m_streamInput && m_fakeInput) { m_fakeInput->requestPointerMove(delta); } } void WaylandIntegration::WaylandIntegrationPrivate::requestPointerMotionAbsolute(const QPointF &pos) { if (m_streamInput && m_fakeInput) { m_fakeInput->requestPointerMoveAbsolute(pos + m_streamedScreenPosition); } } void WaylandIntegration::WaylandIntegrationPrivate::requestPointerAxisDiscrete(Qt::Orientation axis, qreal delta) { if (m_streamInput && m_fakeInput) { m_fakeInput->requestPointerAxis(axis, delta); } } void WaylandIntegration::WaylandIntegrationPrivate::requestKeyboardKeycode(int keycode, bool state) { if (m_streamInput && m_fakeInput) { if (state) { m_fakeInput->requestKeyboardKeyPress(keycode); } else { m_fakeInput->requestKeyboardKeyRelease(keycode); } } } +WaylandIntegration::EGLStruct WaylandIntegration::WaylandIntegrationPrivate::egl() +{ + return m_egl; +} + 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::authenticate() { if (!m_waylandAuthenticationRequested) { m_fakeInput->authenticate(i18n("xdg-desktop-portals-kde"), i18n("Remote desktop")); m_waylandAuthenticationRequested = true; } } 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.setGlobalPosition(output->globalPosition()); 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())) { + if (m_stream->recordFrame(imported, width, height, stride)) { m_lastFrameTime = QDateTime::currentDateTime(); } - glDeleteTextures(1, &texture); - glDeleteFramebuffers(1, &framebuffer); - eglDestroyImageKHR(m_egl.display, image); - - delete capture; + gbm_bo_destroy(imported); + close(gbmHandle); } 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::fakeInputAnnounced, this, [this] (quint32 name, quint32 version) { m_fakeInput = m_registry->createFakeInput(name, version, 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(); } diff --git a/src/waylandintegration.h b/src/waylandintegration.h index 6ae2760..4ed6800 100644 --- a/src/waylandintegration.h +++ b/src/waylandintegration.h @@ -1,108 +1,120 @@ /* * 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 */ #ifndef XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_H #define XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_H #include #include #include #include +#include + +#include +#include namespace WaylandIntegration { +struct EGLStruct { + QList extensions; + EGLDisplay display = EGL_NO_DISPLAY; + EGLContext context = EGL_NO_CONTEXT; +}; + class WaylandOutput { public: enum OutputType { Laptop, Monitor, Television }; void setManufacturer(const QString &manufacturer) { m_manufacturer = manufacturer; } QString manufacturer() const { return m_manufacturer; } void setModel(const QString &model) { m_model = model; } QString model() const { return m_model; } void setGlobalPosition(const QPoint &pos) { m_globalPosition = pos; } QPoint globalPosition() const { return m_globalPosition; } void setResolution(const QSize &resolution) { m_resolution = resolution; } QSize resolution() const { return m_resolution; } void setOutputType(const QString &type); OutputType outputType() const { return m_outputType; } void setWaylandOutputName(int outputName) { m_waylandOutputName = outputName; } int waylandOutputName() const { return m_waylandOutputName; } void setWaylandOutputVersion(int outputVersion) { m_waylandOutputVersion = outputVersion; } int waylandOutputVersion() const { return m_waylandOutputVersion; } private: QString m_manufacturer; QString m_model; QPoint m_globalPosition; QSize m_resolution; OutputType m_outputType; // Needed for later output binding int m_waylandOutputName; int m_waylandOutputVersion; }; class WaylandIntegration : public QObject { Q_OBJECT Q_SIGNALS: void newBuffer(uint8_t *screenData); }; + const char * formatGLError(GLenum err); void authenticate(); void init(); bool isEGLInitialized(); bool isStreamingEnabled(); void startStreamingInput(); bool startStreaming(quint32 outputName); void stopStreaming(); void requestPointerButtonPress(quint32 linuxButton); void requestPointerButtonRelease(quint32 linuxButton); void requestPointerMotion(const QSizeF &delta); void requestPointerMotionAbsolute(const QPointF &pos); void requestPointerAxisDiscrete(Qt::Orientation axis, qreal delta); void requestKeyboardKeycode(int keycode, bool state); + EGLStruct egl(); + QMap screens(); QVariant streams(); WaylandIntegration *waylandIntegration(); - } #endif // XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_H diff --git a/src/waylandintegration_p.h b/src/waylandintegration_p.h index f4a25bc..9cb598c 100644 --- a/src/waylandintegration_p.h +++ b/src/waylandintegration_p.h @@ -1,130 +1,123 @@ /* * 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 */ #ifndef XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_P_H #define XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_P_H #include "waylandintegration.h" #include #include -#include - -#include -#include - class ScreenCastStream; namespace KWayland { namespace Client { class ConnectionThread; class EventQueue; class FakeInput; class Registry; class RemoteAccessManager; class RemoteBuffer; class Output; } } namespace WaylandIntegration { class WaylandIntegrationPrivate : public WaylandIntegration::WaylandIntegration { Q_OBJECT public: typedef struct { uint nodeId; QVariantMap map; } Stream; typedef QList Streams; WaylandIntegrationPrivate(); ~WaylandIntegrationPrivate(); void authenticate(); void initDrm(); void initEGL(); void initWayland(); bool isEGLInitialized() const; bool isStreamingEnabled() const; void bindOutput(int outputName, int outputVersion); void startStreamingInput(); bool startStreaming(quint32 outputName); void stopStreaming(); void requestPointerButtonPress(quint32 linuxButton); void requestPointerButtonRelease(quint32 linuxButton); void requestPointerMotion(const QSizeF &delta); void requestPointerMotionAbsolute(const QPointF &pos); void requestPointerAxisDiscrete(Qt::Orientation axis, qreal delta); void requestKeyboardKeycode(int keycode, bool state); + EGLStruct egl(); QMap screens(); QVariant streams(); protected Q_SLOTS: void addOutput(quint32 name, quint32 version); void removeOutput(quint32 name); void processBuffer(const KWayland::Client::RemoteBuffer *rbuf); void setupRegistry(); private: bool m_eglInitialized; bool m_streamingEnabled; bool m_streamInput = false; bool m_registryInitialized; bool m_waylandAuthenticationRequested; quint32 m_output; QDateTime m_lastFrameTime; ScreenCastStream *m_stream; QThread *m_thread; QPoint m_streamedScreenPosition; QMap m_outputMap; QList m_bindOutputs; KWayland::Client::ConnectionThread *m_connection; KWayland::Client::EventQueue *m_queue; KWayland::Client::FakeInput *m_fakeInput; KWayland::Client::Registry *m_registry; KWayland::Client::RemoteAccessManager *m_remoteAccessManager; qint32 m_drmFd = 0; // for GBM buffer mmap gbm_device *m_gbmDevice = nullptr; // for passed GBM buffer retrieval - struct { - QList extensions; - EGLDisplay display = EGL_NO_DISPLAY; - EGLContext context = EGL_NO_CONTEXT; - } m_egl; + + EGLStruct m_egl; }; } #endif // XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_P_H