1 /*
2  *  Copyright 2018 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "modules/desktop_capture/linux/base_capturer_pipewire.h"
12 
13 #include <gio/gunixfdlist.h>
14 #include <glib-object.h>
15 
16 #include <spa/param/format-utils.h>
17 #include <spa/param/props.h>
18 
19 #include <sys/mman.h>
20 #include <sys/ioctl.h>
21 #include <sys/syscall.h>
22 
23 #include <memory>
24 #include <utility>
25 
26 #include "modules/desktop_capture/desktop_capture_options.h"
27 #include "modules/desktop_capture/desktop_capturer.h"
28 #include "rtc_base/checks.h"
29 #include "rtc_base/logging.h"
30 
31 namespace webrtc {
32 
33 const char kDesktopBusName[] = "org.freedesktop.portal.Desktop";
34 const char kDesktopObjectPath[] = "/org/freedesktop/portal/desktop";
35 const char kDesktopRequestObjectPath[] =
36     "/org/freedesktop/portal/desktop/request";
37 const char kSessionInterfaceName[] = "org.freedesktop.portal.Session";
38 const char kRequestInterfaceName[] = "org.freedesktop.portal.Request";
39 const char kScreenCastInterfaceName[] = "org.freedesktop.portal.ScreenCast";
40 
41 // static
42 struct dma_buf_sync {
43   uint64_t flags;
44 };
45 #define DMA_BUF_SYNC_READ      (1 << 0)
46 #define DMA_BUF_SYNC_START     (0 << 2)
47 #define DMA_BUF_SYNC_END       (1 << 2)
48 #define DMA_BUF_BASE           'b'
49 #define DMA_BUF_IOCTL_SYNC     _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync)
50 
SyncDmaBuf(int fd,uint64_t start_or_end)51 static void SyncDmaBuf(int fd, uint64_t start_or_end) {
52   struct dma_buf_sync sync = { 0 };
53 
54   sync.flags = start_or_end | DMA_BUF_SYNC_READ;
55 
56   while(true) {
57     int ret;
58     ret = ioctl (fd, DMA_BUF_IOCTL_SYNC, &sync);
59     if (ret == -1 && errno == EINTR) {
60       continue;
61     } else if (ret == -1) {
62       RTC_LOG(LS_ERROR) << "Failed to synchronize DMA buffer: " << g_strerror(errno);
63       break;
64     } else {
65       break;
66     }
67   }
68 }
69 
70 // static
OnCoreError(void * data,uint32_t id,int seq,int res,const char * message)71 void BaseCapturerPipeWire::OnCoreError(void *data,
72                                        uint32_t id,
73                                        int seq,
74                                        int res,
75                                        const char *message) {
76   RTC_LOG(LS_ERROR) << "core error: " << message;
77 }
78 
79 // static
OnStreamStateChanged(void * data,pw_stream_state old_state,pw_stream_state state,const char * error_message)80 void BaseCapturerPipeWire::OnStreamStateChanged(void* data,
81                                                 pw_stream_state old_state,
82                                                 pw_stream_state state,
83                                                 const char* error_message) {
84   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
85   RTC_DCHECK(that);
86 
87   switch (state) {
88     case PW_STREAM_STATE_ERROR:
89       RTC_LOG(LS_ERROR) << "PipeWire stream state error: " << error_message;
90       break;
91     case PW_STREAM_STATE_PAUSED:
92     case PW_STREAM_STATE_STREAMING:
93     case PW_STREAM_STATE_UNCONNECTED:
94     case PW_STREAM_STATE_CONNECTING:
95       break;
96   }
97 }
98 
99 // static
OnStreamParamChanged(void * data,uint32_t id,const struct spa_pod * format)100 void BaseCapturerPipeWire::OnStreamParamChanged(void *data, uint32_t id,
101                                                 const struct spa_pod *format) {
102   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
103   RTC_DCHECK(that);
104 
105   RTC_LOG(LS_INFO) << "PipeWire stream param changed.";
106 
107   if (!format || id != SPA_PARAM_Format) {
108     return;
109   }
110 
111   spa_format_video_raw_parse(format, &that->spa_video_format_);
112 
113   auto width = that->spa_video_format_.size.width;
114   auto height = that->spa_video_format_.size.height;
115   auto stride = SPA_ROUND_UP_N(width * kBytesPerPixel, 4);
116   auto size = height * stride;
117 
118   that->desktop_size_ = DesktopSize(width, height);
119 
120   uint8_t buffer[1024] = {};
121   auto builder = spa_pod_builder{buffer, sizeof(buffer)};
122 
123   // Setup buffers and meta header for new format.
124   const struct spa_pod* params[3];
125   params[0] = reinterpret_cast<spa_pod *>(spa_pod_builder_add_object(&builder,
126               SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
127               SPA_PARAM_BUFFERS_dataType, SPA_POD_Int((1<<SPA_DATA_MemPtr) |
128                                                       (1<<SPA_DATA_MemFd)),
129               SPA_PARAM_BUFFERS_size, SPA_POD_Int(size),
130               SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride),
131               SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 1, 32)));
132   params[1] = reinterpret_cast<spa_pod *>(spa_pod_builder_add_object(&builder,
133               SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
134               SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
135               SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))));
136   params[2] = reinterpret_cast<spa_pod *>(spa_pod_builder_add_object(&builder,
137               SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
138               SPA_PARAM_META_type, SPA_POD_Id (SPA_META_VideoCrop),
139               SPA_PARAM_META_size, SPA_POD_Int (sizeof(struct spa_meta_region))));
140   pw_stream_update_params(that->pw_stream_, params, 3);
141 }
142 
143 // static
OnStreamProcess(void * data)144 void BaseCapturerPipeWire::OnStreamProcess(void* data) {
145   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
146   RTC_DCHECK(that);
147 
148   struct pw_buffer *next_buffer;
149   struct pw_buffer *buffer = nullptr;
150 
151   next_buffer = pw_stream_dequeue_buffer(that->pw_stream_);
152   while (next_buffer) {
153     buffer = next_buffer;
154     next_buffer = pw_stream_dequeue_buffer(that->pw_stream_);
155 
156     if (next_buffer) {
157       pw_stream_queue_buffer (that->pw_stream_, buffer);
158     }
159   }
160 
161   if (!buffer) {
162     return;
163   }
164 
165   that->HandleBuffer(buffer);
166 
167   pw_stream_queue_buffer(that->pw_stream_, buffer);
168 }
169 
BaseCapturerPipeWire(CaptureSourceType source_type)170 BaseCapturerPipeWire::BaseCapturerPipeWire(CaptureSourceType source_type)
171     : capture_source_type_(source_type) {}
172 
~BaseCapturerPipeWire()173 BaseCapturerPipeWire::~BaseCapturerPipeWire() {
174   if (pw_main_loop_) {
175     pw_thread_loop_stop(pw_main_loop_);
176   }
177 
178   if (pw_stream_) {
179     pw_stream_destroy(pw_stream_);
180   }
181 
182   if (pw_core_) {
183     pw_core_disconnect(pw_core_);
184   }
185 
186   if (pw_context_) {
187     pw_context_destroy(pw_context_);
188   }
189 
190   if (pw_main_loop_) {
191     pw_thread_loop_destroy(pw_main_loop_);
192   }
193 
194   if (start_request_signal_id_) {
195     g_dbus_connection_signal_unsubscribe(connection_, start_request_signal_id_);
196   }
197   if (sources_request_signal_id_) {
198     g_dbus_connection_signal_unsubscribe(connection_,
199                                          sources_request_signal_id_);
200   }
201   if (session_request_signal_id_) {
202     g_dbus_connection_signal_unsubscribe(connection_,
203                                          session_request_signal_id_);
204   }
205 
206   if (session_handle_) {
207     GDBusMessage* message = g_dbus_message_new_method_call(
208         kDesktopBusName, session_handle_, kSessionInterfaceName, "Close");
209     if (message) {
210       GError* error = nullptr;
211       g_dbus_connection_send_message(connection_, message,
212                                      G_DBUS_SEND_MESSAGE_FLAGS_NONE,
213                                      /*out_serial=*/nullptr, &error);
214       if (error) {
215         RTC_LOG(LS_ERROR) << "Failed to close the session: " << error->message;
216         g_error_free(error);
217       }
218       g_object_unref(message);
219     }
220   }
221 
222   g_free(start_handle_);
223   g_free(sources_handle_);
224   g_free(session_handle_);
225   g_free(portal_handle_);
226 
227   if (cancellable_) {
228     g_cancellable_cancel(cancellable_);
229     g_object_unref(cancellable_);
230     cancellable_ = nullptr;
231   }
232 
233   if (proxy_) {
234     g_object_unref(proxy_);
235     proxy_ = nullptr;
236   }
237 }
238 
InitPortal()239 void BaseCapturerPipeWire::InitPortal() {
240   cancellable_ = g_cancellable_new();
241   g_dbus_proxy_new_for_bus(
242       G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, /*info=*/nullptr,
243       kDesktopBusName, kDesktopObjectPath, kScreenCastInterfaceName,
244       cancellable_,
245       reinterpret_cast<GAsyncReadyCallback>(OnProxyRequested), this);
246 }
247 
InitPipeWire()248 void BaseCapturerPipeWire::InitPipeWire() {
249   pipewire_init(/*argc=*/nullptr, /*argc=*/nullptr);
250 
251   pw_main_loop_ = pw_thread_loop_new("pipewire-main-loop", nullptr);
252   pw_context_ = pw_context_new(pw_thread_loop_get_loop(pw_main_loop_), nullptr, 0);
253   if (!pw_context_) {
254     RTC_LOG(LS_ERROR) << "Failed to create PipeWire context";
255     return;
256   }
257 
258   pw_core_ = pw_context_connect_fd(pw_context_, pw_fd_, nullptr, 0);
259   if (!pw_core_) {
260     RTC_LOG(LS_ERROR) << "Failed to connect PipeWire context";
261     return;
262   }
263 
264   // Initialize event handlers, remote end and stream-related.
265   pw_core_events_.version = PW_VERSION_CORE_EVENTS;
266   pw_core_events_.error = &OnCoreError;
267 
268   pw_stream_events_.version = PW_VERSION_STREAM_EVENTS;
269   pw_stream_events_.state_changed = &OnStreamStateChanged;
270   pw_stream_events_.param_changed = &OnStreamParamChanged;
271   pw_stream_events_.process = &OnStreamProcess;
272 
273   pw_core_add_listener(pw_core_, &spa_core_listener_, &pw_core_events_, this);
274 
275   pw_stream_ = CreateReceivingStream();
276   if (!pw_stream_) {
277     RTC_LOG(LS_ERROR) << "Failed to create PipeWire stream";
278     return;
279   }
280 
281   if (pw_thread_loop_start(pw_main_loop_) < 0) {
282     RTC_LOG(LS_ERROR) << "Failed to start main PipeWire loop";
283     portal_init_failed_ = true;
284   }
285 }
286 
CreateReceivingStream()287 pw_stream* BaseCapturerPipeWire::CreateReceivingStream() {
288   spa_rectangle pwMinScreenBounds = spa_rectangle{1, 1};
289   spa_rectangle pwMaxScreenBounds = spa_rectangle{UINT32_MAX, UINT32_MAX};
290 
291   auto stream = pw_stream_new(pw_core_, "webrtc-pipewire-stream", nullptr);
292 
293   if (!stream) {
294     RTC_LOG(LS_ERROR) << "Could not create receiving stream.";
295     return nullptr;
296   }
297 
298   uint8_t buffer[1024] = {};
299   const spa_pod* params[2];
300   spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer, sizeof (buffer));
301 
302   params[0] = reinterpret_cast<spa_pod *>(spa_pod_builder_add_object(&builder,
303               SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
304               SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
305               SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
306               SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(5, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_RGBA,
307                                                                  SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRA),
308               SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&pwMinScreenBounds,
309                                                                     &pwMinScreenBounds,
310                                                                     &pwMaxScreenBounds),
311               SPA_FORMAT_VIDEO_modifier, SPA_POD_Long(0),
312               0));
313   pw_stream_add_listener(stream, &spa_stream_listener_, &pw_stream_events_, this);
314 
315   if (pw_stream_connect(stream, PW_DIRECTION_INPUT, pw_stream_node_id_,
316       PW_STREAM_FLAG_AUTOCONNECT, params, 1) != 0) {
317     RTC_LOG(LS_ERROR) << "Could not connect receiving stream.";
318     portal_init_failed_ = true;
319   }
320 
321   return stream;
322 }
323 
SpaBufferUnmap(unsigned char * map,int map_size,bool IsDMABuf,int fd)324 static void SpaBufferUnmap(unsigned char *map, int map_size, bool IsDMABuf, int fd) {
325   if (map) {
326     if (IsDMABuf) {
327       SyncDmaBuf(fd, DMA_BUF_SYNC_END);
328     }
329     munmap(map, map_size);
330   }
331 }
332 
HandleBuffer(pw_buffer * buffer)333 void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) {
334   spa_buffer* spaBuffer = buffer->buffer;
335   uint8_t *map = nullptr;
336   uint8_t* src = nullptr;
337 
338   if (spaBuffer->datas[0].chunk->size == 0) {
339     RTC_LOG(LS_ERROR) << "Failed to get video stream: Zero size.";
340     return;
341   }
342 
343   switch (spaBuffer->datas[0].type) {
344     case SPA_DATA_MemFd:
345     case SPA_DATA_DmaBuf:
346       map = static_cast<uint8_t*>(mmap(
347           nullptr, spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
348           PROT_READ, MAP_PRIVATE, spaBuffer->datas[0].fd, 0));
349       if (map == MAP_FAILED) {
350         RTC_LOG(LS_ERROR) << "Failed to mmap memory: " << std::strerror(errno);
351         return;
352       }
353       if (spaBuffer->datas[0].type == SPA_DATA_DmaBuf) {
354         SyncDmaBuf(spaBuffer->datas[0].fd, DMA_BUF_SYNC_START);
355       }
356       src = SPA_MEMBER(map, spaBuffer->datas[0].mapoffset, uint8_t);
357       break;
358     case SPA_DATA_MemPtr:
359       map = nullptr;
360       src = static_cast<uint8_t*>(spaBuffer->datas[0].data);
361       break;
362     default:
363       return;
364   }
365 
366   if (!src) {
367     RTC_LOG(LS_ERROR) << "Failed to get video stream: Wrong data after mmap()";
368     SpaBufferUnmap(map,
369       spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
370       spaBuffer->datas[0].type == SPA_DATA_DmaBuf, spaBuffer->datas[0].fd);
371     return;
372   }
373 
374   struct spa_meta_region* video_metadata =
375     static_cast<struct spa_meta_region*>(
376        spa_buffer_find_meta_data(spaBuffer, SPA_META_VideoCrop, sizeof(*video_metadata)));
377 
378   // Video size from metada is bigger than an actual video stream size.
379   // The metadata are wrong or we should up-scale te video...in both cases
380   // just quit now.
381   if (video_metadata &&
382       (video_metadata->region.size.width > (uint32_t)desktop_size_.width() ||
383         video_metadata->region.size.height > (uint32_t)desktop_size_.height())) {
384     RTC_LOG(LS_ERROR) << "Stream metadata sizes are wrong!";
385     SpaBufferUnmap(map,
386       spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
387       spaBuffer->datas[0].type == SPA_DATA_DmaBuf, spaBuffer->datas[0].fd);
388     return;
389   }
390 
391   // Use video metada when video size from metadata is set and smaller than
392   // video stream size, so we need to adjust it.
393   video_metadata_use_ = (video_metadata &&
394       video_metadata->region.size.width != 0 &&
395         video_metadata->region.size.height != 0 &&
396           (video_metadata->region.size.width < (uint32_t)desktop_size_.width() ||
397             video_metadata->region.size.height < (uint32_t)desktop_size_.height()));
398 
399   DesktopSize video_size_prev = video_size_;
400     if (video_metadata_use_) {
401     video_size_ = DesktopSize(video_metadata->region.size.width,
402                               video_metadata->region.size.height);
403   } else {
404     video_size_ = desktop_size_;
405   }
406 
407   rtc::CritScope lock(&current_frame_lock_);
408   if (!current_frame_ || !video_size_.equals(video_size_prev)) {
409     current_frame_ =
410       std::make_unique<uint8_t[]>
411         (video_size_.width() * video_size_.height() * kBytesPerPixel);
412   }
413 
414   const int32_t dstStride = video_size_.width() * kBytesPerPixel;
415   const int32_t srcStride = spaBuffer->datas[0].chunk->stride;
416 
417   // Adjust source content based on metadata video position
418   if (video_metadata_use_ &&
419       (video_metadata->region.position.y + video_size_.height() <= desktop_size_.height())) {
420     src += srcStride * video_metadata->region.position.y;
421   }
422   const int xOffset =
423       video_metadata_use_ &&
424         (video_metadata->region.position.x + video_size_.width() <= desktop_size_.width())
425           ? video_metadata->region.position.x * kBytesPerPixel
426           : 0;
427 
428   uint8_t* dst = current_frame_.get();
429   for (int i = 0; i < video_size_.height(); ++i) {
430     // Adjust source content based on crop video position if needed
431     src += xOffset;
432     std::memcpy(dst, src, dstStride);
433     // If both sides decided to go with the RGBx format we need to convert it to
434     // BGRx to match color format expected by WebRTC.
435     if (spa_video_format_.format == SPA_VIDEO_FORMAT_RGBx ||
436         spa_video_format_.format == SPA_VIDEO_FORMAT_RGBA) {
437       ConvertRGBxToBGRx(dst, dstStride);
438     }
439     src += srcStride - xOffset;
440     dst += dstStride;
441   }
442 
443   SpaBufferUnmap(map,
444     spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
445     spaBuffer->datas[0].type == SPA_DATA_DmaBuf, spaBuffer->datas[0].fd);
446 }
447 
ConvertRGBxToBGRx(uint8_t * frame,uint32_t size)448 void BaseCapturerPipeWire::ConvertRGBxToBGRx(uint8_t* frame, uint32_t size) {
449   // Change color format for KDE KWin which uses RGBx and not BGRx
450   for (uint32_t i = 0; i < size; i += 4) {
451     uint8_t tempR = frame[i];
452     uint8_t tempB = frame[i + 2];
453     frame[i] = tempB;
454     frame[i + 2] = tempR;
455   }
456 }
457 
SetupRequestResponseSignal(const gchar * object_path,GDBusSignalCallback callback)458 guint BaseCapturerPipeWire::SetupRequestResponseSignal(
459     const gchar* object_path,
460     GDBusSignalCallback callback) {
461   return g_dbus_connection_signal_subscribe(
462       connection_, kDesktopBusName, kRequestInterfaceName, "Response",
463       object_path, /*arg0=*/nullptr, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
464       callback, this, /*user_data_free_func=*/nullptr);
465 }
466 
467 // static
OnProxyRequested(GObject *,GAsyncResult * result,gpointer user_data)468 void BaseCapturerPipeWire::OnProxyRequested(GObject* /*object*/,
469                                             GAsyncResult* result,
470                                             gpointer user_data) {
471   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
472   RTC_DCHECK(that);
473 
474   GError* error = nullptr;
475   GDBusProxy *proxy = g_dbus_proxy_new_finish(result, &error);
476   if (!proxy) {
477     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
478       return;
479     RTC_LOG(LS_ERROR) << "Failed to create a proxy for the screen cast portal: "
480                       << error->message;
481     g_error_free(error);
482     that->portal_init_failed_ = true;
483     return;
484   }
485   that->proxy_ = proxy;
486   that->connection_ = g_dbus_proxy_get_connection(that->proxy_);
487 
488   RTC_LOG(LS_INFO) << "Created proxy for the screen cast portal.";
489   that->SessionRequest();
490 }
491 
492 // static
PrepareSignalHandle(GDBusConnection * connection,const gchar * token)493 gchar* BaseCapturerPipeWire::PrepareSignalHandle(GDBusConnection* connection,
494                                                  const gchar* token) {
495   gchar* sender = g_strdup(g_dbus_connection_get_unique_name(connection) + 1);
496   for (int i = 0; sender[i]; i++) {
497     if (sender[i] == '.') {
498       sender[i] = '_';
499     }
500   }
501 
502   gchar* handle = g_strconcat(kDesktopRequestObjectPath, "/", sender, "/",
503                               token, /*end of varargs*/ nullptr);
504   g_free(sender);
505 
506   return handle;
507 }
508 
SessionRequest()509 void BaseCapturerPipeWire::SessionRequest() {
510   GVariantBuilder builder;
511   gchar* variant_string;
512 
513   g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
514   variant_string =
515       g_strdup_printf("webrtc_session%d", g_random_int_range(0, G_MAXINT));
516   g_variant_builder_add(&builder, "{sv}", "session_handle_token",
517                         g_variant_new_string(variant_string));
518   g_free(variant_string);
519   variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT));
520   g_variant_builder_add(&builder, "{sv}", "handle_token",
521                         g_variant_new_string(variant_string));
522 
523   portal_handle_ = PrepareSignalHandle(connection_, variant_string);
524   session_request_signal_id_ = SetupRequestResponseSignal(
525       portal_handle_, OnSessionRequestResponseSignal);
526   g_free(variant_string);
527 
528   RTC_LOG(LS_INFO) << "Screen cast session requested.";
529   g_dbus_proxy_call(
530       proxy_, "CreateSession", g_variant_new("(a{sv})", &builder),
531       G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
532       reinterpret_cast<GAsyncReadyCallback>(OnSessionRequested), this);
533 }
534 
535 // static
OnSessionRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)536 void BaseCapturerPipeWire::OnSessionRequested(GDBusProxy *proxy,
537                                               GAsyncResult* result,
538                                               gpointer user_data) {
539   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
540   RTC_DCHECK(that);
541 
542   GError* error = nullptr;
543   GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error);
544   if (!variant) {
545     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
546       return;
547     RTC_LOG(LS_ERROR) << "Failed to create a screen cast session: "
548                       << error->message;
549     g_error_free(error);
550     that->portal_init_failed_ = true;
551     return;
552   }
553   RTC_LOG(LS_INFO) << "Initializing the screen cast session.";
554 
555   gchar* handle = nullptr;
556   g_variant_get_child(variant, 0, "o", &handle);
557   g_variant_unref(variant);
558   if (!handle) {
559     RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session.";
560     if (that->session_request_signal_id_) {
561       g_dbus_connection_signal_unsubscribe(that->connection_,
562                                            that->session_request_signal_id_);
563       that->session_request_signal_id_ = 0;
564     }
565     that->portal_init_failed_ = true;
566     return;
567   }
568 
569   g_free(handle);
570 
571   RTC_LOG(LS_INFO) << "Subscribing to the screen cast session.";
572 }
573 
574 // static
OnSessionRequestResponseSignal(GDBusConnection * connection,const gchar * sender_name,const gchar * object_path,const gchar * interface_name,const gchar * signal_name,GVariant * parameters,gpointer user_data)575 void BaseCapturerPipeWire::OnSessionRequestResponseSignal(
576     GDBusConnection* connection,
577     const gchar* sender_name,
578     const gchar* object_path,
579     const gchar* interface_name,
580     const gchar* signal_name,
581     GVariant* parameters,
582     gpointer user_data) {
583   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
584   RTC_DCHECK(that);
585 
586   RTC_LOG(LS_INFO)
587       << "Received response for the screen cast session subscription.";
588 
589   guint32 portal_response;
590   GVariant* response_data;
591   g_variant_get(parameters, "(u@a{sv})", &portal_response, &response_data);
592   g_variant_lookup(response_data, "session_handle", "s",
593                    &that->session_handle_);
594   g_variant_unref(response_data);
595 
596   if (!that->session_handle_ || portal_response) {
597     RTC_LOG(LS_ERROR)
598         << "Failed to request the screen cast session subscription.";
599     that->portal_init_failed_ = true;
600     return;
601   }
602 
603   that->SourcesRequest();
604 }
605 
SourcesRequest()606 void BaseCapturerPipeWire::SourcesRequest() {
607   GVariantBuilder builder;
608   gchar* variant_string;
609 
610   g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
611   // We want to record monitor content.
612   g_variant_builder_add(&builder, "{sv}", "types",
613                         g_variant_new_uint32(capture_source_type_));
614   // We don't want to allow selection of multiple sources.
615   g_variant_builder_add(&builder, "{sv}", "multiple",
616                         g_variant_new_boolean(false));
617   variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT));
618   g_variant_builder_add(&builder, "{sv}", "handle_token",
619                         g_variant_new_string(variant_string));
620 
621   sources_handle_ = PrepareSignalHandle(connection_, variant_string);
622   sources_request_signal_id_ = SetupRequestResponseSignal(
623       sources_handle_, OnSourcesRequestResponseSignal);
624   g_free(variant_string);
625 
626   RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session.";
627   g_dbus_proxy_call(
628       proxy_, "SelectSources",
629       g_variant_new("(oa{sv})", session_handle_, &builder),
630       G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
631       reinterpret_cast<GAsyncReadyCallback>(OnSourcesRequested), this);
632 }
633 
634 // static
OnSourcesRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)635 void BaseCapturerPipeWire::OnSourcesRequested(GDBusProxy *proxy,
636                                               GAsyncResult* result,
637                                               gpointer user_data) {
638   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
639   RTC_DCHECK(that);
640 
641   GError* error = nullptr;
642   GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error);
643   if (!variant) {
644     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
645       return;
646     RTC_LOG(LS_ERROR) << "Failed to request the sources: " << error->message;
647     g_error_free(error);
648     that->portal_init_failed_ = true;
649     return;
650   }
651 
652   RTC_LOG(LS_INFO) << "Sources requested from the screen cast session.";
653 
654   gchar* handle = nullptr;
655   g_variant_get_child(variant, 0, "o", &handle);
656   g_variant_unref(variant);
657   if (!handle) {
658     RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session.";
659     if (that->sources_request_signal_id_) {
660       g_dbus_connection_signal_unsubscribe(that->connection_,
661                                            that->sources_request_signal_id_);
662       that->sources_request_signal_id_ = 0;
663     }
664     that->portal_init_failed_ = true;
665     return;
666   }
667 
668   g_free(handle);
669 
670   RTC_LOG(LS_INFO) << "Subscribed to sources signal.";
671 }
672 
673 // static
OnSourcesRequestResponseSignal(GDBusConnection * connection,const gchar * sender_name,const gchar * object_path,const gchar * interface_name,const gchar * signal_name,GVariant * parameters,gpointer user_data)674 void BaseCapturerPipeWire::OnSourcesRequestResponseSignal(
675     GDBusConnection* connection,
676     const gchar* sender_name,
677     const gchar* object_path,
678     const gchar* interface_name,
679     const gchar* signal_name,
680     GVariant* parameters,
681     gpointer user_data) {
682   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
683   RTC_DCHECK(that);
684 
685   RTC_LOG(LS_INFO) << "Received sources signal from session.";
686 
687   guint32 portal_response;
688   g_variant_get(parameters, "(u@a{sv})", &portal_response, nullptr);
689   if (portal_response) {
690     RTC_LOG(LS_ERROR)
691         << "Failed to select sources for the screen cast session.";
692     that->portal_init_failed_ = true;
693     return;
694   }
695 
696   that->StartRequest();
697 }
698 
StartRequest()699 void BaseCapturerPipeWire::StartRequest() {
700   GVariantBuilder builder;
701   gchar* variant_string;
702 
703   g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
704   variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT));
705   g_variant_builder_add(&builder, "{sv}", "handle_token",
706                         g_variant_new_string(variant_string));
707 
708   start_handle_ = PrepareSignalHandle(connection_, variant_string);
709   start_request_signal_id_ =
710       SetupRequestResponseSignal(start_handle_, OnStartRequestResponseSignal);
711   g_free(variant_string);
712 
713   // "Identifier for the application window", this is Wayland, so not "x11:...".
714   const gchar parent_window[] = "";
715 
716   RTC_LOG(LS_INFO) << "Starting the screen cast session.";
717   g_dbus_proxy_call(
718       proxy_, "Start",
719       g_variant_new("(osa{sv})", session_handle_, parent_window, &builder),
720       G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
721       reinterpret_cast<GAsyncReadyCallback>(OnStartRequested), this);
722 }
723 
724 // static
OnStartRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)725 void BaseCapturerPipeWire::OnStartRequested(GDBusProxy *proxy,
726                                             GAsyncResult* result,
727                                             gpointer user_data) {
728   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
729   RTC_DCHECK(that);
730 
731   GError* error = nullptr;
732   GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error);
733   if (!variant) {
734     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
735       return;
736     RTC_LOG(LS_ERROR) << "Failed to start the screen cast session: "
737                       << error->message;
738     g_error_free(error);
739     that->portal_init_failed_ = true;
740     return;
741   }
742 
743   RTC_LOG(LS_INFO) << "Initializing the start of the screen cast session.";
744 
745   gchar* handle = nullptr;
746   g_variant_get_child(variant, 0, "o", &handle);
747   g_variant_unref(variant);
748   if (!handle) {
749     RTC_LOG(LS_ERROR)
750         << "Failed to initialize the start of the screen cast session.";
751     if (that->start_request_signal_id_) {
752       g_dbus_connection_signal_unsubscribe(that->connection_,
753                                            that->start_request_signal_id_);
754       that->start_request_signal_id_ = 0;
755     }
756     that->portal_init_failed_ = true;
757     return;
758   }
759 
760   g_free(handle);
761 
762   RTC_LOG(LS_INFO) << "Subscribed to the start signal.";
763 }
764 
765 // static
OnStartRequestResponseSignal(GDBusConnection * connection,const gchar * sender_name,const gchar * object_path,const gchar * interface_name,const gchar * signal_name,GVariant * parameters,gpointer user_data)766 void BaseCapturerPipeWire::OnStartRequestResponseSignal(
767     GDBusConnection* connection,
768     const gchar* sender_name,
769     const gchar* object_path,
770     const gchar* interface_name,
771     const gchar* signal_name,
772     GVariant* parameters,
773     gpointer user_data) {
774   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
775   RTC_DCHECK(that);
776 
777   RTC_LOG(LS_INFO) << "Start signal received.";
778   guint32 portal_response;
779   GVariant* response_data;
780   GVariantIter* iter = nullptr;
781   g_variant_get(parameters, "(u@a{sv})", &portal_response, &response_data);
782   if (portal_response || !response_data) {
783     RTC_LOG(LS_ERROR) << "Failed to start the screen cast session.";
784     that->portal_init_failed_ = true;
785     return;
786   }
787 
788   // Array of PipeWire streams. See
789   // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml
790   // documentation for <method name="Start">.
791   if (g_variant_lookup(response_data, "streams", "a(ua{sv})", &iter)) {
792     GVariant* variant;
793 
794     while (g_variant_iter_next(iter, "@(ua{sv})", &variant)) {
795       guint32 stream_id;
796       GVariant* options;
797 
798       g_variant_get(variant, "(u@a{sv})", &stream_id, &options);
799       RTC_DCHECK(options != nullptr);
800 
801       that->pw_stream_node_id_ = stream_id;
802       g_variant_unref(options);
803       g_variant_unref(variant);
804     }
805   }
806   g_variant_iter_free(iter);
807   g_variant_unref(response_data);
808 
809   that->OpenPipeWireRemote();
810 }
811 
OpenPipeWireRemote()812 void BaseCapturerPipeWire::OpenPipeWireRemote() {
813   GVariantBuilder builder;
814   g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
815 
816   RTC_LOG(LS_INFO) << "Opening the PipeWire remote.";
817 
818   g_dbus_proxy_call_with_unix_fd_list(
819       proxy_, "OpenPipeWireRemote",
820       g_variant_new("(oa{sv})", session_handle_, &builder),
821       G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*fd_list=*/nullptr,
822       cancellable_,
823       reinterpret_cast<GAsyncReadyCallback>(OnOpenPipeWireRemoteRequested),
824       this);
825 }
826 
827 // static
OnOpenPipeWireRemoteRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)828 void BaseCapturerPipeWire::OnOpenPipeWireRemoteRequested(
829     GDBusProxy *proxy,
830     GAsyncResult* result,
831     gpointer user_data) {
832   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
833   RTC_DCHECK(that);
834 
835   GError* error = nullptr;
836   GUnixFDList* outlist = nullptr;
837   GVariant* variant = g_dbus_proxy_call_with_unix_fd_list_finish(
838       proxy, &outlist, result, &error);
839   if (!variant) {
840     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
841       return;
842     RTC_LOG(LS_ERROR) << "Failed to open the PipeWire remote: "
843                       << error->message;
844     g_error_free(error);
845     that->portal_init_failed_ = true;
846     return;
847   }
848 
849   gint32 index;
850   g_variant_get(variant, "(h)", &index);
851 
852   if ((that->pw_fd_ = g_unix_fd_list_get(outlist, index, &error)) == -1) {
853     RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: "
854                       << error->message;
855     g_error_free(error);
856     g_variant_unref(variant);
857     that->portal_init_failed_ = true;
858     return;
859   }
860 
861   g_variant_unref(variant);
862   g_object_unref(outlist);
863 
864   that->InitPipeWire();
865   RTC_LOG(LS_INFO) << "PipeWire remote opened.";
866 }
867 
Start(Callback * callback)868 void BaseCapturerPipeWire::Start(Callback* callback) {
869   RTC_DCHECK(!callback_);
870   RTC_DCHECK(callback);
871 
872   InitPortal();
873 
874   callback_ = callback;
875 }
876 
CaptureFrame()877 void BaseCapturerPipeWire::CaptureFrame() {
878   if (portal_init_failed_) {
879     callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
880     return;
881   }
882 
883   rtc::CritScope lock(&current_frame_lock_);
884   if (!current_frame_) {
885     callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
886     return;
887   }
888 
889   DesktopSize frame_size = desktop_size_;
890   if (video_metadata_use_) {
891     frame_size = video_size_;
892   }
893 
894   std::unique_ptr<DesktopFrame> result(new BasicDesktopFrame(frame_size));
895   result->CopyPixelsFrom(
896       current_frame_.get(), (frame_size.width() * kBytesPerPixel),
897       DesktopRect::MakeWH(frame_size.width(), frame_size.height()));
898   if (!result) {
899     callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
900     return;
901   }
902   callback_->OnCaptureResult(Result::SUCCESS, std::move(result));
903 }
904 
905 // Keep in sync with defines at browser/actors/WebRTCParent.jsm
906 // With PipeWire we can't select which system resource is shared so
907 // we don't create a window/screen list. Instead we place these constants
908 // as window name/id so frontend code can identify PipeWire backend
909 // and does not try to create screen/window preview.
910 
911 #define PIPEWIRE_ID   0xaffffff
912 #define PIPEWIRE_NAME "####_PIPEWIRE_PORTAL_####"
913 
GetSourceList(SourceList * sources)914 bool BaseCapturerPipeWire::GetSourceList(SourceList* sources) {
915   sources->push_back({PIPEWIRE_ID, 0, PIPEWIRE_NAME});
916   return true;
917 }
918 
SelectSource(SourceId id)919 bool BaseCapturerPipeWire::SelectSource(SourceId id) {
920   // Screen selection is handled by the xdg-desktop-portal.
921   return id == PIPEWIRE_ID;
922 }
923 
924 // static
925 std::unique_ptr<DesktopCapturer>
CreateRawScreenCapturer(const DesktopCaptureOptions & options)926 BaseCapturerPipeWire::CreateRawScreenCapturer(
927     const DesktopCaptureOptions& options) {
928   std::unique_ptr<BaseCapturerPipeWire> capturer =
929       std::make_unique<BaseCapturerPipeWire>(BaseCapturerPipeWire::CaptureSourceType::kAny);
930   return std::move(capturer);}
931 
932 // static
933 std::unique_ptr<DesktopCapturer>
CreateRawWindowCapturer(const DesktopCaptureOptions & options)934 BaseCapturerPipeWire::CreateRawWindowCapturer(
935     const DesktopCaptureOptions& options) {
936 
937   std::unique_ptr<BaseCapturerPipeWire> capturer =
938       std::make_unique<BaseCapturerPipeWire>(BaseCapturerPipeWire::CaptureSourceType::kAny);
939   return std::move(capturer);
940 }
941 
942 }  // namespace webrtc
943