!67 wayland预览图底层库

Merge pull request !67 from 卜萧庆/waylandpreview
This commit is contained in:
卜萧庆 2023-05-05 06:42:53 +00:00 committed by Gitee
commit d58f7cf6ce
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
15 changed files with 1079 additions and 63 deletions

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.14)
cmake_minimum_required(VERSION 3.16)
project(taskmanager LANGUAGES CXX)
@ -16,37 +16,41 @@ find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Quick Widgets DBus X11Ext
#find kde modules
find_package(Qt5Xdg REQUIRED)
find_package(KF5Wayland)
find_package(KF5I18n)
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
include(FindQtWaylandScanner)
include(ECMQtDeclareLoggingCategory)
#find other modules
find_package(PkgConfig REQUIRED)
pkg_check_modules(GLIB2 REQUIRED glib-2.0)
pkg_check_modules(GIO2 REQUIRED gio-2.0)
pkg_check_modules(Gsetting REQUIRED gsettings-qt)
pkg_check_modules(PipeWire REQUIRED libpipewire-0.3)
include_directories(${GLIB2_INCLUDE_DIRS})
include_directories(${GIO2_INCLUDE_DIRS})
include_directories(${Gsetting_INCLUDE_DIRS})
include_directories(${PipeWire_INCLUDE_DIRS})
#generate libtaskmanager.so
set (PLUGIN_SRCS
screencastingrequest.cpp screencastingrequest.h
screencasting.cpp screencasting.h
pipewirecore.cpp pipewirecore.h
pipewiresourcestream.cpp pipewiresourcestream.h
pipewiresourceitem.cpp pipewiresourceitem.h
taskmanagerplugin.cpp taskmanagerplugin.h
)
add_library(taskmanager SHARED
libtaskmanager_global.h
screencastingrequest.cpp
screencastingrequest.h
screencasting.cpp
screencasting.h
pipewirecore.cpp
pipewirecore.h
pipewiresourcestream.cpp
pipewiresourcestream.h
pipewiresourceitem.cpp
pipewiresourceitem.h
taskmanagerplugin.cpp
taskmanagerplugin.h
)
add_library(${PROJECT_NAME} SHARED ${PLUGIN_SRCS})
target_compile_definitions(${PROJECT_NAME} PRIVATE PLUGIN_IMPORT_URI="${PLUGIN_IMPORT_URI}")
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt5::Core
Qt5::Gui
Qt5::Gui_EGL
Qt::GuiPrivate
Qt5::Quick
Qt5::Widgets
Qt5::DBus
@ -55,7 +59,38 @@ target_link_libraries(${PROJECT_NAME}
${GLIB2_LIBRARIES}
${GIO2_LIBRARIES}
${Gsetting_LIBRARIES}
${PipeWire_LIBRARIES}
KF5::WaylandClient
KF5::I18n
)
target_compile_definitions(taskmanager PRIVATE TASKMANAGER_LIBRARY)
#generate log files
ecm_qt_declare_logging_category(SRCS
HEADER logging.h
IDENTIFIER PIPEWIRE_LOGGING
CATEGORY_NAME plasma_workspace_pipewire_logging
DESCRIPTION "PipeWire components for window thumbnails"
EXPORT PlasmaWorkspacePipeWire)
ecm_qt_install_logging_categories(
EXPORT PlasmaWorkspacePipeWire
FILE myproject.categories
DESTINATION "./"
)
#generate wayland protocol files
set(PLASMA_WAYLAND_PROTOCOLS_DIR /usr/share/plasma-wayland-protocols)
ecm_add_qtwayland_client_protocol(SRCS
PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/screencast.xml
BASENAME zkde-screencast-unstable-v1
)
target_sources(${PROJECT_NAME} PUBLIC pipewirecore.cpp pipewiresourceitem.cpp pipewiresourcestream.cpp
screencasting.cpp screencastingrequest.cpp ${SRCS})
set(PLUGIN_IMPORT_URI "org.ukui.panel.taskmanager")
set(PLUGIN_INSTALL_PATH "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/qt5/qml/org/ukui/panel/taskmanager")
set(taskmanager_LIB_DIR /usr/share/ukui/ukui-panel/libtaskmanager)
install(FILES qmldir DESTINATION ${PLUGIN_INSTALL_PATH})
install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${PLUGIN_INSTALL_PATH})

View File

@ -1,12 +0,0 @@
#ifndef LIBTASKMANAGER_GLOBAL_H
#define LIBTASKMANAGER_GLOBAL_H
#include <QtCore/qglobal.h>
#if defined(LIBTASKMANAGER_LIBRARY)
# define LIBTASKMANAGER_EXPORT Q_DECL_EXPORT
#else
# define LIBTASKMANAGER_EXPORT Q_DECL_IMPORT
#endif
#endif // LIBTASKMANAGER_GLOBAL_H

View File

@ -1,6 +1,93 @@
#include "pipewirecore.h"
#include "logging.h"
#include <KLocalizedString>
#include <QSocketNotifier>
#include <spa/utils/result.h>
PipeWireCore::PipeWireCore()
{
pw_init(nullptr, nullptr);
pwCoreEvents.version = PW_VERSION_CORE_EVENTS;
pwCoreEvents.error = &PipeWireCore::onCoreError;
}
void PipeWireCore::onCoreError(void *data, uint32_t id, int seq, int res, const char *message)
{
Q_UNUSED(seq)
qCWarning(PIPEWIRE_LOGGING) << "PipeWire remote error: " << message;
if (id == PW_ID_CORE && res == -EPIPE) {
PipeWireCore *pw = static_cast<PipeWireCore *>(data);
Q_EMIT pw->pipewireFailed(QString::fromUtf8(message));
}
}
PipeWireCore::~PipeWireCore()
{
if (pwMainLoop) {
pw_loop_leave(pwMainLoop);
}
if (pwCore) {
pw_core_disconnect(pwCore);
}
if (pwContext) {
pw_context_destroy(pwContext);
}
if (pwMainLoop) {
pw_loop_destroy(pwMainLoop);
}
}
bool PipeWireCore::init()
{
pwMainLoop = pw_loop_new(nullptr);
pw_loop_enter(pwMainLoop);
QSocketNotifier *notifier = new QSocketNotifier(pw_loop_get_fd(pwMainLoop), QSocketNotifier::Read, this);
connect(notifier, &QSocketNotifier::activated, this, [this] {
int result = pw_loop_iterate(pwMainLoop, 0);
if (result < 0)
qCWarning(PIPEWIRE_LOGGING) << "pipewire_loop_iterate failed: " << spa_strerror(result);
});
pwContext = pw_context_new(pwMainLoop, nullptr, 0);
if (!pwContext) {
qCWarning(PIPEWIRE_LOGGING) << "Failed to create PipeWire context";
m_error = i18n("Failed to create PipeWire context");
return false;
}
pwCore = pw_context_connect(pwContext, nullptr, 0);
if (!pwCore) {
qCWarning(PIPEWIRE_LOGGING) << "Failed to connect PipeWire context";
m_error = i18n("Failed to connect PipeWire context");
return false;
}
if (pw_loop_iterate(pwMainLoop, 0) < 0) {
qCWarning(PIPEWIRE_LOGGING) << "Failed to start main PipeWire loop";
m_error = i18n("Failed to start main PipeWire loop");
return false;
}
pw_core_add_listener(pwCore, &coreListener, &pwCoreEvents, this);
return true;
}
QSharedPointer<PipeWireCore> PipeWireCore::self()
{
static QWeakPointer<PipeWireCore> global;
QSharedPointer<PipeWireCore> ret;
if (global) {
ret = global.toStrongRef();
} else {
ret.reset(new PipeWireCore);
if (ret->init()) {
global = ret;
}
}
return ret;
}

View File

@ -2,10 +2,33 @@
#define PIPEWIRECORE_H
class PipeWireCore
#include <QObject>
#include <pipewire/pipewire.h>
class PipeWireCore : public QObject
{
Q_OBJECT
public:
PipeWireCore();
static void onCoreError(void *data, uint32_t id, int seq, int res, const char *message);
~PipeWireCore();
bool init();
static QSharedPointer<PipeWireCore> self();
struct pw_core *pwCore = nullptr;
struct pw_context *pwContext = nullptr;
struct pw_loop *pwMainLoop = nullptr;
spa_hook coreListener;
QString m_error;
pw_core_events pwCoreEvents = {};
Q_SIGNALS:
void pipewireFailed(const QString &message);
};
#endif // PIPEWIRECORE_H

View File

@ -1,6 +1,284 @@
#include "pipewiresourceitem.h"
#include "pipewiresourcestream.h"
#include <QGuiApplication>
#include <QOpenGLContext>
#include <QOpenGLTexture>
#include <QQuickWindow>
#include <QRunnable>
#include <QSGImageNode>
#include <QSocketNotifier>
#include <QThread>
#include <qpa/qplatformnativeinterface.h>
PipeWireSourceItem::PipeWireSourceItem()
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <libdrm/drm_fourcc.h>
#include <QtPlatformHeaders/QEGLNativeContext>
static void pwInit()
{
pw_init(nullptr, nullptr);
}
Q_COREAPP_STARTUP_FUNCTION(pwInit);
class DiscardEglPixmapRunnable : public QRunnable
{
public:
DiscardEglPixmapRunnable(EGLImageKHR image, QOpenGLTexture *texture)
: m_image(image)
, m_texture(texture)
{
}
void run() override
{
if (m_image != EGL_NO_IMAGE_KHR) {
static auto eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
eglDestroyImageKHR(eglGetCurrentDisplay(), m_image);
}
delete m_texture;
}
private:
const EGLImageKHR m_image;
QOpenGLTexture *m_texture;
};
PipeWireSourceItem::PipeWireSourceItem(QQuickItem *parent)
: QQuickItem(parent)
{
setFlag(ItemHasContents, true);
connect(this, &QQuickItem::visibleChanged, this, [this]() {
if (m_stream)
m_stream->setActive(isVisible());
});
}
PipeWireSourceItem::~PipeWireSourceItem()
{
}
void PipeWireSourceItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
{
switch (change) {
case ItemVisibleHasChanged:
if (m_stream)
m_stream->setActive(isVisible() && data.boolValue && isComponentComplete());
break;
case ItemSceneChange:
m_needsRecreateTexture = true;
releaseResources();
break;
default:
break;
}
}
void PipeWireSourceItem::releaseResources()
{
if (window()) {
window()->scheduleRenderJob(new DiscardEglPixmapRunnable(m_image, m_texture.take()), QQuickWindow::NoStage);
m_image = EGL_NO_IMAGE_KHR;
}
}
void PipeWireSourceItem::setNodeId(uint nodeId)
{
if (nodeId == m_nodeId)
return;
m_nodeId = nodeId;
if (m_nodeId == 0) {
m_stream.reset(nullptr);
m_createNextTexture = [] {
return nullptr;
};
} else {
m_stream.reset(new PipeWireSourceStream(this));
m_stream->createStream(m_nodeId);
if (!m_stream->error().isEmpty()) {
m_stream.reset(nullptr);
m_nodeId = 0;
return;
}
m_stream->setActive(isVisible() && isComponentComplete());
connect(m_stream.data(), &PipeWireSourceStream::dmabufTextureReceived, this, &PipeWireSourceItem::updateTextureDmaBuf);
connect(m_stream.data(), &PipeWireSourceStream::imageTextureReceived, this, &PipeWireSourceItem::updateTextureImage);
}
Q_EMIT nodeIdChanged(nodeId);
}
QSGNode *PipeWireSourceItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *)
{
if (Q_UNLIKELY(!m_createNextTexture)) {
return node;
}
auto texture = m_createNextTexture();
if (!texture) {
delete node;
return nullptr;
}
if (m_needsRecreateTexture) {
delete node;
node = nullptr;
m_needsRecreateTexture = false;
}
QSGImageNode *textureNode = static_cast<QSGImageNode *>(node);
if (!textureNode) {
textureNode = window()->createImageNode();
textureNode->setOwnsTexture(true);
}
textureNode->setTexture(texture);
const auto br = boundingRect().toRect();
QRect rect({0, 0}, texture->textureSize().scaled(br.size(), Qt::KeepAspectRatio));
rect.moveCenter(br.center());
textureNode->setRect(rect);
return textureNode;
}
QString PipeWireSourceItem::error() const
{
return m_stream->error();
}
static EGLImage createImage(EGLDisplay display, const QVector<DmaBufPlane> &planes, uint32_t format, const QSize &size)
{
const bool hasModifiers = planes[0].modifier != DRM_FORMAT_MOD_INVALID;
QVector<EGLint> attribs;
attribs << EGL_WIDTH << size.width() << EGL_HEIGHT << size.height() << EGL_LINUX_DRM_FOURCC_EXT << EGLint(format)
<< EGL_DMA_BUF_PLANE0_FD_EXT << planes[0].fd << EGL_DMA_BUF_PLANE0_OFFSET_EXT << EGLint(planes[0].offset) << EGL_DMA_BUF_PLANE0_PITCH_EXT
<< EGLint(planes[0].stride);
if (hasModifiers) {
attribs << EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT << EGLint(planes[0].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT
<< EGLint(planes[0].modifier >> 32);
}
if (planes.count() > 1) {
attribs << EGL_DMA_BUF_PLANE1_FD_EXT << planes[1].fd << EGL_DMA_BUF_PLANE1_OFFSET_EXT << EGLint(planes[1].offset) << EGL_DMA_BUF_PLANE1_PITCH_EXT
<< EGLint(planes[1].stride);
if (hasModifiers) {
attribs << EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT << EGLint(planes[1].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT
<< EGLint(planes[1].modifier >> 32);
}
}
if (planes.count() > 2) {
attribs << EGL_DMA_BUF_PLANE2_FD_EXT << planes[2].fd << EGL_DMA_BUF_PLANE2_OFFSET_EXT << EGLint(planes[2].offset) << EGL_DMA_BUF_PLANE2_PITCH_EXT
<< EGLint(planes[2].stride);
if (hasModifiers) {
attribs << EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT << EGLint(planes[2].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT
<< EGLint(planes[2].modifier >> 32);
}
}
if (planes.count() > 3) {
attribs << EGL_DMA_BUF_PLANE3_FD_EXT << planes[3].fd << EGL_DMA_BUF_PLANE3_OFFSET_EXT << EGLint(planes[3].offset) << EGL_DMA_BUF_PLANE3_PITCH_EXT
<< EGLint(planes[3].stride);
if (hasModifiers) {
attribs << EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT << EGLint(planes[3].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT
<< EGLint(planes[3].modifier >> 32);
}
}
attribs << EGL_NONE;
static auto eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
Q_ASSERT(eglCreateImageKHR);
EGLImage ret = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer) nullptr, attribs.data());
if (ret == EGL_NO_IMAGE_KHR) {
qWarning() << "invalid image" << glGetError();
}
// Q_ASSERT(ret);
return ret;
}
void PipeWireSourceItem::updateTextureDmaBuf(const QVector<DmaBufPlane> &planes, uint32_t format)
{
static auto s_glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
if (!s_glEGLImageTargetTexture2DOES) {
qWarning() << "glEGLImageTargetTexture2DOES is not available" << window();
return;
}
if (!window() || !window()->openglContext() || !m_stream) {
qWarning() << "need a window and a context" << window();
return;
}
const EGLDisplay display = static_cast<EGLDisplay>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay"));
if (m_image) {
static auto eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
eglDestroyImageKHR(display, m_image);
}
const auto size = m_stream->size();
m_image = createImage(display, planes, format, size);
if (m_image == EGL_NO_IMAGE_KHR) {
QImage img(200, 200, QImage::Format_ARGB32_Premultiplied);
img.fill(Qt::blue);
updateTextureImage(img);
return;
}
m_createNextTexture = [this, size, format] {
if (!m_texture) {
m_texture.reset(new QOpenGLTexture(QOpenGLTexture::Target2D));
bool created = m_texture->create();
Q_ASSERT(created);
}
m_texture->bind();
s_glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)m_image);
m_texture->setWrapMode(QOpenGLTexture::ClampToEdge);
m_texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
m_texture->release();
m_texture->setSize(size.width(), size.height());
int textureId = m_texture->textureId();
QQuickWindow::CreateTextureOption textureOption = format == DRM_FORMAT_ARGB8888 ? QQuickWindow::TextureHasAlphaChannel : QQuickWindow::TextureIsOpaque;
return window()->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture, &textureId, 0 /*a vulkan thing?*/, size, textureOption);
};
if (window()->isVisible()) {
update();
}
}
void PipeWireSourceItem::updateTextureImage(const QImage &image)
{
if (!window()) {
qWarning() << "pass";
return;
}
m_createNextTexture = [this, image] {
return window()->createTextureFromImage(image, QQuickWindow::TextureIsOpaque);
};
if (window()->isVisible())
update();
}
void PipeWireSourceItem::componentComplete()
{
if (m_stream)
m_stream->setActive(isVisible());
QQuickItem::componentComplete();
}

View File

@ -2,11 +2,55 @@
#define PIPEWIRESOURCEITEM_H
#include <QQuickItem>
#include <functional>
#include <pipewire/pipewire.h>
#include <spa/param/format-utils.h>
#include <spa/param/props.h>
#include <spa/param/video/format-utils.h>
struct DmaBufPlane;
class PipeWireSourceStream;
class QSGTexture;
class QOpenGLTexture;
typedef void *EGLImage;
class PipeWireSourceItem : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(uint nodeId READ nodeId WRITE setNodeId NOTIFY nodeIdChanged)
public:
PipeWireSourceItem();
PipeWireSourceItem(QQuickItem *parent = nullptr);
~PipeWireSourceItem() override;
QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data) override;
Q_SCRIPTABLE QString error() const;
void setNodeId(uint nodeId);
uint nodeId() const
{
return m_nodeId;
}
void componentComplete() override;
void releaseResources() override;
Q_SIGNALS:
void nodeIdChanged(uint nodeId);
private:
void itemChange(ItemChange change, const ItemChangeData &data) override;
void updateTextureDmaBuf(const QVector<DmaBufPlane> &plane, uint32_t format);
void updateTextureImage(const QImage &image);
uint m_nodeId = 0;
std::function<QSGTexture *()> m_createNextTexture;
QScopedPointer<PipeWireSourceStream> m_stream;
QScopedPointer<QOpenGLTexture> m_texture;
EGLImage m_image = nullptr;
bool m_needsRecreateTexture = false;
};
#endif // PIPEWIRESOURCEITEM_H

View File

@ -1,6 +1,364 @@
#include "pipewiresourcestream.h"
PipeWireSourceStream::PipeWireSourceStream()
{
#include "logging.h"
#include "pipewirecore.h"
#include <fcntl.h>
#include <libdrm/drm_fourcc.h>
#include <spa/utils/result.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <QGuiApplication>
#include <QLoggingCategory>
#include <QOpenGLTexture>
#include <QSocketNotifier>
#include <QVersionNumber>
#include <qpa/qplatformnativeinterface.h>
#include <KLocalizedString>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <QtPlatformHeaders/QEGLNativeContext>
#undef Status
#if !PW_CHECK_VERSION(0, 3, 29)
#define SPA_POD_PROP_FLAG_MANDATORY (1u << 3)
#endif
#if !PW_CHECK_VERSION(0, 3, 33)
#define SPA_POD_PROP_FLAG_DONT_FIXATE (1u << 4)
#endif
static uint32_t SpaPixelFormatToDrmFormat(uint32_t spa_format)
{
switch (spa_format) {
case SPA_VIDEO_FORMAT_RGBA:
return DRM_FORMAT_ABGR8888;
case SPA_VIDEO_FORMAT_RGBx:
return DRM_FORMAT_XBGR8888;
case SPA_VIDEO_FORMAT_BGRA:
return DRM_FORMAT_ARGB8888;
case SPA_VIDEO_FORMAT_BGRx:
return DRM_FORMAT_XRGB8888;
default:
return DRM_FORMAT_INVALID;
}
}
static std::vector<uint64_t> queryDmaBufModifiers(EGLDisplay display, uint32_t format)
{
static auto eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT");
static auto eglQueryDmaBufFormatsEXT = (PFNEGLQUERYDMABUFFORMATSEXTPROC)eglGetProcAddress("eglQueryDmaBufFormatsEXT");
if (!eglQueryDmaBufFormatsEXT || !eglQueryDmaBufModifiersEXT) {
return {};
}
uint32_t drm_format = SpaPixelFormatToDrmFormat(format);
if (drm_format == DRM_FORMAT_INVALID) {
qCDebug(PIPEWIRE_LOGGING) << "Failed to find matching DRM format." << format;
return {};
}
EGLint count = 0;
EGLBoolean success = eglQueryDmaBufFormatsEXT(display, 0, nullptr, &count);
if (!success || count == 0) {
qCWarning(PIPEWIRE_LOGGING) << "Failed to query DMA-BUF format count.";
return {};
}
std::vector<uint32_t> formats(count);
if (!eglQueryDmaBufFormatsEXT(display, count, reinterpret_cast<EGLint *>(formats.data()), &count)) {
if (!success)
qCWarning(PIPEWIRE_LOGGING) << "Failed to query DMA-BUF formats.";
return {};
}
if (std::find(formats.begin(), formats.end(), drm_format) == formats.end()) {
qCDebug(PIPEWIRE_LOGGING) << "Format " << drm_format << " not supported for modifiers.";
return {DRM_FORMAT_MOD_INVALID};
}
success = eglQueryDmaBufModifiersEXT(display, drm_format, 0, nullptr, nullptr, &count);
if (!success) {
qCWarning(PIPEWIRE_LOGGING) << "Failed to query DMA-BUF modifier count.";
return {};
}
std::vector<uint64_t> modifiers(count);
if (count > 0) {
if (!eglQueryDmaBufModifiersEXT(display, drm_format, count, modifiers.data(), nullptr, &count)) {
qCWarning(PIPEWIRE_LOGGING) << "Failed to query DMA-BUF modifiers.";
}
}
// Support modifier-less buffers
modifiers.push_back(DRM_FORMAT_MOD_INVALID);
return modifiers;
}
void PipeWireSourceStream::onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message)
{
PipeWireSourceStream *pw = static_cast<PipeWireSourceStream *>(data);
qCDebug(PIPEWIRE_LOGGING) << "state changed" << pw_stream_state_as_string(old) << "->" << pw_stream_state_as_string(state) << error_message;
switch (state) {
case PW_STREAM_STATE_ERROR:
qCWarning(PIPEWIRE_LOGGING) << "Stream error: " << error_message;
break;
case PW_STREAM_STATE_PAUSED:
Q_EMIT pw->streamReady();
break;
case PW_STREAM_STATE_STREAMING:
Q_EMIT pw->startStreaming();
break;
case PW_STREAM_STATE_CONNECTING:
break;
case PW_STREAM_STATE_UNCONNECTED:
if (!pw->m_stopped) {
Q_EMIT pw->stopStreaming();
}
break;
}
}
static spa_pod *buildFormat(spa_pod_builder *builder, spa_video_format format, const std::vector<uint64_t> &modifiers = {})
{
spa_pod_frame f[2];
const spa_rectangle pw_min_screen_bounds{1, 1};
const spa_rectangle pw_max_screen_bounds{UINT32_MAX, UINT32_MAX};
spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
if (modifiers.size()) {
auto pw_version = QVersionNumber::fromString(pw_get_library_version());
// SPA_POD_PROP_FLAG_DONT_FIXATE can be used with PipeWire >= 0.3.33
if (pw_version >= QVersionNumber(0, 3, 33)) {
spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
} else {
spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY);
}
spa_pod_builder_push_choice(builder, &f[1], SPA_CHOICE_Enum, 0);
// mofifiers from the array
for (auto it = modifiers.begin(); it != modifiers.end(); it++) {
spa_pod_builder_long(builder, *it);
if (it == modifiers.begin()) {
spa_pod_builder_long(builder, *it);
}
}
spa_pod_builder_pop(builder, &f[1]);
}
spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&pw_min_screen_bounds, &pw_min_screen_bounds, &pw_max_screen_bounds), 0);
return static_cast<spa_pod *>(spa_pod_builder_pop(builder, &f[0]));
}
void PipeWireSourceStream::onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format)
{
if (!format || id != SPA_PARAM_Format) {
return;
}
PipeWireSourceStream *pw = static_cast<PipeWireSourceStream *>(data);
spa_format_video_raw_parse(format, &pw->videoFormat);
const int32_t width = pw->videoFormat.size.width;
const int32_t height = pw->videoFormat.size.height;
const int bpp = pw->videoFormat.format == SPA_VIDEO_FORMAT_RGB || pw->videoFormat.format == SPA_VIDEO_FORMAT_BGR ? 3 : 4;
const quint32 stride = SPA_ROUND_UP_N(width * bpp, 4);
qCDebug(PIPEWIRE_LOGGING) << "Stream format changed";
const int32_t size = height * stride;
uint8_t paramsBuffer[1024];
spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(paramsBuffer, sizeof(paramsBuffer));
const auto bufferTypes = pw->m_allowDmaBuf && spa_pod_find_prop(format, nullptr, SPA_FORMAT_VIDEO_modifier)
? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr)
: (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr);
const spa_pod *param = (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_CHOICE_RANGE_Int(stride, stride, INT32_MAX),
SPA_PARAM_BUFFERS_align,
SPA_POD_Int(16),
SPA_PARAM_BUFFERS_dataType,
SPA_POD_CHOICE_FLAGS_Int(bufferTypes));
pw_stream_update_params(pw->pwStream, &param, 1);
}
static void onProcess(void *data)
{
PipeWireSourceStream *stream = static_cast<PipeWireSourceStream *>(data);
stream->process();
}
PipeWireSourceStream::PipeWireSourceStream(QObject *parent)
: QObject(parent)
{
pwStreamEvents.version = PW_VERSION_STREAM_EVENTS;
pwStreamEvents.process = &onProcess;
pwStreamEvents.state_changed = &PipeWireSourceStream::onStreamStateChanged;
pwStreamEvents.param_changed = &PipeWireSourceStream::onStreamParamChanged;
}
PipeWireSourceStream::~PipeWireSourceStream()
{
m_stopped = true;
if (pwStream) {
pw_stream_destroy(pwStream);
}
}
uint PipeWireSourceStream::framerate()
{
if (pwStream) {
return videoFormat.max_framerate.num / videoFormat.max_framerate.denom;
}
return 0;
}
uint PipeWireSourceStream::nodeId()
{
return pwNodeId;
}
bool PipeWireSourceStream::createStream(uint nodeid)
{
pwCore = PipeWireCore::self();
if (!pwCore->m_error.isEmpty()) {
m_error = pwCore->m_error;
return false;
}
connect(pwCore.data(), &PipeWireCore::pipewireFailed, this, &PipeWireSourceStream::coreFailed);
pwStream = pw_stream_new(pwCore->pwCore, "plasma-screencast", nullptr);
pwNodeId = nodeid;
pw_stream_add_listener(pwStream, &streamListener, &pwStreamEvents, this);
uint8_t buffer[4096];
spa_pod_builder podBuilder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
const QVector<spa_video_format> formats =
{SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_BGR};
QVector<const spa_pod *> params;
params.reserve(formats.size() * 2);
const EGLDisplay display = static_cast<EGLDisplay>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay"));
for (spa_video_format format : formats) {
if (m_allowDmaBuf) {
if (auto modifiers = queryDmaBufModifiers(display, format); modifiers.size() > 0) {
params += buildFormat(&podBuilder, format, modifiers);
}
}
params += buildFormat(&podBuilder, format, {});
}
pw_stream_flags s = (pw_stream_flags)(PW_STREAM_FLAG_DONT_RECONNECT | PW_STREAM_FLAG_AUTOCONNECT);
if (pw_stream_connect(pwStream, PW_DIRECTION_INPUT, pwNodeId, s, params.data(), params.size()) != 0) {
qCWarning(PIPEWIRE_LOGGING) << "Could not connect to stream";
pw_stream_destroy(pwStream);
return false;
}
return true;
}
void PipeWireSourceStream::handleFrame(struct pw_buffer *buffer)
{
spa_buffer *spaBuffer = buffer->buffer;
if (spaBuffer->datas->chunk->size == 0) {
return;
}
if (spaBuffer->datas->type == SPA_DATA_MemFd) {
uint8_t *map =
static_cast<uint8_t *>(mmap(nullptr, spaBuffer->datas->maxsize + spaBuffer->datas->mapoffset, PROT_READ, MAP_PRIVATE, spaBuffer->datas->fd, 0));
if (map == MAP_FAILED) {
qCWarning(PIPEWIRE_LOGGING) << "Failed to mmap the memory: " << strerror(errno);
return;
}
const QImage::Format format = spaBuffer->datas->chunk->stride / videoFormat.size.width == 3 ? QImage::Format_RGB888 : QImage::Format_ARGB32;
QImage img(map, videoFormat.size.width, videoFormat.size.height, spaBuffer->datas->chunk->stride, format);
Q_EMIT imageTextureReceived(img.copy());
munmap(map, spaBuffer->datas->maxsize + spaBuffer->datas->mapoffset);
} else if (spaBuffer->datas->type == SPA_DATA_DmaBuf) {
QVector<DmaBufPlane> planes;
planes.reserve(spaBuffer->n_datas);
for (uint i = 0; i < spaBuffer->n_datas; ++i) {
const auto &data = spaBuffer->datas[i];
DmaBufPlane plane;
plane.fd = data.fd;
plane.stride = data.chunk->stride;
plane.offset = data.chunk->offset;
plane.modifier = DRM_FORMAT_MOD_INVALID;
planes += plane;
}
Q_EMIT dmabufTextureReceived(planes, DRM_FORMAT_ARGB8888);
} else if (spaBuffer->datas->type == SPA_DATA_MemPtr) {
QImage img(static_cast<uint8_t *>(spaBuffer->datas->data),
videoFormat.size.width,
videoFormat.size.height,
spaBuffer->datas->chunk->stride,
QImage::Format_ARGB32);
Q_EMIT imageTextureReceived(img);
} else {
qWarning() << "unsupported buffer type" << spaBuffer->datas->type;
QImage errorImage(200, 200, QImage::Format_ARGB32_Premultiplied);
errorImage.fill(Qt::red);
Q_EMIT imageTextureReceived(errorImage);
}
}
void PipeWireSourceStream::coreFailed(const QString &errorMessage)
{
m_error = errorMessage;
Q_EMIT stopStreaming();
}
void PipeWireSourceStream::process()
{
pw_buffer *buf = pw_stream_dequeue_buffer(pwStream);
if (!buf) {
return;
}
handleFrame(buf);
pw_stream_queue_buffer(pwStream, buf);
}
void PipeWireSourceStream::stop()
{
if (!m_stopped)
pw_stream_set_active(pwStream, false);
m_stopped = true;
delete this;
}
void PipeWireSourceStream::setActive(bool active)
{
Q_ASSERT(pwStream);
pw_stream_set_active(pwStream, active);
}

View File

@ -1,11 +1,86 @@
#ifndef PIPEWIRESOURCESTREAM_H
#define PIPEWIRESOURCESTREAM_H
#include <QHash>
#include <QObject>
#include <QSharedPointer>
#include <QSize>
class PipeWireSourceStream
#include <pipewire/pipewire.h>
#include <spa/param/format-utils.h>
#include <spa/param/props.h>
#include <spa/param/video/format-utils.h>
#undef Status
namespace KWin
{
class AbstractEglBackend;
class GLTexture;
}
class PipeWireCore;
typedef void *EGLDisplay;
struct DmaBufPlane {
int fd; /// The dmabuf file descriptor
uint32_t offset; /// The offset from the start of buffer
uint32_t stride; /// The distance from the start of a row to the next row in bytes
uint64_t modifier = 0; /// The layout modifier
};
class PipeWireSourceStream : public QObject
{
Q_OBJECT
public:
PipeWireSourceStream();
explicit PipeWireSourceStream(QObject *parent);
~PipeWireSourceStream();
static void onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format);
static void onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message);
uint framerate();
uint nodeId();
QString error() const
{
return m_error;
}
QSize size() const
{
return QSize(videoFormat.size.width, videoFormat.size.height);
}
bool createStream(uint nodeid);
void stop();
void setActive(bool active);
void handleFrame(struct pw_buffer *buffer);
void process();
bool setAllowDmaBuf(bool allowed);
Q_SIGNALS:
void streamReady();
void startStreaming();
void stopStreaming();
void dmabufTextureReceived(const QVector<DmaBufPlane> &planes, uint32_t format);
void imageTextureReceived(const QImage &image);
private:
void coreFailed(const QString &errorMessage);
QSharedPointer<PipeWireCore> pwCore;
pw_stream *pwStream = nullptr;
spa_hook streamListener;
pw_stream_events pwStreamEvents = {};
uint32_t pwNodeId = 0;
bool m_stopped = false;
spa_video_info_raw videoFormat;
QString m_error;
bool m_allowDmaBuf = true;
};
#endif // PIPEWIRESOURCESTREAM_H

2
libtaskmanager/qmldir Normal file
View File

@ -0,0 +1,2 @@
module org.ukui.panel.taskmanager
plugin taskmanager

View File

@ -1,6 +1,109 @@
#include "screencasting.h"
#include "qwayland-zkde-screencast-unstable-v1.h"
#include <KWayland/Client/output.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <QDebug>
#include <QRect>
Screencasting::Screencasting()
using namespace KWayland::Client;
class ScreencastingStreamPrivate : public QtWayland::zkde_screencast_stream_unstable_v1
{
public:
ScreencastingStreamPrivate(ScreencastingStream *q)
: q(q)
{
}
~ScreencastingStreamPrivate()
{
close();
q->deleteLater();
}
void zkde_screencast_stream_unstable_v1_created(uint32_t node) override
{
m_nodeId = node;
Q_EMIT q->created(node);
}
void zkde_screencast_stream_unstable_v1_closed() override
{
Q_EMIT q->closed();
}
void zkde_screencast_stream_unstable_v1_failed(const QString &error) override
{
Q_EMIT q->failed(error);
}
uint m_nodeId = 0;
QPointer<ScreencastingStream> q;
};
ScreencastingStream::ScreencastingStream(QObject *parent)
: QObject(parent)
, d(new ScreencastingStreamPrivate(this))
{
}
ScreencastingStream::~ScreencastingStream() = default;
quint32 ScreencastingStream::nodeId() const
{
return d->m_nodeId;
}
class ScreencastingPrivate : public QtWayland::zkde_screencast_unstable_v1
{
public:
ScreencastingPrivate(Registry *registry, int id, int version, Screencasting *q)
: QtWayland::zkde_screencast_unstable_v1(*registry, id, version)
, q(q)
{
}
ScreencastingPrivate(::zkde_screencast_unstable_v1 *screencasting, Screencasting *q)
: QtWayland::zkde_screencast_unstable_v1(screencasting)
, q(q)
{
}
~ScreencastingPrivate()
{
destroy();
}
Screencasting *const q;
};
Screencasting::Screencasting(QObject *parent)
:QObject(parent)
{
}
Screencasting::Screencasting(Registry *registry, int id, int version, QObject *parent)
: QObject(parent)
, d(new ScreencastingPrivate(registry, id, version, this))
{
}
Screencasting::~Screencasting() = default;
ScreencastingStream *Screencasting::createWindowStream(const QString &uuid, CursorMode mode)
{
auto stream = new ScreencastingStream(this);
stream->d->init(d->stream_window(uuid, mode));
return stream;
}
void Screencasting::setup(::zkde_screencast_unstable_v1 *screencasting)
{
d.reset(new ScreencastingPrivate(screencasting, this));
}
void Screencasting::destroy()
{
d.reset(nullptr);
}

View File

@ -18,7 +18,6 @@ class Output;
}
class ScreencastingPrivate;
class ScreencastingSourcePrivate;
class ScreencastingStreamPrivate;
class ScreencastingStream : public QObject
{
@ -41,8 +40,28 @@ private:
class Screencasting : public QObject
{
Q_OBJECT
public:
Screencasting();
explicit Screencasting(QObject *parent = nullptr);
explicit Screencasting(KWayland::Client::Registry *registry, int id, int version, QObject *parent = nullptr);
~Screencasting() override;
enum CursorMode {
Hidden = 1,
Embedded = 2,
Metadata = 4,
};
Q_ENUM(CursorMode);
ScreencastingStream *createWindowStream(const QString &uuid, CursorMode mode);
void setup(zkde_screencast_unstable_v1 *screencasting);
void destroy();
private:
QScopedPointer<ScreencastingPrivate> d;
};
#endif // SCREENCASTING_H

View File

@ -27,8 +27,8 @@ public:
if (interfaceName != "zkde_screencast_unstable_v1")
return;
// m_screencasting = new Screencasting(registry, name, version, this);
// Q_EMIT created(m_screencasting);
m_screencasting = new Screencasting(registry, name, version, this);
Q_EMIT created(m_screencasting);
});
registry->create(connection);
@ -106,23 +106,23 @@ void ScreencastingRequest::setUuid(const QString &uuid)
void ScreencastingRequest::create(Screencasting *screencasting)
{
// auto stream = screencasting->createWindowStream(m_uuid, Screencasting::CursorMode::Hidden);
// stream->setObjectName(m_uuid);
auto stream = screencasting->createWindowStream(m_uuid, Screencasting::CursorMode::Hidden);
stream->setObjectName(m_uuid);
// connect(stream, &ScreencastingStream::created, this, [stream, this](int nodeId) {
// if (stream->objectName() == m_uuid) {
// setNodeid(nodeId);
// }
// });
// connect(stream, &ScreencastingStream::failed, this, [](const QString &error) {
// qWarning() << "error creating screencast" << error;
// });
// connect(stream, &ScreencastingStream::closed, this, [this, stream] {
// if (stream->nodeId() == m_nodeId) {
// setNodeid(0);
// }
// });
// connect(this, &ScreencastingRequest::closeRunningStreams, stream, &QObject::deleteLater);
connect(stream, &ScreencastingStream::created, this, [stream, this](int nodeId) {
if (stream->objectName() == m_uuid) {
setNodeid(nodeId);
}
});
connect(stream, &ScreencastingStream::failed, this, [](const QString &error) {
qWarning() << "error creating screencast" << error;
});
connect(stream, &ScreencastingStream::closed, this, [this, stream] {
if (stream->nodeId() == m_nodeId) {
setNodeid(0);
}
});
connect(this, &ScreencastingRequest::closeRunningStreams, stream, &QObject::deleteLater);
}
#include "screencastingrequest.moc"

View File

@ -1,7 +1,6 @@
#ifndef SCREENCASTINGREQUEST_H
#define SCREENCASTINGREQUEST_H
#include "libtaskmanager_global.h"
#include "screencasting.h"
#include <QObject>
@ -9,6 +8,10 @@ class ScreencastingStream;
class ScreencastingRequest : public QObject
{
Q_OBJECT
Q_PROPERTY(QString uuid READ uuid WRITE setUuid NOTIFY uuidChanged)
Q_PROPERTY(quint32 nodeId READ nodeId NOTIFY nodeIdChanged)
public:
ScreencastingRequest(QObject *parent = nullptr);
~ScreencastingRequest();
@ -22,6 +25,7 @@ Q_SIGNALS:
void nodeIdChanged(quint32 nodeId);
void uuidChanged(const QString &uuid);
void closeRunningStreams();
void cursorModeChanged(Screencasting::CursorMode cursorMode);
private:
void setNodeid(uint nodeId);

View File

@ -8,7 +8,7 @@ namespace TaskManager
{
void TaskManagerPlugin::registerTypes(const char *uri)
{
Q_ASSERT(uri == QLatin1String("org.ukui.panel.taskmanager"));
Q_ASSERT(QLatin1String(uri) == QLatin1String(PLUGIN_IMPORT_URI));
qmlRegisterType<PipeWireSourceItem>(uri, 0, 1, "PipeWireSourceItem");
qmlRegisterType<ScreencastingRequest>(uri, 0, 1, "ScreencastingRequest");

View File

@ -9,7 +9,7 @@ namespace TaskManager
class TaskManagerPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
void registerTypes(const char *uri) override;