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