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