xref: /aosp_15_r20/external/webrtc/modules/desktop_capture/linux/wayland/screencast_portal.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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