/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2013 Red Hat, Inc. */ /** * SECTION:nmt-edit-connection-list * @short_description: Connection list for "nmtui edit" * * #NmtEditConnectionList is the list of connections displayed by * "nmtui edit". */ #include "libnm/nm-default-client.h" #include "nmtui.h" #include "nmtui-edit.h" #include "nmt-edit-connection-list.h" #include "nmt-editor.h" #include "nm-editor-utils.h" G_DEFINE_TYPE(NmtEditConnectionList, nmt_edit_connection_list, NMT_TYPE_NEWT_GRID) #define NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_EDIT_CONNECTION_LIST, NmtEditConnectionListPrivate)) typedef struct { GSList *connections; gboolean grouped; NmtEditConnectionListFilter connection_filter; gpointer connection_filter_data; NmtNewtListbox * listbox; NmtNewtButtonBox *buttons; NmtNewtWidget *add; NmtNewtWidget *edit; NmtNewtWidget *delete; NmtNewtWidget *extra; } NmtEditConnectionListPrivate; enum { PROP_0, PROP_GROUPED, PROP_CONNECTION_FILTER, PROP_CONNECTION_FILTER_DATA, PROP_EXTRA_WIDGET, PROP_CONNECTIONS, PROP_NUM_CONNECTIONS, LAST_PROP }; enum { ADD_CONNECTION, EDIT_CONNECTION, REMOVE_CONNECTION, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = {0}; static void add_clicked(NmtNewtButton *button, gpointer list); static void edit_clicked(NmtNewtButton *button, gpointer list); static void delete_clicked(NmtNewtButton *button, gpointer list); static void listbox_activated(NmtNewtWidget *listbox, gpointer list); static void nmt_edit_connection_list_init(NmtEditConnectionList *list) { NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list); NmtNewtWidget * listbox, *buttons; NmtNewtGrid * grid = NMT_NEWT_GRID(list); listbox = g_object_new(NMT_TYPE_NEWT_LISTBOX, "flags", NMT_NEWT_LISTBOX_SCROLL | NMT_NEWT_LISTBOX_BORDER, "skip-null-keys", TRUE, NULL); priv->listbox = NMT_NEWT_LISTBOX(listbox); nmt_newt_grid_add(grid, listbox, 0, 0); nmt_newt_grid_set_flags(grid, listbox, NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_FILL_Y | NMT_NEWT_GRID_EXPAND_X | NMT_NEWT_GRID_EXPAND_Y); g_signal_connect(priv->listbox, "activated", G_CALLBACK(listbox_activated), list); buttons = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_VERTICAL); priv->buttons = NMT_NEWT_BUTTON_BOX(buttons); nmt_newt_grid_add(grid, buttons, 1, 0); nmt_newt_widget_set_padding(buttons, 1, 1, 0, 1); nmt_newt_grid_set_flags(grid, buttons, NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_FILL_Y | NMT_NEWT_GRID_EXPAND_Y); priv->add = nmt_newt_button_box_add_start(priv->buttons, _("Add")); g_signal_connect(priv->add, "clicked", G_CALLBACK(add_clicked), list); priv->edit = nmt_newt_button_box_add_start(priv->buttons, _("Edit...")); g_signal_connect(priv->edit, "clicked", G_CALLBACK(edit_clicked), list); priv->delete = nmt_newt_button_box_add_start(priv->buttons, _("Delete")); g_signal_connect(priv->delete, "clicked", G_CALLBACK(delete_clicked), list); } static int sort_by_timestamp(gconstpointer a, gconstpointer b) { NMSettingConnection *s_con_a, *s_con_b; guint64 time_a, time_b; s_con_a = nm_connection_get_setting_connection((NMConnection *) a); s_con_b = nm_connection_get_setting_connection((NMConnection *) b); time_a = nm_setting_connection_get_timestamp(s_con_a); time_b = nm_setting_connection_get_timestamp(s_con_b); return (int) (time_b - time_a); } static void nmt_edit_connection_list_rebuild(NmtEditConnectionList *list); static void rebuild_on_connection_changed(NMRemoteConnection *connection, gpointer list) { nmt_edit_connection_list_rebuild(list); } static void free_connections(NmtEditConnectionList *list) { NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list); NMConnection * conn; GSList * iter; for (iter = priv->connections; iter; iter = iter->next) { conn = iter->data; g_signal_handlers_disconnect_by_func(conn, G_CALLBACK(rebuild_on_connection_changed), list); g_object_unref(conn); } g_slist_free(priv->connections); priv->connections = NULL; } static void nmt_edit_connection_list_rebuild(NmtEditConnectionList *list) { NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list); const GPtrArray * connections; GSList * iter; gboolean did_header = FALSE, did_vpn = FALSE; NMEditorConnectionTypeData ** types; NMConnection * conn, *selected_conn; int i, row, selected_row; selected_row = nmt_newt_listbox_get_active(priv->listbox); selected_conn = nmt_newt_listbox_get_active_key(priv->listbox); free_connections(list); connections = nm_client_get_connections(nm_client); for (i = 0; i < connections->len; i++) { conn = connections->pdata[i]; if (priv->connection_filter && !priv->connection_filter(list, conn, priv->connection_filter_data)) continue; g_signal_connect(conn, NM_CONNECTION_CHANGED, G_CALLBACK(rebuild_on_connection_changed), list); priv->connections = g_slist_prepend(priv->connections, g_object_ref(conn)); } priv->connections = g_slist_sort(priv->connections, sort_by_timestamp); g_object_notify(G_OBJECT(list), "connections"); g_object_notify(G_OBJECT(list), "num-connections"); nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->edit), priv->connections != NULL); nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->delete), priv->connections != NULL); nmt_newt_listbox_clear(priv->listbox); if (!priv->grouped) { /* Just add the connections in order */ for (iter = priv->connections, row = 0; iter; iter = iter->next, row++) { conn = iter->data; nmt_newt_listbox_append(priv->listbox, nm_connection_get_id(conn), conn); if (conn == selected_conn) selected_row = row; } if (selected_row >= row) selected_row = row - 1; nmt_newt_listbox_set_active(priv->listbox, selected_row); return; } types = nm_editor_utils_get_connection_type_list(); for (i = row = 0; types[i]; i++) { if (types[i]->setting_type == NM_TYPE_SETTING_VPN) { if (did_vpn) continue; did_vpn = TRUE; } did_header = FALSE; for (iter = priv->connections; iter; iter = iter->next) { NMSetting *setting; char * indented; conn = iter->data; setting = nm_connection_get_setting(conn, types[i]->setting_type); if (!setting) continue; if (!nm_connection_is_type(conn, nm_setting_get_name(setting))) continue; if (!did_header) { nmt_newt_listbox_append(priv->listbox, types[i]->name, NULL); if (row == selected_row) selected_row++; row++; did_header = TRUE; } indented = g_strdup_printf(" %s", nm_connection_get_id(conn)); nmt_newt_listbox_append(priv->listbox, indented, conn); g_free(indented); if (conn == selected_conn) selected_row = row; row++; } } if (selected_row >= row) selected_row = row - 1; nmt_newt_listbox_set_active(priv->listbox, selected_row); } static void rebuild_on_connections_changed(GObject *object, GParamSpec *pspec, gpointer list) { nmt_edit_connection_list_rebuild(list); } static void nmt_edit_connection_list_constructed(GObject *object) { NmtEditConnectionList * list = NMT_EDIT_CONNECTION_LIST(object); NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list); if (priv->extra) nmt_newt_button_box_add_widget_end(priv->buttons, priv->extra); g_signal_connect(nm_client, "notify::" NM_CLIENT_CONNECTIONS, G_CALLBACK(rebuild_on_connections_changed), list); nmt_edit_connection_list_rebuild(list); G_OBJECT_CLASS(nmt_edit_connection_list_parent_class)->constructed(object); } static void add_clicked(NmtNewtButton *button, gpointer list) { g_signal_emit(list, signals[ADD_CONNECTION], 0); } static void edit_clicked(NmtNewtButton *button, gpointer list) { NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list); NMConnection * connection; connection = nmt_newt_listbox_get_active_key(priv->listbox); g_return_if_fail(connection != NULL); g_signal_emit(list, signals[EDIT_CONNECTION], 0, connection); } static void delete_clicked(NmtNewtButton *button, gpointer list) { NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list); NMRemoteConnection * connection; connection = nmt_newt_listbox_get_active_key(priv->listbox); g_return_if_fail(connection != NULL); g_signal_emit(list, signals[REMOVE_CONNECTION], 0, connection); } static void listbox_activated(NmtNewtWidget *listbox, gpointer list) { NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list); edit_clicked(NMT_NEWT_BUTTON(priv->edit), list); } static void connection_saved(GObject *conn, GAsyncResult *result, gpointer user_data) { nm_remote_connection_save_finish(NM_REMOTE_CONNECTION(conn), result, NULL); } void nmt_edit_connection_list_recommit(NmtEditConnectionList *list) { NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list); NMConnection * conn; GSList * iter; for (iter = priv->connections; iter; iter = iter->next) { conn = iter->data; if (NM_IS_REMOTE_CONNECTION(conn) && (nm_remote_connection_get_unsaved(NM_REMOTE_CONNECTION(conn)) == FALSE)) { nm_remote_connection_save_async(NM_REMOTE_CONNECTION(conn), NULL, connection_saved, NULL); } } } static void nmt_edit_connection_list_finalize(GObject *object) { NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(object); free_connections(NMT_EDIT_CONNECTION_LIST(object)); g_clear_object(&priv->extra); G_OBJECT_CLASS(nmt_edit_connection_list_parent_class)->finalize(object); } static void nmt_edit_connection_list_set_property(GObject * object, guint prop_id, const GValue *value, GParamSpec * pspec) { NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(object); switch (prop_id) { case PROP_GROUPED: priv->grouped = g_value_get_boolean(value); break; case PROP_CONNECTION_FILTER: priv->connection_filter = g_value_get_pointer(value); break; case PROP_CONNECTION_FILTER_DATA: priv->connection_filter_data = g_value_get_pointer(value); break; case PROP_EXTRA_WIDGET: priv->extra = g_value_get_object(value); if (priv->extra) g_object_ref_sink(priv->extra); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void nmt_edit_connection_list_get_property(GObject * object, guint prop_id, GValue * value, GParamSpec *pspec) { NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(object); GPtrArray * connections; GSList * iter; switch (prop_id) { case PROP_GROUPED: g_value_set_boolean(value, priv->grouped); break; case PROP_CONNECTION_FILTER: g_value_set_pointer(value, priv->connection_filter); break; case PROP_CONNECTION_FILTER_DATA: g_value_set_pointer(value, priv->connection_filter_data); break; case PROP_EXTRA_WIDGET: g_value_set_object(value, priv->extra); break; case PROP_CONNECTIONS: connections = g_ptr_array_new_with_free_func(g_object_unref); for (iter = priv->connections; iter; iter = iter->next) g_ptr_array_add(connections, g_object_ref(iter->data)); g_value_take_boxed(value, connections); break; case PROP_NUM_CONNECTIONS: g_value_set_int(value, g_slist_length(priv->connections)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void nmt_edit_connection_list_class_init(NmtEditConnectionListClass *list_class) { GObjectClass *object_class = G_OBJECT_CLASS(list_class); g_type_class_add_private(list_class, sizeof(NmtEditConnectionListPrivate)); /* virtual methods */ object_class->constructed = nmt_edit_connection_list_constructed; object_class->set_property = nmt_edit_connection_list_set_property; object_class->get_property = nmt_edit_connection_list_get_property; object_class->finalize = nmt_edit_connection_list_finalize; /* signals */ /** * NmtEditConnectionList::add-connection: * @list: the #NmtEditConnectionList * * Emitted when the user clicks the list's "Add" button. */ signals[ADD_CONNECTION] = g_signal_new("add-connection", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(NmtEditConnectionListClass, add_connection), NULL, NULL, NULL, G_TYPE_NONE, 0); /** * NmtEditConnectionList::edit-connection: * @list: the #NmtEditConnectionList * @connection: the connection to edit * * Emitted when the user clicks the list's "Edit" button, or * hits "Return" on the listbox. */ signals[EDIT_CONNECTION] = g_signal_new("edit-connection", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(NmtEditConnectionListClass, edit_connection), NULL, NULL, NULL, G_TYPE_NONE, 1, NM_TYPE_CONNECTION); /** * NmtEditConnectionList::remove-connection: * @list: the #NmtEditConnectionList * @connection: the connection to remove * * Emitted when the user clicks the list's "Delete" button. */ signals[REMOVE_CONNECTION] = g_signal_new("remove-connection", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(NmtEditConnectionListClass, remove_connection), NULL, NULL, NULL, G_TYPE_NONE, 1, NM_TYPE_CONNECTION); /* properties */ /** * NmtEditConnectionList:grouped: * * If %TRUE, connections should be grouped by type, with headers * indicating the types (as in the main connection list). If %FALSE, * they will not be grouped (as in slave connection lists). */ g_object_class_install_property( object_class, PROP_GROUPED, g_param_spec_boolean("grouped", "", "", TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * NmtEditConnectionListFilter: * @list: the #NmtEditConnectionList * @connection: an #NMConnection * @user_data: the user data * * Decides whether @connection should be displayed in @list. * * Returns: %TRUE or %FALSE */ /** * NmtEditConnectionList:connection-filter: * * A callback function for filtering which connections appear in * the list. */ g_object_class_install_property( object_class, PROP_CONNECTION_FILTER, g_param_spec_pointer("connection-filter", "", "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * NmtEditConnectionList:connection-filter-data: * * Data for the #NmtEditConnectionList:connection-filter. */ g_object_class_install_property( object_class, PROP_CONNECTION_FILTER_DATA, g_param_spec_pointer("connection-filter-data", "", "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * NmtEditConnectionList:extra-widget: * * An extra button widget to display at the bottom of the button * box. */ g_object_class_install_property( object_class, PROP_EXTRA_WIDGET, g_param_spec_object("extra-widget", "", "", NMT_TYPE_NEWT_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * NmtEditConnectionList:connections: * * The list of connections in the widget. * * Element-Type: #NMConnection */ g_object_class_install_property(object_class, PROP_CONNECTIONS, g_param_spec_boxed("connections", "", "", G_TYPE_PTR_ARRAY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * NmtEditConnectionList:num-connections: * * The number of connections in the widget. */ g_object_class_install_property(object_class, PROP_NUM_CONNECTIONS, g_param_spec_int("num-connections", "", "", 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); }