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