Clear interface menu's sanely when the interface is removed.
[dhcpcd-ui] / src / dhcpcd-gtk / menu.c
index 0f410aca0bc0d9a021f5664a40232ceaa5c6956c..9c3cb5a8c37280791fc2c6f86f67ada5732743cf 100644 (file)
 #include "config.h"
 #include "dhcpcd-gtk.h"
 
-static const char *copyright = "Copyright (c) 2009-2013 Roy Marples";
+static const char *copyright = "Copyright (c) 2009-2014 Roy Marples";
 static const char *authors[] = { "Roy Marples <roy@marples.name>", NULL };
 
+static GtkStatusIcon *sicon;
+static GtkWidget *menu;
+static bool ifmenu;
+
 static void
 on_pref(_unused GObject *o, gpointer data)
 {
 
-       dhcpcd_prefs_show((DHCPCD_CONNECTION *)data);
+       prefs_show((DHCPCD_CONNECTION *)data);
 }
 
 static void
 on_quit(void)
 {
 
+       wpa_abort();
        gtk_main_quit();
 }
 
@@ -73,15 +78,25 @@ url_hook(GtkAboutDialog *dialog, const char *url, _unused gpointer data)
 #endif
 
 static void
-ssid_hook(_unused GtkMenuItem *item, gpointer data)
+ssid_hook(GtkMenuItem *item, _unused gpointer data)
 {
        DHCPCD_WI_SCAN *scan;
        WI_SCAN *wi;
 
-       scan = (DHCPCD_WI_SCAN *)data;
+       scan = g_object_get_data(G_OBJECT(item), "dhcpcd_wi_scan");
        wi = wi_scan_find(scan);
-       if (wi)
-               wpa_configure(wi->connection, wi->interface, scan);
+       if (wi) {
+               DHCPCD_CONNECTION *con;
+
+               con = dhcpcd_if_connection(wi->interface);
+               if (con) {
+                       DHCPCD_WPA *wpa;
+
+                       wpa = dhcpcd_wpa_find(con, wi->interface->ifname);
+                       if (wpa)
+                               wpa_configure(wpa, scan);
+               }
+       }
 }
 
 static void
@@ -104,76 +119,234 @@ on_about(_unused GtkMenuItem *item)
            NULL);
 }
 
-static GtkWidget *
-add_scans(WI_SCAN *scan)
+static void
+update_item(WI_SCAN *wi, WI_MENU *m, DHCPCD_WI_SCAN *scan)
 {
-       DHCPCD_WI_SCAN *wis;
-       GtkWidget *menu, *item, *image, *box, *label, *bar;
+       const char *icon;
+       double perc;
+
+       m->scan = scan;
+
+       g_object_set_data(G_OBJECT(m->menu), "dhcpcd_wi_scan", scan);
+       gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(m->menu),
+               (wi->interface->up &&
+               g_strcmp0(wi->interface->ssid, scan->ssid)));
+
+       gtk_label_set_text(GTK_LABEL(m->ssid), scan->ssid);
+       if (scan->flags[0] == '\0')
+               icon = "network-wireless";
+       else
+               icon = "network-wireless-encrypted";
+       m->icon = gtk_image_new_from_icon_name(icon,
+           GTK_ICON_SIZE_MENU);
+
+       perc = scan->strength.value / 100.0;
+       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(m->bar), perc);
+
+       if (scan->flags[0] == '\0')
+               gtk_widget_set_tooltip_text(m->menu, scan->bssid);
+       else {
+               char *tip = g_strconcat(scan->bssid, " ", scan->flags, NULL);
+               gtk_widget_set_tooltip_text(m->menu, tip);
+               g_free(tip);
+       }
+
+       g_object_set_data(G_OBJECT(m->menu), "dhcpcd_wi_scan", scan);
+}
+
+static WI_MENU *
+create_menu(GtkWidget *m, WI_SCAN *wis, DHCPCD_WI_SCAN *scan)
+{
+       WI_MENU *wim;
+       GtkWidget *box;
        double perc;
-       int strength;
        const char *icon;
        char *tip;
 
-       if (scan->scans == NULL)
-               return NULL;
-       menu = gtk_menu_new();
-       for (wis = scan->scans; wis; wis = wis->next) {
-               item = gtk_check_menu_item_new();
-               gtk_check_menu_item_set_draw_as_radio(
-                       GTK_CHECK_MENU_ITEM(item), true); 
-               box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
-               gtk_container_add(GTK_CONTAINER(item), box); 
-               label = gtk_label_new(wis->ssid);
-               gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
-
-               if (g_strcmp0(wis->ssid, scan->interface->ssid) == 0)
-                       gtk_check_menu_item_set_active(
-                               GTK_CHECK_MENU_ITEM(item), true);
-               if (wis->flags == NULL)
-                       icon = "network-wireless";
-               else
-                       icon = "network-wireless-encrypted";
-               image = gtk_image_new_from_icon_name(icon,
-                   GTK_ICON_SIZE_MENU);
-               gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0);
-
-               bar = gtk_progress_bar_new();
-               gtk_widget_set_size_request(bar, 100, -1);
-               gtk_box_pack_end(GTK_BOX(box), bar, FALSE, TRUE, 0);
-               if (wis->quality.value == 0)
-                       strength = wis->level.average;
-               else
-                       strength = wis->quality.average;
-               perc = CLAMP(strength, 0, 100) / 100.0;
-               gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(bar), perc);
-
-               tip = g_strconcat(wis->bssid, " ", wis->flags, NULL);
-               gtk_widget_set_tooltip_text(item, tip);
+       wim = g_malloc(sizeof(*wim));
+       wim->scan = scan;
+       wim->menu = gtk_check_menu_item_new();
+       gtk_check_menu_item_set_draw_as_radio(
+           GTK_CHECK_MENU_ITEM(wim->menu), true);
+       box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
+       gtk_container_add(GTK_CONTAINER(wim->menu), box);
+       wim->ssid = gtk_label_new(scan->ssid);
+       gtk_box_pack_start(GTK_BOX(box), wim->ssid, TRUE, TRUE, 0);
+
+       if (wis->interface->up &&
+           g_strcmp0(scan->ssid, wis->interface->ssid) == 0)
+               gtk_check_menu_item_set_active(
+                   GTK_CHECK_MENU_ITEM(wim->menu), true);
+
+       if (scan->flags[0] == '\0')
+               icon = "network-wireless";
+       else
+               icon = "network-wireless-encrypted";
+       wim->icon = gtk_image_new_from_icon_name(icon,
+           GTK_ICON_SIZE_MENU);
+
+       gtk_box_pack_start(GTK_BOX(box), wim->icon, FALSE, FALSE, 0);
+
+       wim->bar = gtk_progress_bar_new();
+       gtk_widget_set_size_request(wim->bar, 100, -1);
+       gtk_box_pack_end(GTK_BOX(box), wim->bar, FALSE, TRUE, 0);
+       perc = scan->strength.value / 100.0;
+       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(wim->bar), perc);
+
+       if (scan->flags[0] == '\0')
+               gtk_widget_set_tooltip_text(wim->menu, scan->bssid);
+       else {
+               tip = g_strconcat(scan->bssid, " ", scan->flags, NULL);
+               gtk_widget_set_tooltip_text(wim->menu, tip);
                g_free(tip);
+       }
 
-               gtk_widget_show(label);
-               gtk_widget_show(bar);
-               gtk_widget_show(image);
-               gtk_widget_show(box);
-               g_signal_connect(G_OBJECT(item), "activate",
-                   G_CALLBACK(ssid_hook), wis);
-               gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+       g_signal_connect(G_OBJECT(wim->menu), "toggled",
+           G_CALLBACK(ssid_hook), NULL);
+       g_object_set_data(G_OBJECT(wim->menu), "dhcpcd_wi_scan", scan);
+       gtk_menu_shell_append(GTK_MENU_SHELL(m), wim->menu);
+
+       return wim;
+}
+
+void
+menu_update_scans(WI_SCAN *wi, DHCPCD_WI_SCAN *scans)
+{
+       WI_MENU *wim, *win;
+       DHCPCD_WI_SCAN *s;
+       bool found, update;
+
+       if (wi->ifmenu == NULL) {
+               dhcpcd_wi_scans_free(wi->scans);
+               wi->scans = scans;
+               return;
+       }
+
+       update = false;
+       TAILQ_FOREACH_SAFE(wim, &wi->menus, next, win) {
+               found = false;
+               for (s = scans; s; s = s->next) {
+                       if (memcmp(wim->scan->bssid, s->bssid,
+                           sizeof(s->bssid)) == 0)
+                       {
+                               found = true;
+                               update_item(wi, wim, s);
+                       }
+               }
+               if (!found) {
+                       TAILQ_REMOVE(&wi->menus, wim, next);
+                       gtk_widget_destroy(wim->menu);
+                       g_free(wim);
+                       update = true;
+               }
+       }
+
+       for (s = scans; s; s = s->next) {
+               found = false;
+               TAILQ_FOREACH(wim, &wi->menus, next) {
+                       if (memcmp(wim->scan->bssid, s->bssid,
+                           sizeof(s->bssid)) == 0)
+                       {
+                               found = true;
+                               break;
+                       }
+               }
+               if (!found) {
+                       wim = create_menu(wi->ifmenu, wi, s);
+                       TAILQ_INSERT_TAIL(&wi->menus, wim, next);
+                       gtk_widget_show_all(wim->menu);
+                       update = true;
+               }
+       }
+
+       dhcpcd_wi_scans_free(wi->scans);
+       wi->scans = scans;
+
+       if (update && gtk_widget_get_visible(wi->ifmenu))
+               gtk_menu_reposition(GTK_MENU(wi->ifmenu));
+}
+
+void
+menu_remove_if(WI_SCAN *wi)
+{
+       WI_MENU *wim;
+
+       if (wi->ifmenu == NULL)
+               return;
+
+       if (wi->ifmenu == menu)
+               menu = NULL;
+
+       gtk_widget_destroy(wi->ifmenu);
+       wi->ifmenu = NULL;
+       while ((wim = TAILQ_FIRST(&wi->menus))) {
+               TAILQ_REMOVE(&wi->menus, wim, next);
+               g_free(wim);
+       }
+
+       if (menu && gtk_widget_get_visible(menu))
+               gtk_menu_reposition(GTK_MENU(menu));
+}
+
+static GtkWidget *
+add_scans(WI_SCAN *wi)
+{
+       GtkWidget *m;
+       DHCPCD_WI_SCAN *wis;
+       WI_MENU *wim;
+
+       if (wi->scans == NULL)
+               return NULL;
+
+       m = gtk_menu_new();
+       for (wis = wi->scans; wis; wis = wis->next) {
+               wim = create_menu(m, wi, wis);
+               TAILQ_INSERT_TAIL(&wi->menus, wim, next);
+       }
+
+       return m;
+}
+
+void
+menu_abort(void)
+{
+       WI_SCAN *wis;
+       WI_MENU *wim;
+
+       TAILQ_FOREACH(wis, &wi_scans, next) {
+               wis->ifmenu = NULL;
+               while ((wim = TAILQ_FIRST(&wis->menus))) {
+                       TAILQ_REMOVE(&wis->menus, wim, next);
+                       g_free(wim);
+               }
+       }
+
+       if (menu != NULL) {
+               gtk_widget_destroy(menu);
+               g_object_ref_sink(menu);
+               g_object_unref(menu);
+               menu = NULL;
        }
-       return menu;
 }
 
 static void
 on_activate(GtkStatusIcon *icon)
 {
-       WI_SCAN *w;
-       GtkWidget *menu, *item, *image;
+       WI_SCAN *w, *l;
+       GtkWidget *item, *image;
 
+       sicon = icon;
        notify_close();
-       if (wi_scans == NULL)
+       prefs_abort();
+       menu_abort();
+
+       if ((w = TAILQ_FIRST(&wi_scans)) == NULL)
                return;
-       if (wi_scans->next) {
+
+       if ((l = TAILQ_LAST(&wi_scans, wi_scan_head)) && l != w) {
                menu = gtk_menu_new();
-               for (w = wi_scans; w; w = w->next) {
+               ifmenu = true;
+               TAILQ_FOREACH(w, &wi_scans, next) {
                        item = gtk_image_menu_item_new_with_label(
                                w->interface->ifname);
                        image = gtk_image_new_from_icon_name(
@@ -181,34 +354,35 @@ on_activate(GtkStatusIcon *icon)
                        gtk_image_menu_item_set_image(
                                GTK_IMAGE_MENU_ITEM(item), image);
                        gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+                       w->ifmenu = add_scans(w);
                        gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
-                           add_scans(w));
+                           w->ifmenu);
                }
-       } else
-               menu = add_scans(wi_scans);
-
-       if (menu) {
-               gtk_widget_show_all(GTK_WIDGET(menu));
-               gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
-                   gtk_status_icon_position_menu, icon,
-                   1, gtk_get_current_event_time());
+       } else {
+               ifmenu = false;
+               w->ifmenu = menu = add_scans(w);
        }
+
+       gtk_widget_show_all(GTK_WIDGET(menu));
+       gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
+           gtk_status_icon_position_menu, icon,
+           1, gtk_get_current_event_time());
 }
 
 static void
 on_popup(GtkStatusIcon *icon, guint button, guint32 atime, gpointer data)
 {
        DHCPCD_CONNECTION *con;
-       GtkMenu *menu;
+       GtkMenu *mnu;
        GtkWidget *item, *image;
 
        notify_close();
 
        con = (DHCPCD_CONNECTION *)data;
-       menu = (GtkMenu *)gtk_menu_new();
+       mnu = (GtkMenu *)gtk_menu_new();
 
        item = gtk_image_menu_item_new_with_mnemonic(_("_Preferences"));
-       image = gtk_image_new_from_icon_name(GTK_STOCK_PREFERENCES,
+       image = gtk_image_new_from_icon_name("preferences-system-network",
            GTK_ICON_SIZE_MENU);
        gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
        if (g_strcmp0(dhcpcd_status(con), "down") == 0)
@@ -216,32 +390,32 @@ on_popup(GtkStatusIcon *icon, guint button, guint32 atime, gpointer data)
        else
                g_signal_connect(G_OBJECT(item), "activate",
                    G_CALLBACK(on_pref), data);
-       gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+       gtk_menu_shell_append(GTK_MENU_SHELL(mnu), item);
 
        item = gtk_separator_menu_item_new();
-       gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+       gtk_menu_shell_append(GTK_MENU_SHELL(mnu), item);
 
        item = gtk_image_menu_item_new_with_mnemonic(_("_About"));
-       image = gtk_image_new_from_icon_name(GTK_STOCK_ABOUT,
+       image = gtk_image_new_from_icon_name("help-about",
            GTK_ICON_SIZE_MENU);
        gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
        g_signal_connect(G_OBJECT(item), "activate",
            G_CALLBACK(on_about), icon);
-       gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+       gtk_menu_shell_append(GTK_MENU_SHELL(mnu), item);
 
        item = gtk_image_menu_item_new_with_mnemonic(_("_Quit"));
-       image = gtk_image_new_from_icon_name(GTK_STOCK_QUIT,
+       image = gtk_image_new_from_icon_name("application-exit",
            GTK_ICON_SIZE_MENU);
        gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
        g_signal_connect(G_OBJECT(item), "activate",
            G_CALLBACK(on_quit), icon);
-       gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+       gtk_menu_shell_append(GTK_MENU_SHELL(mnu), item);
 
-       gtk_widget_show_all(GTK_WIDGET(menu));
-       gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
+       gtk_widget_show_all(GTK_WIDGET(mnu));
+       gtk_menu_popup(GTK_MENU(mnu), NULL, NULL,
            gtk_status_icon_position_menu, icon, button, atime);
        if (button == 0)
-               gtk_menu_shell_select_first(GTK_MENU_SHELL(menu), FALSE);
+               gtk_menu_shell_select_first(GTK_MENU_SHELL(mnu), FALSE);
 }
 
 void