955 lines
28 KiB
C
955 lines
28 KiB
C
/* Copyright © 2018 Red Hat, Inc
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors: Christian J. Kellner <ckellner@redhat.com>
|
|
*
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <shell/cc-panel.h>
|
|
|
|
#include <glib/gi18n.h>
|
|
#include <polkit/polkit.h>
|
|
|
|
#include "cc-bolt-device-dialog.h"
|
|
#include "cc-bolt-device-entry.h"
|
|
|
|
#include "bolt-client.h"
|
|
#include "bolt-names.h"
|
|
#include "bolt-str.h"
|
|
|
|
#include "cc-bolt-panel.h"
|
|
#include "cc-thunderbolt-resources.h"
|
|
|
|
struct _CcBoltPanel
|
|
{
|
|
CcPanel parent;
|
|
|
|
BoltClient *client;
|
|
|
|
/* headerbar menu */
|
|
GtkBox *headerbar_box;
|
|
GtkLockButton *lock_button;
|
|
|
|
/* main ui */
|
|
GtkStack *container;
|
|
|
|
/* empty state */
|
|
AdwStatusPage *notb_page;
|
|
|
|
/* notifications */
|
|
GtkLabel *notification_label;
|
|
GtkRevealer *notification_revealer;
|
|
|
|
/* authmode */
|
|
GtkSwitch *authmode_switch;
|
|
GtkSpinner *authmode_spinner;
|
|
GtkStack *direct_access_row;
|
|
|
|
/* device list */
|
|
GHashTable *devices;
|
|
|
|
GtkStack *devices_stack;
|
|
GtkBox *devices_box;
|
|
GtkBox *pending_box;
|
|
|
|
GtkListBox *devices_list;
|
|
GtkListBox *pending_list;
|
|
|
|
/* device details dialog */
|
|
CcBoltDeviceDialog *device_dialog;
|
|
|
|
/* polkit integration */
|
|
GPermission *permission;
|
|
};
|
|
|
|
/* initialization */
|
|
static void bolt_client_ready (GObject *source,
|
|
GAsyncResult *res,
|
|
gpointer user_data);
|
|
|
|
/* panel functions */
|
|
static void cc_bolt_panel_set_no_thunderbolt (CcBoltPanel *panel,
|
|
const char *custom_msg);
|
|
|
|
static void cc_bolt_panel_name_owner_changed (CcBoltPanel *panel);
|
|
|
|
static CcBoltDeviceEntry * cc_bolt_panel_add_device (CcBoltPanel *panel,
|
|
BoltDevice *dev);
|
|
|
|
static void cc_bolt_panel_del_device_entry (CcBoltPanel *panel,
|
|
CcBoltDeviceEntry *entry);
|
|
|
|
static void cc_bolt_panel_authmode_sync (CcBoltPanel *panel);
|
|
|
|
static void cc_panel_list_box_migrate (CcBoltPanel *panel,
|
|
GtkListBox *from,
|
|
GtkListBox *to,
|
|
CcBoltDeviceEntry *entry);
|
|
|
|
/* bolt client signals */
|
|
static void on_bolt_name_owner_changed_cb (GObject *object,
|
|
GParamSpec *pspec,
|
|
gpointer user_data);
|
|
|
|
static void on_bolt_device_added_cb (BoltClient *cli,
|
|
const char *path,
|
|
CcBoltPanel *panel);
|
|
|
|
static void on_bolt_device_removed_cb (BoltClient *cli,
|
|
const char *opath,
|
|
CcBoltPanel *panel);
|
|
|
|
static void on_bolt_notify_authmode_cb (GObject *gobject,
|
|
GParamSpec *pspec,
|
|
gpointer user_data);
|
|
|
|
/* panel signals */
|
|
static gboolean on_authmode_state_set_cb (CcBoltPanel *panel,
|
|
gboolean state,
|
|
GtkSwitch *toggle);
|
|
|
|
static void on_device_entry_row_activated_cb (CcBoltPanel *panel,
|
|
GtkListBoxRow *row);
|
|
|
|
static void on_device_entry_status_changed_cb (CcBoltDeviceEntry *entry,
|
|
BoltStatus new_status,
|
|
CcBoltPanel *panel);
|
|
|
|
static void on_notification_button_clicked_cb (GtkButton *button,
|
|
CcBoltPanel *panel);
|
|
|
|
|
|
/* polkit */
|
|
static void on_permission_ready (GObject *source_object,
|
|
GAsyncResult *res,
|
|
gpointer user_data);
|
|
|
|
static void on_permission_notify_cb (GPermission *permission,
|
|
GParamSpec *pspec,
|
|
CcBoltPanel *panel);
|
|
|
|
CC_PANEL_REGISTER (CcBoltPanel, cc_bolt_panel);
|
|
|
|
static void
|
|
bolt_client_ready (GObject *source,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr(GError) err = NULL;
|
|
g_autoptr(CcBoltPanel) panel = NULL;
|
|
BoltClient *client;
|
|
|
|
panel = CC_BOLT_PANEL (user_data);
|
|
client = bolt_client_new_finish (res, &err);
|
|
|
|
if (client == NULL)
|
|
{
|
|
const char *text;
|
|
|
|
/* operation got cancelled because the panel got destroyed */
|
|
if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED) ||
|
|
g_error_matches (err, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED))
|
|
return;
|
|
|
|
g_warning ("Could not create client: %s", err->message);
|
|
text = _("The Thunderbolt subsystem (boltd) is not installed or "
|
|
"not set up properly.");
|
|
|
|
adw_status_page_set_description (panel->notb_page, text);
|
|
gtk_stack_set_visible_child_name (panel->container, "no-thunderbolt");
|
|
|
|
return;
|
|
}
|
|
|
|
g_signal_connect_object (client,
|
|
"notify::g-name-owner",
|
|
G_CALLBACK (on_bolt_name_owner_changed_cb),
|
|
panel,
|
|
0);
|
|
|
|
g_signal_connect_object (client,
|
|
"device-added",
|
|
G_CALLBACK (on_bolt_device_added_cb),
|
|
panel,
|
|
0);
|
|
|
|
g_signal_connect_object (client,
|
|
"device-removed",
|
|
G_CALLBACK (on_bolt_device_removed_cb),
|
|
panel,
|
|
0);
|
|
|
|
g_signal_connect_object (client,
|
|
"notify::auth-mode",
|
|
G_CALLBACK (on_bolt_notify_authmode_cb),
|
|
panel,
|
|
0);
|
|
|
|
/* Treat security-level changes, which should rarely happen, as
|
|
* if the name owner changed, i.e. as if boltd got restarted */
|
|
g_signal_connect_object (client,
|
|
"notify::security-level",
|
|
G_CALLBACK (on_bolt_name_owner_changed_cb),
|
|
panel,
|
|
0);
|
|
|
|
panel->client = client;
|
|
|
|
cc_bolt_device_dialog_set_client (panel->device_dialog, client);
|
|
|
|
cc_bolt_panel_authmode_sync (panel);
|
|
|
|
g_object_bind_property (panel->authmode_switch,
|
|
"active",
|
|
panel->devices_box,
|
|
"sensitive",
|
|
G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
|
|
|
|
g_object_bind_property (panel->authmode_switch,
|
|
"active",
|
|
panel->pending_box,
|
|
"sensitive",
|
|
G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
|
|
|
|
gtk_stack_set_visible_child_name (panel->devices_stack, "no-devices");
|
|
cc_bolt_panel_name_owner_changed (panel);
|
|
}
|
|
|
|
static gboolean
|
|
devices_table_transfer_entry (GHashTable *from,
|
|
GHashTable *to,
|
|
gconstpointer key)
|
|
{
|
|
gpointer k, v;
|
|
gboolean found;
|
|
|
|
found = g_hash_table_lookup_extended (from, key, &k, &v);
|
|
|
|
if (found)
|
|
{
|
|
g_hash_table_steal (from, key);
|
|
g_hash_table_insert (to, k, v);
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
static void
|
|
devices_table_clear_entries (GHashTable *table,
|
|
CcBoltPanel *panel)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
g_hash_table_iter_init (&iter, table);
|
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
|
{
|
|
CcBoltDeviceEntry *entry = value;
|
|
|
|
cc_bolt_panel_del_device_entry (panel, entry);
|
|
g_hash_table_iter_remove (&iter);
|
|
}
|
|
}
|
|
|
|
static void
|
|
devices_table_synchronize (CcBoltPanel *panel)
|
|
{
|
|
g_autoptr(GHashTable) old = NULL;
|
|
g_autoptr(GPtrArray) devices = NULL;
|
|
g_autoptr(GError) err = NULL;
|
|
guint i;
|
|
|
|
devices = bolt_client_list_devices (panel->client, cc_panel_get_cancellable (CC_PANEL (panel)), &err);
|
|
|
|
if (!devices)
|
|
{
|
|
g_warning ("Could not list devices: %s", err->message);
|
|
devices = g_ptr_array_new_with_free_func (g_object_unref);
|
|
}
|
|
|
|
old = panel->devices;
|
|
panel->devices = g_hash_table_new (g_str_hash, g_str_equal);
|
|
|
|
for (i = 0; i < devices->len; i++)
|
|
{
|
|
BoltDevice *dev = g_ptr_array_index (devices, i);
|
|
const char *path;
|
|
gboolean found;
|
|
|
|
path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (dev));
|
|
found = devices_table_transfer_entry (old, panel->devices, path);
|
|
|
|
if (found)
|
|
continue;
|
|
|
|
cc_bolt_panel_add_device (panel, dev);
|
|
}
|
|
|
|
devices_table_clear_entries (old, panel);
|
|
gtk_stack_set_visible_child_name (panel->container, "devices-listing");
|
|
}
|
|
|
|
static gboolean
|
|
list_box_sync_visible (GtkListBox *listbox)
|
|
{
|
|
GtkWidget *child;
|
|
gboolean show;
|
|
|
|
child = gtk_widget_get_first_child (GTK_WIDGET (listbox));
|
|
show = child != NULL;
|
|
|
|
gtk_widget_set_visible (GTK_WIDGET (listbox), show);
|
|
|
|
return show;
|
|
}
|
|
|
|
static GtkWidget *
|
|
cc_bolt_panel_box_for_listbox (CcBoltPanel *panel,
|
|
GtkListBox *lstbox)
|
|
{
|
|
if ((gpointer) lstbox == panel->devices_list)
|
|
return GTK_WIDGET (panel->devices_box);
|
|
else if ((gpointer) lstbox == panel->pending_list)
|
|
return GTK_WIDGET (panel->pending_box);
|
|
|
|
g_return_val_if_reached (NULL);
|
|
}
|
|
|
|
static CcBoltDeviceEntry *
|
|
cc_bolt_panel_add_device (CcBoltPanel *panel,
|
|
BoltDevice *dev)
|
|
{
|
|
CcBoltDeviceEntry *entry;
|
|
BoltDeviceType type;
|
|
BoltStatus status;
|
|
const char *path;
|
|
|
|
type = bolt_device_get_device_type (dev);
|
|
|
|
if (type != BOLT_DEVICE_PERIPHERAL)
|
|
return FALSE;
|
|
|
|
entry = cc_bolt_device_entry_new (dev, FALSE);
|
|
path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (dev));
|
|
|
|
/* add to the list box */
|
|
status = bolt_device_get_status (dev);
|
|
|
|
if (bolt_status_is_pending (status))
|
|
{
|
|
gtk_list_box_append (panel->pending_list, GTK_WIDGET (entry));
|
|
gtk_widget_show (GTK_WIDGET (panel->pending_list));
|
|
gtk_widget_show (GTK_WIDGET (panel->pending_box));
|
|
}
|
|
else
|
|
{
|
|
gtk_list_box_append (panel->devices_list, GTK_WIDGET (entry));
|
|
gtk_widget_show (GTK_WIDGET (panel->devices_list));
|
|
gtk_widget_show (GTK_WIDGET (panel->devices_box));
|
|
}
|
|
|
|
g_signal_connect_object (entry,
|
|
"status-changed",
|
|
G_CALLBACK (on_device_entry_status_changed_cb),
|
|
panel,
|
|
0);
|
|
|
|
gtk_stack_set_visible_child_name (panel->devices_stack, "have-devices");
|
|
g_hash_table_insert (panel->devices, (gpointer) path, entry);
|
|
|
|
return entry;
|
|
}
|
|
|
|
static void
|
|
cc_bolt_panel_del_device_entry (CcBoltPanel *panel,
|
|
CcBoltDeviceEntry *entry)
|
|
{
|
|
BoltDevice *dev;
|
|
GtkWidget *box;
|
|
GtkWidget *p;
|
|
gboolean show;
|
|
|
|
dev = cc_bolt_device_entry_get_device (entry);
|
|
if (cc_bolt_device_dialog_device_equal (panel->device_dialog, dev))
|
|
{
|
|
gtk_widget_hide (GTK_WIDGET (panel->device_dialog));
|
|
cc_bolt_device_dialog_set_device (panel->device_dialog, NULL, NULL);
|
|
}
|
|
|
|
p = gtk_widget_get_parent (GTK_WIDGET (entry));
|
|
gtk_list_box_remove (GTK_LIST_BOX (p), GTK_WIDGET (entry));
|
|
|
|
box = cc_bolt_panel_box_for_listbox (panel, GTK_LIST_BOX (p));
|
|
show = list_box_sync_visible (GTK_LIST_BOX (p));
|
|
gtk_widget_set_visible (box, show);
|
|
|
|
if (!gtk_widget_is_visible (GTK_WIDGET (panel->pending_list)) &&
|
|
!gtk_widget_is_visible (GTK_WIDGET (panel->devices_list)))
|
|
{
|
|
gtk_stack_set_visible_child_name (panel->devices_stack, "no-devices");
|
|
}
|
|
}
|
|
|
|
static void
|
|
cc_bolt_panel_authmode_sync (CcBoltPanel *panel)
|
|
{
|
|
BoltClient *client = panel->client;
|
|
BoltAuthMode mode;
|
|
gboolean enabled;
|
|
|
|
mode = bolt_client_get_authmode (client);
|
|
enabled = (mode & BOLT_AUTH_ENABLED) != 0;
|
|
|
|
g_signal_handlers_block_by_func (panel->authmode_switch, on_authmode_state_set_cb, panel);
|
|
|
|
gtk_switch_set_state (panel->authmode_switch, enabled);
|
|
|
|
g_signal_handlers_unblock_by_func (panel->authmode_switch, on_authmode_state_set_cb, panel);
|
|
|
|
adw_preferences_row_set_title (ADW_PREFERENCES_ROW (panel->direct_access_row),
|
|
enabled ?
|
|
_("Allow direct access to devices such as docks and external GPUs.") :
|
|
_("Only USB and Display Port devices can attach."));
|
|
}
|
|
|
|
static void
|
|
cc_panel_list_box_migrate (CcBoltPanel *panel,
|
|
GtkListBox *from,
|
|
GtkListBox *to,
|
|
CcBoltDeviceEntry *entry)
|
|
{
|
|
GtkWidget *from_box;
|
|
GtkWidget *to_box;
|
|
gboolean show;
|
|
GtkWidget *target;
|
|
|
|
target = GTK_WIDGET (entry);
|
|
|
|
gtk_list_box_remove (from, target);
|
|
gtk_list_box_append (to, target);
|
|
gtk_widget_show (GTK_WIDGET (to));
|
|
|
|
from_box = cc_bolt_panel_box_for_listbox (panel, from);
|
|
to_box = cc_bolt_panel_box_for_listbox (panel, to);
|
|
|
|
show = list_box_sync_visible (from);
|
|
gtk_widget_set_visible (from_box, show);
|
|
gtk_widget_set_visible (to_box, TRUE);
|
|
}
|
|
|
|
/* bolt client signals */
|
|
static void
|
|
cc_bolt_panel_set_no_thunderbolt (CcBoltPanel *panel,
|
|
const char *msg)
|
|
{
|
|
if (!msg)
|
|
{
|
|
msg = _("Thunderbolt could not be detected.\n"
|
|
"Either the system lacks Thunderbolt support, "
|
|
"it has been disabled in the BIOS or is set to "
|
|
"an unsupported security level in the BIOS.");
|
|
}
|
|
|
|
adw_status_page_set_description (panel->notb_page, msg);
|
|
gtk_stack_set_visible_child_name (panel->container, "no-thunderbolt");
|
|
}
|
|
|
|
static void
|
|
cc_bolt_panel_name_owner_changed (CcBoltPanel *panel)
|
|
{
|
|
g_autofree char *name_owner = NULL;
|
|
BoltClient *client = panel->client;
|
|
BoltSecurity sl;
|
|
gboolean notb = TRUE;
|
|
const char *text = NULL;
|
|
|
|
name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (panel->client));
|
|
|
|
if (name_owner == NULL)
|
|
{
|
|
cc_bolt_panel_set_no_thunderbolt (panel, NULL);
|
|
devices_table_clear_entries (panel->devices, panel);
|
|
gtk_widget_hide (GTK_WIDGET (panel->headerbar_box));
|
|
return;
|
|
}
|
|
|
|
gtk_stack_set_visible_child_name (panel->container, "loading");
|
|
|
|
sl = bolt_client_get_security (client);
|
|
|
|
switch (sl)
|
|
{
|
|
case BOLT_SECURITY_NONE:
|
|
case BOLT_SECURITY_SECURE:
|
|
case BOLT_SECURITY_USER:
|
|
/* we fetch the device list and show them here */
|
|
notb = FALSE;
|
|
break;
|
|
|
|
case BOLT_SECURITY_DPONLY:
|
|
case BOLT_SECURITY_USBONLY:
|
|
text = _("Thunderbolt support has been disabled in the BIOS.");
|
|
break;
|
|
|
|
case BOLT_SECURITY_UNKNOWN:
|
|
text = _("Thunderbolt security level could not be determined.");;
|
|
break;
|
|
}
|
|
|
|
if (notb)
|
|
{
|
|
/* security level is unknown or un-handled */
|
|
cc_bolt_panel_set_no_thunderbolt (panel, text);
|
|
return;
|
|
}
|
|
|
|
if (panel->permission)
|
|
{
|
|
gtk_widget_show (GTK_WIDGET (panel->headerbar_box));
|
|
}
|
|
else
|
|
{
|
|
polkit_permission_new ("org.freedesktop.bolt.manage",
|
|
NULL,
|
|
cc_panel_get_cancellable (CC_PANEL (panel)),
|
|
on_permission_ready,
|
|
g_object_ref (panel));
|
|
}
|
|
|
|
devices_table_synchronize (panel);
|
|
}
|
|
|
|
/* bolt client signals */
|
|
static void
|
|
on_bolt_name_owner_changed_cb (GObject *object,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
cc_bolt_panel_name_owner_changed (CC_BOLT_PANEL (user_data));
|
|
}
|
|
|
|
static void
|
|
on_bolt_device_added_cb (BoltClient *cli,
|
|
const char *path,
|
|
CcBoltPanel *panel)
|
|
{
|
|
g_autoptr(GError) err = NULL;
|
|
GDBusConnection *bus;
|
|
BoltDevice *dev;
|
|
gboolean found;
|
|
|
|
found = g_hash_table_contains (panel->devices, path);
|
|
|
|
if (found)
|
|
return;
|
|
|
|
bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (panel->client));
|
|
dev = bolt_device_new_for_object_path (bus, path, cc_panel_get_cancellable (CC_PANEL (panel)), &err);
|
|
|
|
if (!dev)
|
|
{
|
|
g_warning ("Could not create proxy for %s", path);
|
|
return;
|
|
}
|
|
|
|
cc_bolt_panel_add_device (panel, dev);
|
|
}
|
|
|
|
static void
|
|
on_bolt_device_removed_cb (BoltClient *cli,
|
|
const char *path,
|
|
CcBoltPanel *panel)
|
|
{
|
|
CcBoltDeviceEntry *entry;
|
|
|
|
entry = g_hash_table_lookup (panel->devices, path);
|
|
|
|
if (!entry)
|
|
return;
|
|
|
|
cc_bolt_panel_del_device_entry (panel, entry);
|
|
g_hash_table_remove (panel->devices, path);
|
|
}
|
|
|
|
static void
|
|
on_bolt_notify_authmode_cb (GObject *gobject,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
cc_bolt_panel_authmode_sync (CC_BOLT_PANEL (user_data));
|
|
}
|
|
|
|
/* panel signals */
|
|
|
|
static void
|
|
on_authmode_ready (GObject *source_object,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
CcBoltPanel *panel = CC_BOLT_PANEL (user_data);
|
|
gboolean ok;
|
|
|
|
ok = bolt_client_set_authmode_finish (BOLT_CLIENT (source_object), res, &error);
|
|
if (!ok)
|
|
{
|
|
g_autofree char *text = NULL;
|
|
|
|
g_warning ("Could not set authmode: %s", error->message);
|
|
|
|
text = g_strdup_printf (_("Error switching direct mode: %s"), error->message);
|
|
gtk_label_set_markup (panel->notification_label, text);
|
|
gtk_revealer_set_reveal_child (panel->notification_revealer, TRUE);
|
|
|
|
/* make sure we are reflecting the correct state */
|
|
cc_bolt_panel_authmode_sync (panel);
|
|
}
|
|
|
|
gtk_spinner_stop (panel->authmode_spinner);
|
|
gtk_widget_set_sensitive (GTK_WIDGET (panel->authmode_switch), TRUE);
|
|
}
|
|
|
|
static gboolean
|
|
on_authmode_state_set_cb (CcBoltPanel *panel,
|
|
gboolean enable,
|
|
GtkSwitch *toggle)
|
|
{
|
|
BoltClient *client = panel->client;
|
|
BoltAuthMode mode;
|
|
|
|
gtk_widget_set_sensitive (GTK_WIDGET (panel->authmode_switch), FALSE);
|
|
gtk_spinner_start (panel->authmode_spinner);
|
|
|
|
mode = bolt_client_get_authmode (client);
|
|
|
|
if (enable)
|
|
mode = mode | BOLT_AUTH_ENABLED;
|
|
else
|
|
mode = mode & ~BOLT_AUTH_ENABLED;
|
|
|
|
bolt_client_set_authmode_async (client, mode, NULL, on_authmode_ready, panel);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
on_device_entry_row_activated_cb (CcBoltPanel *panel,
|
|
GtkListBoxRow *row)
|
|
{
|
|
g_autoptr(GPtrArray) parents = NULL;
|
|
CcBoltDeviceEntry *entry;
|
|
BoltDevice *device;
|
|
BoltDevice *iter;
|
|
const char *parent;
|
|
|
|
if (!CC_IS_BOLT_DEVICE_ENTRY (row))
|
|
return;
|
|
|
|
entry = CC_BOLT_DEVICE_ENTRY (row);
|
|
device = cc_bolt_device_entry_get_device (entry);
|
|
|
|
/* walk up the chain and collect all parents */
|
|
parents = g_ptr_array_new_with_free_func (g_object_unref);
|
|
iter = device;
|
|
|
|
parent = bolt_device_get_parent (iter);
|
|
while (parent != NULL)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
CcBoltDeviceEntry *child;
|
|
BoltDevice *dev;
|
|
|
|
path = bolt_gen_object_path (BOLT_DBUS_PATH_DEVICES, parent);
|
|
|
|
/* NB: the host device is not a peripheral and thus not
|
|
* in the hash table; therefore when get a NULL back, we
|
|
* should have reached the end of the chain */
|
|
child = g_hash_table_lookup (panel->devices, path);
|
|
if (!child)
|
|
break;
|
|
|
|
dev = cc_bolt_device_entry_get_device (child);
|
|
g_ptr_array_add (parents, g_object_ref (dev));
|
|
iter = dev;
|
|
|
|
parent = bolt_device_get_parent (iter);
|
|
}
|
|
|
|
cc_bolt_device_dialog_set_device (panel->device_dialog, device, parents);
|
|
|
|
gtk_window_set_default_size (GTK_WINDOW (panel->device_dialog), 1, 1);
|
|
gtk_widget_show (GTK_WIDGET (panel->device_dialog));
|
|
}
|
|
|
|
static void
|
|
on_device_entry_status_changed_cb (CcBoltDeviceEntry *entry,
|
|
BoltStatus new_status,
|
|
CcBoltPanel *panel)
|
|
{
|
|
GtkListBox *from = NULL;
|
|
GtkListBox *to = NULL;
|
|
GtkWidget *p;
|
|
gboolean is_pending;
|
|
gboolean parent_pending;
|
|
|
|
/* if we are doing some active work, then lets not change
|
|
* the list the entry is in; otherwise we might just hop
|
|
* from one box to the other and back again.
|
|
*/
|
|
if (new_status == BOLT_STATUS_CONNECTING || new_status == BOLT_STATUS_AUTHORIZING)
|
|
return;
|
|
|
|
is_pending = bolt_status_is_pending (new_status);
|
|
|
|
p = gtk_widget_get_parent (GTK_WIDGET (entry));
|
|
parent_pending = (gpointer) p == panel->pending_list;
|
|
|
|
/* */
|
|
if (is_pending && !parent_pending)
|
|
{
|
|
from = panel->devices_list;
|
|
to = panel->pending_list;
|
|
}
|
|
else if (!is_pending && parent_pending)
|
|
{
|
|
from = panel->pending_list;
|
|
to = panel->devices_list;
|
|
}
|
|
|
|
if (from && to)
|
|
cc_panel_list_box_migrate (panel, from, to, entry);
|
|
}
|
|
|
|
|
|
static void
|
|
on_notification_button_clicked_cb (GtkButton *button,
|
|
CcBoltPanel *panel)
|
|
{
|
|
gtk_revealer_set_reveal_child (panel->notification_revealer, FALSE);
|
|
}
|
|
|
|
/* polkit */
|
|
|
|
static void
|
|
on_permission_ready (GObject *source_object,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr(CcBoltPanel) panel = user_data;
|
|
g_autoptr(GError) err = NULL;
|
|
GPermission *permission;
|
|
gboolean is_allowed;
|
|
const char *name;
|
|
|
|
permission = polkit_permission_new_finish (res, &err);
|
|
panel->permission = permission;
|
|
|
|
if (!panel->permission)
|
|
{
|
|
g_warning ("Could not get polkit permissions: %s", err->message);
|
|
return;
|
|
}
|
|
|
|
g_signal_connect_object (permission,
|
|
"notify",
|
|
G_CALLBACK (on_permission_notify_cb),
|
|
panel,
|
|
G_CONNECT_AFTER);
|
|
|
|
is_allowed = g_permission_get_allowed (permission);
|
|
gtk_widget_set_sensitive (GTK_WIDGET (panel->authmode_switch), is_allowed);
|
|
gtk_lock_button_set_permission (panel->lock_button, permission);
|
|
|
|
name = gtk_stack_get_visible_child_name (panel->container);
|
|
|
|
gtk_widget_set_visible (GTK_WIDGET (panel->headerbar_box),
|
|
bolt_streq (name, "devices-listing"));
|
|
}
|
|
|
|
static void
|
|
on_permission_notify_cb (GPermission *permission,
|
|
GParamSpec *pspec,
|
|
CcBoltPanel *panel)
|
|
{
|
|
gboolean is_allowed = g_permission_get_allowed (permission);
|
|
|
|
gtk_widget_set_sensitive (GTK_WIDGET (panel->authmode_switch), is_allowed);
|
|
}
|
|
|
|
static gint
|
|
device_entries_sort_by_recency_cb (GtkListBoxRow *a_row,
|
|
GtkListBoxRow *b_row,
|
|
gpointer user_data)
|
|
{
|
|
CcBoltDeviceEntry *a_entry = CC_BOLT_DEVICE_ENTRY (a_row);
|
|
CcBoltDeviceEntry *b_entry = CC_BOLT_DEVICE_ENTRY (b_row);
|
|
BoltDevice *a = cc_bolt_device_entry_get_device (a_entry);
|
|
BoltDevice *b = cc_bolt_device_entry_get_device (b_entry);
|
|
BoltStatus status;
|
|
gint64 a_ts, b_ts;
|
|
gint64 score;
|
|
|
|
a_ts = (gint64) bolt_device_get_timestamp (a);
|
|
b_ts = (gint64) bolt_device_get_timestamp (b);
|
|
|
|
score = b_ts - a_ts;
|
|
|
|
if (score != 0)
|
|
return score;
|
|
|
|
status = bolt_device_get_status (a);
|
|
|
|
if (bolt_status_is_connected (status))
|
|
{
|
|
const char *a_path;
|
|
const char *b_path;
|
|
|
|
a_path = bolt_device_get_syspath (a);
|
|
b_path = bolt_device_get_syspath (b);
|
|
|
|
return g_strcmp0 (a_path, b_path);
|
|
}
|
|
else
|
|
{
|
|
const char *a_name;
|
|
const char *b_name;
|
|
|
|
a_name = bolt_device_get_name (a);
|
|
b_name = bolt_device_get_name (b);
|
|
|
|
return g_strcmp0 (a_name, b_name);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gint
|
|
device_entries_sort_by_syspath_cb (GtkListBoxRow *a_row,
|
|
GtkListBoxRow *b_row,
|
|
gpointer user_data)
|
|
{
|
|
CcBoltDeviceEntry *a_entry = CC_BOLT_DEVICE_ENTRY (a_row);
|
|
CcBoltDeviceEntry *b_entry = CC_BOLT_DEVICE_ENTRY (b_row);
|
|
BoltDevice *a = cc_bolt_device_entry_get_device (a_entry);
|
|
BoltDevice *b = cc_bolt_device_entry_get_device (b_entry);
|
|
|
|
const char *a_path;
|
|
const char *b_path;
|
|
|
|
a_path = bolt_device_get_syspath (a);
|
|
b_path = bolt_device_get_syspath (b);
|
|
|
|
return g_strcmp0 (a_path, b_path);
|
|
}
|
|
|
|
/* GObject overrides */
|
|
|
|
static void
|
|
cc_bolt_panel_finalize (GObject *object)
|
|
{
|
|
CcBoltPanel *panel = CC_BOLT_PANEL (object);
|
|
|
|
g_clear_object (&panel->client);
|
|
g_clear_pointer (&panel->devices, g_hash_table_unref);
|
|
g_clear_object (&panel->permission);
|
|
|
|
G_OBJECT_CLASS (cc_bolt_panel_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
cc_bolt_panel_dispose (GObject *object)
|
|
{
|
|
CcBoltPanel *panel = CC_BOLT_PANEL (object);
|
|
|
|
/* Must be destroyed in dispose, not finalize. */
|
|
cc_bolt_device_dialog_set_device (panel->device_dialog, NULL, NULL);
|
|
g_clear_pointer ((GtkWindow **) &panel->device_dialog, gtk_window_destroy);
|
|
|
|
G_OBJECT_CLASS (cc_bolt_panel_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
cc_bolt_panel_constructed (GObject *object)
|
|
{
|
|
CcBoltPanel *panel = CC_BOLT_PANEL (object);
|
|
GtkWindow *parent;
|
|
CcShell *shell;
|
|
|
|
G_OBJECT_CLASS (cc_bolt_panel_parent_class)->constructed (object);
|
|
|
|
shell = cc_panel_get_shell (CC_PANEL (panel));
|
|
parent = GTK_WINDOW (cc_shell_get_toplevel (shell));
|
|
gtk_window_set_transient_for (GTK_WINDOW (panel->device_dialog), parent);
|
|
}
|
|
|
|
static void
|
|
cc_bolt_panel_class_init (CcBoltPanelClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
|
|
object_class->constructed = cc_bolt_panel_constructed;
|
|
object_class->dispose = cc_bolt_panel_dispose;
|
|
object_class->finalize = cc_bolt_panel_finalize;
|
|
|
|
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/thunderbolt/cc-bolt-panel.ui");
|
|
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, authmode_spinner);
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, authmode_switch);
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, container);
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, devices_list);
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, devices_box);
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, devices_stack);
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, direct_access_row);
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, headerbar_box);
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, lock_button);
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, notb_page);
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, notification_label);
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, notification_revealer);
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, pending_box);
|
|
gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, pending_list);
|
|
|
|
gtk_widget_class_bind_template_callback (widget_class, on_notification_button_clicked_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, on_authmode_state_set_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, on_device_entry_row_activated_cb);
|
|
}
|
|
|
|
static void
|
|
cc_bolt_panel_init (CcBoltPanel *panel)
|
|
{
|
|
g_resources_register (cc_thunderbolt_get_resource ());
|
|
|
|
gtk_widget_init_template (GTK_WIDGET (panel));
|
|
|
|
gtk_stack_set_visible_child_name (panel->container, "loading");
|
|
|
|
gtk_list_box_set_sort_func (panel->devices_list,
|
|
device_entries_sort_by_recency_cb,
|
|
panel,
|
|
NULL);
|
|
|
|
gtk_list_box_set_sort_func (panel->pending_list,
|
|
device_entries_sort_by_syspath_cb,
|
|
panel,
|
|
NULL);
|
|
|
|
panel->devices = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
|
|
|
|
panel->device_dialog = cc_bolt_device_dialog_new ();
|
|
|
|
bolt_client_new_async (cc_panel_get_cancellable (CC_PANEL (panel)), bolt_client_ready, g_object_ref (panel));
|
|
}
|