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 #include "modules/portal/xdg_desktop_portal_utils.h"
11
12 #include <string>
13
14 #include "absl/strings/string_view.h"
15 #include "modules/portal/scoped_glib.h"
16 #include "rtc_base/logging.h"
17
18 namespace webrtc {
19 namespace xdg_portal {
20
RequestResponseToString(RequestResponse request)21 std::string RequestResponseToString(RequestResponse request) {
22 switch (request) {
23 case RequestResponse::kUnknown:
24 return "kUnknown";
25 case RequestResponse::kSuccess:
26 return "kSuccess";
27 case RequestResponse::kUserCancelled:
28 return "kUserCancelled";
29 case RequestResponse::kError:
30 return "kError";
31 default:
32 return "Uknown";
33 }
34 }
35
RequestResponseFromPortalResponse(uint32_t portal_response)36 RequestResponse RequestResponseFromPortalResponse(uint32_t portal_response) {
37 // See:
38 // https://docs.flatpak.org/en/latest/portal-api-reference.html#gdbus-signal-org-freedesktop-portal-Request.Response
39 switch (portal_response) {
40 case 0:
41 return RequestResponse::kSuccess;
42 case 1:
43 return RequestResponse::kUserCancelled;
44 case 2:
45 return RequestResponse::kError;
46 default:
47 return RequestResponse::kUnknown;
48 }
49 }
50
PrepareSignalHandle(absl::string_view token,GDBusConnection * connection)51 std::string PrepareSignalHandle(absl::string_view token,
52 GDBusConnection* connection) {
53 Scoped<char> sender(
54 g_strdup(g_dbus_connection_get_unique_name(connection) + 1));
55 for (int i = 0; sender.get()[i]; ++i) {
56 if (sender.get()[i] == '.') {
57 sender.get()[i] = '_';
58 }
59 }
60 const char* handle =
61 g_strconcat(kDesktopRequestObjectPath, "/", sender.get(), "/",
62 std::string(token).c_str(), /*end of varargs*/ nullptr);
63 return handle;
64 }
65
SetupRequestResponseSignal(absl::string_view object_path,const GDBusSignalCallback callback,gpointer user_data,GDBusConnection * connection)66 uint32_t SetupRequestResponseSignal(absl::string_view object_path,
67 const GDBusSignalCallback callback,
68 gpointer user_data,
69 GDBusConnection* connection) {
70 return g_dbus_connection_signal_subscribe(
71 connection, kDesktopBusName, kRequestInterfaceName, "Response",
72 std::string(object_path).c_str(), /*arg0=*/nullptr,
73 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, callback, user_data,
74 /*user_data_free_func=*/nullptr);
75 }
76
RequestSessionProxy(absl::string_view interface_name,const ProxyRequestCallback proxy_request_callback,GCancellable * cancellable,gpointer user_data)77 void RequestSessionProxy(absl::string_view interface_name,
78 const ProxyRequestCallback proxy_request_callback,
79 GCancellable* cancellable,
80 gpointer user_data) {
81 g_dbus_proxy_new_for_bus(
82 G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, /*info=*/nullptr,
83 kDesktopBusName, kDesktopObjectPath, std::string(interface_name).c_str(),
84 cancellable,
85 reinterpret_cast<GAsyncReadyCallback>(proxy_request_callback), user_data);
86 }
87
SetupSessionRequestHandlers(absl::string_view portal_prefix,const SessionRequestCallback session_request_callback,const SessionRequestResponseSignalHandler request_response_signale_handler,GDBusConnection * connection,GDBusProxy * proxy,GCancellable * cancellable,std::string & portal_handle,guint & session_request_signal_id,gpointer user_data)88 void SetupSessionRequestHandlers(
89 absl::string_view portal_prefix,
90 const SessionRequestCallback session_request_callback,
91 const SessionRequestResponseSignalHandler request_response_signale_handler,
92 GDBusConnection* connection,
93 GDBusProxy* proxy,
94 GCancellable* cancellable,
95 std::string& portal_handle,
96 guint& session_request_signal_id,
97 gpointer user_data) {
98 GVariantBuilder builder;
99 Scoped<char> variant_string;
100
101 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
102 variant_string =
103 g_strdup_printf("%.*s_session%d", static_cast<int>(portal_prefix.size()),
104 portal_prefix.data(), g_random_int_range(0, G_MAXINT));
105 g_variant_builder_add(&builder, "{sv}", "session_handle_token",
106 g_variant_new_string(variant_string.get()));
107
108 variant_string =
109 g_strdup_printf("%.*s_%d", static_cast<int>(portal_prefix.size()),
110 portal_prefix.data(), g_random_int_range(0, G_MAXINT));
111 g_variant_builder_add(&builder, "{sv}", "handle_token",
112 g_variant_new_string(variant_string.get()));
113
114 portal_handle = PrepareSignalHandle(variant_string.get(), connection);
115 session_request_signal_id = SetupRequestResponseSignal(
116 portal_handle.c_str(), request_response_signale_handler, user_data,
117 connection);
118
119 RTC_LOG(LS_INFO) << "Desktop session requested.";
120 g_dbus_proxy_call(
121 proxy, "CreateSession", g_variant_new("(a{sv})", &builder),
122 G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable,
123 reinterpret_cast<GAsyncReadyCallback>(session_request_callback),
124 user_data);
125 }
126
StartSessionRequest(absl::string_view prefix,absl::string_view session_handle,const StartRequestResponseSignalHandler signal_handler,const SessionStartRequestedHandler session_started_handler,GDBusProxy * proxy,GDBusConnection * connection,GCancellable * cancellable,guint & start_request_signal_id,std::string & start_handle,gpointer user_data)127 void StartSessionRequest(
128 absl::string_view prefix,
129 absl::string_view session_handle,
130 const StartRequestResponseSignalHandler signal_handler,
131 const SessionStartRequestedHandler session_started_handler,
132 GDBusProxy* proxy,
133 GDBusConnection* connection,
134 GCancellable* cancellable,
135 guint& start_request_signal_id,
136 std::string& start_handle,
137 gpointer user_data) {
138 GVariantBuilder builder;
139 Scoped<char> variant_string;
140
141 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
142 variant_string =
143 g_strdup_printf("%.*s%d", static_cast<int>(prefix.size()), prefix.data(),
144 g_random_int_range(0, G_MAXINT));
145 g_variant_builder_add(&builder, "{sv}", "handle_token",
146 g_variant_new_string(variant_string.get()));
147
148 start_handle = PrepareSignalHandle(variant_string.get(), connection);
149 start_request_signal_id = SetupRequestResponseSignal(
150 start_handle.c_str(), signal_handler, user_data, connection);
151
152 // "Identifier for the application window", this is Wayland, so not "x11:...".
153 const char parent_window[] = "";
154
155 RTC_LOG(LS_INFO) << "Starting the portal session.";
156 g_dbus_proxy_call(
157 proxy, "Start",
158 g_variant_new("(osa{sv})", std::string(session_handle).c_str(),
159 parent_window, &builder),
160 G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable,
161 reinterpret_cast<GAsyncReadyCallback>(session_started_handler),
162 user_data);
163 }
164
TearDownSession(absl::string_view session_handle,GDBusProxy * proxy,GCancellable * cancellable,GDBusConnection * connection)165 void TearDownSession(absl::string_view session_handle,
166 GDBusProxy* proxy,
167 GCancellable* cancellable,
168 GDBusConnection* connection) {
169 if (!session_handle.empty()) {
170 Scoped<GDBusMessage> message(g_dbus_message_new_method_call(
171 kDesktopBusName, std::string(session_handle).c_str(),
172 kSessionInterfaceName, "Close"));
173 if (message.get()) {
174 Scoped<GError> error;
175 g_dbus_connection_send_message(connection, message.get(),
176 G_DBUS_SEND_MESSAGE_FLAGS_NONE,
177 /*out_serial=*/nullptr, error.receive());
178 if (error.get()) {
179 RTC_LOG(LS_ERROR) << "Failed to close the session: " << error->message;
180 }
181 }
182 }
183
184 if (cancellable) {
185 g_cancellable_cancel(cancellable);
186 g_object_unref(cancellable);
187 }
188
189 if (proxy) {
190 g_object_unref(proxy);
191 }
192 }
193
194 } // namespace xdg_portal
195 } // namespace webrtc
196