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