diff --git a/src/nmtui/nmtui-connect.c b/src/nmtui/nmtui-connect.c index 4f90adf324..2cb8c5b7c4 100644 --- a/src/nmtui/nmtui-connect.c +++ b/src/nmtui/nmtui-connect.c @@ -342,12 +342,256 @@ 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); + NMDeviceInterfaceFlags devflags = nm_device_get_interface_flags(dev); + + if ((devflags & NM_DEVICE_INTERFACE_FLAG_UP) == 0) + continue; + + if (NM_IS_DEVICE_WIFI(dev) + && !NM_IN_SET(nm_device_get_state(dev), + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_UNMANAGED, + NM_DEVICE_STATE_FAILED)) { + 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 or all the scans are cancelled. 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); + 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; + RescanData *data; + RescanBatch *batch_data = data_batch; + + devices = active_wifi_devices(); + + 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 NmtNewtForm * nmt_connect_connection_list(gboolean is_top) { - int screen_width, screen_height; - NmtNewtForm *form; - NmtNewtWidget *list, *activate, *quit, *bbox, *grid; + int screen_width, screen_height; + NmtNewtForm *form; + NmtNewtWidget *list, *activate, *quit, *bbox, *grid, *rescan; + RescanBatch *batch_data; + gs_unref_ptrarray GPtrArray *all_active_wifi_devices; newtGetScreenSize(&screen_width, &screen_height); @@ -372,6 +616,17 @@ 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; + 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); diff --git a/src/nmtui/nmtui-connect.h b/src/nmtui/nmtui-connect.h index 811893e808..3e42714b07 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 */