• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include <spa/param/format-utils.h>
16 #include <spa/param/props.h>
17 #include <spa/param/video/raw-utils.h>
18 #include <spa/support/type-map.h>
19 
20 #include <memory>
21 #include <utility>
22 
23 #include "absl/memory/memory.h"
24 #include "modules/desktop_capture/desktop_capture_options.h"
25 #include "modules/desktop_capture/desktop_capturer.h"
26 #include "rtc_base/checks.h"
27 #include "rtc_base/logging.h"
28 
29 #if defined(WEBRTC_DLOPEN_PIPEWIRE)
30 #include "modules/desktop_capture/linux/pipewire_stubs.h"
31 
32 using modules_desktop_capture_linux::InitializeStubs;
33 using modules_desktop_capture_linux::kModulePipewire;
34 using modules_desktop_capture_linux::StubPathMap;
35 #endif  // defined(WEBRTC_DLOPEN_PIPEWIRE)
36 
37 namespace webrtc {
38 
39 const char kDesktopBusName[] = "org.freedesktop.portal.Desktop";
40 const char kDesktopObjectPath[] = "/org/freedesktop/portal/desktop";
41 const char kDesktopRequestObjectPath[] =
42     "/org/freedesktop/portal/desktop/request";
43 const char kSessionInterfaceName[] = "org.freedesktop.portal.Session";
44 const char kRequestInterfaceName[] = "org.freedesktop.portal.Request";
45 const char kScreenCastInterfaceName[] = "org.freedesktop.portal.ScreenCast";
46 
47 const int kBytesPerPixel = 4;
48 
49 #if defined(WEBRTC_DLOPEN_PIPEWIRE)
50 const char kPipeWireLib[] = "libpipewire-0.2.so.1";
51 #endif
52 
53 // static
OnStateChanged(void * data,pw_remote_state old_state,pw_remote_state state,const char * error_message)54 void BaseCapturerPipeWire::OnStateChanged(void* data,
55                                           pw_remote_state old_state,
56                                           pw_remote_state state,
57                                           const char* error_message) {
58   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
59   RTC_DCHECK(that);
60 
61   switch (state) {
62     case PW_REMOTE_STATE_ERROR:
63       RTC_LOG(LS_ERROR) << "PipeWire remote state error: " << error_message;
64       break;
65     case PW_REMOTE_STATE_CONNECTED:
66       RTC_LOG(LS_INFO) << "PipeWire remote state: connected.";
67       that->CreateReceivingStream();
68       break;
69     case PW_REMOTE_STATE_CONNECTING:
70       RTC_LOG(LS_INFO) << "PipeWire remote state: connecting.";
71       break;
72     case PW_REMOTE_STATE_UNCONNECTED:
73       RTC_LOG(LS_INFO) << "PipeWire remote state: unconnected.";
74       break;
75   }
76 }
77 
78 // static
OnStreamStateChanged(void * data,pw_stream_state old_state,pw_stream_state state,const char * error_message)79 void BaseCapturerPipeWire::OnStreamStateChanged(void* data,
80                                                 pw_stream_state old_state,
81                                                 pw_stream_state state,
82                                                 const char* error_message) {
83   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
84   RTC_DCHECK(that);
85 
86   switch (state) {
87     case PW_STREAM_STATE_ERROR:
88       RTC_LOG(LS_ERROR) << "PipeWire stream state error: " << error_message;
89       break;
90     case PW_STREAM_STATE_CONFIGURE:
91       pw_stream_set_active(that->pw_stream_, true);
92       break;
93     case PW_STREAM_STATE_UNCONNECTED:
94     case PW_STREAM_STATE_CONNECTING:
95     case PW_STREAM_STATE_READY:
96     case PW_STREAM_STATE_PAUSED:
97     case PW_STREAM_STATE_STREAMING:
98       break;
99   }
100 }
101 
102 // static
OnStreamFormatChanged(void * data,const struct spa_pod * format)103 void BaseCapturerPipeWire::OnStreamFormatChanged(void* data,
104                                                  const struct spa_pod* format) {
105   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
106   RTC_DCHECK(that);
107 
108   RTC_LOG(LS_INFO) << "PipeWire stream format changed.";
109 
110   if (!format) {
111     pw_stream_finish_format(that->pw_stream_, /*res=*/0, /*params=*/nullptr,
112                             /*n_params=*/0);
113     return;
114   }
115 
116   that->spa_video_format_ = new spa_video_info_raw();
117   spa_format_video_raw_parse(format, that->spa_video_format_,
118                              &that->pw_type_->format_video);
119 
120   auto width = that->spa_video_format_->size.width;
121   auto height = that->spa_video_format_->size.height;
122   auto stride = SPA_ROUND_UP_N(width * kBytesPerPixel, 4);
123   auto size = height * stride;
124 
125   uint8_t buffer[1024] = {};
126   auto builder = spa_pod_builder{buffer, sizeof(buffer)};
127 
128   // Setup buffers and meta header for new format.
129   const struct spa_pod* params[2];
130   params[0] = reinterpret_cast<spa_pod*>(spa_pod_builder_object(
131       &builder,
132       // id to enumerate buffer requirements
133       that->pw_core_type_->param.idBuffers,
134       that->pw_core_type_->param_buffers.Buffers,
135       // Size: specified as integer (i) and set to specified size
136       ":", that->pw_core_type_->param_buffers.size, "i", size,
137       // Stride: specified as integer (i) and set to specified stride
138       ":", that->pw_core_type_->param_buffers.stride, "i", stride,
139       // Buffers: specifies how many buffers we want to deal with, set as
140       // integer (i) where preferred number is 8, then allowed number is defined
141       // as range (r) from min and max values and it is undecided (u) to allow
142       // negotiation
143       ":", that->pw_core_type_->param_buffers.buffers, "iru", 8,
144       SPA_POD_PROP_MIN_MAX(1, 32),
145       // Align: memory alignment of the buffer, set as integer (i) to specified
146       // value
147       ":", that->pw_core_type_->param_buffers.align, "i", 16));
148   params[1] = reinterpret_cast<spa_pod*>(spa_pod_builder_object(
149       &builder,
150       // id to enumerate supported metadata
151       that->pw_core_type_->param.idMeta, that->pw_core_type_->param_meta.Meta,
152       // Type: specified as id or enum (I)
153       ":", that->pw_core_type_->param_meta.type, "I",
154       that->pw_core_type_->meta.Header,
155       // Size: size of the metadata, specified as integer (i)
156       ":", that->pw_core_type_->param_meta.size, "i",
157       sizeof(struct spa_meta_header)));
158 
159   pw_stream_finish_format(that->pw_stream_, /*res=*/0, params, /*n_params=*/2);
160 }
161 
162 // static
OnStreamProcess(void * data)163 void BaseCapturerPipeWire::OnStreamProcess(void* data) {
164   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
165   RTC_DCHECK(that);
166 
167   pw_buffer* buf = nullptr;
168 
169   if (!(buf = pw_stream_dequeue_buffer(that->pw_stream_))) {
170     return;
171   }
172 
173   that->HandleBuffer(buf);
174 
175   pw_stream_queue_buffer(that->pw_stream_, buf);
176 }
177 
BaseCapturerPipeWire(CaptureSourceType source_type)178 BaseCapturerPipeWire::BaseCapturerPipeWire(CaptureSourceType source_type)
179     : capture_source_type_(source_type) {}
180 
~BaseCapturerPipeWire()181 BaseCapturerPipeWire::~BaseCapturerPipeWire() {
182   if (pw_main_loop_) {
183     pw_thread_loop_stop(pw_main_loop_);
184   }
185 
186   if (pw_type_) {
187     delete pw_type_;
188   }
189 
190   if (spa_video_format_) {
191     delete spa_video_format_;
192   }
193 
194   if (pw_stream_) {
195     pw_stream_destroy(pw_stream_);
196   }
197 
198   if (pw_remote_) {
199     pw_remote_destroy(pw_remote_);
200   }
201 
202   if (pw_core_) {
203     pw_core_destroy(pw_core_);
204   }
205 
206   if (pw_main_loop_) {
207     pw_thread_loop_destroy(pw_main_loop_);
208   }
209 
210   if (pw_loop_) {
211     pw_loop_destroy(pw_loop_);
212   }
213 
214   if (current_frame_) {
215     free(current_frame_);
216   }
217 
218   if (start_request_signal_id_) {
219     g_dbus_connection_signal_unsubscribe(connection_, start_request_signal_id_);
220   }
221   if (sources_request_signal_id_) {
222     g_dbus_connection_signal_unsubscribe(connection_,
223                                          sources_request_signal_id_);
224   }
225   if (session_request_signal_id_) {
226     g_dbus_connection_signal_unsubscribe(connection_,
227                                          session_request_signal_id_);
228   }
229 
230   if (session_handle_) {
231     GDBusMessage* message = g_dbus_message_new_method_call(
232         kDesktopBusName, session_handle_, kSessionInterfaceName, "Close");
233     if (message) {
234       GError* error = nullptr;
235       g_dbus_connection_send_message(connection_, message,
236                                      G_DBUS_SEND_MESSAGE_FLAGS_NONE,
237                                      /*out_serial=*/nullptr, &error);
238       if (error) {
239         RTC_LOG(LS_ERROR) << "Failed to close the session: " << error->message;
240         g_error_free(error);
241       }
242       g_object_unref(message);
243     }
244   }
245 
246   g_free(start_handle_);
247   g_free(sources_handle_);
248   g_free(session_handle_);
249   g_free(portal_handle_);
250 
251   if (cancellable_) {
252     g_cancellable_cancel(cancellable_);
253     g_object_unref(cancellable_);
254     cancellable_ = nullptr;
255   }
256 
257   if (proxy_) {
258     g_object_unref(proxy_);
259     proxy_ = nullptr;
260   }
261 }
262 
InitPortal()263 void BaseCapturerPipeWire::InitPortal() {
264   cancellable_ = g_cancellable_new();
265   g_dbus_proxy_new_for_bus(
266       G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, /*info=*/nullptr,
267       kDesktopBusName, kDesktopObjectPath, kScreenCastInterfaceName,
268       cancellable_,
269       reinterpret_cast<GAsyncReadyCallback>(OnProxyRequested), this);
270 }
271 
InitPipeWire()272 void BaseCapturerPipeWire::InitPipeWire() {
273 #if defined(WEBRTC_DLOPEN_PIPEWIRE)
274   StubPathMap paths;
275 
276   // Check if the PipeWire library is available.
277   paths[kModulePipewire].push_back(kPipeWireLib);
278   if (!InitializeStubs(paths)) {
279     RTC_LOG(LS_ERROR) << "Failed to load the PipeWire library and symbols.";
280     portal_init_failed_ = true;
281     return;
282   }
283 #endif  // defined(WEBRTC_DLOPEN_PIPEWIRE)
284 
285   pw_init(/*argc=*/nullptr, /*argc=*/nullptr);
286 
287   pw_loop_ = pw_loop_new(/*properties=*/nullptr);
288   pw_main_loop_ = pw_thread_loop_new(pw_loop_, "pipewire-main-loop");
289 
290   pw_core_ = pw_core_new(pw_loop_, /*properties=*/nullptr);
291   pw_core_type_ = pw_core_get_type(pw_core_);
292   pw_remote_ = pw_remote_new(pw_core_, nullptr, /*user_data_size=*/0);
293 
294   InitPipeWireTypes();
295 
296   // Initialize event handlers, remote end and stream-related.
297   pw_remote_events_.version = PW_VERSION_REMOTE_EVENTS;
298   pw_remote_events_.state_changed = &OnStateChanged;
299 
300   pw_stream_events_.version = PW_VERSION_STREAM_EVENTS;
301   pw_stream_events_.state_changed = &OnStreamStateChanged;
302   pw_stream_events_.format_changed = &OnStreamFormatChanged;
303   pw_stream_events_.process = &OnStreamProcess;
304 
305   pw_remote_add_listener(pw_remote_, &spa_remote_listener_, &pw_remote_events_,
306                          this);
307   pw_remote_connect_fd(pw_remote_, pw_fd_);
308 
309   if (pw_thread_loop_start(pw_main_loop_) < 0) {
310     RTC_LOG(LS_ERROR) << "Failed to start main PipeWire loop";
311     portal_init_failed_ = true;
312   }
313 
314   RTC_LOG(LS_INFO) << "PipeWire remote opened.";
315 }
316 
InitPipeWireTypes()317 void BaseCapturerPipeWire::InitPipeWireTypes() {
318   spa_type_map* map = pw_core_type_->map;
319   pw_type_ = new PipeWireType();
320 
321   spa_type_media_type_map(map, &pw_type_->media_type);
322   spa_type_media_subtype_map(map, &pw_type_->media_subtype);
323   spa_type_format_video_map(map, &pw_type_->format_video);
324   spa_type_video_format_map(map, &pw_type_->video_format);
325 }
326 
CreateReceivingStream()327 void BaseCapturerPipeWire::CreateReceivingStream() {
328   spa_rectangle pwMinScreenBounds = spa_rectangle{1, 1};
329   spa_rectangle pwScreenBounds =
330       spa_rectangle{static_cast<uint32_t>(desktop_size_.width()),
331                     static_cast<uint32_t>(desktop_size_.height())};
332 
333   spa_fraction pwFrameRateMin = spa_fraction{0, 1};
334   spa_fraction pwFrameRateMax = spa_fraction{60, 1};
335 
336   pw_properties* reuseProps =
337       pw_properties_new_string("pipewire.client.reuse=1");
338   pw_stream_ = pw_stream_new(pw_remote_, "webrtc-consume-stream", reuseProps);
339 
340   uint8_t buffer[1024] = {};
341   const spa_pod* params[1];
342   spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
343   params[0] = reinterpret_cast<spa_pod*>(spa_pod_builder_object(
344       &builder,
345       // id to enumerate formats
346       pw_core_type_->param.idEnumFormat, pw_core_type_->spa_format, "I",
347       pw_type_->media_type.video, "I", pw_type_->media_subtype.raw,
348       // Video format: specified as id or enum (I), preferred format is BGRx,
349       // then allowed formats are enumerated (e) and the format is undecided (u)
350       // to allow negotiation
351       ":", pw_type_->format_video.format, "Ieu", pw_type_->video_format.BGRx,
352       SPA_POD_PROP_ENUM(2, pw_type_->video_format.RGBx,
353                         pw_type_->video_format.BGRx),
354       // Video size: specified as rectangle (R), preferred size is specified as
355       // first parameter, then allowed size is defined as range (r) from min and
356       // max values and the format is undecided (u) to allow negotiation
357       ":", pw_type_->format_video.size, "Rru", &pwScreenBounds, 2,
358       &pwMinScreenBounds, &pwScreenBounds,
359       // Frame rate: specified as fraction (F) and set to minimum frame rate
360       // value
361       ":", pw_type_->format_video.framerate, "F", &pwFrameRateMin,
362       // Max frame rate: specified as fraction (F), preferred frame rate is set
363       // to maximum value, then allowed frame rate is defined as range (r) from
364       // min and max values and it is undecided (u) to allow negotiation
365       ":", pw_type_->format_video.max_framerate, "Fru", &pwFrameRateMax, 2,
366       &pwFrameRateMin, &pwFrameRateMax));
367 
368   pw_stream_add_listener(pw_stream_, &spa_stream_listener_, &pw_stream_events_,
369                          this);
370   pw_stream_flags flags = static_cast<pw_stream_flags>(
371       PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE |
372       PW_STREAM_FLAG_MAP_BUFFERS);
373   if (pw_stream_connect(pw_stream_, PW_DIRECTION_INPUT, /*port_path=*/nullptr,
374                         flags, params,
375                         /*n_params=*/1) != 0) {
376     RTC_LOG(LS_ERROR) << "Could not connect receiving stream.";
377     portal_init_failed_ = true;
378     return;
379   }
380 }
381 
HandleBuffer(pw_buffer * buffer)382 void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) {
383   spa_buffer* spaBuffer = buffer->buffer;
384   void* src = nullptr;
385 
386   if (!(src = spaBuffer->datas[0].data)) {
387     return;
388   }
389 
390   uint32_t maxSize = spaBuffer->datas[0].maxsize;
391   int32_t srcStride = spaBuffer->datas[0].chunk->stride;
392   if (srcStride != (desktop_size_.width() * kBytesPerPixel)) {
393     RTC_LOG(LS_ERROR) << "Got buffer with stride different from screen stride: "
394                       << srcStride
395                       << " != " << (desktop_size_.width() * kBytesPerPixel);
396     portal_init_failed_ = true;
397     return;
398   }
399 
400   if (!current_frame_) {
401     current_frame_ = static_cast<uint8_t*>(malloc(maxSize));
402   }
403   RTC_DCHECK(current_frame_ != nullptr);
404 
405   // If both sides decided to go with the RGBx format we need to convert it to
406   // BGRx to match color format expected by WebRTC.
407   if (spa_video_format_->format == pw_type_->video_format.RGBx) {
408     uint8_t* tempFrame = static_cast<uint8_t*>(malloc(maxSize));
409     std::memcpy(tempFrame, src, maxSize);
410     ConvertRGBxToBGRx(tempFrame, maxSize);
411     std::memcpy(current_frame_, tempFrame, maxSize);
412     free(tempFrame);
413   } else {
414     std::memcpy(current_frame_, src, maxSize);
415   }
416 }
417 
ConvertRGBxToBGRx(uint8_t * frame,uint32_t size)418 void BaseCapturerPipeWire::ConvertRGBxToBGRx(uint8_t* frame, uint32_t size) {
419   // Change color format for KDE KWin which uses RGBx and not BGRx
420   for (uint32_t i = 0; i < size; i += 4) {
421     uint8_t tempR = frame[i];
422     uint8_t tempB = frame[i + 2];
423     frame[i] = tempB;
424     frame[i + 2] = tempR;
425   }
426 }
427 
SetupRequestResponseSignal(const gchar * object_path,GDBusSignalCallback callback)428 guint BaseCapturerPipeWire::SetupRequestResponseSignal(
429     const gchar* object_path,
430     GDBusSignalCallback callback) {
431   return g_dbus_connection_signal_subscribe(
432       connection_, kDesktopBusName, kRequestInterfaceName, "Response",
433       object_path, /*arg0=*/nullptr, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
434       callback, this, /*user_data_free_func=*/nullptr);
435 }
436 
437 // static
OnProxyRequested(GObject *,GAsyncResult * result,gpointer user_data)438 void BaseCapturerPipeWire::OnProxyRequested(GObject* /*object*/,
439                                             GAsyncResult* result,
440                                             gpointer user_data) {
441   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
442   RTC_DCHECK(that);
443 
444   GError* error = nullptr;
445   GDBusProxy *proxy = g_dbus_proxy_new_finish(result, &error);
446   if (!proxy) {
447     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
448       return;
449     RTC_LOG(LS_ERROR) << "Failed to create a proxy for the screen cast portal: "
450                       << error->message;
451     g_error_free(error);
452     that->portal_init_failed_ = true;
453     return;
454   }
455   that->proxy_ = proxy;
456   that->connection_ = g_dbus_proxy_get_connection(that->proxy_);
457 
458   RTC_LOG(LS_INFO) << "Created proxy for the screen cast portal.";
459   that->SessionRequest();
460 }
461 
462 // static
PrepareSignalHandle(GDBusConnection * connection,const gchar * token)463 gchar* BaseCapturerPipeWire::PrepareSignalHandle(GDBusConnection* connection,
464                                                  const gchar* token) {
465   gchar* sender = g_strdup(g_dbus_connection_get_unique_name(connection) + 1);
466   for (int i = 0; sender[i]; i++) {
467     if (sender[i] == '.') {
468       sender[i] = '_';
469     }
470   }
471 
472   gchar* handle = g_strconcat(kDesktopRequestObjectPath, "/", sender, "/",
473                               token, /*end of varargs*/ nullptr);
474   g_free(sender);
475 
476   return handle;
477 }
478 
SessionRequest()479 void BaseCapturerPipeWire::SessionRequest() {
480   GVariantBuilder builder;
481   gchar* variant_string;
482 
483   g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
484   variant_string =
485       g_strdup_printf("webrtc_session%d", g_random_int_range(0, G_MAXINT));
486   g_variant_builder_add(&builder, "{sv}", "session_handle_token",
487                         g_variant_new_string(variant_string));
488   g_free(variant_string);
489   variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT));
490   g_variant_builder_add(&builder, "{sv}", "handle_token",
491                         g_variant_new_string(variant_string));
492 
493   portal_handle_ = PrepareSignalHandle(connection_, variant_string);
494   session_request_signal_id_ = SetupRequestResponseSignal(
495       portal_handle_, OnSessionRequestResponseSignal);
496   g_free(variant_string);
497 
498   RTC_LOG(LS_INFO) << "Screen cast session requested.";
499   g_dbus_proxy_call(
500       proxy_, "CreateSession", g_variant_new("(a{sv})", &builder),
501       G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
502       reinterpret_cast<GAsyncReadyCallback>(OnSessionRequested), this);
503 }
504 
505 // static
OnSessionRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)506 void BaseCapturerPipeWire::OnSessionRequested(GDBusProxy *proxy,
507                                               GAsyncResult* result,
508                                               gpointer user_data) {
509   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
510   RTC_DCHECK(that);
511 
512   GError* error = nullptr;
513   GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error);
514   if (!variant) {
515     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
516       return;
517     RTC_LOG(LS_ERROR) << "Failed to create a screen cast session: "
518                       << error->message;
519     g_error_free(error);
520     that->portal_init_failed_ = true;
521     return;
522   }
523   RTC_LOG(LS_INFO) << "Initializing the screen cast session.";
524 
525   gchar* handle = nullptr;
526   g_variant_get_child(variant, 0, "o", &handle);
527   g_variant_unref(variant);
528   if (!handle) {
529     RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session.";
530     if (that->session_request_signal_id_) {
531       g_dbus_connection_signal_unsubscribe(that->connection_,
532                                            that->session_request_signal_id_);
533       that->session_request_signal_id_ = 0;
534     }
535     that->portal_init_failed_ = true;
536     return;
537   }
538 
539   g_free(handle);
540 
541   RTC_LOG(LS_INFO) << "Subscribing to the screen cast session.";
542 }
543 
544 // 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)545 void BaseCapturerPipeWire::OnSessionRequestResponseSignal(
546     GDBusConnection* connection,
547     const gchar* sender_name,
548     const gchar* object_path,
549     const gchar* interface_name,
550     const gchar* signal_name,
551     GVariant* parameters,
552     gpointer user_data) {
553   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
554   RTC_DCHECK(that);
555 
556   RTC_LOG(LS_INFO)
557       << "Received response for the screen cast session subscription.";
558 
559   guint32 portal_response;
560   GVariant* response_data;
561   g_variant_get(parameters, "(u@a{sv})", &portal_response, &response_data);
562   g_variant_lookup(response_data, "session_handle", "s",
563                    &that->session_handle_);
564   g_variant_unref(response_data);
565 
566   if (!that->session_handle_ || portal_response) {
567     RTC_LOG(LS_ERROR)
568         << "Failed to request the screen cast session subscription.";
569     that->portal_init_failed_ = true;
570     return;
571   }
572 
573   that->SourcesRequest();
574 }
575 
SourcesRequest()576 void BaseCapturerPipeWire::SourcesRequest() {
577   GVariantBuilder builder;
578   gchar* variant_string;
579 
580   g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
581   // We want to record monitor content.
582   g_variant_builder_add(&builder, "{sv}", "types",
583                         g_variant_new_uint32(capture_source_type_));
584   // We don't want to allow selection of multiple sources.
585   g_variant_builder_add(&builder, "{sv}", "multiple",
586                         g_variant_new_boolean(false));
587   variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT));
588   g_variant_builder_add(&builder, "{sv}", "handle_token",
589                         g_variant_new_string(variant_string));
590 
591   sources_handle_ = PrepareSignalHandle(connection_, variant_string);
592   sources_request_signal_id_ = SetupRequestResponseSignal(
593       sources_handle_, OnSourcesRequestResponseSignal);
594   g_free(variant_string);
595 
596   RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session.";
597   g_dbus_proxy_call(
598       proxy_, "SelectSources",
599       g_variant_new("(oa{sv})", session_handle_, &builder),
600       G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
601       reinterpret_cast<GAsyncReadyCallback>(OnSourcesRequested), this);
602 }
603 
604 // static
OnSourcesRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)605 void BaseCapturerPipeWire::OnSourcesRequested(GDBusProxy *proxy,
606                                               GAsyncResult* result,
607                                               gpointer user_data) {
608   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
609   RTC_DCHECK(that);
610 
611   GError* error = nullptr;
612   GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error);
613   if (!variant) {
614     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
615       return;
616     RTC_LOG(LS_ERROR) << "Failed to request the sources: " << error->message;
617     g_error_free(error);
618     that->portal_init_failed_ = true;
619     return;
620   }
621 
622   RTC_LOG(LS_INFO) << "Sources requested from the screen cast session.";
623 
624   gchar* handle = nullptr;
625   g_variant_get_child(variant, 0, "o", &handle);
626   g_variant_unref(variant);
627   if (!handle) {
628     RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session.";
629     if (that->sources_request_signal_id_) {
630       g_dbus_connection_signal_unsubscribe(that->connection_,
631                                            that->sources_request_signal_id_);
632       that->sources_request_signal_id_ = 0;
633     }
634     that->portal_init_failed_ = true;
635     return;
636   }
637 
638   g_free(handle);
639 
640   RTC_LOG(LS_INFO) << "Subscribed to sources signal.";
641 }
642 
643 // 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)644 void BaseCapturerPipeWire::OnSourcesRequestResponseSignal(
645     GDBusConnection* connection,
646     const gchar* sender_name,
647     const gchar* object_path,
648     const gchar* interface_name,
649     const gchar* signal_name,
650     GVariant* parameters,
651     gpointer user_data) {
652   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
653   RTC_DCHECK(that);
654 
655   RTC_LOG(LS_INFO) << "Received sources signal from session.";
656 
657   guint32 portal_response;
658   g_variant_get(parameters, "(u@a{sv})", &portal_response, nullptr);
659   if (portal_response) {
660     RTC_LOG(LS_ERROR)
661         << "Failed to select sources for the screen cast session.";
662     that->portal_init_failed_ = true;
663     return;
664   }
665 
666   that->StartRequest();
667 }
668 
StartRequest()669 void BaseCapturerPipeWire::StartRequest() {
670   GVariantBuilder builder;
671   gchar* variant_string;
672 
673   g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
674   variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT));
675   g_variant_builder_add(&builder, "{sv}", "handle_token",
676                         g_variant_new_string(variant_string));
677 
678   start_handle_ = PrepareSignalHandle(connection_, variant_string);
679   start_request_signal_id_ =
680       SetupRequestResponseSignal(start_handle_, OnStartRequestResponseSignal);
681   g_free(variant_string);
682 
683   // "Identifier for the application window", this is Wayland, so not "x11:...".
684   const gchar parent_window[] = "";
685 
686   RTC_LOG(LS_INFO) << "Starting the screen cast session.";
687   g_dbus_proxy_call(
688       proxy_, "Start",
689       g_variant_new("(osa{sv})", session_handle_, parent_window, &builder),
690       G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
691       reinterpret_cast<GAsyncReadyCallback>(OnStartRequested), this);
692 }
693 
694 // static
OnStartRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)695 void BaseCapturerPipeWire::OnStartRequested(GDBusProxy *proxy,
696                                             GAsyncResult* result,
697                                             gpointer user_data) {
698   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
699   RTC_DCHECK(that);
700 
701   GError* error = nullptr;
702   GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error);
703   if (!variant) {
704     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
705       return;
706     RTC_LOG(LS_ERROR) << "Failed to start the screen cast session: "
707                       << error->message;
708     g_error_free(error);
709     that->portal_init_failed_ = true;
710     return;
711   }
712 
713   RTC_LOG(LS_INFO) << "Initializing the start of the screen cast session.";
714 
715   gchar* handle = nullptr;
716   g_variant_get_child(variant, 0, "o", &handle);
717   g_variant_unref(variant);
718   if (!handle) {
719     RTC_LOG(LS_ERROR)
720         << "Failed to initialize the start of the screen cast session.";
721     if (that->start_request_signal_id_) {
722       g_dbus_connection_signal_unsubscribe(that->connection_,
723                                            that->start_request_signal_id_);
724       that->start_request_signal_id_ = 0;
725     }
726     that->portal_init_failed_ = true;
727     return;
728   }
729 
730   g_free(handle);
731 
732   RTC_LOG(LS_INFO) << "Subscribed to the start signal.";
733 }
734 
735 // 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)736 void BaseCapturerPipeWire::OnStartRequestResponseSignal(
737     GDBusConnection* connection,
738     const gchar* sender_name,
739     const gchar* object_path,
740     const gchar* interface_name,
741     const gchar* signal_name,
742     GVariant* parameters,
743     gpointer user_data) {
744   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
745   RTC_DCHECK(that);
746 
747   RTC_LOG(LS_INFO) << "Start signal received.";
748   guint32 portal_response;
749   GVariant* response_data;
750   GVariantIter* iter = nullptr;
751   g_variant_get(parameters, "(u@a{sv})", &portal_response, &response_data);
752   if (portal_response || !response_data) {
753     RTC_LOG(LS_ERROR) << "Failed to start the screen cast session.";
754     that->portal_init_failed_ = true;
755     return;
756   }
757 
758   // Array of PipeWire streams. See
759   // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml
760   // documentation for <method name="Start">.
761   if (g_variant_lookup(response_data, "streams", "a(ua{sv})", &iter)) {
762     GVariant* variant;
763 
764     while (g_variant_iter_next(iter, "@(ua{sv})", &variant)) {
765       guint32 stream_id;
766       gint32 width;
767       gint32 height;
768       GVariant* options;
769 
770       g_variant_get(variant, "(u@a{sv})", &stream_id, &options);
771       RTC_DCHECK(options != nullptr);
772 
773       g_variant_lookup(options, "size", "(ii)", &width, &height);
774 
775       that->desktop_size_.set(width, height);
776 
777       g_variant_unref(options);
778       g_variant_unref(variant);
779     }
780   }
781   g_variant_iter_free(iter);
782   g_variant_unref(response_data);
783 
784   that->OpenPipeWireRemote();
785 }
786 
OpenPipeWireRemote()787 void BaseCapturerPipeWire::OpenPipeWireRemote() {
788   GVariantBuilder builder;
789   g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
790 
791   RTC_LOG(LS_INFO) << "Opening the PipeWire remote.";
792 
793   g_dbus_proxy_call_with_unix_fd_list(
794       proxy_, "OpenPipeWireRemote",
795       g_variant_new("(oa{sv})", session_handle_, &builder),
796       G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*fd_list=*/nullptr,
797       cancellable_,
798       reinterpret_cast<GAsyncReadyCallback>(OnOpenPipeWireRemoteRequested),
799       this);
800 }
801 
802 // static
OnOpenPipeWireRemoteRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)803 void BaseCapturerPipeWire::OnOpenPipeWireRemoteRequested(
804     GDBusProxy *proxy,
805     GAsyncResult* result,
806     gpointer user_data) {
807   BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
808   RTC_DCHECK(that);
809 
810   GError* error = nullptr;
811   GUnixFDList* outlist = nullptr;
812   GVariant* variant = g_dbus_proxy_call_with_unix_fd_list_finish(
813       proxy, &outlist, result, &error);
814   if (!variant) {
815     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
816       return;
817     RTC_LOG(LS_ERROR) << "Failed to open the PipeWire remote: "
818                       << error->message;
819     g_error_free(error);
820     that->portal_init_failed_ = true;
821     return;
822   }
823 
824   gint32 index;
825   g_variant_get(variant, "(h)", &index);
826 
827   if ((that->pw_fd_ = g_unix_fd_list_get(outlist, index, &error)) == -1) {
828     RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: "
829                       << error->message;
830     g_error_free(error);
831     g_variant_unref(variant);
832     that->portal_init_failed_ = true;
833     return;
834   }
835 
836   g_variant_unref(variant);
837   g_object_unref(outlist);
838 
839   that->InitPipeWire();
840 }
841 
Start(Callback * callback)842 void BaseCapturerPipeWire::Start(Callback* callback) {
843   RTC_DCHECK(!callback_);
844   RTC_DCHECK(callback);
845 
846   InitPortal();
847 
848   callback_ = callback;
849 }
850 
CaptureFrame()851 void BaseCapturerPipeWire::CaptureFrame() {
852   if (portal_init_failed_) {
853     callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
854     return;
855   }
856 
857   if (!current_frame_) {
858     callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
859     return;
860   }
861 
862   std::unique_ptr<DesktopFrame> result(new BasicDesktopFrame(desktop_size_));
863   result->CopyPixelsFrom(
864       current_frame_, (desktop_size_.width() * kBytesPerPixel),
865       DesktopRect::MakeWH(desktop_size_.width(), desktop_size_.height()));
866   if (!result) {
867     callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
868     return;
869   }
870 
871   // TODO(julien.isorce): http://crbug.com/945468. Set the icc profile on the
872   // frame, see ScreenCapturerX11::CaptureFrame.
873 
874   callback_->OnCaptureResult(Result::SUCCESS, std::move(result));
875 }
876 
GetSourceList(SourceList * sources)877 bool BaseCapturerPipeWire::GetSourceList(SourceList* sources) {
878   RTC_DCHECK(sources->size() == 0);
879   // List of available screens is already presented by the xdg-desktop-portal.
880   // But we have to add an empty source as the code expects it.
881   sources->push_back({0});
882   return true;
883 }
884 
SelectSource(SourceId id)885 bool BaseCapturerPipeWire::SelectSource(SourceId id) {
886   // Screen selection is handled by the xdg-desktop-portal.
887   return true;
888 }
889 
890 }  // namespace webrtc
891