Refactor: Initial setup for Wayland POC with KDE-specific protocol and Docker build environment.
This commit is contained in:
parent
795e6202ac
commit
ab4bb5a7ec
@ -9,39 +9,40 @@ set(CMAKE_BUILD_TYPE Release)
|
|||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
find_package(Qt5Core REQUIRED)
|
find_package(Qt5 COMPONENTS Core Gui WaylandClient 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})
|
|
||||||
|
|
||||||
# Add the Wayland POC executable
|
# Add the Wayland POC executable
|
||||||
add_executable(wayland_poc "wayland_poc.cpp")
|
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
|
target_link_libraries(wayland_poc
|
||||||
Qt5::Core
|
Qt5::Core
|
||||||
Qt5::Gui
|
Qt5::Gui
|
||||||
Qt5::DBus # Add Qt5DBus
|
Qt5::WaylandClient
|
||||||
${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}
|
|
||||||
)
|
)
|
||||||
@ -1,8 +1,8 @@
|
|||||||
# Use a base image with a recent Linux distribution
|
# Use a base image with a recent Linux distribution
|
||||||
FROM ubuntu:latest
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
# Install necessary dependencies as root
|
# 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
|
# Set up a non-root user
|
||||||
@ -15,12 +15,10 @@ RUN chown -R builder:builder /home/builder/Hyperion_Grabber_X11_QT
|
|||||||
USER builder
|
USER builder
|
||||||
WORKDIR /home/builder/Hyperion_Grabber_X11_QT
|
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
|
RUN rm -rf build && mkdir build
|
||||||
WORKDIR build
|
WORKDIR build
|
||||||
RUN cmake .. -DCMAKE_CXX_COMPILER=clang++
|
RUN cmake .. -DCMAKE_CXX_COMPILER=clang++
|
||||||
RUN qmake -v
|
|
||||||
RUN make
|
|
||||||
|
|
||||||
# You can add commands here to run tests or package the application
|
# You can add commands here to run tests or package the application
|
||||||
# For now, we'll just ensure it builds successfully
|
# For now, we'll just ensure it builds successfully
|
||||||
|
|||||||
190
wayland_poc.cpp
190
wayland_poc.cpp
@ -1,147 +1,99 @@
|
|||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QUuid>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDBusConnection>
|
#include <QScreen>
|
||||||
#include <QDBusConnectionInterface>
|
#include <QGuiApplication>
|
||||||
#include <QDBusMessage>
|
#include <QtWaylandClient/QWaylandClientExtensionTemplate>
|
||||||
#include <QDBusPendingCall>
|
#include <QNativeInterface>
|
||||||
#include <QDBusPendingCallWatcher>
|
|
||||||
#include <QDBusObjectPath>
|
|
||||||
#include <QVariantMap>
|
|
||||||
#include <QThread>
|
|
||||||
|
|
||||||
// Global variable to store the session handle
|
#include "qwayland-zkde-screencast-unstable-v1.h"
|
||||||
static QDBusObjectPath sessionHandle;
|
|
||||||
|
|
||||||
// QDBus callback for xdg-desktop-portal SelectSources response
|
class ScreencastManager : public QObject, public QtWayland::zkde_screencast_unstable_v1
|
||||||
void handleSelectSourcesResponse(QDBusPendingCallWatcher *watcher) {
|
{
|
||||||
QDBusPendingCall reply = *watcher;
|
Q_OBJECT
|
||||||
watcher->deleteLater();
|
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()) {
|
// Request a screencast of the primary screen
|
||||||
qCritical() << "D-Bus call to SelectSources failed:" << reply.error().message(); fflush(stderr);
|
QScreen *screen = QGuiApplication::primaryScreen();
|
||||||
|
if (!screen) {
|
||||||
|
qCritical() << "No primary screen found.";
|
||||||
QCoreApplication::quit();
|
QCoreApplication::quit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QVariant> arguments = reply.reply().arguments();
|
if (auto *waylandScreen = qobject_cast<QNativeInterface::QWaylandScreen *>(screen->nativeInterface())) {
|
||||||
if (arguments.isEmpty()) {
|
wl_output *output = waylandScreen->output();
|
||||||
qCritical() << "SelectSources response has no arguments."; fflush(stderr);
|
if (!output) {
|
||||||
|
qCritical() << "Failed to get native Wayland output from QWaylandScreen.";
|
||||||
QCoreApplication::quit();
|
QCoreApplication::quit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Create a stream for the output
|
||||||
bool success = arguments.at(0).toBool();
|
// The 'mode' parameter (last argument) can be Hidden = 1, Embedded = 2, Metadata = 4
|
||||||
if (!success) {
|
// We'll use Metadata for now.
|
||||||
qCritical() << "SelectSources request denied or failed."; fflush(stderr);
|
stream_output(output, 4); // 4 for Metadata cursor mode
|
||||||
|
} else {
|
||||||
|
qCritical() << "Failed to get Wayland screen interface from QScreen.";
|
||||||
QCoreApplication::quit();
|
QCoreApplication::quit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QVariantList sources = arguments.at(1).toList();
|
protected:
|
||||||
if (sources.isEmpty()) {
|
// Implement the generated Wayland protocol callbacks
|
||||||
qCritical() << "No sources selected."; fflush(stderr);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void zkde_screencast_unstable_v1_stream_destroyed(zkde_screencast_stream_unstable_v1 *stream) override
|
||||||
|
{
|
||||||
|
qDebug() << "Screencast stream destroyed.";
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
void onStreamClosed()
|
||||||
|
{
|
||||||
|
qDebug() << "Screencast stream closed.";
|
||||||
QCoreApplication::quit();
|
QCoreApplication::quit();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For POC, just take the first source
|
void onStreamFailed(const QString &error)
|
||||||
QVariantMap sourceMap = sources.at(0).toMap();
|
{
|
||||||
if (!sourceMap.contains("pipewire_node_id")) {
|
qCritical() << "Screencast stream failed:" << error;
|
||||||
qCritical() << "Selected source missing pipewire_node_id."; fflush(stderr);
|
|
||||||
QCoreApplication::quit();
|
QCoreApplication::quit();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
quint32 node_id = sourceMap.value("pipewire_node_id").toUInt();
|
int main(int argc, char *argv[])
|
||||||
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);
|
|
||||||
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[]) {
|
|
||||||
QCoreApplication app(argc, argv);
|
QCoreApplication app(argc, argv);
|
||||||
|
|
||||||
qDebug() << "Requesting screen capture via xdg-desktop-portal...";
|
qDebug() << "Starting Wayland POC...";
|
||||||
|
|
||||||
QDBusConnection sessionBus = QDBusConnection::sessionBus();
|
ScreencastManager manager;
|
||||||
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);
|
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "wayland_poc.moc"
|
||||||
Loading…
x
Reference in New Issue
Block a user