camera_portal.cc (9344B)
1 /* 2 * Copyright (c) 2023 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/video_capture/linux/camera_portal.h" 12 13 #include <gio/gunixfdlist.h> 14 15 #include <cstdint> 16 #include <memory> 17 #include <string> 18 19 #include "modules/portal/pipewire_utils.h" 20 #include "modules/portal/portal_request_response.h" 21 #include "modules/portal/scoped_glib.h" 22 #include "modules/portal/xdg_desktop_portal_utils.h" 23 #include "rtc_base/checks.h" 24 #include "rtc_base/logging.h" 25 #include "rtc_base/synchronization/mutex.h" 26 #include "rtc_base/thread_annotations.h" 27 28 namespace webrtc { 29 30 using xdg_portal::RequestResponse; 31 using xdg_portal::RequestResponseFromPortalResponse; 32 using xdg_portal::RequestSessionProxy; 33 34 constexpr char kCameraInterfaceName[] = "org.freedesktop.portal.Camera"; 35 36 class CameraPortalPrivate { 37 public: 38 explicit CameraPortalPrivate(CameraPortal::PortalNotifier* notifier); 39 ~CameraPortalPrivate(); 40 41 void Start(); 42 43 private: 44 void OnPortalDone(xdg_portal::RequestResponse result, 45 int fd = kInvalidPipeWireFd); 46 47 static void OnProxyRequested(GObject* object, 48 GAsyncResult* result, 49 gpointer user_data); 50 void ProxyRequested(GDBusProxy* proxy); 51 52 static void OnAccessResponse(GDBusProxy* proxy, 53 GAsyncResult* result, 54 gpointer user_data); 55 static void OnResponseSignalEmitted(GDBusConnection* connection, 56 const char* sender_name, 57 const char* object_path, 58 const char* interface_name, 59 const char* signal_name, 60 GVariant* parameters, 61 gpointer user_data); 62 static void OnOpenResponse(GDBusProxy* proxy, 63 GAsyncResult* result, 64 gpointer user_data); 65 66 Mutex notifier_lock_; 67 CameraPortal::PortalNotifier* notifier_ RTC_GUARDED_BY(¬ifier_lock_) = 68 nullptr; 69 70 GDBusConnection* connection_ = nullptr; 71 GDBusProxy* proxy_ = nullptr; 72 GCancellable* cancellable_ = nullptr; 73 guint access_request_signal_id_ = 0; 74 }; 75 76 CameraPortalPrivate::CameraPortalPrivate(CameraPortal::PortalNotifier* notifier) 77 : notifier_(notifier) {} 78 79 CameraPortalPrivate::~CameraPortalPrivate() { 80 { 81 MutexLock lock(¬ifier_lock_); 82 notifier_ = nullptr; 83 } 84 85 if (access_request_signal_id_) { 86 g_dbus_connection_signal_unsubscribe(connection_, 87 access_request_signal_id_); 88 access_request_signal_id_ = 0; 89 } 90 if (cancellable_) { 91 g_cancellable_cancel(cancellable_); 92 g_object_unref(cancellable_); 93 cancellable_ = nullptr; 94 } 95 if (proxy_) { 96 g_object_unref(proxy_); 97 proxy_ = nullptr; 98 connection_ = nullptr; 99 } 100 } 101 102 void CameraPortalPrivate::Start() { 103 cancellable_ = g_cancellable_new(); 104 Scoped<GError> error; 105 RequestSessionProxy(kCameraInterfaceName, OnProxyRequested, cancellable_, 106 this); 107 } 108 109 // static 110 void CameraPortalPrivate::OnProxyRequested(GObject* gobject, 111 GAsyncResult* result, 112 gpointer user_data) { 113 CameraPortalPrivate* that = static_cast<CameraPortalPrivate*>(user_data); 114 Scoped<GError> error; 115 GDBusProxy* proxy = g_dbus_proxy_new_finish(result, error.receive()); 116 if (!proxy) { 117 // Ignore the error caused by user cancelling the request via `cancellable_` 118 if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) 119 return; 120 RTC_LOG(LS_ERROR) << "Failed to get a proxy for the portal: " 121 << error->message; 122 that->OnPortalDone(RequestResponse::kError); 123 return; 124 } 125 126 RTC_LOG(LS_VERBOSE) << "Successfully created proxy for the portal."; 127 that->ProxyRequested(proxy); 128 } 129 130 void CameraPortalPrivate::ProxyRequested(GDBusProxy* proxy) { 131 GVariantBuilder builder; 132 Scoped<char> variant_string; 133 std::string access_handle; 134 135 proxy_ = proxy; 136 connection_ = g_dbus_proxy_get_connection(proxy); 137 138 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); 139 variant_string = 140 g_strdup_printf("capture%d", g_random_int_range(0, G_MAXINT)); 141 g_variant_builder_add(&builder, "{sv}", "handle_token", 142 g_variant_new_string(variant_string.get())); 143 144 access_handle = 145 xdg_portal::PrepareSignalHandle(variant_string.get(), connection_); 146 access_request_signal_id_ = xdg_portal::SetupRequestResponseSignal( 147 access_handle.c_str(), OnResponseSignalEmitted, this, connection_); 148 149 RTC_LOG(LS_VERBOSE) << "Requesting camera access from the portal."; 150 g_dbus_proxy_call(proxy_, "AccessCamera", g_variant_new("(a{sv})", &builder), 151 G_DBUS_CALL_FLAGS_NONE, /*timeout_msec=*/-1, cancellable_, 152 reinterpret_cast<GAsyncReadyCallback>(OnAccessResponse), 153 this); 154 } 155 156 // static 157 void CameraPortalPrivate::OnAccessResponse(GDBusProxy* proxy, 158 GAsyncResult* result, 159 gpointer user_data) { 160 CameraPortalPrivate* that = static_cast<CameraPortalPrivate*>(user_data); 161 RTC_DCHECK(that); 162 163 Scoped<GError> error; 164 Scoped<GVariant> variant( 165 g_dbus_proxy_call_finish(proxy, result, error.receive())); 166 if (!variant) { 167 if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) 168 return; 169 RTC_LOG(LS_ERROR) << "Failed to access portal:" << error->message; 170 if (that->access_request_signal_id_) { 171 g_dbus_connection_signal_unsubscribe(that->connection_, 172 that->access_request_signal_id_); 173 that->access_request_signal_id_ = 0; 174 } 175 that->OnPortalDone(RequestResponse::kError); 176 } 177 } 178 179 // static 180 void CameraPortalPrivate::OnResponseSignalEmitted(GDBusConnection* connection, 181 const char* sender_name, 182 const char* object_path, 183 const char* interface_name, 184 const char* signal_name, 185 GVariant* parameters, 186 gpointer user_data) { 187 CameraPortalPrivate* that = static_cast<CameraPortalPrivate*>(user_data); 188 RTC_DCHECK(that); 189 190 uint32_t portal_response; 191 g_variant_get(parameters, "(u@a{sv})", &portal_response, nullptr); 192 if (portal_response) { 193 RTC_LOG(LS_INFO) << "Camera access denied by the XDG portal."; 194 that->OnPortalDone(RequestResponseFromPortalResponse(portal_response)); 195 return; 196 } 197 198 RTC_LOG(LS_VERBOSE) << "Camera access granted by the XDG portal."; 199 200 GVariantBuilder builder; 201 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); 202 203 g_dbus_proxy_call( 204 that->proxy_, "OpenPipeWireRemote", g_variant_new("(a{sv})", &builder), 205 G_DBUS_CALL_FLAGS_NONE, /*timeout_msec=*/-1, that->cancellable_, 206 reinterpret_cast<GAsyncReadyCallback>(OnOpenResponse), that); 207 } 208 209 void CameraPortalPrivate::OnOpenResponse(GDBusProxy* proxy, 210 GAsyncResult* result, 211 gpointer user_data) { 212 CameraPortalPrivate* that = static_cast<CameraPortalPrivate*>(user_data); 213 RTC_DCHECK(that); 214 215 Scoped<GError> error; 216 Scoped<GUnixFDList> outlist; 217 Scoped<GVariant> variant(g_dbus_proxy_call_with_unix_fd_list_finish( 218 proxy, outlist.receive(), result, error.receive())); 219 if (!variant) { 220 if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) 221 return; 222 RTC_LOG(LS_ERROR) << "Failed to open PipeWire remote:" << error->message; 223 if (that->access_request_signal_id_) { 224 g_dbus_connection_signal_unsubscribe(that->connection_, 225 that->access_request_signal_id_); 226 that->access_request_signal_id_ = 0; 227 } 228 that->OnPortalDone(RequestResponse::kError); 229 return; 230 } 231 232 int32_t index; 233 g_variant_get(variant.get(), "(h)", &index); 234 235 int fd = g_unix_fd_list_get(outlist.get(), index, error.receive()); 236 237 if (fd == kInvalidPipeWireFd) { 238 RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: " 239 << error->message; 240 that->OnPortalDone(RequestResponse::kError); 241 return; 242 } 243 244 that->OnPortalDone(RequestResponse::kSuccess, fd); 245 } 246 247 void CameraPortalPrivate::OnPortalDone(RequestResponse result, int fd) { 248 MutexLock lock(¬ifier_lock_); 249 if (notifier_) { 250 notifier_->OnCameraRequestResult(result, fd); 251 notifier_ = nullptr; 252 } 253 } 254 255 CameraPortal::CameraPortal(PortalNotifier* notifier) 256 : private_(std::make_unique<CameraPortalPrivate>(notifier)) {} 257 258 CameraPortal::~CameraPortal() {} 259 260 void CameraPortal::Start() { 261 private_->Start(); 262 } 263 264 } // namespace webrtc