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