1 /*
2 * Copyright 2022 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/wayland/screencast_portal.h"
12
13 #include <gio/gunixfdlist.h>
14 #include <glib-object.h>
15
16 #include "modules/portal/scoped_glib.h"
17 #include "modules/portal/xdg_desktop_portal_utils.h"
18 #include "rtc_base/checks.h"
19 #include "rtc_base/logging.h"
20
21 namespace webrtc {
22 namespace {
23
24 using xdg_portal::kScreenCastInterfaceName;
25 using xdg_portal::PrepareSignalHandle;
26 using xdg_portal::RequestResponse;
27 using xdg_portal::RequestSessionProxy;
28 using xdg_portal::SetupRequestResponseSignal;
29 using xdg_portal::SetupSessionRequestHandlers;
30 using xdg_portal::StartSessionRequest;
31 using xdg_portal::TearDownSession;
32 using xdg_portal::RequestResponseFromPortalResponse;
33
34 } // namespace
35
36 // static
ToCaptureSourceType(CaptureType type)37 ScreenCastPortal::CaptureSourceType ScreenCastPortal::ToCaptureSourceType(
38 CaptureType type) {
39 switch (type) {
40 case CaptureType::kScreen:
41 return ScreenCastPortal::CaptureSourceType::kScreen;
42 case CaptureType::kWindow:
43 return ScreenCastPortal::CaptureSourceType::kWindow;
44 }
45 }
46
ScreenCastPortal(CaptureType type,PortalNotifier * notifier)47 ScreenCastPortal::ScreenCastPortal(CaptureType type, PortalNotifier* notifier)
48 : ScreenCastPortal(type,
49 notifier,
50 OnProxyRequested,
51 OnSourcesRequestResponseSignal,
52 this) {}
53
ScreenCastPortal(CaptureType type,PortalNotifier * notifier,ProxyRequestResponseHandler proxy_request_response_handler,SourcesRequestResponseSignalHandler sources_request_response_signal_handler,gpointer user_data,bool prefer_cursor_embedded)54 ScreenCastPortal::ScreenCastPortal(
55 CaptureType type,
56 PortalNotifier* notifier,
57 ProxyRequestResponseHandler proxy_request_response_handler,
58 SourcesRequestResponseSignalHandler sources_request_response_signal_handler,
59 gpointer user_data,
60 bool prefer_cursor_embedded)
61 : notifier_(notifier),
62 capture_source_type_(ToCaptureSourceType(type)),
63 cursor_mode_(prefer_cursor_embedded ? CursorMode::kEmbedded
64 : CursorMode::kMetadata),
65 proxy_request_response_handler_(proxy_request_response_handler),
66 sources_request_response_signal_handler_(
67 sources_request_response_signal_handler),
68 user_data_(user_data) {}
69
~ScreenCastPortal()70 ScreenCastPortal::~ScreenCastPortal() {
71 Stop();
72 }
73
Stop()74 void ScreenCastPortal::Stop() {
75 UnsubscribeSignalHandlers();
76 TearDownSession(std::move(session_handle_), proxy_, cancellable_,
77 connection_);
78 session_handle_ = "";
79 cancellable_ = nullptr;
80 proxy_ = nullptr;
81 restore_token_ = "";
82
83 if (pw_fd_ != -1) {
84 close(pw_fd_);
85 pw_fd_ = -1;
86 }
87 }
88
UnsubscribeSignalHandlers()89 void ScreenCastPortal::UnsubscribeSignalHandlers() {
90 if (start_request_signal_id_) {
91 g_dbus_connection_signal_unsubscribe(connection_, start_request_signal_id_);
92 start_request_signal_id_ = 0;
93 }
94
95 if (sources_request_signal_id_) {
96 g_dbus_connection_signal_unsubscribe(connection_,
97 sources_request_signal_id_);
98 sources_request_signal_id_ = 0;
99 }
100
101 if (session_request_signal_id_) {
102 g_dbus_connection_signal_unsubscribe(connection_,
103 session_request_signal_id_);
104 session_request_signal_id_ = 0;
105 }
106 }
107
SetSessionDetails(const xdg_portal::SessionDetails & session_details)108 void ScreenCastPortal::SetSessionDetails(
109 const xdg_portal::SessionDetails& session_details) {
110 if (session_details.proxy) {
111 proxy_ = session_details.proxy;
112 connection_ = g_dbus_proxy_get_connection(proxy_);
113 }
114 if (session_details.cancellable) {
115 cancellable_ = session_details.cancellable;
116 }
117 if (!session_details.session_handle.empty()) {
118 session_handle_ = session_details.session_handle;
119 }
120 if (session_details.pipewire_stream_node_id) {
121 pw_stream_node_id_ = session_details.pipewire_stream_node_id;
122 }
123 }
124
Start()125 void ScreenCastPortal::Start() {
126 cancellable_ = g_cancellable_new();
127 RequestSessionProxy(kScreenCastInterfaceName, proxy_request_response_handler_,
128 cancellable_, this);
129 }
130
GetSessionDetails()131 xdg_portal::SessionDetails ScreenCastPortal::GetSessionDetails() {
132 return {}; // No-op
133 }
134
OnPortalDone(RequestResponse result)135 void ScreenCastPortal::OnPortalDone(RequestResponse result) {
136 notifier_->OnScreenCastRequestResult(result, pw_stream_node_id_, pw_fd_);
137 if (result != RequestResponse::kSuccess) {
138 Stop();
139 }
140 }
141
142 // static
OnProxyRequested(GObject * gobject,GAsyncResult * result,gpointer user_data)143 void ScreenCastPortal::OnProxyRequested(GObject* gobject,
144 GAsyncResult* result,
145 gpointer user_data) {
146 static_cast<ScreenCastPortal*>(user_data)->RequestSessionUsingProxy(result);
147 }
148
RequestSession(GDBusProxy * proxy)149 void ScreenCastPortal::RequestSession(GDBusProxy* proxy) {
150 proxy_ = proxy;
151 connection_ = g_dbus_proxy_get_connection(proxy_);
152 SetupSessionRequestHandlers(
153 "webrtc", OnSessionRequested, OnSessionRequestResponseSignal, connection_,
154 proxy_, cancellable_, portal_handle_, session_request_signal_id_, this);
155 }
156
157 // static
OnSessionRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)158 void ScreenCastPortal::OnSessionRequested(GDBusProxy* proxy,
159 GAsyncResult* result,
160 gpointer user_data) {
161 static_cast<ScreenCastPortal*>(user_data)->OnSessionRequestResult(proxy,
162 result);
163 }
164
165 // static
OnSessionRequestResponseSignal(GDBusConnection * connection,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer user_data)166 void ScreenCastPortal::OnSessionRequestResponseSignal(
167 GDBusConnection* connection,
168 const char* sender_name,
169 const char* object_path,
170 const char* interface_name,
171 const char* signal_name,
172 GVariant* parameters,
173 gpointer user_data) {
174 ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
175 RTC_DCHECK(that);
176 that->RegisterSessionClosedSignalHandler(
177 OnSessionClosedSignal, parameters, that->connection_,
178 that->session_handle_, that->session_closed_signal_id_);
179
180 // Do not continue if we don't get session_handle back. The call above will
181 // already notify the capturer there is a failure, but we would still continue
182 // to make following request and crash on that.
183 if (!that->session_handle_.empty()) {
184 that->SourcesRequest();
185 }
186 }
187
188 // static
OnSessionClosedSignal(GDBusConnection * connection,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer user_data)189 void ScreenCastPortal::OnSessionClosedSignal(GDBusConnection* connection,
190 const char* sender_name,
191 const char* object_path,
192 const char* interface_name,
193 const char* signal_name,
194 GVariant* parameters,
195 gpointer user_data) {
196 ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
197 RTC_DCHECK(that);
198
199 RTC_LOG(LS_INFO) << "Received closed signal from session.";
200
201 that->notifier_->OnScreenCastSessionClosed();
202
203 // Unsubscribe from the signal and free the session handle to avoid calling
204 // Session::Close from the destructor since it's already closed
205 g_dbus_connection_signal_unsubscribe(that->connection_,
206 that->session_closed_signal_id_);
207 }
208
SourcesRequest()209 void ScreenCastPortal::SourcesRequest() {
210 GVariantBuilder builder;
211 Scoped<char> variant_string;
212
213 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
214 // We want to record monitor content.
215 g_variant_builder_add(
216 &builder, "{sv}", "types",
217 g_variant_new_uint32(static_cast<uint32_t>(capture_source_type_)));
218 // We don't want to allow selection of multiple sources.
219 g_variant_builder_add(&builder, "{sv}", "multiple",
220 g_variant_new_boolean(false));
221
222 Scoped<GVariant> cursorModesVariant(
223 g_dbus_proxy_get_cached_property(proxy_, "AvailableCursorModes"));
224 if (cursorModesVariant.get()) {
225 uint32_t modes = 0;
226 g_variant_get(cursorModesVariant.get(), "u", &modes);
227 // Make request only if this mode is advertised by the portal
228 // implementation.
229 if (modes & static_cast<uint32_t>(cursor_mode_)) {
230 g_variant_builder_add(
231 &builder, "{sv}", "cursor_mode",
232 g_variant_new_uint32(static_cast<uint32_t>(cursor_mode_)));
233 }
234 }
235
236 Scoped<GVariant> versionVariant(
237 g_dbus_proxy_get_cached_property(proxy_, "version"));
238 if (versionVariant.get()) {
239 uint32_t version = 0;
240 g_variant_get(versionVariant.get(), "u", &version);
241 // Make request only if xdg-desktop-portal has required API version
242 if (version >= 4) {
243 g_variant_builder_add(
244 &builder, "{sv}", "persist_mode",
245 g_variant_new_uint32(static_cast<uint32_t>(persist_mode_)));
246 if (!restore_token_.empty()) {
247 g_variant_builder_add(&builder, "{sv}", "restore_token",
248 g_variant_new_string(restore_token_.c_str()));
249 }
250 }
251 }
252
253 variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT));
254 g_variant_builder_add(&builder, "{sv}", "handle_token",
255 g_variant_new_string(variant_string.get()));
256
257 sources_handle_ = PrepareSignalHandle(variant_string.get(), connection_);
258 sources_request_signal_id_ = SetupRequestResponseSignal(
259 sources_handle_.c_str(), sources_request_response_signal_handler_,
260 user_data_, connection_);
261
262 RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session.";
263 g_dbus_proxy_call(
264 proxy_, "SelectSources",
265 g_variant_new("(oa{sv})", session_handle_.c_str(), &builder),
266 G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
267 reinterpret_cast<GAsyncReadyCallback>(OnSourcesRequested), this);
268 }
269
270 // static
OnSourcesRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)271 void ScreenCastPortal::OnSourcesRequested(GDBusProxy* proxy,
272 GAsyncResult* result,
273 gpointer user_data) {
274 ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
275 RTC_DCHECK(that);
276
277 Scoped<GError> error;
278 Scoped<GVariant> variant(
279 g_dbus_proxy_call_finish(proxy, result, error.receive()));
280 if (!variant) {
281 if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
282 return;
283 RTC_LOG(LS_ERROR) << "Failed to request the sources: " << error->message;
284 that->OnPortalDone(RequestResponse::kError);
285 return;
286 }
287
288 RTC_LOG(LS_INFO) << "Sources requested from the screen cast session.";
289
290 Scoped<char> handle;
291 g_variant_get_child(variant.get(), 0, "o", handle.receive());
292 if (!handle) {
293 RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session.";
294 if (that->sources_request_signal_id_) {
295 g_dbus_connection_signal_unsubscribe(that->connection_,
296 that->sources_request_signal_id_);
297 that->sources_request_signal_id_ = 0;
298 }
299 that->OnPortalDone(RequestResponse::kError);
300 return;
301 }
302
303 RTC_LOG(LS_INFO) << "Subscribed to sources signal.";
304 }
305
306 // static
OnSourcesRequestResponseSignal(GDBusConnection * connection,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer user_data)307 void ScreenCastPortal::OnSourcesRequestResponseSignal(
308 GDBusConnection* connection,
309 const char* sender_name,
310 const char* object_path,
311 const char* interface_name,
312 const char* signal_name,
313 GVariant* parameters,
314 gpointer user_data) {
315 ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
316 RTC_DCHECK(that);
317
318 RTC_LOG(LS_INFO) << "Received sources signal from session.";
319
320 uint32_t portal_response;
321 g_variant_get(parameters, "(u@a{sv})", &portal_response, nullptr);
322 if (portal_response) {
323 RTC_LOG(LS_ERROR)
324 << "Failed to select sources for the screen cast session.";
325 that->OnPortalDone(RequestResponse::kError);
326 return;
327 }
328
329 that->StartRequest();
330 }
331
StartRequest()332 void ScreenCastPortal::StartRequest() {
333 StartSessionRequest("webrtc", session_handle_, OnStartRequestResponseSignal,
334 OnStartRequested, proxy_, connection_, cancellable_,
335 start_request_signal_id_, start_handle_, this);
336 }
337
338 // static
OnStartRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)339 void ScreenCastPortal::OnStartRequested(GDBusProxy* proxy,
340 GAsyncResult* result,
341 gpointer user_data) {
342 static_cast<ScreenCastPortal*>(user_data)->OnStartRequestResult(proxy,
343 result);
344 }
345
346 // static
OnStartRequestResponseSignal(GDBusConnection * connection,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer user_data)347 void ScreenCastPortal::OnStartRequestResponseSignal(GDBusConnection* connection,
348 const char* sender_name,
349 const char* object_path,
350 const char* interface_name,
351 const char* signal_name,
352 GVariant* parameters,
353 gpointer user_data) {
354 ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
355 RTC_DCHECK(that);
356
357 RTC_LOG(LS_INFO) << "Start signal received.";
358 uint32_t portal_response;
359 Scoped<GVariant> response_data;
360 Scoped<GVariantIter> iter;
361 Scoped<char> restore_token;
362 g_variant_get(parameters, "(u@a{sv})", &portal_response,
363 response_data.receive());
364 if (portal_response || !response_data) {
365 RTC_LOG(LS_ERROR) << "Failed to start the screen cast session.";
366 that->OnPortalDone(RequestResponseFromPortalResponse(portal_response));
367 return;
368 }
369
370 // Array of PipeWire streams. See
371 // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml
372 // documentation for <method name="Start">.
373 if (g_variant_lookup(response_data.get(), "streams", "a(ua{sv})",
374 iter.receive())) {
375 Scoped<GVariant> variant;
376
377 while (g_variant_iter_next(iter.get(), "@(ua{sv})", variant.receive())) {
378 uint32_t stream_id;
379 uint32_t type;
380 Scoped<GVariant> options;
381
382 g_variant_get(variant.get(), "(u@a{sv})", &stream_id, options.receive());
383 RTC_DCHECK(options.get());
384
385 if (g_variant_lookup(options.get(), "source_type", "u", &type)) {
386 that->capture_source_type_ =
387 static_cast<ScreenCastPortal::CaptureSourceType>(type);
388 }
389
390 that->pw_stream_node_id_ = stream_id;
391
392 break;
393 }
394 }
395
396 if (g_variant_lookup(response_data.get(), "restore_token", "s",
397 restore_token.receive())) {
398 that->restore_token_ = restore_token.get();
399 }
400
401 that->OpenPipeWireRemote();
402 }
403
pipewire_stream_node_id()404 uint32_t ScreenCastPortal::pipewire_stream_node_id() {
405 return pw_stream_node_id_;
406 }
407
SetPersistMode(ScreenCastPortal::PersistMode mode)408 void ScreenCastPortal::SetPersistMode(ScreenCastPortal::PersistMode mode) {
409 persist_mode_ = mode;
410 }
411
SetRestoreToken(const std::string & token)412 void ScreenCastPortal::SetRestoreToken(const std::string& token) {
413 restore_token_ = token;
414 }
415
RestoreToken() const416 std::string ScreenCastPortal::RestoreToken() const {
417 return restore_token_;
418 }
419
OpenPipeWireRemote()420 void ScreenCastPortal::OpenPipeWireRemote() {
421 GVariantBuilder builder;
422 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
423
424 RTC_LOG(LS_INFO) << "Opening the PipeWire remote.";
425
426 g_dbus_proxy_call_with_unix_fd_list(
427 proxy_, "OpenPipeWireRemote",
428 g_variant_new("(oa{sv})", session_handle_.c_str(), &builder),
429 G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*fd_list=*/nullptr, cancellable_,
430 reinterpret_cast<GAsyncReadyCallback>(OnOpenPipeWireRemoteRequested),
431 this);
432 }
433
434 // static
OnOpenPipeWireRemoteRequested(GDBusProxy * proxy,GAsyncResult * result,gpointer user_data)435 void ScreenCastPortal::OnOpenPipeWireRemoteRequested(GDBusProxy* proxy,
436 GAsyncResult* result,
437 gpointer user_data) {
438 ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
439 RTC_DCHECK(that);
440
441 Scoped<GError> error;
442 Scoped<GUnixFDList> outlist;
443 Scoped<GVariant> variant(g_dbus_proxy_call_with_unix_fd_list_finish(
444 proxy, outlist.receive(), result, error.receive()));
445 if (!variant) {
446 if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED))
447 return;
448 RTC_LOG(LS_ERROR) << "Failed to open the PipeWire remote: "
449 << error->message;
450 that->OnPortalDone(RequestResponse::kError);
451 return;
452 }
453
454 int32_t index;
455 g_variant_get(variant.get(), "(h)", &index);
456
457 that->pw_fd_ = g_unix_fd_list_get(outlist.get(), index, error.receive());
458
459 if (that->pw_fd_ == -1) {
460 RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: "
461 << error->message;
462 that->OnPortalDone(RequestResponse::kError);
463 return;
464 }
465
466 that->OnPortalDone(RequestResponse::kSuccess);
467 }
468
469 } // namespace webrtc
470