Re-write libdhcpcd to talk to dhcpcd directly and not use DBus.
[dhcpcd-ui] / src / dhcpcd-gtk / main.c
index 2754d5c901baf493a89f251272aec6da20a927fd..b16f721395a67539412ddb6cb345d99f0c6fb361 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * dhcpcd-gtk
- * Copyright 2009 Roy Marples <roy@marples.name>
+ * Copyright 2009-2014 Roy Marples <roy@marples.name>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * SUCH DAMAGE.
  */
 
+#include <errno.h>
 #include <locale.h>
-#include <poll.h>
 #include <stdlib.h>
 #include <string.h>
 
 #ifdef NOTIFY
 #  include <libnotify/notify.h>
+#ifndef NOTIFY_CHECK_VERSION
+#  define NOTIFY_CHECK_VERSION(a,b,c) 0
+#endif
 static NotifyNotification *nn;
 #endif
 
 #include "config.h"
+#include "dhcpcd.h"
 #include "dhcpcd-gtk.h"
 
 static GtkStatusIcon *status_icon;
-static int ani_timer;
+static guint ani_timer;
 static int ani_counter;
 static bool online;
 static bool carrier;
 
 struct watch {
-       struct pollfd pollfd;
-       int eventid;
+       gpointer ref;
+       int fd;
+       guint eventid;
        GIOChannel *gio;
        struct watch *next;
 };
@@ -53,6 +58,9 @@ static struct watch *watches;
 
 WI_SCAN *wi_scans;
 
+static gboolean dhcpcd_try_open(gpointer data);
+static gboolean dhcpcd_wpa_try_open(gpointer data);
+
 WI_SCAN *
 wi_scan_find(DHCPCD_WI_SCAN *scan)
 {
@@ -73,7 +81,7 @@ static gboolean
 animate_carrier(_unused gpointer data)
 {
        const char *icon;
-       
+
        if (ani_timer == 0)
                return false;
 
@@ -97,7 +105,7 @@ static gboolean
 animate_online(_unused gpointer data)
 {
        const char *icon;
-       
+
        if (ani_timer == 0)
                return false;
 
@@ -126,23 +134,26 @@ update_online(DHCPCD_CONNECTION *con, bool showif)
        msgs = NULL;
        ifs = dhcpcd_interfaces(con);
        for (i = ifs; i; i = i->next) {
-               if (showif)
-                       g_message("%s: %s", i->ifname, i->reason);
-               if (strcmp(i->reason, "RELEASE") == 0 ||
-                   strcmp(i->reason, "STOP") == 0)
-                       continue;
-               if (dhcpcd_if_up(i))
-                       ison = iscarrier = true;
-               if (!iscarrier && g_strcmp0(i->reason, "CARRIER") == 0)
-                       iscarrier = true;
+               if (g_strcmp0(i->type, "link") == 0) {
+                       if (i->up)
+                               iscarrier = true;
+               } else {
+                       if (i->up)
+                               ison = true;
+               }
                msg = dhcpcd_if_message(i);
-               if (msgs) {
-                       tmp = g_strconcat(msgs, "\n", msg, NULL);
-                       g_free(msgs);
-                       g_free(msg);
-                       msgs = tmp;
-               } else
-                       msgs = msg;
+               if (msg) {
+                       if (showif)
+                               g_message("%s", msg);
+                       if (msgs) {
+                               tmp = g_strconcat(msgs, "\n", msg, NULL);
+                               g_free(msgs);
+                               g_free(msg);
+                               msgs = tmp;
+                       } else
+                               msgs = msg;
+               } else if (showif)
+                       g_message("%s: %s", i->ifname, i->reason);
        }
 
        if (online != ison || carrier != iscarrier) {
@@ -163,7 +174,7 @@ update_online(DHCPCD_CONNECTION *con, bool showif)
                            "network-offline");
                }
        }
-       gtk_status_icon_set_tooltip(status_icon, msgs);
+       gtk_status_icon_set_tooltip_text(status_icon, msgs);
        g_free(msgs);
 }
 
@@ -176,31 +187,44 @@ notify_close(void)
 #endif
 }
 
+#ifdef NOTIFY
+static char *notify_last_msg;
+
 static void
 notify_closed(void)
 {
-#ifdef NOTIFY
        nn = NULL;
-#endif
 }
 
-#ifdef NOTIFY
 static void
 notify(const char *title, const char *msg, const char *icon)
 {
-       char **msgs, **m;
 
-       msgs = g_strsplit(msg, "\n", 0);
-       for (m = msgs; *m; m++)
-               g_message("%s", *m);
-       g_strfreev(msgs);
+       if (msg == NULL)
+               return;
+       /* Don't spam the same message */
+       if (notify_last_msg) {
+               if (notify_last_msg && strcmp(msg, notify_last_msg) == 0)
+                       return;
+               g_free(notify_last_msg);
+       }
+       notify_last_msg = g_strdup(msg);
+
        if (nn != NULL)
                notify_notification_close(nn, NULL);
+
+#if NOTIFY_CHECK_VERSION(0,7,0)
+       nn = notify_notification_new(title, msg, icon);
+       notify_notification_set_hint(nn, "transient",
+           g_variant_new_boolean(TRUE));
+#else
        if (gtk_status_icon_get_visible(status_icon))
                nn = notify_notification_new_with_status_icon(title,
                    msg, icon, status_icon);
        else
                nn = notify_notification_new(title, msg, icon, NULL);
+#endif
+
        notify_notification_set_timeout(nn, 5000);
        g_signal_connect(nn, "closed", G_CALLBACK(notify_closed), NULL);
        notify_notification_show(nn, NULL);
@@ -210,35 +234,9 @@ notify(const char *title, const char *msg, const char *icon)
 #endif
 
 static void
-event_cb(DHCPCD_CONNECTION *con, DHCPCD_IF *i, _unused void *data)
-{
-       char *msg;
-       const char *icon;
-
-       g_message("%s: %s", i->ifname, i->reason);
-       update_online(con, false);
-       
-       /* We should ignore renew and stop so we don't annoy the user */
-       if (g_strcmp0(i->reason, "RENEW") == 0 ||
-           g_strcmp0(i->reason, "STOP") == 0)
-               return;
-
-       msg = dhcpcd_if_message(i);
-       if (dhcpcd_if_up(i))
-               icon = "network-transmit-receive";
-       else
-               icon = "network-transmit";
-       if (dhcpcd_if_down(i))
-               icon = "network-offline";
-       notify(_("Network event"), msg, icon);
-       g_free(msg);
-}
-
-static void
-status_cb(DHCPCD_CONNECTION *con, const char *status, _unused void *data)
+dhcpcd_status_cb(DHCPCD_CONNECTION *con, const char *status, _unused void *data)
 {
        static char *last = NULL;
-       char *version;
        const char *msg;
        bool refresh;
        WI_SCAN *w;
@@ -247,7 +245,14 @@ status_cb(DHCPCD_CONNECTION *con, const char *status, _unused void *data)
        if (g_strcmp0(status, "down") == 0) {
                msg = N_(last ?
                    "Connection to dhcpcd lost" : "dhcpcd not running");
-               gtk_status_icon_set_tooltip(status_icon, msg);
+               if (ani_timer != 0) {
+                       g_source_remove(ani_timer);
+                       ani_timer = 0;
+                       ani_counter = 0;
+               }
+               gtk_status_icon_set_from_icon_name(status_icon,
+                   "network-offline");
+               gtk_status_icon_set_tooltip_text(status_icon, msg);
                notify(_("No network"), msg, "network-offline");
                dhcpcd_prefs_abort();
                while (wi_scans) {
@@ -257,11 +262,9 @@ status_cb(DHCPCD_CONNECTION *con, const char *status, _unused void *data)
                        wi_scans = w;
                }
        } else {
-               if ((last == NULL || g_strcmp0(last, "down") == 0) &&
-                   dhcpcd_command(con, "GetDhcpcdVersion", NULL, &version))
-               {
-                       g_message(_("Connected to %s-%s"), "dhcpcd", version);
-                       g_free(version);
+               if ((last == NULL || g_strcmp0(last, "down") == 0)) {
+                       g_message(_("Connected to %s-%s"), "dhcpcd",
+                           dhcpcd_version(con));
                        refresh = true;
                } else
                        refresh = false;
@@ -270,26 +273,249 @@ status_cb(DHCPCD_CONNECTION *con, const char *status, _unused void *data)
        last = g_strdup(status);
 }
 
+static struct watch *
+dhcpcd_findwatch(int fd, gpointer data, struct watch **last)
+{
+       struct watch *w;
+
+       if (last)
+               *last = NULL;
+       for (w = watches; w; w = w->next) {
+               if (w->fd == fd || w->ref == data)
+                       return w;
+               if (last)
+                       *last = w;
+       }
+       return NULL;
+}
+
+static void
+dhcpcd_unwatch(int fd, gpointer data)
+{
+       struct watch *w, *l;
+
+       if ((w = dhcpcd_findwatch(fd, data, &l))) {
+               if (l)
+                       l->next = w->next;
+               else
+                       watches = w->next;
+               g_source_remove(w->eventid);
+               g_io_channel_unref(w->gio);
+               g_free(w);
+       }
+}
+
+static gboolean
+dhcpcd_watch(int fd,
+    gboolean (*cb)(GIOChannel *, GIOCondition, gpointer),
+    gpointer data)
+{
+       struct watch *w, *l;
+       GIOChannel *gio;
+       GIOCondition flags;
+       guint eventid;
+
+       /* Sanity */
+       if ((w = dhcpcd_findwatch(fd, data, &l))) {
+               if (w->fd == fd)
+                       return TRUE;
+               if (l)
+                       l->next = w->next;
+               else
+                       watches = w->next;
+               g_source_remove(w->eventid);
+               g_io_channel_unref(w->gio);
+               g_free(w);
+       }
+
+       gio = g_io_channel_unix_new(fd);
+       if (gio == NULL) {
+               g_warning(_("Error creating new GIO Channel\n"));
+               return FALSE;
+       }
+       flags = G_IO_IN | G_IO_ERR | G_IO_HUP;
+       if ((eventid = g_io_add_watch(gio, flags, cb, data)) == 0) {
+               g_warning(_("Error creating watch\n"));
+               g_io_channel_unref(gio);
+               return FALSE;
+       }
+
+       w = g_try_malloc(sizeof(*w));
+       if (w == NULL) {
+               g_warning(_("g_try_malloc\n"));
+               g_source_remove(eventid);
+               g_io_channel_unref(gio);
+               return FALSE;
+       }
+
+       w->ref = data;
+       w->fd = fd;
+       w->eventid = eventid;
+       w->gio = gio;
+       w->next = watches;
+       watches = w;
+
+       return TRUE;
+}
+
+static gboolean
+dhcpcd_cb(_unused GIOChannel *gio, _unused GIOCondition c, gpointer data)
+{
+       DHCPCD_CONNECTION *con;
+
+       con = (DHCPCD_CONNECTION *)data;
+       if (dhcpcd_get_fd(con) == -1) {
+               g_warning(_("dhcpcd connection lost"));
+               dhcpcd_unwatch(-1, con);
+               g_timeout_add(DHCPCD_RETRYOPEN, dhcpcd_try_open, con);
+               return FALSE;
+       }
+
+       dhcpcd_dispatch(con);
+       return TRUE;
+}
+
+static gboolean
+dhcpcd_try_open(gpointer data)
+{
+       DHCPCD_CONNECTION *con;
+       int fd;
+       static int last_error;
+
+       con = (DHCPCD_CONNECTION *)data;
+       fd = dhcpcd_open(con);
+       if (fd == -1) {
+               if (errno != last_error)
+                       g_critical("dhcpcd_open: %s", strerror(errno));
+               last_error = errno;
+               return TRUE;
+       }
+
+       if (!dhcpcd_watch(fd, dhcpcd_cb, con)) {
+               dhcpcd_close(con);
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void
+dhcpcd_if_cb(DHCPCD_IF *i, _unused void *data)
+{
+       DHCPCD_CONNECTION *con;
+       char *msg;
+       const char *icon;
+
+       /* Update the tooltip with connection information */
+       con = dhcpcd_if_connection(i);
+       update_online(con, false);
+
+       /* We should ignore renew and stop so we don't annoy the user */
+       if (g_strcmp0(i->reason, "RENEW") == 0 ||
+           g_strcmp0(i->reason, "STOP") == 0 ||
+           g_strcmp0(i->reason, "STOPPED") == 0)
+               return;
+
+       msg = dhcpcd_if_message(i);
+       if (msg) {
+               g_message("%s", msg);
+               if (i->up)
+                       icon = "network-transmit-receive";
+               //else
+               //      icon = "network-transmit";
+               if (!i->up)
+                       icon = "network-offline";
+               notify(_("Network event"), msg, icon);
+               g_free(msg);
+       }
+}
+
+static gboolean
+dhcpcd_wpa_cb(_unused GIOChannel *gio, _unused GIOCondition c,
+    gpointer data)
+{
+       DHCPCD_WPA *wpa;
+       DHCPCD_IF *i;
+
+       wpa = (DHCPCD_WPA *)data;
+       if (dhcpcd_wpa_get_fd(wpa) == -1) {
+               dhcpcd_unwatch(-1, wpa);
+
+               /* If the interface hasn't left, try re-opening */
+               i = dhcpcd_wpa_if(wpa);
+               if (i == NULL ||
+                   g_strcmp0(i->reason, "DEPARTED") == 0 ||
+                   g_strcmp0(i->reason, "STOPPED") == 0)
+                       return TRUE;
+               g_warning(_("dhcpcd WPA connection lost: %s"), i->ifname);
+               g_timeout_add(DHCPCD_RETRYOPEN, dhcpcd_wpa_try_open, wpa);
+               return FALSE;
+       }
+
+       dhcpcd_wpa_dispatch(wpa);
+       return TRUE;
+}
+
+static gboolean
+dhcpcd_wpa_try_open(gpointer data)
+{
+       DHCPCD_WPA *wpa;
+       int fd;
+       static int last_error;
+
+       wpa = (DHCPCD_WPA *)data;
+       fd = dhcpcd_wpa_open(wpa);
+       if (fd == -1) {
+               if (errno != last_error)
+                       g_critical("dhcpcd_wpa_open: %s", strerror(errno));
+               last_error = errno;
+               return TRUE;
+       }
+
+       if (!dhcpcd_watch(fd, dhcpcd_wpa_cb, wpa)) {
+               dhcpcd_wpa_close(wpa);
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
 static void
-scan_cb(DHCPCD_CONNECTION *con, DHCPCD_IF *i, _unused void *data)
+dhcpcd_wpa_scan_cb(DHCPCD_WPA *wpa, _unused void *data)
 {
+       DHCPCD_IF *i;
        WI_SCAN *w;
        DHCPCD_WI_SCAN *scans, *s1, *s2;
        char *txt, *t;
+       int lerrno, fd;
        const char *msg;
 
-       g_message(_("%s: Received scan results"), i->ifname);
-       scans = dhcpcd_wi_scans(con, i);
-       if (scans == NULL) {
-               g_warning("%s: %s", i->ifname, dhcpcd_error(con));
-               dhcpcd_error_clear(con);
+       /* This could be a new WPA so watch it */
+       fd = dhcpcd_wpa_get_fd(wpa);
+       if (fd == -1) {
+               g_critical("No fd for WPA %p", wpa);
+               dhcpcd_unwatch(-1, wpa);
+               return;
        }
+       dhcpcd_watch(fd, dhcpcd_wpa_cb, wpa);
+
+       i = dhcpcd_wpa_if(wpa);
+       if (i == NULL) {
+               g_critical("No interface for WPA %p", wpa);
+               return;
+       }
+       g_message(_("%s: Received scan results"), i->ifname);
+       lerrno = errno;
+       errno = 0;
+       scans = dhcpcd_wi_scans(i);
+       if (scans == NULL && errno)
+               g_warning("%s: %s", i->ifname, strerror(errno));
+       errno = lerrno;
        for (w = wi_scans; w; w = w->next)
-               if (w->connection == con && w->interface == i)
+               if (w->interface == i)
                        break;
        if (w == NULL) {
                w = g_malloc(sizeof(*w));
-               w->connection = con;
                w->interface = i;
                w->next = wi_scans;
                wi_scans = w;
@@ -321,124 +547,47 @@ scan_cb(DHCPCD_CONNECTION *con, DHCPCD_IF *i, _unused void *data)
        w->scans = scans;
 }
 
-static gboolean
-gio_callback(GIOChannel *gio, _unused GIOCondition c, _unused gpointer d)
-{
-       int fd;
-
-       fd = g_io_channel_unix_get_fd(gio);
-       dhcpcd_dispatch(fd);
-       return true;
-}
-
-static void
-delete_watch_cb(_unused DHCPCD_CONNECTION *con, const struct pollfd *fd,
-    _unused void *data)
-{
-       struct watch *w, *l;
-
-       l = NULL;
-       for (w = watches; w; w = w->next) {
-               if (w->pollfd.fd == fd->fd) {
-                       if (l == NULL)
-                               watches = w->next;
-                       else
-                               l->next = w->next;
-                       g_source_remove(w->eventid);
-                       g_io_channel_unref(w->gio);
-                       g_free(w);
-                       break;
-               }
-       }
-}
-
-static void
-add_watch_cb(DHCPCD_CONNECTION *con, const struct pollfd *fd,
-    _unused void *data)
-{
-       struct watch *w;
-       GIOChannel *gio;
-       int flags, eventid;
-
-       /* Remove any existing watch */
-       delete_watch_cb(con, fd, data);
-       
-       gio = g_io_channel_unix_new(fd->fd);
-       if (gio == NULL) {
-               g_error(_("Error creating new GIO Channel\n"));
-               return;
-       }
-       flags = 0;
-       if (fd->events & POLLIN)
-               flags |= G_IO_IN;
-       if (fd->events & POLLOUT)
-               flags |= G_IO_OUT;
-       if (fd->events & POLLERR)
-               flags |= G_IO_ERR;
-       if (fd->events & POLLHUP)
-               flags |= G_IO_HUP;
-       if ((eventid = g_io_add_watch(gio, flags, gio_callback, con)) == 0) {
-               g_io_channel_unref(gio);
-               g_error(_("Error creating watch\n"));
-               return;
-       }
-       w = g_malloc(sizeof(*w));
-       memcpy(&w->pollfd, fd, sizeof(w->pollfd));
-       w->eventid = eventid;
-       w->gio = gio;
-       w->next = watches;
-       watches = w;
-}
-
 int
 main(int argc, char *argv[])
 {
-       char *error = NULL;
-       char *version = NULL;
        DHCPCD_CONNECTION *con;
-               
+
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, NULL);
        bind_textdomain_codeset(PACKAGE, "UTF-8");
-       textdomain(PACKAGE); 
+       textdomain(PACKAGE);
 
        gtk_init(&argc, &argv);
        g_set_application_name("Network Configurator");
        gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(),
            ICONDIR);
        status_icon = gtk_status_icon_new_from_icon_name("network-offline");
-       
-       gtk_status_icon_set_tooltip(status_icon,
+
+       gtk_status_icon_set_tooltip_text(status_icon,
            _("Connecting to dhcpcd ..."));
        gtk_status_icon_set_visible(status_icon, true);
-
+       online = false;
+#ifdef NOTIFY
        notify_init(PACKAGE);
+#endif
 
        g_message(_("Connecting ..."));
-       con = dhcpcd_open(&error);
+       con = dhcpcd_new();
        if (con ==  NULL) {
-               g_critical("libdhcpcd: %s", error);
+               g_critical("libdhcpcd: %s", strerror(errno));
                exit(EXIT_FAILURE);
        }
-
-       gtk_status_icon_set_tooltip(status_icon, _("Triggering dhcpcd ..."));
-       online = false;
-
-       if (!dhcpcd_command(con, "GetVersion", NULL, &version)) {
-               g_critical("libdhcpcd: GetVersion: %s", dhcpcd_error(con));
-               exit(EXIT_FAILURE);
-       }
-       g_message(_("Connected to %s-%s"), "dhcpcd-dbus", version);
-       g_free(version);
-
-       dhcpcd_set_watch_functions(con, add_watch_cb, delete_watch_cb, NULL);
-       dhcpcd_set_signal_functions(con, event_cb, status_cb, scan_cb, NULL);
-       if (dhcpcd_error(con))
-               g_error("libdhcpcd: %s", dhcpcd_error(con));
+       dhcpcd_set_status_callback(con, dhcpcd_status_cb, NULL);
+       dhcpcd_set_if_callback(con, dhcpcd_if_cb, NULL);
+       dhcpcd_wpa_set_scan_callback(con, dhcpcd_wpa_scan_cb, NULL);
+       //dhcpcd_wpa_set_status_callback(con, dhcpcd_wpa_status_cb, NULL);
+       if (dhcpcd_try_open(con))
+               g_timeout_add(DHCPCD_RETRYOPEN, dhcpcd_try_open, con);
 
        menu_init(status_icon, con);
 
        gtk_main();
        dhcpcd_close(con);
+       dhcpcd_free(con);
        return 0;
 }