diff options
Diffstat (limited to 'panels/thunderbolt')
29 files changed, 6724 insertions, 0 deletions
diff --git a/panels/thunderbolt/bolt-client.c b/panels/thunderbolt/bolt-client.c new file mode 100644 index 0000000..1612b65 --- /dev/null +++ b/panels/thunderbolt/bolt-client.c @@ -0,0 +1,1054 @@ +/* + * Copyright © 2017 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#include "bolt-client.h" + +#include "bolt-device.h" +#include "bolt-error.h" +#include "bolt-names.h" + +#include <gio/gio.h> + +static void handle_dbus_device_added (GObject *self, + GDBusProxy *bus_proxy, + GVariant *params); +static void handle_dbus_device_removed (GObject *self, + GDBusProxy *bus_proxy, + GVariant *params); + +struct _BoltClient +{ + BoltProxy parent; +}; + +enum { + PROP_0, + + /* D-Bus Props */ + PROP_VERSION, + PROP_PROBING, + PROP_SECURITY, + PROP_AUTHMODE, + + PROP_LAST +}; + +static GParamSpec *props[PROP_LAST] = {NULL, }; + +enum { + SIGNAL_DEVICE_ADDED, + SIGNAL_DEVICE_REMOVED, + SIGNAL_LAST +}; + +static guint signals[SIGNAL_LAST] = {0}; + + +G_DEFINE_TYPE (BoltClient, + bolt_client, + BOLT_TYPE_PROXY); + + +static void +bolt_client_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + if (bolt_proxy_get_dbus_property (object, pspec, value)) + return; + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static const BoltProxySignal * +bolt_client_get_dbus_signals (guint *n) +{ + static BoltProxySignal dbus_signals[] = { + {"DeviceAdded", handle_dbus_device_added}, + {"DeviceRemoved", handle_dbus_device_removed}, + }; + + *n = G_N_ELEMENTS (dbus_signals); + + return dbus_signals; +} + + +static void +bolt_client_class_init (BoltClientClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + BoltProxyClass *proxy_class = BOLT_PROXY_CLASS (klass); + + gobject_class->get_property = bolt_client_get_property; + + proxy_class->get_dbus_signals = bolt_client_get_dbus_signals; + + props[PROP_VERSION] + = g_param_spec_uint ("version", + "Version", NULL, + 0, G_MAXUINT, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME); + + props[PROP_PROBING] + = g_param_spec_boolean ("probing", + "Probing", NULL, + FALSE, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME); + + props[PROP_SECURITY] + = g_param_spec_enum ("security-level", + "SecurityLevel", NULL, + BOLT_TYPE_SECURITY, + BOLT_SECURITY_UNKNOWN, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME); + + props[PROP_AUTHMODE] = + g_param_spec_flags ("auth-mode", "AuthMode", NULL, + BOLT_TYPE_AUTH_MODE, + BOLT_AUTH_ENABLED, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + PROP_LAST, + props); + + /* signals */ + signals[SIGNAL_DEVICE_ADDED] = + g_signal_new ("device-added", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, + 1, G_TYPE_STRING); + + signals[SIGNAL_DEVICE_REMOVED] = + g_signal_new ("device-removed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, + 1, G_TYPE_STRING); +} + + +static void +bolt_client_init (BoltClient *cli) +{ +} + +/* dbus signals */ + +static void +handle_dbus_device_added (GObject *self, GDBusProxy *bus_proxy, GVariant *params) +{ + BoltClient *cli = BOLT_CLIENT (self); + const char *opath = NULL; + + g_variant_get_child (params, 0, "&o", &opath); + g_signal_emit (cli, signals[SIGNAL_DEVICE_ADDED], 0, opath); +} + +static void +handle_dbus_device_removed (GObject *self, GDBusProxy *bus_proxy, GVariant *params) +{ + BoltClient *cli = BOLT_CLIENT (self); + const char *opath = NULL; + + g_variant_get_child (params, 0, "&o", &opath); + g_signal_emit (cli, signals[SIGNAL_DEVICE_REMOVED], 0, opath); +} + +/* public methods */ + +BoltClient * +bolt_client_new (GError **error) +{ + BoltClient *cli; + GDBusConnection *bus; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); + if (bus == NULL) + { + g_prefix_error (error, "Error connecting to D-Bus: "); + return FALSE; + } + + cli = g_initable_new (BOLT_TYPE_CLIENT, + NULL, error, + "g-flags", G_DBUS_PROXY_FLAGS_NONE, + "g-connection", bus, + "g-name", BOLT_DBUS_NAME, + "g-object-path", BOLT_DBUS_PATH, + "g-interface-name", BOLT_DBUS_INTERFACE, + NULL); + + g_object_unref (bus); + + return cli; +} + +static void +got_the_client (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GError *error = NULL; + GTask *task = user_data; + GObject *obj; + + obj = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, &error); + + if (obj == NULL) + { + /* error ownership gets transferred to the task */ + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + g_task_return_pointer (task, obj, g_object_unref); + g_object_unref (task); +} + +static void +got_the_bus (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GError *error = NULL; + GTask *task = user_data; + GCancellable *cancellable; + GDBusConnection *bus; + + bus = g_bus_get_finish (res, &error); + if (bus == NULL) + { + g_prefix_error (&error, "could not connect to D-Bus: "); + /* error ownership gets transferred to the task */ + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + cancellable = g_task_get_cancellable (task); + g_async_initable_new_async (BOLT_TYPE_CLIENT, + G_PRIORITY_DEFAULT, + cancellable, + got_the_client, task, + "g-flags", G_DBUS_PROXY_FLAGS_NONE, + "g-connection", bus, + "g-name", BOLT_DBUS_NAME, + "g-object-path", BOLT_DBUS_PATH, + "g-interface-name", BOLT_DBUS_INTERFACE, + NULL); + g_object_unref (bus); +} + +void +bolt_client_new_async (GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (NULL, cancellable, callback, user_data); + g_bus_get (G_BUS_TYPE_SYSTEM, cancellable, got_the_bus, task); +} + +BoltClient * +bolt_client_new_finish (GAsyncResult *res, + GError **error) +{ + g_return_val_if_fail (G_IS_TASK (res), NULL); + + return g_task_propagate_pointer (G_TASK (res), error); +} + +GPtrArray * +bolt_client_list_devices (BoltClient *client, + GCancellable *cancel, + GError **error) +{ + g_autoptr(GVariant) val = NULL; + g_autoptr(GPtrArray) devices = NULL; + g_autoptr(GVariantIter) iter = NULL; + GDBusConnection *bus = NULL; + const char *d; + + g_return_val_if_fail (BOLT_IS_CLIENT (client), NULL); + + val = g_dbus_proxy_call_sync (G_DBUS_PROXY (client), + "ListDevices", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + cancel, + error); + if (val == NULL) + return NULL; + + bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client)); + + devices = g_ptr_array_new_with_free_func (g_object_unref); + + g_variant_get (val, "(ao)", &iter); + while (g_variant_iter_loop (iter, "&o", &d, NULL)) + { + BoltDevice *dev; + + dev = bolt_device_new_for_object_path (bus, d, cancel, error); + if (dev == NULL) + return NULL; + + g_ptr_array_add (devices, dev); + } + + return g_steal_pointer (&devices); +} + +BoltDevice * +bolt_client_get_device (BoltClient *client, + const char *uid, + GCancellable *cancel, + GError **error) +{ + g_autoptr(GVariant) val = NULL; + g_autoptr(GError) err = NULL; + BoltDevice *dev = NULL; + GDBusConnection *bus = NULL; + const char *opath = NULL; + + g_return_val_if_fail (BOLT_IS_CLIENT (client), NULL); + + val = g_dbus_proxy_call_sync (G_DBUS_PROXY (client), + "DeviceByUid", + g_variant_new ("(s)", uid), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancel, + &err); + + if (val == NULL) + { + bolt_error_propagate_stripped (error, &err); + return NULL; + } + + bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client)); + g_variant_get (val, "(&o)", &opath); + + if (opath == NULL) + return NULL; + + dev = bolt_device_new_for_object_path (bus, opath, cancel, error); + return dev; +} + +BoltDevice * +bolt_client_enroll_device (BoltClient *client, + const char *uid, + BoltPolicy policy, + BoltAuthCtrl flags, + GError **error) +{ + g_autoptr(GVariant) val = NULL; + g_autoptr(GError) err = NULL; + g_autofree char *fstr = NULL; + BoltDevice *dev = NULL; + GDBusConnection *bus = NULL; + GVariant *params = NULL; + const char *opath = NULL; + const char *pstr; + + g_return_val_if_fail (BOLT_IS_CLIENT (client), NULL); + + pstr = bolt_enum_to_string (BOLT_TYPE_POLICY, policy, error); + if (pstr == NULL) + return NULL; + + fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, error); + if (fstr == NULL) + return NULL; + + params = g_variant_new ("(sss)", uid, pstr, fstr); + val = g_dbus_proxy_call_sync (G_DBUS_PROXY (client), + "EnrollDevice", + params, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &err); + + if (val == NULL) + { + bolt_error_propagate_stripped (error, &err); + return NULL; + } + + bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client)); + g_variant_get (val, "(&o)", &opath); + + if (opath == NULL) + return NULL; + + dev = bolt_device_new_for_object_path (bus, opath, NULL, error); + return dev; +} + +void +bolt_client_enroll_device_async (BoltClient *client, + const char *uid, + BoltPolicy policy, + BoltAuthCtrl flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autofree char *fstr = NULL; + GError *err = NULL; + GVariant *params; + const char *pstr; + + g_return_if_fail (BOLT_IS_CLIENT (client)); + g_return_if_fail (uid != NULL); + + pstr = bolt_enum_to_string (BOLT_TYPE_POLICY, policy, &err); + if (pstr == NULL) + { + g_task_report_error (client, callback, user_data, NULL, err); + return; + } + + fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err); + if (fstr == NULL) + { + g_task_report_error (client, callback, user_data, NULL, err); + return; + } + + params = g_variant_new ("(sss)", uid, pstr, fstr); + g_dbus_proxy_call (G_DBUS_PROXY (client), + "EnrollDevice", + params, + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +gboolean +bolt_client_enroll_device_finish (BoltClient *client, + GAsyncResult *res, + char **path, + GError **error) +{ + g_autoptr(GError) err = NULL; + g_autoptr(GVariant) val = NULL; + + g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE); + + val = g_dbus_proxy_call_finish (G_DBUS_PROXY (client), res, &err); + if (val == NULL) + { + bolt_error_propagate_stripped (error, &err); + return FALSE; + } + + if (path != NULL) + g_variant_get (val, "(o)", path); + + return TRUE; +} + +typedef struct OpData +{ + const char *iface; /* Manager or Device */ + const char *method; /* Enroll or Authorize */ + char *path; /* object path */ + GVariant *params; /* parameters */ + +} OpData; + +static OpData * +op_data_new_enroll (const char *uid, + const char *policy, + const char *flags) +{ + GVariant *params; + OpData *op; + + params = g_variant_new ("(sss)", uid, policy, flags); + + op = g_slice_new (OpData); + op->iface = BOLT_DBUS_INTERFACE; + op->method = "EnrollDevice"; + op->params = g_variant_ref_sink (params); + op->path = g_strdup (BOLT_DBUS_PATH); + + return op; +} + + +static OpData * +op_data_new_authorize (const char *uid, + const char *flags) +{ + OpData *op = NULL; + GVariant *params; + char *path; + + path = bolt_gen_object_path (BOLT_DBUS_PATH_DEVICES, uid); + params = g_variant_new ("(s)", flags); + + op = g_slice_new (OpData); + + op->iface = BOLT_DBUS_DEVICE_INTERFACE; + op->method = "Authorize"; + op->params = g_variant_ref_sink (params); + op->path = path; /* takes ownership */ + + return op; +} + +static void +op_data_free (OpData *op) +{ + g_clear_pointer (&op->params, g_variant_unref); + g_clear_pointer (&op->path, g_free); + g_slice_free (OpData, op); +} + +static void +op_queue_free (GQueue *queue) +{ + g_queue_free_full (queue, (GDestroyNotify) op_data_free); +} + +static void allop_one_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data); + +static gboolean +allop_continue (BoltClient *client, GTask *task, GQueue *ops) +{ + GDBusConnection *bus; + GCancellable *cancel; + OpData *op; + + cancel = g_task_get_cancellable (task); + bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client)); + + op = g_queue_pop_head (ops); + + if (op == NULL) + return TRUE; + + g_dbus_connection_call (bus, + BOLT_DBUS_NAME, + op->path, + op->iface, + op->method, + op->params, + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + cancel, + allop_one_done, + task); + + op_data_free (op); + + return FALSE; +} + +static void +allop_one_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GVariant) val = NULL; + BoltClient *client; + GDBusConnection *bus; + gboolean done; + GError *err = NULL; + GQueue *ops; + GTask *task; + + task = G_TASK (user_data); + + ops = g_task_get_task_data (task); + client = g_task_get_source_object (task); + bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (client)); + + val = g_dbus_connection_call_finish (bus, res, &err); + + if (val == NULL) + { + g_task_return_error (task, err); /* takes ownership */ + g_object_unref (task); + /* we are done (albeit with an error) */ + return; + } + + done = allop_continue (client, task, ops); + + if (done) + { + /* we are done */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + } +} + +void +bolt_client_enroll_all_async (BoltClient *client, + GPtrArray *uuids, + BoltPolicy policy, + BoltAuthCtrl flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autofree char *fstr = NULL; + GError *err = NULL; + const char *pstr; + GQueue *ops; + GTask *task; + + g_return_if_fail (BOLT_IS_CLIENT (client)); + g_return_if_fail (uuids != NULL && uuids->len > 0); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (callback != NULL); + + pstr = bolt_enum_to_string (BOLT_TYPE_POLICY, policy, &err); + if (pstr == NULL) + { + g_task_report_error (client, callback, user_data, NULL, err); + return; + } + + fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err); + if (fstr == NULL) + { + g_task_report_error (client, callback, user_data, NULL, err); + return; + } + + task = g_task_new (client, cancellable, callback, user_data); + g_task_set_return_on_cancel (task, TRUE); + + ops = g_queue_new (); + g_task_set_task_data (task, ops, (GDestroyNotify) op_queue_free); + + for (guint i = 0; i < uuids->len; i++) + { + const char *uid = g_ptr_array_index (uuids, i); + OpData *op; + + op = op_data_new_enroll (uid, pstr, fstr); + + g_queue_push_tail (ops, op); + } + + allop_continue (client, task, ops); +} + +gboolean +bolt_client_enroll_all_finish (BoltClient *client, + GAsyncResult *res, + GError **error) +{ + g_autoptr(GError) err = NULL; + gboolean ok; + + g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE); + g_return_val_if_fail (g_task_is_valid (res, client), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ok = g_task_propagate_boolean (G_TASK (res), &err); + + if (!ok) + bolt_error_propagate_stripped (error, &err); + + return ok; +} + +void +bolt_client_authorize_all_async (BoltClient *client, + GPtrArray *uuids, + BoltAuthCtrl flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autofree char *fstr = NULL; + GError *err = NULL; + GQueue *ops; + GTask *task; + + g_return_if_fail (BOLT_IS_CLIENT (client)); + g_return_if_fail (uuids != NULL && uuids->len > 0); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (callback != NULL); + + fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err); + if (fstr == NULL) + { + g_task_report_error (client, callback, user_data, NULL, err); + return; + } + + task = g_task_new (client, cancellable, callback, user_data); + g_task_set_return_on_cancel (task, TRUE); + + ops = g_queue_new (); + g_task_set_task_data (task, ops, (GDestroyNotify) op_queue_free); + + for (guint i = 0; i < uuids->len; i++) + { + const char *uid = g_ptr_array_index (uuids, i); + OpData *op; + + op = op_data_new_authorize (uid, fstr); + + g_queue_push_tail (ops, op); + } + + allop_continue (client, task, ops); +} + +gboolean +bolt_client_authorize_all_finish (BoltClient *client, + GAsyncResult *res, + GError **error) +{ + g_autoptr(GError) err = NULL; + gboolean ok; + + g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE); + g_return_val_if_fail (g_task_is_valid (res, client), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ok = g_task_propagate_boolean (G_TASK (res), &err); + + if (!ok) + bolt_error_propagate_stripped (error, &err); + + return ok; +} + +void +bolt_client_connect_all_async (BoltClient *client, + GPtrArray *devices, + BoltPolicy policy, + BoltAuthCtrl flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autofree char *fstr = NULL; + GError *err = NULL; + const char *pstr; + GQueue *ops; + GTask *task; + + g_return_if_fail (BOLT_IS_CLIENT (client)); + g_return_if_fail (devices != NULL && devices->len > 0); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (callback != NULL); + + pstr = bolt_enum_to_string (BOLT_TYPE_POLICY, policy, &err); + if (pstr == NULL) + { + g_task_report_error (client, callback, user_data, NULL, err); + return; + } + + fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err); + if (fstr == NULL) + { + g_task_report_error (client, callback, user_data, NULL, err); + return; + } + + task = g_task_new (client, cancellable, callback, user_data); + g_task_set_return_on_cancel (task, TRUE); + + ops = g_queue_new (); + g_task_set_task_data (task, ops, (GDestroyNotify) op_queue_free); + + for (guint i = 0; i < devices->len; i++) + { + BoltDevice *dev = g_ptr_array_index (devices, i); + const char *uid = bolt_device_get_uid (dev); + OpData *op; + + if (bolt_device_is_stored (dev)) + op = op_data_new_authorize (uid, fstr); + else + op = op_data_new_enroll (uid, pstr, fstr); + + g_queue_push_tail (ops, op); + } + + allop_continue (client, task, ops); +} + +gboolean +bolt_client_connect_all_finish (BoltClient *client, + GAsyncResult *res, + GError **error) +{ + g_autoptr(GError) err = NULL; + gboolean ok; + + g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE); + g_return_val_if_fail (g_task_is_valid (res, client), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + ok = g_task_propagate_boolean (G_TASK (res), &err); + + if (!ok) + bolt_error_propagate_stripped (error, &err); + + return ok; +} + +gboolean +bolt_client_forget_device (BoltClient *client, + const char *uid, + GError **error) +{ + g_autoptr(GVariant) val = NULL; + g_autoptr(GError) err = NULL; + + g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE); + + val = g_dbus_proxy_call_sync (G_DBUS_PROXY (client), + "ForgetDevice", + g_variant_new ("(s)", uid), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &err); + + if (val == NULL) + { + bolt_error_propagate_stripped (error, &err); + return FALSE; + } + + return TRUE; +} + +void +bolt_client_forget_device_async (BoltClient *client, + const char *uid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (BOLT_IS_CLIENT (client)); + + g_dbus_proxy_call (G_DBUS_PROXY (client), + "ForgetDevice", + g_variant_new ("(s)", uid), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +gboolean +bolt_client_forget_device_finish (BoltClient *client, + GAsyncResult *res, + GError **error) +{ + g_autoptr(GVariant) val = NULL; + g_autoptr(GError) err = NULL; + + g_return_val_if_fail (BOLT_IS_CLIENT (client), FALSE); + + val = g_dbus_proxy_call_finish (G_DBUS_PROXY (client), res, &err); + if (val == NULL) + { + bolt_error_propagate_stripped (error, &err); + return FALSE; + } + + return TRUE; +} + +/* getter */ +guint +bolt_client_get_version (BoltClient *client) +{ + const char *key; + guint val = 0; + gboolean ok; + + g_return_val_if_fail (BOLT_IS_CLIENT (client), val); + + key = g_param_spec_get_name (props[PROP_VERSION]); + ok = bolt_proxy_get_property_uint32 (BOLT_PROXY (client), key, &val); + + if (!ok) + g_warning ("failed to get property '%s'", key); + + return val; +} + +gboolean +bolt_client_is_probing (BoltClient *client) +{ + const char *key; + gboolean val = FALSE; + gboolean ok; + + g_return_val_if_fail (BOLT_IS_CLIENT (client), val); + + key = g_param_spec_get_name (props[PROP_PROBING]); + ok = bolt_proxy_get_property_bool (BOLT_PROXY (client), key, &val); + + if (!ok) + g_warning ("failed to get enum property '%s'", key); + + return val; +} + +BoltSecurity +bolt_client_get_security (BoltClient *client) +{ + const char *key; + gboolean ok; + gint val = BOLT_SECURITY_UNKNOWN; + + g_return_val_if_fail (BOLT_IS_CLIENT (client), val); + + key = g_param_spec_get_name (props[PROP_SECURITY]); + ok = bolt_proxy_get_property_enum (BOLT_PROXY (client), key, &val); + + if (!ok) + g_warning ("failed to get enum property '%s'", key); + + return val; +} + +BoltAuthMode +bolt_client_get_authmode (BoltClient *client) +{ + const char *key; + gboolean ok; + guint val = BOLT_AUTH_DISABLED; + + g_return_val_if_fail (BOLT_IS_CLIENT (client), val); + + key = g_param_spec_get_name (props[PROP_AUTHMODE]); + ok = bolt_proxy_get_property_flags (BOLT_PROXY (client), key, &val); + + if (!ok) + g_warning ("failed to get enum property '%s'", key); + + return val; +} + +void +bolt_client_set_authmode_async (BoltClient *client, + BoltAuthMode mode, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autofree char *str = NULL; + GError *err = NULL; + GParamSpec *pspec; + GParamSpecFlags *flags_pspec; + GFlagsClass *flags_class; + + pspec = props[PROP_AUTHMODE]; + flags_pspec = G_PARAM_SPEC_FLAGS (pspec); + flags_class = flags_pspec->flags_class; + str = bolt_flags_class_to_string (flags_class, mode, &err); + + if (str == NULL) + { + g_task_report_error (client, callback, user_data, NULL, err); + return; + } + + bolt_proxy_set_property_async (BOLT_PROXY (client), + g_param_spec_get_nick (pspec), + g_variant_new ("s", str), + cancellable, + callback, + user_data); +} + +gboolean +bolt_client_set_authmode_finish (BoltClient *client, + GAsyncResult *res, + GError **error) +{ + return bolt_proxy_set_property_finish (res, error); +} + +/* utility functions */ +static gint +device_sort_by_syspath (gconstpointer ap, + gconstpointer bp, + gpointer data) +{ + BoltDevice *a = BOLT_DEVICE (*((BoltDevice **) ap)); + BoltDevice *b = BOLT_DEVICE (*((BoltDevice **) bp)); + gint sort_order = GPOINTER_TO_INT (data); + const char *pa; + const char *pb; + + pa = bolt_device_get_syspath (a); + pb = bolt_device_get_syspath (b); + + return sort_order * g_strcmp0 (pa, pb); +} + +void +bolt_devices_sort_by_syspath (GPtrArray *devices, + gboolean reverse) +{ + gpointer sort_order = GINT_TO_POINTER (reverse ? -1 : 1); + + if (devices == NULL) + return; + + g_ptr_array_sort_with_data (devices, + device_sort_by_syspath, + sort_order); +} diff --git a/panels/thunderbolt/bolt-client.h b/panels/thunderbolt/bolt-client.h new file mode 100644 index 0000000..571d5bd --- /dev/null +++ b/panels/thunderbolt/bolt-client.h @@ -0,0 +1,143 @@ +/* + * Copyright © 2017 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#pragma once + +#include "bolt-enums.h" +#include "bolt-device.h" +#include "bolt-proxy.h" + +G_BEGIN_DECLS + +#define BOLT_TYPE_CLIENT bolt_client_get_type () +G_DECLARE_FINAL_TYPE (BoltClient, bolt_client, BOLT, CLIENT, BoltProxy); + +BoltClient * bolt_client_new (GError **error); + +void bolt_client_new_async (GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +BoltClient * bolt_client_new_finish (GAsyncResult *res, + GError **error); + +GPtrArray * bolt_client_list_devices (BoltClient *client, + GCancellable *cancellable, + GError **error); + +BoltDevice * bolt_client_get_device (BoltClient *client, + const char *uid, + GCancellable *cancellable, + GError **error); + +BoltDevice * bolt_client_enroll_device (BoltClient *client, + const char *uid, + BoltPolicy policy, + BoltAuthCtrl flags, + GError **error); + +void bolt_client_enroll_device_async (BoltClient *client, + const char *uid, + BoltPolicy policy, + BoltAuthCtrl flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean bolt_client_enroll_device_finish (BoltClient *client, + GAsyncResult *res, + char **path, + GError **error); + +void bolt_client_enroll_all_async (BoltClient *client, + GPtrArray *uuids, + BoltPolicy policy, + BoltAuthCtrl flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean bolt_client_enroll_all_finish (BoltClient *client, + GAsyncResult *res, + GError **error); + +void bolt_client_authorize_all_async (BoltClient *client, + GPtrArray *uuids, + BoltAuthCtrl flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean bolt_client_authorize_all_finish (BoltClient *client, + GAsyncResult *res, + GError **error); + +void bolt_client_connect_all_async (BoltClient *client, + GPtrArray *devices, + BoltPolicy policy, + BoltAuthCtrl flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean bolt_client_connect_all_finish (BoltClient *client, + GAsyncResult *res, + GError **error); + + +gboolean bolt_client_forget_device (BoltClient *client, + const char *uid, + GError **error); + +void bolt_client_forget_device_async (BoltClient *client, + const char *uid, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean bolt_client_forget_device_finish (BoltClient *client, + GAsyncResult *res, + GError **error); + +/* getter */ +guint bolt_client_get_version (BoltClient *client); + +gboolean bolt_client_is_probing (BoltClient *client); + +BoltSecurity bolt_client_get_security (BoltClient *client); + +BoltAuthMode bolt_client_get_authmode (BoltClient *client); + +/* setter */ + +void bolt_client_set_authmode_async (BoltClient *client, + BoltAuthMode mode, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean bolt_client_set_authmode_finish (BoltClient *client, + GAsyncResult *res, + GError **error); + +/* utility functions */ +void bolt_devices_sort_by_syspath (GPtrArray *devices, + gboolean reverse); + +G_END_DECLS diff --git a/panels/thunderbolt/bolt-device.c b/panels/thunderbolt/bolt-device.c new file mode 100644 index 0000000..b316950 --- /dev/null +++ b/panels/thunderbolt/bolt-device.c @@ -0,0 +1,604 @@ +/* + * Copyright © 2017 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#include "config.h" + +#include "bolt-device.h" + +#include "bolt-enums.h" +#include "bolt-error.h" +#include "bolt-names.h" + +#include <gio/gio.h> + +struct _BoltDevice +{ + BoltProxy parent; +}; + +enum { + PROP_0, + + /* D-Bus Props */ + PROP_UID, + PROP_NAME, + PROP_VENDOR, + PROP_TYPE, + PROP_STATUS, + PROP_AUTHFLAGS, + PROP_PARENT, + PROP_SYSPATH, + PROP_CONNTIME, + PROP_AUTHTIME, + + PROP_STORED, + PROP_POLICY, + PROP_KEY, + PROP_STORETIME, + PROP_LABEL, + + PROP_LAST +}; + +static GParamSpec *props[PROP_LAST] = {NULL, }; + +G_DEFINE_TYPE (BoltDevice, + bolt_device, + BOLT_TYPE_PROXY); + +static void +bolt_device_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + if (bolt_proxy_get_dbus_property (object, pspec, value)) + return; +} + + + +static void +bolt_device_class_init (BoltDeviceClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = bolt_device_get_property; + + props[PROP_UID] = + g_param_spec_string ("uid", + "Uid", NULL, + "unknown", + G_PARAM_READABLE | + G_PARAM_STATIC_NICK); + + props[PROP_NAME] = + g_param_spec_string ("name", + "Name", NULL, + "unknown", + G_PARAM_READABLE | + G_PARAM_STATIC_NICK); + + props[PROP_VENDOR] = + g_param_spec_string ("vendor", + "Vendor", NULL, + "unknown", + G_PARAM_READABLE | + G_PARAM_STATIC_NICK); + + props[PROP_TYPE] = + g_param_spec_enum ("type", + "Type", NULL, + BOLT_TYPE_DEVICE_TYPE, + BOLT_DEVICE_PERIPHERAL, + G_PARAM_READABLE | + G_PARAM_STATIC_NICK); + + props[PROP_STATUS] = + g_param_spec_enum ("status", + "Status", NULL, + BOLT_TYPE_STATUS, + BOLT_STATUS_DISCONNECTED, + G_PARAM_READABLE | + G_PARAM_STATIC_NICK); + + props[PROP_AUTHFLAGS] = + g_param_spec_flags ("authflags", + "AuthFlags", NULL, + BOLT_TYPE_AUTH_FLAGS, + BOLT_AUTH_NONE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + props[PROP_PARENT] = + g_param_spec_string ("parent", + "Parent", NULL, + "unknown", + G_PARAM_READABLE | + G_PARAM_STATIC_NICK); + + props[PROP_SYSPATH] = + g_param_spec_string ("syspath", + "SysfsPath", NULL, + "unknown", + G_PARAM_READABLE | + G_PARAM_STATIC_NICK); + + props[PROP_CONNTIME] = + g_param_spec_uint64 ("conntime", + "ConnectTime", NULL, + 0, G_MAXUINT64, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + props[PROP_AUTHTIME] = + g_param_spec_uint64 ("authtime", + "AuthorizeTime", NULL, + 0, G_MAXUINT64, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + props[PROP_STORED] = + g_param_spec_boolean ("stored", + "Stored", NULL, + FALSE, + G_PARAM_READABLE | + G_PARAM_STATIC_NICK); + + props[PROP_POLICY] = + g_param_spec_enum ("policy", + "Policy", NULL, + BOLT_TYPE_POLICY, + BOLT_POLICY_DEFAULT, + G_PARAM_READABLE | + G_PARAM_STATIC_NICK); + + props[PROP_KEY] = + g_param_spec_enum ("key", + "Key", NULL, + BOLT_TYPE_KEY_STATE, + BOLT_KEY_MISSING, + G_PARAM_READABLE | + G_PARAM_STATIC_NICK); + + props[PROP_STORETIME] = + g_param_spec_uint64 ("storetime", + "StoreTime", NULL, + 0, G_MAXUINT64, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + props[PROP_LABEL] = + g_param_spec_string ("label", + "Label", NULL, + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + PROP_LAST, + props); + +} + +static void +bolt_device_init (BoltDevice *mgr) +{ +} + +/* public methods */ + +BoltDevice * +bolt_device_new_for_object_path (GDBusConnection *bus, + const char *path, + GCancellable *cancel, + GError **error) +{ + BoltDevice *dev; + + dev = g_initable_new (BOLT_TYPE_DEVICE, + cancel, error, + "g-flags", G_DBUS_PROXY_FLAGS_NONE, + "g-connection", bus, + "g-name", BOLT_DBUS_NAME, + "g-object-path", path, + "g-interface-name", BOLT_DBUS_DEVICE_INTERFACE, + NULL); + + return dev; +} + +gboolean +bolt_device_authorize (BoltDevice *dev, + BoltAuthCtrl flags, + GCancellable *cancel, + GError **error) +{ + g_autoptr(GError) err = NULL; + g_autofree char *fstr = NULL; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE); + + fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, error); + if (fstr == NULL) + return FALSE; + + g_dbus_proxy_call_sync (G_DBUS_PROXY (dev), + "Authorize", + g_variant_new ("(s)", fstr), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancel, + &err); + + if (err != NULL) + return bolt_error_propagate_stripped (error, &err); + + return TRUE; +} + +void +bolt_device_authorize_async (BoltDevice *dev, + BoltAuthCtrl flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GError *err = NULL; + g_autofree char *fstr = NULL; + + g_return_if_fail (BOLT_IS_DEVICE (dev)); + + fstr = bolt_flags_to_string (BOLT_TYPE_AUTH_CTRL, flags, &err); + if (fstr == NULL) + { + g_task_report_error (dev, callback, user_data, NULL, err); + return; + } + + g_dbus_proxy_call (G_DBUS_PROXY (dev), + "Authorize", + g_variant_new ("(s)", fstr), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +gboolean +bolt_device_authorize_finish (BoltDevice *dev, + GAsyncResult *res, + GError **error) +{ + g_autoptr(GError) err = NULL; + g_autoptr(GVariant) val = NULL; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), FALSE); + + val = g_dbus_proxy_call_finish (G_DBUS_PROXY (dev), res, &err); + if (val == NULL) + { + bolt_error_propagate_stripped (error, &err); + return FALSE; + } + + return TRUE; +} + +const char * +bolt_device_get_uid (BoltDevice *dev) +{ + const char *key; + const char *str; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); + + key = g_param_spec_get_name (props[PROP_UID]); + str = bolt_proxy_get_property_string (BOLT_PROXY (dev), key); + + return str; +} + +const char * +bolt_device_get_name (BoltDevice *dev) +{ + const char *key; + const char *str; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); + + key = g_param_spec_get_name (props[PROP_NAME]); + str = bolt_proxy_get_property_string (BOLT_PROXY (dev), key); + + return str; +} + +const char * +bolt_device_get_vendor (BoltDevice *dev) +{ + const char *key; + const char *str; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); + + key = g_param_spec_get_name (props[PROP_VENDOR]); + str = bolt_proxy_get_property_string (BOLT_PROXY (dev), key); + + return str; +} + +BoltDeviceType +bolt_device_get_device_type (BoltDevice *dev) +{ + const char *key; + gboolean ok; + gint val = BOLT_DEVICE_PERIPHERAL; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); + + key = g_param_spec_get_name (props[PROP_TYPE]); + ok = bolt_proxy_get_property_enum (BOLT_PROXY (dev), key, &val); + + if (!ok) + g_warning ("failed to get enum property '%s'", key); + + return val; +} + +BoltStatus +bolt_device_get_status (BoltDevice *dev) +{ + const char *key; + gboolean ok; + gint val = BOLT_STATUS_UNKNOWN; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); + + key = g_param_spec_get_name (props[PROP_STATUS]); + ok = bolt_proxy_get_property_enum (BOLT_PROXY (dev), key, &val); + + if (!ok) + g_warning ("failed to get enum property '%s'", key); + + return val; +} + +BoltAuthFlags +bolt_device_get_authflags (BoltDevice *dev) +{ + const char *key; + gboolean ok; + guint val = BOLT_AUTH_NONE; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); + + key = g_param_spec_get_name (props[PROP_AUTHFLAGS]); + ok = bolt_proxy_get_property_flags (BOLT_PROXY (dev), key, &val); + + if (!ok) + g_warning ("failed to get enum property '%s'", key); + + return val; +} + +const char * +bolt_device_get_parent (BoltDevice *dev) +{ + const char *key; + const char *str; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); + + key = g_param_spec_get_name (props[PROP_PARENT]); + str = bolt_proxy_get_property_string (BOLT_PROXY (dev), key); + + return str; +} + +const char * +bolt_device_get_syspath (BoltDevice *dev) +{ + const char *key; + const char *str; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); + + key = g_param_spec_get_name (props[PROP_SYSPATH]); + str = bolt_proxy_get_property_string (BOLT_PROXY (dev), key); + + return str; +} + +guint64 +bolt_device_get_conntime (BoltDevice *dev) +{ + const char *key; + guint64 val = 0; + gboolean ok; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); + + key = g_param_spec_get_name (props[PROP_CONNTIME]); + ok = bolt_proxy_get_property_uint64 (BOLT_PROXY (dev), key, &val); + + if (!ok) + g_warning ("failed to get enum property '%s'", key); + + return val; +} + +guint64 +bolt_device_get_authtime (BoltDevice *dev) +{ + const char *key; + guint64 val = 0; + gboolean ok; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); + + key = g_param_spec_get_name (props[PROP_AUTHTIME]); + ok = bolt_proxy_get_property_uint64 (BOLT_PROXY (dev), key, &val); + + if (!ok) + g_warning ("failed to get enum property '%s'", key); + + return val; +} + +gboolean +bolt_device_is_stored (BoltDevice *dev) +{ + const char *key; + gboolean val = FALSE; + gboolean ok; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); + + key = g_param_spec_get_name (props[PROP_STORED]); + ok = bolt_proxy_get_property_bool (BOLT_PROXY (dev), key, &val); + + if (!ok) + g_warning ("failed to get enum property '%s'", key); + + return val; +} + +BoltPolicy +bolt_device_get_policy (BoltDevice *dev) +{ + const char *key; + gboolean ok; + gint val = BOLT_POLICY_DEFAULT; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); + + key = g_param_spec_get_name (props[PROP_POLICY]); + ok = bolt_proxy_get_property_enum (BOLT_PROXY (dev), key, &val); + + if (!ok) + g_warning ("failed to get enum property '%s'", key); + + return val; +} + +BoltKeyState +bolt_device_get_keystate (BoltDevice *dev) +{ + const char *key; + gboolean ok; + gint val = BOLT_KEY_MISSING; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); + + key = g_param_spec_get_name (props[PROP_KEY]); + ok = bolt_proxy_get_property_enum (BOLT_PROXY (dev), key, &val); + + if (!ok) + g_warning ("failed to get enum property '%s'", key); + + return val; +} + +guint64 +bolt_device_get_storetime (BoltDevice *dev) +{ + const char *key; + guint64 val = 0; + gboolean ok; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), val); + + key = g_param_spec_get_name (props[PROP_STORETIME]); + ok = bolt_proxy_get_property_uint64 (BOLT_PROXY (dev), key, &val); + + if (!ok) + g_warning ("failed to get enum property '%s'", key); + + return val; +} + +const char * +bolt_device_get_label (BoltDevice *dev) +{ + const char *key; + const char *str; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); + + key = g_param_spec_get_name (props[PROP_LABEL]); + str = bolt_proxy_get_property_string (BOLT_PROXY (dev), key); + + return str; +} + +char * +bolt_device_get_display_name (BoltDevice *dev) +{ + const char *label; + const char *name; + const char *vendor; + + label = bolt_device_get_label (dev); + if (label != NULL) + return g_strdup (label); + + name = bolt_device_get_name (dev); + vendor = bolt_device_get_vendor (dev); + + return g_strdup_printf ("%s %s", vendor, name); +} + +guint64 +bolt_device_get_timestamp (BoltDevice *dev) +{ + BoltStatus status; + guint64 timestamp = 0; + + status = bolt_device_get_status (dev); + + switch (status) + { + case BOLT_STATUS_AUTHORIZING: + case BOLT_STATUS_AUTH_ERROR: + case BOLT_STATUS_CONNECTING: + case BOLT_STATUS_CONNECTED: + timestamp = bolt_device_get_conntime (dev); + break; + + case BOLT_STATUS_DISCONNECTED: + /* implicit: device is stored */ + timestamp = bolt_device_get_storetime (dev); + break; + + case BOLT_STATUS_AUTHORIZED: + case BOLT_STATUS_AUTHORIZED_DPONLY: + case BOLT_STATUS_AUTHORIZED_NEWKEY: + case BOLT_STATUS_AUTHORIZED_SECURE: + timestamp = bolt_device_get_authtime (dev); + break; + + case BOLT_STATUS_UNKNOWN: + timestamp = 0; + break; + } + + return timestamp; +} diff --git a/panels/thunderbolt/bolt-device.h b/panels/thunderbolt/bolt-device.h new file mode 100644 index 0000000..ffd09f9 --- /dev/null +++ b/panels/thunderbolt/bolt-device.h @@ -0,0 +1,87 @@ +/* + * Copyright © 2017 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#pragma once + +#include "bolt-enums.h" +#include "bolt-proxy.h" + +G_BEGIN_DECLS + +#define BOLT_TYPE_DEVICE bolt_device_get_type () +G_DECLARE_FINAL_TYPE (BoltDevice, bolt_device, BOLT, DEVICE, BoltProxy); + +BoltDevice * bolt_device_new_for_object_path (GDBusConnection *bus, + const char *path, + GCancellable *cancellable, + GError **error); + +gboolean bolt_device_authorize (BoltDevice *dev, + BoltAuthCtrl flags, + GCancellable *cancellable, + GError **error); + +void bolt_device_authorize_async (BoltDevice *dev, + BoltAuthCtrl flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean bolt_device_authorize_finish (BoltDevice *dev, + GAsyncResult *res, + GError **error); + +/* getter */ +const char * bolt_device_get_uid (BoltDevice *dev); + +const char * bolt_device_get_name (BoltDevice *dev); + +const char * bolt_device_get_vendor (BoltDevice *dev); + +BoltDeviceType bolt_device_get_device_type (BoltDevice *dev); + +BoltStatus bolt_device_get_status (BoltDevice *dev); + +BoltAuthFlags bolt_device_get_authflags (BoltDevice *dev); + +const char * bolt_device_get_parent (BoltDevice *dev); + +const char * bolt_device_get_syspath (BoltDevice *dev); + +guint64 bolt_device_get_conntime (BoltDevice *dev); + +guint64 bolt_device_get_authtime (BoltDevice *dev); + +gboolean bolt_device_is_stored (BoltDevice *dev); + +BoltPolicy bolt_device_get_policy (BoltDevice *dev); + +BoltKeyState bolt_device_get_keystate (BoltDevice *dev); + +guint64 bolt_device_get_storetime (BoltDevice *dev); + +const char * bolt_device_get_label (BoltDevice *dev); + +/* derived getter */ +char * bolt_device_get_display_name (BoltDevice *dev); + +guint64 bolt_device_get_timestamp (BoltDevice *dev); + +G_END_DECLS diff --git a/panels/thunderbolt/bolt-enums.c b/panels/thunderbolt/bolt-enums.c new file mode 100644 index 0000000..bb34ba7 --- /dev/null +++ b/panels/thunderbolt/bolt-enums.c @@ -0,0 +1,397 @@ +/* + * Copyright © 2017 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#include "config.h" + +#include "bolt-enums.h" +#include "bolt-error.h" + +#include <gio/gio.h> + +#if !GLIB_CHECK_VERSION(2, 57, 0) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GEnumClass, g_type_class_unref); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GFlagsClass, g_type_class_unref); +#endif + +gboolean +bolt_enum_class_validate (GEnumClass *enum_class, + gint value, + GError **error) +{ + const char *name; + gboolean oob; + + if (enum_class == NULL) + { + name = g_type_name_from_class ((GTypeClass *) enum_class); + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "could not determine enum class for '%s'", + name); + + return FALSE; + } + + oob = value < enum_class->minimum || value > enum_class->maximum; + + if (oob) + { + name = g_type_name_from_class ((GTypeClass *) enum_class); + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "enum value '%d' is out of bounds for '%s'", + value, name); + return FALSE; + } + + return TRUE; +} + +gboolean +bolt_enum_validate (GType enum_type, + gint value, + GError **error) +{ + g_autoptr(GEnumClass) klass = g_type_class_ref (enum_type); + return bolt_enum_class_validate (klass, value, error); +} + +const char * +bolt_enum_to_string (GType enum_type, + gint value, + GError **error) +{ + g_autoptr(GEnumClass) klass = NULL; + GEnumValue *ev; + + klass = g_type_class_ref (enum_type); + + if (!bolt_enum_class_validate (klass, value, error)) + return NULL; + + ev = g_enum_get_value (klass, value); + return ev->value_nick; +} + +gint +bolt_enum_from_string (GType enum_type, + const char *string, + GError **error) +{ + g_autoptr(GEnumClass) klass = NULL; + const char *name; + GEnumValue *ev; + + klass = g_type_class_ref (enum_type); + + if (klass == NULL) + { + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "could not determine enum class"); + return -1; + } + + if (string == NULL) + { + name = g_type_name_from_class ((GTypeClass *) klass); + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "empty string passed for enum class for '%s'", + name); + return -1; + } + + ev = g_enum_get_value_by_nick (klass, string); + + if (ev == NULL) + { + name = g_type_name (enum_type); + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "invalid string '%s' for enum '%s'", string, name); + return -1; + } + + return ev->value; +} + +char * +bolt_flags_class_to_string (GFlagsClass *flags_class, + guint value, + GError **error) +{ + g_autoptr(GString) str = NULL; + const char *name; + GFlagsValue *fv; + + if (flags_class == NULL) + { + name = g_type_name_from_class ((GTypeClass *) flags_class); + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "could not determine flags class for '%s'", + name); + + return FALSE; + } + + fv = g_flags_get_first_value (flags_class, value); + if (fv == NULL) + { + if (value == 0) + return g_strdup (""); + + name = g_type_name_from_class ((GTypeClass *) flags_class); + + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "invalid value '%u' for flags '%s'", value, name); + return NULL; + } + + value &= ~fv->value; + str = g_string_new (fv->value_nick); + + while (value != 0 && + (fv = g_flags_get_first_value (flags_class, value)) != NULL) + { + g_string_append (str, " | "); + g_string_append (str, fv->value_nick); + + value &= ~fv->value; + } + + if (value != 0) + { + name = g_type_name_from_class ((GTypeClass *) flags_class); + + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "unhandled value '%u' for flags '%s'", value, name); + return NULL; + } + + return g_string_free (g_steal_pointer (&str), FALSE); +} + +gboolean +bolt_flags_class_from_string (GFlagsClass *flags_class, + const char *string, + guint *flags_out, + GError **error) +{ + g_auto(GStrv) vals = NULL; + const char *name; + guint flags = 0; + + if (flags_class == NULL) + { + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "could not determine flags class"); + + return FALSE; + } + + if (string == NULL) + { + name = g_type_name_from_class ((GTypeClass *) flags_class); + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "empty string passed for flags class for '%s'", + name); + return FALSE; + } + + vals = g_strsplit (string, "|", -1); + + for (guint i = 0; vals[i]; i++) + { + GFlagsValue *fv; + char *nick; + + nick = g_strstrip (vals[i]); + fv = g_flags_get_value_by_nick (flags_class, nick); + + if (fv == NULL) + { + name = g_type_name_from_class ((GTypeClass *) flags_class); + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "invalid flag '%s' for flags '%s'", string, name); + + return FALSE; + } + + flags |= fv->value; + } + + if (flags_out != NULL) + *flags_out = flags; + + return TRUE; +} + +char * +bolt_flags_to_string (GType flags_type, + guint value, + GError **error) +{ + g_autoptr(GFlagsClass) klass = NULL; + + klass = g_type_class_ref (flags_type); + return bolt_flags_class_to_string (klass, value, error); +} + +gboolean +bolt_flags_from_string (GType flags_type, + const char *string, + guint *flags_out, + GError **error) +{ + g_autoptr(GFlagsClass) klass = NULL; + + klass = g_type_class_ref (flags_type); + return bolt_flags_class_from_string (klass, string, flags_out, error); +} + +gboolean +bolt_flags_update (guint from, + guint *to, + guint mask) +{ + guint val; + gboolean chg; + + g_return_val_if_fail (to != NULL, FALSE); + + val = *to & ~mask; /* clear all bits in mask */ + val = val | (from & mask); /* set all bits in from and mask */ + chg = *to != val; + *to = val; + + return chg; +} + +const char * +bolt_status_to_string (BoltStatus status) +{ + return bolt_enum_to_string (BOLT_TYPE_STATUS, status, NULL); +} + +gboolean +bolt_status_is_authorized (BoltStatus status) +{ + return status == BOLT_STATUS_AUTHORIZED || + status == BOLT_STATUS_AUTHORIZED_SECURE || + status == BOLT_STATUS_AUTHORIZED_NEWKEY; +} + +gboolean +bolt_status_is_pending (BoltStatus status) +{ + return status == BOLT_STATUS_AUTH_ERROR || + status == BOLT_STATUS_CONNECTED; +} + +gboolean +bolt_status_validate (BoltStatus status) +{ + return bolt_enum_validate (BOLT_TYPE_STATUS, status, NULL); +} + +gboolean +bolt_status_is_connected (BoltStatus status) +{ + return status > BOLT_STATUS_DISCONNECTED; +} + +BoltSecurity +bolt_security_from_string (const char *str) +{ + return bolt_enum_from_string (BOLT_TYPE_SECURITY, str, NULL); +} + +const char * +bolt_security_to_string (BoltSecurity security) +{ + return bolt_enum_to_string (BOLT_TYPE_SECURITY, security, NULL); +} + +gboolean +bolt_security_validate (BoltSecurity security) +{ + return bolt_enum_validate (BOLT_TYPE_SECURITY, security, NULL); +} + +gboolean +bolt_security_allows_pcie (BoltSecurity security) +{ + gboolean pcie = FALSE; + + switch (security) + { + case BOLT_SECURITY_NONE: + case BOLT_SECURITY_USER: + case BOLT_SECURITY_SECURE: + pcie = TRUE; + break; + + case BOLT_SECURITY_DPONLY: + case BOLT_SECURITY_USBONLY: + case BOLT_SECURITY_UNKNOWN: + pcie = FALSE; + break; + } + + return pcie; +} + +BoltPolicy +bolt_policy_from_string (const char *str) +{ + return bolt_enum_from_string (BOLT_TYPE_POLICY, str, NULL); +} + +const char * +bolt_policy_to_string (BoltPolicy policy) +{ + return bolt_enum_to_string (BOLT_TYPE_POLICY, policy, NULL); +} + +gboolean +bolt_policy_validate (BoltPolicy policy) +{ + return bolt_enum_validate (BOLT_TYPE_POLICY, policy, NULL); +} + +BoltDeviceType +bolt_device_type_from_string (const char *str) +{ + return bolt_enum_from_string (BOLT_TYPE_DEVICE_TYPE, str, NULL); +} + +const char * +bolt_device_type_to_string (BoltDeviceType type) +{ + return bolt_enum_to_string (BOLT_TYPE_DEVICE_TYPE, type, NULL); +} + +gboolean +bolt_device_type_validate (BoltDeviceType type) +{ + return bolt_enum_validate (BOLT_TYPE_DEVICE_TYPE, type, NULL); +} + +gboolean +bolt_device_type_is_host (BoltDeviceType type) +{ + return type == BOLT_DEVICE_HOST; +} diff --git a/panels/thunderbolt/bolt-enums.h b/panels/thunderbolt/bolt-enums.h new file mode 100644 index 0000000..6e2953f --- /dev/null +++ b/panels/thunderbolt/bolt-enums.h @@ -0,0 +1,249 @@ +/* + * Copyright © 2017 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#pragma once + +#include "bolt-names.h" +#include "bolt-enum-types.h" + + +gboolean bolt_enum_validate (GType enum_type, + gint value, + GError **error); + +gboolean bolt_enum_class_validate (GEnumClass *enum_class, + gint value, + GError **error); + +const char * bolt_enum_to_string (GType enum_type, + gint value, + GError **error); + +gint bolt_enum_from_string (GType enum_type, + const char *string, + GError **error); + + +char * bolt_flags_class_to_string (GFlagsClass *flags_class, + guint value, + GError **error); + +gboolean bolt_flags_class_from_string (GFlagsClass *flags_class, + const char *string, + guint *flags_out, + GError **error); + +char * bolt_flags_to_string (GType flags_type, + guint value, + GError **error); + +gboolean bolt_flags_from_string (GType flags_type, + const char *string, + guint *flags_out, + GError **error); + +gboolean bolt_flags_update (guint from, + guint *to, + guint mask); + +#define bolt_flag_isset(flags_, flag_) (!!(flags_ & flag_)) +#define bolt_flag_isclear(flags_, flag_) (!(flags_ & flag_)) + +/** + * BoltStatus: + * @BOLT_STATUS_UNKNOWN: Device is in an unknown state (should normally not happen). + * @BOLT_STATUS_DISCONNECTED: Device is not connected. + * @BOLT_STATUS_CONNECTING: Device is currently being connected. + * @BOLT_STATUS_CONNECTED: Device is connected, but not authorized. + * @BOLT_STATUS_AUTHORIZING: Device is currently authorizing. + * @BOLT_STATUS_AUTH_ERROR: Failed to authorize a device via a key. + * @BOLT_STATUS_AUTHORIZED: Device connected and authorized. + * @BOLT_STATUS_AUTHORIZED_SECURE: Device connected and securely authorized via a key (deprecated). + * @BOLT_STATUS_AUTHORIZED_NEWKEY: Device connected and authorized via a new key (deprecated). + * @BOLT_STATUS_AUTHORIZED_DPONLY: Device authorized but with thunderbolt disabled (deprecated). + * + * The current status of the device. + */ +typedef enum { + + BOLT_STATUS_UNKNOWN = -1, + BOLT_STATUS_DISCONNECTED = 0, + BOLT_STATUS_CONNECTING, + BOLT_STATUS_CONNECTED, + BOLT_STATUS_AUTHORIZING, + BOLT_STATUS_AUTH_ERROR, + BOLT_STATUS_AUTHORIZED, + + /* deprecated, do not use */ + BOLT_STATUS_AUTHORIZED_SECURE, + BOLT_STATUS_AUTHORIZED_NEWKEY, + BOLT_STATUS_AUTHORIZED_DPONLY + +} BoltStatus; + +const char * bolt_status_to_string (BoltStatus status); +gboolean bolt_status_is_authorized (BoltStatus status); +gboolean bolt_status_is_connected (BoltStatus status); +gboolean bolt_status_is_pending (BoltStatus status); +gboolean bolt_status_validate (BoltStatus status); + +/** + * BoltAuthFlags: + * @BOLT_AUTH_NONE: No specific authorization. + * @BOLT_AUTH_NOPCIE: PCIe tunnels are *not* authorized. + * @BOLT_AUTH_SECURE: Device is securely authorized. + * @BOLT_AUTH_NOKEY: Device does *not* support key verification. + * @BOLT_AUTH_BOOT: Device was already authorized during pre-boot. + * + * More specific information about device authorization. + */ +typedef enum { /*< flags >*/ + + BOLT_AUTH_NONE = 0, + BOLT_AUTH_NOPCIE = 1 << 0, + BOLT_AUTH_SECURE = 1 << 1, + BOLT_AUTH_NOKEY = 1 << 2, + BOLT_AUTH_BOOT = 1 << 3, + +} BoltAuthFlags; + +/** + * BoltKeyState: + * @BOLT_KEY_UNKNOWN: unknown key state + * @BOLT_KEY_MISSING: no key + * @BOLT_KEY_HAVE: key exists + * @BOLT_KEY_NEW: key is new + * + * The state of the key. + */ + +typedef enum { + + BOLT_KEY_UNKNOWN = -1, + BOLT_KEY_MISSING = 0, + BOLT_KEY_HAVE = 1, + BOLT_KEY_NEW = 2 + +} BoltKeyState; + +/** + * BoltSecurity: + * @BOLT_SECURITY_UNKNOWN : Unknown security. + * @BOLT_SECURITY_NONE : No security, all devices are automatically connected. + * @BOLT_SECURITY_DPONLY : Display Port only devices only. + * @BOLT_SECURITY_USER : User needs to authorize devices. + * @BOLT_SECURITY_SECURE : User needs to authorize devices. Authorization can + * be done via key exchange to verify the device identity. + * @BOLT_SECURITY_USBONLY : Only create a PCIe tunnel to the USB controller in a + * connected thunderbolt dock, allowing no downstream PCIe tunnels. + * + * The security level of the thunderbolt domain. + */ +typedef enum { + + BOLT_SECURITY_UNKNOWN = -1, + BOLT_SECURITY_NONE = 0, + BOLT_SECURITY_DPONLY = 1, + BOLT_SECURITY_USER = '1', + BOLT_SECURITY_SECURE = '2', + BOLT_SECURITY_USBONLY = 4, + +} BoltSecurity; + + +BoltSecurity bolt_security_from_string (const char *str); +const char * bolt_security_to_string (BoltSecurity security); +gboolean bolt_security_validate (BoltSecurity security); +gboolean bolt_security_allows_pcie (BoltSecurity security); + +/** + * BoltPolicy: + * @BOLT_POLICY_UNKNOWN: Unknown policy. + * @BOLT_POLICY_DEFAULT: Default policy. + * @BOLT_POLICY_MANUAL: Manual authorization of the device. + * @BOLT_POLICY_AUTO: Connect the device automatically, + * with the best possible security level supported + * by the domain controller. + * + * What do to for connected devices. + */ +typedef enum { + + BOLT_POLICY_UNKNOWN = -1, + BOLT_POLICY_DEFAULT = 0, + BOLT_POLICY_MANUAL = 1, + BOLT_POLICY_AUTO = 2, + +} BoltPolicy; + + +BoltPolicy bolt_policy_from_string (const char *str); +const char * bolt_policy_to_string (BoltPolicy policy); +gboolean bolt_policy_validate (BoltPolicy policy); + +/** + * BoltAuthCtrl: + * @BOLT_AUTHCTRL_NONE: No authorization flags. + * + * Control authorization. + */ +typedef enum { /*< flags >*/ + + BOLT_AUTHCTRL_NONE = 0 + +} BoltAuthCtrl; + +/** + * BoltDeviceType: + * @BOLT_DEVICE_UNKNOWN_TYPE: Unknown device type + * @BOLT_DEVICE_HOST: The device representing the host + * @BOLT_DEVICE_PERIPHERAL: A generic thunderbolt peripheral + * + * The type of the device. + */ +typedef enum { + + BOLT_DEVICE_UNKNOWN_TYPE = -1, + BOLT_DEVICE_HOST = 0, + BOLT_DEVICE_PERIPHERAL + +} BoltDeviceType; + +BoltDeviceType bolt_device_type_from_string (const char *str); +const char * bolt_device_type_to_string (BoltDeviceType type); +gboolean bolt_device_type_validate (BoltDeviceType type); +gboolean bolt_device_type_is_host (BoltDeviceType type); + +/** + * BoltAuthMode: + * @BOLT_AUTH_DISABLED: Authorization is disabled + * @BOLT_AUTH_ENABLED: Authorization is enabled. + * + * Control authorization. + */ +typedef enum { /*< flags >*/ + + BOLT_AUTH_DISABLED = 0, + BOLT_AUTH_ENABLED = 1 + +} BoltAuthMode; + +#define bolt_auth_mode_is_enabled(auth) ((auth & BOLT_AUTH_ENABLED) != 0) +#define bolt_auth_mode_is_disabled(auth) (!bolt_auth_mode_is_enabled (auth)) diff --git a/panels/thunderbolt/bolt-error.c b/panels/thunderbolt/bolt-error.c new file mode 100644 index 0000000..37d844e --- /dev/null +++ b/panels/thunderbolt/bolt-error.c @@ -0,0 +1,99 @@ +/* + * Copyright © 2017 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#include "config.h" + +#include "bolt-error.h" + +#include "bolt-names.h" + +#include <gio/gio.h> + +/** + * SECTION:bolt-error + * @Title: Error codes + * + */ + +static const GDBusErrorEntry bolt_error_entries[] = { + {BOLT_ERROR_FAILED, BOLT_DBUS_NAME ".Error.Failed"}, + {BOLT_ERROR_UDEV, BOLT_DBUS_NAME ".Error.UDev"}, +}; + + +GQuark +bolt_error_quark (void) +{ + static volatile gsize quark_volatile = 0; + + g_dbus_error_register_error_domain ("bolt-error-quark", + &quark_volatile, + bolt_error_entries, + G_N_ELEMENTS (bolt_error_entries)); + return (GQuark) quark_volatile; +} + +gboolean +bolt_err_notfound (const GError *error) +{ + return g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) || + g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) || + g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND) || + g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND); +} + +gboolean +bolt_err_exists (const GError *error) +{ + return g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS) || + g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_EXIST); +} + +gboolean +bolt_err_inval (const GError *error) +{ + return g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE); +} + +gboolean +bolt_err_cancelled (const GError *error) +{ + return g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); +} + +gboolean +bolt_error_propagate_stripped (GError **dest, + GError **source) +{ + GError *src; + + g_return_val_if_fail (source != NULL, FALSE); + + src = *source; + + if (src == NULL) + return TRUE; + + if (g_dbus_error_is_remote_error (src)) + g_dbus_error_strip_remote_error (src); + + g_propagate_error (dest, g_steal_pointer (source)); + return FALSE; +} diff --git a/panels/thunderbolt/bolt-error.h b/panels/thunderbolt/bolt-error.h new file mode 100644 index 0000000..7d3823d --- /dev/null +++ b/panels/thunderbolt/bolt-error.h @@ -0,0 +1,55 @@ +/* + * Copyright © 2017 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#pragma once + +#include <glib.h> + +G_BEGIN_DECLS + +/** + * BoltError: + * @BOLT_ERROR_FAILED: Generic error code + * @BOLT_ERROR_UDEV: UDev error + * + * Error codes used inside Bolt. + */ +typedef enum { + BOLT_ERROR_FAILED = 0, + BOLT_ERROR_UDEV, + BOLT_ERROR_NOKEY, + BOLT_ERROR_BADKEY, + BOLT_ERROR_CFG, +} BoltError; + + +GQuark bolt_error_quark (void); +#define BOLT_ERROR (bolt_error_quark ()) + +/* helper function to check for certain error types */ +gboolean bolt_err_notfound (const GError *error); +gboolean bolt_err_exists (const GError *error); +gboolean bolt_err_inval (const GError *error); +gboolean bolt_err_cancelled (const GError *error); + +gboolean bolt_error_propagate_stripped (GError **dest, + GError **source); + +G_END_DECLS diff --git a/panels/thunderbolt/bolt-names.c b/panels/thunderbolt/bolt-names.c new file mode 100644 index 0000000..7426b2f --- /dev/null +++ b/panels/thunderbolt/bolt-names.c @@ -0,0 +1,48 @@ +/* + * Copyright © 2019 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#include "config.h" + +#include "bolt-names.h" + +/* Each element must only contain the ASCII characters "[A-Z][a-z][0-9]_" */ +#define DBUS_OPATH_VALID_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_" + +char * +bolt_gen_object_path (const char *base, + const char *oid) +{ + g_autofree char *id = NULL; + + if (oid) + { + id = g_strdup (oid); + g_strcanon (id, DBUS_OPATH_VALID_CHARS, '_'); + } + + if (base && id) + return g_build_path ("/", "/", base, id, NULL); + else if (base) + return g_build_path ("/", "/", base, NULL); + else if (id) + return g_build_path ("/", "/", id, NULL); + + return g_strdup ("/"); +} diff --git a/panels/thunderbolt/bolt-names.h b/panels/thunderbolt/bolt-names.h new file mode 100644 index 0000000..460eed5 --- /dev/null +++ b/panels/thunderbolt/bolt-names.h @@ -0,0 +1,73 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#pragma once + +#include <glib.h> + +G_BEGIN_DECLS + +/* D-Bus API revision (here for the lack of a better place) */ +#define BOLT_DBUS_API_VERSION 1U + +/* logging */ + +#define BOLT_LOG_DOMAIN_UID "BOLT_DOMAIN_UID" +#define BOLT_LOG_DOMAIN_NAME "BOLT_DOMAIN_NAME" + +#define BOLT_LOG_DEVICE_UID "BOLT_DEVICE_UID" +#define BOLT_LOG_DEVICE_NAME "BOLT_DEVICE_NAME" +#define BOLT_LOG_DEVICE_STATE "BOLT_DEVICE_STATE" + +#define BOLT_LOG_ERROR_DOMAIN "ERROR_DOMAIN" +#define BOLT_LOG_ERROR_CODE "ERROR_CODE" +#define BOLT_LOG_ERROR_MESSAGE "ERROR_MESSAGE" + +#define BOLT_LOG_TOPIC "BOLT_TOPIC" +#define BOLT_LOG_VERSION "BOLT_VERSION" +#define BOLT_LOG_CONTEXT "BOLT_LOG_CONTEXT" +#define BOLT_LOG_BUG_MARK "BOLT_LOG_BUG" + +/* logging - message ids */ +#define BOLT_LOG_MSG_IDLEN 33 +#define BOLT_LOG_MSG_ID_STARTUP "dd11929c788e48bdbb6276fb5f26b08a" + + +/* dbus */ + +#define BOLT_DBUS_NAME "org.freedesktop.bolt" +#define BOLT_DBUS_PATH "/org/freedesktop/bolt" +#define BOLT_DBUS_PATH_DOMAINS BOLT_DBUS_PATH "/domains" +#define BOLT_DBUS_PATH_DEVICES BOLT_DBUS_PATH "/devices" +#define BOLT_DBUS_INTERFACE "org.freedesktop.bolt1.Manager" + +#define BOLT_DBUS_DEVICE_INTERFACE "org.freedesktop.bolt1.Device" +#define BOLT_DBUS_DOMAIN_INTERFACE "org.freedesktop.bolt1.Domain" +#define BOLT_DBUS_POWER_INTERFACE "org.freedesktop.bolt1.Power" + +/* other well known names */ +#define INTEL_WMI_THUNDERBOLT_GUID "86CCFD48-205E-4A77-9C48-2021CBEDE341" + +/* helper functions */ + +char * bolt_gen_object_path (const char *path_base, + const char *object_id); + +G_END_DECLS diff --git a/panels/thunderbolt/bolt-proxy.c b/panels/thunderbolt/bolt-proxy.c new file mode 100644 index 0000000..e044c87 --- /dev/null +++ b/panels/thunderbolt/bolt-proxy.c @@ -0,0 +1,514 @@ +/* + * Copyright © 2017 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#include "bolt-proxy.h" + +#include "bolt-enums.h" +#include "bolt-error.h" +#include "bolt-names.h" +#include "bolt-str.h" + +static void bolt_proxy_handle_props_changed (GDBusProxy *proxy, + GVariant *changed_properties, + GStrv invalidated_properties, + gpointer user_data); + +static void bolt_proxy_handle_dbus_signal (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *params, + gpointer user_data); + +G_DEFINE_TYPE (BoltProxy, bolt_proxy, G_TYPE_DBUS_PROXY); + + +static void +bolt_proxy_constructed (GObject *object) +{ + G_OBJECT_CLASS (bolt_proxy_parent_class)->constructed (object); + + g_signal_connect (object, "g-properties-changed", + G_CALLBACK (bolt_proxy_handle_props_changed), object); + + g_signal_connect (object, "g-signal", + G_CALLBACK (bolt_proxy_handle_dbus_signal), object); +} + +static const BoltProxySignal * +bolt_proxy_get_dbus_signals (guint *n) +{ + *n = 0; + return NULL; +} + +static void +bolt_proxy_class_init (BoltProxyClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructed = bolt_proxy_constructed; + + klass->get_dbus_signals = bolt_proxy_get_dbus_signals; + +} + +static void +bolt_proxy_init (BoltProxy *object) +{ +} + +static void +bolt_proxy_handle_props_changed (GDBusProxy *proxy, + GVariant *changed_properties, + GStrv invalidated_properties, + gpointer user_data) +{ + g_autoptr(GVariantIter) iter = NULL; + gboolean handled; + GParamSpec **pp; + const char *key; + guint n; + + pp = g_object_class_list_properties (G_OBJECT_GET_CLASS (proxy), &n); + + g_variant_get (changed_properties, "a{sv}", &iter); + while (g_variant_iter_next (iter, "{&sv}", &key, NULL)) + { + handled = FALSE; + for (guint i = 0; !handled && i < n; i++) + { + GParamSpec *pspec = pp[i]; + const char *nick; + const char *name; + + nick = g_param_spec_get_nick (pspec); + name = g_param_spec_get_name (pspec); + + handled = bolt_streq (nick, key); + + if (handled) + g_object_notify (G_OBJECT (user_data), name); + } + } + + g_free (pp); +} + +static void +bolt_proxy_handle_dbus_signal (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *params, + gpointer user_data) +{ + const BoltProxySignal *ps; + guint n; + + if (signal_name == NULL) + return; + + ps = BOLT_PROXY_GET_CLASS (proxy)->get_dbus_signals (&n); + + for (guint i = 0; i < n; i++) + { + const BoltProxySignal *sig = &ps[i]; + + if (g_str_equal (sig->theirs, signal_name)) + { + sig->handle (G_OBJECT (proxy), proxy, params); + break; + } + } + +} + +/* public methods */ + +gboolean +bolt_proxy_get_dbus_property (GObject *proxy, + GParamSpec *spec, + GValue *value) +{ + g_autoptr(GVariant) val = NULL; + const GVariantType *vt; + gboolean handled = FALSE; + const char *nick; + + nick = g_param_spec_get_nick (spec); + val = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), nick); + + if (val == NULL) + return FALSE; + + vt = g_variant_get_type (val); + + if (g_variant_type_equal (vt, G_VARIANT_TYPE_STRING) && + G_IS_PARAM_SPEC_ENUM (spec)) + { + GParamSpecEnum *enum_spec = G_PARAM_SPEC_ENUM (spec); + GEnumValue *ev; + const char *str; + + str = g_variant_get_string (val, NULL); + ev = g_enum_get_value_by_nick (enum_spec->enum_class, str); + + handled = ev != NULL; + + if (handled) + g_value_set_enum (value, ev->value); + else + g_value_set_enum (value, enum_spec->default_value); + } + else if (g_variant_type_equal (vt, G_VARIANT_TYPE_STRING) && + G_IS_PARAM_SPEC_FLAGS (spec)) + { + GParamSpecFlags *flags_spec = G_PARAM_SPEC_FLAGS (spec); + GFlagsClass *flags_class = flags_spec->flags_class; + const char *str; + guint v; + + str = g_variant_get_string (val, NULL); + handled = bolt_flags_class_from_string (flags_class, str, &v, NULL); + + if (handled) + g_value_set_flags (value, v); + else + g_value_set_flags (value, flags_spec->default_value); + } + else + { + g_dbus_gvariant_to_gvalue (val, value); + } + + return handled; +} + +gboolean +bolt_proxy_has_name_owner (BoltProxy *proxy) +{ + const char *name_owner; + + g_return_val_if_fail (proxy != NULL, FALSE); + g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE); + + name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)); + + return name_owner != NULL; +} + +static GParamSpec * +find_property (BoltProxy *proxy, + const char *name, + GError **error) +{ + GParamSpec *res = NULL; + GParamSpec **pp; + guint n; + + pp = g_object_class_list_properties (G_OBJECT_GET_CLASS (proxy), &n); + + for (guint i = 0; i < n; i++) + { + GParamSpec *pspec = pp[i]; + + if (bolt_streq (pspec->name, name)) + { + res = pspec; + break; + } + } + + if (pp == NULL) + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, + "could not find property '%s'", name); + + g_free (pp); + return res; +} + +static GVariant * +bolt_proxy_get_cached_property (BoltProxy *proxy, + const char *name) +{ + const char *bus_name = NULL; + GParamSpec *pspec; + GVariant *var; + + g_return_val_if_fail (BOLT_IS_PROXY (proxy), NULL); + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (proxy), name); + + if (pspec == NULL) + return NULL; + + bus_name = g_param_spec_get_nick (pspec); + var = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), bus_name); + + return var; +} + +gboolean +bolt_proxy_get_property_bool (BoltProxy *proxy, + const char *name, + gboolean *value) +{ + g_autoptr(GVariant) var = NULL; + + var = bolt_proxy_get_cached_property (proxy, name); + + if (var == NULL) + return FALSE; + else if (value) + *value = g_variant_get_boolean (var); + + return TRUE; +} + +gboolean +bolt_proxy_get_property_enum (BoltProxy *proxy, + const char *name, + gint *value) +{ + g_autoptr(GVariant) var = NULL; + const char *str = NULL; + const char *bus_name = NULL; + GParamSpec *pspec; + GEnumValue *ev; + + g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE); + + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (proxy), name); + + if (pspec == NULL) + return FALSE; + + bus_name = g_param_spec_get_nick (pspec); + var = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), bus_name); + if (var == NULL) + return FALSE; + + str = g_variant_get_string (var, NULL); + + if (str == NULL) + return FALSE; + + ev = g_enum_get_value_by_nick (G_PARAM_SPEC_ENUM (pspec)->enum_class, str); + + if (ev == NULL) + return FALSE; + + if (value) + *value = ev->value; + + return TRUE; +} + +gboolean +bolt_proxy_get_property_flags (BoltProxy *proxy, + const char *name, + guint *value) +{ + g_autoptr(GVariant) var = NULL; + const char *str = NULL; + const char *bus_name = NULL; + GFlagsClass *flags_class; + GParamSpec *pspec; + guint v; + gboolean ok; + + g_return_val_if_fail (BOLT_IS_PROXY (proxy), FALSE); + + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (proxy), name); + + if (pspec == NULL || !G_IS_PARAM_SPEC_FLAGS (pspec)) + return FALSE; + + bus_name = g_param_spec_get_nick (pspec); + var = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), bus_name); + if (var == NULL) + return FALSE; + + str = g_variant_get_string (var, NULL); + + if (str == NULL) + return FALSE; + + flags_class = G_PARAM_SPEC_FLAGS (pspec)->flags_class; + ok = bolt_flags_class_from_string (flags_class, str, &v, NULL); + + if (ok && value) + *value = v; + + return ok; +} + +gboolean +bolt_proxy_get_property_uint32 (BoltProxy *proxy, + const char *name, + guint *value) +{ + g_autoptr(GVariant) var = NULL; + + var = bolt_proxy_get_cached_property (proxy, name); + + if (var == NULL) + return FALSE; + else if (value) + *value = g_variant_get_uint32 (var); + + return TRUE; +} + +gboolean +bolt_proxy_get_property_int64 (BoltProxy *proxy, + const char *name, + gint64 *value) +{ + g_autoptr(GVariant) var = NULL; + + var = bolt_proxy_get_cached_property (proxy, name); + + if (var == NULL) + return FALSE; + else if (value) + *value = g_variant_get_int64 (var); + + return TRUE; +} + +gboolean +bolt_proxy_get_property_uint64 (BoltProxy *proxy, + const char *name, + guint64 *value) +{ + g_autoptr(GVariant) var = NULL; + + var = bolt_proxy_get_cached_property (proxy, name); + + if (var == NULL) + return FALSE; + else if (value) + *value = g_variant_get_uint64 (var); + + return TRUE; +} + +const char * +bolt_proxy_get_property_string (BoltProxy *proxy, + const char *name) +{ + g_autoptr(GVariant) var = NULL; + const char *val = NULL; + + var = bolt_proxy_get_cached_property (proxy, name); + + if (var != NULL) + val = g_variant_get_string (var, NULL); + + if (val && *val == '\0') + val = NULL; + + return val; +} + +gboolean +bolt_proxy_set_property (BoltProxy *proxy, + const char *name, + GVariant *value, + GCancellable *cancellable, + GError **error) +{ + GParamSpec *pp; + const char *iface; + gboolean ok = FALSE; + GVariant *res; + + pp = find_property (proxy, name, NULL); + if (pp != NULL) + name = g_param_spec_get_nick (pp); + + iface = g_dbus_proxy_get_interface_name (G_DBUS_PROXY (proxy)); + + res = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy), + "org.freedesktop.DBus.Properties.Set", + g_variant_new ("(ssv)", + iface, + name, + value), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + error); + + if (res) + { + g_variant_unref (res); + ok = TRUE; + } + + return ok; +} + +void +bolt_proxy_set_property_async (BoltProxy *proxy, + const char *name, + GVariant *value, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GParamSpec *pp; + const char *iface; + + pp = find_property (proxy, name, NULL); + + if (pp != NULL) + name = g_param_spec_get_nick (pp); + + iface = g_dbus_proxy_get_interface_name (G_DBUS_PROXY (proxy)); + + g_dbus_proxy_call (G_DBUS_PROXY (proxy), + "org.freedesktop.DBus.Properties.Set", + g_variant_new ("(ssv)", + iface, + name, + value), + G_DBUS_CALL_FLAGS_NONE, + -1, + cancellable, + callback, + user_data); +} + +gboolean +bolt_proxy_set_property_finish (GAsyncResult *res, + GError **error) +{ + BoltProxy *proxy; + GVariant *val = NULL; + + proxy = (BoltProxy *) g_async_result_get_source_object (res); + val = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error); + + if (val == NULL) + return FALSE; + + g_variant_unref (val); + return TRUE; +} diff --git a/panels/thunderbolt/bolt-proxy.h b/panels/thunderbolt/bolt-proxy.h new file mode 100644 index 0000000..c05eb8c --- /dev/null +++ b/panels/thunderbolt/bolt-proxy.h @@ -0,0 +1,97 @@ +/* + * Copyright © 2017 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#pragma once + +#include <gio/gio.h> + +G_BEGIN_DECLS + +typedef struct BoltProxySignal +{ + + const char *theirs; + void (*handle)(GObject *self, + GDBusProxy *bus_proxy, + GVariant *params); + +} BoltProxySignal; + +#define BOLT_TYPE_PROXY (bolt_proxy_get_type ()) +G_DECLARE_DERIVABLE_TYPE (BoltProxy, bolt_proxy, BOLT, PROXY, GDBusProxy) + +struct _BoltProxyClass +{ + GDBusProxyClass parent; + + /* virtuals */ + const BoltProxySignal * (*get_dbus_signals) (guint *n); +}; + +gboolean bolt_proxy_get_dbus_property (GObject *proxy, + GParamSpec *spec, + GValue *value); + +gboolean bolt_proxy_has_name_owner (BoltProxy *proxy); + +gboolean bolt_proxy_get_property_bool (BoltProxy *proxy, + const char *name, + gboolean *value); + +gboolean bolt_proxy_get_property_enum (BoltProxy *proxy, + const char *name, + gint *value); + +gboolean bolt_proxy_get_property_flags (BoltProxy *proxy, + const char *name, + guint *value); + +gboolean bolt_proxy_get_property_uint32 (BoltProxy *proxy, + const char *name, + guint *value); + +gboolean bolt_proxy_get_property_int64 (BoltProxy *proxy, + const char *name, + gint64 *value); + +gboolean bolt_proxy_get_property_uint64 (BoltProxy *proxy, + const char *name, + guint64 *value); + +const char * bolt_proxy_get_property_string (BoltProxy *proxy, + const char *name); + +gboolean bolt_proxy_set_property (BoltProxy *proxy, + const char *name, + GVariant *value, + GCancellable *cancellable, + GError **error); + +void bolt_proxy_set_property_async (BoltProxy *proxy, + const char *name, + GVariant *value, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean bolt_proxy_set_property_finish (GAsyncResult *res, + GError **error); + +G_END_DECLS diff --git a/panels/thunderbolt/bolt-str.c b/panels/thunderbolt/bolt-str.c new file mode 100644 index 0000000..fe0580d --- /dev/null +++ b/panels/thunderbolt/bolt-str.c @@ -0,0 +1,117 @@ +/* + * Copyright © 2017 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#include "config.h" + +#include "bolt-str.h" + +#include <string.h> + +typedef void (* zero_fn_t) (void *s, + size_t n); +void +bolt_erase_n (void *data, gsize n) +{ +#if !HAVE_FN_EXPLICIT_BZERO + #warning no explicit bzero, using fallback + static volatile zero_fn_t explicit_bzero = bzero; +#endif + + explicit_bzero (data, n); +} + +void +bolt_str_erase (char *str) +{ + if (str == NULL) + return; + + bolt_erase_n (str, strlen (str)); +} + +void +bolt_str_erase_clear (char **str) +{ + g_return_if_fail (str != NULL); + if (*str == NULL) + return; + + bolt_str_erase (*str); + g_free (*str); + *str = NULL; +} + +GStrv +bolt_strv_from_ptr_array (GPtrArray **array) +{ + GPtrArray *a; + + if (array == NULL || *array == NULL) + return NULL; + + a = *array; + + if (a->len == 0 || a->pdata[a->len - 1] != NULL) + g_ptr_array_add (a, NULL); + + *array = NULL; + return (GStrv) g_ptr_array_free (a, FALSE); +} + +char * +bolt_strdup_validate (const char *string) +{ + g_autofree char *str = NULL; + gboolean ok; + gsize l; + + if (string == NULL) + return NULL; + + str = g_strdup (string); + str = g_strstrip (str); + + l = strlen (str); + if (l == 0) + return NULL; + + ok = g_utf8_validate (str, l, NULL); + + if (!ok) + return NULL; + + return g_steal_pointer (&str); +} + +char * +bolt_strstrip (char *string) +{ + char *str; + + if (string == NULL) + return NULL; + + str = g_strstrip (string); + + if (strlen (str) == 0) + g_clear_pointer (&str, g_free); + + return str; +} diff --git a/panels/thunderbolt/bolt-str.h b/panels/thunderbolt/bolt-str.h new file mode 100644 index 0000000..ecf95a7 --- /dev/null +++ b/panels/thunderbolt/bolt-str.h @@ -0,0 +1,43 @@ +/* + * Copyright © 2017 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#pragma once + +#include <glib.h> +#include <string.h> + +G_BEGIN_DECLS + +void bolt_erase_n (void *data, + gsize n); +void bolt_str_erase (char *str); +void bolt_str_erase_clear (char **str); + +#define bolt_streq(s1, s2) (g_strcmp0 (s1, s2) == 0) + +GStrv bolt_strv_from_ptr_array (GPtrArray **array); + +#define bolt_yesno(val) val ? "yes" : "no" + +char *bolt_strdup_validate (const char *string); + +char *bolt_strstrip (char *string); + +G_END_DECLS diff --git a/panels/thunderbolt/bolt-time.c b/panels/thunderbolt/bolt-time.c new file mode 100644 index 0000000..606aed6 --- /dev/null +++ b/panels/thunderbolt/bolt-time.c @@ -0,0 +1,44 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#include "config.h" + +#include "bolt-time.h" + +char * +bolt_epoch_format (guint64 seconds, const char *format) +{ + g_autoptr(GDateTime) dt = NULL; + + dt = g_date_time_new_from_unix_utc ((gint64) seconds); + + if (dt == NULL) + return NULL; + + return g_date_time_format (dt, format); +} + +guint64 +bolt_now_in_seconds (void) +{ + gint64 now = g_get_real_time (); + + return (guint64) now / G_USEC_PER_SEC; +} diff --git a/panels/thunderbolt/bolt-time.h b/panels/thunderbolt/bolt-time.h new file mode 100644 index 0000000..fc3ed97 --- /dev/null +++ b/panels/thunderbolt/bolt-time.h @@ -0,0 +1,32 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Christian J. Kellner <christian@kellner.me> + */ + +#pragma once + +#include <glib.h> + +G_BEGIN_DECLS + +char * bolt_epoch_format (guint64 seconds, + const char *format); + +guint64 bolt_now_in_seconds (void); + +G_END_DECLS diff --git a/panels/thunderbolt/cc-bolt-device-dialog.c b/panels/thunderbolt/cc-bolt-device-dialog.c new file mode 100644 index 0000000..a1683c4 --- /dev/null +++ b/panels/thunderbolt/cc-bolt-device-dialog.c @@ -0,0 +1,522 @@ +/* Copyright (C) 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 <list-box-helper.h> + +#include <glib/gi18n.h> + +#include "bolt-device.h" +#include "bolt-error.h" +#include "bolt-time.h" + +#include "cc-thunderbolt-resources.h" + +#include "cc-bolt-device-dialog.h" +#include "cc-bolt-device-entry.h" + +struct _CcBoltDeviceDialog +{ + GtkDialog parent; + + BoltClient *client; + BoltDevice *device; + GCancellable *cancel; + + /* main ui */ + GtkHeaderBar *header_bar; + + /* notifications */ + GtkLabel *notify_label; + GtkRevealer *notify_revealer; + + /* device details */ + GtkLabel *name_label; + GtkLabel *status_label; + GtkLabel *uuid_label; + + GtkLabel *time_title; + GtkLabel *time_label; + + /* parents */ + GtkExpander *parents_expander; + GtkLabel *parents_label; + GtkListBox *parents_devices; + + /* actions */ + GtkWidget *button_box; + GtkSpinner *spinner; + GtkButton *connect_button; + GtkButton *forget_button; +}; + +static void on_notify_button_clicked_cb (GtkButton *button, + CcBoltDeviceDialog *panel); + +static void on_forget_button_clicked_cb (CcBoltDeviceDialog *dialog); +static void on_connect_button_clicked_cb (CcBoltDeviceDialog *dialog); + +G_DEFINE_TYPE (CcBoltDeviceDialog, cc_bolt_device_dialog, GTK_TYPE_DIALOG); + +#define RESOURCE_UI "/org/gnome/control-center/thunderbolt/cc-bolt-device-dialog.ui" + +static const char * +status_to_string_for_ui (BoltDevice *dev) +{ + BoltStatus status; + BoltAuthFlags aflags; + gboolean nopcie; + + status = bolt_device_get_status (dev); + aflags = bolt_device_get_authflags(dev); + nopcie = bolt_flag_isset (aflags, BOLT_AUTH_NOPCIE); + + switch (status) + { + case BOLT_STATUS_DISCONNECTED: + return C_("Thunderbolt Device Status", "Disconnected"); + + case BOLT_STATUS_CONNECTING: + return C_("Thunderbolt Device Status", "Connecting"); + + case BOLT_STATUS_CONNECTED: + return C_("Thunderbolt Device Status", "Connected"); + + case BOLT_STATUS_AUTH_ERROR: + return C_("Thunderbolt Device Status", "Authorization Error"); + + case BOLT_STATUS_AUTHORIZING: + return C_("Thunderbolt Device Status", "Authorizing"); + + case BOLT_STATUS_AUTHORIZED: + case BOLT_STATUS_AUTHORIZED_NEWKEY: + case BOLT_STATUS_AUTHORIZED_SECURE: + case BOLT_STATUS_AUTHORIZED_DPONLY: + if (nopcie) + return C_("Thunderbolt Device Status", "Reduced Functionality"); + else + return C_("Thunderbolt Device Status", "Connected & Authorized"); + + case BOLT_STATUS_UNKNOWN: + break; /* use default return value, i.e. Unknown */ + } + + return C_("Thunderbolt Device Status", "Unknown"); +} + +static void +dialog_update_from_device (CcBoltDeviceDialog *dialog) +{ + g_autofree char *generated = NULL; + g_autofree char *timestr = NULL; + const char *label; + const char *uuid; + const char *status_brief; + BoltStatus status; + gboolean stored; + BoltDevice *dev; + guint timestamp; + + if (gtk_widget_in_destruction (GTK_WIDGET (dialog))) + return; + + dev = dialog->device; + + uuid = bolt_device_get_uid (dev); + label = bolt_device_get_label (dev); + + stored = bolt_device_is_stored (dev); + status = bolt_device_get_status (dev); + + if (label == NULL) + { + const char *name = bolt_device_get_name (dev); + const char *vendor = bolt_device_get_vendor (dev); + + generated = g_strdup_printf ("%s %s", name, vendor); + label = generated; + } + + gtk_label_set_label (dialog->name_label, label); + gtk_header_bar_set_title (dialog->header_bar, label); + + status_brief = status_to_string_for_ui (dev); + gtk_label_set_label (dialog->status_label, status_brief); + gtk_widget_set_visible (GTK_WIDGET (dialog->forget_button), stored); + + /* while we are having an ongoing operation we are setting the buttons + * to be in-sensitive. In that case, if the button was visible + * before it will be hidden when the operation is finished by the + * dialog_operation_done() function */ + if (gtk_widget_is_sensitive (GTK_WIDGET (dialog->connect_button))) + gtk_widget_set_visible (GTK_WIDGET (dialog->connect_button), + status == BOLT_STATUS_CONNECTED); + + gtk_label_set_label (dialog->uuid_label, uuid); + + if (bolt_status_is_authorized (status)) + { + /* Translators: The time point the device was authorized. */ + gtk_label_set_label (dialog->time_title, _("Authorized at:")); + timestamp = bolt_device_get_authtime (dev); + } + else if (bolt_status_is_connected (status)) + { + /* Translators: The time point the device was connected. */ + gtk_label_set_label (dialog->time_title, _("Connected at:")); + timestamp = bolt_device_get_conntime (dev); + } + else + { + /* Translators: The time point the device was enrolled, + * i.e. authorized and stored in the device database. */ + gtk_label_set_label (dialog->time_title, _("Enrolled at:")); + timestamp = bolt_device_get_storetime (dev); + } + + timestr = bolt_epoch_format (timestamp, "%c"); + gtk_label_set_label (dialog->time_label, timestr); + +} + +static void +on_device_notify_cb (GObject *gobject, + GParamSpec *pspec, + gpointer user_data) +{ + CcBoltDeviceDialog *dialog = CC_BOLT_DEVICE_DIALOG (user_data); + + dialog_update_from_device (dialog); +} + +static void +dialog_operation_start (CcBoltDeviceDialog *dialog) +{ + gtk_widget_set_sensitive (GTK_WIDGET (dialog->connect_button), FALSE); + gtk_widget_set_sensitive (GTK_WIDGET (dialog->forget_button), FALSE); + gtk_spinner_start (dialog->spinner); +} + +static void +dialog_operation_done (CcBoltDeviceDialog *dialog, + GtkWidget *sender, + GError *error) +{ + GtkWidget *cb = GTK_WIDGET (dialog->connect_button); + GtkWidget *fb = GTK_WIDGET (dialog->forget_button); + + /* don' do anything if we are being destroyed */ + if (gtk_widget_in_destruction (GTK_WIDGET (dialog))) + return; + + /* also don't do anything if the op was canceled */ + if (error != NULL && bolt_err_cancelled (error)) + return; + + gtk_spinner_stop (dialog->spinner); + + if (error != NULL) + { + gtk_label_set_label (dialog->notify_label, error->message); + gtk_revealer_set_reveal_child (dialog->notify_revealer, TRUE); + + /* set the *other* button to sensitive */ + gtk_widget_set_sensitive (cb, cb != sender); + gtk_widget_set_sensitive (fb, fb != sender); + } + else + { + gtk_widget_set_visible (sender, FALSE); + gtk_widget_set_sensitive (cb, TRUE); + gtk_widget_set_sensitive (fb, TRUE); + } +} + +static void +on_connect_all_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) err = NULL; + CcBoltDeviceDialog *dialog = CC_BOLT_DEVICE_DIALOG (user_data); + gboolean ok; + + ok = bolt_client_connect_all_finish (dialog->client, res, &err); + + if (!ok) + g_prefix_error (&err, _("Failed to authorize device: ")); + + dialog_operation_done (dialog, GTK_WIDGET (dialog->connect_button), err); +} + +static void +on_connect_button_clicked_cb (CcBoltDeviceDialog *dialog) +{ + g_autoptr(GPtrArray) devices = NULL; + g_autoptr(GList) entries = NULL; + BoltDevice *device = dialog->device; + GList *iter; + + g_return_if_fail (device != NULL); + + dialog_operation_start (dialog); + + entries = gtk_container_get_children (GTK_CONTAINER (dialog->parents_devices)); + devices = g_ptr_array_new (); + + /* reverse the order, so to start with the devices closest to the host */ + entries = g_list_reverse (entries); + + for (iter = entries; iter; iter = iter->next) + { + CcBoltDeviceEntry *entry; + BoltDevice *dev; + BoltStatus status; + + entry = (CcBoltDeviceEntry *) iter->data; + dev = cc_bolt_device_entry_get_device (entry); + status = bolt_device_get_status (dev); + + /* skip any devices down in the chain that are already authorized + * NB: it is not possible to have gaps of non-authorized devices + * in the chain, i.e. once we encounter a non-authorized device, + * all following device (down the chain, towards the target) will + * also be not authorized. */ + if (!bolt_status_is_pending (status)) + continue; + + /* device is now either !stored || pending */ + g_ptr_array_add (devices, dev); + } + + /* finally the actual device of the dialog */ + g_ptr_array_add (devices, device); + + bolt_client_connect_all_async (dialog->client, + devices, + BOLT_POLICY_DEFAULT, + BOLT_AUTHCTRL_NONE, + dialog->cancel, + on_connect_all_done, + dialog); +} + +static void +on_forget_device_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) err = NULL; + CcBoltDeviceDialog *dialog = CC_BOLT_DEVICE_DIALOG (user_data); + gboolean ok; + + ok = bolt_client_forget_device_finish (dialog->client, res, &err); + + if (!ok) + g_prefix_error (&err, _("Failed to forget device: ")); + + dialog_operation_done (dialog, GTK_WIDGET (dialog->forget_button), err); +} + +static void +on_forget_button_clicked_cb (CcBoltDeviceDialog *dialog) +{ + const char *uid = NULL; + + g_return_if_fail (dialog->device != NULL); + + uid = bolt_device_get_uid (dialog->device); + dialog_operation_start (dialog); + + bolt_client_forget_device_async (dialog->client, + uid, + dialog->cancel, + on_forget_device_done, + dialog); +} + +static void +on_notify_button_clicked_cb (GtkButton *button, + CcBoltDeviceDialog *dialog) +{ + gtk_revealer_set_reveal_child (dialog->notify_revealer, FALSE); +} + + +static void +cc_bolt_device_dialog_finalize (GObject *object) +{ + CcBoltDeviceDialog *dialog = CC_BOLT_DEVICE_DIALOG (object); + + g_clear_object (&dialog->device); + g_cancellable_cancel (dialog->cancel); + g_clear_object (&dialog->cancel); + g_clear_object (&dialog->client); + + G_OBJECT_CLASS (cc_bolt_device_dialog_parent_class)->finalize (object); +} + +static void +cc_bolt_device_dialog_class_init (CcBoltDeviceDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = cc_bolt_device_dialog_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, RESOURCE_UI); + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, header_bar); + + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, notify_label); + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, notify_revealer); + + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, name_label); + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, status_label); + + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, uuid_label); + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, time_title); + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, time_label); + + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, parents_expander); + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, parents_label); + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, parents_devices); + + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, button_box); + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, spinner); + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, connect_button); + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceDialog, forget_button); + + gtk_widget_class_bind_template_callback (widget_class, on_notify_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, on_connect_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, on_forget_button_clicked_cb); +} + +static void +cc_bolt_device_dialog_init (CcBoltDeviceDialog *dialog) +{ + g_resources_register (cc_thunderbolt_get_resource ()); + gtk_widget_init_template (GTK_WIDGET (dialog)); + + gtk_list_box_set_header_func (dialog->parents_devices, + cc_list_box_update_header_func, + NULL, + NULL); +} + +/* public functions */ +CcBoltDeviceDialog * +cc_bolt_device_dialog_new (void) +{ + CcBoltDeviceDialog *dialog; + + dialog = g_object_new (CC_TYPE_BOLT_DEVICE_DIALOG, + "use-header-bar", TRUE, + NULL); + return dialog; +} + +void +cc_bolt_device_dialog_set_client (CcBoltDeviceDialog *dialog, + BoltClient *client) +{ + g_clear_object (&dialog->client); + dialog->client = g_object_ref (client); +} + +void +cc_bolt_device_dialog_set_device (CcBoltDeviceDialog *dialog, + BoltDevice *device, + GPtrArray *parents) +{ + g_autofree char *msg = NULL; + guint i; + + if (device == dialog->device) + return; + + if (dialog->device) + { + g_cancellable_cancel (dialog->cancel); + g_clear_object (&dialog->cancel); + dialog->cancel = g_cancellable_new (); + + g_signal_handlers_disconnect_by_func (dialog->device, + G_CALLBACK (on_device_notify_cb), + dialog); + g_clear_object (&dialog->device); + + gtk_container_foreach (GTK_CONTAINER (dialog->parents_devices), + (GtkCallback) gtk_widget_destroy, NULL); + gtk_widget_hide (GTK_WIDGET (dialog->parents_expander)); + } + + if (device == NULL) + return; + + dialog->device = g_object_ref (device); + g_signal_connect_object (dialog->device, + "notify", + G_CALLBACK (on_device_notify_cb), + dialog, + 0); + + /* reset the sensitivity of the buttons, because + * dialog_update_from_device, because it can't know */ + gtk_widget_set_sensitive (GTK_WIDGET (dialog->connect_button), TRUE); + gtk_widget_set_sensitive (GTK_WIDGET (dialog->forget_button), TRUE); + + dialog_update_from_device (dialog); + + /* no parents, we are done here */ + if (!parents || parents->len == 0) + return; + + msg = g_strdup_printf (ngettext ("Depends on %u other device", + "Depends on %u other devices", + parents->len), parents->len); + + gtk_label_set_label (dialog->parents_label, msg); + gtk_widget_show (GTK_WIDGET (dialog->parents_expander)); + + for (i = 0; i < parents->len; i++) + { + CcBoltDeviceEntry *entry; + BoltDevice *parent; + + parent = g_ptr_array_index (parents, i); + + entry = cc_bolt_device_entry_new (parent, TRUE); + gtk_widget_show (GTK_WIDGET (entry)); + gtk_container_add (GTK_CONTAINER (dialog->parents_devices), GTK_WIDGET (entry)); + } +} + +BoltDevice * +cc_bolt_device_dialog_peek_device (CcBoltDeviceDialog *dialog) +{ + return dialog->device; +} + +gboolean +cc_bolt_device_dialog_device_equal (CcBoltDeviceDialog *dialog, + BoltDevice *device) +{ + return dialog->device != NULL && device == dialog->device; +} diff --git a/panels/thunderbolt/cc-bolt-device-dialog.h b/panels/thunderbolt/cc-bolt-device-dialog.h new file mode 100644 index 0000000..abdb46f --- /dev/null +++ b/panels/thunderbolt/cc-bolt-device-dialog.h @@ -0,0 +1,47 @@ +/* Copyright (C) 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> + * + */ + +#pragma once + +#include <gtk/gtk.h> + +#include "bolt-client.h" +#include "bolt-device.h" + +G_BEGIN_DECLS + +#define CC_TYPE_BOLT_DEVICE_DIALOG cc_bolt_device_dialog_get_type () + +G_DECLARE_FINAL_TYPE (CcBoltDeviceDialog, cc_bolt_device_dialog, CC, BOLT_DEVICE_DIALOG, GtkDialog); + +CcBoltDeviceDialog * cc_bolt_device_dialog_new (void); + +void cc_bolt_device_dialog_set_client (CcBoltDeviceDialog *dialog, + BoltClient *client); + +void cc_bolt_device_dialog_set_device (CcBoltDeviceDialog *dialog, + BoltDevice *device, + GPtrArray *parents); + +BoltDevice * cc_bolt_device_dialog_peek_device (CcBoltDeviceDialog *dialog); + +gboolean cc_bolt_device_dialog_device_equal (CcBoltDeviceDialog *dialog, + BoltDevice *device); + +G_END_DECLS diff --git a/panels/thunderbolt/cc-bolt-device-dialog.ui b/panels/thunderbolt/cc-bolt-device-dialog.ui new file mode 100644 index 0000000..4229a1d --- /dev/null +++ b/panels/thunderbolt/cc-bolt-device-dialog.ui @@ -0,0 +1,401 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.20.0 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <template class="CcBoltDeviceDialog" parent="GtkDialog"> + <property name="can_focus">False</property> + <property name="type_hint">dialog</property> + <property name="use_header_bar">1</property> + <property name="resizable">False</property> + <property name="modal">True</property> + + <child internal-child="headerbar"> + <object class="GtkHeaderBar" id="header_bar"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="show_close_button">True</property> + <property name="title">Device Identifier</property> + <property name="subtitle"></property> + <property name="has-subtitle">False</property> + </object> + </child> + + <child internal-child="vbox"> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="margin-bottom">24</property> + <property name="border-width">0</property> + <child> + <object class="GtkOverlay"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child type="overlay"> + <object class="GtkRevealer" id="notify_revealer"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">start</property> + <property name="transition_type">slide-down</property> + <child> + <object class="GtkFrame"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="notify_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_markup">True</property> + <property name="wrap">True</property> + </object> + </child> + <child> + <object class="GtkButton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="relief">none</property> + <signal name="clicked" + handler="on_notify_button_clicked_cb" + object="CcBoltDeviceDialog" + swapped="no" /> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon-name">window-close-symbolic</property> + </object> + </child> + </object> + </child> + </object> + </child> + <style> + <class name="app-notification" /> + </style> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="expand">True</property> + <property name="orientation">vertical</property> + + <child> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin-left">72</property> + <property name="margin-right">72</property> + <property name="margin-top">24</property> + <property name="margin-bottom">0</property> + <property name="row_spacing">12</property> + <property name="column_spacing">24</property> + <child> + <object class="GtkLabel" id="name_title_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="hexpand">False</property> + <property name="vexpand">False</property> + <property name="label" translatable="yes">Name:</property> + <property name="justify">right</property> + <property name="xalign">1</property> + <property name="mnemonic_widget">name_label</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="name_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="label">Device identifier</property> + <property name="use_markup">True</property> + <property name="ellipsize">end</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="status_title_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="hexpand">False</property> + <property name="vexpand">False</property> + <property name="label" translatable="yes">Status:</property> + <property name="justify">right</property> + <property name="xalign">1</property> + <property name="mnemonic_widget">status_label</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="status_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">center</property> + <property name="hexpand">True</property> + <property name="label">Status</property> + <property name="ellipsize">end</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + + <child> + <object class="GtkLabel" id="uuid_title_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="hexpand">False</property> + <property name="vexpand">False</property> + <property name="label" translatable="yes">UUID:</property> + <property name="justify">right</property> + <property name="xalign">1</property> + <property name="mnemonic_widget">uuid_label</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="uuid_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">center</property> + <property name="hexpand">True</property> + <property name="label">Status</property> + <property name="ellipsize">end</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + </packing> + </child> + + <child> + <object class="GtkLabel" id="time_title"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="hexpand">False</property> + <property name="vexpand">False</property> + <property name="label">Timestamp:</property> + <property name="justify">right</property> + <property name="xalign">1</property> + <property name="mnemonic_widget">time_label</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="time_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">center</property> + <property name="hexpand">True</property> + <property name="label">Status</property> + <property name="ellipsize">end</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + </packing> + </child> + + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="padding">1</property> + <property name="position">1</property> + </packing> + <object class="GtkSizeGroup" id="device_titles_sizegroup"> + <widgets> + <widget name="name_title_label"/> + <widget name="status_title_label"/> + <widget name="uuid_title_label"/> + <widget name="time_title"/> + </widgets> + </object> + <object class="GtkSizeGroup" id="device_labels_sizegroup"> + <widgets> + <widget name="name_label"/> + <widget name="status_label"/> + <widget name="uuid_label"/> + <widget name="time_label"/> + </widgets> + </object> + </child> + <!-- end of grid --> + <child> + <object class="GtkExpander" id="parents_expander"> + <property name="visible">False</property> + <property name="halign">fill</property> + <property name="margin-left">72</property> + <property name="margin-right">72</property> + <property name="margin-top">12</property> + <property name="margin-bottom">0</property> + <property name="spacing">12</property> + <child type="label"> + <object class="GtkLabel" id="parents_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="hexpand">False</property> + <property name="vexpand">False</property> + <property name="label">Depends on other devices:</property> + <property name="justify">center</property> + <property name="xalign">1</property> + </object> + </child> + <child> + <object class="GtkFrame"> + <property name="margin-top">12</property> + <property name="visible">True</property> + <property name="valign">start</property> + <property name="vexpand">False</property> + <style> + <class name="view" /> + </style> + <child> + <object class="GtkListBox" id="parents_devices"> + <property name="valign">start</property> + <property name="vexpand">False</property> + <property name="visible">True</property> + <property name="selection-mode">none</property> + <property name="can_focus">True</property> + </object> + </child> + </object> + </child> + </object> + </child> + <!-- end of --> + <child> + <object class="GtkBox" id="button_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <property name="spacing">12</property> + <property name="margin-left">72</property> + <property name="margin-right">72</property> + <property name="margin-top">36</property> + <property name="margin-bottom">0</property> + <property name="halign">fill</property> + <child> + <object class="GtkSpinner" id="spinner"> + <property name="visible">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="active">False</property> + </object> + </child> + + <child> + <object class="GtkButton" id="connect_button"> + <property name="label" translatable="yes">Authorize and Connect</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="halign">fill</property> + + <signal name="clicked" + handler="on_connect_button_clicked_cb" + object="CcBoltDeviceDialog" + swapped="yes" /> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + + <child> + <object class="GtkButton" id="forget_button"> + <property name="label" translatable="yes">Forget Device</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="halign">fill</property> + <signal name="clicked" + handler="on_forget_button_clicked_cb" + object="CcBoltDeviceDialog" + swapped="yes" /> + <style> + <class name="destructive-action"/> + </style> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkBox" id="spinner_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + </child> + </object> + + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + + <object class="GtkSizeGroup" id="actions_sizegroup"> + <widgets> + <widget name="forget_button"/> + <widget name="connect_button"/> + </widgets> + </object> + + <object class="GtkSizeGroup" id="spinner_sizegroup"> + <widgets> + <widget name="spinner"/> + <widget name="spinner_box"/> + </widgets> + </object> + + </child> + </object> + </child> + + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/thunderbolt/cc-bolt-device-entry.c b/panels/thunderbolt/cc-bolt-device-entry.c new file mode 100644 index 0000000..cba7ac0 --- /dev/null +++ b/panels/thunderbolt/cc-bolt-device-entry.c @@ -0,0 +1,227 @@ +/* Copyright (C) 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 "bolt-str.h" + +#include "cc-bolt-device-entry.h" + +#include "cc-thunderbolt-resources.h" + +#include <glib/gi18n.h> + +#define RESOURCE_UI "/org/gnome/control-center/thunderbolt/cc-bolt-device-entry.ui" + +struct _CcBoltDeviceEntry +{ + GtkListBoxRow parent; + + BoltDevice *device; + + /* main ui */ + GtkLabel *name_label; + GtkLabel *status_label; + GtkLabel *status_warning; + gboolean show_warnings; +}; + +static const char * device_status_to_brief_for_ui (BoltDevice *dev); + +G_DEFINE_TYPE (CcBoltDeviceEntry, cc_bolt_device_entry, GTK_TYPE_LIST_BOX_ROW); + +enum +{ + SIGNAL_STATUS_CHANGED, + SIGNAL_LAST +}; + +static guint signals[SIGNAL_LAST] = { 0, }; + +static void +entry_set_name (CcBoltDeviceEntry *entry) +{ + g_autofree char *name = NULL; + BoltDevice *dev = entry->device; + + g_return_if_fail (dev != NULL); + + name = bolt_device_get_display_name (dev); + + gtk_label_set_label (entry->name_label, name); +} + +static void +entry_update_status (CcBoltDeviceEntry *entry) +{ + const char *brief; + BoltStatus status; + gboolean warn; + + status = bolt_device_get_status (entry->device); + brief = device_status_to_brief_for_ui (entry->device); + + gtk_label_set_label (entry->status_label, brief); + + g_signal_emit (entry, + signals[SIGNAL_STATUS_CHANGED], + 0, + status); + + warn = entry->show_warnings && bolt_status_is_pending (status); + gtk_widget_set_visible (GTK_WIDGET (entry->status_warning), warn); +} + +static void +on_device_notify_cb (GObject *gobject, + GParamSpec *pspec, + gpointer user_data) +{ + CcBoltDeviceEntry *entry = CC_BOLT_DEVICE_ENTRY (user_data); + const char *what; + + what = g_param_spec_get_name (pspec); + + if (bolt_streq (what, "status")) + entry_update_status (entry); + else if (bolt_streq (what, "label") || + bolt_streq (what, "name") || + bolt_streq (what, "vendor")) + entry_set_name (entry); +} + +/* device helpers */ + +static const char * +device_status_to_brief_for_ui (BoltDevice *dev) +{ + BoltStatus status; + BoltAuthFlags aflags; + gboolean nopcie; + + status = bolt_device_get_status (dev); + aflags = bolt_device_get_authflags(dev); + nopcie = bolt_flag_isset (aflags, BOLT_AUTH_NOPCIE); + + switch (status) + { + case BOLT_STATUS_DISCONNECTED: + return C_("Thunderbolt Device Status", "Disconnected"); + + case BOLT_STATUS_CONNECTING: + return C_("Thunderbolt Device Status", "Connecting"); + + case BOLT_STATUS_CONNECTED: + case BOLT_STATUS_AUTHORIZED_DPONLY: + return C_("Thunderbolt Device Status", "Connected"); + + case BOLT_STATUS_AUTH_ERROR: + return C_("Thunderbolt Device Status", "Error"); + + case BOLT_STATUS_AUTHORIZING: + return C_("Thunderbolt Device Status", "Authorizing"); + + case BOLT_STATUS_AUTHORIZED: + case BOLT_STATUS_AUTHORIZED_NEWKEY: + case BOLT_STATUS_AUTHORIZED_SECURE: + if (nopcie) + return C_("Thunderbolt Device Status", "Connected"); + else + return C_("Thunderbolt Device Status", "Authorized"); + + case BOLT_STATUS_UNKNOWN: + break; /* use function default */ + } + + return C_("Thunderbolt Device Status", "Unknown"); +} + +static void +cc_bolt_device_entry_finalize (GObject *object) +{ + CcBoltDeviceEntry *entry = CC_BOLT_DEVICE_ENTRY (object); + + g_clear_object (&entry->device); + + G_OBJECT_CLASS (cc_bolt_device_entry_parent_class)->finalize (object); +} + +static void +cc_bolt_device_entry_class_init (CcBoltDeviceEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = cc_bolt_device_entry_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, RESOURCE_UI); + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceEntry, name_label); + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceEntry, status_label); + gtk_widget_class_bind_template_child (widget_class, CcBoltDeviceEntry, status_warning); + + signals[SIGNAL_STATUS_CHANGED] = + g_signal_new ("status-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, + 1, BOLT_TYPE_STATUS); +} + +static void +cc_bolt_device_entry_init (CcBoltDeviceEntry *entry) +{ + g_resources_register (cc_thunderbolt_get_resource ()); + gtk_widget_init_template (GTK_WIDGET (entry)); +} + +/* public function */ + +CcBoltDeviceEntry * +cc_bolt_device_entry_new (BoltDevice *device, + gboolean show_warnings) +{ + CcBoltDeviceEntry *entry; + + entry = g_object_new (CC_TYPE_BOLT_DEVICE_ENTRY, NULL); + entry->device = g_object_ref (device); + entry->show_warnings = show_warnings; + + entry_set_name (entry); + entry_update_status (entry); + + g_signal_connect_object (entry->device, + "notify", + G_CALLBACK (on_device_notify_cb), + entry, + 0); + + return entry; +} + +BoltDevice * +cc_bolt_device_entry_get_device (CcBoltDeviceEntry *entry) +{ + g_return_val_if_fail (entry != NULL, NULL); + g_return_val_if_fail (CC_IS_BOLT_DEVICE_ENTRY (entry), NULL); + + return entry->device; +} diff --git a/panels/thunderbolt/cc-bolt-device-entry.h b/panels/thunderbolt/cc-bolt-device-entry.h new file mode 100644 index 0000000..f6fc1f7 --- /dev/null +++ b/panels/thunderbolt/cc-bolt-device-entry.h @@ -0,0 +1,35 @@ +/* Copyright (C) 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> + * + */ + +#pragma once + +#include <gtk/gtk.h> +#include "bolt-device.h" + +G_BEGIN_DECLS + +#define CC_TYPE_BOLT_DEVICE_ENTRY cc_bolt_device_entry_get_type () +G_DECLARE_FINAL_TYPE (CcBoltDeviceEntry, cc_bolt_device_entry, CC, BOLT_DEVICE_ENTRY, GtkListBoxRow); + + +CcBoltDeviceEntry * cc_bolt_device_entry_new (BoltDevice *device, + gboolean show_warnings); +BoltDevice * cc_bolt_device_entry_get_device (CcBoltDeviceEntry *entry); + +G_END_DECLS diff --git a/panels/thunderbolt/cc-bolt-device-entry.ui b/panels/thunderbolt/cc-bolt-device-entry.ui new file mode 100644 index 0000000..7668f6a --- /dev/null +++ b/panels/thunderbolt/cc-bolt-device-entry.ui @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.20"/> + + <template class="CcBoltDeviceEntry" parent="GtkListBoxRow"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="activatable">True</property> + <child> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="border-width">12</property> + <property name="margin_left">6</property> + <property name="margin_right">6</property> + <property name="column-spacing">12</property> + <property name="row-spacing">2</property> + <child> + <object class="GtkLabel" id="name_label"> + <property name="ellipsize">end</property> + <property name="use-markup">True</property> + <property name="visible">True</property> + <property name="hexpand">True</property> + <property name="label">Device Name</property> + <property name="xalign">0.0</property> + <property name="valign">center</property> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="hexpand">False</property> + <property name="halign">end</property> + <property name="spacing">6</property> + <child> + <object class="GtkImage" id="status_warning"> + <property name="visible">False</property> + <property name="can_focus">False</property> + <property name="icon_name">dialog-warning-symbolic</property> + <property name="icon_size">1</property> + <property name="margin_left">0</property> + <property name="xalign">0.0</property> + </object> + </child> + <child> + <object class="GtkLabel" id="status_label"> + <property name="visible">True</property> + <property name="hexpand">False</property> + <property name="label">Status</property> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="xalign">1.0</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">0</property> + </packing> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/thunderbolt/cc-bolt-panel.c b/panels/thunderbolt/cc-bolt-panel.c new file mode 100644 index 0000000..9004c3c --- /dev/null +++ b/panels/thunderbolt/cc-bolt-panel.c @@ -0,0 +1,994 @@ +/* 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 <list-box-helper.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 */ + GtkLabel *notb_caption; + GtkLabel *notb_details; + + /* notifications */ + GtkLabel *notification_label; + GtkRevealer *notification_revealer; + + /* authmode */ + GtkSwitch *authmode_switch; + GtkSpinner *authmode_spinner; + GtkStack *authmode_mode; + + /* 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 gboolean on_device_dialog_delete_event_cb (GtkWidget *widget, + GdkEvent *event, + CcBoltPanel *panel); + +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."); + + gtk_label_set_label (panel->notb_details, 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 *lstbox) +{ + g_autoptr(GList) children = NULL; + gboolean show; + + children = gtk_container_get_children (GTK_CONTAINER (lstbox)); + show = g_list_length (children) > 0; + + gtk_widget_set_visible (GTK_WIDGET (lstbox), 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 */ + gtk_widget_show (GTK_WIDGET (entry)); + + status = bolt_device_get_status (dev); + + if (bolt_status_is_pending (status)) + { + gtk_container_add (GTK_CONTAINER (panel->pending_list), GTK_WIDGET (entry)); + gtk_widget_show (GTK_WIDGET (panel->pending_list)); + gtk_widget_show (GTK_WIDGET (panel->pending_box)); + } + else + { + gtk_container_add (GTK_CONTAINER (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_widget_destroy (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; + const char *name; + + 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); + + name = enabled ? "enabled" : "disabled"; + gtk_stack_set_visible_child_name (panel->authmode_mode, name); +} + +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_container_remove (GTK_CONTAINER (from), target); + gtk_container_add (GTK_CONTAINER (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."); + } + + gtk_label_set_label (panel->notb_details, 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_resize (GTK_WINDOW (panel->device_dialog), 1, 1); + gtk_widget_show (GTK_WIDGET (panel->device_dialog)); +} + +static gboolean +on_device_dialog_delete_event_cb (GtkWidget *widget, + GdkEvent *event, + CcBoltPanel *panel) +{ + CcBoltDeviceDialog *dialog; + + dialog = CC_BOLT_DEVICE_DIALOG (widget); + + cc_bolt_device_dialog_set_device (dialog, NULL, NULL); + gtk_widget_hide (widget); + + return TRUE; +} + +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. */ + g_clear_pointer ((GtkWidget **) &panel->device_dialog, gtk_widget_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; + + parent = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel)))); + gtk_window_set_transient_for (GTK_WINDOW (panel->device_dialog), parent); + + G_OBJECT_CLASS (cc_bolt_panel_parent_class)->constructed (object); + + shell = cc_panel_get_shell (CC_PANEL (panel)); + cc_shell_embed_widget_in_header (shell, GTK_WIDGET (panel->headerbar_box), GTK_POS_RIGHT); +} + +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_mode); + 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, headerbar_box); + gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, lock_button); + gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, notb_caption); + gtk_widget_class_bind_template_child (widget_class, CcBoltPanel, notb_details); + 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_header_func (panel->devices_list, + cc_list_box_update_header_func, + NULL, + NULL); + + gtk_list_box_set_header_func (panel->pending_list, + cc_list_box_update_header_func, + NULL, + NULL); + + 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 (); + g_signal_connect_object (panel->device_dialog, + "delete-event", + G_CALLBACK (on_device_dialog_delete_event_cb), + panel, 0); + + bolt_client_new_async (cc_panel_get_cancellable (CC_PANEL (panel)), bolt_client_ready, g_object_ref (panel)); + +} diff --git a/panels/thunderbolt/cc-bolt-panel.h b/panels/thunderbolt/cc-bolt-panel.h new file mode 100644 index 0000000..5901044 --- /dev/null +++ b/panels/thunderbolt/cc-bolt-panel.h @@ -0,0 +1,30 @@ +/* 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> + * + */ + +#pragma once + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define CC_TYPE_BOLT_PANEL cc_bolt_panel_get_type () + +G_DECLARE_FINAL_TYPE (CcBoltPanel, cc_bolt_panel, CC, BOLT_PANEL, CcPanel); + +G_END_DECLS diff --git a/panels/thunderbolt/cc-bolt-panel.ui b/panels/thunderbolt/cc-bolt-panel.ui new file mode 100644 index 0000000..226353c --- /dev/null +++ b/panels/thunderbolt/cc-bolt-panel.ui @@ -0,0 +1,594 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="3.20"/> + + <template class="CcBoltPanel" parent="CcPanel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + + <child> + <object class="GtkOverlay"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child type="overlay"> + <object class="GtkRevealer" id="notification_revealer"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">start</property> + <property name="transition_type">slide-down</property> + <child> + <object class="GtkFrame"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="notification_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_markup">True</property> + <property name="wrap">True</property> + </object> + </child> + <child> + <object class="GtkButton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="relief">none</property> + <signal name="clicked" + handler="on_notification_button_clicked_cb" + object="CcBoltPanel" + swapped="no" /> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon-name">window-close-symbolic</property> + </object> + </child> + </object> + </child> + </object> + </child> + <style> + <class name="app-notification" /> + </style> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkStack" id="container"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="homogeneous">False</property> + <property name="transition_type">crossfade</property> + + <!-- Spinner for when we are creating --> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="expand">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <property name="spacing">10</property> + <property name="margin">18</property> + <child type="center"> + <object class="GtkSpinner" id="loading-spinner"> + <property name="visible">True</property> + <property name="active">True</property> + <property name="expand">True</property> + </object> + </child> + </object> + <packing> + <property name="name">loading</property> + </packing> + </child> + + <!-- No tunderbolt --> + + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="expand">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <property name="spacing">10</property> + <property name="margin">18</property> + <child type="center" > + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_start">12</property> + <property name="margin_end">6</property> + <property name="margin_top">12</property> + <property name="margin_bottom">12</property> + <property name="row_spacing">12</property> + <property name="column_spacing">24</property> + + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">thunderbolt-symbolic</property> + <property name="pixel_size">96</property> + <property name="yalign">0</property> + <style> + <class name="dim-label" /> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="height">2</property> + </packing> + </child> + + <child> + <object class="GtkLabel" id="notb_caption"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="wrap">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">No Thunderbolt support</property> + <attributes> + <attribute name="scale" value="1.2" /> + </attributes> + <style> + <class name="dim-label" /> + </style> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + + <child> + <object class="GtkLabel" id="notb_details"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="max-width-chars">40</property> + <property name="use_markup">True</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="wrap">True</property> + <property name="label" translatable="no">Could not connect to the thunderbolt subsystem.</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> + + </object> + </child> + + </object> + <packing> + <property name="name">no-thunderbolt</property> + </packing> + </child> + + <!-- Normal operation mode (show list of devices) --> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hscrollbar-policy">never</property> + + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="shadow-type">none</property> + + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <property name="valign">start</property> + + <!-- Stub box --> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + </object> + </child> + + <!-- center/content box --> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="spacing">32</property> + <property name="margin_top">32</property> + <property name="margin_bottom">32</property> + <property name="margin_left">18</property> + <property name="margin_right">18</property> + <property name="orientation">vertical</property> + + <!-- Auth Mode --> + <child> + <object class="GtkBox" id="authmode_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <property name="spacing">12</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">False</property> + <property name="halign">start</property> + <property name="xalign">0.0</property> + <property name="label" translatable="yes">Direct Access</property> + <property name="mnemonic_widget">authmode_switch</property> + <attributes> + <attribute name="weight" value="bold" /> + </attributes> + </object> + </child> + + <child> + <object class="GtkStack" id="authmode_mode"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="transition-type">crossfade</property> + <property name="homogeneous">True</property> + + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="margin_left">0</property> + <property name="hexpand">False</property> + <property name="vexpand">False</property> + <property name="label" translatable="yes" >Allow direct access to devices such as docks and external GPUs.</property> + <property name="use_markup">True</property> + <property name="wrap">True</property> + <property name="xalign">0.0</property> + <property name="yalign">0.0</property> + <property name="max-width-chars">45</property> + </object> + <packing> + <property name="name">enabled</property> + </packing> + </child> + + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="margin_left">0</property> + <property name="hexpand">False</property> + <property name="vexpand">False</property> + <property name="label" translatable="yes" >Only USB and Display Port devices can attach.</property> + <property name="use_markup">True</property> + <property name="wrap">True</property> + <property name="xalign">0.0</property> + <property name="yalign">0.0</property> + <property name="max-width-chars">45</property> + </object> + <packing> + <property name="name">disabled</property> + </packing> + </child> + + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">horizontal</property> + <property name="spacing">6</property> + <property name="halign">center</property> + <property name="valign">start</property> + + <child> + <object class="GtkSpinner" id="authmode_spinner"> + <property name="visible">True</property> + <property name="active">False</property> + </object> + </child> + + <child> + <object class="GtkSwitch" id="authmode_switch"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="halign">end</property> + <property name="valign">start</property> + <property name="active">True</property> + <signal name="state-set" + handler="on_authmode_state_set_cb" + object="CcBoltPanel" + swapped="yes" /> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + <property name="pack-type">end</property> + </packing> + </child> + </object> + </child> + + <!-- Stack: devices/no-devices --> + <child> + <object class="GtkStack" id="devices_stack"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="transition-type">crossfade</property> + + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">32</property> + + <!-- Pending Device List --> + <child> + <object class="GtkBox" id="pending_box"> + <property name="visible">False</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + + <!-- Pending Device List: Header --> + <child> + <object class="GtkBox" id="pending_header"> + <property name="visible">True</property> + <property name="hexpand">True</property> + <property name="halign">start</property> + <property name="spacing">6</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">dialog-warning-symbolic</property> + <property name="icon_size">1</property> + <property name="margin_left">0</property> + <property name="xalign">0.0</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">Pending Devices</property> + <property name="xalign">0.0</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkSpinner" id="pending_spinner"> + <property name="hexpand">True</property> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + + <!-- Pending List: Devices --> + <child> + <object class="GtkFrame"> + <property name="visible">True</property> + <property name="valign">start</property> + <property name="vexpand">False</property> + <style> + <class name="view" /> + </style> + <child> + <object class="GtkListBox" id="pending_list"> + <property name="visible">True</property> + <property name="selection-mode">none</property> + <property name="can_focus">True</property> + <signal name="row-activated" + handler="on_device_entry_row_activated_cb" + object="CcBoltPanel" + swapped="yes" /> + </object> + </child> + </object> + </child> + </object> + </child> + + <!-- Device List --> + <child> + <object class="GtkBox" id="devices_box"> + <property name="visible">False</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + + <!-- Device List: Header --> + <child> + <object class="GtkBox" id="devices_header"> + <property name="visible">True</property> + <property name="hexpand">True</property> + <property name="halign">start</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">Devices</property> + <property name="xalign">0.0</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkSpinner" id="probing_spinner"> + <property name="hexpand">True</property> + <property name="visible">True</property> + </object> + </child> + </object> + </child> + + <!-- Device List: Devices --> + <child> + <object class="GtkFrame"> + <property name="visible">True</property> + <property name="valign">start</property> + <property name="vexpand">False</property> + <style> + <class name="view" /> + </style> + <child> + <object class="GtkListBox" id="devices_list"> + <property name="visible">True</property> + <property name="selection-mode">none</property> + <property name="can_focus">True</property> + <signal name="row-activated" + handler="on_device_entry_row_activated_cb" + object="CcBoltPanel" + swapped="yes" /> + </object> + </child> + </object> + </child> + + </object> + </child> + + </object> + <packing> + <property name="name">have-devices</property> + </packing> + </child> + + <!-- No Devices --> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="hexpand">True</property> + <property name="halign">start</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">Devices</property> + <property name="xalign">0.0</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">No devices attached</property> + <property name="xalign">0.0</property> + </object> + </child> + </object> + <packing> + <property name="name">no-devices</property> + </packing> + </child> <!-- End of: No Devices --> + + </object> + </child> <!-- End of Stack: devices/no-devices --> + + </object> + </child> <!-- End of enter/content box --> + + + <!-- Stub box --> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + </object> + </child> + + <!-- End of content --> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="name">devices-listing</property> + </packing> + </child> + + <!-- End of 'container' --> + </object> + </child> + + <!-- End of overlay --> + </object> + </child> + </template> + + <!-- Headerbar entries --> + <object class="GtkBox" id="headerbar_box"> + <property name="visible">False</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <property name="halign">end</property> + <child> + <object class="GtkLockButton" id="lock_button"> + <property name="visible">True</property> + </object> + </child> + </object> + +</interface> diff --git a/panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in b/panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in new file mode 100644 index 0000000..f7f9e4b --- /dev/null +++ b/panels/thunderbolt/gnome-thunderbolt-panel.desktop.in.in @@ -0,0 +1,18 @@ +[Desktop Entry] +Name=Thunderbolt +Comment=Manage Thunderbolt devices +Exec=gnome-control-center thunderbolt +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=thunderbolt +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;X-GNOME-Settings-Panel;HardwareSettings;X-GNOME-PrivacySettings; +OnlyShowIn=GNOME;Unity; +X-GNOME-Bugzilla-Bugzilla=GNOME +X-GNOME-Bugzilla-Product=gnome-control-center +X-GNOME-Bugzilla-Component=thunderbolt +X-GNOME-Bugzilla-Version=@VERSION@ +# Translators: those are keywords for the thunderbolt control-center panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! +Keywords=Thunderbolt;privacy; diff --git a/panels/thunderbolt/meson.build b/panels/thunderbolt/meson.build new file mode 100644 index 0000000..2b1a03a --- /dev/null +++ b/panels/thunderbolt/meson.build @@ -0,0 +1,74 @@ +panels_list += cappletname + +desktop = 'gnome-@0@-panel.desktop'.format(cappletname) +desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf +) + +i18n.merge_file( + desktop, + type: 'desktop', + input: desktop_in, + output: desktop, + po_dir: po_dir, + install: true, + install_dir: control_center_desktopdir +) + +sources = files( + 'bolt-client.c', + 'bolt-device.c', + 'bolt-enums.c', + 'bolt-error.c', + 'bolt-names.c', + 'bolt-proxy.c', + 'bolt-str.c', + 'bolt-time.c', + 'cc-bolt-panel.c', + 'cc-bolt-device-dialog.c', + 'cc-bolt-device-entry.c', +) + +enum_headers = [ + 'bolt-enums.h', + 'bolt-error.h' +] + +sources += gnome.mkenums_simple( + 'bolt-enum-types', + sources: enum_headers) + +resource_data = files( + 'cc-bolt-device-dialog.ui', + 'cc-bolt-device-entry.ui', + 'cc-bolt-panel.ui' +) + +sources += gnome.compile_resources( + 'cc-' + cappletname + '-resources', + cappletname + '.gresource.xml', + source_dir: '.', + c_name: 'cc_' + cappletname, + dependencies: resource_data, + export: true +) + +deps = common_deps + [ + gnome_desktop_dep, + polkit_gobject_dep, + m_dep, +] + +cflags += [ + '-DBINDIR="@0@"'.format(control_center_bindir) +] + +panels_libs += static_library( + cappletname, + sources: sources, + include_directories: [top_inc, common_inc], + dependencies: deps, + c_args: cflags +) diff --git a/panels/thunderbolt/thunderbolt.gresource.xml b/panels/thunderbolt/thunderbolt.gresource.xml new file mode 100644 index 0000000..8953d62 --- /dev/null +++ b/panels/thunderbolt/thunderbolt.gresource.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/thunderbolt"> + <file preprocess="xml-stripblanks">cc-bolt-device-dialog.ui</file> + <file preprocess="xml-stripblanks">cc-bolt-device-entry.ui</file> + <file preprocess="xml-stripblanks">cc-bolt-panel.ui</file> + </gresource> +</gresources> + diff --git a/panels/thunderbolt/update-from-bolt.sh b/panels/thunderbolt/update-from-bolt.sh new file mode 100755 index 0000000..8b22f08 --- /dev/null +++ b/panels/thunderbolt/update-from-bolt.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +if [ $# -ne 1 ]; then + echo "$0: usage: <BOLT-SOURCE>" + exit 1 +fi + +boltsrc="$1" + +function die() { + echo $* + exit 1 +} + +function copyone() { + dst=$1 + src="$boltsrc/$dst" + + search=(common cli) + for base in ${search[*]} + do + path="$boltsrc/$base/$dst" + if [ -f $path ]; then + src=$path + break; + fi + done + + if [ ! -f $src ]; then + echo -e "$dst \t[ skipped ] $src (ENOENT)" + elif cmp -s $src $dst; then + echo -e "$dst \t[ unchanged ]" + else + cp $src $dst || die "$dst [failed] source: $src" + echo -e "$dst \t[ updated ] $src" + git add $dst + fi +} + +names=(client device enums error names proxy str time) + +for fn in ${names[*]} +do + header="bolt-$fn.h" + source="bolt-$fn.c" + + copyone $header + copyone $source +done + |