We should use gettext.
[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 static struct if_msg *
119 make_if_msg(GHashTable *config)
120 {
121         GValue *val;
122         struct if_msg *ifm;
123
124         val = g_hash_table_lookup(config, "Interface");
125         if (val == NULL)
126                 return NULL;
127         ifm = g_malloc0(sizeof(*ifm));
128         ifm->name = g_strdup(g_value_get_string(val));
129         val = g_hash_table_lookup(config, "Reason");
130         if (val)
131                 ifm->reason = g_strdup(g_value_get_string(val));
132         val = g_hash_table_lookup(config, "Wireless");
133         if (val)
134                 ifm->wireless = g_value_get_boolean(val);
135         if (ifm->wireless) {
136                 val = g_hash_table_lookup(config, "SSID");
137                 if (val)
138                         ifm->ssid = g_strdup(g_value_get_string(val));
139         }
140         val = g_hash_table_lookup(config, "IPAddress");
141         if (val)
142                 ifm->ip.s_addr = g_value_get_uint(val);
143         val = g_hash_table_lookup(config, "SubnetCIDR");
144         if (val)
145                 ifm->cidr = g_value_get_uchar(val);
146         val = g_hash_table_lookup(config, "InterfaceOrder");
147         if (val) {
148                 g_strfreev(interface_order);
149                 interface_order = g_strsplit(g_value_get_string(val), " ", 0);
150         }
151         return ifm;
152 }
153
154 static gboolean
155 if_up(const struct if_msg *ifm)
156 {
157         const char *const *r;
158
159         for (r = up_reasons; *r; r++)
160                 if (g_strcmp0(*r, ifm->reason) == 0)
161                         return TRUE;
162         return FALSE;
163 }
164
165 static char *
166 print_if_msg(const struct if_msg *ifm)
167 {
168         char *msg, *p;
169         const char *reason = NULL;
170         size_t len;
171         gboolean showip, showssid;
172     
173         showip = TRUE;
174         showssid = FALSE;
175         if (if_up(ifm))
176                 reason = N_("Acquired address");
177         else {
178                 if (g_strcmp0(ifm->reason, "EXPIRE") == 0)
179                         reason = N_("Failed to renew");
180                 else if (g_strcmp0(ifm->reason, "CARRIER") == 0) {
181                         if (ifm->wireless) {
182                                 reason = N_("Asssociated with");
183                                 if (ifm->ssid != NULL)
184                                         showssid = TRUE;
185                         } else
186                                 reason = N_("Cable plugged in");
187                         showip = FALSE;
188                 } else if (g_strcmp0(ifm->reason, "NOCARRIER") == 0) {
189                         if (ifm->wireless) {
190                                 if (ifm->ssid != NULL || ifm->ip.s_addr != 0) {
191                                         reason = N_("Lost association with");
192                                         showssid = TRUE;
193                                 } else
194                                     reason = N_("Not associated");
195                         } else
196                                 reason = N_("Cable unplugged");
197                         showip = FALSE;
198                 }
199         }
200         if (reason == NULL)
201                 reason = ifm->reason;
202         
203         len = strlen(ifm->name) + 3;
204         len += strlen(reason) + 1;
205         if (ifm->ip.s_addr != 0) {
206                 len += 16; /* 000. * 4 */
207                 if (ifm->cidr != 0)
208                         len += 3; /* /32 */
209         }
210         if (showssid)
211                 len += strlen(ifm->ssid) + 1;
212         msg = p = g_malloc(len);
213         p += g_snprintf(msg, len, "%s: %s", ifm->name, reason);
214         if (showssid)
215                 p += g_snprintf(p, len - (p - msg), " %s", ifm->ssid);
216         if (ifm->ip.s_addr != 0 && showip) {
217                 p += g_snprintf(p, len - (p - msg), " %s", inet_ntoa(ifm->ip));
218                 if (ifm->cidr != 0)
219                         g_snprintf(p, len - (p - msg), "/%d", ifm->cidr);
220         }
221         return msg;
222 }
223
224 static gint
225 if_msg_comparer(gconstpointer a, gconstpointer b)
226 {
227         const struct if_msg *ifa, *ifb;
228         const char *const *order;
229
230         ifa = (const struct if_msg *)a;
231         ifb = (const struct if_msg *)b;
232         for (order = (const char *const *)interface_order; *order; order++) {
233                 if (g_strcmp0(*order, ifa->name) == 0)
234                         return -1;
235                 if (g_strcmp0(*order, ifb->name) == 0)
236                         return 1;
237         }
238         return 0;
239 }
240
241 static void
242 update_online(char **buffer)
243 {
244         gboolean ison, iscarrier;
245         char *msg, *msgs, *tmp;
246         const char *icon;
247         const GList *gl;
248         const struct if_msg *ifm;
249
250         ison = iscarrier = FALSE;
251         msgs = NULL;
252         for (gl = interfaces; gl; gl = gl->next) {
253                 ifm = (const struct if_msg *)gl->data;
254                 if (if_up(ifm))
255                         ison = iscarrier = TRUE;
256                 if (!iscarrier && g_strcmp0(ifm->reason, "CARRIER") == 0)
257                         iscarrier = TRUE;
258                 msg = print_if_msg(ifm);
259                 if (msgs) {
260                         tmp = g_strconcat(msgs, "\n", msg, NULL);
261                         g_free(msgs);
262                         g_free(msg);
263                         msgs = tmp;
264                 } else
265                         msgs = msg;
266         }
267
268         if (online != ison || carrier != iscarrier) {
269                 online = ison;
270                 if (ison)
271                         icon = "network-transmit-receive";
272                 else if (iscarrier)
273                         icon = "network-transmit";
274                 else
275                         icon = "network-offline";
276                 gtk_status_icon_set_from_icon_name(status_icon, icon);
277         }
278         gtk_status_icon_set_tooltip(status_icon, msgs);
279         if (buffer)
280                 *buffer = msgs;
281         else
282                 g_free(msgs);
283 }
284
285 void
286 notify_close(void)
287 {
288         if (nn != NULL)
289                 notify_notification_close(nn, NULL);
290 }
291
292 static void
293 notify_closed(void)
294 {
295         nn = NULL;
296 }
297
298 static void
299 notify(const char *title, const char *msg, const char *icon)
300 {
301         char **msgs, **m;
302
303         msgs = g_strsplit(msg, "\n", 0);
304         for (m = msgs; *m; m++)
305                 g_message("%s", *m);
306         g_strfreev(msgs);
307         if (nn != NULL)
308                 notify_notification_close(nn, NULL);
309         if (gtk_status_icon_get_visible(status_icon))
310                 nn = notify_notification_new_with_status_icon(title,
311                                                               msg,
312                                                               icon,
313                                                               status_icon);
314         else
315                 nn = notify_notification_new(title, msg, icon, NULL);
316         notify_notification_set_timeout(nn, 5000);
317         g_signal_connect(nn, "closed", G_CALLBACK(notify_closed), NULL);
318         notify_notification_show(nn, NULL);
319 }
320
321 static void
322 dhcpcd_event(_unused DBusGProxy *proxy, GHashTable *config, _unused void *data)
323 {
324         struct if_msg *ifm, *ifp;
325         gboolean rem;
326         GList *gl;
327         char *msg, *title;
328         const char *act, *net;
329         const char *const *r;
330         in_addr_t ipn;
331
332         ifm = make_if_msg(config);
333         if (ifm == NULL)
334                 return;
335
336         rem = ignore_if_msg(ifm);
337         ifp = NULL;
338         for (gl = interfaces; gl; gl = gl->next) {
339                 ifp = (struct if_msg *)gl->data;
340                 if (g_strcmp0(ifp->name, ifm->name) == 0) {
341                         free_if_msg(ifp);
342                         if (rem)
343                                 interfaces = g_list_delete_link(interfaces, gl);
344                         else
345                                 gl->data = ifm;
346                         break;
347                 }
348         }
349         if (ifp == NULL && !rem)
350                 interfaces = g_list_prepend(interfaces, ifm);
351         interfaces = g_list_sort(interfaces, if_msg_comparer);
352         update_online(NULL);
353
354         /* We should ignore renew and stop so we don't annoy the user */
355         if (g_strcmp0(ifm->reason, "RENEW") == 0 ||
356             g_strcmp0(ifm->reason, "STOP") == 0)
357                 return;
358
359         msg = print_if_msg(ifm);
360         title = NULL;
361         if (if_up(ifm))
362                 act = N_("Connected to ");
363         else
364                 act = NULL;
365         for (r = down_reasons; *r; r++) {
366                 if (g_strcmp0(*r, ifm->reason) == 0) {
367                         act = N_("Disconnected from ");
368                         break;
369                 }
370         }
371         if (act && ifm->ip.s_addr) {
372                 ipn = htonl(ifm->ip.s_addr);
373                 if (IN_LINKLOCAL(ipn))
374                         net = N_("private network");
375                 else if (IN_PRIVATE(ipn))
376                         net = N_("LAN");
377                 else
378                         net = N_("internet");
379                 title = g_strconcat(act, net, NULL);
380         }
381
382         if (title) {
383                 notify(title, msg, GTK_STOCK_NETWORK);
384                 g_free(title);
385         } else
386                 notify("Interface event", msg, GTK_STOCK_NETWORK);
387         g_free(msg);
388 }
389
390 static void
391 foreach_make_ifm(_unused gpointer key, gpointer value, _unused gpointer data)
392 {
393         struct if_msg *ifm;
394
395         ifm = make_if_msg((GHashTable *)value);
396         if (ignore_if_msg(ifm))
397                 g_free(ifm);
398         else if (ifm)
399                 interfaces = g_list_prepend(interfaces, ifm);
400 }
401
402 static void
403 dhcpcd_get_interfaces()
404 {
405         GHashTable *ifs;
406         GError *error = NULL;
407         GType otype;
408         char *msg;
409
410         otype = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE);
411         otype = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, otype);
412         if (!dbus_g_proxy_call(dbus, "GetInterfaces", &error,
413                                G_TYPE_INVALID,
414                                otype, &ifs, G_TYPE_INVALID))
415                 error_exit("GetInterfaces", error);
416         g_hash_table_foreach(ifs, foreach_make_ifm, NULL);
417         g_hash_table_unref(ifs);
418
419         /* Each interface config only remembers the last order when
420          * that interface was configured, so get the real order now. */
421         g_strfreev(interface_order);
422         interface_order = NULL;
423         if (!dbus_g_proxy_call(dbus, "ListInterfaces", &error,
424                                G_TYPE_INVALID,
425                                G_TYPE_STRV, &interface_order, G_TYPE_INVALID))
426                 error_exit("ListInterfaces", error);
427         interfaces = g_list_sort(interfaces, if_msg_comparer);
428         msg = NULL;
429         update_online(&msg);
430         // GTK+ 2.16 msg = gtk_status_icon_get_tooltip_text(status_icon);
431         if (msg != NULL) {
432                 notify("Interface status", msg, GTK_STOCK_NETWORK);
433                 g_free(msg);
434         }
435 }
436
437 static void
438 check_status(const char *status)
439 {
440         static char *last = NULL;
441         GList *gl;
442         char *version;
443         const char *msg;
444         gboolean refresh;
445         GError *error = NULL;
446
447         g_message("Status changed to %s", status);
448         if (g_strcmp0(status, "down") == 0) {
449                 for (gl = interfaces; gl; gl = gl->next)
450                         free_if_msg((struct if_msg *)gl->data);
451                 g_list_free(interfaces);
452                 interfaces = NULL;
453                 update_online(NULL);
454                 msg = N_(last? "Connection to dhcpcd lost" : "dhcpcd not running");
455                 gtk_status_icon_set_tooltip(status_icon, msg);
456                 notify(_("No network"), msg, GTK_STOCK_NETWORK);
457         }
458
459         refresh = FALSE;
460         if (last == NULL) {
461                 if (g_strcmp0(status, "down") != 0)
462                         refresh = TRUE;
463         } else {
464                 if (g_strcmp0(status, last) == 0)
465                         return;
466                 if (g_strcmp0(last, "down") == 0)
467                         refresh = TRUE;
468                 g_free(last);
469         }
470         last = g_strdup(status);
471
472         if (!refresh)
473                 return;
474         if (!dbus_g_proxy_call(dbus, "GetDhcpcdVersion", &error,
475                                G_TYPE_INVALID,
476                                G_TYPE_STRING, &version, G_TYPE_INVALID))
477                 error_exit(_("GetDhcpcdVersion"), error);
478         g_message(_("Connected to %s-%s"), "dhcpcd", version);
479         g_free(version);
480         dhcpcd_get_interfaces();
481 }
482
483 static void
484 dhcpcd_status(_unused DBusGProxy *proxy, const char *status, _unused void *data)
485 {
486         check_status(status);
487 }
488
489 int
490 main(int argc, char *argv[])
491 {
492         DBusGConnection *bus;
493         GError *error = NULL;
494         char *version = NULL;
495         GType otype;
496         int tries = 5;
497
498                 
499         setlocale(LC_ALL, "");
500         bindtextdomain(PACKAGE, NULL);
501         bind_textdomain_codeset(PACKAGE, "UTF-8");
502         textdomain(PACKAGE); 
503
504         gtk_init(&argc, &argv);
505         g_set_application_name("dhcpcd Monitor");
506         status_icon = gtk_status_icon_new_from_icon_name("network-offline");
507         if (status_icon == NULL)
508                 status_icon = gtk_status_icon_new_from_stock(GTK_STOCK_DISCONNECT);
509         
510         gtk_status_icon_set_tooltip(status_icon, _("Connecting to dhcpcd ..."));
511         gtk_status_icon_set_visible(status_icon, TRUE);
512
513         notify_init(PACKAGE);
514
515         g_message(_("Connecting to dbus ..."));
516         bus = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
517         if (bus == NULL || error != NULL)
518                 error_exit(_("Could not connect to system bus"), error);
519         dbus = dbus_g_proxy_new_for_name(bus,
520                                               DHCPCD_SERVICE,
521                                               DHCPCD_PATH,
522                                               DHCPCD_SERVICE);
523
524         g_message(_("Connecting to dhcpcd-dbus ..."));
525         while (--tries > 0) {
526                 g_clear_error(&error);
527                 if (dbus_g_proxy_call_with_timeout(dbus,
528                                                    "GetVersion",
529                                                    500,
530                                                    &error,
531                                                    G_TYPE_INVALID,
532                                                    G_TYPE_STRING,
533                                                    &version,
534                                                    G_TYPE_INVALID))
535                         break;
536         }
537         if (tries == 0)
538                 error_exit(_("GetVersion"), error);
539         g_message(_("Connected to %s-%s"), "dhcpcd-dbus", version);
540         g_free(version);
541
542         gtk_status_icon_set_tooltip(status_icon, _("Triggering dhcpcd ..."));
543         online = FALSE;
544         menu_init(status_icon);
545
546         if (!dbus_g_proxy_call(dbus, "GetStatus", &error,
547                                G_TYPE_INVALID,
548                                G_TYPE_STRING, &version, G_TYPE_INVALID))
549                 error_exit(_("GetStatus"), error);
550         check_status(version);
551         g_free(version);
552
553         otype = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE);
554         dbus_g_proxy_add_signal(dbus, "Event",
555                                 otype, G_TYPE_INVALID);
556         dbus_g_proxy_connect_signal(dbus, "Event",
557                                     G_CALLBACK(dhcpcd_event),
558                                     NULL, NULL);
559         dbus_g_proxy_add_signal(dbus, "StatusChanged",
560                                 G_TYPE_STRING, G_TYPE_INVALID);
561         dbus_g_proxy_connect_signal(dbus, "StatusChanged",
562                                     G_CALLBACK(dhcpcd_status),
563                                     NULL, NULL);
564
565         gtk_main();
566         return 0;
567 }