Make the GTK menu dynamic like the QT port ... has display issues though.
[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 typedef struct wi_menu {
38         DHCPCD_IF *interface;
39         DHCPCD_WI_SCAN *scan;
40
41         GtkWidget *menu;
42         GtkWidget *ssid;
43         GtkWidget *icon;
44         GtkWidget *bar;
45 } WI_MENU;
46 static GList *wi_menus;
47
48 static void
49 on_pref(_unused GObject *o, gpointer data)
50 {
51
52         dhcpcd_prefs_show((DHCPCD_CONNECTION *)data);
53 }
54
55 static void
56 on_quit(void)
57 {
58
59         gtk_main_quit();
60 }
61
62 #if GTK_MAJOR_VERSION == 2
63 static void
64 url_show(GtkAboutDialog *dialog, const char *url)
65 {
66         GdkScreen *screen;
67
68         screen = gtk_widget_get_screen(GTK_WIDGET(dialog));
69         gtk_show_uri(screen, url, GDK_CURRENT_TIME, NULL);
70 }
71
72 static void
73 email_hook(GtkAboutDialog *dialog, const char *url, _unused gpointer data)
74 {
75         char *address;
76
77         address = g_strdup_printf("mailto:%s", url);
78         url_show(dialog, address);
79         g_free(address);
80 }
81
82
83 static void
84 url_hook(GtkAboutDialog *dialog, const char *url, _unused gpointer data)
85 {
86         url_show(dialog, url);
87 }
88 #endif
89
90 static void
91 ssid_hook(GtkMenuItem *item, _unused gpointer data)
92 {
93         DHCPCD_WI_SCAN *scan;
94         WI_SCAN *wi;
95
96         scan = g_object_get_data(G_OBJECT(item), "dhcpcd_wi_scan");
97         wi = wi_scan_find(scan);
98         if (wi) {
99                 DHCPCD_CONNECTION *con;
100
101                 con = dhcpcd_if_connection(wi->interface);
102                 if (con) {
103                         DHCPCD_WPA *wpa;
104
105                         wpa = dhcpcd_wpa_find(con, wi->interface->ifname);
106                         if (wpa)
107                                 wpa_configure(wpa, scan);
108                 }
109         }
110 }
111
112 static void
113 on_about(_unused GtkMenuItem *item)
114 {
115
116         gtk_window_set_default_icon_name("network-transmit-receive");
117 #if GTK_MAJOR_VERSION == 2
118         gtk_about_dialog_set_email_hook(email_hook, NULL, NULL);
119         gtk_about_dialog_set_url_hook(url_hook, NULL, NULL);
120 #endif
121         gtk_show_about_dialog(NULL,
122             "version", VERSION,
123             "copyright", copyright,
124             "website-label", "dhcpcd Website",
125             "website", "http://roy.marples.name/projects/dhcpcd",
126             "authors", authors,
127             "logo-icon-name", "network-transmit-receive",
128             "comments", "Part of the dhcpcd project",
129             NULL);
130 }
131
132 static void
133 update_item(WI_MENU *m, DHCPCD_WI_SCAN *scan)
134 {
135         const char *icon;
136         double perc;
137
138         gtk_label_set_text(GTK_LABEL(m->ssid), scan->ssid);
139         if (scan->flags[0] == '\0')
140                 icon = "network-wireless";
141         else
142                 icon = "network-wireless-encrypted";
143         m->icon = gtk_image_new_from_icon_name(icon,
144             GTK_ICON_SIZE_MENU);
145
146         perc = scan->strength.value / 100.0;
147         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(m->bar), perc);
148
149         if (scan->flags[0] == '\0')
150                 gtk_widget_set_tooltip_text(m->menu, scan->bssid);
151         else {
152                 char *tip = g_strconcat(scan->bssid, " ", scan->flags, NULL);
153                 gtk_widget_set_tooltip_text(m->menu, tip);
154                 g_free(tip);
155         }
156
157         g_object_set_data(G_OBJECT(m->menu), "dhcpcd_wi_scan", scan);
158 }
159
160 static WI_MENU *
161 create_menu(GtkWidget *m, WI_SCAN *scan, DHCPCD_WI_SCAN *wis)
162 {
163         WI_MENU *wim;
164         GtkWidget *box;
165         double perc;
166         const char *icon;
167         char *tip;
168
169         wim = g_malloc(sizeof(*wim));
170         wim->interface = scan->interface;
171         wim->scan = wis;
172         wim->menu = gtk_check_menu_item_new();
173         gtk_check_menu_item_set_draw_as_radio(
174             GTK_CHECK_MENU_ITEM(wim->menu), true);
175         box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
176         gtk_container_add(GTK_CONTAINER(wim->menu), box);
177         wim->ssid = gtk_label_new(wis->ssid);
178         gtk_box_pack_start(GTK_BOX(box), wim->ssid, TRUE, TRUE, 0);
179
180         if (g_strcmp0(wis->ssid, scan->interface->ssid) == 0)
181                 gtk_check_menu_item_set_active(
182                     GTK_CHECK_MENU_ITEM(wim->menu), true);
183         if (wis->flags[0] == '\0')
184                 icon = "network-wireless";
185         else
186                 icon = "network-wireless-encrypted";
187         wim->icon = gtk_image_new_from_icon_name(icon,
188             GTK_ICON_SIZE_MENU);
189         gtk_box_pack_start(GTK_BOX(box), wim->icon, FALSE, FALSE, 0);
190
191         wim->bar = gtk_progress_bar_new();
192         gtk_widget_set_size_request(wim->bar, 100, -1);
193         gtk_box_pack_end(GTK_BOX(box), wim->bar, FALSE, TRUE, 0);
194         perc = wis->strength.value / 100.0;
195         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(wim->bar), perc);
196
197         if (wis->flags[0] == '\0')
198                 gtk_widget_set_tooltip_text(wim->menu, wis->bssid);
199         else {
200                 tip = g_strconcat(wis->bssid, " ", wis->flags, NULL);
201                 gtk_widget_set_tooltip_text(wim->menu, tip);
202                 g_free(tip);
203         }
204
205         g_signal_connect(G_OBJECT(wim->menu), "activate",
206             G_CALLBACK(ssid_hook), NULL);
207         g_object_set_data(G_OBJECT(wim->menu), "dhcpcd_wi_scan", wis);
208         gtk_menu_shell_append(GTK_MENU_SHELL(m), wim->menu);
209
210         return wim;
211 }
212
213 void
214 menu_update_scans(WI_SCAN *wi, DHCPCD_WI_SCAN *scans)
215 {
216         GList *item, *next;
217         WI_MENU *wim;
218         DHCPCD_WI_SCAN *s;
219         bool found, added;
220
221         item = wi_menus;
222         while (item) {
223                 next = item->next;
224                 wim = (WI_MENU *)item->data;
225                 if (wim->interface != wi->interface)
226                         goto cont;
227                 found = false;
228                 for (s = scans; s; s = s->next) {
229                         if (memcmp(wim->scan->bssid, s->bssid,
230                             sizeof(s->bssid)) == 0)
231                         {
232                                 found = true;
233                                 update_item(wim, s);
234                         }
235                 }
236                 if (!found) {
237                         g_message("removed %s", wim->scan->ssid);
238                         gtk_widget_destroy(wim->menu);
239                         g_free(wim);
240                         wi_menus = g_list_delete_link(wi_menus, item);
241                 }
242 cont:
243                 item = next;
244         }
245
246         added = false;
247         for (s = scans; s; s = s->next) {
248                 found = false;
249                 for (item = wi_menus; item; item = item->next) {
250                         wim = (WI_MENU *)item->data;
251                         if (memcmp(wim->scan->bssid, s->bssid,
252                             sizeof(s->bssid)) == 0)
253                         {
254                                 found = true;
255                                 break;
256                         }
257                 }
258                 if (!found) {
259                         added = true;
260                         wim = create_menu(wi->menu, wi, s);
261                         gtk_widget_show_all(wim->menu);
262                         g_message("added %s", wim->scan->ssid);
263                         wi_menus = g_list_append(wi_menus, wim);
264                 }
265         }
266         if (added) {
267                 gtk_widget_hide(wi->menu);
268                 gtk_menu_popup(GTK_MENU(wi->menu), NULL, NULL,
269                     gtk_status_icon_position_menu, sicon,
270                     1, gtk_get_current_event_time());
271                 gtk_widget_show(wi->menu);
272         }
273 }
274
275
276 static GtkWidget *
277 add_scans(WI_SCAN *scan)
278 {
279         GtkWidget *m;
280         DHCPCD_WI_SCAN *wis;
281         WI_MENU *wim;
282
283         if (scan->scans == NULL)
284                 return NULL;
285
286         m = gtk_menu_new();
287         for (wis = scan->scans; wis; wis = wis->next) {
288                 wim = create_menu(m, scan, wis);
289                 wi_menus = g_list_append(wi_menus, wim);
290         }
291         return m;
292 }
293
294 void
295 dhcpcd_menu_abort(void)
296 {
297
298         while (wi_menus) {
299                 g_free(wi_menus->data);
300                 wi_menus = g_list_delete_link(wi_menus, wi_menus);
301         }
302
303         if (menu != NULL) {
304                 gtk_widget_destroy(menu);
305                 g_object_ref_sink(menu);
306                 g_object_unref(menu);
307                 menu = NULL;
308         }
309 }
310
311 static void
312 on_activate(GtkStatusIcon *icon)
313 {
314         WI_SCAN *w;
315         GtkWidget *item, *image;
316
317         sicon = icon;
318         notify_close();
319         dhcpcd_menu_abort();
320         if (wi_scans == NULL)
321                 return;
322         if (wi_scans->next) {
323                 menu = gtk_menu_new();
324                 ifmenu = true;
325                 for (w = wi_scans; w; w = w->next) {
326                         item = gtk_image_menu_item_new_with_label(
327                                 w->interface->ifname);
328                         image = gtk_image_new_from_icon_name(
329                                 "network-wireless", GTK_ICON_SIZE_MENU);
330                         gtk_image_menu_item_set_image(
331                                 GTK_IMAGE_MENU_ITEM(item), image);
332                         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
333                         w->menu = add_scans(w);
334                         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
335                             w->menu);
336                 }
337         } else if (wi_scans->scans) {
338                 ifmenu = false;
339                 wi_scans->menu = menu = add_scans(wi_scans);
340         } else
341                 menu = NULL;
342
343         if (menu) {
344                 gtk_widget_show_all(GTK_WIDGET(menu));
345                 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
346                     gtk_status_icon_position_menu, icon,
347                     1, gtk_get_current_event_time());
348         }
349 }
350
351 static void
352 on_popup(GtkStatusIcon *icon, guint button, guint32 atime, gpointer data)
353 {
354         DHCPCD_CONNECTION *con;
355         GtkMenu *mnu;
356         GtkWidget *item, *image;
357
358         notify_close();
359
360         con = (DHCPCD_CONNECTION *)data;
361         mnu = (GtkMenu *)gtk_menu_new();
362
363         item = gtk_image_menu_item_new_with_mnemonic(_("_Preferences"));
364         image = gtk_image_new_from_icon_name(GTK_STOCK_PREFERENCES,
365             GTK_ICON_SIZE_MENU);
366         gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
367         if (g_strcmp0(dhcpcd_status(con), "down") == 0)
368                 gtk_widget_set_sensitive(item, false);
369         else
370                 g_signal_connect(G_OBJECT(item), "activate",
371                     G_CALLBACK(on_pref), data);
372         gtk_menu_shell_append(GTK_MENU_SHELL(mnu), item);
373
374         item = gtk_separator_menu_item_new();
375         gtk_menu_shell_append(GTK_MENU_SHELL(mnu), item);
376
377         item = gtk_image_menu_item_new_with_mnemonic(_("_About"));
378         image = gtk_image_new_from_icon_name(GTK_STOCK_ABOUT,
379             GTK_ICON_SIZE_MENU);
380         gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
381         g_signal_connect(G_OBJECT(item), "activate",
382             G_CALLBACK(on_about), icon);
383         gtk_menu_shell_append(GTK_MENU_SHELL(mnu), item);
384
385         item = gtk_image_menu_item_new_with_mnemonic(_("_Quit"));
386         image = gtk_image_new_from_icon_name(GTK_STOCK_QUIT,
387             GTK_ICON_SIZE_MENU);
388         gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
389         g_signal_connect(G_OBJECT(item), "activate",
390             G_CALLBACK(on_quit), icon);
391         gtk_menu_shell_append(GTK_MENU_SHELL(mnu), item);
392
393         gtk_widget_show_all(GTK_WIDGET(mnu));
394         gtk_menu_popup(GTK_MENU(mnu), NULL, NULL,
395             gtk_status_icon_position_menu, icon, button, atime);
396         if (button == 0)
397                 gtk_menu_shell_select_first(GTK_MENU_SHELL(mnu), FALSE);
398 }
399
400 void
401 menu_init(GtkStatusIcon *icon, DHCPCD_CONNECTION *con)
402 {
403
404         g_signal_connect(G_OBJECT(icon), "activate",
405             G_CALLBACK(on_activate), con);
406         g_signal_connect(G_OBJECT(icon), "popup_menu",
407             G_CALLBACK(on_popup), con);
408 }
409
410
411 #if GTK_MAJOR_VERSION == 2
412 GtkWidget *
413 gtk_box_new(GtkOrientation o, gint s)
414 {
415
416         if (o == GTK_ORIENTATION_HORIZONTAL)
417                 return gtk_hbox_new(false, s);
418         else
419                 return gtk_vbox_new(false, s);
420 }
421 #endif