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