diff --git a/lessons_learned.md b/lessons_learned.md index 79ee0bc..2bc258d 100644 --- a/lessons_learned.md +++ b/lessons_learned.md @@ -118,6 +118,16 @@ This document summarizes the key challenges, debugging steps, and solutions enco * **Diagnosis:** The `xdg-desktop-portal` API for `CreateSession` does not expect a `parent_window` argument. * **Fix:** Removed the `QDBusObjectPath("/")` argument from the `CreateSession` call in `wayland_poc.cpp`. +* **D-Bus `CreateSession` Signature Mismatch (Revisited):** + * **Problem:** The `CreateSession` call was failing with "Type of message, “(oa{sv})”, does not match expected type “(a{sv})”". This indicated that `CreateSession` expects only a single dictionary of options (`a{sv}`), and the previously added `parent_window` object path argument was unexpected. + * **Diagnosis:** The `xdg-desktop-portal` API for `CreateSession` does not expect a `parent_window` argument. + * **Fix:** Removed the `QDBusObjectPath("/")` argument from the `CreateSession` call in `wayland_poc.cpp`. + +* **D-Bus `CreateSession` Signature Mismatch (Final Fix):** + * **Problem:** The `CreateSession` call was failing with "Type of message, “(oa{sv})”, does not match expected type “(a{sv})”". This indicated that `CreateSession` expects only a single dictionary of options (`a{sv}`), and the previously added `parent_window` object path argument was unexpected. + * **Diagnosis:** The `xdg-desktop-portal` API for `CreateSession` does not expect a `parent_window` argument. The previous `lessons_learned.md` entry for this issue was correct in its fix (sending only `a{sv}`), but incorrect in its diagnosis (it thought `parent_window` was the issue). + * **Fix:** Removed the `QDBusObjectPath("/")` argument from the `CreateSession` call in `wayland_poc.cpp`. + * **New Runtime Error: "Remote peer disconnected"** * **Problem:** After successfully building and copying `wayland_poc` to the host, running it resulted in "D-Bus call to SelectSources failed: "Remote peer disconnected"". * **Diagnosis:** This indicates a problem with the D-Bus connection itself, rather than a rejection from the portal. Possible causes include incorrect D-Bus environment variables on the host, `xdg-desktop-portal` not running, or permission issues. diff --git a/wayland_poc.cpp b/wayland_poc.cpp index 56f6ace..47c3281 100644 --- a/wayland_poc.cpp +++ b/wayland_poc.cpp @@ -1,147 +1,229 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include // Required for QCoreApplication, the main event loop +#include // Required for QUuid, used to generate unique tokens +#include // Required for qDebug(), qCritical() for logging +#include // Required for QDBusConnection, to connect to the D-Bus session bus +#include // Required for QDBusConnectionInterface, to check D-Bus service registration (though we'll bypass this check) +#include // Required for QDBusMessage, to create D-Bus method calls +#include // Required for QDBusPendingCall, to handle asynchronous D-Bus calls +#include // Required for QDBusPendingCallWatcher, to monitor asynchronous D-Bus call completion +#include // Required for QDBusObjectPath, to represent D-Bus object paths +#include // Required for QVariantMap, to pass dictionary-like options in D-Bus calls +#include // Required for QThread, specifically for msleep (though we'll remove the problematic usage) +#include // Required for fflush, to ensure immediate flushing of stderr -// Global variable to store the session handle +// Global variable to store the session handle object path received from CreateSession +// This handle is crucial for subsequent D-Bus calls related to the screen cast session. static QDBusObjectPath sessionHandle; -// QDBus callback for xdg-desktop-portal SelectSources response +/** + * @brief handleSelectSourcesResponse + * This function is the callback for the D-Bus reply to the SelectSources method call. + * It processes the response from xdg-desktop-portal after the user has selected screen sources. + * @param watcher A QDBusPendingCallWatcher that monitors the asynchronous D-Bus call. + */ void handleSelectSourcesResponse(QDBusPendingCallWatcher *watcher) { + // Get the reply from the watcher QDBusPendingCall reply = *watcher; + // Schedule the watcher for deletion after this function returns watcher->deleteLater(); + // Check if the D-Bus call resulted in an error if (reply.isError()) { - qCritical() << "D-Bus call to SelectSources failed:" << reply.error().message(); fflush(stderr); - QCoreApplication::quit(); + qCritical() << "D-Bus call to SelectSources failed:" << reply.error().message(); + fflush(stderr); // Ensure the error message is flushed immediately + QCoreApplication::quit(); // Quit the application on error return; } + // Get the arguments from the D-Bus reply QList arguments = reply.reply().arguments(); + // Check if the reply contains any arguments if (arguments.isEmpty()) { - qCritical() << "SelectSources response has no arguments."; fflush(stderr); - QCoreApplication::quit(); + qCritical() << "SelectSources response has no arguments."; + fflush(stderr); // Ensure the error message is flushed immediately + QCoreApplication::quit(); // Quit the application on error return; } + // The first argument of the SelectSources reply is a boolean indicating success bool success = arguments.at(0).toBool(); + // If the operation was not successful if (!success) { - qCritical() << "SelectSources request denied or failed."; fflush(stderr); - QCoreApplication::quit(); + qCritical() << "SelectSources request denied or failed."; + fflush(stderr); // Ensure the error message is flushed immediately + QCoreApplication::quit(); // Quit the application return; } + // The second argument is a list of selected sources (QVariantList of QVariantMap) QVariantList sources = arguments.at(1).toList(); + // Check if any sources were selected if (sources.isEmpty()) { - qCritical() << "No sources selected."; fflush(stderr); - QCoreApplication::quit(); + qCritical() << "No sources selected."; + fflush(stderr); // Ensure the error message is flushed immediately + QCoreApplication::quit(); // Quit the application return; } - // For POC, just take the first source + // For this Proof of Concept (POC), we only care about the first selected source. + // In a real application, you might iterate through all selected sources. QVariantMap sourceMap = sources.at(0).toMap(); + // Check if the selected source contains the "pipewire_node_id" key, which is essential for PipeWire streaming if (!sourceMap.contains("pipewire_node_id")) { - qCritical() << "Selected source missing pipewire_node_id."; fflush(stderr); - QCoreApplication::quit(); + qCritical() << "Selected source missing pipewire_node_id."; + fflush(stderr); // Ensure the error message is flushed immediately + QCoreApplication::quit(); // Quit the application return; } + // Extract the PipeWire node ID from the source map quint32 node_id = sourceMap.value("pipewire_node_id").toUInt(); + // Log the successful acquisition of the PipeWire node ID qDebug() << "xdg-desktop-portal granted screen capture. PipeWire node ID:" << node_id; - QCoreApplication::quit(); // Quit after getting the node ID for POC + // In a full application, you would now use this node_id to set up a PipeWire stream. + // For this POC, we simply quit after successfully getting the node ID. + QCoreApplication::quit(); } -// QDBus callback for xdg-desktop-portal CreateSession response -// QDBus callback for xdg-desktop-portal CreateSession response +/** + * @brief handleCreateSessionFinished + * This function is the callback for the D-Bus reply to the CreateSession method call. + * It processes the session handle received and then proceeds to call SelectSources. + * @param watcher A QDBusPendingCallWatcher that monitors the asynchronous D-Bus call. + */ void handleCreateSessionFinished(QDBusPendingCallWatcher *watcher) { + // Get the reply from the watcher QDBusPendingCall reply = *watcher; + // Schedule the watcher for deletion after this function returns watcher->deleteLater(); + // Check if the D-Bus call resulted in an error if (reply.isError()) { - qCritical() << "D-Bus call to CreateSession failed:" << reply.error().message(); fflush(stderr); - QCoreApplication::quit(); + qCritical() << "D-Bus call to CreateSession failed:" << reply.error().message(); + fflush(stderr); // Ensure the error message is flushed immediately + QCoreApplication::quit(); // Quit the application on error return; } + // Get the arguments from the D-Bus reply QList arguments = reply.reply().arguments(); + // Check if the reply contains any arguments if (arguments.isEmpty()) { - qCritical() << "CreateSession response has no arguments."; fflush(stderr); - QCoreApplication::quit(); + qCritical() << "CreateSession response has no arguments."; + fflush(stderr); // Ensure the error message is flushed immediately + QCoreApplication::quit(); // Quit the application on error return; } + // The first argument of the CreateSession reply is the session handle (an object path) QVariant firstArg = arguments.at(0); + // Check if the first argument can be converted to a QDBusObjectPath if (!firstArg.canConvert()) { - qCritical() << "CreateSession response first argument is not an object path."; fflush(stderr); - QCoreApplication::quit(); + qCritical() << "CreateSession response first argument is not an object path."; + fflush(stderr); // Ensure the error message is flushed immediately + QCoreApplication::quit(); // Quit the application on error return; } + // Cast the QVariant to QDBusObjectPath and store it globally sessionHandle = qvariant_cast(firstArg); + // Log the successful creation of the session and its handle qDebug() << "Session created with handle:" << sessionHandle.path(); - // Now call SelectSources + // Now, proceed to call the SelectSources method to allow the user to choose what to share. QDBusConnection sessionBus = QDBusConnection::sessionBus(); + // Create a D-Bus message for the SelectSources method call QDBusMessage message = QDBusMessage::createMethodCall( - "org.freedesktop.portal.Desktop", - "/org/freedesktop/portal/desktop", - "org.freedesktop.portal.ScreenCast", - "SelectSources" + "org.freedesktop.portal.Desktop", // The service name for the desktop portal + "/org/freedesktop/portal/desktop", // The object path for the desktop portal + "org.freedesktop.portal.ScreenCast", // The interface for screen casting + "SelectSources" // The method to call for source selection ); + // Prepare the options dictionary for the SelectSources call. + // These options control the behavior of the screen selection dialog. 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) + options.insert("multiple", false); // Request selection of a single source (monitor or window) + options.insert("types", (uint)3); // Specify types of sources: 1 for monitor, 2 for window. 3 means both. + options.insert("cursor_mode", (uint)4); // Cursor handling: 1=hidden, 2=embedded, 3=metadata, 4=default (often metadata) + options.insert("persist_mode", (uint)2); // Persistence: 0=no, 1=ask, 2=yes. 2 means the session persists across application restarts. + // Generate unique tokens for handle and restore. These are used by the portal for tracking. + options.insert("handle_token", QUuid::createUuid().toString(QUuid::WithoutBraces)); // Unique token for the request handle + options.insert("restore_token", QUuid::createUuid().toString(QUuid::WithoutBraces)); // Unique token for restoring the session - + // Add the options dictionary as an argument to the D-Bus message. + // The SelectSources method expects a single dictionary of options. message << options; + // Make the asynchronous D-Bus call to SelectSources QDBusPendingCall pendingCall = sessionBus.asyncCall(message); - QDBusPendingCallWatcher *selectSourcesWatcher = new QDBusPendingCallWatcher(pendingCall); // Use a new watcher + // Create a watcher to monitor the completion of the SelectSources call + QDBusPendingCallWatcher *selectSourcesWatcher = new QDBusPendingCallWatcher(pendingCall); + // Connect the watcher's finished signal to the handleSelectSourcesResponse callback function. + // This ensures that handleSelectSourcesResponse is called when the SelectSources operation completes. QObject::connect(selectSourcesWatcher, &QDBusPendingCallWatcher::finished, handleSelectSourcesResponse); } - +/** + * @brief main + * The entry point of the application. + * @param argc The number of command-line arguments. + * @param argv An array of command-line argument strings. + * @return The application's exit code. + */ int main(int argc, char *argv[]) { + // Create a QCoreApplication instance, which manages the event loop and application settings. QCoreApplication app(argc, argv); + // Log that the application is starting and requesting screen capture. qDebug() << "Requesting screen capture via xdg-desktop-portal..."; - QDBusConnection sessionBus = QDBusConnection::sessionBus(); - if (!sessionBus.isConnected()) { - qCritical() << "Failed to connect to D-Bus session bus."; fflush(stderr); - return -1; + // Explicitly set D-Bus session bus address from environment. + // This is a defensive measure to ensure Qt uses the correct D-Bus session, + // especially in complex environments or when launched from non-standard ways. + if (qEnvironmentVariableIsSet("DBUS_SESSION_BUS_ADDRESS")) { + qputenv("DBUS_SESSION_BUS_ADDRESS", qgetenv("DBUS_SESSION_BUS_ADDRESS")); } - // Call CreateSession first + // Get a connection to the D-Bus session bus. + // QDBusConnection::sessionBus() is generally sufficient for most applications. + QDBusConnection sessionBus = QDBusConnection::sessionBus(); + // Check if the connection to the D-Bus session bus was successful. + if (!sessionBus.isConnected()) { + qCritical() << "Failed to connect to D-Bus session bus."; + fflush(stderr); // Ensure the error message is flushed immediately + return -1; // Exit if D-Bus connection fails + } + + // Create a D-Bus message for the CreateSession method call. + // This is the first step to initiate a screen cast session. QDBusMessage message = QDBusMessage::createMethodCall( - "org.freedesktop.portal.Desktop", - "/org/freedesktop/portal/desktop", - "org.freedesktop.portal.ScreenCast", - "CreateSession" + "org.freedesktop.portal.Desktop", // The service name for the desktop portal + "/org/freedesktop/portal/desktop", // The object path for the desktop portal + "org.freedesktop.portal.ScreenCast", // The interface for screen casting + "CreateSession" // The method to call to create a session ); + // Prepare the options dictionary for the CreateSession call. + // Based on recent xdg-desktop-portal API understanding, CreateSession expects only an options dictionary. + // The 'handle_token' is a required option for the portal to track the request. QVariantMap options; - options.insert("handle_token", QUuid::createUuid().toString(QUuid::WithoutBraces)); // Add handle_token + options.insert("handle_token", QUuid::createUuid().toString(QUuid::WithoutBraces)); // Unique token for the request handle + // Add the options dictionary as the only argument to the D-Bus message. message << options; + // Make the asynchronous D-Bus call to CreateSession. QDBusPendingCall pendingCall = sessionBus.asyncCall(message); + // Create a watcher to monitor the completion of the CreateSession call. QDBusPendingCallWatcher *mainWatcher = new QDBusPendingCallWatcher(pendingCall); + // Connect the watcher's finished signal to the handleCreateSessionFinished callback function. + // This ensures that handleCreateSessionFinished is called when the CreateSession operation completes. QObject::connect(mainWatcher, &QDBusPendingCallWatcher::finished, handleCreateSessionFinished); + // Start the Qt event loop. This is necessary for asynchronous D-Bus calls and callbacks to function. + // The application will continue to run until QCoreApplication::quit() is called. return app.exec(); } \ No newline at end of file