1 /*
2  * Copyright (C) 2018, 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 "account.h"
23 #include "portal-private.h"
24 
25 typedef struct {
26   XdpPortal *portal;
27   XdpParent *parent;
28   char *parent_handle;
29   char *reason;
30   GTask *task;
31   guint signal_id;
32   char *request_path;
33   guint cancelled_id;
34 } AccountCall;
35 
36 static void
account_call_free(AccountCall * call)37 account_call_free (AccountCall *call)
38 {
39   g_debug ("freeing AccountCall");
40   if (call->parent)
41     {
42       call->parent->parent_unexport (call->parent);
43       xdp_parent_free (call->parent);
44     }
45   g_free (call->parent_handle);
46 
47   if (call->signal_id)
48     g_dbus_connection_signal_unsubscribe (call->portal->bus, call->signal_id);
49 
50   if (call->cancelled_id)
51     g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
52 
53   g_free (call->request_path);
54 
55   g_object_unref (call->portal);
56   g_object_unref (call->task);
57 
58   g_free (call->reason);
59 
60   g_free (call);
61 }
62 
63 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)64 response_received (GDBusConnection *bus,
65                    const char *sender_name,
66                    const char *object_path,
67                    const char *interface_name,
68                    const char *signal_name,
69                    GVariant *parameters,
70                    gpointer data)
71 {
72   AccountCall *call = data;
73   guint32 response;
74   g_autoptr(GVariant) ret = NULL;
75 
76   if (call->cancelled_id)
77     {
78       g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
79       call->cancelled_id = 0;
80     }
81 
82   g_variant_get (parameters, "(u@a{sv})", &response, &ret);
83 
84   if (response == 0)
85     g_task_return_pointer (call->task, g_variant_ref (ret), (GDestroyNotify)g_variant_unref);
86   else if (response == 1)
87     g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Account call canceled user");
88   else
89     g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Account call failed");
90 
91   account_call_free (call);
92 }
93 
94 static void get_user_information (AccountCall *call);
95 
96 static void
parent_exported(XdpParent * parent,const char * handle,gpointer data)97 parent_exported (XdpParent *parent,
98                  const char *handle,
99                  gpointer data)
100 {
101   AccountCall *call = data;
102   call->parent_handle = g_strdup (handle);
103   get_user_information (call);
104 }
105 
106 static void
cancelled_cb(GCancellable * cancellable,gpointer data)107 cancelled_cb (GCancellable *cancellable,
108               gpointer data)
109 {
110   AccountCall *call = data;
111 
112   g_dbus_connection_call (call->portal->bus,
113                           PORTAL_BUS_NAME,
114                           call->request_path,
115                           REQUEST_INTERFACE,
116                           "Close",
117                           NULL,
118                           NULL,
119                           G_DBUS_CALL_FLAGS_NONE,
120                           -1,
121                           NULL, NULL, NULL);
122 
123   g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Account call canceled by caller");
124 
125   account_call_free (call);
126 }
127 
128 static void
call_returned(GObject * object,GAsyncResult * result,gpointer data)129 call_returned (GObject *object,
130                GAsyncResult *result,
131                gpointer data)
132 {
133   GDBusConnection *bus = G_DBUS_CONNECTION (object);
134   AccountCall *call = data;
135   GError *error = NULL;
136   g_autoptr(GVariant) ret = NULL;
137 
138   ret = g_dbus_connection_call_finish (bus, result, &error);
139   if (error)
140     {
141       g_task_return_error (call->task, error);
142       account_call_free (call);
143     }
144 }
145 
146 static void
get_user_information(AccountCall * call)147 get_user_information (AccountCall *call)
148 {
149   GVariantBuilder options;
150   g_autofree char *token = NULL;
151   GCancellable *cancellable;
152 
153   if (call->parent_handle == NULL)
154     {
155       call->parent->parent_export (call->parent, parent_exported, call);
156       return;
157     }
158 
159   token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
160   call->request_path = g_strconcat (REQUEST_PATH_PREFIX, call->portal->sender, "/", token, NULL);
161   call->signal_id = g_dbus_connection_signal_subscribe (call->portal->bus,
162                                                         PORTAL_BUS_NAME,
163                                                         REQUEST_INTERFACE,
164                                                         "Response",
165                                                         call->request_path,
166                                                         NULL,
167                                                         G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
168                                                         response_received,
169                                                         call,
170                                                         NULL);
171 
172   cancellable = g_task_get_cancellable (call->task);
173   if (cancellable)
174     call->cancelled_id = g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), call);
175 
176   g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
177   g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
178   g_variant_builder_add (&options, "{sv}", "reason", g_variant_new_string (call->reason));
179 
180   g_dbus_connection_call (call->portal->bus,
181                           PORTAL_BUS_NAME,
182                           PORTAL_OBJECT_PATH,
183                           "org.freedesktop.portal.Account",
184                           "GetUserInformation",
185                           g_variant_new ("(sa{sv})", call->parent_handle, &options),
186                           NULL,
187                           G_DBUS_CALL_FLAGS_NONE,
188                           -1,
189                           NULL,
190                           call_returned,
191                           call);
192 }
193 
194 /**
195  * xdp_portal_get_user_information:
196  * @portal: a [class@Portal]
197  * @parent: (nullable): parent window information
198  * @reason: (nullable): a string that can be shown in the dialog to explain
199  *    why the information is needed
200  * @flags: options for this call
201  * @cancellable: (nullable): optional [class@Gio.Cancellable]
202  * @callback: (scope async): a callback to call when the request is done
203  * @data: (closure): data to pass to @callback
204  *
205  * Gets information about the user.
206  *
207  * When the request is done, @callback will be called. You can then
208  * call [method@Portal.get_user_information_finish] to get the results.
209  */
210 void
xdp_portal_get_user_information(XdpPortal * portal,XdpParent * parent,const char * reason,XdpUserInformationFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)211 xdp_portal_get_user_information (XdpPortal *portal,
212                                  XdpParent *parent,
213                                  const char *reason,
214                                  XdpUserInformationFlags flags,
215                                  GCancellable *cancellable,
216                                  GAsyncReadyCallback  callback,
217                                  gpointer data)
218 {
219   AccountCall *call = NULL;
220 
221   g_return_if_fail (XDP_IS_PORTAL (portal));
222   g_return_if_fail (flags == XDP_USER_INFORMATION_FLAG_NONE);
223 
224   call = g_new0 (AccountCall, 1);
225   call->portal = g_object_ref (portal);
226   if (parent)
227     call->parent = xdp_parent_copy (parent);
228   else
229     call->parent_handle = g_strdup ("");
230   call->reason = g_strdup (reason);
231   call->task = g_task_new (portal, cancellable, callback, data);
232   g_task_set_source_tag (call->task, xdp_portal_get_user_information);
233 
234   get_user_information (call);
235 }
236 
237 /**
238  * xdp_portal_get_user_information_finish:
239  * @portal: a [class@Portal]
240  * @result: a [iface@Gio.AsyncResult]
241  * @error: return location for an error
242  *
243  * Finishes the get-user-information request, and returns
244  * the result in the form of a [struct@GLib.Variant] dictionary containing
245  * the following fields:
246  *
247  * - id `s`: the user ID
248  * - name `s`: the users real name
249  * - image `s`: the uri of an image file for the users avatar picture
250  *
251  * Returns: (transfer full): a [struct@GLib.Variant] dictionary with user information
252  */
253 GVariant *
xdp_portal_get_user_information_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)254 xdp_portal_get_user_information_finish (XdpPortal *portal,
255                                         GAsyncResult *result,
256                                         GError **error)
257 {
258   g_return_val_if_fail (XDP_IS_PORTAL (portal), NULL);
259   g_return_val_if_fail (g_task_is_valid (result, portal), NULL);
260   g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_get_user_information, NULL);
261 
262   return g_task_propagate_pointer (G_TASK (result), error);
263 }
264