5ec3d14f84c6ca17e4285d81176c5b2147eb2a79
[dhcpcd-ui] / src / dhcpcd-gtk / menu.c
1 /*
2  * dhcpcd-gtk
3  * Copyright 2009-2014 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 "config.h"
28 #include "dhcpcd-gtk.h"
29
30 static const char *copyright = "Copyright (c) 2009-2014 Roy Marples";
31 static const char *authors[] = { "Roy Marples <roy@marples.name>", NULL };
32
33 static GtkStatusIcon *sicon;
34 static GtkWidget *menu;
35 static bool ifmenu;
36
37 static void
38 on_pref(_unused GObject *o, gpointer data)
39 {
40
41         prefs_show((DHCPCD_CONNECTION *)data);
42 }
43
44 static void
45 on_quit(void)
46 {
47
48         wpa_abort();
49         gtk_main_quit();
50 }
51
52 #if GTK_MAJOR_VERSION == 2
53 static void
54 url_show(GtkAboutDialog *dialog, const char *url)
55 {
56         GdkScreen *screen;
57
58         screen = gtk_widget_get_screen(GTK_WIDGET(dialog));
59         gtk_show_uri(screen, url, GDK_CURRENT_TIME, NULL);
60 }
61
62 static void
63 email_hook(GtkAboutDialog *dialog, const char *url, _unused gpointer data)
64 {
65         char *address;
66
67         address = g_strdup_printf("mailto:%s", url);
68         url_show(dialog, address);
69         g_free(address);
70 }
71
72
73 static void
74 url_hook(GtkAboutDialog *dialog, const char *url, _unused gpointer data)
75 {
76         url_show(dialog, url);
77 }
78 #endif
79
80 static void
81 ssid_hook(GtkMenuItem *item, _unused gpointer data)
82 {
83         DHCPCD_WI_SCAN *scan;
84         WI_SCAN *wi;
85
86         scan = g_object_get_data(G_OBJECT(item), "dhcpcd_wi_scan");
87         wi = wi_scan_find(scan);
88         if (wi) {
89                 DHCPCD_CONNECTION *con;
90
91                 con = dhcpcd_if_connection(wi->interface);
92                 if (con) {
93                         DHCPCD_WPA *wpa;
94
95                         wpa = dhcpcd_wpa_find(con, wi->interface->ifname);
96                         if (wpa)
97                                 wpa_configure(wpa, scan);
98                 }
99         }
100 }
101
102 static void
103 on_about(_unused GtkMenuItem *item)
104 {
105
106         gtk_window_set_default_icon_name("network-transmit-receive");
107 #if GTK_MAJOR_VERSION == 2
108         gtk_about_dialog_set_email_hook(email_hook, NULL, NULL);
109         gtk_about_dialog_set_url_hook(url_hook, NULL, NULL);
110 #endif
111         gtk_show_about_dialog(NULL,
112             "version", VERSION,
113             "copyright", copyright,
114             "website-label", "dhcpcd Website",
115             "website", "http://roy.marples.name/projects/dhcpcd",
116             "authors", authors,
117             "logo-icon-name", "network-transmit-receive",
118             "comments", "Part of the dhcpcd project",
119             NULL);
120 }
121
122 static const char *
123 get_strength_icon_name(int strength)
124 {
125
126         if (strength > 80)
127                 return "network-wireless-connected-100";
128         else if (strength > 55)
129                 return "network-wireless-connected-75";
130         else if (strength > 30)
131                 return "network-wireless-connected-50";
132         else if (strength > 5)
133                 return "network-wireless-connected-25";
134         else
135                 return "network-wireless-connected-00";
136 }
137
138 static void
139 update_item(WI_SCAN *wi, WI_MENU *m, DHCPCD_WI_SCAN *scan)
140 {
141         const char *icon;
142         GtkWidget *sel;
143
144         m->scan = scan;
145
146         g_object_set_data(G_OBJECT(m->menu), "dhcpcd_wi_scan", scan);
147
148         if (wi->interface->up &&
149             g_strcmp0(scan->ssid, wi->interface->ssid) == 0)
150                 sel = gtk_image_new_from_icon_name("dialog-ok-apply",
151                     GTK_ICON_SIZE_MENU);
152         else
153                 sel = NULL;
154         gtk_image_menu_item_set_image(
155             GTK_IMAGE_MENU_ITEM(m->menu), sel);
156
157         gtk_label_set_text(GTK_LABEL(m->ssid), scan->ssid);
158         if (scan->flags[0] == '\0')
159                 icon = "network-wireless";
160         else
161                 icon = "network-wireless-encrypted";
162         m->icon = gtk_image_new_from_icon_name(icon,
163             GTK_ICON_SIZE_MENU);
164
165         icon = get_strength_icon_name(scan->strength.value);
166         m->strength = gtk_image_new_from_icon_name(icon,
167                 GTK_ICON_SIZE_MENU);
168
169 #if 0
170         if (scan->flags[0] == '\0')
171                 gtk_widget_set_tooltip_text(m->menu, scan->bssid);
172         else {
173                 char *tip = g_strconcat(scan->bssid, " ", scan->flags, NULL);
174                 gtk_widget_set_tooltip_text(m->menu, tip);
175                 g_free(tip);
176         }
177 #endif
178
179         g_object_set_data(G_OBJECT(m->menu), "dhcpcd_wi_scan", scan);
180 }
181
182 static WI_MENU *
183 create_menu(GtkWidget *m, WI_SCAN *wis, DHCPCD_WI_SCAN *scan)
184 {
185         WI_MENU *wim;
186         GtkWidget *box, *sel;
187         const char *icon;
188
189         wim = g_malloc(sizeof(*wim));
190         wim->scan = scan;
191         wim->menu = gtk_image_menu_item_new();
192         box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
193         gtk_container_add(GTK_CONTAINER(wim->menu), box);
194
195         if (wis->interface->up &&
196             g_strcmp0(scan->ssid, wis->interface->ssid) == 0)
197                 sel = gtk_image_new_from_icon_name("dialog-ok-apply",
198                     GTK_ICON_SIZE_MENU);
199         else
200                 sel = NULL;
201         gtk_image_menu_item_set_image(
202             GTK_IMAGE_MENU_ITEM(wim->menu), sel);
203
204         wim->ssid = gtk_label_new(scan->ssid);
205         gtk_misc_set_alignment(GTK_MISC(wim->ssid), 0.0, 0.5);
206         gtk_box_pack_start(GTK_BOX(box), wim->ssid, TRUE, TRUE, 0);
207
208         if (scan->flags[0] == '\0')
209                 icon = "network-wireless";
210         else
211                 icon = "network-wireless-encrypted";
212         wim->icon = gtk_image_new_from_icon_name(icon,
213             GTK_ICON_SIZE_MENU);
214         gtk_box_pack_start(GTK_BOX(box), wim->icon, FALSE, FALSE, 0);
215
216         icon = get_strength_icon_name(scan->strength.value);
217         wim->strength = gtk_image_new_from_icon_name(icon,
218                 GTK_ICON_SIZE_MENU);
219         gtk_box_pack_start(GTK_BOX(box), wim->strength, FALSE, FALSE, 0);
220
221 #if 0
222         if (scan->flags[0] == '\0')
223                 gtk_widget_set_tooltip_text(wim->menu, scan->bssid);
224         else {
225                 tip = g_strconcat(scan->bssid, " ", scan->flags, NULL);
226                 gtk_widget_set_tooltip_text(wim->menu, tip);
227                 g_free(tip);
228         }
229 #endif
230
231         g_signal_connect(G_OBJECT(wim->menu), "activate",
232             G_CALLBACK(ssid_hook), NULL);
233         g_object_set_data(G_OBJECT(wim->menu), "dhcpcd_wi_scan", scan);
234         gtk_menu_shell_append(GTK_MENU_SHELL(m), wim->menu);
235
236         return wim;
237 }
238
239 void
240 menu_update_scans(WI_SCAN *wi, DHCPCD_WI_SCAN *scans)
241 {
242         WI_MENU *wim, *win;
243         DHCPCD_WI_SCAN *s;
244         bool found;
245
246         if (wi->ifmenu == NULL) {
247                 dhcpcd_wi_scans_free(wi->scans);
248                 wi->scans = scans;
249                 return;
250         }
251
252         TAILQ_FOREACH_SAFE(wim, &wi->menus, next, win) {
253                 found = false;
254                 for (s = scans; s; s = s->next) {
255                         if (memcmp(wim->scan->bssid, s->bssid,
256                             sizeof(s->bssid)) == 0)
257                         {
258                                 found = true;
259                                 update_item(wi, wim, s);
260                         }
261                 }
262                 if (!found) {
263                         TAILQ_REMOVE(&wi->menus, wim, next);
264                         gtk_widget_destroy(wim->menu);
265                         g_free(wim);
266                 }
267         }
268
269         for (s = scans; s; s = s->next) {
270                 found = false;
271                 TAILQ_FOREACH(wim, &wi->menus, next) {
272                         if (memcmp(wim->scan->bssid, s->bssid,
273                             sizeof(s->bssid)) == 0)
274                         {
275                                 found = true;
276                                 break;
277                         }
278                 }
279                 if (!found) {
280                         wim = create_menu(wi->ifmenu, wi, s);
281                         TAILQ_INSERT_TAIL(&wi->menus, wim, next);
282                         gtk_widget_show_all(wim->menu);
283                 }
284         }
285
286         dhcpcd_wi_scans_free(wi->scans);
287         wi->scans = scans;
288
289         if (gtk_widget_get_visible(wi->ifmenu))
290                 gtk_menu_reposition(GTK_MENU(wi->ifmenu));
291 }
292
293 void
294 menu_remove_if(WI_SCAN *wi)
295 {
296         WI_MENU *wim;
297
298         if (wi->ifmenu == NULL)
299                 return;
300
301         if (wi->ifmenu == menu)
302                 menu = NULL;
303
304         gtk_widget_destroy(wi->ifmenu);
305         wi->ifmenu = NULL;
306         while ((wim = TAILQ_FIRST(&wi->menus))) {
307                 TAILQ_REMOVE(&wi->menus, wim, next);
308                 g_free(wim);
309         }
310
311         if (menu && gtk_widget_get_visible(menu))
312                 gtk_menu_reposition(GTK_MENU(menu));
313 }
314
315 static GtkWidget *
316 add_scans(WI_SCAN *wi)
317 {
318         GtkWidget *m;
319         DHCPCD_WI_SCAN *wis;
320         WI_MENU *wim;
321
322         if (wi->scans == NULL)
323                 return NULL;
324
325         m = gtk_menu_new();
326         for (wis = wi->scans; wis; wis = wis->next) {
327                 wim = create_menu(m, wi, wis);
328                 TAILQ_INSERT_TAIL(&wi->menus, wim, next);
329         }
330
331         return m;
332 }
333
334 void
335 menu_abort(void)
336 {
337         WI_SCAN *wis;
338         WI_MENU *wim;
339
340         TAILQ_FOREACH(wis, &wi_scans, next) {
341                 wis->ifmenu = NULL;
342                 while ((wim = TAILQ_FIRST(&wis->menus))) {
343                         TAILQ_REMOVE(&wis->menus, wim, next);
344                         g_free(wim);
345                 }
346         }
347
348         if (menu != NULL) {
349                 gtk_widget_destroy(menu);
350                 g_object_ref_sink(menu);
351                 g_object_unref(menu);
352                 menu = NULL;
353         }
354 }
355
356 static void
357 on_activate(GtkStatusIcon *icon)
358 {
359         WI_SCAN *w, *l;
360         GtkWidget *item, *image;
361
362         sicon = icon;
363         notify_close();
364         prefs_abort();
365         menu_abort();
366
367         if ((w = TAILQ_FIRST(&wi_scans)) == NULL)
368                 return;
369
370         if ((l = TAILQ_LAST(&wi_scans, wi_scan_head)) && l != w) {
371                 menu = gtk_menu_new();
372                 ifmenu = true;
373                 TAILQ_FOREACH(w, &wi_scans, next) {
374                         item = gtk_image_menu_item_new_with_label(
375                                 w->interface->ifname);
376                         image = gtk_image_new_from_icon_name(
377                                 "network-wireless", GTK_ICON_SIZE_MENU);
378                         gtk_image_menu_item_set_image(
379                                 GTK_IMAGE_MENU_ITEM(item), image);
380                         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
381                         w->ifmenu = add_scans(w);
382                         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
383                             w->ifmenu);
384                 }
385         } else {
386                 ifmenu = false;
387                 w->ifmenu = menu = add_scans(w);
388         }
389
390         gtk_widget_show_all(GTK_WIDGET(menu));
391         gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
392             gtk_status_icon_position_menu, icon,
393             1, gtk_get_current_event_time());
394 }
395
396 static void
397 on_popup(GtkStatusIcon *icon, guint button, guint32 atime, gpointer data)
398 {
399         DHCPCD_CONNECTION *con;
400         GtkMenu *mnu;
401         GtkWidget *item, *image;
402
403         notify_close();
404
405         con = (DHCPCD_CONNECTION *)data;
406         mnu = (GtkMenu *)gtk_menu_new();
407
408         item = gtk_image_menu_item_new_with_mnemonic(_("_Preferences"));
409         image = gtk_image_new_from_icon_name("preferences-system-network",
410             GTK_ICON_SIZE_MENU);
411         gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
412         if (g_strcmp0(dhcpcd_status(con), "down") == 0)
413                 gtk_widget_set_sensitive(item, false);
414         else
415                 g_signal_connect(G_OBJECT(item), "activate",
416                     G_CALLBACK(on_pref), data);
417         gtk_menu_shell_append(GTK_MENU_SHELL(mnu), item);
418
419         item = gtk_separator_menu_item_new();
420         gtk_menu_shell_append(GTK_MENU_SHELL(mnu), item);
421
422         item = gtk_image_menu_item_new_with_mnemonic(_("_About"));
423         image = gtk_image_new_from_icon_name("help-about",
424             GTK_ICON_SIZE_MENU);
425         gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
426         g_signal_connect(G_OBJECT(item), "activate",
427             G_CALLBACK(on_about), icon);
428         gtk_menu_shell_append(GTK_MENU_SHELL(mnu), item);
429
430         item = gtk_image_menu_item_new_with_mnemonic(_("_Quit"));
431         image = gtk_image_new_from_icon_name("application-exit",
432             GTK_ICON_SIZE_MENU);
433         gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
434         g_signal_connect(G_OBJECT(item), "activate",
435             G_CALLBACK(on_quit), icon);
436         gtk_menu_shell_append(GTK_MENU_SHELL(mnu), item);
437
438         gtk_widget_show_all(GTK_WIDGET(mnu));
439         gtk_menu_popup(GTK_MENU(mnu), NULL, NULL,
440             gtk_status_icon_position_menu, icon, button, atime);
441         if (button == 0)
442                 gtk_menu_shell_select_first(GTK_MENU_SHELL(mnu), FALSE);
443 }
444
445 void
446 menu_init(GtkStatusIcon *icon, DHCPCD_CONNECTION *con)
447 {
448
449         g_signal_connect(G_OBJECT(icon), "activate",
450             G_CALLBACK(on_activate), con);
451         g_signal_connect(G_OBJECT(icon), "popup_menu",
452             G_CALLBACK(on_popup), con);
453 }
454
455
456 #if GTK_MAJOR_VERSION == 2
457 GtkWidget *
458 gtk_box_new(GtkOrientation o, gint s)
459 {
460
461         if (o == GTK_ORIENTATION_HORIZONTAL)
462                 return gtk_hbox_new(false, s);
463         else
464                 return gtk_vbox_new(false, s);
465 }
466 #endif