diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ea9eaf..eb0456c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a282c99..267dde9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] \ No newline at end of file +CMD ["./wayland_poc"] diff --git a/wayland_poc.cpp b/wayland_poc.cpp index 5251782..0866060 100644 --- a/wayland_poc.cpp +++ b/wayland_poc.cpp @@ -1,147 +1,99 @@ #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include -// 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(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 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(stream), &QtWayland::zkde_screencast_stream_unstable_v1::created, this, &ScreencastManager::onStreamCreated); + connect(static_cast(stream), &QtWayland::zkde_screencast_stream_unstable_v1::closed, this, &ScreencastManager::onStreamClosed); + connect(static_cast(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 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()) { - qCritical() << "CreateSession response first argument is not an object path."; fflush(stderr); - QCoreApplication::quit(); - return; - } - - sessionHandle = qvariant_cast(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" \ No newline at end of file