GCC Code Coverage Report


Directory: ./
File: panels/privacy/location/cc-location-page.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 181 0.0%
Functions: 0 15 0.0%
Branches: 0 91 0.0%

Line Branch Exec Source
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 *
3 * Copyright (C) 2018 Red Hat, Inc
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Matthias Clasen <mclasen@redhat.com>
19 */
20
21 #include "cc-location-page.h"
22 #include "cc-util.h"
23
24 #include <gio/gdesktopappinfo.h>
25 #include <glib/gi18n.h>
26
27 #define LOCATION_ENABLED "enabled"
28 #define APP_PERMISSIONS_TABLE "location"
29 #define APP_PERMISSIONS_ID "location"
30
31 struct _CcLocationPage
32 {
33 AdwNavigationPage parent_instance;
34
35 GtkLabel *privacy_policy_link;
36
37 GtkListBox *location_apps_list_box;
38 AdwSwitchRow *location_row;
39
40 GSettings *location_settings;
41 GCancellable *cancellable;
42
43 GDBusProxy *perm_store;
44 GVariant *location_apps_perms;
45 GVariant *location_apps_data;
46 GHashTable *location_app_switches;
47 GHashTable *location_app_rows;
48
49 GtkSizeGroup *location_icon_size_group;
50 };
51
52 G_DEFINE_TYPE (CcLocationPage, cc_location_page, ADW_TYPE_NAVIGATION_PAGE)
53
54 typedef struct
55 {
56 CcLocationPage *self;
57 GtkWidget *widget;
58 gchar *app_id;
59 gboolean changing_state;
60 gboolean pending_state;
61 } LocationAppStateData;
62
63 static void
64 location_app_state_data_free (LocationAppStateData *data)
65 {
66 g_free (data->app_id);
67 g_slice_free (LocationAppStateData, data);
68 }
69
70 static void
71 on_perm_store_set_done (GObject *source_object,
72 GAsyncResult *res,
73 gpointer user_data)
74 {
75 g_autoptr(GVariant) results = NULL;
76 g_autoptr(GError) error = NULL;
77 LocationAppStateData *data;
78
79 results = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
80 res,
81 &error);
82 if (results == NULL)
83 {
84 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
85 g_warning ("Failed to store permissions: %s", error->message);
86
87 return;
88 }
89
90 data = (LocationAppStateData *) user_data;
91 data->changing_state = FALSE;
92 gtk_switch_set_state (GTK_SWITCH (data->widget), data->pending_state);
93 }
94
95 static gboolean
96 on_location_app_state_set (GtkSwitch *widget,
97 gboolean state,
98 gpointer user_data)
99 {
100 LocationAppStateData *data = (LocationAppStateData *) user_data;
101 CcLocationPage *self = data->self;
102 GVariant *params;
103 GVariantIter iter;
104 const gchar *key;
105 gchar **value;
106 GVariantBuilder builder;
107 gboolean active_location;
108
109 if (data->changing_state)
110 return TRUE;
111
112 active_location = g_settings_get_boolean (self->location_settings,
113 LOCATION_ENABLED);
114 data->changing_state = TRUE;
115 data->pending_state = active_location && state;
116
117 g_variant_iter_init (&iter, self->location_apps_perms);
118 g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
119 while (g_variant_iter_loop (&iter, "{&s^a&s}", &key, &value))
120 {
121 /* It's OK to drop the entry if it's not in expected format */
122 if (g_strv_length (value) < 2)
123 continue;
124
125 if (g_strcmp0 (data->app_id, key) == 0)
126 value[0] = state ? "EXACT" : "NONE";
127
128 g_variant_builder_add (&builder, "{s^as}", key, value);
129 }
130
131 params = g_variant_new ("(sbsa{sas}v)",
132 APP_PERMISSIONS_TABLE,
133 TRUE,
134 APP_PERMISSIONS_ID,
135 &builder,
136 self->location_apps_data);
137
138 g_dbus_proxy_call (self->perm_store,
139 "Set",
140 params,
141 G_DBUS_CALL_FLAGS_NONE,
142 -1,
143 self->cancellable,
144 on_perm_store_set_done,
145 data);
146
147 return TRUE;
148 }
149
150 static gboolean
151 update_app_switch_state (GValue *value,
152 GVariant *variant,
153 gpointer data)
154 {
155 GtkSwitch *w = GTK_SWITCH (data);
156 gboolean active_location;
157 gboolean active_app;
158 gboolean state;
159
160 active_location = g_variant_get_boolean (variant);
161 active_app = gtk_switch_get_active (w);
162 state = active_location && active_app;
163
164 g_value_set_boolean (value, state);
165
166 return TRUE;
167 }
168
169 static void
170 add_location_app (CcLocationPage *self,
171 const gchar *app_id,
172 gboolean enabled,
173 gint64 last_used)
174 {
175 LocationAppStateData *data;
176 GDesktopAppInfo *app_info;
177 GDateTime *t;
178 GtkWidget *row, *w;
179 GIcon *icon;
180 gchar *last_used_str;
181 gchar *desktop_id;
182
183 w = g_hash_table_lookup (self->location_app_switches, app_id);
184 if (w != NULL)
185 {
186 gtk_switch_set_active (GTK_SWITCH (w), enabled);
187 return;
188 }
189
190 desktop_id = g_strdup_printf ("%s.desktop", app_id);
191 app_info = g_desktop_app_info_new (desktop_id);
192 g_free (desktop_id);
193 if (app_info == NULL)
194 return;
195
196 row = adw_action_row_new ();
197 gtk_list_box_append (self->location_apps_list_box, row);
198
199 icon = g_app_info_get_icon (G_APP_INFO (app_info));
200 w = gtk_image_new_from_gicon (icon);
201 gtk_image_set_icon_size (GTK_IMAGE (w), GTK_ICON_SIZE_LARGE);
202 gtk_widget_set_valign (w, GTK_ALIGN_CENTER);
203 gtk_size_group_add_widget (self->location_icon_size_group, w);
204 adw_action_row_add_prefix (ADW_ACTION_ROW (row), w);
205
206 adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row),
207 g_app_info_get_name (G_APP_INFO (app_info)));
208
209 t = g_date_time_new_from_unix_utc (last_used);
210 last_used_str = cc_util_get_smart_date (t);
211 w = gtk_label_new (last_used_str);
212 g_free (last_used_str);
213 gtk_widget_add_css_class (w, "dim-label");
214 gtk_widget_set_margin_start (w, 12);
215 gtk_widget_set_margin_end (w, 12);
216 gtk_widget_set_valign (w, GTK_ALIGN_CENTER);
217 adw_action_row_add_suffix (ADW_ACTION_ROW (row), w);
218
219 w = gtk_switch_new ();
220 gtk_switch_set_active (GTK_SWITCH (w), enabled);
221 gtk_widget_set_valign (w, GTK_ALIGN_CENTER);
222 adw_action_row_add_suffix (ADW_ACTION_ROW (row), w);
223 adw_action_row_set_activatable_widget (ADW_ACTION_ROW (row), w);
224
225 g_settings_bind_with_mapping (self->location_settings,
226 LOCATION_ENABLED,
227 w,
228 "state",
229 G_SETTINGS_BIND_GET,
230 update_app_switch_state,
231 NULL,
232 g_object_ref (w),
233 g_object_unref);
234
235 g_hash_table_insert (self->location_app_switches,
236 g_strdup (app_id),
237 g_object_ref (w));
238 g_hash_table_insert (self->location_app_rows,
239 g_strdup (app_id),
240 g_object_ref (row));
241
242 data = g_slice_new (LocationAppStateData);
243 data->self = self;
244 data->app_id = g_strdup (app_id);
245 data->widget = w;
246 data->changing_state = FALSE;
247 g_signal_connect_data (w,
248 "state-set",
249 G_CALLBACK (on_location_app_state_set),
250 data,
251 (GClosureNotify) location_app_state_data_free,
252 0);
253 }
254
255 /* Steals permissions and permissions_data references */
256 static void
257 update_perm_store (CcLocationPage *self,
258 GVariant *permissions,
259 GVariant *permissions_data)
260 {
261 GVariantIter iter;
262 const gchar *key;
263 gchar **value;
264 GHashTableIter row_iter;
265 GtkWidget *row;
266
267 g_clear_pointer (&self->location_apps_perms, g_variant_unref);
268 self->location_apps_perms = permissions;
269
270 g_clear_pointer (&self->location_apps_data, g_variant_unref);
271 self->location_apps_data = permissions_data;
272
273 /* We iterate over all rows, if the permissions do not contain the app id of
274 the row, we remove it. */
275 g_hash_table_iter_init (&row_iter, self->location_app_rows);
276 while (g_hash_table_iter_next (&row_iter, (gpointer *) &key, (gpointer *) &row))
277 {
278 if (!g_variant_lookup_value (permissions, key, NULL))
279 {
280 gtk_list_box_remove (self->location_apps_list_box, row);
281 g_hash_table_remove (self->location_app_switches, key);
282 g_hash_table_iter_remove (&row_iter);
283 }
284 }
285
286 g_variant_iter_init (&iter, permissions);
287 while (g_variant_iter_loop (&iter, "{&s^a&s}", &key, &value))
288 {
289 gboolean enabled;
290 gint64 last_used;
291
292 if (g_strv_length (value) < 2)
293 {
294 g_debug ("Permissions for %s in incorrect format, ignoring..", key);
295 continue;
296 }
297
298 enabled = (g_strcmp0 (value[0], "NONE") != 0);
299 last_used = g_ascii_strtoll (value[1], NULL, 10);
300
301 add_location_app (self, key, enabled, last_used);
302 }
303 }
304
305 static void
306 on_perm_store_signal (GDBusProxy *proxy,
307 gchar *sender_name,
308 gchar *signal_name,
309 GVariant *parameters,
310 gpointer user_data)
311 {
312 GVariant *permissions, *permissions_data;
313 g_autoptr(GVariant) boxed_permission_data = NULL;
314 g_autoptr(GVariant) table = NULL;
315 g_autoptr(GVariant) id = NULL;
316
317 if (g_strcmp0 (signal_name, "Changed") != 0)
318 return;
319
320 table = g_variant_get_child_value (parameters, 0);
321 id = g_variant_get_child_value (parameters, 1);
322
323 if (g_strcmp0 (g_variant_get_string (table, NULL), "location") != 0 || g_strcmp0 (g_variant_get_string (id, NULL), "location") != 0)
324 return;
325
326 permissions = g_variant_get_child_value (parameters, 4);
327 boxed_permission_data = g_variant_get_child_value (parameters, 3);
328 permissions_data = g_variant_get_variant (boxed_permission_data);
329 update_perm_store (user_data, permissions, permissions_data);
330 }
331
332 static void
333 on_perm_store_lookup_done(GObject *source_object,
334 GAsyncResult *res,
335 gpointer user_data)
336 {
337 g_autoptr(GError) error = NULL;
338 g_autoptr(GVariant) boxed_permission_data = NULL;
339 GVariant *ret, *permissions, *permissions_data;
340
341 ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
342 res,
343 &error);
344 if (ret == NULL)
345 {
346 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
347 g_warning ("Failed fetch permissions from flatpak permission store: %s",
348 error->message);
349 return;
350 }
351
352 permissions = g_variant_get_child_value (ret, 0);
353 boxed_permission_data = g_variant_get_child_value (ret, 1);
354 permissions_data = g_variant_get_variant (boxed_permission_data);
355 update_perm_store (user_data, permissions, permissions_data);
356
357 g_signal_connect_object (source_object,
358 "g-signal",
359 G_CALLBACK (on_perm_store_signal),
360 user_data,
361 0);
362 }
363
364 static void
365 on_perm_store_ready (GObject *source_object,
366 GAsyncResult *res,
367 gpointer user_data)
368 {
369 g_autoptr(GError) error = NULL;
370 CcLocationPage *self;
371 GDBusProxy *proxy;
372 GVariant *params;
373
374 proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
375 if (proxy == NULL)
376 {
377 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
378 g_warning ("Failed to connect to flatpak permission store: %s",
379 error->message);
380
381 return;
382 }
383 self = user_data;
384 self->perm_store = proxy;
385
386 params = g_variant_new ("(ss)",
387 APP_PERMISSIONS_TABLE,
388 APP_PERMISSIONS_ID);
389 g_dbus_proxy_call (self->perm_store,
390 "Lookup",
391 params,
392 G_DBUS_CALL_FLAGS_NONE,
393 -1,
394 self->cancellable,
395 on_perm_store_lookup_done,
396 self);
397 }
398
399 static void
400 cc_location_page_finalize (GObject *object)
401 {
402 CcLocationPage *self = CC_LOCATION_PAGE (object);
403
404 g_cancellable_cancel (self->cancellable);
405 g_clear_object (&self->cancellable);
406
407 g_clear_object (&self->location_settings);
408 g_clear_object (&self->perm_store);
409 g_clear_object (&self->location_icon_size_group);
410 g_clear_pointer (&self->location_apps_perms, g_variant_unref);
411 g_clear_pointer (&self->location_apps_data, g_variant_unref);
412 g_clear_pointer (&self->location_app_switches, g_hash_table_unref);
413 g_clear_pointer (&self->location_app_rows, g_hash_table_unref);
414
415 G_OBJECT_CLASS (cc_location_page_parent_class)->finalize (object);
416 }
417
418 static void
419 cc_location_page_class_init (CcLocationPageClass *klass)
420 {
421 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
422 GObjectClass *object_class = G_OBJECT_CLASS (klass);
423
424 object_class->finalize = cc_location_page_finalize;
425
426 gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/privacy/location/cc-location-page.ui");
427
428 gtk_widget_class_bind_template_child (widget_class, CcLocationPage, privacy_policy_link);
429 gtk_widget_class_bind_template_child (widget_class, CcLocationPage, location_apps_list_box);
430 gtk_widget_class_bind_template_child (widget_class, CcLocationPage, location_row);
431 }
432
433 static void
434 cc_location_page_init (CcLocationPage *self)
435 {
436 g_autofree gchar *privacy_policy_link = NULL;
437
438 gtk_widget_init_template (GTK_WIDGET (self));
439
440 self->location_icon_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH);
441 self->location_settings = g_settings_new ("org.gnome.system.location");
442
443 self->cancellable = g_cancellable_new ();
444
445 /* Translators: This will be presented as the text of a link to the privacy policy */
446 privacy_policy_link = g_strdup_printf ("<a href='https://location.services.mozilla.com/privacy'>%s</a>", _("Learn about what data is collected, and how it is used."));
447 gtk_label_set_label (GTK_LABEL (self->privacy_policy_link), privacy_policy_link);
448
449 g_settings_bind (self->location_settings,
450 LOCATION_ENABLED,
451 self->location_row,
452 "active",
453 G_SETTINGS_BIND_DEFAULT);
454
455 self->location_app_switches = g_hash_table_new_full (g_str_hash,
456 g_str_equal,
457 g_free,
458 g_object_unref);
459 self->location_app_rows = g_hash_table_new_full (g_str_hash,
460 g_str_equal,
461 g_free,
462 g_object_unref);
463
464 g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
465 G_DBUS_PROXY_FLAGS_NONE,
466 NULL,
467 "org.freedesktop.impl.portal.PermissionStore",
468 "/org/freedesktop/impl/portal/PermissionStore",
469 "org.freedesktop.impl.portal.PermissionStore",
470 self->cancellable,
471 on_perm_store_ready,
472 self);
473 }
474