9375c08b5831f47613e6dbcf620a0f9d4690c852
[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 /* TODO: Animate the icon from carrier -> address
28  * maybe use network-idle -> network-transmit ->
29  * network-receive -> network-transmit-receive */
30
31 #include <locale.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <libnotify/notify.h>
36
37 #include "dhcpcd-gtk.h"
38 #include "menu.h"
39
40 DBusGProxy *dbus = NULL;
41 GList *interfaces = NULL;
42
43 static GtkStatusIcon *status_icon;
44 static gint ani_timer;
45 static int ani_counter;
46 static gboolean online;
47 static gboolean carrier;
48 static char **interface_order;
49 static NotifyNotification *nn;
50
51 const char *const up_reasons[] = {
52         "BOUND",
53         "RENEW",
54         "REBIND",
55         "REBOOT",
56         "IPV4LL",
57         "INFORM",
58         "TIMEOUT",
59         NULL
60 };
61
62 const char *const down_reasons[] = {
63         "EXPIRE",
64         "FAIL",
65         "NAK",
66         "NOCARRIER",
67         "STOP",
68         NULL
69 };
70
71 static gboolean
72 ignore_if_msg(const struct if_msg *ifm)
73 {
74         if (g_strcmp0(ifm->reason, "STOP") == 0 ||
75             g_strcmp0(ifm->reason, "RELEASE") == 0)
76                 return TRUE;
77         return FALSE;
78 }
79
80 static void
81 free_if_msg(struct if_msg *ifm)
82 {
83         g_free(ifm->name);
84         g_free(ifm->reason);
85         g_free(ifm->ssid);
86         g_free(ifm);
87 }
88
89 static void
90 error_exit(const char *msg, GError *error)
91 {
92         GtkWidget *dialog;
93
94         if (error) {
95                 g_critical("%s: %s", msg, error->message);
96                 dialog = gtk_message_dialog_new(NULL,
97                                                 0,
98                                                 GTK_MESSAGE_ERROR,
99                                                 GTK_BUTTONS_CLOSE,
100                                                 "%s: %s",
101                                                 msg,
102                                                 error->message);
103         } else {
104                 g_critical("%s", msg);
105                 dialog = gtk_message_dialog_new(NULL,
106                                                 0,
107                                                 GTK_MESSAGE_ERROR,
108                                                 GTK_BUTTONS_CLOSE,
109                                                 "%s",
110                                                 msg);
111         }
112         gtk_dialog_run(GTK_DIALOG(dialog));
113         gtk_widget_destroy(dialog);
114         if (gtk_main_level())
115                 gtk_main_quit();
116         else
117                 exit(EXIT_FAILURE);
118 }
119
120 GList *
121 get_scan_results(const char *iface)
122 {
123         GType otype;
124         GPtrArray *array = NULL;
125         GValueArray *item;
126         GError *error = NULL;
127         GList *list = NULL;
128         struct if_ap *ifa;
129         guint i;
130         GValue *v;
131
132         /* Below code causes dbus to go belly up for some reason :/ */
133         return NULL;
134         otype = dbus_g_type_get_struct("GValueArray",
135                                        G_TYPE_STRING,
136                                        G_TYPE_UINT,
137                                        G_TYPE_UINT,
138                                        G_TYPE_STRING,
139                                        G_TYPE_STRING,
140                                        G_TYPE_INVALID);
141         otype = dbus_g_type_get_collection("GPtrArray", otype);
142         if (!dbus_g_proxy_call(dbus, "GetScanResults", &error,
143                                G_TYPE_STRING, &iface, G_TYPE_INVALID,
144                                otype, &array, G_TYPE_INVALID)) {
145                 g_message("GetScanResults: %s\n", error->message);
146                 g_clear_error(&error);
147                 return NULL;
148         }
149
150         printf ("got %d\n", array->len);
151         for (i = 0; i < array->len; i++) {
152                 ifa = g_malloc(sizeof(*ifa));
153                 item = g_ptr_array_index(array, i);
154                 v = g_value_array_get_nth(item, 0);
155                 ifa->bssid = g_strdup(g_value_get_string(v));
156                 v = g_value_array_get_nth(item, 1);
157                 ifa->freq = g_value_get_uint(v);
158                 v = g_value_array_get_nth(item, 2);
159                 ifa->level = g_value_get_uint(v);
160                 v = g_value_array_get_nth(item, 3);
161                 ifa->flags = g_strdup(g_value_get_string(v));
162                 v = g_value_array_get_nth(item, 3);
163                 ifa->ssid = g_strdup(g_value_get_string(v));
164                 list = g_list_append(list, ifa);
165         }
166         g_ptr_array_free(array, TRUE);
167         return list;
168 }
169
170 static struct if_msg *
171 make_if_msg(GHashTable *config)
172 {
173         GValue *val;
174         struct if_msg *ifm;
175
176         val = g_hash_table_lookup(config, "Interface");
177         if (val == NULL)
178                 return NULL;
179         ifm = g_malloc0(sizeof(*ifm));
180         ifm->name = g_strdup(g_value_get_string(val));
181         val = g_hash_table_lookup(config, "Reason");
182         if (val)
183                 ifm->reason = g_strdup(g_value_get_string(val));
184         val = g_hash_table_lookup(config, "Wireless");
185         if (val)
186                 ifm->wireless = g_value_get_boolean(val);
187         if (ifm->wireless) {
188                 val = g_hash_table_lookup(config, "SSID");
189                 if (val)
190                         ifm->ssid = g_strdup(g_value_get_string(val));
191         }
192         val = g_hash_table_lookup(config, "IPAddress");
193         if (val)
194                 ifm->ip.s_addr = g_value_get_uint(val);
195         val = g_hash_table_lookup(config, "SubnetCIDR");
196         if (val)
197                 ifm->cidr = g_value_get_uchar(val);
198         val = g_hash_table_lookup(config, "InterfaceOrder");
199         if (val) {
200                 g_strfreev(interface_order);
201                 interface_order = g_strsplit(g_value_get_string(val), " ", 0);
202         }
203         if (ifm->wireless)
204                 ifm->scan_results = get_scan_results(ifm->name);
205         return ifm;
206 }
207
208 static gboolean
209 if_up(const struct if_msg *ifm)
210 {
211         const char *const *r;
212
213         for (r = up_reasons; *r; r++)
214                 if (g_strcmp0(*r, ifm->reason) == 0)
215                         return TRUE;
216         return FALSE;
217 }
218
219 static char *
220 print_if_msg(const struct if_msg *ifm)
221 {
222         char *msg, *p;
223         const char *reason = NULL;
224         size_t len;
225         gboolean showip, showssid;
226     
227         showip = TRUE;
228         showssid = FALSE;
229         if (if_up(ifm))
230                 reason = N_("Acquired address");
231         else {
232                 if (g_strcmp0(ifm->reason, "EXPIRE") == 0)
233                         reason = N_("Failed to renew");
234                 else if (g_strcmp0(ifm->reason, "CARRIER") == 0) {
235                         if (ifm->wireless) {
236                                 reason = N_("Asssociated with");
237                                 if (ifm->ssid != NULL)
238                                         showssid = TRUE;
239                         } else
240                                 reason = N_("Cable plugged in");
241                         showip = FALSE;
242                 } else if (g_strcmp0(ifm->reason, "NOCARRIER") == 0) {
243                         if (ifm->wireless) {
244                                 if (ifm->ssid != NULL || ifm->ip.s_addr != 0) {
245                                         reason = N_("Disassociated from");
246                                         showssid = TRUE;
247                                 } else
248                                     reason = N_("Not associated");
249                         } else
250                                 reason = N_("Cable unplugged");
251                         showip = FALSE;
252                 }
253         }
254         if (reason == NULL)
255                 reason = ifm->reason;
256         
257         len = strlen(ifm->name) + 3;
258         len += strlen(reason) + 1;
259         if (ifm->ip.s_addr != 0) {
260                 len += 16; /* 000. * 4 */
261                 if (ifm->cidr != 0)
262                         len += 3; /* /32 */
263         }
264         if (showssid)
265                 len += strlen(ifm->ssid) + 1;
266         msg = p = g_malloc(len);
267         p += g_snprintf(msg, len, "%s: %s", ifm->name, reason);
268         if (showssid)
269                 p += g_snprintf(p, len - (p - msg), " %s", ifm->ssid);
270         if (ifm->ip.s_addr != 0 && showip) {
271                 p += g_snprintf(p, len - (p - msg), " %s", inet_ntoa(ifm->ip));
272                 if (ifm->cidr != 0)
273                         g_snprintf(p, len - (p - msg), "/%d", ifm->cidr);
274         }
275         return msg;
276 }
277
278 static gint
279 if_msg_comparer(gconstpointer a, gconstpointer b)
280 {
281         const struct if_msg *ifa, *ifb;
282         const char *const *order;
283
284         ifa = (const struct if_msg *)a;
285         ifb = (const struct if_msg *)b;
286         for (order = (const char *const *)interface_order; *order; order++) {
287                 if (g_strcmp0(*order, ifa->name) == 0)
288                         return -1;
289                 if (g_strcmp0(*order, ifb->name) == 0)
290                         return 1;
291         }
292         return 0;
293 }
294
295 static gboolean
296 animate_carrier(_unused gpointer data)
297 {
298         if (ani_timer == 0)
299                 return FALSE;
300
301         switch(ani_counter++) {
302         case 0:
303                 gtk_status_icon_set_from_icon_name(status_icon, "network-transmit");
304                 break;
305         case 1:
306                 gtk_status_icon_set_from_icon_name(status_icon, "network-receive");
307                 break;
308         default:
309                 gtk_status_icon_set_from_icon_name(status_icon, "network-idle");
310                 ani_counter = 0;
311                 break;
312         }
313         return TRUE;
314 }
315
316 static gboolean
317 animate_online(gpointer data)
318 {
319         if (ani_timer == 0)
320                 return FALSE;
321
322         if (ani_counter++ > 6) {
323                 ani_timer = 0;
324                 ani_counter = 0;
325                 return FALSE;
326         }
327
328         if (ani_counter % 2 == GPOINTER_TO_INT(data))
329                 gtk_status_icon_set_from_icon_name(status_icon, "network-idle");
330         else
331                 gtk_status_icon_set_from_icon_name(status_icon, "network-transmit-receive");
332         return TRUE;
333 }
334
335 static void
336 update_online(char **buffer)
337 {
338         gboolean ison, iscarrier;
339         char *msg, *msgs, *tmp;
340         GSourceFunc ani_func;
341         const GList *gl;
342         const struct if_msg *ifm;
343         gpointer toggle;
344
345         ison = iscarrier = FALSE;
346         msgs = NULL;
347         for (gl = interfaces; gl; gl = gl->next) {
348                 ifm = (const struct if_msg *)gl->data;
349                 if (if_up(ifm))
350                         ison = iscarrier = TRUE;
351                 if (!iscarrier && g_strcmp0(ifm->reason, "CARRIER") == 0)
352                         iscarrier = TRUE;
353                 msg = print_if_msg(ifm);
354                 if (msgs) {
355                         tmp = g_strconcat(msgs, "\n", msg, NULL);
356                         g_free(msgs);
357                         g_free(msg);
358                         msgs = tmp;
359                 } else
360                         msgs = msg;
361         }
362
363         if (online != ison || carrier != iscarrier) {
364                 online = ison;
365                 if (ani_timer != 0) {
366                         g_source_remove(ani_timer);
367                         ani_timer = 0;
368                         ani_counter = 0;
369                 }
370                 if (ison) {
371                         toggle = GINT_TO_POINTER(0);
372                         ani_func = animate_online;
373                 } else if (iscarrier) {
374                         toggle = NULL;
375                         ani_func = animate_carrier;
376                 } else {
377                         toggle = GINT_TO_POINTER(1);
378                         ani_func = animate_online;
379                 }
380                 ani_timer = g_timeout_add(300, ani_func, toggle);
381                 ani_func(toggle);
382         }
383         gtk_status_icon_set_tooltip(status_icon, msgs);
384         if (buffer)
385                 *buffer = msgs;
386         else
387                 g_free(msgs);
388 }
389
390 void
391 notify_close(void)
392 {
393         if (nn != NULL)
394                 notify_notification_close(nn, NULL);
395 }
396
397 static void
398 notify_closed(void)
399 {
400         nn = NULL;
401 }
402
403 static void
404 notify(const char *title, const char *msg, const char *icon)
405 {
406         char **msgs, **m;
407
408         msgs = g_strsplit(msg, "\n", 0);
409         for (m = msgs; *m; m++)
410                 g_message("%s", *m);
411         g_strfreev(msgs);
412         if (nn != NULL)
413                 notify_notification_close(nn, NULL);
414         if (gtk_status_icon_get_visible(status_icon))
415                 nn = notify_notification_new_with_status_icon(title,
416                                                               msg,
417                                                               icon,
418                                                               status_icon);
419         else
420                 nn = notify_notification_new(title, msg, icon, NULL);
421         notify_notification_set_timeout(nn, 5000);
422         g_signal_connect(nn, "closed", G_CALLBACK(notify_closed), NULL);
423         notify_notification_show(nn, NULL);
424 }
425
426 static void
427 dhcpcd_event(_unused DBusGProxy *proxy, GHashTable *config, _unused void *data)
428 {
429         struct if_msg *ifm, *ifp;
430         gboolean rem;
431         GList *gl;
432         char *msg, *title;
433         const char *act, *net;
434         const char *const *r;
435         in_addr_t ipn;
436
437         ifm = make_if_msg(config);
438         if (ifm == NULL)
439                 return;
440
441         rem = ignore_if_msg(ifm);
442         ifp = NULL;
443         for (gl = interfaces; gl; gl = gl->next) {
444                 ifp = (struct if_msg *)gl->data;
445                 if (g_strcmp0(ifp->name, ifm->name) == 0) {
446                         free_if_msg(ifp);
447                         if (rem)
448                                 interfaces = g_list_delete_link(interfaces, gl);
449                         else
450                                 gl->data = ifm;
451                         break;
452                 }
453         }
454         if (ifp == NULL && !rem)
455                 interfaces = g_list_prepend(interfaces, ifm);
456         interfaces = g_list_sort(interfaces, if_msg_comparer);
457         update_online(NULL);
458
459         /* We should ignore renew and stop so we don't annoy the user */
460         if (g_strcmp0(ifm->reason, "RENEW") == 0 ||
461             g_strcmp0(ifm->reason, "STOP") == 0)
462                 return;
463
464         msg = print_if_msg(ifm);
465         title = NULL;
466         if (if_up(ifm))
467                 act = N_("Connected to ");
468         else
469                 act = NULL;
470         for (r = down_reasons; *r; r++) {
471                 if (g_strcmp0(*r, ifm->reason) == 0) {
472                         act = N_("Disconnected from ");
473                         break;
474                 }
475         }
476         if (act && ifm->ip.s_addr) {
477                 ipn = htonl(ifm->ip.s_addr);
478                 if (IN_LINKLOCAL(ipn))
479                         net = N_("private network");
480                 else if (IN_PRIVATE(ipn))
481                         net = N_("LAN");
482                 else
483                         net = N_("internet");
484                 title = g_strconcat(act, net, NULL);
485         }
486
487         if (title) {
488                 notify(title, msg, GTK_STOCK_NETWORK);
489                 g_free(title);
490         } else
491                 notify("Interface event", msg, GTK_STOCK_NETWORK);
492         g_free(msg);
493 }
494
495 static void
496 foreach_make_ifm(_unused gpointer key, gpointer value, _unused gpointer data)
497 {
498         struct if_msg *ifm;
499
500         ifm = make_if_msg((GHashTable *)value);
501         if (ignore_if_msg(ifm))
502                 g_free(ifm);
503         else if (ifm)
504                 interfaces = g_list_prepend(interfaces, ifm);
505 }
506
507 static void
508 dhcpcd_get_interfaces()
509 {
510         GHashTable *ifs;
511         GError *error = NULL;
512         GType otype;
513         char *msg;
514
515         otype = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE);
516         otype = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, otype);
517         if (!dbus_g_proxy_call(dbus, "GetInterfaces", &error,
518                                G_TYPE_INVALID,
519                                otype, &ifs, G_TYPE_INVALID))
520                 error_exit("GetInterfaces", error);
521         g_hash_table_foreach(ifs, foreach_make_ifm, NULL);
522         g_hash_table_unref(ifs);
523
524         /* Each interface config only remembers the last order when
525          * that interface was configured, so get the real order now. */
526         g_strfreev(interface_order);
527         interface_order = NULL;
528         if (!dbus_g_proxy_call(dbus, "ListInterfaces", &error,
529                                G_TYPE_INVALID,
530                                G_TYPE_STRV, &interface_order, G_TYPE_INVALID))
531                 error_exit("ListInterfaces", error);
532         interfaces = g_list_sort(interfaces, if_msg_comparer);
533         msg = NULL;
534         update_online(&msg);
535         // GTK+ 2.16 msg = gtk_status_icon_get_tooltip_text(status_icon);
536         if (msg != NULL) {
537                 notify("Interface status", msg, GTK_STOCK_NETWORK);
538                 g_free(msg);
539         }
540 }
541
542 static void
543 check_status(const char *status)
544 {
545         static char *last = NULL;
546         GList *gl;
547         char *version;
548         const char *msg;
549         gboolean refresh;
550         GError *error = NULL;
551
552         g_message("Status changed to %s", status);
553         if (g_strcmp0(status, "down") == 0) {
554                 for (gl = interfaces; gl; gl = gl->next)
555                         free_if_msg((struct if_msg *)gl->data);
556                 g_list_free(interfaces);
557                 interfaces = NULL;
558                 update_online(NULL);
559                 msg = N_(last? "Connection to dhcpcd lost" : "dhcpcd not running");
560                 gtk_status_icon_set_tooltip(status_icon, msg);
561                 notify(_("No network"), msg, GTK_STOCK_NETWORK);
562         }
563
564         refresh = FALSE;
565         if (last == NULL) {
566                 if (g_strcmp0(status, "down") != 0)
567                         refresh = TRUE;
568         } else {
569                 if (g_strcmp0(status, last) == 0)
570                         return;
571                 if (g_strcmp0(last, "down") == 0)
572                         refresh = TRUE;
573                 g_free(last);
574         }
575         last = g_strdup(status);
576
577         if (!refresh)
578                 return;
579         if (!dbus_g_proxy_call(dbus, "GetDhcpcdVersion", &error,
580                                G_TYPE_INVALID,
581                                G_TYPE_STRING, &version, G_TYPE_INVALID))
582                 error_exit(_("GetDhcpcdVersion"), error);
583         g_message(_("Connected to %s-%s"), "dhcpcd", version);
584         g_free(version);
585         dhcpcd_get_interfaces();
586 }
587
588 static void
589 dhcpcd_status(_unused DBusGProxy *proxy, const char *status, _unused void *data)
590 {
591         check_status(status);
592 }
593
594 int
595 main(int argc, char *argv[])
596 {
597         DBusGConnection *bus;
598         GError *error = NULL;
599         char *version = NULL;
600         GType otype;
601         int tries = 5;
602
603                 
604         setlocale(LC_ALL, "");
605         bindtextdomain(PACKAGE, NULL);
606         bind_textdomain_codeset(PACKAGE, "UTF-8");
607         textdomain(PACKAGE); 
608
609         gtk_init(&argc, &argv);
610         g_set_application_name("dhcpcd Monitor");
611         status_icon = gtk_status_icon_new_from_icon_name("network-offline");
612         if (status_icon == NULL)
613                 status_icon = gtk_status_icon_new_from_stock(GTK_STOCK_DISCONNECT);
614         //network_offline = gtk_status_icon_get_pixbuf(status_icon);
615         
616         gtk_status_icon_set_tooltip(status_icon, _("Connecting to dhcpcd ..."));
617         gtk_status_icon_set_visible(status_icon, TRUE);
618
619         notify_init(PACKAGE);
620
621         g_message(_("Connecting to dbus ..."));
622         bus = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
623         if (bus == NULL || error != NULL)
624                 error_exit(_("Could not connect to system bus"), error);
625         dbus = dbus_g_proxy_new_for_name(bus,
626                                               DHCPCD_SERVICE,
627                                               DHCPCD_PATH,
628                                               DHCPCD_SERVICE);
629
630         g_message(_("Connecting to dhcpcd-dbus ..."));
631         while (--tries > 0) {
632                 g_clear_error(&error);
633                 if (dbus_g_proxy_call_with_timeout(dbus,
634                                                    "GetVersion",
635                                                    500,
636                                                    &error,
637                                                    G_TYPE_INVALID,
638                                                    G_TYPE_STRING,
639                                                    &version,
640                                                    G_TYPE_INVALID))
641                         break;
642         }
643         if (tries == 0)
644                 error_exit(_("GetVersion"), error);
645         g_message(_("Connected to %s-%s"), "dhcpcd-dbus", version);
646         g_free(version);
647
648         gtk_status_icon_set_tooltip(status_icon, _("Triggering dhcpcd ..."));
649         online = FALSE;
650         menu_init(status_icon);
651
652         if (!dbus_g_proxy_call(dbus, "GetStatus", &error,
653                                G_TYPE_INVALID,
654                                G_TYPE_STRING, &version, G_TYPE_INVALID))
655                 error_exit(_("GetStatus"), error);
656         check_status(version);
657         g_free(version);
658
659         otype = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE);
660         dbus_g_proxy_add_signal(dbus, "Event",
661                                 otype, G_TYPE_INVALID);
662         dbus_g_proxy_connect_signal(dbus, "Event",
663                                     G_CALLBACK(dhcpcd_event),
664                                     NULL, NULL);
665         dbus_g_proxy_add_signal(dbus, "StatusChanged",
666                                 G_TYPE_STRING, G_TYPE_INVALID);
667         dbus_g_proxy_connect_signal(dbus, "StatusChanged",
668                                     G_CALLBACK(dhcpcd_status),
669                                     NULL, NULL);
670
671         gtk_main();
672         return 0;
673 }