Prettyify the messages and don't notify on renew or stopping.
[dhcpcd-ui] / main.c
1 /*
2  * dhcpcd-gtk
3  * Copyright 2009 Roy Marples <roy@marples.name>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26
27 #include <arpa/inet.h>
28
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include <dbus/dbus-glib.h>
33 #include <gtk/gtk.h>
34 #include <libnotify/notify.h>
35
36 #include "config.h"
37 #include "menu.h"
38
39 /* Work out if we have a private address or not
40  * 10/8
41  * 172.16/12
42  * 192.168/16
43  */
44 #ifndef IN_PRIVATE
45 # define IN_PRIVATE(addr) (((addr & IN_CLASSA_NET) == 0x0a000000) || \
46                            ((addr & 0xfff00000)    == 0xac100000) || \
47                            ((addr & IN_CLASSB_NET) == 0xc0a80000))
48 #endif
49 #ifndef IN_LINKLOCAL
50 # define IN_LINKLOCAL(addr) ((addr & IN_CLASSB_NET) == 0xa9fe0000)
51 #endif
52
53 struct if_msg {
54         char *name;
55         char *reason;
56         struct in_addr ip;
57         unsigned char cidr;
58         gboolean wireless;
59         char *ssid;
60 };
61
62 static DBusGProxy *bus_proxy;
63 static GtkStatusIcon *status_icon;
64 static GList *interfaces;
65 static gboolean online;
66 static NotifyNotification *nn;
67
68 static char **interface_order;
69
70 const char *const up_reasons[] = {
71         "BOUND",
72         "RENEW",
73         "REBIND",
74         "REBOOT",
75         "IPV4LL",
76         "INFORM",
77         "TIMEOUT",
78         NULL
79 };
80
81 const char *const down_reasons[] = {
82         "EXPIRE",
83         "FAIL",
84         "NAK",
85         "NOCARRIER",
86         "STOP",
87         NULL
88 };
89
90 /* Should be in a header */
91 void notify_close(void);
92
93 static gboolean
94 ignore_if_msg(const struct if_msg *ifm)
95 {
96         if (g_strcmp0(ifm->reason, "STOP") == 0 ||
97             g_strcmp0(ifm->reason, "RELEASE") == 0)
98                 return TRUE;
99         return FALSE;
100 }
101
102 static void
103 free_if_msg(struct if_msg *ifm)
104 {
105         g_free(ifm->name);
106         g_free(ifm->reason);
107         g_free(ifm->ssid);
108         g_free(ifm);
109 }
110
111 static void
112 error_exit(const char *msg, GError *error)
113 {
114         GtkWidget *dialog;
115
116         if (error) {
117                 g_critical("%s: %s", msg, error->message);
118                 dialog = gtk_message_dialog_new(NULL,
119                                                 0,
120                                                 GTK_MESSAGE_ERROR,
121                                                 GTK_BUTTONS_CLOSE,
122                                                 "%s: %s",
123                                                 msg,
124                                                 error->message);
125         } else {
126                 g_critical("%s", msg);
127                 dialog = gtk_message_dialog_new(NULL,
128                                                 0,
129                                                 GTK_MESSAGE_ERROR,
130                                                 GTK_BUTTONS_CLOSE,
131                                                 "%s",
132                                                 msg);
133         }
134         gtk_dialog_run(GTK_DIALOG(dialog));
135         gtk_widget_destroy(dialog);
136         if (gtk_main_level())
137                 gtk_main_quit();
138         else
139                 exit(EXIT_FAILURE);
140 }
141
142 static struct if_msg *
143 make_if_msg(GHashTable *config)
144 {
145         GValue *val;
146         struct if_msg *ifm;
147
148         val = g_hash_table_lookup(config, "Interface");
149         if (val == NULL)
150                 return NULL;
151         ifm = g_malloc0(sizeof(*ifm));
152         ifm->name = g_strdup(g_value_get_string(val));
153         val = g_hash_table_lookup(config, "Reason");
154         if (val)
155                 ifm->reason = g_strdup(g_value_get_string(val));
156         val = g_hash_table_lookup(config, "Wireless");
157         if (val)
158                 ifm->wireless = g_value_get_boolean(val);
159         if (ifm->wireless) {
160                 val = g_hash_table_lookup(config, "SSID");
161                 if (val)
162                         ifm->ssid = g_strdup(g_value_get_string(val));
163         }
164         val = g_hash_table_lookup(config, "IPAddress");
165         if (val)
166                 ifm->ip.s_addr = g_value_get_uint(val);
167         val = g_hash_table_lookup(config, "SubnetCIDR");
168         if (val)
169                 ifm->cidr = g_value_get_uchar(val);
170         val = g_hash_table_lookup(config, "InterfaceOrder");
171         if (val) {
172                 g_strfreev(interface_order);
173                 interface_order = g_strsplit(g_value_get_string(val), " ", 0);
174         }
175         return ifm;
176 }
177
178 static gboolean
179 if_up(const struct if_msg *ifm)
180 {
181         const char *const *r;
182
183         for (r = up_reasons; *r; r++)
184                 if (g_strcmp0(*r, ifm->reason) == 0)
185                         return TRUE;
186         return FALSE;
187 }
188
189 static char *
190 print_if_msg(const struct if_msg *ifm)
191 {
192         char *msg, *p;
193         const char *reason = NULL;
194         size_t len;
195         gboolean showip, showssid;
196     
197         showip = TRUE;
198         showssid = FALSE;
199         if (if_up(ifm))
200                 reason = "Acquired address";
201         else {
202                 if (g_strcmp0(ifm->reason, "EXPIRE") == 0)
203                         reason = "Failed to renew";
204                 else if (g_strcmp0(ifm->reason, "CARRIER") == 0) {
205                         if (ifm->wireless) {
206                                 reason = "Asssociated to";
207                                 if (ifm->ssid != NULL)
208                                         showssid = TRUE;
209                         } else
210                                 reason = "Cable plugged in";
211                         showip = FALSE;
212                 } else if (g_strcmp0(ifm->reason, "NOCARRIER") == 0) {
213                         if (ifm->wireless)
214                                 reason = "Not associated";
215                         else
216                                 reason = "Cable unplugged";
217                         showip = FALSE;
218                 }
219         }
220         if (reason == NULL)
221                 reason = ifm->reason;
222         
223         len = strlen(ifm->name) + 3;
224         len += strlen(reason) + 1;
225         if (ifm->ip.s_addr != 0) {
226                 len += 16; /* 000. * 4 */
227                 if (ifm->cidr != 0)
228                         len += 3; /* /32 */
229         }
230         if (showssid)
231                 len += strlen(ifm->ssid) + 1;
232         msg = p = g_malloc(len);
233         p += g_snprintf(msg, len, "%s: %s", ifm->name, reason);
234         if (showssid)
235                 p += g_snprintf(p, len - (p - msg), " %s", ifm->ssid);
236         if (ifm->ip.s_addr != 0 && showip) {
237                 p += g_snprintf(p, len - (p - msg), " %s", inet_ntoa(ifm->ip));
238                 if (ifm->cidr != 0)
239                         g_snprintf(p, len - (p - msg), "/%d", ifm->cidr);
240         }
241         return msg;
242 }
243
244 static gint
245 if_msg_comparer(gconstpointer a, gconstpointer b)
246 {
247         const struct if_msg *ifa, *ifb;
248         const char *const *order;
249
250         ifa = (const struct if_msg *)a;
251         ifb = (const struct if_msg *)b;
252         for (order = (const char *const *)interface_order; *order; order++) {
253                 if (g_strcmp0(*order, ifa->name) == 0)
254                         return -1;
255                 if (g_strcmp0(*order, ifb->name) == 0)
256                         return 1;
257         }
258         return 0;
259 }
260
261 static void
262 update_online(char **buffer)
263 {
264         gboolean ison;
265         char *msg, *msgs, *tmp;
266         const char *icon;
267         const GList *gl;
268         const struct if_msg *ifm;
269
270         ison = FALSE;
271         msgs = NULL;
272         for (gl = interfaces; gl; gl = gl->next) {
273                 ifm = (const struct if_msg *)gl->data;
274                 if (if_up(ifm))
275                         ison = TRUE;
276                 msg = print_if_msg(ifm);
277                 if (msgs) {
278                         tmp = g_strconcat(msgs, "\n", msg, NULL);
279                         g_free(msgs);
280                         g_free(msg);
281                         msgs = tmp;
282                 } else
283                         msgs = msg;
284         }
285
286         if (online != ison) {
287                 online = ison;
288                 icon = online ? GTK_STOCK_CONNECT : GTK_STOCK_DISCONNECT;
289                 gtk_status_icon_set_from_stock(status_icon, icon);
290         }
291         gtk_status_icon_set_tooltip(status_icon, msgs);
292         if (buffer)
293                 *buffer = msgs;
294         else
295                 g_free(msgs);
296 }
297
298 void
299 notify_close(void)
300 {
301         if (nn != NULL)
302                 notify_notification_close(nn, NULL);
303 }
304
305 static void
306 notify_closed(void)
307 {
308         nn = NULL;
309 }
310
311 static void
312 notify(const char *title, const char *msg, const char *icon)
313 {
314         char **msgs, **m;
315
316         msgs = g_strsplit(msg, "\n", 0);
317         for (m = msgs; *m; m++)
318                 g_message("%s", *m);
319         g_strfreev(msgs);
320         if (nn != NULL)
321                 notify_notification_close(nn, NULL);
322         if (gtk_status_icon_get_visible(status_icon))
323                 nn = notify_notification_new_with_status_icon(title,
324                                                               msg,
325                                                               icon,
326                                                               status_icon);
327         else
328                 nn = notify_notification_new(title, msg, icon, NULL);
329         notify_notification_set_timeout(nn, 5000);
330         g_signal_connect(nn, "closed", G_CALLBACK(notify_closed), NULL);
331         notify_notification_show(nn, NULL);
332 }
333
334 static void
335 dhcpcd_event(_unused DBusGProxy *proxy, GHashTable *config, _unused void *data)
336 {
337         struct if_msg *ifm, *ifp;
338         gboolean rem;
339         GList *gl;
340         char *msg, *title;
341         const char *act, *net;
342         const char *const *r;
343         in_addr_t ipn;
344
345         ifm = make_if_msg(config);
346         if (ifm == NULL)
347                 return;
348
349         rem = ignore_if_msg(ifm);
350         ifp = NULL;
351         for (gl = interfaces; gl; gl = gl->next) {
352                 ifp = (struct if_msg *)gl->data;
353                 if (g_strcmp0(ifp->name, ifm->name) == 0) {
354                         free_if_msg(ifp);
355                         if (rem)
356                                 interfaces = g_list_delete_link(interfaces, gl);
357                         else
358                                 gl->data = ifm;
359                         break;
360                 }
361         }
362         if (ifp == NULL && !rem)
363                 interfaces = g_list_prepend(interfaces, ifm);
364         interfaces = g_list_sort(interfaces, if_msg_comparer);
365         update_online(NULL);
366
367         /* We should ignore renew and stop so we don't annoy the user */
368         if (g_strcmp0(ifm->reason, "RENEW") == 0 ||
369             g_strcmp0(ifm->reason, "STOP") == 0)
370                 return;
371
372         msg = print_if_msg(ifm);
373         title = NULL;
374         if (if_up(ifm))
375                 act = "Connected to ";
376         else
377                 act = NULL;
378         for (r = down_reasons; *r; r++) {
379                 if (g_strcmp0(*r, ifm->reason) == 0) {
380                         act = "Disconnected from ";
381                         break;
382                 }
383         }
384         if (act && ifm->ip.s_addr) {
385                 ipn = htonl(ifm->ip.s_addr);
386                 if (IN_LINKLOCAL(ipn))
387                         net = "private network";
388                 else if (IN_PRIVATE(ipn))
389                         net = "LAN";
390                 else
391                         net = "internet";
392                 title = g_strconcat(act, net, NULL);
393         }
394
395         if (title) {
396                 notify(title, msg, GTK_STOCK_NETWORK);
397                 g_free(title);
398         } else
399                 notify("Interface event", msg, GTK_STOCK_NETWORK);
400         g_free(msg);
401 }
402
403 static void
404 foreach_make_ifm(_unused gpointer key, gpointer value, _unused gpointer data)
405 {
406         struct if_msg *ifm;
407
408         ifm = make_if_msg((GHashTable *)value);
409         if (ignore_if_msg(ifm))
410                 g_free(ifm);
411         else if (ifm)
412                 interfaces = g_list_prepend(interfaces, ifm);
413 }
414
415 static void
416 dhcpcd_get_interfaces()
417 {
418         GHashTable *ifs;
419         GError *error = NULL;
420         GType otype;
421         char *msg;
422
423         otype = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE);
424         otype = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, otype);
425         if (!dbus_g_proxy_call(bus_proxy, "GetInterfaces", &error,
426                                G_TYPE_INVALID,
427                                otype, &ifs, G_TYPE_INVALID))
428                 error_exit("GetInterfaces", error);
429         g_hash_table_foreach(ifs, foreach_make_ifm, NULL);
430         g_hash_table_unref(ifs);
431
432         /* Each interface config only remembers the last order when
433          * that interface was configured, so get the real order now. */
434         g_strfreev(interface_order);
435         interface_order = NULL;
436         if (!dbus_g_proxy_call(bus_proxy, "ListInterfaces", &error,
437                                G_TYPE_INVALID,
438                                G_TYPE_STRV, &interface_order, G_TYPE_INVALID))
439                 error_exit("ListInterfaces", error);
440         interfaces = g_list_sort(interfaces, if_msg_comparer);
441         msg = NULL;
442         update_online(&msg);
443         // GTK+ 2.16 msg = gtk_status_icon_get_tooltip_text(status_icon);
444         if (msg != NULL) {
445                 notify("Interface status", msg, GTK_STOCK_NETWORK);
446                 g_free(msg);
447         }
448 }
449
450 static void
451 check_status(const char *status)
452 {
453         static char *last = NULL;
454         GList *gl;
455         char *version;
456         const char *msg;
457         gboolean refresh;
458         GError *error = NULL;
459
460         g_message("status changed to %s", status);
461         if (g_strcmp0(status, "down") == 0) {
462                 for (gl = interfaces; gl; gl = gl->next)
463                         free_if_msg((struct if_msg *)gl->data);
464                 g_list_free(interfaces);
465                 interfaces = NULL;
466                 update_online(NULL);
467                 msg = last? "Connection to dhcpcd lost" : "dhcpcd not running";
468                 gtk_status_icon_set_tooltip(status_icon, msg);
469                 notify("No network", msg, GTK_STOCK_NETWORK);
470         }
471
472         refresh = FALSE;
473         if (last == NULL) {
474                 if (g_strcmp0(status, "down") != 0)
475                         refresh = TRUE;
476         } else {
477                 if (g_strcmp0(status, last) == 0)
478                         return;
479                 if (g_strcmp0(last, "down") == 0)
480                         refresh = TRUE;
481                 g_free(last);
482         }
483         last = g_strdup(status);
484
485         if (!refresh)
486                 return;
487         if (!dbus_g_proxy_call(bus_proxy, "GetDhcpcdVersion", &error,
488                                G_TYPE_INVALID,
489                                G_TYPE_STRING, &version, G_TYPE_INVALID))
490                 error_exit("GetDhcpcdVersion", error);
491         g_message("Connected to dhcpcd-%s", version);
492         g_free(version);
493         dhcpcd_get_interfaces();
494 }
495
496 static void
497 dhcpcd_status(_unused DBusGProxy *proxy, const char *status, _unused void *data)
498 {
499         check_status(status);
500 }
501
502 int
503 main(int argc, char *argv[])
504 {
505         DBusGConnection *bus;
506         GError *error = NULL;
507         char *version = NULL;
508         GType otype;
509         
510         gtk_init(&argc, &argv);
511         g_set_application_name("dhcpcd Monitor");
512         status_icon = gtk_status_icon_new_from_stock(GTK_STOCK_DISCONNECT);
513         gtk_status_icon_set_tooltip(status_icon, "Connecting to dhcpcd ...");
514         gtk_status_icon_set_visible(status_icon, TRUE);
515
516         notify_init(PACKAGE);
517
518         bus = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
519         if (bus == NULL || error != NULL)
520                 error_exit("could not connect to system bus", error);
521         bus_proxy = dbus_g_proxy_new_for_name(bus,
522                                               DHCPCD_SERVICE,
523                                               DHCPCD_PATH,
524                                               DHCPCD_SERVICE);
525         if (!dbus_g_proxy_call(bus_proxy, "GetVersion", &error,
526                                G_TYPE_INVALID,
527                                G_TYPE_STRING, &version, G_TYPE_INVALID))
528                 error_exit("GetVersion", error);
529         g_message("Connected to dhcpcd-dbus-%s", version);
530         g_free(version);
531
532         gtk_status_icon_set_tooltip(status_icon, "Triggering dhcpcd ...");
533         online = FALSE;
534         menu_init(status_icon);
535
536         if (!dbus_g_proxy_call(bus_proxy, "GetStatus", &error,
537                                G_TYPE_INVALID,
538                                G_TYPE_STRING, &version, G_TYPE_INVALID))
539                 error_exit("GetStatus", error);
540         check_status(version);
541         g_free(version);
542
543         otype = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE);
544         dbus_g_proxy_add_signal(bus_proxy, "Event",
545                                 otype, G_TYPE_INVALID);
546         dbus_g_proxy_connect_signal(bus_proxy, "Event",
547                                     G_CALLBACK(dhcpcd_event),
548                                     NULL, NULL);
549         dbus_g_proxy_add_signal(bus_proxy, "StatusChanged",
550                                 G_TYPE_STRING, G_TYPE_INVALID);
551         dbus_g_proxy_connect_signal(bus_proxy, "StatusChanged",
552                                     G_CALLBACK(dhcpcd_status),
553                                     NULL, NULL);
554
555         gtk_main();
556         return 0;
557 }