/* * Copyright 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/desktop_capture/linux/base_capturer_pipewire.h" #include #include #include #include #include #include #include #include #include "absl/memory/memory.h" #include "modules/desktop_capture/desktop_capture_options.h" #include "modules/desktop_capture/desktop_capturer.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" #if defined(WEBRTC_DLOPEN_PIPEWIRE) #include "modules/desktop_capture/linux/pipewire_stubs.h" using modules_desktop_capture_linux::InitializeStubs; using modules_desktop_capture_linux::kModulePipewire; using modules_desktop_capture_linux::StubPathMap; #endif // defined(WEBRTC_DLOPEN_PIPEWIRE) namespace webrtc { const char kDesktopBusName[] = "org.freedesktop.portal.Desktop"; const char kDesktopObjectPath[] = "/org/freedesktop/portal/desktop"; const char kDesktopRequestObjectPath[] = "/org/freedesktop/portal/desktop/request"; const char kSessionInterfaceName[] = "org.freedesktop.portal.Session"; const char kRequestInterfaceName[] = "org.freedesktop.portal.Request"; const char kScreenCastInterfaceName[] = "org.freedesktop.portal.ScreenCast"; const int kBytesPerPixel = 4; #if defined(WEBRTC_DLOPEN_PIPEWIRE) const char kPipeWireLib[] = "libpipewire-0.2.so.1"; #endif // static void BaseCapturerPipeWire::OnStateChanged(void* data, pw_remote_state old_state, pw_remote_state state, const char* error_message) { BaseCapturerPipeWire* that = static_cast(data); RTC_DCHECK(that); switch (state) { case PW_REMOTE_STATE_ERROR: RTC_LOG(LS_ERROR) << "PipeWire remote state error: " << error_message; break; case PW_REMOTE_STATE_CONNECTED: RTC_LOG(LS_INFO) << "PipeWire remote state: connected."; that->CreateReceivingStream(); break; case PW_REMOTE_STATE_CONNECTING: RTC_LOG(LS_INFO) << "PipeWire remote state: connecting."; break; case PW_REMOTE_STATE_UNCONNECTED: RTC_LOG(LS_INFO) << "PipeWire remote state: unconnected."; break; } } // static void BaseCapturerPipeWire::OnStreamStateChanged(void* data, pw_stream_state old_state, pw_stream_state state, const char* error_message) { BaseCapturerPipeWire* that = static_cast(data); RTC_DCHECK(that); switch (state) { case PW_STREAM_STATE_ERROR: RTC_LOG(LS_ERROR) << "PipeWire stream state error: " << error_message; break; case PW_STREAM_STATE_CONFIGURE: pw_stream_set_active(that->pw_stream_, true); break; case PW_STREAM_STATE_UNCONNECTED: case PW_STREAM_STATE_CONNECTING: case PW_STREAM_STATE_READY: case PW_STREAM_STATE_PAUSED: case PW_STREAM_STATE_STREAMING: break; } } // static void BaseCapturerPipeWire::OnStreamFormatChanged(void* data, const struct spa_pod* format) { BaseCapturerPipeWire* that = static_cast(data); RTC_DCHECK(that); RTC_LOG(LS_INFO) << "PipeWire stream format changed."; if (!format) { pw_stream_finish_format(that->pw_stream_, /*res=*/0, /*params=*/nullptr, /*n_params=*/0); return; } that->spa_video_format_ = new spa_video_info_raw(); spa_format_video_raw_parse(format, that->spa_video_format_, &that->pw_type_->format_video); auto width = that->spa_video_format_->size.width; auto height = that->spa_video_format_->size.height; auto stride = SPA_ROUND_UP_N(width * kBytesPerPixel, 4); auto size = height * stride; uint8_t buffer[1024] = {}; auto builder = spa_pod_builder{buffer, sizeof(buffer)}; // Setup buffers and meta header for new format. const struct spa_pod* params[2]; params[0] = reinterpret_cast(spa_pod_builder_object( &builder, // id to enumerate buffer requirements that->pw_core_type_->param.idBuffers, that->pw_core_type_->param_buffers.Buffers, // Size: specified as integer (i) and set to specified size ":", that->pw_core_type_->param_buffers.size, "i", size, // Stride: specified as integer (i) and set to specified stride ":", that->pw_core_type_->param_buffers.stride, "i", stride, // Buffers: specifies how many buffers we want to deal with, set as // integer (i) where preferred number is 8, then allowed number is defined // as range (r) from min and max values and it is undecided (u) to allow // negotiation ":", that->pw_core_type_->param_buffers.buffers, "iru", 8, SPA_POD_PROP_MIN_MAX(1, 32), // Align: memory alignment of the buffer, set as integer (i) to specified // value ":", that->pw_core_type_->param_buffers.align, "i", 16)); params[1] = reinterpret_cast(spa_pod_builder_object( &builder, // id to enumerate supported metadata that->pw_core_type_->param.idMeta, that->pw_core_type_->param_meta.Meta, // Type: specified as id or enum (I) ":", that->pw_core_type_->param_meta.type, "I", that->pw_core_type_->meta.Header, // Size: size of the metadata, specified as integer (i) ":", that->pw_core_type_->param_meta.size, "i", sizeof(struct spa_meta_header))); pw_stream_finish_format(that->pw_stream_, /*res=*/0, params, /*n_params=*/2); } // static void BaseCapturerPipeWire::OnStreamProcess(void* data) { BaseCapturerPipeWire* that = static_cast(data); RTC_DCHECK(that); pw_buffer* buf = nullptr; if (!(buf = pw_stream_dequeue_buffer(that->pw_stream_))) { return; } that->HandleBuffer(buf); pw_stream_queue_buffer(that->pw_stream_, buf); } BaseCapturerPipeWire::BaseCapturerPipeWire(CaptureSourceType source_type) : capture_source_type_(source_type) {} BaseCapturerPipeWire::~BaseCapturerPipeWire() { if (pw_main_loop_) { pw_thread_loop_stop(pw_main_loop_); } if (pw_type_) { delete pw_type_; } if (spa_video_format_) { delete spa_video_format_; } if (pw_stream_) { pw_stream_destroy(pw_stream_); } if (pw_remote_) { pw_remote_destroy(pw_remote_); } if (pw_core_) { pw_core_destroy(pw_core_); } if (pw_main_loop_) { pw_thread_loop_destroy(pw_main_loop_); } if (pw_loop_) { pw_loop_destroy(pw_loop_); } if (current_frame_) { free(current_frame_); } if (start_request_signal_id_) { g_dbus_connection_signal_unsubscribe(connection_, start_request_signal_id_); } if (sources_request_signal_id_) { g_dbus_connection_signal_unsubscribe(connection_, sources_request_signal_id_); } if (session_request_signal_id_) { g_dbus_connection_signal_unsubscribe(connection_, session_request_signal_id_); } if (session_handle_) { GDBusMessage* message = g_dbus_message_new_method_call( kDesktopBusName, session_handle_, kSessionInterfaceName, "Close"); if (message) { GError* error = nullptr; g_dbus_connection_send_message(connection_, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, /*out_serial=*/nullptr, &error); if (error) { RTC_LOG(LS_ERROR) << "Failed to close the session: " << error->message; g_error_free(error); } g_object_unref(message); } } g_free(start_handle_); g_free(sources_handle_); g_free(session_handle_); g_free(portal_handle_); if (cancellable_) { g_cancellable_cancel(cancellable_); g_object_unref(cancellable_); cancellable_ = nullptr; } if (proxy_) { g_object_unref(proxy_); proxy_ = nullptr; } } void BaseCapturerPipeWire::InitPortal() { cancellable_ = g_cancellable_new(); g_dbus_proxy_new_for_bus( G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, /*info=*/nullptr, kDesktopBusName, kDesktopObjectPath, kScreenCastInterfaceName, cancellable_, reinterpret_cast(OnProxyRequested), this); } void BaseCapturerPipeWire::InitPipeWire() { #if defined(WEBRTC_DLOPEN_PIPEWIRE) StubPathMap paths; // Check if the PipeWire library is available. paths[kModulePipewire].push_back(kPipeWireLib); if (!InitializeStubs(paths)) { RTC_LOG(LS_ERROR) << "Failed to load the PipeWire library and symbols."; portal_init_failed_ = true; return; } #endif // defined(WEBRTC_DLOPEN_PIPEWIRE) pw_init(/*argc=*/nullptr, /*argc=*/nullptr); pw_loop_ = pw_loop_new(/*properties=*/nullptr); pw_main_loop_ = pw_thread_loop_new(pw_loop_, "pipewire-main-loop"); pw_core_ = pw_core_new(pw_loop_, /*properties=*/nullptr); pw_core_type_ = pw_core_get_type(pw_core_); pw_remote_ = pw_remote_new(pw_core_, nullptr, /*user_data_size=*/0); InitPipeWireTypes(); // Initialize event handlers, remote end and stream-related. pw_remote_events_.version = PW_VERSION_REMOTE_EVENTS; pw_remote_events_.state_changed = &OnStateChanged; pw_stream_events_.version = PW_VERSION_STREAM_EVENTS; pw_stream_events_.state_changed = &OnStreamStateChanged; pw_stream_events_.format_changed = &OnStreamFormatChanged; pw_stream_events_.process = &OnStreamProcess; pw_remote_add_listener(pw_remote_, &spa_remote_listener_, &pw_remote_events_, this); pw_remote_connect_fd(pw_remote_, pw_fd_); if (pw_thread_loop_start(pw_main_loop_) < 0) { RTC_LOG(LS_ERROR) << "Failed to start main PipeWire loop"; portal_init_failed_ = true; } RTC_LOG(LS_INFO) << "PipeWire remote opened."; } void BaseCapturerPipeWire::InitPipeWireTypes() { spa_type_map* map = pw_core_type_->map; pw_type_ = new PipeWireType(); spa_type_media_type_map(map, &pw_type_->media_type); spa_type_media_subtype_map(map, &pw_type_->media_subtype); spa_type_format_video_map(map, &pw_type_->format_video); spa_type_video_format_map(map, &pw_type_->video_format); } void BaseCapturerPipeWire::CreateReceivingStream() { spa_rectangle pwMinScreenBounds = spa_rectangle{1, 1}; spa_rectangle pwScreenBounds = spa_rectangle{static_cast(desktop_size_.width()), static_cast(desktop_size_.height())}; spa_fraction pwFrameRateMin = spa_fraction{0, 1}; spa_fraction pwFrameRateMax = spa_fraction{60, 1}; pw_properties* reuseProps = pw_properties_new_string("pipewire.client.reuse=1"); pw_stream_ = pw_stream_new(pw_remote_, "webrtc-consume-stream", reuseProps); uint8_t buffer[1024] = {}; const spa_pod* params[1]; spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)}; params[0] = reinterpret_cast(spa_pod_builder_object( &builder, // id to enumerate formats pw_core_type_->param.idEnumFormat, pw_core_type_->spa_format, "I", pw_type_->media_type.video, "I", pw_type_->media_subtype.raw, // Video format: specified as id or enum (I), preferred format is BGRx, // then allowed formats are enumerated (e) and the format is undecided (u) // to allow negotiation ":", pw_type_->format_video.format, "Ieu", pw_type_->video_format.BGRx, SPA_POD_PROP_ENUM(2, pw_type_->video_format.RGBx, pw_type_->video_format.BGRx), // Video size: specified as rectangle (R), preferred size is specified as // first parameter, then allowed size is defined as range (r) from min and // max values and the format is undecided (u) to allow negotiation ":", pw_type_->format_video.size, "Rru", &pwScreenBounds, 2, &pwMinScreenBounds, &pwScreenBounds, // Frame rate: specified as fraction (F) and set to minimum frame rate // value ":", pw_type_->format_video.framerate, "F", &pwFrameRateMin, // Max frame rate: specified as fraction (F), preferred frame rate is set // to maximum value, then allowed frame rate is defined as range (r) from // min and max values and it is undecided (u) to allow negotiation ":", pw_type_->format_video.max_framerate, "Fru", &pwFrameRateMax, 2, &pwFrameRateMin, &pwFrameRateMax)); pw_stream_add_listener(pw_stream_, &spa_stream_listener_, &pw_stream_events_, this); pw_stream_flags flags = static_cast( PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS); if (pw_stream_connect(pw_stream_, PW_DIRECTION_INPUT, /*port_path=*/nullptr, flags, params, /*n_params=*/1) != 0) { RTC_LOG(LS_ERROR) << "Could not connect receiving stream."; portal_init_failed_ = true; return; } } void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) { spa_buffer* spaBuffer = buffer->buffer; void* src = nullptr; if (!(src = spaBuffer->datas[0].data)) { return; } uint32_t maxSize = spaBuffer->datas[0].maxsize; int32_t srcStride = spaBuffer->datas[0].chunk->stride; if (srcStride != (desktop_size_.width() * kBytesPerPixel)) { RTC_LOG(LS_ERROR) << "Got buffer with stride different from screen stride: " << srcStride << " != " << (desktop_size_.width() * kBytesPerPixel); portal_init_failed_ = true; return; } if (!current_frame_) { current_frame_ = static_cast(malloc(maxSize)); } RTC_DCHECK(current_frame_ != nullptr); // If both sides decided to go with the RGBx format we need to convert it to // BGRx to match color format expected by WebRTC. if (spa_video_format_->format == pw_type_->video_format.RGBx) { uint8_t* tempFrame = static_cast(malloc(maxSize)); std::memcpy(tempFrame, src, maxSize); ConvertRGBxToBGRx(tempFrame, maxSize); std::memcpy(current_frame_, tempFrame, maxSize); free(tempFrame); } else { std::memcpy(current_frame_, src, maxSize); } } void BaseCapturerPipeWire::ConvertRGBxToBGRx(uint8_t* frame, uint32_t size) { // Change color format for KDE KWin which uses RGBx and not BGRx for (uint32_t i = 0; i < size; i += 4) { uint8_t tempR = frame[i]; uint8_t tempB = frame[i + 2]; frame[i] = tempB; frame[i + 2] = tempR; } } guint BaseCapturerPipeWire::SetupRequestResponseSignal( const gchar* object_path, GDBusSignalCallback callback) { return g_dbus_connection_signal_subscribe( connection_, kDesktopBusName, kRequestInterfaceName, "Response", object_path, /*arg0=*/nullptr, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, callback, this, /*user_data_free_func=*/nullptr); } // static void BaseCapturerPipeWire::OnProxyRequested(GObject* /*object*/, GAsyncResult* result, gpointer user_data) { BaseCapturerPipeWire* that = static_cast(user_data); RTC_DCHECK(that); GError* error = nullptr; GDBusProxy *proxy = g_dbus_proxy_new_finish(result, &error); if (!proxy) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; RTC_LOG(LS_ERROR) << "Failed to create a proxy for the screen cast portal: " << error->message; g_error_free(error); that->portal_init_failed_ = true; return; } that->proxy_ = proxy; that->connection_ = g_dbus_proxy_get_connection(that->proxy_); RTC_LOG(LS_INFO) << "Created proxy for the screen cast portal."; that->SessionRequest(); } // static gchar* BaseCapturerPipeWire::PrepareSignalHandle(GDBusConnection* connection, const gchar* token) { gchar* sender = g_strdup(g_dbus_connection_get_unique_name(connection) + 1); for (int i = 0; sender[i]; i++) { if (sender[i] == '.') { sender[i] = '_'; } } gchar* handle = g_strconcat(kDesktopRequestObjectPath, "/", sender, "/", token, /*end of varargs*/ nullptr); g_free(sender); return handle; } void BaseCapturerPipeWire::SessionRequest() { GVariantBuilder builder; gchar* variant_string; g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); variant_string = g_strdup_printf("webrtc_session%d", g_random_int_range(0, G_MAXINT)); g_variant_builder_add(&builder, "{sv}", "session_handle_token", g_variant_new_string(variant_string)); g_free(variant_string); variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT)); g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(variant_string)); portal_handle_ = PrepareSignalHandle(connection_, variant_string); session_request_signal_id_ = SetupRequestResponseSignal( portal_handle_, OnSessionRequestResponseSignal); g_free(variant_string); RTC_LOG(LS_INFO) << "Screen cast session requested."; g_dbus_proxy_call( proxy_, "CreateSession", g_variant_new("(a{sv})", &builder), G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_, reinterpret_cast(OnSessionRequested), this); } // static void BaseCapturerPipeWire::OnSessionRequested(GDBusProxy *proxy, GAsyncResult* result, gpointer user_data) { BaseCapturerPipeWire* that = static_cast(user_data); RTC_DCHECK(that); GError* error = nullptr; GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error); if (!variant) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; RTC_LOG(LS_ERROR) << "Failed to create a screen cast session: " << error->message; g_error_free(error); that->portal_init_failed_ = true; return; } RTC_LOG(LS_INFO) << "Initializing the screen cast session."; gchar* handle = nullptr; g_variant_get_child(variant, 0, "o", &handle); g_variant_unref(variant); if (!handle) { RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session."; if (that->session_request_signal_id_) { g_dbus_connection_signal_unsubscribe(that->connection_, that->session_request_signal_id_); that->session_request_signal_id_ = 0; } that->portal_init_failed_ = true; return; } g_free(handle); RTC_LOG(LS_INFO) << "Subscribing to the screen cast session."; } // static void BaseCapturerPipeWire::OnSessionRequestResponseSignal( GDBusConnection* connection, const gchar* sender_name, const gchar* object_path, const gchar* interface_name, const gchar* signal_name, GVariant* parameters, gpointer user_data) { BaseCapturerPipeWire* that = static_cast(user_data); RTC_DCHECK(that); RTC_LOG(LS_INFO) << "Received response for the screen cast session subscription."; guint32 portal_response; GVariant* response_data; g_variant_get(parameters, "(u@a{sv})", &portal_response, &response_data); g_variant_lookup(response_data, "session_handle", "s", &that->session_handle_); g_variant_unref(response_data); if (!that->session_handle_ || portal_response) { RTC_LOG(LS_ERROR) << "Failed to request the screen cast session subscription."; that->portal_init_failed_ = true; return; } that->SourcesRequest(); } void BaseCapturerPipeWire::SourcesRequest() { GVariantBuilder builder; gchar* variant_string; g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); // We want to record monitor content. g_variant_builder_add(&builder, "{sv}", "types", g_variant_new_uint32(capture_source_type_)); // We don't want to allow selection of multiple sources. g_variant_builder_add(&builder, "{sv}", "multiple", g_variant_new_boolean(false)); variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT)); g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(variant_string)); sources_handle_ = PrepareSignalHandle(connection_, variant_string); sources_request_signal_id_ = SetupRequestResponseSignal( sources_handle_, OnSourcesRequestResponseSignal); g_free(variant_string); RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session."; g_dbus_proxy_call( proxy_, "SelectSources", g_variant_new("(oa{sv})", session_handle_, &builder), G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_, reinterpret_cast(OnSourcesRequested), this); } // static void BaseCapturerPipeWire::OnSourcesRequested(GDBusProxy *proxy, GAsyncResult* result, gpointer user_data) { BaseCapturerPipeWire* that = static_cast(user_data); RTC_DCHECK(that); GError* error = nullptr; GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error); if (!variant) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; RTC_LOG(LS_ERROR) << "Failed to request the sources: " << error->message; g_error_free(error); that->portal_init_failed_ = true; return; } RTC_LOG(LS_INFO) << "Sources requested from the screen cast session."; gchar* handle = nullptr; g_variant_get_child(variant, 0, "o", &handle); g_variant_unref(variant); if (!handle) { RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session."; if (that->sources_request_signal_id_) { g_dbus_connection_signal_unsubscribe(that->connection_, that->sources_request_signal_id_); that->sources_request_signal_id_ = 0; } that->portal_init_failed_ = true; return; } g_free(handle); RTC_LOG(LS_INFO) << "Subscribed to sources signal."; } // static void BaseCapturerPipeWire::OnSourcesRequestResponseSignal( GDBusConnection* connection, const gchar* sender_name, const gchar* object_path, const gchar* interface_name, const gchar* signal_name, GVariant* parameters, gpointer user_data) { BaseCapturerPipeWire* that = static_cast(user_data); RTC_DCHECK(that); RTC_LOG(LS_INFO) << "Received sources signal from session."; guint32 portal_response; g_variant_get(parameters, "(u@a{sv})", &portal_response, nullptr); if (portal_response) { RTC_LOG(LS_ERROR) << "Failed to select sources for the screen cast session."; that->portal_init_failed_ = true; return; } that->StartRequest(); } void BaseCapturerPipeWire::StartRequest() { GVariantBuilder builder; gchar* variant_string; g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT)); g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(variant_string)); start_handle_ = PrepareSignalHandle(connection_, variant_string); start_request_signal_id_ = SetupRequestResponseSignal(start_handle_, OnStartRequestResponseSignal); g_free(variant_string); // "Identifier for the application window", this is Wayland, so not "x11:...". const gchar parent_window[] = ""; RTC_LOG(LS_INFO) << "Starting the screen cast session."; g_dbus_proxy_call( proxy_, "Start", g_variant_new("(osa{sv})", session_handle_, parent_window, &builder), G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_, reinterpret_cast(OnStartRequested), this); } // static void BaseCapturerPipeWire::OnStartRequested(GDBusProxy *proxy, GAsyncResult* result, gpointer user_data) { BaseCapturerPipeWire* that = static_cast(user_data); RTC_DCHECK(that); GError* error = nullptr; GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error); if (!variant) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; RTC_LOG(LS_ERROR) << "Failed to start the screen cast session: " << error->message; g_error_free(error); that->portal_init_failed_ = true; return; } RTC_LOG(LS_INFO) << "Initializing the start of the screen cast session."; gchar* handle = nullptr; g_variant_get_child(variant, 0, "o", &handle); g_variant_unref(variant); if (!handle) { RTC_LOG(LS_ERROR) << "Failed to initialize the start of the screen cast session."; if (that->start_request_signal_id_) { g_dbus_connection_signal_unsubscribe(that->connection_, that->start_request_signal_id_); that->start_request_signal_id_ = 0; } that->portal_init_failed_ = true; return; } g_free(handle); RTC_LOG(LS_INFO) << "Subscribed to the start signal."; } // static void BaseCapturerPipeWire::OnStartRequestResponseSignal( GDBusConnection* connection, const gchar* sender_name, const gchar* object_path, const gchar* interface_name, const gchar* signal_name, GVariant* parameters, gpointer user_data) { BaseCapturerPipeWire* that = static_cast(user_data); RTC_DCHECK(that); RTC_LOG(LS_INFO) << "Start signal received."; guint32 portal_response; GVariant* response_data; GVariantIter* iter = nullptr; g_variant_get(parameters, "(u@a{sv})", &portal_response, &response_data); if (portal_response || !response_data) { RTC_LOG(LS_ERROR) << "Failed to start the screen cast session."; that->portal_init_failed_ = true; return; } // Array of PipeWire streams. See // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml // documentation for . if (g_variant_lookup(response_data, "streams", "a(ua{sv})", &iter)) { GVariant* variant; while (g_variant_iter_next(iter, "@(ua{sv})", &variant)) { guint32 stream_id; gint32 width; gint32 height; GVariant* options; g_variant_get(variant, "(u@a{sv})", &stream_id, &options); RTC_DCHECK(options != nullptr); g_variant_lookup(options, "size", "(ii)", &width, &height); that->desktop_size_.set(width, height); g_variant_unref(options); g_variant_unref(variant); } } g_variant_iter_free(iter); g_variant_unref(response_data); that->OpenPipeWireRemote(); } void BaseCapturerPipeWire::OpenPipeWireRemote() { GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); RTC_LOG(LS_INFO) << "Opening the PipeWire remote."; g_dbus_proxy_call_with_unix_fd_list( proxy_, "OpenPipeWireRemote", g_variant_new("(oa{sv})", session_handle_, &builder), G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*fd_list=*/nullptr, cancellable_, reinterpret_cast(OnOpenPipeWireRemoteRequested), this); } // static void BaseCapturerPipeWire::OnOpenPipeWireRemoteRequested( GDBusProxy *proxy, GAsyncResult* result, gpointer user_data) { BaseCapturerPipeWire* that = static_cast(user_data); RTC_DCHECK(that); GError* error = nullptr; GUnixFDList* outlist = nullptr; GVariant* variant = g_dbus_proxy_call_with_unix_fd_list_finish( proxy, &outlist, result, &error); if (!variant) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; RTC_LOG(LS_ERROR) << "Failed to open the PipeWire remote: " << error->message; g_error_free(error); that->portal_init_failed_ = true; return; } gint32 index; g_variant_get(variant, "(h)", &index); if ((that->pw_fd_ = g_unix_fd_list_get(outlist, index, &error)) == -1) { RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: " << error->message; g_error_free(error); g_variant_unref(variant); that->portal_init_failed_ = true; return; } g_variant_unref(variant); g_object_unref(outlist); that->InitPipeWire(); } void BaseCapturerPipeWire::Start(Callback* callback) { RTC_DCHECK(!callback_); RTC_DCHECK(callback); InitPortal(); callback_ = callback; } void BaseCapturerPipeWire::CaptureFrame() { if (portal_init_failed_) { callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); return; } if (!current_frame_) { callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); return; } std::unique_ptr result(new BasicDesktopFrame(desktop_size_)); result->CopyPixelsFrom( current_frame_, (desktop_size_.width() * kBytesPerPixel), DesktopRect::MakeWH(desktop_size_.width(), desktop_size_.height())); if (!result) { callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); return; } // TODO(julien.isorce): http://crbug.com/945468. Set the icc profile on the // frame, see ScreenCapturerX11::CaptureFrame. callback_->OnCaptureResult(Result::SUCCESS, std::move(result)); } bool BaseCapturerPipeWire::GetSourceList(SourceList* sources) { RTC_DCHECK(sources->size() == 0); // List of available screens is already presented by the xdg-desktop-portal. // But we have to add an empty source as the code expects it. sources->push_back({0}); return true; } bool BaseCapturerPipeWire::SelectSource(SourceId id) { // Screen selection is handled by the xdg-desktop-portal. return true; } } // namespace webrtc