1 /*
2 * Copyright (C) 2019, Matthias Clasen
3 *
4 * This file is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as
6 * published by the Free Software Foundation, version 3.0 of the
7 * License.
8 *
9 * This file is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * SPDX-License-Identifier: LGPL-3.0-only
18 */
19
20 #include "config.h"
21
22 #include <gio/gunixfdlist.h>
23 #include "camera.h"
24 #include "session-private.h"
25 #include "portal-private.h"
26
27 /**
28 * xdp_portal_is_camera_present:
29 * @portal: a [class@Portal]
30 *
31 * Returns whether any camera are present.
32 *
33 * Returns: `TRUE` if the system has cameras
34 */
35 gboolean
xdp_portal_is_camera_present(XdpPortal * portal)36 xdp_portal_is_camera_present (XdpPortal *portal)
37 {
38 g_autoptr(GError) error = NULL;
39 g_autoptr(GVariant) ret = NULL;
40 gboolean result;
41
42 g_return_val_if_fail (XDP_IS_PORTAL (portal), FALSE);
43
44 ret = g_dbus_connection_call_sync (portal->bus,
45 PORTAL_BUS_NAME,
46 PORTAL_OBJECT_PATH,
47 "org.freedesktop.DBus.Properties",
48 "Get",
49 g_variant_new ("(ss)", "org.freedesktop.portal.Camera", "IsCameraPresent"),
50 G_VARIANT_TYPE_BOOLEAN,
51 G_DBUS_CALL_FLAGS_NONE,
52 -1,
53 NULL,
54 &error);
55 if (!ret)
56 {
57 g_warning ("Failed to get IsCameraPresent property: %s", error->message);
58 return FALSE;
59 }
60
61 g_variant_get (ret, "(b)", &result);
62
63 return result;
64 }
65
66 typedef struct {
67 XdpPortal *portal;
68 guint signal_id;
69 GCancellable *cancellable;
70 GTask *task;
71 char *request_path;
72 guint cancelled_id;
73 } AccessCameraCall;
74
75 static void
access_camera_call_free(AccessCameraCall * call)76 access_camera_call_free (AccessCameraCall *call)
77 {
78 if (call->signal_id)
79 g_dbus_connection_signal_unsubscribe (call->portal->bus, call->signal_id);
80
81 if (call->cancelled_id)
82 g_signal_handler_disconnect (call->cancellable, call->cancelled_id);
83
84 g_free (call->request_path);
85
86 if (call->cancellable)
87 g_object_unref (call->cancellable);
88 g_object_unref (call->portal);
89 g_object_unref (call->task);
90
91 g_free (call);
92 }
93
94 static void
response_received(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)95 response_received (GDBusConnection *bus,
96 const char *sender_name,
97 const char *object_path,
98 const char *interface_name,
99 const char *signal_name,
100 GVariant *parameters,
101 gpointer data)
102 {
103 AccessCameraCall *call = data;
104 guint32 response;
105 g_autoptr(GVariant) ret = NULL;
106
107 if (call->cancelled_id)
108 {
109 g_signal_handler_disconnect (call->cancellable, call->cancelled_id);
110 call->cancelled_id = 0;
111 }
112
113 g_variant_get (parameters, "(u@a{sv})", &response, &ret);
114
115 if (response == 0)
116 g_task_return_boolean (call->task, TRUE);
117 else if (response == 1)
118 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Camera access canceled");
119 else
120 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Camera access failed");
121
122 access_camera_call_free (call);
123 }
124
125 static void
cancelled_cb(GCancellable * cancellable,gpointer data)126 cancelled_cb (GCancellable *cancellable,
127 gpointer data)
128 {
129 AccessCameraCall *call = data;
130
131 g_debug ("Calling Close");
132 g_dbus_connection_call (call->portal->bus,
133 PORTAL_BUS_NAME,
134 call->request_path,
135 REQUEST_INTERFACE,
136 "Close",
137 NULL,
138 NULL,
139 G_DBUS_CALL_FLAGS_NONE,
140 -1,
141 NULL, NULL, NULL);
142
143 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "AccessCamera call canceled by caller");
144
145 access_camera_call_free (call);
146 }
147
148 static void
call_returned(GObject * object,GAsyncResult * result,gpointer data)149 call_returned (GObject *object,
150 GAsyncResult *result,
151 gpointer data)
152 {
153 AccessCameraCall *call = data;
154 GError *error = NULL;
155 g_autoptr(GVariant) ret = NULL;
156
157 ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
158 if (error)
159 {
160 g_task_return_error (call->task, error);
161 access_camera_call_free (call);
162 }
163 }
164
165 static void
access_camera(AccessCameraCall * call)166 access_camera (AccessCameraCall *call)
167 {
168 GVariantBuilder options;
169 g_autofree char *token = NULL;
170
171 token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
172 call->request_path = g_strconcat (REQUEST_PATH_PREFIX, call->portal->sender, "/", token, NULL);
173 call->signal_id = g_dbus_connection_signal_subscribe (call->portal->bus,
174 PORTAL_BUS_NAME,
175 REQUEST_INTERFACE,
176 "Response",
177 call->request_path,
178 NULL,
179 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
180 response_received,
181 call,
182 NULL);
183
184 if (call->cancellable)
185 call->cancelled_id = g_signal_connect (call->cancellable, "cancelled", G_CALLBACK (cancelled_cb), call);
186
187 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
188 g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
189
190 g_debug ("Calling AccessCamera");
191 g_dbus_connection_call (call->portal->bus,
192 PORTAL_BUS_NAME,
193 PORTAL_OBJECT_PATH,
194 "org.freedesktop.portal.Camera",
195 "AccessCamera",
196 g_variant_new ("(a{sv})", &options),
197 NULL,
198 G_DBUS_CALL_FLAGS_NONE,
199 -1,
200 NULL,
201 call_returned,
202 call);
203 }
204
205 /**
206 * xdp_portal_access_camera:
207 * @portal: a [class@Portal]
208 * @parent: (nullable): parent window information
209 * @flags: options for this call
210 * @cancellable: (nullable): optional [class@Gio.Cancellable]
211 * @callback: (scope async): a callback to call when the request is done
212 * @data: (closure): data to pass to @callback
213 *
214 * Request access to a camera.
215 *
216 * When the request is done, @callback will be called.
217 * You can then call [method@Portal.access_camera_finish]
218 * to get the results.
219 */
220 void
xdp_portal_access_camera(XdpPortal * portal,XdpParent * parent,XdpCameraFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)221 xdp_portal_access_camera (XdpPortal *portal,
222 XdpParent *parent,
223 XdpCameraFlags flags,
224 GCancellable *cancellable,
225 GAsyncReadyCallback callback,
226 gpointer data)
227 {
228 AccessCameraCall *call;
229
230 g_return_if_fail (XDP_IS_PORTAL (portal));
231 g_return_if_fail (flags == XDP_CAMERA_FLAG_NONE);
232
233 call = g_new0 (AccessCameraCall, 1);
234 call->portal = g_object_ref (portal);
235 if (cancellable)
236 call->cancellable = g_object_ref (cancellable);
237 call->task = g_task_new (portal, NULL, callback, data);
238 g_task_set_source_tag (call->task, xdp_portal_access_camera);
239
240 access_camera (call);
241 }
242
243 /**
244 * xdp_portal_access_camera_finish:
245 * @portal: a [class@Portal]
246 * @result: a [iface@Gio.AsyncResult]
247 * @error: return location for an error
248 *
249 * Finishes a camera acess request, and returns
250 * the result as a boolean.
251 *
252 * If the access was granted, you can then call
253 * [method@Portal.open_pipewire_remote_for_camera]
254 * to obtain a pipewire remote.
255 *
256 * Returns: `TRUE` if access to a camera was granted
257 */
258 gboolean
xdp_portal_access_camera_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)259 xdp_portal_access_camera_finish (XdpPortal *portal,
260 GAsyncResult *result,
261 GError **error)
262 {
263 g_return_val_if_fail (XDP_IS_PORTAL (portal), FALSE);
264 g_return_val_if_fail (g_task_is_valid (result, portal), FALSE);
265 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_access_camera, FALSE);
266
267 return g_task_propagate_boolean (G_TASK (result), error);
268 }
269
270 /**
271 * xdp_portal_open_pipewire_remote_for_camera:
272 * @portal: a [class@Portal]
273 *
274 * Opens a file descriptor to the pipewire remote where the camera
275 * nodes are available. The file descriptor should be used to create
276 * a pw_remote object, by using pw_remote_connect_fd(). Only the
277 * camera nodes will be available from this pipewire node.
278 *
279 * Returns: the file descriptor
280 */
281 int
xdp_portal_open_pipewire_remote_for_camera(XdpPortal * portal)282 xdp_portal_open_pipewire_remote_for_camera (XdpPortal *portal)
283 {
284 GVariantBuilder options;
285 g_autoptr(GError) error = NULL;
286 g_autoptr(GVariant) ret = NULL;
287 g_autoptr(GUnixFDList) fd_list = NULL;
288 int fd_out;
289
290 g_return_val_if_fail (XDP_IS_PORTAL (portal), -1);
291
292 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
293 ret = g_dbus_connection_call_with_unix_fd_list_sync (portal->bus,
294 PORTAL_BUS_NAME,
295 PORTAL_OBJECT_PATH,
296 "org.freedesktop.portal.Camera",
297 "OpenPipeWireRemote",
298 g_variant_new ("(a{sv})", &options),
299 G_VARIANT_TYPE ("(h)"),
300 G_DBUS_CALL_FLAGS_NONE,
301 -1,
302 NULL,
303 &fd_list,
304 NULL,
305 &error);
306
307 if (ret == NULL)
308 {
309 g_warning ("Failed to get pipewire fd: %s", error->message);
310 return -1;
311 }
312
313 g_variant_get (ret, "(h)", &fd_out);
314
315 return g_unix_fd_list_get (fd_list, fd_out, NULL);
316 }
317