/* * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/video_capture/linux/camera_portal.h" #include #include #include "modules/portal/pipewire_utils.h" #include "modules/portal/xdg_desktop_portal_utils.h" namespace webrtc { using xdg_portal::RequestResponse; using xdg_portal::RequestResponseFromPortalResponse; using xdg_portal::RequestSessionProxy; constexpr char kCameraInterfaceName[] = "org.freedesktop.portal.Camera"; class CameraPortalPrivate { public: explicit CameraPortalPrivate(CameraPortal::PortalNotifier* notifier); ~CameraPortalPrivate(); void Start(); private: void OnPortalDone(xdg_portal::RequestResponse result, int fd = kInvalidPipeWireFd); static void OnProxyRequested(GObject* object, GAsyncResult* result, gpointer user_data); void ProxyRequested(GDBusProxy* proxy); static void OnAccessResponse(GDBusProxy* proxy, GAsyncResult* result, gpointer user_data); static void OnResponseSignalEmitted(GDBusConnection* connection, const char* sender_name, const char* object_path, const char* interface_name, const char* signal_name, GVariant* parameters, gpointer user_data); static void OnOpenResponse(GDBusProxy* proxy, GAsyncResult* result, gpointer user_data); CameraPortal::PortalNotifier* notifier_ = nullptr; GDBusConnection* connection_ = nullptr; GDBusProxy* proxy_ = nullptr; GCancellable* cancellable_ = nullptr; guint access_request_signal_id_ = 0; }; CameraPortalPrivate::CameraPortalPrivate(CameraPortal::PortalNotifier* notifier) : notifier_(notifier) {} CameraPortalPrivate::~CameraPortalPrivate() { if (access_request_signal_id_) { g_dbus_connection_signal_unsubscribe(connection_, access_request_signal_id_); access_request_signal_id_ = 0; } if (cancellable_) { g_cancellable_cancel(cancellable_); g_object_unref(cancellable_); cancellable_ = nullptr; } if (proxy_) { g_object_unref(proxy_); proxy_ = nullptr; connection_ = nullptr; } } void CameraPortalPrivate::Start() { cancellable_ = g_cancellable_new(); Scoped error; RequestSessionProxy(kCameraInterfaceName, OnProxyRequested, cancellable_, this); } // static void CameraPortalPrivate::OnProxyRequested(GObject* gobject, GAsyncResult* result, gpointer user_data) { CameraPortalPrivate* that = static_cast(user_data); Scoped error; GDBusProxy* proxy = g_dbus_proxy_new_finish(result, error.receive()); if (!proxy) { // Ignore the error caused by user cancelling the request via `cancellable_` if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; RTC_LOG(LS_ERROR) << "Failed to get a proxy for the portal: " << error->message; that->OnPortalDone(RequestResponse::kError); return; } RTC_LOG(LS_VERBOSE) << "Successfully created proxy for the portal."; that->ProxyRequested(proxy); } void CameraPortalPrivate::ProxyRequested(GDBusProxy* proxy) { GVariantBuilder builder; Scoped variant_string; std::string access_handle; proxy_ = proxy; connection_ = g_dbus_proxy_get_connection(proxy); g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); variant_string = g_strdup_printf("capture%d", g_random_int_range(0, G_MAXINT)); g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(variant_string.get())); access_handle = xdg_portal::PrepareSignalHandle(variant_string.get(), connection_); access_request_signal_id_ = xdg_portal::SetupRequestResponseSignal( access_handle.c_str(), OnResponseSignalEmitted, this, connection_); RTC_LOG(LS_VERBOSE) << "Requesting camera access from the portal."; g_dbus_proxy_call(proxy_, "AccessCamera", g_variant_new("(a{sv})", &builder), G_DBUS_CALL_FLAGS_NONE, /*timeout_msec=*/-1, cancellable_, reinterpret_cast(OnAccessResponse), this); } // static void CameraPortalPrivate::OnAccessResponse(GDBusProxy* proxy, GAsyncResult* result, gpointer user_data) { CameraPortalPrivate* that = static_cast(user_data); RTC_DCHECK(that); Scoped error; Scoped variant( g_dbus_proxy_call_finish(proxy, result, error.receive())); if (!variant) { if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; RTC_LOG(LS_ERROR) << "Failed to access portal:" << error->message; if (that->access_request_signal_id_) { g_dbus_connection_signal_unsubscribe(that->connection_, that->access_request_signal_id_); that->access_request_signal_id_ = 0; } that->OnPortalDone(RequestResponse::kError); } } // static void CameraPortalPrivate::OnResponseSignalEmitted(GDBusConnection* connection, const char* sender_name, const char* object_path, const char* interface_name, const char* signal_name, GVariant* parameters, gpointer user_data) { CameraPortalPrivate* that = static_cast(user_data); RTC_DCHECK(that); uint32_t portal_response; g_variant_get(parameters, "(u@a{sv})", &portal_response, nullptr); if (portal_response) { RTC_LOG(LS_INFO) << "Camera access denied by the XDG portal."; that->OnPortalDone(RequestResponseFromPortalResponse(portal_response)); return; } RTC_LOG(LS_VERBOSE) << "Camera access granted by the XDG portal."; GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); g_dbus_proxy_call( that->proxy_, "OpenPipeWireRemote", g_variant_new("(a{sv})", &builder), G_DBUS_CALL_FLAGS_NONE, /*timeout_msec=*/-1, that->cancellable_, reinterpret_cast(OnOpenResponse), that); } void CameraPortalPrivate::OnOpenResponse(GDBusProxy* proxy, GAsyncResult* result, gpointer user_data) { CameraPortalPrivate* that = static_cast(user_data); RTC_DCHECK(that); Scoped error; Scoped outlist; Scoped variant(g_dbus_proxy_call_with_unix_fd_list_finish( proxy, outlist.receive(), result, error.receive())); if (!variant) { if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; RTC_LOG(LS_ERROR) << "Failed to open PipeWire remote:" << error->message; if (that->access_request_signal_id_) { g_dbus_connection_signal_unsubscribe(that->connection_, that->access_request_signal_id_); that->access_request_signal_id_ = 0; } that->OnPortalDone(RequestResponse::kError); return; } int32_t index; g_variant_get(variant.get(), "(h)", &index); int fd = g_unix_fd_list_get(outlist.get(), index, error.receive()); if (fd == kInvalidPipeWireFd) { RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: " << error->message; that->OnPortalDone(RequestResponse::kError); return; } that->OnPortalDone(RequestResponse::kSuccess, fd); } void CameraPortalPrivate::OnPortalDone(RequestResponse result, int fd) { notifier_->OnCameraRequestResult(result, fd); } CameraPortal::CameraPortal(PortalNotifier* notifier) : private_(std::make_unique(notifier)) {} CameraPortal::~CameraPortal() {} void CameraPortal::Start() { private_->Start(); } } // namespace webrtc