diff options
| author | Michael Biebl <biebl@debian.org> | 2026-07-03 19:53:18 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2026-07-03 19:53:18 +0200 |
| commit | 537bfce2bda471c92caabd388589230200891509 (patch) | |
| tree | aedeccfaf0ba52c238ecf51fc009c0db5d4b60f0 /src/nmtui | |
| parent | 869e9027026cdbb15d4e4a6327ff41d2697858eb (diff) | |
New upstream version 1.58~rc1 upstream/1.58_rc1
Diffstat (limited to 'src/nmtui')
32 files changed, 1371 insertions, 218 deletions
diff --git a/src/nmtui/meson.build b/src/nmtui/meson.build index 40bb40b9..56812cfa 100644 --- a/src/nmtui/meson.build +++ b/src/nmtui/meson.build @@ -6,7 +6,6 @@ executable( 'nm-editor-bindings.c', 'nm-editor-utils.c', 'nmt-8021x-fields.c', - 'nmt-address-list.c', 'nmt-connect-connection-list.c', 'nmt-device-entry.c', 'nmt-edit-connection-list.c', @@ -16,6 +15,7 @@ executable( 'nmt-editor-page-device.c', 'nmt-editor-section.c', 'nmt-ip-entry.c', + 'nmt-list.c', 'nmt-mac-entry.c', 'nmt-mtu-entry.c', 'nmt-page-bond.c', @@ -42,6 +42,7 @@ executable( 'nmt-route-editor.c', 'nmt-route-entry.c', 'nmt-route-table.c', + 'nmt-wifi-qr-dialog.c', 'nmt-port-list.c', 'nmt-wireguard-peer-list.c', 'nmt-wireguard-peer-editor.c', @@ -56,6 +57,7 @@ executable( dependencies: [ libnm_dep, newt_dep, + slang_dep, glib_dep, ], link_with: [ diff --git a/src/nmtui/nm-editor-bindings.c b/src/nmtui/nm-editor-bindings.c index 360cf3ad..1408d263 100644 --- a/src/nmtui/nm-editor-bindings.c +++ b/src/nmtui/nm-editor-bindings.c @@ -195,7 +195,7 @@ ip_addresses_with_prefix_from_strv(GBinding *binding, * @source: the source object (eg, an #NMSettingIP4Config) * @source_property: the property on @source to bind (eg, * %NM_SETTING_IP4_CONFIG_ADDRESSES) - * @target: the target object (eg, an #NmtAddressList) + * @target: the target object (eg, an #NmtList) * @target_property: the property on @target to bind * (eg, "strings") * @flags: %GBindingFlags @@ -253,7 +253,7 @@ ip_addresses_check_and_copy(GBinding *binding, * @source: the source object (eg, an #NMSettingIP4Config) * @source_property: the property on @source to bind (eg, * %NM_SETTING_IP4_CONFIG_DNS) - * @target: the target object (eg, an #NmtAddressList) + * @target: the target object (eg, an #NmtList) * @target_property: the property on @target to bind * (eg, "strings") * @flags: %GBindingFlags diff --git a/src/nmtui/nmt-address-list.h b/src/nmtui/nmt-address-list.h deleted file mode 100644 index 01669fbd..00000000 --- a/src/nmtui/nmt-address-list.h +++ /dev/null @@ -1,43 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2013 Red Hat, Inc. - */ - -#ifndef NMT_ADDRESS_LIST_H -#define NMT_ADDRESS_LIST_H - -#include "nmt-widget-list.h" - -#define NMT_TYPE_ADDRESS_LIST (nmt_address_list_get_type()) -#define NMT_ADDRESS_LIST(obj) \ - (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_ADDRESS_LIST, NmtAddressList)) -#define NMT_ADDRESS_LIST_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_ADDRESS_LIST, NmtAddressListClass)) -#define NMT_IS_ADDRESS_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_ADDRESS_LIST)) -#define NMT_IS_ADDRESS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_ADDRESS_LIST)) -#define NMT_ADDRESS_LIST_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_ADDRESS_LIST, NmtAddressListClass)) - -typedef struct { - NmtWidgetList parent; - -} NmtAddressList; - -typedef struct { - NmtWidgetListClass parent; - -} NmtAddressListClass; - -GType nmt_address_list_get_type(void); - -typedef enum { - NMT_ADDRESS_LIST_IP4_WITH_PREFIX, - NMT_ADDRESS_LIST_IP4, - NMT_ADDRESS_LIST_IP6_WITH_PREFIX, - NMT_ADDRESS_LIST_IP6, - NMT_ADDRESS_LIST_HOSTNAME -} NmtAddressListType; - -NmtNewtWidget *nmt_address_list_new(NmtAddressListType list_type); - -#endif /* NMT_ADDRESS_LIST_H */ diff --git a/src/nmtui/nmt-connect-connection-list.c b/src/nmtui/nmt-connect-connection-list.c index 8e7dc807..1980c30d 100644 --- a/src/nmtui/nmt-connect-connection-list.c +++ b/src/nmtui/nmt-connect-connection-list.c @@ -17,6 +17,7 @@ #include "nmtui.h" #include "nmt-connect-connection-list.h" +#include "nmt-utils.h" #include "libnmc-base/nm-client-utils.h" G_DEFINE_TYPE(NmtConnectConnectionList, nmt_connect_connection_list, NMT_TYPE_NEWT_LISTBOX) @@ -47,6 +48,8 @@ typedef struct { typedef struct { GSList *nmt_devices; + char *filter_text; + int match_count; } NmtConnectConnectionListPrivate; /** @@ -444,6 +447,13 @@ connection_find_ac(NMConnection *conn, const GPtrArray *acs) return NULL; } +static gboolean +connection_matches(NmtConnectConnection *nmtconn, const char *needle) +{ + return nmt_utils_filter_match(nmtconn->name, needle) + || nmt_utils_filter_match(nmtconn->ssid, needle); +} + static void nmt_connect_connection_list_rebuild(NmtConnectConnectionList *list) { @@ -456,6 +466,8 @@ nmt_connect_connection_list_rebuild(NmtConnectConnectionList *list) GSList *nmt_devices, *diter, *citer; NmtConnectDevice *nmtdev; NmtConnectConnection *nmtconn; + gboolean did_group; + int n_matches = 0; g_slist_free_full(priv->nmt_devices, (GDestroyNotify) nmt_connect_device_free); priv->nmt_devices = NULL; @@ -486,18 +498,32 @@ nmt_connect_connection_list_rebuild(NmtConnectConnectionList *list) } } + did_group = FALSE; for (diter = nmt_devices; diter; diter = diter->next) { + gboolean dev_matches = FALSE; + nmtdev = diter->data; - if (nmtdev->conns) { - if (diter != nmt_devices) - nmt_newt_listbox_append(listbox, "", NULL); - nmt_newt_listbox_append(listbox, nmtdev->name, NULL); + for (citer = nmtdev->conns; citer; citer = citer->next) { + if (connection_matches(citer->data, priv->filter_text)) { + dev_matches = TRUE; + break; + } } + if (!dev_matches) + continue; + + if (did_group) + nmt_newt_listbox_append(listbox, "", NULL); + nmt_newt_listbox_append(listbox, nmtdev->name, NULL); + did_group = TRUE; for (citer = nmtdev->conns; citer; citer = citer->next) { nmtconn = citer->data; + if (!connection_matches(nmtconn, priv->filter_text)) + continue; + if (nmtconn->conn) nmtconn->active = connection_find_ac(nmtconn->conn, acs); if (nmtconn->active) { @@ -523,15 +549,36 @@ nmt_connect_connection_list_rebuild(NmtConnectConnectionList *list) nmt_newt_listbox_append(listbox, row, nmtconn); g_free(row); + n_matches++; } } priv->nmt_devices = nmt_devices; + priv->match_count = n_matches; g_object_notify(G_OBJECT(listbox), "active"); g_object_notify(G_OBJECT(listbox), "active-key"); } +void +nmt_connect_connection_list_set_filter_text(NmtConnectConnectionList *list, const char *text) +{ + NmtConnectConnectionListPrivate *priv = NMT_CONNECT_CONNECTION_LIST_GET_PRIVATE(list); + + if (nm_streq0(text, priv->filter_text)) + return; + + g_free(priv->filter_text); + priv->filter_text = g_strdup(text); + nmt_connect_connection_list_rebuild(list); +} + +int +nmt_connect_connection_list_get_match_count(NmtConnectConnectionList *list) +{ + return NMT_CONNECT_CONNECTION_LIST_GET_PRIVATE(list)->match_count; +} + static void rebuild_on_property_changed(GObject *object, GParamSpec *spec, gpointer list) { @@ -567,6 +614,7 @@ nmt_connect_connection_list_finalize(GObject *object) NmtConnectConnectionListPrivate *priv = NMT_CONNECT_CONNECTION_LIST_GET_PRIVATE(object); g_slist_free_full(priv->nmt_devices, (GDestroyNotify) nmt_connect_device_free); + nm_clear_g_free(&priv->filter_text); g_signal_handlers_disconnect_by_func(nm_client, G_CALLBACK(rebuild_on_property_changed), diff --git a/src/nmtui/nmt-connect-connection-list.h b/src/nmtui/nmt-connect-connection-list.h index bfc0c007..7b8d4a4b 100644 --- a/src/nmtui/nmt-connect-connection-list.h +++ b/src/nmtui/nmt-connect-connection-list.h @@ -40,6 +40,10 @@ GType nmt_connect_connection_list_get_type(void); NmtNewtWidget *nmt_connect_connection_list_new(void); +void nmt_connect_connection_list_set_filter_text(NmtConnectConnectionList *list, const char *text); + +int nmt_connect_connection_list_get_match_count(NmtConnectConnectionList *list); + gboolean nmt_connect_connection_list_get_connection(NmtConnectConnectionList *list, const char *identifier, NMConnection **connection, diff --git a/src/nmtui/nmt-device-entry.c b/src/nmtui/nmt-device-entry.c index 99cc43ad..2034aabd 100644 --- a/src/nmtui/nmt-device-entry.c +++ b/src/nmtui/nmt-device-entry.c @@ -14,11 +14,6 @@ * the entry recognizes the interface name or mac address typed in as * matching a known #NMDevice, then it will also display the other * property in parentheses. - * - * FIXME: #NmtDeviceEntry is currently an #NmtEditorGrid object, so that - * we can possibly eventually add a button to its "extra" field, that - * would pop up a form for selecting a device. But if we're not going - * to implement that then we should make it just an #NmtNewtEntry. */ #include "libnm-client-aux-extern/nm-default-client.h" @@ -49,6 +44,7 @@ typedef struct { NmtNewtWidget *button; gboolean updating; + gboolean show_select_button; } NmtDeviceEntryPrivate; enum { @@ -58,6 +54,7 @@ enum { PROP_HARDWARE_TYPE, PROP_INTERFACE_NAME, PROP_MAC_ADDRESS, + PROP_SHOW_SELECT_BUTTON, LAST_PROP }; @@ -68,16 +65,18 @@ enum { * @width: the width of the entry * @hardware_type: the type of #NMDevice to be selected, or * %G_TYPE_NONE if this is for a virtual device type. + * @show_select_button: whether to show select button or not. * * Creates a new #NmtDeviceEntry, for identifying a device of type * @hardware_type. If @hardware_type is %G_TYPE_NONE (and you do not * set a #NmtDeviceEntryDeviceFilter), then this will only allow - * specifying an interface name, not a hardware address. + * specifying an interface name, not a hardware address. @show_select_button + * will allow the user to select from a list of available devices of type @hardware_type. * * Returns: a new #NmtDeviceEntry. */ NmtNewtWidget * -nmt_device_entry_new(const char *label, int width, GType hardware_type) +nmt_device_entry_new(const char *label, int width, GType hardware_type, gboolean show_select_button) { return g_object_new(NMT_TYPE_DEVICE_ENTRY, "label", @@ -86,6 +85,8 @@ nmt_device_entry_new(const char *label, int width, GType hardware_type) width, "hardware-type", hardware_type, + "show-select-button", + show_select_button, NULL); } @@ -334,6 +335,133 @@ entry_text_changed(GObject *object, GParamSpec *pspec, gpointer deventry) } static void +device_selected(NmtNewtWidget *listbox, gpointer user_data) +{ + NMDevice *candidate = nmt_newt_listbox_get_active_key(NMT_NEWT_LISTBOX(listbox)); + NmtDeviceEntry *deventry = NMT_DEVICE_ENTRY(user_data); + const char *ifname; + + if (!candidate) + return; + + ifname = nm_device_get_iface(candidate); + if (!ifname) + return; + + if (nmt_device_entry_set_interface_name(deventry, ifname)) + update_entry(deventry); +} + +static int +compare_devices_by_name(gconstpointer a, gconstpointer b) +{ + NMDevice **dev_a = (NMDevice **) a; + NMDevice **dev_b = (NMDevice **) b; + + return nm_strcmp0(nm_device_get_iface(*dev_a), nm_device_get_iface(*dev_b)); +} + +static void +do_select_dialog(NmtNewtWidget *button, gpointer user_data) +{ + NmtDeviceEntry *deventry; + NmtDeviceEntryPrivate *priv; + gs_unref_object NmtNewtForm *popup_form = NULL; + NmtNewtWidget *listbox_widget; + NmtNewtForm *parent_form; + const GPtrArray *devices; + gs_unref_ptrarray GPtrArray *matching_devices = NULL; + const char *ifname, *driver; + int i; + int entry_x, entry_y; + int window_x, window_y; + int popup_x, popup_y; + int list_w, list_h; + newtComponent entry_component; + + deventry = NMT_DEVICE_ENTRY(user_data); + priv = NMT_DEVICE_ENTRY_GET_PRIVATE(deventry); + parent_form = nmt_newt_widget_get_form(NMT_NEWT_WIDGET(deventry)); + if (!parent_form) + return; + + matching_devices = g_ptr_array_new(); + + entry_component = nmt_newt_component_get_component(NMT_NEWT_COMPONENT(priv->entry)); + newtComponentGetPosition(entry_component, &entry_x, &entry_y); + g_object_get(parent_form, "x", &window_x, "y", &window_y, NULL); + + listbox_widget = nmt_newt_listbox_new(5, NMT_NEWT_LISTBOX_SCROLL); + nmt_newt_widget_set_exit_on_activate(listbox_widget, TRUE); + + nmt_newt_widget_set_padding(listbox_widget, 1, 0, 1, 0); + + devices = nm_client_get_devices(nm_client); + for (i = 0; i < devices->len; i++) { + NMDevice *candidate = devices->pdata[i]; + + if (!G_TYPE_CHECK_INSTANCE_TYPE(candidate, priv->hardware_type)) + continue; + + if (priv->device_filter + && !priv->device_filter(deventry, candidate, priv->device_filter_data)) + continue; + + ifname = nm_device_get_iface(candidate); + if (!ifname) + continue; + + g_ptr_array_add(matching_devices, candidate); + } + + if (matching_devices->len == 0) { + nmt_newt_message_dialog(_("No devices available")); + return; + } + + g_ptr_array_sort(matching_devices, compare_devices_by_name); + + for (i = 0; i < matching_devices->len; i++) { + gs_free char *display_text = NULL; + NMDevice *candidate = matching_devices->pdata[i]; + + ifname = nm_device_get_iface(candidate); + + driver = nm_device_get_driver(candidate); + + if (driver && driver[0] != '\0') { + display_text = g_strdup_printf("%s (%s)", ifname, driver); + } else { + display_text = g_strdup(ifname); + } + + nmt_newt_listbox_append(NMT_NEWT_LISTBOX(listbox_widget), display_text, candidate); + } + + g_signal_connect(listbox_widget, "activated", G_CALLBACK(device_selected), deventry); + + nmt_newt_widget_size_request(listbox_widget, &list_w, &list_h); + popup_x = window_x + entry_x + 1; + popup_y = window_y + entry_y + 1; + + popup_form = g_object_new(NMT_TYPE_NEWT_FORM, + "x", + popup_x, + "y", + popup_y, + "width", + list_w, + "height", + list_h, + "padding", + 0, + NULL); + + nmt_newt_form_set_content(popup_form, listbox_widget); + nmt_newt_form_show(popup_form); +} + +static void nmt_device_entry_init(NmtDeviceEntry *deventry) { NmtDeviceEntryPrivate *priv = NMT_DEVICE_ENTRY_GET_PRIVATE(deventry); @@ -346,11 +474,9 @@ nmt_device_entry_init(NmtDeviceEntry *deventry) nmt_newt_entry_set_validator(priv->entry, device_entry_validate, deventry); g_signal_connect(priv->entry, "notify::text", G_CALLBACK(entry_text_changed), deventry); -#if 0 - priv->button = nmt_newt_button_new (_("Select...")); - g_signal_connect (priv->button, "clicked", - G_CALLBACK (do_select_dialog), deventry); -#endif + priv->button = nmt_newt_button_new(_("Select...")); + nmt_newt_widget_set_visible(priv->button, FALSE); + g_signal_connect(priv->button, "clicked", G_CALLBACK(do_select_dialog), deventry); } static void @@ -361,7 +487,7 @@ nmt_device_entry_constructed(GObject *object) nmt_editor_grid_append(NMT_EDITOR_GRID(object), priv->label, NMT_NEWT_WIDGET(priv->entry), - NULL); + priv->button); G_OBJECT_CLASS(nmt_device_entry_parent_class)->constructed(object); } @@ -446,6 +572,10 @@ nmt_device_entry_set_property(GObject *object, if (nmt_device_entry_set_mac_address(deventry, mac_address)) update_entry(deventry); break; + case PROP_SHOW_SELECT_BUTTON: + priv->show_select_button = g_value_get_boolean(value); + nmt_newt_widget_set_visible(priv->button, priv->show_select_button); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; @@ -473,6 +603,9 @@ nmt_device_entry_get_property(GObject *object, guint prop_id, GValue *value, GPa case PROP_MAC_ADDRESS: g_value_set_string(value, priv->mac_address); break; + case PROP_SHOW_SELECT_BUTTON: + g_value_set_boolean(value, priv->show_select_button); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; @@ -554,4 +687,18 @@ nmt_device_entry_class_init(NmtDeviceEntryClass *deventry_class) "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * NmtDeviceEntry:show-select-button: + * + * Display select button to select device from available devices. + */ + g_object_class_install_property( + object_class, + PROP_SHOW_SELECT_BUTTON, + g_param_spec_boolean("show-select-button", + "", + "", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } diff --git a/src/nmtui/nmt-device-entry.h b/src/nmtui/nmt-device-entry.h index 6265ce01..21be28c7 100644 --- a/src/nmtui/nmt-device-entry.h +++ b/src/nmtui/nmt-device-entry.h @@ -30,7 +30,10 @@ typedef struct { GType nmt_device_entry_get_type(void); -NmtNewtWidget *nmt_device_entry_new(const char *label, int width, GType hardware_type); +NmtNewtWidget *nmt_device_entry_new(const char *label, + int width, + GType hardware_type, + gboolean show_select_button); typedef gboolean (*NmtDeviceEntryDeviceFilter)(NmtDeviceEntry *deventry, NMDevice *device, diff --git a/src/nmtui/nmt-edit-connection-list.c b/src/nmtui/nmt-edit-connection-list.c index 1ea44007..45e6eb44 100644 --- a/src/nmtui/nmt-edit-connection-list.c +++ b/src/nmtui/nmt-edit-connection-list.c @@ -17,6 +17,7 @@ #include "nmtui-edit.h" #include "nmt-edit-connection-list.h" #include "nmt-editor.h" +#include "nmt-utils.h" #include "nm-editor-utils.h" @@ -35,9 +36,14 @@ typedef struct { NmtNewtListbox *listbox; NmtNewtButtonBox *buttons; + NmtSearch *search; + char *filter_text; + int match_count; + NmtNewtWidget *add; NmtNewtWidget *edit; NmtNewtWidget *delete; + NmtNewtWidget *share; NmtNewtWidget *extra; } NmtEditConnectionListPrivate; @@ -58,6 +64,7 @@ enum { ADD_CONNECTION, EDIT_CONNECTION, REMOVE_CONNECTION, + SHARE_CONNECTION, LAST_SIGNAL }; @@ -67,14 +74,19 @@ 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 share_clicked(NmtNewtButton *button, gpointer list); static void listbox_activated(NmtNewtWidget *listbox, gpointer list); +static void edit_search_apply(gpointer list, const char *text); +static int edit_search_count(gpointer list); +static void update_share_sensitive(NmtEditConnectionList *list); static void nmt_edit_connection_list_init(NmtEditConnectionList *list) { NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list); - NmtNewtWidget *listbox, *buttons; + NmtNewtWidget *listbox, *buttons, *search_row, *search_label, *search; NmtNewtGrid *grid = NMT_NEWT_GRID(list); + NmtNewtGrid *search_grid; listbox = g_object_new(NMT_TYPE_NEWT_LISTBOX, "flags", @@ -89,6 +101,32 @@ nmt_edit_connection_list_init(NmtEditConnectionList *list) 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); + g_signal_connect_swapped(priv->listbox, + "notify::active", + G_CALLBACK(update_share_sensitive), + list); + + /* Search row below the listbox. The row is always present so revealing the + * entry does not resize the form; vim-style '/' shows the entry, and once a + * filter is applied the label reports it ("Matching '...' (N)"). */ + search_row = nmt_newt_grid_new(); + search_grid = NMT_NEWT_GRID(search_row); + nmt_newt_grid_add(grid, search_row, 0, 1); + nmt_newt_grid_set_flags(grid, search_row, NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_EXPAND_X); + + search_label = nmt_newt_label_new(""); + nmt_newt_grid_add(search_grid, search_label, 0, 0); + + search = nmt_newt_entry_new(NMT_SEARCH_ENTRY_WIDTH, 0); + nmt_newt_grid_add(search_grid, search, 1, 0); + nmt_newt_widget_set_padding(search, 1, 0, 0, 0); + + priv->search = nmt_search_new(NMT_NEWT_ENTRY(search), + NMT_NEWT_LABEL(search_label), + NMT_NEWT_WIDGET(priv->listbox), + edit_search_apply, + edit_search_count, + list); buttons = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_VERTICAL); priv->buttons = NMT_NEWT_BUTTON_BOX(buttons); @@ -106,6 +144,9 @@ nmt_edit_connection_list_init(NmtEditConnectionList *list) priv->delete = nmt_newt_button_box_add_start(priv->buttons, _("Delete")); g_signal_connect(priv->delete, "clicked", G_CALLBACK(delete_clicked), list); + + priv->share = nmt_newt_button_box_add_start(priv->buttons, _("Share QR...")); + g_signal_connect(priv->share, "clicked", G_CALLBACK(share_clicked), list); } static int @@ -157,7 +198,7 @@ nmt_edit_connection_list_rebuild(NmtEditConnectionList *list) gboolean did_header = FALSE, did_vpn = FALSE, did_any = FALSE; NMEditorConnectionTypeData **types; NMConnection *conn, *selected_conn; - int i, row, selected_row; + int i, row, selected_row, n_matches = 0; selected_row = nmt_newt_listbox_get_active(priv->listbox); selected_conn = nmt_newt_listbox_get_active_key(priv->listbox); @@ -185,17 +226,21 @@ nmt_edit_connection_list_rebuild(NmtEditConnectionList *list) if (!priv->grouped) { /* Just add the connections in order */ - for (iter = priv->connections, row = 0; iter; iter = iter->next, row++) { + for (iter = priv->connections, row = 0; iter; iter = iter->next) { conn = iter->data; + if (!nmt_utils_filter_match(nm_connection_get_id(conn), priv->filter_text)) + continue; nmt_newt_listbox_append(priv->listbox, nm_connection_get_id(conn), conn); if (conn == selected_conn) selected_row = row; + row++; + n_matches++; } if (selected_row >= row) selected_row = row - 1; nmt_newt_listbox_set_active(priv->listbox, selected_row); - did_any = !!priv->connections; + did_any = n_matches > 0; goto done; } @@ -220,6 +265,8 @@ nmt_edit_connection_list_rebuild(NmtEditConnectionList *list) continue; if (!nm_connection_is_type(conn, nm_setting_get_name(setting))) continue; + if (!nmt_utils_filter_match(nm_connection_get_id(conn), priv->filter_text)) + continue; if (!did_header) { nmt_newt_listbox_append(priv->listbox, types[i]->name, NULL); @@ -240,6 +287,7 @@ nmt_edit_connection_list_rebuild(NmtEditConnectionList *list) if (conn == selected_conn) selected_row = row; row++; + n_matches++; } } @@ -248,8 +296,12 @@ nmt_edit_connection_list_rebuild(NmtEditConnectionList *list) nmt_newt_listbox_set_active(priv->listbox, selected_row); done: + priv->match_count = n_matches; + if (priv->search) + nmt_search_update_label(priv->search); nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->edit), did_any); nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->delete), did_any); + update_share_sensitive(list); } static void @@ -308,6 +360,29 @@ delete_clicked(NmtNewtButton *button, gpointer list) } static void +share_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[SHARE_CONNECTION], 0, connection); +} + +static void +update_share_sensitive(NmtEditConnectionList *list) +{ + NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list); + NMConnection *connection; + + connection = nmt_newt_listbox_get_active_key(priv->listbox); + nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->share), + connection && nm_connection_get_setting_wireless(connection)); +} + +static void listbox_activated(NmtNewtWidget *listbox, gpointer list) { NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list); @@ -316,6 +391,31 @@ listbox_activated(NmtNewtWidget *listbox, gpointer list) } static void +edit_search_apply(gpointer list, const char *text) +{ + NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list); + + if (nm_streq0(text, priv->filter_text)) + return; + + g_free(priv->filter_text); + priv->filter_text = g_strdup(text); + nmt_edit_connection_list_rebuild(list); +} + +static int +edit_search_count(gpointer list) +{ + return NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list)->match_count; +} + +void +nmt_edit_connection_list_bind_search(NmtEditConnectionList *list, NmtNewtForm *form) +{ + nmt_search_bind_form(NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list)->search, form); +} + +static void connection_saved(GObject *conn, GAsyncResult *result, gpointer user_data) { nm_remote_connection_save_finish(NM_REMOTE_CONNECTION(conn), result, NULL); @@ -348,6 +448,8 @@ nmt_edit_connection_list_finalize(GObject *object) free_connections(NMT_EDIT_CONNECTION_LIST(object)); g_clear_object(&priv->extra); + nm_clear_pointer(&priv->search, g_free); + nm_clear_g_free(&priv->filter_text); G_OBJECT_CLASS(nmt_edit_connection_list_parent_class)->finalize(object); } @@ -490,6 +592,25 @@ nmt_edit_connection_list_class_init(NmtEditConnectionListClass *list_class) 1, NM_TYPE_CONNECTION); + /** + * NmtEditConnectionList::share-connection: + * @list: the #NmtEditConnectionList + * @connection: the connection to share + * + * Emitted when the user clicks the list's "Share QR..." button. + */ + signals[SHARE_CONNECTION] = + g_signal_new("share-connection", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(NmtEditConnectionListClass, share_connection), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + NM_TYPE_CONNECTION); + /* properties */ /** diff --git a/src/nmtui/nmt-edit-connection-list.h b/src/nmtui/nmt-edit-connection-list.h index 0d23f8b1..1ae91d5f 100644 --- a/src/nmtui/nmt-edit-connection-list.h +++ b/src/nmtui/nmt-edit-connection-list.h @@ -32,6 +32,7 @@ typedef struct { void (*add_connection)(NmtEditConnectionList *list); void (*edit_connection)(NmtEditConnectionList *list, NMConnection *connection); void (*remove_connection)(NmtEditConnectionList *list, NMRemoteConnection *connection); + void (*share_connection)(NmtEditConnectionList *list, NMConnection *connection); } NmtEditConnectionListClass; GType nmt_edit_connection_list_get_type(void); @@ -42,4 +43,6 @@ typedef gboolean (*NmtEditConnectionListFilter)(NmtEditConnectionList *list, void nmt_edit_connection_list_recommit(NmtEditConnectionList *list); +void nmt_edit_connection_list_bind_search(NmtEditConnectionList *list, NmtNewtForm *form); + #endif /* NMT_EDIT_CONNECTION_LIST_H */ diff --git a/src/nmtui/nmt-editor-grid.c b/src/nmtui/nmt-editor-grid.c index bdcc02e2..21d362c4 100644 --- a/src/nmtui/nmt-editor-grid.c +++ b/src/nmtui/nmt-editor-grid.c @@ -244,8 +244,10 @@ nmt_editor_grid_get_components(NmtNewtWidget *widget) if (rows[i].extra) { child_cos = nmt_newt_widget_get_components(rows[i].extra); - for (c = 0; child_cos[c]; c++) - g_ptr_array_add(cos, child_cos[c]); + if (child_cos) { + for (c = 0; child_cos[c]; c++) + g_ptr_array_add(cos, child_cos[c]); + } g_free(child_cos); } } diff --git a/src/nmtui/nmt-editor.c b/src/nmtui/nmt-editor.c index 474d08c2..b9f25ad0 100644 --- a/src/nmtui/nmt-editor.c +++ b/src/nmtui/nmt-editor.c @@ -14,6 +14,7 @@ #include "nmt-editor.h" +#include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "nm-utils.h" #include "nmtui.h" @@ -153,10 +154,16 @@ save_connection_and_exit(NmtNewtButton *button, gpointer user_data) NmtEditor *editor = user_data; NmtEditorPrivate *priv = NMT_EDITOR_GET_PRIVATE(editor); NmtSyncOp op; - GError *error = NULL; + GError *error = NULL; + gs_free char *gw_warning = NULL; nm_connection_replace_settings_from_connection(priv->orig_connection, priv->edit_connection); + gw_warning = nm_connection_get_unreachable_gateways_warning(priv->orig_connection, TRUE); + if (gw_warning) { + nmt_newt_message_dialog(_("Warning: %s"), gw_warning); + } + nmt_sync_op_init(&op); if (NM_IS_REMOTE_CONNECTION(priv->orig_connection)) { nm_remote_connection_commit_changes_async(NM_REMOTE_CONNECTION(priv->orig_connection), @@ -194,19 +201,6 @@ save_connection_and_exit(NmtNewtButton *button, gpointer user_data) nmt_newt_form_quit(NMT_NEWT_FORM(editor)); } -static void -got_secrets(GObject *object, GAsyncResult *result, gpointer op) -{ - GVariant *secrets; - GError *error = NULL; - - secrets = nm_remote_connection_get_secrets_finish(NM_REMOTE_CONNECTION(object), result, &error); - if (secrets) - g_variant_ref(secrets); - nmt_sync_op_complete_pointer(op, secrets, error); - g_clear_error(&error); -} - static NMConnection * build_edit_connection(NMConnection *orig_connection) { @@ -214,7 +208,6 @@ build_edit_connection(NMConnection *orig_connection) GVariant *settings, *secrets; GVariantIter iter; const char *setting_name; - NmtSyncOp op; edit_connection = nm_simple_connection_new_clone(orig_connection); @@ -227,14 +220,8 @@ build_edit_connection(NMConnection *orig_connection) if (!nm_meta_setting_info_editor_has_secrets( nm_meta_setting_info_editor_find_by_name(setting_name, FALSE))) continue; - nmt_sync_op_init(&op); - nm_remote_connection_get_secrets_async(NM_REMOTE_CONNECTION(orig_connection), - setting_name, - NULL, - got_secrets, - &op); /* FIXME: error handling */ - secrets = nmt_sync_op_wait_pointer(&op, NULL); + secrets = nmt_sync_get_secrets(NM_REMOTE_CONNECTION(orig_connection), setting_name, NULL); if (secrets) { (void) nm_connection_update_secrets(edit_connection, setting_name, secrets, NULL); g_variant_unref(secrets); @@ -310,6 +297,7 @@ nmt_editor_constructed(GObject *object) GType hardware_type; const char *port_type; NmtEditorPage *page; + gboolean show_select_button; if (G_OBJECT_CLASS(nmt_editor_parent_class)->constructed) G_OBJECT_CLASS(nmt_editor_parent_class)->constructed(object); @@ -333,10 +321,13 @@ nmt_editor_constructed(GObject *object) G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); nmt_editor_grid_append(grid, _("Profile name"), widget, NULL); - if (priv->type_data->virtual) - hardware_type = G_TYPE_NONE; - else - hardware_type = priv->type_data->device_type; + if (priv->type_data->virtual) { + hardware_type = G_TYPE_NONE; + show_select_button = FALSE; + } else { + hardware_type = priv->type_data->device_type; + show_select_button = TRUE; + } if (nm_connection_is_type(priv->edit_connection, NM_SETTING_LOOPBACK_SETTING_NAME)) { g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "lo", NULL); @@ -349,7 +340,7 @@ nmt_editor_constructed(GObject *object) else deventry_label = _("Device"); - widget = nmt_device_entry_new(deventry_label, 40, hardware_type); + widget = nmt_device_entry_new(deventry_label, 40, hardware_type, show_select_button); nmt_editor_grid_append(grid, NULL, widget, NULL); deventry = NMT_DEVICE_ENTRY(widget); g_object_bind_property(s_con, diff --git a/src/nmtui/nmt-address-list.c b/src/nmtui/nmt-list.c index ff4bf1a4..3058638a 100644 --- a/src/nmtui/nmt-address-list.c +++ b/src/nmtui/nmt-list.c @@ -4,20 +4,19 @@ */ /** - * SECTION:nmt-address-list - * @short_description: An editable list of IP addresses or hostnames + * SECTION:nmt-list + * @short_description: An editable list of IP addresses, hostnames, or key=value pairs * - * #NmtAddressList is a subclass of #NmtWidgetList that contains - * entries displaying IP addresses, address/prefix strings, or - * hostnames. This is designed for binding its #NmtAddressList:strings - * property to an appropriate #NMSettingIP4Config or - * #NMSettingIP6Config property via one of the nm-editor-bindings - * functions. + * #NmtList is a subclass of #NmtWidgetList that contains + * entries displaying IP addresses, address/prefix strings, + * hostnames, or key=value pairs. This is designed for binding its + * #NmtList:strings property to an appropriate property via one + * of the nm-editor-bindings functions. */ #include "libnm-client-aux-extern/nm-default-client.h" -#include "nmt-address-list.h" +#include "nmt-list.h" #include <arpa/inet.h> #include <netinet/in.h> @@ -25,15 +24,14 @@ #include "nmt-ip-entry.h" -G_DEFINE_TYPE(NmtAddressList, nmt_address_list, NMT_TYPE_WIDGET_LIST) +G_DEFINE_TYPE(NmtList, nmt_list, NMT_TYPE_WIDGET_LIST) -#define NMT_ADDRESS_LIST_GET_PRIVATE(o) \ - (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_ADDRESS_LIST, NmtAddressListPrivate)) +#define NMT_LIST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_LIST, NmtListPrivate)) typedef struct { - NmtAddressListType list_type; - char **strings; -} NmtAddressListPrivate; + NmtListType list_type; + char **strings; +} NmtListPrivate; enum { PROP_0, @@ -44,32 +42,32 @@ enum { }; /** - * NmtAddressListType: - * @NMT_ADDRESS_LIST_IP4_WITH_PREFIX: IPv4 address/prefix strings - * @NMT_ADDRESS_LIST_IP4: IPv4 addresses - * @NMT_ADDRESS_LIST_IP6_WITH_PREFIX: IPv6 address/prefix strings - * @NMT_ADDRESS_LIST_IP6: IPv6 addresses - * @NMT_ADDRESS_LIST_HOSTNAME: hostnames + * NmtListType: + * @NMT_LIST_IP4_WITH_PREFIX: IPv4 address/prefix strings + * @NMT_LIST_IP4: IPv4 addresses + * @NMT_LIST_IP6_WITH_PREFIX: IPv6 address/prefix strings + * @NMT_LIST_IP6: IPv6 addresses + * @NMT_LIST_HOSTNAME: hostnames * - * The type of address in an #NmtAddressList + * The type of address in an #NmtList */ /** - * nmt_address_list_new: + * nmt_list_new: * @list_type: the type of address the list will contain * - * Creates a new #NmtAddressList + * Creates a new #NmtList * - * Returns: a new #NmtAddressList + * Returns: a new #NmtList */ NmtNewtWidget * -nmt_address_list_new(NmtAddressListType list_type) +nmt_list_new(NmtListType list_type) { - return g_object_new(NMT_TYPE_ADDRESS_LIST, "list-type", list_type, NULL); + return g_object_new(NMT_TYPE_LIST, "list-type", list_type, NULL); } static void -nmt_address_list_init(NmtAddressList *list) +nmt_list_init(NmtList *list) {} static gboolean @@ -95,9 +93,9 @@ strings_transform_from_entry(GBinding *binding, GValue *target_value, gpointer user_data) { - NmtAddressList *list = NMT_ADDRESS_LIST(g_binding_get_source(binding)); - NmtAddressListPrivate *priv = NMT_ADDRESS_LIST_GET_PRIVATE(list); - int n = GPOINTER_TO_INT(user_data); + NmtList *list = NMT_LIST(g_binding_get_source(binding)); + NmtListPrivate *priv = NMT_LIST_GET_PRIVATE(list); + int n = GPOINTER_TO_INT(user_data); if (n >= g_strv_length(priv->strings)) return FALSE; @@ -112,26 +110,41 @@ strings_transform_from_entry(GBinding *binding, static gboolean hostname_filter(NmtNewtEntry *entry, const char *text, int ch, int position, gpointer user_data) { - return g_ascii_isalnum(ch) || ch == '.' || ch == '-'; + return g_ascii_isalnum(ch) || ch == '.' || ch == '-' || ch == '~'; +} + +static gboolean +key_value_validate(NmtNewtEntry *entry, const char *text, gpointer user_data) +{ + const char *val; + + if (!text || !text[0]) + return TRUE; + + val = strchr(text, '='); + return val && val != text && val[1]; } static NmtNewtWidget * -nmt_address_list_create_widget(NmtWidgetList *list, int num) +nmt_list_create_widget(NmtWidgetList *list, int num) { - NmtAddressListPrivate *priv = NMT_ADDRESS_LIST_GET_PRIVATE(list); - NmtNewtWidget *entry; + NmtListPrivate *priv = NMT_LIST_GET_PRIVATE(list); + NmtNewtWidget *entry; - if (priv->list_type == NMT_ADDRESS_LIST_IP4_WITH_PREFIX) { + if (priv->list_type == NMT_LIST_IP4_WITH_PREFIX) { entry = nmt_ip_entry_new(25, AF_INET, TRUE, FALSE); - } else if (priv->list_type == NMT_ADDRESS_LIST_IP4) { + } else if (priv->list_type == NMT_LIST_IP4) { entry = nmt_ip_entry_new(25, AF_INET, FALSE, FALSE); - } else if (priv->list_type == NMT_ADDRESS_LIST_IP6_WITH_PREFIX) { + } else if (priv->list_type == NMT_LIST_IP6_WITH_PREFIX) { entry = nmt_ip_entry_new(25, AF_INET6, TRUE, FALSE); - } else if (priv->list_type == NMT_ADDRESS_LIST_IP6) { + } else if (priv->list_type == NMT_LIST_IP6) { entry = nmt_ip_entry_new(25, AF_INET6, FALSE, FALSE); - } else if (priv->list_type == NMT_ADDRESS_LIST_HOSTNAME) { + } else if (priv->list_type == NMT_LIST_HOSTNAME) { entry = nmt_newt_entry_new(25, NMT_NEWT_ENTRY_NONEMPTY); nmt_newt_entry_set_filter(NMT_NEWT_ENTRY(entry), hostname_filter, list); + } else if (priv->list_type == NMT_LIST_KEY_VALUE) { + entry = nmt_newt_entry_new(40, 0); + nmt_newt_entry_set_validator(NMT_NEWT_ENTRY(entry), key_value_validate, NULL); } else { g_return_val_if_reached(NULL); } @@ -150,10 +163,10 @@ nmt_address_list_create_widget(NmtWidgetList *list, int num) } static void -nmt_address_list_add_clicked(NmtWidgetList *list) +nmt_list_add_clicked(NmtWidgetList *list) { - NmtAddressListPrivate *priv = NMT_ADDRESS_LIST_GET_PRIVATE(list); - int len; + NmtListPrivate *priv = NMT_LIST_GET_PRIVATE(list); + int len; len = priv->strings ? g_strv_length(priv->strings) : 0; priv->strings = g_renew(char *, priv->strings, len + 2); @@ -165,10 +178,10 @@ nmt_address_list_add_clicked(NmtWidgetList *list) } static void -nmt_address_list_remove_clicked(NmtWidgetList *list, int num) +nmt_list_remove_clicked(NmtWidgetList *list, int num) { - NmtAddressListPrivate *priv = NMT_ADDRESS_LIST_GET_PRIVATE(list); - int len; + NmtListPrivate *priv = NMT_LIST_GET_PRIVATE(list); + int len; len = g_strv_length(priv->strings); g_free(priv->strings[num]); @@ -179,12 +192,9 @@ nmt_address_list_remove_clicked(NmtWidgetList *list, int num) } static void -nmt_address_list_set_property(GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) +nmt_list_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { - NmtAddressListPrivate *priv = NMT_ADDRESS_LIST_GET_PRIVATE(object); + NmtListPrivate *priv = NMT_LIST_GET_PRIVATE(object); switch (prop_id) { case PROP_LIST_TYPE: @@ -204,9 +214,9 @@ nmt_address_list_set_property(GObject *object, } static void -nmt_address_list_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +nmt_list_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { - NmtAddressListPrivate *priv = NMT_ADDRESS_LIST_GET_PRIVATE(object); + NmtListPrivate *priv = NMT_LIST_GET_PRIVATE(object); switch (prop_id) { case PROP_LIST_TYPE: @@ -222,23 +232,23 @@ nmt_address_list_get_property(GObject *object, guint prop_id, GValue *value, GPa } static void -nmt_address_list_class_init(NmtAddressListClass *list_class) +nmt_list_class_init(NmtListClass *list_class) { GObjectClass *object_class = G_OBJECT_CLASS(list_class); NmtWidgetListClass *widget_list_class = NMT_WIDGET_LIST_CLASS(list_class); - g_type_class_add_private(list_class, sizeof(NmtAddressListPrivate)); + g_type_class_add_private(list_class, sizeof(NmtListPrivate)); /* virtual methods */ - object_class->set_property = nmt_address_list_set_property; - object_class->get_property = nmt_address_list_get_property; + object_class->set_property = nmt_list_set_property; + object_class->get_property = nmt_list_get_property; - widget_list_class->create_widget = nmt_address_list_create_widget; - widget_list_class->add_clicked = nmt_address_list_add_clicked; - widget_list_class->remove_clicked = nmt_address_list_remove_clicked; + widget_list_class->create_widget = nmt_list_create_widget; + widget_list_class->add_clicked = nmt_list_add_clicked; + widget_list_class->remove_clicked = nmt_list_remove_clicked; /** - * NmtAddressList:list-type: + * NmtList:list-type: * * The type of address the list holds. */ @@ -253,7 +263,7 @@ nmt_address_list_class_init(NmtAddressListClass *list_class) 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** - * NmtAddressList:strings: + * NmtList:strings: * * The strings in the list's entries. */ diff --git a/src/nmtui/nmt-list.h b/src/nmtui/nmt-list.h new file mode 100644 index 00000000..65def938 --- /dev/null +++ b/src/nmtui/nmt-list.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#ifndef NMT_LIST_H +#define NMT_LIST_H + +#include "nmt-widget-list.h" + +#define NMT_TYPE_LIST (nmt_list_get_type()) +#define NMT_LIST(obj) (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_LIST, NmtList)) +#define NMT_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_LIST, NmtListClass)) +#define NMT_IS_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_LIST)) +#define NMT_IS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_LIST)) +#define NMT_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_LIST, NmtListClass)) + +typedef struct { + NmtWidgetList parent; + +} NmtList; + +typedef struct { + NmtWidgetListClass parent; + +} NmtListClass; + +GType nmt_list_get_type(void); + +typedef enum { + NMT_LIST_IP4_WITH_PREFIX, + NMT_LIST_IP4, + NMT_LIST_IP6_WITH_PREFIX, + NMT_LIST_IP6, + NMT_LIST_HOSTNAME, + NMT_LIST_KEY_VALUE +} NmtListType; + +NmtNewtWidget *nmt_list_new(NmtListType list_type); + +#endif /* NMT_LIST_H */ diff --git a/src/nmtui/nmt-page-bond.c b/src/nmtui/nmt-page-bond.c index f74bac27..eeea0b0b 100644 --- a/src/nmtui/nmt-page-bond.c +++ b/src/nmtui/nmt-page-bond.c @@ -21,7 +21,7 @@ #include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "nmt-mac-entry.h" -#include "nmt-address-list.h" +#include "nmt-list.h" #include "nmt-port-list.h" G_DEFINE_TYPE(NmtPageBond, nmt_page_bond, NMT_TYPE_EDITOR_PAGE_DEVICE) @@ -41,14 +41,15 @@ typedef struct { /* Note: when adding new options to the UI also ensure they are * initialized in bond_connection_setup_func() */ - NmtNewtPopup *mode; - NmtNewtEntry *primary; - NmtNewtPopup *monitoring; - NmtNewtEntry *miimon; - NmtNewtEntry *updelay; - NmtNewtEntry *downdelay; - NmtNewtEntry *arp_interval; - NmtAddressList *arp_ip_target; + NmtNewtPopup *mode; + NmtNewtEntry *primary; + NmtNewtPopup *monitoring; + NmtNewtEntry *miimon; + NmtNewtEntry *updelay; + NmtNewtEntry *downdelay; + NmtNewtEntry *arp_interval; + NmtList *arp_ip_target; + NmtList *other_options; NmtPageBondMonitoringMode monitoring_mode; @@ -63,6 +64,45 @@ static void arp_ip_target_widget_changed(GObject *object, GParamSpec *pspec, gpo /*****************************************************************************/ +static gboolean +_is_other_option(const char *option) +{ + return !NM_IN_STRSET(option, + NM_SETTING_BOND_OPTION_MODE, + NM_SETTING_BOND_OPTION_PRIMARY, + NM_SETTING_BOND_OPTION_MIIMON, + NM_SETTING_BOND_OPTION_UPDELAY, + NM_SETTING_BOND_OPTION_DOWNDELAY, + NM_SETTING_BOND_OPTION_ARP_INTERVAL, + NM_SETTING_BOND_OPTION_ARP_IP_TARGET); +} + +static void +_bond_update_other_options(NMSettingBond *s_bond, NmtList *list) +{ + gs_unref_ptrarray GPtrArray *arr = g_ptr_array_new_with_free_func(g_free); + guint num_opts = nm_setting_bond_get_num_options(s_bond); + guint i; + + for (i = 0; i < num_opts; i++) { + const char *opt_name; + const char *opt_value; + gboolean ret; + + ret = nm_setting_bond_get_option(s_bond, i, &opt_name, &opt_value); + nm_assert(ret); + + if (_is_other_option(opt_name)) { + g_ptr_array_add(arr, g_strdup_printf("%s=%s", opt_name, opt_value)); + } + } + + g_ptr_array_add(arr, NULL); + g_object_set(G_OBJECT(list), "strings", arr->pdata, NULL); +} + +/*****************************************************************************/ + NmtEditorPage * nmt_page_bond_new(NMConnection *conn, NmtDeviceEntry *deventry) { @@ -155,6 +195,8 @@ bond_options_changed(GObject *object, GParamSpec *pspec, gpointer user_data) nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->arp_interval), !visible_mii); nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->arp_ip_target), !visible_mii); + _bond_update_other_options(s_bond, priv->other_options); + priv->updating = FALSE; } @@ -308,6 +350,49 @@ arp_ip_target_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_d g_strfreev(ips); } +static void +other_options_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + NmtPageBond *bond = NMT_PAGE_BOND(user_data); + NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond); + gs_strfreev char **other_options = NULL; + const char *name; + gboolean ret; + guint num; + guint i; + + if (priv->updating) + return; + + priv->updating = TRUE; + + g_object_get(G_OBJECT(priv->other_options), "strings", &other_options, NULL); + +again: + num = nm_setting_bond_get_num_options(priv->s_bond); + for (i = 0; i < num; i++) { + ret = nm_setting_bond_get_option(priv->s_bond, i, &name, NULL); + nm_assert(ret); + + if (_is_other_option(name)) { + nm_setting_bond_remove_option(priv->s_bond, name); + goto again; + } + } + + for (i = 0; other_options && other_options[i]; i++) { + char *val = strchr(other_options[i], '='); + + if (val && val != other_options[i] && val[1]) { + *val = '\0'; + if (_is_other_option(other_options[i])) + nm_setting_bond_add_option(priv->s_bond, other_options[i], val + 1); + } + } + + priv->updating = FALSE; +} + static gboolean bond_connection_type_filter(GType connection_type, gpointer user_data) { @@ -390,10 +475,15 @@ nmt_page_bond_constructed(GObject *object) nmt_editor_grid_append(grid, _("Monitoring frequency"), widget, label); priv->arp_interval = NMT_NEWT_ENTRY(widget); - widget = nmt_address_list_new(NMT_ADDRESS_LIST_IP4); + widget = nmt_list_new(NMT_LIST_IP4); g_signal_connect(widget, "notify::strings", G_CALLBACK(arp_ip_target_widget_changed), bond); nmt_editor_grid_append(grid, _("ARP targets"), widget, NULL); - priv->arp_ip_target = NMT_ADDRESS_LIST(widget); + priv->arp_ip_target = NMT_LIST(widget); + + widget = nmt_list_new(NMT_LIST_KEY_VALUE); + g_signal_connect(widget, "notify::strings", G_CALLBACK(other_options_widget_changed), bond); + nmt_editor_grid_append(grid, _("Other options (key=value)"), widget, NULL); + priv->other_options = NMT_LIST(widget); widget = nmt_mac_entry_new(40, ETH_ALEN, NMT_MAC_ENTRY_TYPE_CLONED_ETHERNET); g_object_bind_property(s_wired, diff --git a/src/nmtui/nmt-page-bridge.c b/src/nmtui/nmt-page-bridge.c index da765b14..97c444be 100644 --- a/src/nmtui/nmt-page-bridge.c +++ b/src/nmtui/nmt-page-bridge.c @@ -13,7 +13,7 @@ #include "nmt-page-bridge.h" #include "libnm-core-aux-intern/nm-libnm-core-utils.h" -#include "nmt-address-list.h" +#include "nmt-list.h" #include "nmt-port-list.h" G_DEFINE_TYPE(NmtPageBridge, nmt_page_bridge, NMT_TYPE_EDITOR_PAGE_DEVICE) diff --git a/src/nmtui/nmt-page-ip-tunnel.c b/src/nmtui/nmt-page-ip-tunnel.c index c74037b6..4932c0dc 100644 --- a/src/nmtui/nmt-page-ip-tunnel.c +++ b/src/nmtui/nmt-page-ip-tunnel.c @@ -127,7 +127,7 @@ nmt_page_ip_tunnel_constructed(GObject *object) w2s); nmt_editor_grid_append(grid, _("Mode"), widget, NULL); - widget = parent = nmt_device_entry_new(_("Parent"), 40, G_TYPE_NONE); + widget = parent = nmt_device_entry_new(_("Parent"), 40, G_TYPE_NONE, FALSE); g_object_bind_property(s_ip_tunnel, NM_SETTING_IP_TUNNEL_PARENT, widget, diff --git a/src/nmtui/nmt-page-ip4.c b/src/nmtui/nmt-page-ip4.c index 4d01b356..ebbc7682 100644 --- a/src/nmtui/nmt-page-ip4.c +++ b/src/nmtui/nmt-page-ip4.c @@ -16,7 +16,7 @@ #include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "nmt-ip-entry.h" -#include "nmt-address-list.h" +#include "nmt-list.h" #include "nmt-route-editor.h" #include "nm-editor-bindings.h" @@ -112,7 +112,7 @@ nmt_page_ip4_constructed(GObject *object) section = nmt_editor_section_new(_("IPv4 CONFIGURATION"), widget, show_by_default); grid = nmt_editor_section_get_body(section); - widget = nmt_address_list_new(NMT_ADDRESS_LIST_IP4_WITH_PREFIX); + widget = nmt_list_new(NMT_LIST_IP4_WITH_PREFIX); nm_editor_bind_ip_addresses_with_prefix_to_strv(AF_INET, s_ip4, NM_SETTING_IP_CONFIG_ADDRESSES, @@ -131,7 +131,7 @@ nmt_page_ip4_constructed(GObject *object) G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); nmt_editor_grid_append(grid, _("Gateway"), widget, NULL); - widget = nmt_address_list_new(NMT_ADDRESS_LIST_IP4); + widget = nmt_list_new(NMT_LIST_IP4); nm_editor_bind_ip_addresses_to_strv(AF_INET, s_ip4, NM_SETTING_IP_CONFIG_DNS, @@ -140,7 +140,7 @@ nmt_page_ip4_constructed(GObject *object) G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); nmt_editor_grid_append(grid, _("DNS servers"), widget, NULL); - widget = nmt_address_list_new(NMT_ADDRESS_LIST_HOSTNAME); + widget = nmt_list_new(NMT_LIST_HOSTNAME); g_object_bind_property(s_ip4, NM_SETTING_IP_CONFIG_DNS_SEARCH, widget, diff --git a/src/nmtui/nmt-page-ip6.c b/src/nmtui/nmt-page-ip6.c index bd29a3a7..c0dc88f3 100644 --- a/src/nmtui/nmt-page-ip6.c +++ b/src/nmtui/nmt-page-ip6.c @@ -16,7 +16,7 @@ #include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "nmt-ip-entry.h" -#include "nmt-address-list.h" +#include "nmt-list.h" #include "nmt-route-editor.h" #include "nm-editor-bindings.h" @@ -114,7 +114,7 @@ nmt_page_ip6_constructed(GObject *object) section = nmt_editor_section_new(_("IPv6 CONFIGURATION"), widget, show_by_default); grid = nmt_editor_section_get_body(section); - widget = nmt_address_list_new(NMT_ADDRESS_LIST_IP6_WITH_PREFIX); + widget = nmt_list_new(NMT_LIST_IP6_WITH_PREFIX); nm_editor_bind_ip_addresses_with_prefix_to_strv(AF_INET6, s_ip6, NM_SETTING_IP_CONFIG_ADDRESSES, @@ -133,7 +133,7 @@ nmt_page_ip6_constructed(GObject *object) G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); nmt_editor_grid_append(grid, _("Gateway"), widget, NULL); - widget = nmt_address_list_new(NMT_ADDRESS_LIST_IP6); + widget = nmt_list_new(NMT_LIST_IP6); nm_editor_bind_ip_addresses_to_strv(AF_INET6, s_ip6, NM_SETTING_IP_CONFIG_DNS, @@ -142,7 +142,7 @@ nmt_page_ip6_constructed(GObject *object) G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); nmt_editor_grid_append(grid, _("DNS servers"), widget, NULL); - widget = nmt_address_list_new(NMT_ADDRESS_LIST_HOSTNAME); + widget = nmt_list_new(NMT_LIST_HOSTNAME); g_object_bind_property(s_ip6, NM_SETTING_IP_CONFIG_DNS_SEARCH, widget, diff --git a/src/nmtui/nmt-page-macsec.c b/src/nmtui/nmt-page-macsec.c index 6a017486..cd6a5494 100644 --- a/src/nmtui/nmt-page-macsec.c +++ b/src/nmtui/nmt-page-macsec.c @@ -99,7 +99,7 @@ nmt_page_macsec_constructed(GObject *object) section = nmt_editor_section_new(_("MACsec"), NULL, TRUE); grid = nmt_editor_section_get_body(section); - widget = nmt_device_entry_new(_("Parent device"), 40, G_TYPE_NONE); + widget = nmt_device_entry_new(_("Parent device"), 40, G_TYPE_NONE, FALSE); g_object_bind_property(s_macsec, NM_SETTING_MACSEC_PARENT, widget, diff --git a/src/nmtui/nmt-page-vlan.c b/src/nmtui/nmt-page-vlan.c index 238dbafb..64c10ef7 100644 --- a/src/nmtui/nmt-page-vlan.c +++ b/src/nmtui/nmt-page-vlan.c @@ -59,7 +59,7 @@ nmt_page_vlan_constructed(GObject *object) nm_editor_bind_vlan_name(s_vlan, nm_connection_get_setting_connection(conn)); - widget = parent = nmt_device_entry_new(_("Parent"), 40, G_TYPE_NONE); + widget = parent = nmt_device_entry_new(_("Parent"), 40, G_TYPE_NONE, FALSE); nmt_device_entry_set_device_filter(NMT_DEVICE_ENTRY(widget), vlan_device_filter, vlan); g_object_bind_property(s_vlan, NM_SETTING_VLAN_PARENT, diff --git a/src/nmtui/nmt-page-wifi.c b/src/nmtui/nmt-page-wifi.c index 9767597b..d2d0024d 100644 --- a/src/nmtui/nmt-page-wifi.c +++ b/src/nmtui/nmt-page-wifi.c @@ -54,10 +54,9 @@ static NmtNewtPopupEntry wifi_mode[] = {{NC_("Wi-Fi", "Client"), NM_SETTING_WIRE {NULL, NULL}}; static NmtNewtPopupEntry wifi_band[] = {{NC_("Wi-Fi", "Automatic"), NULL}, - /* 802.11a Wi-Fi network */ - {N_("A (5 GHz)"), "a"}, - /* 802.11b / 802.11g Wi-Fi network */ - {N_("B/G (2.4 GHz)"), "bg"}, + {N_("5 GHz"), "a"}, + {N_("2.4 GHz"), "bg"}, + {N_("6 GHz"), "6GHz"}, {NULL, NULL}}; static struct { diff --git a/src/nmtui/nmt-password-dialog.c b/src/nmtui/nmt-password-dialog.c index 6f1a5f03..1c2e6b00 100644 --- a/src/nmtui/nmt-password-dialog.c +++ b/src/nmtui/nmt-password-dialog.c @@ -109,12 +109,13 @@ maybe_save_input_and_exit(NmtNewtWidget *widget, gpointer dialog) static void nmt_password_dialog_constructed(GObject *object) { - NmtPasswordDialog *dialog = NMT_PASSWORD_DIALOG(object); - NmtPasswordDialogPrivate *priv = NMT_PASSWORD_DIALOG_GET_PRIVATE(dialog); - NmtNewtWidget *widget; - NmtNewtGrid *grid, *secret_grid; - NmtNewtButtonBox *bbox; - int i; + NmtPasswordDialog *dialog = NMT_PASSWORD_DIALOG(object); + NmtPasswordDialogPrivate *priv = NMT_PASSWORD_DIALOG_GET_PRIVATE(dialog); + NmtNewtWidget *widget; + NmtNewtGrid *grid, *secret_grid; + NmtNewtButtonBox *bbox; + gs_unref_ptrarray GPtrArray *masked_entries = g_ptr_array_new(); + int i; widget = nmt_newt_grid_new(); nmt_newt_form_set_content(NMT_NEWT_FORM(dialog), widget); @@ -133,19 +134,23 @@ nmt_password_dialog_constructed(GObject *object) for (i = 0; i < priv->secrets->len; i++) { NMSecretAgentSimpleSecret *secret = priv->secrets->pdata[i]; NmtNewtEntryFlags flags; + gboolean masked; widget = nmt_newt_label_new(secret->pretty_name); nmt_newt_grid_add(secret_grid, widget, 0, i); nmt_newt_widget_set_padding(widget, 4, 0, 1, 0); - flags = NMT_NEWT_ENTRY_NONEMPTY; - if (secret->is_secret && !secret->force_echo) + flags = NMT_NEWT_ENTRY_NONEMPTY; + masked = secret->is_secret && !secret->force_echo; + if (masked) flags |= NMT_NEWT_ENTRY_PASSWORD; widget = nmt_newt_entry_new(30, flags); if (secret->value) nmt_newt_entry_set_text(NMT_NEWT_ENTRY(widget), secret->value); nmt_newt_grid_add(secret_grid, widget, 1, i); g_ptr_array_add(priv->entries, widget); + if (masked) + g_ptr_array_add(masked_entries, widget); if (i == priv->secrets->len - 1) { priv->last_entry = widget; @@ -153,6 +158,18 @@ nmt_password_dialog_constructed(GObject *object) } } + if (masked_entries->len > 0) { + widget = nmt_newt_checkbox_new(_("Show password")); + nmt_newt_grid_add(secret_grid, widget, 1, priv->secrets->len); + for (i = 0; i < masked_entries->len; i++) { + g_object_bind_property(widget, + "active", + masked_entries->pdata[i], + "password", + G_BINDING_INVERT_BOOLEAN | G_BINDING_SYNC_CREATE); + } + } + widget = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_HORIZONTAL); nmt_newt_grid_add(grid, widget, 0, 2); bbox = NMT_NEWT_BUTTON_BOX(widget); diff --git a/src/nmtui/nmt-utils.c b/src/nmtui/nmt-utils.c index 7a500542..90e3bacb 100644 --- a/src/nmtui/nmt-utils.c +++ b/src/nmtui/nmt-utils.c @@ -12,6 +12,8 @@ #include "nmt-utils.h" +#include "libnmt-newt/nmt-newt.h" + /** * NmtSyncOp: * @@ -128,3 +130,210 @@ nmt_sync_op_complete_pointer(NmtSyncOp *op, gpointer result, GError *error) real->error = error ? g_error_copy(error) : NULL; real->complete = GUINT_TO_POINTER(TRUE); } + +/** + * nmt_utils_filter_match: + * @haystack: (nullable): the string to search in + * @needle: (nullable): the search term + * + * Case-insensitive UTF-8 substring test. An empty or %NULL @needle matches + * anything; a %NULL @haystack matches only an empty @needle. + * + * Returns: %TRUE if @haystack contains @needle. + */ +gboolean +nmt_utils_filter_match(const char *haystack, const char *needle) +{ + gs_free char *h = NULL; + gs_free char *n = NULL; + + if (!needle || !needle[0]) + return TRUE; + if (!haystack) + return FALSE; + + h = g_utf8_casefold(haystack, -1); + n = g_utf8_casefold(needle, -1); + return strstr(h, n) != NULL; +} + +/* + * Renders the search state into @label: "Search:" while typing, + * "Matching '...' (N)" once a filter is applied with the entry hidden, or + * empty when idle. + * + * When the entry is hidden the text is padded to the width the row occupies + * while searching ("Search:" plus the entry), so revealing or hiding the entry + * never changes the form's width. + */ +static void +set_search_label(NmtNewtLabel *label, const char *filter_text, int match_count, gboolean searching) +{ + gs_free char *body = NULL; + int reserve, body_width; + + if (searching) { + nmt_newt_label_set_text(label, _("Search:")); + return; + } + + reserve = nmt_newt_text_width(_("Search:")) + 1 + NMT_SEARCH_ENTRY_WIDTH; + + if (!nm_str_is_empty(filter_text)) { + gs_free char *shown = NULL; + int overhead; + + /* Echo the filter, but elide it so the confirmed label never exceeds + * the reserved width; otherwise the form grows when a long search is + * confirmed with Enter. */ + body = g_strdup_printf(_("Matching '%s' (%d)"), "", match_count); + overhead = nmt_newt_text_width(body); + nm_clear_g_free(&body); + + shown = nmt_newt_text_truncate(filter_text, reserve - overhead); + body = g_strdup_printf(_("Matching '%s' (%d)"), shown, match_count); + } else + body = g_strdup(""); + + body_width = nmt_newt_text_width(body); + if (body_width < reserve) { + gs_free char *padded = NULL; + + padded = g_strdup_printf("%s%*s", body, reserve - body_width, ""); + nmt_newt_label_set_text(label, padded); + } else + nmt_newt_label_set_text(label, body); +} + +struct _NmtSearch { + NmtNewtEntry *entry; + NmtNewtLabel *label; + NmtNewtWidget *focus; + NmtSearchApplyFunc apply; + NmtSearchCountFunc count; + gpointer user_data; +}; + +void +nmt_search_update_label(NmtSearch *search) +{ + set_search_label(search->label, + nmt_newt_entry_get_text(search->entry), + search->count(search->user_data), + nmt_newt_widget_get_visible(NMT_NEWT_WIDGET(search->entry))); +} + +static void +search_text_changed(GObject *entry, GParamSpec *pspec, gpointer user_data) +{ + NmtSearch *search = user_data; + + search->apply(search->user_data, nmt_newt_entry_get_text(search->entry)); + nmt_search_update_label(search); +} + +static void +search_activated(NmtNewtWidget *entry, gpointer user_data) +{ + NmtSearch *search = user_data; + NmtNewtForm *form = nmt_newt_widget_get_form(search->focus); + + /* Enter: hide the entry but keep the filter; the label now reports it. */ + nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(search->entry), FALSE); + nmt_search_update_label(search); + if (form) + nmt_newt_form_set_focus(form, search->focus); +} + +static gboolean +search_hotkey(NmtNewtForm *form, int key, gpointer user_data) +{ + NmtSearch *search = user_data; + const char *filter = nmt_newt_entry_get_text(search->entry); + + if (key == '/') { + nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(search->entry), TRUE); + nmt_search_update_label(search); + nmt_newt_form_set_focus(form, NMT_NEWT_WIDGET(search->entry)); + return TRUE; + } + if (key == NEWT_KEY_ESCAPE + && (nmt_newt_widget_get_visible(NMT_NEWT_WIDGET(search->entry)) + || !nm_str_is_empty(filter))) { + /* Esc: clear the filter (via notify::text) and return to the list. */ + nmt_newt_entry_set_text(search->entry, ""); + nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(search->entry), FALSE); + nmt_search_update_label(search); + nmt_newt_form_set_focus(form, search->focus); + return TRUE; + } + return FALSE; +} + +NmtSearch * +nmt_search_new(NmtNewtEntry *entry, + NmtNewtLabel *label, + NmtNewtWidget *focus, + NmtSearchApplyFunc apply, + NmtSearchCountFunc count, + gpointer user_data) +{ + NmtSearch *search = g_new0(NmtSearch, 1); + + search->entry = entry; + search->label = label; + search->focus = focus; + search->apply = apply; + search->count = count; + search->user_data = user_data; + + nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(entry), FALSE); + g_signal_connect(entry, "notify::text", G_CALLBACK(search_text_changed), search); + g_signal_connect(entry, "activated", G_CALLBACK(search_activated), search); + + return search; +} + +void +nmt_search_bind_form(NmtSearch *search, NmtNewtForm *form) +{ + g_signal_connect(form, "hotkey", G_CALLBACK(search_hotkey), search); + nmt_newt_form_add_hotkey(form, '/'); + nmt_newt_form_set_stable_width(form); + + /* Reserve the search row's width up front so the form does not grow when + * the entry is first revealed. */ + nmt_search_update_label(search); +} + +static void +get_secrets_cb(GObject *object, GAsyncResult *result, gpointer op) +{ + GVariant *secrets; + GError *error = NULL; + + secrets = nm_remote_connection_get_secrets_finish(NM_REMOTE_CONNECTION(object), result, &error); + nmt_sync_op_complete_pointer(op, secrets, error); + g_clear_error(&error); +} + +/** + * nmt_sync_get_secrets: + * @connection: the #NMRemoteConnection to fetch secrets from + * @setting_name: the setting to fetch secrets for + * @error: return location for a #GError + * + * Synchronously requests @setting_name's secrets for @connection, running the + * main loop until the request completes. + * + * Returns: (transfer full): the secrets variant, or %NULL on error. + */ +GVariant * +nmt_sync_get_secrets(NMRemoteConnection *connection, const char *setting_name, GError **error) +{ + NmtSyncOp op; + + nmt_sync_op_init(&op); + nm_remote_connection_get_secrets_async(connection, setting_name, NULL, get_secrets_cb, &op); + return nmt_sync_op_wait_pointer(&op, error); +} diff --git a/src/nmtui/nmt-utils.h b/src/nmtui/nmt-utils.h index 3b780ae7..79058c0d 100644 --- a/src/nmtui/nmt-utils.h +++ b/src/nmtui/nmt-utils.h @@ -6,6 +6,8 @@ #ifndef NMT_UTILS_H #define NMT_UTILS_H +#include "libnmt-newt/nmt-newt-types.h" + typedef struct { gpointer private[3]; } NmtSyncOp; @@ -18,4 +20,37 @@ void nmt_sync_op_complete_boolean(NmtSyncOp *op, gboolean result, GError *er gpointer nmt_sync_op_wait_pointer(NmtSyncOp *op, GError **error); void nmt_sync_op_complete_pointer(NmtSyncOp *op, gpointer result, GError *error); +gboolean nmt_utils_filter_match(const char *haystack, const char *needle); + +#define NMT_SEARCH_ENTRY_WIDTH 34 + +/** + * NmtSearch: + * + * Vim-style '/' search glue shared by the connection lists. The caller builds + * the @label and @entry into its own layout and hands them over; NmtSearch + * wires '/' to reveal the entry, Enter to confirm, Esc to clear, keeps the + * status label in sync, and pins the form width so the list does not jump. + * + * @apply is invoked with the current text whenever the filter changes; @count + * returns the live match count for the label. + */ +typedef struct _NmtSearch NmtSearch; + +typedef void (*NmtSearchApplyFunc)(gpointer user_data, const char *text); +typedef int (*NmtSearchCountFunc)(gpointer user_data); + +NmtSearch *nmt_search_new(NmtNewtEntry *entry, + NmtNewtLabel *label, + NmtNewtWidget *focus, + NmtSearchApplyFunc apply, + NmtSearchCountFunc count, + gpointer user_data); + +void nmt_search_bind_form(NmtSearch *search, NmtNewtForm *form); +void nmt_search_update_label(NmtSearch *search); + +GVariant * +nmt_sync_get_secrets(NMRemoteConnection *connection, const char *setting_name, GError **error); + #endif /* NMT_UTILS_H */ diff --git a/src/nmtui/nmt-widget-list.c b/src/nmtui/nmt-widget-list.c index d150eee0..c827db04 100644 --- a/src/nmtui/nmt-widget-list.c +++ b/src/nmtui/nmt-widget-list.c @@ -11,7 +11,7 @@ * buttons next to each one, and an "Add" button at the button to add * new ones. * - * It is the base class for #NmtAddressList, and is used internally by + * It is the base class for #NmtList, and is used internally by * #NmtRouteTable. * * FIXME: The way this works is sort of weird. diff --git a/src/nmtui/nmt-wifi-qr-dialog.c b/src/nmtui/nmt-wifi-qr-dialog.c new file mode 100644 index 00000000..d8e7711f --- /dev/null +++ b/src/nmtui/nmt-wifi-qr-dialog.c @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Red Hat, Inc. + */ + +/** + * SECTION:nmt-wifi-qr-dialog + * @short_description: A dialog that shares a Wi-Fi connection as a QR code + * + * Shows the "WIFI:" provisioning URI of a saved Wi-Fi connection as a + * scannable QR code, along with the SSID and password. If the password is a + * passphrase that cannot be read, it shows a warning instead of a QR code. + */ + +#include "libnm-client-aux-extern/nm-default-client.h" + +#include "nmt-wifi-qr-dialog.h" + +#include "libnm-glib-aux/nm-secret-utils.h" +#include "libnmc-base/nm-client-utils.h" +#include "nmt-utils.h" + +static NMConnection * +clone_with_secrets(NMConnection *connection) +{ + NMConnection *clone = nm_simple_connection_new_clone(connection); + gs_unref_variant GVariant *secrets = NULL; + + if (!NM_IS_REMOTE_CONNECTION(connection)) + return clone; + + secrets = nmt_sync_get_secrets(NM_REMOTE_CONNECTION(connection), + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NULL); + if (secrets) + nm_connection_update_secrets(clone, + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + secrets, + NULL); + + return clone; +} + +static gboolean +qr_fits_screen(const char *qr) +{ + gs_strfreev char **lines = NULL; + int screen_w; + int screen_h; + int qr_rows; + int qr_cols; + + newtGetScreenSize(&screen_w, &screen_h); + + lines = g_strsplit(qr, "\n", -1); + qr_rows = g_strv_length(lines); + qr_cols = lines[0] ? (int) g_utf8_strlen(lines[0], -1) : 0; + + /* Reserve room for the form border, title, the SSID/password labels and + * the button row. */ + return qr_cols + 4 <= screen_w && qr_rows + 8 <= screen_h; +} + +/** + * nmt_wifi_qr_dialog_run: + * @connection: a Wi-Fi #NMConnection + * + * Fetches @connection's secrets and shows a modal dialog with a QR code that + * encodes its Wi-Fi credentials, or a warning if the password cannot be read. + * Returns when the user closes the dialog. + */ +void +nmt_wifi_qr_dialog_run(NMConnection *connection) +{ + gs_unref_object NMConnection *clone = NULL; + gs_unref_object NmtNewtForm *form = NULL; + NMSettingWireless *s_wireless; + NMSettingWirelessSecurity *s_wsec; + NmtNewtGrid *grid; + NmtNewtWidget *widget; + NmtNewtButtonBox *bbox; + GBytes *ssid_bytes; + gs_free char *ssid = NULL; + nm_auto_free_secret char *uri = NULL; + nm_auto_free_secret char *qr = NULL; + const char *key_mgmt = NULL; + const char *psk = NULL; + gboolean no_password; + int row = 0; + + g_return_if_fail(NM_IS_CONNECTION(connection)); + + if (!nm_connection_get_setting_wireless(connection)) + return; + + clone = clone_with_secrets(connection); + s_wireless = nm_connection_get_setting_wireless(clone); + + ssid_bytes = nm_setting_wireless_get_ssid(s_wireless); + if (ssid_bytes) + ssid = + nm_utils_ssid_to_utf8(g_bytes_get_data(ssid_bytes, NULL), g_bytes_get_size(ssid_bytes)); + + s_wsec = nm_connection_get_setting_wireless_security(clone); + if (s_wsec) { + key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec); + psk = nm_setting_wireless_security_get_psk(s_wsec); + } + + /* A QR code for a secured network without its password connects to nothing, + * so don't render one; show a warning instead. */ + no_password = nmc_wifi_key_mgmt_uses_psk(key_mgmt) && (!psk || !psk[0]); + + if (!no_password) { + uri = nmc_wifi_qr_uri_new(ssid, key_mgmt, psk, nm_setting_wireless_get_hidden(s_wireless)); + qr = nmc_wifi_qr_render_string(uri); + } + + form = nmt_newt_form_new(_("Share Wi-Fi")); + + widget = nmt_newt_grid_new(); + nmt_newt_form_set_content(form, widget); + grid = NMT_NEWT_GRID(widget); + + if (no_password) { + widget = nmt_newt_textbox_new(0, 50); + nmt_newt_textbox_set_text( + NMT_NEWT_TEXTBOX(widget), + _("Warning: cannot read the Wi-Fi password due to insufficient privileges.")); + } else if (qr && qr_fits_screen(qr)) { + widget = nmt_newt_textbox_new(NMT_NEWT_TEXTBOX_SET_BACKGROUND, 0); + nmt_newt_textbox_set_text(NMT_NEWT_TEXTBOX(widget), qr); + } else { + widget = nmt_newt_textbox_new(0, 50); + nmt_newt_textbox_set_text(NMT_NEWT_TEXTBOX(widget), + qr ? _("The terminal is too small to display the QR code.") + : _("The Wi-Fi credentials could not be encoded.")); + } + nmt_newt_grid_add(grid, widget, 0, row++); + nmt_newt_widget_set_padding(widget, 0, 0, 0, 1); + + if (ssid) { + gs_free char *label = g_strdup_printf("%s: %s", _("SSID"), ssid); + + widget = nmt_newt_label_new(label); + nmt_newt_grid_add(grid, widget, 0, row++); + } + + if (psk) { + nm_auto_free_secret char *label = g_strdup_printf("%s: %s", _("Password"), psk); + + widget = nmt_newt_label_new(label); + nmt_newt_grid_add(grid, widget, 0, row++); + } + + widget = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_HORIZONTAL); + nmt_newt_grid_add(grid, widget, 0, row++); + nmt_newt_widget_set_padding(widget, 0, 1, 0, 0); + bbox = NMT_NEWT_BUTTON_BOX(widget); + + widget = nmt_newt_button_box_add_end(bbox, _("Close")); + nmt_newt_widget_set_exit_on_activate(widget, TRUE); + + nmt_newt_form_run_sync(form); +} diff --git a/src/nmtui/nmt-wifi-qr-dialog.h b/src/nmtui/nmt-wifi-qr-dialog.h new file mode 100644 index 00000000..310bb7c8 --- /dev/null +++ b/src/nmtui/nmt-wifi-qr-dialog.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Red Hat, Inc. + */ + +#ifndef NMT_WIFI_QR_DIALOG_H +#define NMT_WIFI_QR_DIALOG_H + +#include "libnmt-newt/nmt-newt.h" + +void nmt_wifi_qr_dialog_run(NMConnection *connection); + +#endif /* NMT_WIFI_QR_DIALOG_H */ diff --git a/src/nmtui/nmt-wireguard-peer-list.c b/src/nmtui/nmt-wireguard-peer-list.c index c6db61a6..38987a97 100644 --- a/src/nmtui/nmt-wireguard-peer-list.c +++ b/src/nmtui/nmt-wireguard-peer-list.c @@ -170,6 +170,7 @@ nmt_wireguard_peer_list_add_peer(NmtWireguardPeerList *list) NmtNewtForm *editor; editor = nmt_wireguard_peer_editor_new(priv->setting, peer); + nm_wireguard_peer_unref(peer); if (!editor) return; diff --git a/src/nmtui/nmtui-connect.c b/src/nmtui/nmtui-connect.c index 4f90adf3..3fcf527b 100644 --- a/src/nmtui/nmtui-connect.c +++ b/src/nmtui/nmtui-connect.c @@ -342,16 +342,268 @@ listbox_active_changed(GObject *object, GParamSpec *pspec, gpointer button) } } +/* Contains both the UI and batch data for wifi-rescans */ +typedef struct { + NmtNewtWidget *activate_button; + NmtNewtWidget *listbox; + NmtNewtForm *rescan_form; + NmtNewtWidget *rescan_button; + int pending; + GCancellable *cancellable; + gboolean cancelled; + gboolean has_error; + gboolean timeout_occurred; +} RescanBatch; + +typedef struct { + NMDeviceWifi *wifi_device; + GSource *timeout_id; + gulong signal_id; + RescanBatch *batch; +} RescanData; + +static GPtrArray * +active_wifi_devices(void) +{ + const GPtrArray *devices; + GPtrArray *active_wifi_devices; + + devices = nm_client_get_devices(nm_client); + + if (!devices) + return NULL; + + active_wifi_devices = g_ptr_array_new_with_free_func(g_object_unref); + + for (guint i = 0; i < devices->len; i++) { + NMDevice *dev = g_ptr_array_index((GPtrArray *) devices, i); + + if (!NM_IS_DEVICE_WIFI(dev)) + continue; + if (NM_IN_SET(nm_device_get_state(dev), + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_UNMANAGED, + NM_DEVICE_STATE_FAILED)) + continue; + + g_ptr_array_add(active_wifi_devices, g_object_ref(dev)); + } + return active_wifi_devices; +} + +static void +on_rescan_cancel(NmtNewtForm *form, gpointer user_data) +{ + RescanBatch *batch_data = user_data; + + if (!batch_data || batch_data->cancelled) + return; + + batch_data->cancelled = TRUE; + + /* Cancel the async operations using nm_clear_g_cancellable */ + if (batch_data->cancellable) + nm_clear_g_cancellable(&batch_data->cancellable); + + g_object_unref(batch_data->rescan_form); + batch_data->rescan_form = NULL; +} + +/* creates the wifi-rescan form and manages other ui properties while rescanning */ +static void +wifi_rescan_form(RescanBatch *batch_data) +{ + NmtNewtForm *rescan_form; + NmtNewtWidget *label; + + nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(batch_data->listbox), FALSE); + nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(batch_data->rescan_button), FALSE); + nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(batch_data->activate_button), FALSE); + + /* open the scanning form*/ + rescan_form = g_object_new(NMT_TYPE_NEWT_FORM, NULL); + label = nmt_newt_label_new(_("Rescanning Wi-Fi devices...")); + nmt_newt_form_set_content(rescan_form, label); + + /* connect the quit signal to rescan cancel */ + g_signal_connect(rescan_form, "quit", G_CALLBACK(on_rescan_cancel), batch_data); + + nmt_newt_form_show(rescan_form); + batch_data->rescan_form = rescan_form; +} + +/* rebuilds the connection list and changes back the + * state for various UI components which were + * changed during rescanning */ +static void +on_rescan_complete(gpointer data) +{ + NmtConnectConnectionList *list; + RescanData *rescan_data; + RescanBatch *batch_data; + NMDeviceWifi *wifi_device; + + rescan_data = data; + batch_data = rescan_data->batch; + wifi_device = rescan_data->wifi_device; + + g_free(rescan_data); + rescan_data = NULL; + + if (batch_data->has_error) { + nmt_newt_message_dialog(_("Wi-Fi scan failed for device : %s"), + nm_device_get_iface(NM_DEVICE(wifi_device))); + batch_data->has_error = FALSE; + } + + if (batch_data->timeout_occurred) { + nmt_newt_message_dialog(_("Wi-Fi scan timed out for device : %s"), + nm_device_get_iface(NM_DEVICE(wifi_device))); + batch_data->timeout_occurred = FALSE; + } + + /* If the scans are not complete then simply wait for them to complete + * This also ensures that UI is only enabled either after all + * scans complete (Either complete or cancel ). Preventing any + * overlapping scans caused due to the user pressing rescan multiple times + */ + + if (--batch_data->pending > 0) + return; + + /* If the scan is not cancelled quit normally */ + if (!batch_data->cancelled) { + if (nmt_newt_widget_get_realized(NMT_NEWT_WIDGET(batch_data->rescan_form))) { + nmt_newt_form_quit(batch_data->rescan_form); + nm_clear_g_object(&batch_data->rescan_form); + batch_data->rescan_form = NULL; + } + list = NMT_CONNECT_CONNECTION_LIST(batch_data->listbox); + nmt_newt_listbox_clear(NMT_NEWT_LISTBOX(list)); + + g_object_notify(G_OBJECT(nm_client), NM_CLIENT_CONNECTIONS); + } + + /* The following cleanup is required regardless of any cancellation */ + nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(batch_data->listbox), TRUE); + nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(batch_data->rescan_button), TRUE); + nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(batch_data->activate_button), TRUE); + + g_clear_object(&batch_data->cancellable); +} + +static gboolean +scan_timeout_callback(gpointer user_data) +{ + RescanData *data = user_data; + + nm_clear_g_source_inst(&data->timeout_id); + + /* Timeout reached - scan took too long */ + if (data->signal_id) { + g_signal_handler_disconnect(data->wifi_device, data->signal_id); + data->signal_id = 0; + } + + data->batch->timeout_occurred = TRUE; + + on_rescan_complete(data); + return G_SOURCE_REMOVE; +} + +static void +on_last_scan_changed(GObject *object, GParamSpec *pspec, gpointer user_data) +{ + RescanData *data = user_data; + + /* Scan completed successfully */ + nm_clear_g_source_inst(&data->timeout_id); + if (data->signal_id) { + g_signal_handler_disconnect(data->wifi_device, data->signal_id); + data->signal_id = 0; + } + + on_rescan_complete(data); +} + +static void +wifi_rescan_callback(GObject *source_object, GAsyncResult *result, gpointer rescan_data) +{ + RescanData *data = rescan_data; + + if (!nm_device_wifi_request_scan_finish(data->wifi_device, result, NULL)) { + data->batch->has_error = TRUE; + on_rescan_complete(data); + return; + } + + /* Listen for last-scan property changes */ + data->signal_id = g_signal_connect(data->wifi_device, + "notify::last-scan", + G_CALLBACK(on_last_scan_changed), + data); + + /* Set a 10-second timeout in case scan doesn't complete */ + data->timeout_id = nm_g_timeout_add_source(RESCAN_TIMEOUT_MS, scan_timeout_callback, data); +} + +static void +wifi_rescan(NmtNewtButton *button, gpointer data_batch) +{ + gs_unref_ptrarray GPtrArray *devices = active_wifi_devices(); + RescanData *data; + RescanBatch *batch_data = data_batch; + + if (!devices || devices->len == 0) { + nmt_newt_message_dialog(_("No active Wi-Fi devices found")); + return; + } + + /* create a shared batch for all the devices*/ + batch_data->pending = devices->len; + batch_data->cancelled = FALSE; + batch_data->cancellable = g_cancellable_new(); + + wifi_rescan_form(batch_data); + + for (guint i = 0; i < devices->len; i++) { + NMDevice *dev = g_ptr_array_index(devices, i); + + /* per-device data */ + data = g_new0(RescanData, 1); + data->wifi_device = NM_DEVICE_WIFI(dev); + data->batch = batch_data; + + nm_device_wifi_request_scan_async(NM_DEVICE_WIFI(dev), + batch_data->cancellable, + wifi_rescan_callback, + data); + } +} + +static void +connect_search_apply(gpointer list, const char *text) +{ + nmt_connect_connection_list_set_filter_text(NMT_CONNECT_CONNECTION_LIST(list), text); +} + +static int +connect_search_count(gpointer list) +{ + return nmt_connect_connection_list_get_match_count(NMT_CONNECT_CONNECTION_LIST(list)); +} + static NmtNewtForm * nmt_connect_connection_list(gboolean is_top) { - int screen_width, screen_height; - NmtNewtForm *form; - NmtNewtWidget *list, *activate, *quit, *bbox, *grid; + NmtNewtForm *form; + NmtNewtWidget *list, *activate, *quit, *bbox, *grid, *rescan; + NmtNewtWidget *search_row, *search_label, *search_entry; + NmtSearch *search; + RescanBatch *batch_data; + gs_unref_ptrarray GPtrArray *all_active_wifi_devices = NULL; - newtGetScreenSize(&screen_width, &screen_height); - - form = g_object_new(NMT_TYPE_NEWT_FORM, "y", 2, "height", screen_height - 4, NULL); + form = g_object_new(NMT_TYPE_NEWT_FORM, "fullscreen-vertical", TRUE, NULL); grid = nmt_newt_grid_new(); @@ -372,9 +624,50 @@ nmt_connect_connection_list(gboolean is_top) listbox_active_changed(G_OBJECT(list), NULL, activate); g_signal_connect(activate, "clicked", G_CALLBACK(activate_clicked), list); + all_active_wifi_devices = active_wifi_devices(); + if (all_active_wifi_devices && all_active_wifi_devices->len > 0) { + rescan = nmt_newt_button_box_add_start(NMT_NEWT_BUTTON_BOX(bbox), _("Rescan Wi-Fi")); + + batch_data = g_new0(RescanBatch, 1); + batch_data->activate_button = activate; + batch_data->listbox = list; + batch_data->rescan_button = rescan; + + /* Bind the lifecycle of batch_data to the rescan button. + * The data will be freed automatically when the button is destroyed i.e form quits. */ + g_object_set_data_full(G_OBJECT(rescan), "rescan-batch-data", batch_data, g_free); + + g_signal_connect(rescan, "clicked", G_CALLBACK(wifi_rescan), batch_data); + } + quit = nmt_newt_button_box_add_end(NMT_NEWT_BUTTON_BOX(bbox), is_top ? _("Quit") : _("Back")); nmt_newt_widget_set_exit_on_activate(quit, TRUE); + /* Search row below the list. The row is always present so revealing the + * entry does not resize the form; vim-style '/' shows the entry, and once a + * filter is applied the label reports it ("Matching '...' (N)"). */ + search_row = nmt_newt_grid_new(); + nmt_newt_grid_add(NMT_NEWT_GRID(grid), search_row, 0, 1); + nmt_newt_grid_set_flags(NMT_NEWT_GRID(grid), + search_row, + NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_EXPAND_X); + + search_label = nmt_newt_label_new(""); + nmt_newt_grid_add(NMT_NEWT_GRID(search_row), search_label, 0, 0); + + search_entry = nmt_newt_entry_new(NMT_SEARCH_ENTRY_WIDTH, 0); + nmt_newt_grid_add(NMT_NEWT_GRID(search_row), search_entry, 1, 0); + nmt_newt_widget_set_padding(search_entry, 1, 0, 0, 0); + + search = nmt_search_new(NMT_NEWT_ENTRY(search_entry), + NMT_NEWT_LABEL(search_label), + list, + connect_search_apply, + connect_search_count, + list); + nmt_search_bind_form(search, form); + g_object_set_data_full(G_OBJECT(form), "search-data", search, g_free); + nmt_newt_form_set_content(form, grid); return form; } diff --git a/src/nmtui/nmtui-connect.h b/src/nmtui/nmtui-connect.h index 811893e8..3e42714b 100644 --- a/src/nmtui/nmtui-connect.h +++ b/src/nmtui/nmtui-connect.h @@ -6,6 +6,8 @@ #ifndef NMTUI_CONNECT_H #define NMTUI_CONNECT_H +#define RESCAN_TIMEOUT_MS 10000 + NmtNewtForm *nmtui_connect(gboolean is_top, int argc, char **argv); #endif /* NMTUI_CONNECT_H */ diff --git a/src/nmtui/nmtui-edit.c b/src/nmtui/nmtui-edit.c index 0ba3bd0d..dcdce568 100644 --- a/src/nmtui/nmtui-edit.c +++ b/src/nmtui/nmtui-edit.c @@ -19,6 +19,7 @@ #include "nmt-edit-connection-list.h" #include "nmt-editor.h" #include "nmt-utils.h" +#include "nmt-wifi-qr-dialog.h" #include "nm-editor-utils.h" @@ -43,6 +44,13 @@ list_remove_connection(NmtEditConnectionList *list, NMRemoteConnection *connecti nmt_newt_form_set_focus(form, NMT_NEWT_WIDGET(list)); } +static void +list_share_connection(NmtEditConnectionList *list, NMConnection *connection, gpointer form) +{ + nmt_wifi_qr_dialog_run(connection); + nmt_newt_form_set_focus(form, NMT_NEWT_WIDGET(list)); +} + static gboolean edit_connection_list_filter(NmtEditConnectionList *list, NMConnection *connection, @@ -85,13 +93,10 @@ edit_connection_list_filter(NmtEditConnectionList *list, static NmtNewtForm * nmt_edit_main_connection_list(gboolean is_top) { - int screen_width, screen_height; NmtNewtForm *form; NmtNewtWidget *quit, *list; - newtGetScreenSize(&screen_width, &screen_height); - - form = g_object_new(NMT_TYPE_NEWT_FORM, "y", 2, "height", screen_height - 4, NULL); + form = g_object_new(NMT_TYPE_NEWT_FORM, "fullscreen-vertical", TRUE, NULL); quit = nmt_newt_button_new(is_top ? _("Quit") : _("Back")); nmt_newt_widget_set_exit_on_activate(quit, TRUE); @@ -106,6 +111,9 @@ nmt_edit_main_connection_list(gboolean is_top) g_signal_connect(list, "add-connection", G_CALLBACK(list_add_connection), form); g_signal_connect(list, "edit-connection", G_CALLBACK(list_edit_connection), form); g_signal_connect(list, "remove-connection", G_CALLBACK(list_remove_connection), form); + g_signal_connect(list, "share-connection", G_CALLBACK(list_share_connection), form); + + nmt_edit_connection_list_bind_search(NMT_EDIT_CONNECTION_LIST(list), form); nmt_newt_form_set_content(form, list); return form; diff --git a/src/nmtui/nmtui.c b/src/nmtui/nmtui.c index 9927e40a..0838187a 100644 --- a/src/nmtui/nmtui.c +++ b/src/nmtui/nmtui.c @@ -75,12 +75,11 @@ main_list_activated(NmtNewtWidget *widget, NmtNewtListbox *listbox) static NmtNewtForm * nmtui_main(gboolean is_top, int argc, char **argv) { - NmtNewtForm *form; - NmtNewtWidget *widget, *ok; - NmtNewtGrid *grid; - NmtNewtListbox *listbox; - NmtNewtButtonBox *bbox; - int i; + NmtNewtForm *form; + NmtNewtWidget *widget; + NmtNewtGrid *grid; + NmtNewtListbox *listbox; + int i; form = g_object_new(NMT_TYPE_NEWT_FORM, "title", _("NetworkManager TUI"), NULL); @@ -108,13 +107,6 @@ nmtui_main(gboolean is_top, int argc, char **argv) nmt_newt_listbox_append(listbox, "", NULL); nmt_newt_listbox_append(listbox, _("Quit"), quit_func); - widget = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_HORIZONTAL); - nmt_newt_grid_add(grid, widget, 0, 2); - bbox = NMT_NEWT_BUTTON_BOX(widget); - - ok = nmt_newt_button_box_add_end(bbox, _("OK")); - g_signal_connect(ok, "activated", G_CALLBACK(main_list_activated), listbox); - toplevel_form = form; return form; |