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