tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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