Refactor: Initial setup for Wayland POC with KDE-specific protocol and Docker build environment.

This commit is contained in:
Tobias J. Endres 2025-08-14 19:07:12 +02:00
parent 795e6202ac
commit ab4bb5a7ec
3 changed files with 109 additions and 158 deletions

View File

@ -9,39 +9,40 @@ set(CMAKE_BUILD_TYPE Release)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt5Core REQUIRED)
find_package(Qt5Network REQUIRED)
find_package(Qt5Widgets REQUIRED)
find_package(X11 REQUIRED)
find_package(Qt5DBus REQUIRED) # Add Qt5DBus
# Find PipeWire
find_package(PkgConfig REQUIRED)
pkg_check_modules(PIPEWIRE REQUIRED libpipewire-0.3)
# Find GIO/GLib for GDBus using pkg-config
pkg_check_modules(GIO REQUIRED gio-2.0) # Use gio-2.0 for pkg-config
set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lXrender -lXdamage -lXss")
include_directories(${X11_INCLUDE_DIR} ${PIPEWIRE_INCLUDE_DIRS})
# add_executable(${PROJECT_NAME} "main.cpp" "hgx11.h" "hgx11.cpp" "hgx11net.h" "hgx11net.cpp" "hgx11damage.h" "hgx11damage.cpp" "hgx11grab.h" "hgx11grab.cpp" "hgx11screensaver.h" "hgx11screensaver.cpp")
# target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Network Qt5::Widgets ${X11_LIBRARIES})
find_package(Qt5 COMPONENTS Core Gui WaylandClient REQUIRED)
# Add the Wayland POC executable
add_executable(wayland_poc "wayland_poc.cpp")
find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner++)
set(WAYLAND_PROTOCOLS_DIR "${CMAKE_CURRENT_BINARY_DIR}/wayland_protocols")
file(MAKE_DIRECTORY ${WAYLAND_PROTOCOLS_DIR})
add_custom_command(
OUTPUT ${WAYLAND_PROTOCOLS_DIR}/qwayland-zkde-screencast-unstable-v1.h
COMMAND ${WAYLAND_SCANNER_EXECUTABLE} client-header "${CMAKE_CURRENT_SOURCE_DIR}/cloned_repos/plasma-wayland-protocols/src/protocols/zkde-screencast-unstable-v1.xml" ${WAYLAND_PROTOCOLS_DIR}/qwayland-zkde-screencast-unstable-v1.h
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/cloned_repos/plasma-wayland-protocols/src/protocols/zkde-screencast-unstable-v1.xml"
COMMENT "Generating Wayland client header for zkde-screencast-unstable-v1"
)
add_custom_command(
OUTPUT ${WAYLAND_PROTOCOLS_DIR}/zkde-screencast-unstable-v1-client-protocol.h
COMMAND ${WAYLAND_SCANNER_EXECUTABLE} code "${CMAKE_CURRENT_SOURCE_DIR}/cloned_repos/plasma-wayland-protocols/src/protocols/zkde-screencast-unstable-v1.xml" ${WAYLAND_PROTOCOLS_DIR}/zkde-screencast-unstable-v1-client-protocol.h
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/cloned_repos/plasma-wayland-protocols/src/protocols/zkde-screencast-unstable-v1.xml"
COMMENT "Generating Wayland client protocol code for zkde-screencast-unstable-v1"
)
add_custom_target(generate_wayland_protocols ALL
DEPENDS ${WAYLAND_PROTOCOLS_DIR}/qwayland-zkde-screencast-unstable-v1.h
${WAYLAND_PROTOCOLS_DIR}/zkde-screencast-unstable-v1-client-protocol.h
)
target_include_directories(wayland_poc PRIVATE ${WAYLAND_PROTOCOLS_DIR})
add_dependencies(wayland_poc generate_wayland_protocols)
target_link_libraries(wayland_poc
Qt5::Core
Qt5::Gui
Qt5::DBus # Add Qt5DBus
${PIPEWIRE_LIBRARIES}
${GIO_LIBRARIES}
)
# target_include_directories(wayland_poc PRIVATE ${PIPEWIRE_INCLUDE_DIRS})
# Add the Tutorial 5 executable
add_executable(tutorial5 tutorial/tutorial5.c)
set_property(SOURCE tutorial/tutorial5.c PROPERTY LANGUAGE C)
target_link_libraries(tutorial5
${PIPEWIRE_LIBRARIES}
)
Qt5::WaylandClient
)

View File

@ -1,8 +1,8 @@
# Use a base image with a recent Linux distribution
FROM ubuntu:latest
FROM ubuntu:22.04
# Install necessary dependencies as root
RUN apt-get update && apt-get install -y --no-install-recommends build-essential cmake qtbase5-dev libpipewire-0.3-dev libgirepository1.0-dev pkg-config git libx11-dev libxext-dev libxdamage-dev libxss-dev libxrender-dev clang && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y --no-install-recommends build-essential cmake qtbase5-dev libqt5waylandclient5-dev libpipewire-0.3-dev libgirepository1.0-dev pkg-config git libx11-dev libxext-dev libxdamage-dev libxss-dev libxrender-dev clang wayland-scanner++ && rm -rf /var/lib/apt/lists/*
# Set up a non-root user
@ -15,13 +15,11 @@ RUN chown -R builder:builder /home/builder/Hyperion_Grabber_X11_QT
USER builder
WORKDIR /home/builder/Hyperion_Grabber_X11_QT
# Create a build directory and build the project
# Create a build directory and configure cmake
RUN rm -rf build && mkdir build
WORKDIR build
RUN cmake .. -DCMAKE_CXX_COMPILER=clang++
RUN qmake -v
RUN make
# You can add commands here to run tests or package the application
# For now, we'll just ensure it builds successfully
CMD ["./wayland_poc"]
CMD ["./wayland_poc"]

View File

@ -1,147 +1,99 @@
#include <QCoreApplication>
#include <QUuid>
#include <QDebug>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusMessage>
#include <QDBusPendingCall>
#include <QDBusPendingCallWatcher>
#include <QDBusObjectPath>
#include <QVariantMap>
#include <QThread>
#include <QScreen>
#include <QGuiApplication>
#include <QtWaylandClient/QWaylandClientExtensionTemplate>
#include <QNativeInterface>
// Global variable to store the session handle
static QDBusObjectPath sessionHandle;
#include "qwayland-zkde-screencast-unstable-v1.h"
// QDBus callback for xdg-desktop-portal SelectSources response
void handleSelectSourcesResponse(QDBusPendingCallWatcher *watcher) {
QDBusPendingCall reply = *watcher;
watcher->deleteLater();
class ScreencastManager : public QObject, public QtWayland::zkde_screencast_unstable_v1
{
Q_OBJECT
public:
ScreencastManager(QObject *parent = nullptr)
: QObject(parent)
, QtWayland::zkde_screencast_unstable_v1(5) // Version 5 is required for stream_output
{
if (!isInitialized()) {
qCritical() << "Failed to initialize zkde_screencast_unstable_v1. Make sure KWin is running and supports this protocol.";
QCoreApplication::quit();
return;
}
qDebug() << "zkde_screencast_unstable_v1 initialized.";
if (reply.isError()) {
qCritical() << "D-Bus call to SelectSources failed:" << reply.error().message(); fflush(stderr);
QCoreApplication::quit();
return;
// Request a screencast of the primary screen
QScreen *screen = QGuiApplication::primaryScreen();
if (!screen) {
qCritical() << "No primary screen found.";
QCoreApplication::quit();
return;
}
if (auto *waylandScreen = qobject_cast<QNativeInterface::QWaylandScreen *>(screen->nativeInterface())) {
wl_output *output = waylandScreen->output();
if (!output) {
qCritical() << "Failed to get native Wayland output from QWaylandScreen.";
QCoreApplication::quit();
return;
}
// Create a stream for the output
// The 'mode' parameter (last argument) can be Hidden = 1, Embedded = 2, Metadata = 4
// We'll use Metadata for now.
stream_output(output, 4); // 4 for Metadata cursor mode
} else {
qCritical() << "Failed to get Wayland screen interface from QScreen.";
QCoreApplication::quit();
return;
}
}
QList<QVariant> arguments = reply.reply().arguments();
if (arguments.isEmpty()) {
qCritical() << "SelectSources response has no arguments."; fflush(stderr);
QCoreApplication::quit();
return;
protected:
// Implement the generated Wayland protocol callbacks
void zkde_screencast_unstable_v1_stream_created(zkde_screencast_stream_unstable_v1 *stream) override
{
qDebug() << "Screencast stream created!";
// Connect to the stream's signals to get the PipeWire node ID
connect(static_cast<QtWayland::zkde_screencast_stream_unstable_v1 *>(stream), &QtWayland::zkde_screencast_stream_unstable_v1::created, this, &ScreencastManager::onStreamCreated);
connect(static_cast<QtWayland::zkde_screencast_stream_unstable_v1 *>(stream), &QtWayland::zkde_screencast_stream_unstable_v1::closed, this, &ScreencastManager::onStreamClosed);
connect(static_cast<QtWayland::zkde_screencast_stream_unstable_v1 *>(stream), &QtWayland::zkde_screencast_stream_unstable_v1::failed, this, &ScreencastManager::onStreamFailed);
}
bool success = arguments.at(0).toBool();
if (!success) {
qCritical() << "SelectSources request denied or failed."; fflush(stderr);
QCoreApplication::quit();
return;
void zkde_screencast_unstable_v1_stream_destroyed(zkde_screencast_stream_unstable_v1 *stream) override
{
qDebug() << "Screencast stream destroyed.";
}
QVariantList sources = arguments.at(1).toList();
if (sources.isEmpty()) {
qCritical() << "No sources selected."; fflush(stderr);
QCoreApplication::quit();
return;
private slots:
void onStreamCreated(quint32 node_id)
{
qDebug() << "PipeWire node ID:" << node_id;
// Here you would typically use the node_id to connect to PipeWire and start processing frames.
QCoreApplication::quit(); // For POC, quit after getting the node ID
}
// For POC, just take the first source
QVariantMap sourceMap = sources.at(0).toMap();
if (!sourceMap.contains("pipewire_node_id")) {
qCritical() << "Selected source missing pipewire_node_id."; fflush(stderr);
void onStreamClosed()
{
qDebug() << "Screencast stream closed.";
QCoreApplication::quit();
return;
}
quint32 node_id = sourceMap.value("pipewire_node_id").toUInt();
qDebug() << "xdg-desktop-portal granted screen capture. PipeWire node ID:" << node_id;
QCoreApplication::quit(); // Quit after getting the node ID for POC
}
// QDBus callback for xdg-desktop-portal CreateSession response
// QDBus callback for xdg-desktop-portal CreateSession response
void handleCreateSessionFinished(QDBusPendingCallWatcher *watcher) {
QDBusPendingCall reply = *watcher;
watcher->deleteLater();
if (reply.isError()) {
qCritical() << "D-Bus call to CreateSession failed:" << reply.error().message(); fflush(stderr);
void onStreamFailed(const QString &error)
{
qCritical() << "Screencast stream failed:" << error;
QCoreApplication::quit();
return;
}
};
QList<QVariant> arguments = reply.reply().arguments();
if (arguments.isEmpty()) {
qCritical() << "CreateSession response has no arguments."; fflush(stderr);
QCoreApplication::quit();
return;
}
QVariant firstArg = arguments.at(0);
if (!firstArg.canConvert<QDBusObjectPath>()) {
qCritical() << "CreateSession response first argument is not an object path."; fflush(stderr);
QCoreApplication::quit();
return;
}
sessionHandle = qvariant_cast<QDBusObjectPath>(firstArg);
qDebug() << "Session created with handle:" << sessionHandle.path();
// Now call SelectSources
QDBusConnection sessionBus = QDBusConnection::sessionBus();
QDBusMessage message = QDBusMessage::createMethodCall(
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.ScreenCast",
"SelectSources"
);
QVariantMap options;
options.insert("multiple", false); // Request single source
options.insert("types", (uint)3); // Monitor and Window
options.insert("cursor_mode", (uint)4); // Metadata
options.insert("persist_mode", (uint)2); // Persist for session (OBS value)
options.insert("handle_token", QUuid::createUuid().toString(QUuid::WithoutBraces)); // Use QUuid without braces
options.insert("restore_token", QUuid::createUuid().toString(QUuid::WithoutBraces)); // Add restore_token (OBS value)
message << options;
QDBusPendingCall pendingCall = sessionBus.asyncCall(message);
QDBusPendingCallWatcher *selectSourcesWatcher = new QDBusPendingCallWatcher(pendingCall); // Use a new watcher
QObject::connect(selectSourcesWatcher, &QDBusPendingCallWatcher::finished, handleSelectSourcesResponse);
}
int main(int argc, char *argv[]) {
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
qDebug() << "Requesting screen capture via xdg-desktop-portal...";
qDebug() << "Starting Wayland POC...";
QDBusConnection sessionBus = QDBusConnection::sessionBus();
if (!sessionBus.isConnected()) {
qCritical() << "Failed to connect to D-Bus session bus."; fflush(stderr);
return -1;
}
// Call CreateSession first
QDBusMessage message = QDBusMessage::createMethodCall(
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.ScreenCast",
"CreateSession"
);
QVariantMap options;
options.insert("handle_token", QUuid::createUuid().toString(QUuid::WithoutBraces)); // Add handle_token
message << options;
QDBusPendingCall pendingCall = sessionBus.asyncCall(message);
QDBusPendingCallWatcher *mainWatcher = new QDBusPendingCallWatcher(pendingCall);
QObject::connect(mainWatcher, &QDBusPendingCallWatcher::finished, handleCreateSessionFinished);
ScreencastManager manager;
return app.exec();
}
#include "wayland_poc.moc"