GCC Code Coverage Report


Directory: ./
File: panels/privacy/bolt/cc-bolt-page.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 380 0.0%
Functions: 0 36 0.0%
Branches: 0 127 0.0%

Line Branch Exec Source
1 /* Copyright © 2018 Red Hat, Inc
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: Christian J. Kellner <ckellner@redhat.com>
17 *
18 */
19
20 #include <config.h>
21
22 #include <glib/gi18n.h>
23 #include <polkit/polkit.h>
24
25 #include "shell/cc-application.h"
26 #include "cc-bolt-device-dialog.h"
27 #include "cc-bolt-device-entry.h"
28
29 #include "bolt-client.h"
30 #include "bolt-names.h"
31 #include "bolt-str.h"
32
33 #include "cc-bolt-page.h"
34
35 struct _CcBoltPage
36 {
37 AdwNavigationPage parent;
38
39 BoltClient *client;
40
41 /* headerbar menu */
42 GtkBox *headerbar_box;
43 GtkLockButton *lock_button;
44
45 /* main ui */
46 GtkStack *container;
47
48 /* empty state */
49 AdwStatusPage *notb_page;
50
51 /* notifications */
52 GtkLabel *notification_label;
53 GtkRevealer *notification_revealer;
54
55 /* authmode */
56 GtkSwitch *authmode_switch;
57 GtkSpinner *authmode_spinner;
58 GtkStack *direct_access_row;
59
60 /* device list */
61 GHashTable *devices;
62
63 GtkStack *devices_stack;
64 GtkBox *devices_box;
65 GtkBox *pending_box;
66
67 GtkListBox *devices_list;
68 GtkListBox *pending_list;
69
70 /* device details dialog */
71 CcBoltDeviceDialog *device_dialog;
72
73 /* polkit integration */
74 GPermission *permission;
75
76 GCancellable *cancellable;
77 };
78
79 /* initialization */
80 static void bolt_client_ready (GObject *source,
81 GAsyncResult *res,
82 gpointer user_data);
83
84 /* panel functions */
85 static void cc_bolt_page_set_no_thunderbolt (CcBoltPage *self,
86 const char *custom_msg);
87
88 static void cc_bolt_page_name_owner_changed (CcBoltPage *self);
89
90 static CcBoltDeviceEntry * cc_bolt_page_add_device (CcBoltPage *self,
91 BoltDevice *dev);
92
93 static void cc_bolt_page_del_device_entry (CcBoltPage *self,
94 CcBoltDeviceEntry *entry);
95
96 static void cc_bolt_page_authmode_sync (CcBoltPage *self);
97
98 static void cc_panel_list_box_migrate (CcBoltPage *self,
99 GtkListBox *from,
100 GtkListBox *to,
101 CcBoltDeviceEntry *entry);
102
103 /* bolt client signals */
104 static void on_bolt_name_owner_changed_cb (GObject *object,
105 GParamSpec *pspec,
106 gpointer user_data);
107
108 static void on_bolt_device_added_cb (BoltClient *cli,
109 const char *path,
110 CcBoltPage *self);
111
112 static void on_bolt_device_removed_cb (BoltClient *cli,
113 const char *opath,
114 CcBoltPage *self);
115
116 static void on_bolt_notify_authmode_cb (GObject *gobject,
117 GParamSpec *pspec,
118 gpointer user_data);
119
120 /* panel signals */
121 static gboolean on_authmode_state_set_cb (CcBoltPage *self,
122 gboolean state,
123 GtkSwitch *toggle);
124
125 static void on_device_entry_row_activated_cb (CcBoltPage *self,
126 GtkListBoxRow *row);
127
128 static void on_device_entry_status_changed_cb (CcBoltDeviceEntry *entry,
129 BoltStatus new_status,
130 CcBoltPage *self);
131
132 static void on_notification_button_clicked_cb (CcBoltPage *self);
133
134
135 /* polkit */
136 static void on_permission_ready (GObject *source_object,
137 GAsyncResult *res,
138 gpointer user_data);
139
140 static void on_permission_notify_cb (GPermission *permission,
141 GParamSpec *pspec,
142 CcBoltPage *self);
143
144 G_DEFINE_TYPE (CcBoltPage, cc_bolt_page, ADW_TYPE_NAVIGATION_PAGE)
145
146 static void
147 update_visibility (BoltClient *client,
148 const char *path,
149 gpointer user_data)
150 {
151 g_autoptr(GPtrArray) devices = NULL;
152 gboolean visible = FALSE;
153 CcBoltPage *self;
154
155 self = CC_BOLT_PAGE (user_data);
156
157 if (client)
158 {
159 devices = bolt_client_list_devices (client, self->cancellable, NULL);
160 if (devices)
161 visible = devices->len > 0;
162 }
163
164 gtk_widget_set_visible (GTK_WIDGET (self), visible);
165 }
166
167 static void
168 on_visibility_client_ready (GObject *source,
169 GAsyncResult *res,
170 gpointer user_data)
171 {
172 BoltClient *client;
173 CcBoltPage *self;
174
175 self = CC_BOLT_PAGE (user_data);
176
177 client = bolt_client_new_finish (res, NULL);
178 if (client == NULL)
179 {
180 gtk_widget_set_visible (GTK_WIDGET (self), FALSE);
181 return;
182 }
183
184 g_signal_connect_object (client,
185 "device-added",
186 G_CALLBACK (update_visibility),
187 self,
188 0);
189 g_signal_connect_object (client,
190 "device-removed",
191 G_CALLBACK (update_visibility),
192 self,
193 0);
194 update_visibility (client, NULL, self);
195 }
196
197 static void
198 bolt_client_ready (GObject *source,
199 GAsyncResult *res,
200 gpointer user_data)
201 {
202 g_autoptr(GError) err = NULL;
203 g_autoptr(CcBoltPage) self = NULL;
204 BoltClient *client;
205
206 self = CC_BOLT_PAGE (user_data);
207 client = bolt_client_new_finish (res, &err);
208
209 if (client == NULL)
210 {
211 const char *text;
212
213 /* operation got cancelled because the panel got destroyed */
214 if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED) ||
215 g_error_matches (err, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED))
216 return;
217
218 g_warning ("Could not create client: %s", err->message);
219 text = _("The Thunderbolt subsystem (boltd) is not installed or "
220 "not set up properly.");
221
222 adw_status_page_set_description (self->notb_page, text);
223 gtk_stack_set_visible_child_name (self->container, "no-thunderbolt");
224
225 return;
226 }
227
228 g_signal_connect_object (client,
229 "notify::g-name-owner",
230 G_CALLBACK (on_bolt_name_owner_changed_cb),
231 self,
232 0);
233
234 g_signal_connect_object (client,
235 "device-added",
236 G_CALLBACK (on_bolt_device_added_cb),
237 self,
238 0);
239
240 g_signal_connect_object (client,
241 "device-removed",
242 G_CALLBACK (on_bolt_device_removed_cb),
243 self,
244 0);
245
246 g_signal_connect_object (client,
247 "notify::auth-mode",
248 G_CALLBACK (on_bolt_notify_authmode_cb),
249 self,
250 0);
251
252 /* Treat security-level changes, which should rarely happen, as
253 * if the name owner changed, i.e. as if boltd got restarted */
254 g_signal_connect_object (client,
255 "notify::security-level",
256 G_CALLBACK (on_bolt_name_owner_changed_cb),
257 self,
258 0);
259
260 self->client = client;
261
262 cc_bolt_device_dialog_set_client (self->device_dialog, client);
263
264 cc_bolt_page_authmode_sync (self);
265
266 g_object_bind_property (self->authmode_switch,
267 "active",
268 self->devices_box,
269 "sensitive",
270 G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
271
272 g_object_bind_property (self->authmode_switch,
273 "active",
274 self->pending_box,
275 "sensitive",
276 G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
277
278 gtk_stack_set_visible_child_name (self->devices_stack, "no-devices");
279 cc_bolt_page_name_owner_changed (self);
280 }
281
282 static gboolean
283 devices_table_transfer_entry (GHashTable *from,
284 GHashTable *to,
285 gconstpointer key)
286 {
287 gpointer k, v;
288 gboolean found;
289
290 found = g_hash_table_lookup_extended (from, key, &k, &v);
291
292 if (found)
293 {
294 g_hash_table_steal (from, key);
295 g_hash_table_insert (to, k, v);
296 }
297
298 return found;
299 }
300
301 static void
302 devices_table_clear_entries (GHashTable *table,
303 CcBoltPage *self)
304 {
305 GHashTableIter iter;
306 gpointer key, value;
307
308 g_hash_table_iter_init (&iter, table);
309 while (g_hash_table_iter_next (&iter, &key, &value))
310 {
311 CcBoltDeviceEntry *entry = value;
312
313 cc_bolt_page_del_device_entry (self, entry);
314 g_hash_table_iter_remove (&iter);
315 }
316 }
317
318 static void
319 devices_table_synchronize (CcBoltPage *self)
320 {
321 g_autoptr(GHashTable) old = NULL;
322 g_autoptr(GPtrArray) devices = NULL;
323 g_autoptr(GError) err = NULL;
324 guint i;
325
326 devices = bolt_client_list_devices (self->client, self->cancellable, &err);
327
328 if (!devices)
329 {
330 g_warning ("Could not list devices: %s", err->message);
331 devices = g_ptr_array_new_with_free_func (g_object_unref);
332 }
333
334 old = self->devices;
335 self->devices = g_hash_table_new (g_str_hash, g_str_equal);
336
337 for (i = 0; i < devices->len; i++)
338 {
339 BoltDevice *dev = g_ptr_array_index (devices, i);
340 const char *path;
341 gboolean found;
342
343 path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (dev));
344 found = devices_table_transfer_entry (old, self->devices, path);
345
346 if (found)
347 continue;
348
349 cc_bolt_page_add_device (self, dev);
350 }
351
352 devices_table_clear_entries (old, self);
353 gtk_stack_set_visible_child_name (self->container, "devices-listing");
354 }
355
356 static gboolean
357 list_box_sync_visible (GtkListBox *listbox)
358 {
359 GtkWidget *child;
360 gboolean show;
361
362 child = gtk_widget_get_first_child (GTK_WIDGET (listbox));
363 show = child != NULL;
364
365 gtk_widget_set_visible (GTK_WIDGET (listbox), show);
366
367 return show;
368 }
369
370 static GtkWidget *
371 cc_bolt_page_box_for_listbox (CcBoltPage *self,
372 GtkListBox *lstbox)
373 {
374 if ((gpointer) lstbox == self->devices_list)
375 return GTK_WIDGET (self->devices_box);
376 else if ((gpointer) lstbox == self->pending_list)
377 return GTK_WIDGET (self->pending_box);
378
379 g_return_val_if_reached (NULL);
380 }
381
382 static CcBoltDeviceEntry *
383 cc_bolt_page_add_device (CcBoltPage *self,
384 BoltDevice *dev)
385 {
386 CcBoltDeviceEntry *entry;
387 BoltDeviceType type;
388 BoltStatus status;
389 const char *path;
390
391 type = bolt_device_get_device_type (dev);
392
393 if (type != BOLT_DEVICE_PERIPHERAL)
394 return FALSE;
395
396 entry = cc_bolt_device_entry_new (dev, FALSE);
397 path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (dev));
398
399 /* add to the list box */
400 status = bolt_device_get_status (dev);
401
402 if (bolt_status_is_pending (status))
403 {
404 gtk_list_box_append (self->pending_list, GTK_WIDGET (entry));
405 gtk_widget_set_visible (GTK_WIDGET (self->pending_list), TRUE);
406 gtk_widget_set_visible (GTK_WIDGET (self->pending_box), TRUE);
407 }
408 else
409 {
410 gtk_list_box_append (self->devices_list, GTK_WIDGET (entry));
411 gtk_widget_set_visible (GTK_WIDGET (self->devices_list), TRUE);
412 gtk_widget_set_visible (GTK_WIDGET (self->devices_box), TRUE);
413 }
414
415 g_signal_connect_object (entry,
416 "status-changed",
417 G_CALLBACK (on_device_entry_status_changed_cb),
418 self,
419 0);
420
421 gtk_stack_set_visible_child_name (self->devices_stack, "have-devices");
422 g_hash_table_insert (self->devices, (gpointer) path, entry);
423
424 return entry;
425 }
426
427 static void
428 cc_bolt_page_del_device_entry (CcBoltPage *self,
429 CcBoltDeviceEntry *entry)
430 {
431 BoltDevice *dev;
432 GtkWidget *box;
433 GtkWidget *p;
434 gboolean show;
435
436 dev = cc_bolt_device_entry_get_device (entry);
437 if (cc_bolt_device_dialog_device_equal (self->device_dialog, dev))
438 {
439 gtk_window_close (GTK_WINDOW (self->device_dialog));
440 cc_bolt_device_dialog_set_device (self->device_dialog, NULL, NULL);
441 }
442
443 p = gtk_widget_get_parent (GTK_WIDGET (entry));
444 gtk_list_box_remove (GTK_LIST_BOX (p), GTK_WIDGET (entry));
445
446 box = cc_bolt_page_box_for_listbox (self, GTK_LIST_BOX (p));
447 show = list_box_sync_visible (GTK_LIST_BOX (p));
448 gtk_widget_set_visible (box, show);
449
450 if (!gtk_widget_is_visible (GTK_WIDGET (self->pending_list)) &&
451 !gtk_widget_is_visible (GTK_WIDGET (self->devices_list)))
452 {
453 gtk_stack_set_visible_child_name (self->devices_stack, "no-devices");
454 }
455 }
456
457 static void
458 cc_bolt_page_authmode_sync (CcBoltPage *self)
459 {
460 BoltClient *client = self->client;
461 BoltAuthMode mode;
462 gboolean enabled;
463
464 mode = bolt_client_get_authmode (client);
465 enabled = (mode & BOLT_AUTH_ENABLED) != 0;
466
467 g_signal_handlers_block_by_func (self->authmode_switch, on_authmode_state_set_cb, self);
468
469 gtk_switch_set_active (self->authmode_switch, enabled);
470
471 g_signal_handlers_unblock_by_func (self->authmode_switch, on_authmode_state_set_cb, self);
472
473 adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self->direct_access_row),
474 enabled ?
475 _("Allow direct access to devices such as docks and external GPUs.") :
476 _("Only USB and Display Port devices can attach."));
477 }
478
479 static void
480 cc_panel_list_box_migrate (CcBoltPage *self,
481 GtkListBox *from,
482 GtkListBox *to,
483 CcBoltDeviceEntry *entry)
484 {
485 GtkWidget *from_box;
486 GtkWidget *to_box;
487 gboolean show;
488 GtkWidget *target;
489
490 target = GTK_WIDGET (entry);
491
492 gtk_list_box_remove (from, target);
493 gtk_list_box_append (to, target);
494 gtk_widget_set_visible (GTK_WIDGET (to), TRUE);
495
496 from_box = cc_bolt_page_box_for_listbox (self, from);
497 to_box = cc_bolt_page_box_for_listbox (self, to);
498
499 show = list_box_sync_visible (from);
500 gtk_widget_set_visible (from_box, show);
501 gtk_widget_set_visible (to_box, TRUE);
502 }
503
504 /* bolt client signals */
505 static void
506 cc_bolt_page_set_no_thunderbolt (CcBoltPage *self,
507 const char *msg)
508 {
509 if (!msg)
510 {
511 msg = _("Thunderbolt could not be detected.\n"
512 "Either the system lacks Thunderbolt support, "
513 "it has been disabled in the BIOS or is set to "
514 "an unsupported security level in the BIOS.");
515 }
516
517 adw_status_page_set_description (self->notb_page, msg);
518 gtk_stack_set_visible_child_name (self->container, "no-thunderbolt");
519 }
520
521 static void
522 cc_bolt_page_name_owner_changed (CcBoltPage *self)
523 {
524 g_autofree char *name_owner = NULL;
525 BoltClient *client = self->client;
526 BoltSecurity sl;
527 gboolean notb = TRUE;
528 const char *text = NULL;
529
530 name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (self->client));
531
532 if (name_owner == NULL)
533 {
534 cc_bolt_page_set_no_thunderbolt (self, NULL);
535 devices_table_clear_entries (self->devices, self);
536 gtk_widget_set_visible (GTK_WIDGET (self->headerbar_box), FALSE);
537 return;
538 }
539
540 gtk_stack_set_visible_child_name (self->container, "loading");
541
542 sl = bolt_client_get_security (client);
543
544 switch (sl)
545 {
546 case BOLT_SECURITY_NONE:
547 case BOLT_SECURITY_SECURE:
548 case BOLT_SECURITY_USER:
549 /* we fetch the device list and show them here */
550 notb = FALSE;
551 break;
552
553 case BOLT_SECURITY_DPONLY:
554 case BOLT_SECURITY_USBONLY:
555 text = _("Thunderbolt support has been disabled in the BIOS.");
556 break;
557
558 case BOLT_SECURITY_UNKNOWN:
559 text = _("Thunderbolt security level could not be determined.");;
560 break;
561 }
562
563 if (notb)
564 {
565 /* security level is unknown or un-handled */
566 cc_bolt_page_set_no_thunderbolt (self, text);
567 return;
568 }
569
570 if (self->permission)
571 {
572 gtk_widget_set_visible (GTK_WIDGET (self->headerbar_box), TRUE);
573 }
574 else
575 {
576 polkit_permission_new ("org.freedesktop.bolt.manage",
577 NULL,
578 self->cancellable,
579 on_permission_ready,
580 g_object_ref (self));
581 }
582
583 devices_table_synchronize (self);
584 }
585
586 /* bolt client signals */
587 static void
588 on_bolt_name_owner_changed_cb (GObject *object,
589 GParamSpec *pspec,
590 gpointer user_data)
591 {
592 cc_bolt_page_name_owner_changed (CC_BOLT_PAGE (user_data));
593 }
594
595 static void
596 on_bolt_device_added_cb (BoltClient *cli,
597 const char *path,
598 CcBoltPage *self)
599 {
600 g_autoptr(GError) err = NULL;
601 GDBusConnection *bus;
602 BoltDevice *dev;
603 gboolean found;
604
605 found = g_hash_table_contains (self->devices, path);
606
607 if (found)
608 return;
609
610 bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (self->client));
611 dev = bolt_device_new_for_object_path (bus, path, self->cancellable, &err);
612
613 if (!dev)
614 {
615 g_warning ("Could not create proxy for %s", path);
616 return;
617 }
618
619 cc_bolt_page_add_device (self, dev);
620 }
621
622 static void
623 on_bolt_device_removed_cb (BoltClient *cli,
624 const char *path,
625 CcBoltPage *self)
626 {
627 CcBoltDeviceEntry *entry;
628
629 entry = g_hash_table_lookup (self->devices, path);
630
631 if (!entry)
632 return;
633
634 cc_bolt_page_del_device_entry (self, entry);
635 g_hash_table_remove (self->devices, path);
636 }
637
638 static void
639 on_bolt_notify_authmode_cb (GObject *gobject,
640 GParamSpec *pspec,
641 gpointer user_data)
642 {
643 cc_bolt_page_authmode_sync (CC_BOLT_PAGE (user_data));
644 }
645
646 /* panel signals */
647
648 static void
649 on_authmode_ready (GObject *source_object,
650 GAsyncResult *res,
651 gpointer user_data)
652 {
653 g_autoptr(GError) error = NULL;
654 BoltClient *client = BOLT_CLIENT (source_object);
655 CcBoltPage *self;
656 gboolean ok;
657
658 ok = bolt_client_set_authmode_finish (client, res, &error);
659 if (ok)
660 {
661 BoltAuthMode mode;
662 gboolean enabled;
663
664 self = CC_BOLT_PAGE (user_data);
665 mode = bolt_client_get_authmode (client);
666 enabled = (mode & BOLT_AUTH_ENABLED) != 0;
667 gtk_switch_set_state (self->authmode_switch, enabled);
668 }
669 else
670 {
671 g_autofree char *text = NULL;
672
673 g_warning ("Could not set authmode: %s", error->message);
674
675 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
676 return;
677
678 self = CC_BOLT_PAGE (user_data);
679 text = g_strdup_printf (_("Error switching direct mode: %s"), error->message);
680 gtk_label_set_markup (self->notification_label, text);
681 gtk_revealer_set_reveal_child (self->notification_revealer, TRUE);
682
683 /* make sure we are reflecting the correct state */
684 cc_bolt_page_authmode_sync (self);
685 }
686
687 gtk_spinner_stop (self->authmode_spinner);
688 gtk_widget_set_sensitive (GTK_WIDGET (self->authmode_switch), TRUE);
689 }
690
691 static gboolean
692 on_authmode_state_set_cb (CcBoltPage *self,
693 gboolean enable,
694 GtkSwitch *toggle)
695 {
696 BoltClient *client = self->client;
697 BoltAuthMode mode;
698
699 gtk_widget_set_sensitive (GTK_WIDGET (self->authmode_switch), FALSE);
700 gtk_spinner_start (self->authmode_spinner);
701
702 mode = bolt_client_get_authmode (client);
703
704 if (enable)
705 mode = mode | BOLT_AUTH_ENABLED;
706 else
707 mode = mode & ~BOLT_AUTH_ENABLED;
708
709 bolt_client_set_authmode_async (client, mode, NULL, on_authmode_ready, self);
710
711 return TRUE;
712 }
713
714 static void
715 on_device_entry_row_activated_cb (CcBoltPage *self,
716 GtkListBoxRow *row)
717 {
718 g_autoptr(GPtrArray) parents = NULL;
719 CcBoltDeviceEntry *entry;
720 BoltDevice *device;
721 BoltDevice *iter;
722 const char *parent;
723
724 if (!CC_IS_BOLT_DEVICE_ENTRY (row))
725 return;
726
727 entry = CC_BOLT_DEVICE_ENTRY (row);
728 device = cc_bolt_device_entry_get_device (entry);
729
730 /* walk up the chain and collect all parents */
731 parents = g_ptr_array_new_with_free_func (g_object_unref);
732 iter = device;
733
734 parent = bolt_device_get_parent (iter);
735 while (parent != NULL)
736 {
737 g_autofree char *path = NULL;
738 CcBoltDeviceEntry *child;
739 BoltDevice *dev;
740
741 path = bolt_gen_object_path (BOLT_DBUS_PATH_DEVICES, parent);
742
743 /* NB: the host device is not a peripheral and thus not
744 * in the hash table; therefore when get a NULL back, we
745 * should have reached the end of the chain */
746 child = g_hash_table_lookup (self->devices, path);
747 if (!child)
748 break;
749
750 dev = cc_bolt_device_entry_get_device (child);
751 g_ptr_array_add (parents, g_object_ref (dev));
752 iter = dev;
753
754 parent = bolt_device_get_parent (iter);
755 }
756
757 cc_bolt_device_dialog_set_device (self->device_dialog, device, parents);
758
759 gtk_window_set_default_size (GTK_WINDOW (self->device_dialog), 1, 1);
760 gtk_window_present (GTK_WINDOW (self->device_dialog));
761 }
762
763 static void
764 on_device_entry_status_changed_cb (CcBoltDeviceEntry *entry,
765 BoltStatus new_status,
766 CcBoltPage *self)
767 {
768 GtkListBox *from = NULL;
769 GtkListBox *to = NULL;
770 GtkWidget *p;
771 gboolean is_pending;
772 gboolean parent_pending;
773
774 /* if we are doing some active work, then lets not change
775 * the list the entry is in; otherwise we might just hop
776 * from one box to the other and back again.
777 */
778 if (new_status == BOLT_STATUS_CONNECTING || new_status == BOLT_STATUS_AUTHORIZING)
779 return;
780
781 is_pending = bolt_status_is_pending (new_status);
782
783 p = gtk_widget_get_parent (GTK_WIDGET (entry));
784 parent_pending = (gpointer) p == self->pending_list;
785
786 /* */
787 if (is_pending && !parent_pending)
788 {
789 from = self->devices_list;
790 to = self->pending_list;
791 }
792 else if (!is_pending && parent_pending)
793 {
794 from = self->pending_list;
795 to = self->devices_list;
796 }
797
798 if (from && to)
799 cc_panel_list_box_migrate (self, from, to, entry);
800 }
801
802
803 static void
804 on_notification_button_clicked_cb (CcBoltPage *self)
805 {
806 gtk_revealer_set_reveal_child (self->notification_revealer, FALSE);
807 }
808
809 /* polkit */
810
811 static void
812 on_permission_ready (GObject *source_object,
813 GAsyncResult *res,
814 gpointer user_data)
815 {
816 g_autoptr(CcBoltPage) self = user_data;
817 g_autoptr(GError) err = NULL;
818 GPermission *permission;
819 gboolean is_allowed;
820 const char *name;
821
822 permission = polkit_permission_new_finish (res, &err);
823 self->permission = permission;
824
825 if (!self->permission)
826 {
827 g_warning ("Could not get polkit permissions: %s", err->message);
828 return;
829 }
830
831 g_signal_connect_object (permission,
832 "notify",
833 G_CALLBACK (on_permission_notify_cb),
834 self,
835 G_CONNECT_AFTER);
836
837 is_allowed = g_permission_get_allowed (permission);
838 gtk_widget_set_sensitive (GTK_WIDGET (self->authmode_switch), is_allowed);
839 gtk_lock_button_set_permission (self->lock_button, permission);
840
841 name = gtk_stack_get_visible_child_name (self->container);
842
843 gtk_widget_set_visible (GTK_WIDGET (self->headerbar_box),
844 bolt_streq (name, "devices-listing"));
845 }
846
847 static void
848 on_permission_notify_cb (GPermission *permission,
849 GParamSpec *pspec,
850 CcBoltPage *self)
851 {
852 gboolean is_allowed = g_permission_get_allowed (permission);
853
854 gtk_widget_set_sensitive (GTK_WIDGET (self->authmode_switch), is_allowed);
855 }
856
857 static gint
858 device_entries_sort_by_recency_cb (GtkListBoxRow *a_row,
859 GtkListBoxRow *b_row,
860 gpointer user_data)
861 {
862 CcBoltDeviceEntry *a_entry = CC_BOLT_DEVICE_ENTRY (a_row);
863 CcBoltDeviceEntry *b_entry = CC_BOLT_DEVICE_ENTRY (b_row);
864 BoltDevice *a = cc_bolt_device_entry_get_device (a_entry);
865 BoltDevice *b = cc_bolt_device_entry_get_device (b_entry);
866 BoltStatus status;
867 gint64 a_ts, b_ts;
868 gint64 score;
869
870 a_ts = (gint64) bolt_device_get_timestamp (a);
871 b_ts = (gint64) bolt_device_get_timestamp (b);
872
873 score = b_ts - a_ts;
874
875 if (score != 0)
876 return score;
877
878 status = bolt_device_get_status (a);
879
880 if (bolt_status_is_connected (status))
881 {
882 const char *a_path;
883 const char *b_path;
884
885 a_path = bolt_device_get_syspath (a);
886 b_path = bolt_device_get_syspath (b);
887
888 return g_strcmp0 (a_path, b_path);
889 }
890 else
891 {
892 const char *a_name;
893 const char *b_name;
894
895 a_name = bolt_device_get_name (a);
896 b_name = bolt_device_get_name (b);
897
898 return g_strcmp0 (a_name, b_name);
899 }
900
901 return 0;
902 }
903
904 static gint
905 device_entries_sort_by_syspath_cb (GtkListBoxRow *a_row,
906 GtkListBoxRow *b_row,
907 gpointer user_data)
908 {
909 CcBoltDeviceEntry *a_entry = CC_BOLT_DEVICE_ENTRY (a_row);
910 CcBoltDeviceEntry *b_entry = CC_BOLT_DEVICE_ENTRY (b_row);
911 BoltDevice *a = cc_bolt_device_entry_get_device (a_entry);
912 BoltDevice *b = cc_bolt_device_entry_get_device (b_entry);
913
914 const char *a_path;
915 const char *b_path;
916
917 a_path = bolt_device_get_syspath (a);
918 b_path = bolt_device_get_syspath (b);
919
920 return g_strcmp0 (a_path, b_path);
921 }
922
923 /* GObject overrides */
924
925 static void
926 cc_bolt_page_finalize (GObject *object)
927 {
928 CcBoltPage *self = CC_BOLT_PAGE (object);
929
930 g_clear_object (&self->client);
931 g_clear_pointer (&self->devices, g_hash_table_unref);
932 g_clear_object (&self->permission);
933
934 g_cancellable_cancel (self->cancellable);
935 g_clear_object (&self->cancellable);
936
937 G_OBJECT_CLASS (cc_bolt_page_parent_class)->finalize (object);
938 }
939
940 static void
941 cc_bolt_page_dispose (GObject *object)
942 {
943 CcBoltPage *self = CC_BOLT_PAGE (object);
944
945 /* Must be destroyed in dispose, not finalize. */
946 cc_bolt_device_dialog_set_device (self->device_dialog, NULL, NULL);
947 g_clear_pointer ((GtkWindow **) &self->device_dialog, gtk_window_destroy);
948
949 G_OBJECT_CLASS (cc_bolt_page_parent_class)->dispose (object);
950 }
951
952 static void
953 cc_bolt_page_constructed (GObject *object)
954 {
955 CcBoltPage *self = CC_BOLT_PAGE (object);
956 GtkWindow *parent;
957
958 G_OBJECT_CLASS (cc_bolt_page_parent_class)->constructed (object);
959
960 parent = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self)));
961 gtk_window_set_transient_for (GTK_WINDOW (self->device_dialog), parent);
962 }
963
964 static void
965 cc_bolt_page_class_init (CcBoltPageClass *klass)
966 {
967 GObjectClass *object_class = G_OBJECT_CLASS (klass);
968 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
969
970 object_class->constructed = cc_bolt_page_constructed;
971 object_class->dispose = cc_bolt_page_dispose;
972 object_class->finalize = cc_bolt_page_finalize;
973
974 gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/privacy/bolt/cc-bolt-page.ui");
975
976 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, authmode_spinner);
977 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, authmode_switch);
978 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, container);
979 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, devices_list);
980 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, devices_box);
981 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, devices_stack);
982 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, direct_access_row);
983 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, headerbar_box);
984 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, lock_button);
985 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, notb_page);
986 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, notification_label);
987 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, notification_revealer);
988 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, pending_box);
989 gtk_widget_class_bind_template_child (widget_class, CcBoltPage, pending_list);
990
991 gtk_widget_class_bind_template_callback (widget_class, on_notification_button_clicked_cb);
992 gtk_widget_class_bind_template_callback (widget_class, on_authmode_state_set_cb);
993 gtk_widget_class_bind_template_callback (widget_class, on_device_entry_row_activated_cb);
994 }
995
996 static void
997 cc_bolt_page_init (CcBoltPage *self)
998 {
999 gtk_widget_init_template (GTK_WIDGET (self));
1000
1001 self->cancellable = g_cancellable_new ();
1002 bolt_client_new_async (self->cancellable, on_visibility_client_ready, self);
1003
1004 gtk_stack_set_visible_child_name (self->container, "loading");
1005
1006 gtk_list_box_set_sort_func (self->devices_list,
1007 device_entries_sort_by_recency_cb,
1008 self,
1009 NULL);
1010
1011 gtk_list_box_set_sort_func (self->pending_list,
1012 device_entries_sort_by_syspath_cb,
1013 self,
1014 NULL);
1015
1016 self->devices = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1017
1018 self->device_dialog = cc_bolt_device_dialog_new ();
1019
1020 bolt_client_new_async (self->cancellable, bolt_client_ready, g_object_ref (self));
1021 }
1022
1023 CcBoltPage *
1024 cc_bolt_page_new (void)
1025 {
1026 return g_object_new (CC_TYPE_BOLT_PAGE, NULL);
1027 }
1028