GCC Code Coverage Report


Directory: ./
File: panels/privacy/camera/cc-camera-page.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 172 0.0%
Functions: 0 15 0.0%
Branches: 0 95 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-camera-page.h"
22 #include "cc-util.h"
23
24 #include <gio/gdesktopappinfo.h>
25 #include <glib/gi18n.h>
26
27 #define APP_PERMISSIONS_TABLE "devices"
28 #define APP_PERMISSIONS_ID "camera"
29
30 struct _CcCameraPage
31 {
32 AdwNavigationPage parent_instance;
33
34 GtkListBox *camera_apps_list_box;
35 AdwSwitchRow *camera_row;
36
37 GSettings *privacy_settings;
38 GCancellable *cancellable;
39
40 GDBusProxy *perm_store;
41 GVariant *camera_apps_perms;
42 GVariant *camera_apps_data;
43 GHashTable *camera_app_switches;
44 GHashTable *camera_app_rows;
45
46 GtkSizeGroup *camera_icon_size_group;
47 };
48
49 G_DEFINE_TYPE (CcCameraPage, cc_camera_page, ADW_TYPE_NAVIGATION_PAGE)
50
51 typedef struct
52 {
53 CcCameraPage *self;
54 GtkWidget *widget;
55 gchar *app_id;
56 gboolean changing_state;
57 gboolean pending_state;
58 } CameraAppStateData;
59
60 static void
61 camera_app_state_data_free (CameraAppStateData *data)
62 {
63 g_free (data->app_id);
64 g_slice_free (CameraAppStateData, data);
65 }
66
67 static void
68 on_perm_store_set_done (GObject *source_object,
69 GAsyncResult *res,
70 gpointer user_data)
71 {
72 g_autoptr(GVariant) results = NULL;
73 g_autoptr(GError) error = NULL;
74 CameraAppStateData *data;
75
76 results = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
77 res,
78 &error);
79 if (results == NULL)
80 {
81 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
82 g_warning ("Failed to store permissions: %s", error->message);
83 return;
84 }
85
86 data = (CameraAppStateData *) user_data;
87 data->changing_state = FALSE;
88 gtk_switch_set_state (GTK_SWITCH (data->widget), data->pending_state);
89 }
90
91 static gboolean
92 on_camera_app_state_set (GtkSwitch *widget,
93 gboolean state,
94 gpointer user_data)
95 {
96 CameraAppStateData *data = (CameraAppStateData *) user_data;
97 GVariantBuilder builder;
98 CcCameraPage *self;
99 GVariantIter iter;
100 GVariant *params;
101 const gchar *key;
102 gchar **value;
103 gboolean active_camera;
104
105 self = data->self;
106
107 if (data->changing_state)
108 return TRUE;
109
110 active_camera = !g_settings_get_boolean (self->privacy_settings,
111 "disable-camera");
112 data->changing_state = TRUE;
113 data->pending_state = active_camera && state;
114
115 g_variant_iter_init (&iter, self->camera_apps_perms);
116 g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
117 while (g_variant_iter_loop (&iter, "{&s^a&s}", &key, &value))
118 {
119 gchar *tmp = NULL;
120
121 /* It's OK to drop the entry if it's not in expected format */
122 if (g_strv_length (value) != 1)
123 continue;
124
125 if (g_strcmp0 (data->app_id, key) == 0)
126 {
127 tmp = value[0];
128 value[0] = state ? "yes" : "no";
129 }
130
131 g_variant_builder_add (&builder, "{s^as}", key, value);
132
133 if (tmp != NULL)
134 value[0] = tmp;
135 }
136
137 params = g_variant_new ("(sbsa{sas}v)",
138 APP_PERMISSIONS_TABLE,
139 TRUE,
140 APP_PERMISSIONS_ID,
141 &builder,
142 self->camera_apps_data);
143
144 g_dbus_proxy_call (self->perm_store,
145 "Set",
146 params,
147 G_DBUS_CALL_FLAGS_NONE,
148 -1,
149 self->cancellable,
150 on_perm_store_set_done,
151 data);
152
153 return TRUE;
154 }
155
156 static gboolean
157 update_app_switch_state (GValue *value,
158 GVariant *variant,
159 gpointer data)
160 {
161 GtkSwitch *w = GTK_SWITCH (data);
162 gboolean active_camera;
163 gboolean active_app;
164 gboolean state;
165
166 active_camera = !g_variant_get_boolean (variant);
167 active_app = gtk_switch_get_active (w);
168 state = active_camera && active_app;
169
170 g_value_set_boolean (value, state);
171
172 return TRUE;
173 }
174
175 static void
176 add_camera_app (CcCameraPage *self,
177 const gchar *app_id,
178 gboolean enabled)
179 {
180 g_autofree gchar *desktop_id = NULL;
181 CameraAppStateData *data;
182 GDesktopAppInfo *app_info;
183 GtkWidget *row, *w;
184 GIcon *icon;
185
186 w = g_hash_table_lookup (self->camera_app_switches, app_id);
187 if (w != NULL)
188 {
189 gtk_switch_set_active (GTK_SWITCH (w), enabled);
190 return;
191 }
192
193 desktop_id = g_strdup_printf ("%s.desktop", app_id);
194 app_info = g_desktop_app_info_new (desktop_id);
195 if (!app_info)
196 return;
197
198 row = adw_action_row_new ();
199 gtk_list_box_append (self->camera_apps_list_box, row);
200
201 icon = g_app_info_get_icon (G_APP_INFO (app_info));
202 w = gtk_image_new_from_gicon (icon);
203 gtk_image_set_icon_size (GTK_IMAGE (w), GTK_ICON_SIZE_LARGE);
204 gtk_widget_set_valign (w, GTK_ALIGN_CENTER);
205 gtk_size_group_add_widget (self->camera_icon_size_group, w);
206 adw_action_row_add_prefix (ADW_ACTION_ROW (row), w);
207
208 adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row),
209 g_app_info_get_name (G_APP_INFO (app_info)));
210
211 w = gtk_switch_new ();
212 gtk_switch_set_active (GTK_SWITCH (w), enabled);
213 gtk_widget_set_valign (w, GTK_ALIGN_CENTER);
214 adw_action_row_add_suffix (ADW_ACTION_ROW (row), w);
215 adw_action_row_set_activatable_widget (ADW_ACTION_ROW (row), w);
216
217 g_settings_bind_with_mapping (self->privacy_settings,
218 "disable-camera",
219 w,
220 "state",
221 G_SETTINGS_BIND_GET,
222 update_app_switch_state,
223 NULL,
224 g_object_ref (w),
225 g_object_unref);
226
227 g_hash_table_insert (self->camera_app_switches,
228 g_strdup (app_id),
229 g_object_ref (w));
230
231 g_hash_table_insert (self->camera_app_rows,
232 g_strdup (app_id),
233 g_object_ref (row));
234
235 data = g_slice_new (CameraAppStateData);
236 data->self = self;
237 data->app_id = g_strdup (app_id);
238 data->widget = w;
239 data->changing_state = FALSE;
240 g_signal_connect_data (G_OBJECT (w),
241 "state-set",
242 G_CALLBACK (on_camera_app_state_set),
243 data,
244 (GClosureNotify) camera_app_state_data_free,
245 0);
246 }
247
248 /* Steals permissions and permissions_data references */
249 static void
250 update_perm_store (CcCameraPage *self,
251 GVariant *permissions,
252 GVariant *permissions_data)
253 {
254 GVariantIter iter;
255 const gchar *key;
256 gchar **value;
257 GHashTableIter row_iter;
258 GtkWidget *row;
259
260 g_clear_pointer (&self->camera_apps_perms, g_variant_unref);
261 self->camera_apps_perms = permissions;
262 g_clear_pointer (&self->camera_apps_data, g_variant_unref);
263 self->camera_apps_data = permissions_data;
264
265 /* We iterate over all rows, if the permissions do not contain the app id of
266 the row, we remove it. */
267 g_hash_table_iter_init (&row_iter, self->camera_app_rows);
268 while (g_hash_table_iter_next (&row_iter, (gpointer *) &key, (gpointer *) &row))
269 {
270 if (!g_variant_lookup_value (permissions, key, NULL))
271 {
272 gtk_list_box_remove (self->camera_apps_list_box, row);
273 g_hash_table_remove (self->camera_app_switches, key);
274 g_hash_table_iter_remove (&row_iter);
275 }
276 }
277
278 g_variant_iter_init (&iter, permissions);
279 while (g_variant_iter_loop (&iter, "{&s^a&s}", &key, &value))
280 {
281 gboolean enabled;
282
283 if (g_strv_length (value) != 1)
284 {
285 g_debug ("Permissions for %s in incorrect format, ignoring..", key);
286 continue;
287 }
288
289 enabled = (g_strcmp0 (value[0], "no") != 0);
290
291 add_camera_app (self, key, enabled);
292 }
293 }
294
295 static void
296 on_perm_store_signal (GDBusProxy *proxy,
297 gchar *sender_name,
298 gchar *signal_name,
299 GVariant *parameters,
300 gpointer user_data)
301 {
302 GVariant *permissions, *permissions_data;
303 g_autoptr(GVariant) boxed_permission_data = NULL;
304 g_autoptr(GVariant) table = NULL;
305 g_autoptr(GVariant) id = NULL;
306
307 if (g_strcmp0 (signal_name, "Changed") != 0)
308 return;
309
310 table = g_variant_get_child_value (parameters, 0);
311 id = g_variant_get_child_value (parameters, 1);
312
313 if (g_strcmp0 (g_variant_get_string (table, NULL), "devices") != 0 || g_strcmp0 (g_variant_get_string (id, NULL), "camera") != 0)
314 return;
315
316 permissions = g_variant_get_child_value (parameters, 4);
317 boxed_permission_data = g_variant_get_child_value (parameters, 3);
318 permissions_data = g_variant_get_variant (boxed_permission_data);
319 update_perm_store (user_data, permissions, permissions_data);
320 }
321
322 static void
323 on_perm_store_lookup_done (GObject *source_object,
324 GAsyncResult *res,
325 gpointer user_data)
326 {
327 CcCameraPage *self = user_data;
328 GVariant *ret, *permissions, *permissions_data;
329 g_autoptr(GVariant) boxed_permission_data = NULL;
330 g_autoptr(GError) error = NULL;
331
332 ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
333 res,
334 &error);
335 if (ret == NULL)
336 {
337 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
338 g_warning ("Failed fetch permissions from flatpak permission store: %s", error->message);
339 return;
340 }
341
342 permissions = g_variant_get_child_value (ret, 0);
343 boxed_permission_data = g_variant_get_child_value (ret, 1);
344 permissions_data = g_variant_get_variant (boxed_permission_data);
345 update_perm_store (user_data, permissions, permissions_data);
346
347 g_signal_connect_object (source_object,
348 "g-signal",
349 G_CALLBACK (on_perm_store_signal),
350 self,
351 0);
352 }
353
354 static void
355 on_perm_store_ready (GObject *source_object,
356 GAsyncResult *res,
357 gpointer user_data)
358 {
359 CcCameraPage *self;
360 GVariant *params;
361 g_autoptr(GError) error = NULL;
362 GDBusProxy *proxy;
363
364 proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
365 if (proxy == NULL)
366 {
367 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
368 g_warning ("Failed to connect to flatpak permission store: %s", error->message);
369 return;
370 }
371 self = user_data;
372 self->perm_store = proxy;
373
374 params = g_variant_new ("(ss)",
375 APP_PERMISSIONS_TABLE,
376 APP_PERMISSIONS_ID);
377 g_dbus_proxy_call (self->perm_store,
378 "Lookup",
379 params,
380 G_DBUS_CALL_FLAGS_NONE,
381 -1,
382 self->cancellable,
383 on_perm_store_lookup_done,
384 self);
385 }
386
387 static void
388 cc_camera_page_finalize (GObject *object)
389 {
390 CcCameraPage *self = CC_CAMERA_PAGE (object);
391
392 g_cancellable_cancel (self->cancellable);
393 g_clear_object (&self->cancellable);
394
395 g_clear_object (&self->privacy_settings);
396 g_clear_object (&self->perm_store);
397 g_clear_object (&self->camera_icon_size_group);
398 g_clear_pointer (&self->camera_apps_perms, g_variant_unref);
399 g_clear_pointer (&self->camera_apps_data, g_variant_unref);
400 g_clear_pointer (&self->camera_app_switches, g_hash_table_unref);
401 g_clear_pointer (&self->camera_app_rows, g_hash_table_unref);
402
403 G_OBJECT_CLASS (cc_camera_page_parent_class)->finalize (object);
404 }
405
406 static void
407 cc_camera_page_class_init (CcCameraPageClass *klass)
408 {
409 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
410 GObjectClass *object_class = G_OBJECT_CLASS (klass);
411
412 object_class->finalize = cc_camera_page_finalize;
413
414 gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/privacy/camera/cc-camera-page.ui");
415
416 gtk_widget_class_bind_template_child (widget_class, CcCameraPage, camera_apps_list_box);
417 gtk_widget_class_bind_template_child (widget_class, CcCameraPage, camera_row);
418 }
419
420 static void
421 cc_camera_page_init (CcCameraPage *self)
422 {
423 gtk_widget_init_template (GTK_WIDGET (self));
424
425 self->camera_icon_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH);
426
427 self->privacy_settings = g_settings_new ("org.gnome.desktop.privacy");
428
429 self->cancellable = g_cancellable_new ();
430
431 g_settings_bind (self->privacy_settings, "disable-camera",
432 self->camera_row, "active",
433 G_SETTINGS_BIND_INVERT_BOOLEAN);
434
435 self->camera_app_switches = g_hash_table_new_full (g_str_hash,
436 g_str_equal,
437 g_free,
438 g_object_unref);
439 self->camera_app_rows = g_hash_table_new_full (g_str_hash,
440 g_str_equal,
441 g_free,
442 g_object_unref);
443
444 g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
445 G_DBUS_PROXY_FLAGS_NONE,
446 NULL,
447 "org.freedesktop.impl.portal.PermissionStore",
448 "/org/freedesktop/impl/portal/PermissionStore",
449 "org.freedesktop.impl.portal.PermissionStore",
450 self->cancellable,
451 on_perm_store_ready,
452 self);
453 }
454