1 /* $Id$ */
2 /* Copyright (c) 2009-2015 Pierre Pronchery <khorben@defora.org> */
3 /* This file is part of DeforaOS Desktop Panel */
4 /* This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, version 3 of the License.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program.  If not, see <http://www.gnu.org/licenses/>. */
15 
16 
17 
18 #include <sys/stat.h>
19 #if defined(__sun)
20 # include <fcntl.h>
21 #endif
22 #include <dirent.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <libintl.h>
28 #include <System.h>
29 #include "Panel/applet.h"
30 #include "../../config.h"
31 #define _(string) gettext(string)
32 
33 /* constants */
34 #ifndef PREFIX
35 # define PREFIX		"/usr/local"
36 #endif
37 #ifndef BINDIR
38 # define BINDIR		PREFIX "/bin"
39 #endif
40 #ifndef DATADIR
41 # define DATADIR	PREFIX "/share"
42 #endif
43 
44 
45 /* Menu */
46 /* private */
47 /* types */
48 typedef struct _PanelApplet
49 {
50 	PanelAppletHelper * helper;
51 	GSList * apps;
52 	guint idle;
53 	gboolean refresh;
54 	time_t refresh_mti;
55 	GtkWidget * widget;
56 } Menu;
57 
58 typedef struct _MenuMenu
59 {
60 	char const * category;
61 	char const * label;
62 	char const * stock;
63 } MenuMenu;
64 
65 
66 /* constants */
67 static const MenuMenu _menu_menus[] =
68 {
69 	{ "Audio",	"Audio",	"gnome-mime-audio",		},
70 	{ "Development","Development",	"applications-development",	},
71 	{ "Education",	"Education",	"applications-science",		},
72 	{ "Game",	"Games",	"applications-games",		},
73 	{ "Graphics",	"Graphics",	"applications-graphics",	},
74 	{ "AudioVideo",	"Multimedia",	"applications-multimedia",	},
75 	{ "Network",	"Network",	"applications-internet",	},
76 	{ "Office",	"Office",	"applications-office",		},
77 	{ "Settings",	"Settings",	"gnome-settings",		},
78 	{ "System",	"System",	"applications-system",		},
79 	{ "Utility",	"Utilities",	"applications-utilities",	},
80 	{ "Video",	"Video",	"video",			}
81 };
82 #define MENU_MENUS_COUNT (sizeof(_menu_menus) / sizeof(*_menu_menus))
83 
84 
85 /* prototypes */
86 static Menu * _menu_init(PanelAppletHelper * helper, GtkWidget ** widget);
87 static void _menu_destroy(Menu * menu);
88 
89 /* helpers */
90 static GtkWidget * _menu_applications(Menu * menu);
91 static GtkWidget * _menu_icon(Menu * menu, char const * path,
92 		char const * icon);
93 static GtkWidget * _menu_menuitem(Menu * menu, char const * path,
94 		char const * label, char const * icon);
95 static GtkWidget * _menu_menuitem_stock(char const * icon, char const * label,
96 		gboolean mnemonic);
97 
98 static void _menu_xdg_dirs(Menu * menu, void (*callback)(Menu * menu,
99 			char const * path, char const * apppath));
100 
101 /* callbacks */
102 static void _menu_on_about(gpointer data);
103 static void _menu_on_clicked(gpointer data);
104 static gboolean _menu_on_idle(gpointer data);
105 static void _menu_on_lock(gpointer data);
106 static void _menu_on_logout(gpointer data);
107 #ifdef EMBEDDED
108 static void _menu_on_rotate(gpointer data);
109 #endif
110 static void _menu_on_run(gpointer data);
111 static void _menu_on_shutdown(gpointer data);
112 static void _menu_on_suspend(gpointer data);
113 static gboolean _menu_on_timeout(gpointer data);
114 
115 
116 /* public */
117 /* variables */
118 PanelAppletDefinition applet =
119 {
120 	"Main menu",
121 	"start-here",
122 	NULL,
123 	_menu_init,
124 	_menu_destroy,
125 	NULL,
126 	FALSE,
127 	TRUE
128 };
129 
130 
131 /* private */
132 /* functions */
133 /* menu_init */
_menu_init(PanelAppletHelper * helper,GtkWidget ** widget)134 static Menu * _menu_init(PanelAppletHelper * helper, GtkWidget ** widget)
135 {
136 	Menu * menu;
137 	GtkWidget * hbox;
138 	GtkWidget * image;
139 	char const * p;
140 	PangoFontDescription * bold;
141 	GtkWidget * label;
142 
143 	if((menu = malloc(sizeof(*menu))) == NULL)
144 	{
145 		error_set("%s: %s", applet.name, strerror(errno));
146 		return NULL;
147 	}
148 	menu->helper = helper;
149 	menu->apps = NULL;
150 	menu->idle = g_idle_add(_menu_on_idle, menu);
151 	menu->refresh_mti = 0;
152 	menu->widget = gtk_button_new();
153 #if GTK_CHECK_VERSION(3, 0, 0)
154 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
155 #else
156 	hbox = gtk_hbox_new(FALSE, 4);
157 #endif
158 	image = gtk_image_new_from_icon_name("start-here",
159 			panel_window_get_icon_size(helper->window));
160 	gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, TRUE, 0);
161 	/* add some text if configured so */
162 	if((p = helper->config_get(helper->panel, "menu", "text")) != NULL
163 			&& strlen(p) > 0)
164 	{
165 		bold = pango_font_description_new();
166 		pango_font_description_set_weight(bold, PANGO_WEIGHT_BOLD);
167 		label = gtk_label_new(p);
168 #if GTK_CHECK_VERSION(3, 0, 0)
169 		gtk_widget_override_font(label, bold);
170 #else
171 		gtk_widget_modify_font(label, bold);
172 #endif
173 		pango_font_description_free(bold);
174 		gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
175 	}
176 	gtk_button_set_relief(GTK_BUTTON(menu->widget), GTK_RELIEF_NONE);
177 #if GTK_CHECK_VERSION(2, 12, 0)
178 	gtk_widget_set_tooltip_text(menu->widget, _("Main menu"));
179 #endif
180 	g_signal_connect_swapped(menu->widget, "clicked", G_CALLBACK(
181 				_menu_on_clicked), menu);
182 	gtk_container_add(GTK_CONTAINER(menu->widget), hbox);
183 	gtk_widget_show_all(menu->widget);
184 	*widget = menu->widget;
185 	return menu;
186 }
187 
188 
189 /* menu_destroy */
_menu_destroy(Menu * menu)190 static void _menu_destroy(Menu * menu)
191 {
192 	if(menu->idle != 0)
193 		g_source_remove(menu->idle);
194 	g_slist_foreach(menu->apps, (GFunc)config_delete, NULL);
195 	g_slist_free(menu->apps);
196 	gtk_widget_destroy(menu->widget);
197 	free(menu);
198 }
199 
200 
201 /* helpers */
202 /* menu_applications */
203 static void _applications_on_activate(gpointer data);
204 static void _applications_on_activate_application(Config * config);
205 static void _applications_on_activate_directory(Config * config);
206 static void _applications_on_activate_url(Config * config);
207 static void _applications_categories(GtkWidget * menu, GtkWidget ** menus);
208 
_menu_applications(Menu * menu)209 static GtkWidget * _menu_applications(Menu * menu)
210 {
211 	GtkWidget * menus[MENU_MENUS_COUNT];
212 	GSList * p;
213 	GtkWidget * menushell;
214 	GtkWidget * menuitem;
215 	Config * config;
216 	const char section[] = "Desktop Entry";
217 	char const * name;
218 #if GTK_CHECK_VERSION(2, 12, 0)
219 	char const * comment;
220 #endif
221 	char const * q;
222 	char const * r;
223 	char const * path;
224 	size_t i;
225 
226 	if(menu->apps == NULL)
227 		_menu_on_idle(menu);
228 	memset(&menus, 0, sizeof(menus));
229 	menushell = gtk_menu_new();
230 	for(p = menu->apps; p != NULL; p = p->next)
231 	{
232 		config = p->data;
233 		/* should not fail */
234 		name = config_get(config, section, "Name");
235 #if GTK_CHECK_VERSION(2, 12, 0)
236 		comment = config_get(config, section, "Comment");
237 #endif
238 		if((q = config_get(config, section, "GenericName")) != NULL)
239 		{
240 #if GTK_CHECK_VERSION(2, 12, 0)
241 			if(comment == NULL)
242 				comment = name;
243 #endif
244 			name = q;
245 		}
246 		path = config_get(config, NULL, "path");
247 		menuitem = _menu_menuitem(menu, path, name,
248 				config_get(config, section, "Icon"));
249 #if GTK_CHECK_VERSION(2, 12, 0)
250 		if(comment != NULL)
251 			gtk_widget_set_tooltip_text(menuitem, comment);
252 #endif
253 		if((q = config_get(config, section, "Type")) != NULL
254 				&& strcmp(q, "Application") == 0
255 				&& config_get(config, section, "Exec") == NULL)
256 			gtk_widget_set_sensitive(menuitem, FALSE);
257 		else
258 			g_signal_connect_swapped(menuitem, "activate",
259 					G_CALLBACK(_applications_on_activate),
260 					config);
261 		if((q = config_get(config, section, "Categories")) == NULL)
262 		{
263 			gtk_menu_shell_append(GTK_MENU_SHELL(menushell), menuitem);
264 			continue;
265 		}
266 		for(i = 0; i < MENU_MENUS_COUNT; i++)
267 		{
268 			if((r = string_find(q, _menu_menus[i].category)) == NULL)
269 				continue;
270 			r += string_length(_menu_menus[i].category);
271 			if(*r == '\0' || *r == ';')
272 				break;
273 		}
274 		if(i == MENU_MENUS_COUNT)
275 		{
276 			gtk_menu_shell_append(GTK_MENU_SHELL(menushell),
277 					menuitem);
278 			continue;
279 		}
280 		if(menus[i] == NULL)
281 			menus[i] = gtk_menu_new();
282 		gtk_menu_shell_append(GTK_MENU_SHELL(menus[i]), menuitem);
283 	}
284 	_applications_categories(menushell, menus);
285 	return menushell;
286 }
287 
_applications_on_activate(gpointer data)288 static void _applications_on_activate(gpointer data)
289 {
290 	Config * config = data;
291 	const char section[] = "Desktop Entry";
292 	char const * q;
293 
294 	if((q = config_get(config, section, "Type")) == NULL)
295 		return;
296 	else if(strcmp(q, "Application") == 0)
297 		_applications_on_activate_application(config);
298 	else if(strcmp(q, "Directory") == 0)
299 		_applications_on_activate_directory(config);
300 	else if(strcmp(q, "URL") == 0)
301 		_applications_on_activate_url(config);
302 }
303 
_applications_on_activate_application(Config * config)304 static void _applications_on_activate_application(Config * config)
305 {
306 	const char section[] = "Desktop Entry";
307 	char * program;
308 	char * p;
309 	char const * q;
310 	pid_t pid;
311 	GError * error = NULL;
312 
313 	if((q = config_get(config, section, "Exec")) == NULL)
314 		return;
315 	if((program = strdup(q)) == NULL)
316 		return; /* XXX report error */
317 	/* XXX crude way to ignore %f, %F, %u and %U */
318 	if((p = strchr(program, '%')) != NULL)
319 		*p = '\0';
320 #ifdef DEBUG
321 	fprintf(stderr, "DEBUG: %s() \"%s\"", __func__, program);
322 #endif
323 	if((q = config_get(config, section, "Path")) == NULL)
324 	{
325 		/* execute the program directly */
326 		if(g_spawn_command_line_async(program, &error) != TRUE)
327 		{
328 			fprintf(stderr, "%s: %s\n", program, error->message);
329 			g_error_free(error);
330 		}
331 	}
332 	else if((pid = fork()) == 0)
333 	{
334 		/* change the current working directory */
335 		if(chdir(q) != 0)
336 			fprintf(stderr, "%s: %s: %s\n", program, q,
337 					strerror(errno));
338 		else if(g_spawn_command_line_async(program, &error) != TRUE)
339 		{
340 			fprintf(stderr, "%s: %s\n", program, error->message);
341 			g_error_free(error);
342 		}
343 		exit(0);
344 	}
345 	else if(pid < 0)
346 		fprintf(stderr, "%s: %s\n", program, strerror(errno));
347 	free(program);
348 }
349 
_applications_on_activate_directory(Config * config)350 static void _applications_on_activate_directory(Config * config)
351 {
352 	const char section[] = "Desktop Entry";
353 	char const * directory;
354 	/* XXX open with the default file manager instead */
355 	char * argv[] = { "browser", "--", NULL, NULL };
356 	const unsigned int flags = G_SPAWN_SEARCH_PATH;
357 	GError * error = NULL;
358 
359 	/* XXX this may not might the correct key */
360 	if((directory = config_get(config, section, "Path")) == NULL)
361 		return;
362 	if((argv[2] = strdup(directory)) == NULL)
363 		fprintf(stderr, "%s: %s\n", directory, strerror(errno));
364 	else if(g_spawn_async(NULL, argv, NULL, flags, NULL, NULL, NULL, &error)
365 			!= TRUE)
366 	{
367 		fprintf(stderr, "%s: %s\n", directory, error->message);
368 		g_error_free(error);
369 	}
370 	free(argv[2]);
371 }
372 
_applications_on_activate_url(Config * config)373 static void _applications_on_activate_url(Config * config)
374 {
375 	const char section[] = "Desktop Entry";
376 	char const * url;
377 	/* XXX open with the default web browser instead */
378 	char * argv[] = { BINDIR "/htmlapp", "--", NULL, NULL };
379 	unsigned int flags = 0;
380 	GError * error = NULL;
381 
382 	if((url = config_get(config, section, "URL")) == NULL)
383 		return;
384 	if((argv[2] = strdup(url)) == NULL)
385 		fprintf(stderr, "%s: %s\n", url, strerror(errno));
386 	else if(g_spawn_async(NULL, argv, NULL, flags, NULL, NULL, NULL, &error)
387 			!= TRUE)
388 	{
389 		fprintf(stderr, "%s: %s\n", url, error->message);
390 		g_error_free(error);
391 	}
392 	free(argv[2]);
393 }
394 
_applications_categories(GtkWidget * menu,GtkWidget ** menus)395 static void _applications_categories(GtkWidget * menu, GtkWidget ** menus)
396 {
397 	size_t i;
398 	MenuMenu const * m;
399 	GtkWidget * menuitem;
400 	size_t pos = 0;
401 
402 	for(i = 0; i < MENU_MENUS_COUNT; i++)
403 	{
404 		if(menus[i] == NULL)
405 			continue;
406 		m = &_menu_menus[i];
407 		menuitem = _menu_menuitem_stock(m->stock, m->label, FALSE);
408 		gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menus[i]);
409 		gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menuitem, pos++);
410 	}
411 }
412 
413 
414 /* menu_icon */
_menu_icon(Menu * menu,char const * path,char const * icon)415 static GtkWidget * _menu_icon(Menu * menu, char const * path, char const * icon)
416 {
417 	const char pixmaps[] = "/pixmaps/";
418 	int width = 16;
419 	int height = 16;
420 	String * buf;
421 	GdkPixbuf * pixbuf = NULL;
422 	GError * error = NULL;
423 
424 	gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
425 	if(icon[0] == '/')
426 		pixbuf = gdk_pixbuf_new_from_file_at_size(icon, width, height,
427 				&error);
428 	else if(strchr(icon, '.') != NULL)
429 	{
430 		if(path == NULL)
431 			path = DATADIR;
432 		if((buf = string_new_append(path, pixmaps, icon, NULL)) != NULL)
433 		{
434 			pixbuf = gdk_pixbuf_new_from_file_at_size(buf, width,
435 					height, &error);
436 			string_delete(buf);
437 		}
438 	}
439 	if(error != NULL)
440 	{
441 		menu->helper->error(NULL, error->message, 1);
442 		g_error_free(error);
443 	}
444 	if(pixbuf == NULL)
445 		return gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_MENU);
446 	return gtk_image_new_from_pixbuf(pixbuf);
447 }
448 
449 
450 /* menu_menuitem */
_menu_menuitem(Menu * menu,char const * path,char const * label,char const * icon)451 static GtkWidget * _menu_menuitem(Menu * menu, char const * path,
452 		char const * label, char const * icon)
453 {
454 	GtkWidget * ret;
455 	GtkWidget * image;
456 
457 	ret = gtk_image_menu_item_new_with_label(label);
458 	if(icon != NULL)
459 	{
460 		image = _menu_icon(menu, path, icon);
461 		gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(ret), image);
462 	}
463 	return ret;
464 }
465 
466 
467 /* menu_menuitem_stock */
_menu_menuitem_stock(char const * icon,char const * label,gboolean mnemonic)468 static GtkWidget * _menu_menuitem_stock(char const * icon, char const * label,
469 		gboolean mnemonic)
470 {
471 	GtkWidget * ret;
472 	GtkWidget * image;
473 
474 	ret = (mnemonic) ? gtk_image_menu_item_new_with_mnemonic(label)
475 		: gtk_image_menu_item_new_with_label(label);
476 	if(icon != NULL)
477 	{
478 		image = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_MENU);
479 		gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(ret), image);
480 	}
481 	return ret;
482 }
483 
484 
485 /* menu_xdg_dirs */
486 static void _xdg_dirs_home(Menu * menu, void (*callback)(Menu * menu,
487 			char const * path, char const * apppath));
488 static void _xdg_dirs_path(Menu * menu, void (*callback)(Menu * menu,
489 			char const * path, char const * apppath),
490 		char const * path);
491 
_menu_xdg_dirs(Menu * menu,void (* callback)(Menu * menu,char const * path,char const * apppath))492 static void _menu_xdg_dirs(Menu * menu, void (*callback)(Menu * menu,
493 			char const * path, char const * apppath))
494 {
495 	char const * path;
496 	char * p;
497 	size_t i;
498 	size_t j;
499 	int datadir = 1;
500 
501 	/* read through every XDG application folder */
502 	if((path = getenv("XDG_DATA_DIRS")) == NULL || strlen(path) == 0)
503 	{
504 		path = "/usr/local/share:/usr/share";
505 		datadir = 0;
506 	}
507 	if((p = strdup(path)) == NULL)
508 		menu->helper->error(NULL, "strdup", 1);
509 	else
510 		for(i = 0, j = 0;; i++)
511 			if(p[i] == '\0')
512 			{
513 				string_rtrim(&p[j], "/");
514 				_xdg_dirs_path(menu, callback, &p[j]);
515 				datadir |= (strcmp(&p[j], DATADIR) == 0);
516 				break;
517 			}
518 			else if(p[i] == ':')
519 			{
520 				p[i] = '\0';
521 				string_rtrim(&p[j], "/");
522 				_xdg_dirs_path(menu, callback, &p[j]);
523 				datadir |= (strcmp(&p[j], DATADIR) == 0);
524 				j = i + 1;
525 			}
526 	free(p);
527 	if(datadir == 0)
528 		_xdg_dirs_path(menu, callback, DATADIR);
529 	_xdg_dirs_home(menu, callback);
530 }
531 
_xdg_dirs_home(Menu * menu,void (* callback)(Menu * menu,char const * path,char const * apppath))532 static void _xdg_dirs_home(Menu * menu, void (*callback)(Menu * menu,
533 			char const * path, char const * apppath))
534 {
535 	char const fallback[] = ".local/share";
536 	char const * path;
537 	char const * homedir;
538 	String * p;
539 
540 	/* use $XDG_DATA_HOME if set and not empty */
541 	if((path = getenv("XDG_DATA_HOME")) != NULL && strlen(path) > 0)
542 	{
543 		_xdg_dirs_path(menu, callback, path);
544 		return;
545 	}
546 	/* fallback to "$HOME/.local/share" */
547 	if((homedir = getenv("HOME")) == NULL)
548 		homedir = g_get_home_dir();
549 	if((p = string_new_append(homedir, "/", fallback, NULL)) == NULL)
550 	{
551 		menu->helper->error(NULL, homedir, 1);
552 		return;
553 	}
554 	_xdg_dirs_path(menu, callback, p);
555 	string_delete(p);
556 }
557 
_xdg_dirs_path(Menu * menu,void (* callback)(Menu * menu,char const * path,char const * apppath),char const * path)558 static void _xdg_dirs_path(Menu * menu, void (*callback)(Menu * menu,
559 			char const * path, char const * apppath),
560 		char const * path)
561 {
562 	const char applications[] = "/applications";
563 	char * apppath;
564 
565 	if((apppath = string_new_append(path, applications, NULL)) == NULL)
566 		menu->helper->error(NULL, path, 1);
567 	callback(menu, path, apppath);
568 	string_delete(apppath);
569 }
570 
571 
572 /* callbacks */
573 /* menu_on_about */
_menu_on_about(gpointer data)574 static void _menu_on_about(gpointer data)
575 {
576 	Menu * menu = data;
577 
578 	menu->helper->about_dialog(menu->helper->panel);
579 }
580 
581 
582 /* menu_on_clicked */
583 static void _clicked_position_menu(GtkMenu * widget, gint * x, gint * y,
584 		gboolean * push_in, gpointer data);
585 
_menu_on_clicked(gpointer data)586 static void _menu_on_clicked(gpointer data)
587 {
588 	Menu * menu = data;
589 	PanelAppletHelper * helper = menu->helper;
590 	GtkWidget * menushell;
591 	GtkWidget * menuitem;
592 	GtkWidget * widget;
593 	char const * p;
594 
595 	menushell = gtk_menu_new();
596 	if((p = helper->config_get(helper->panel, "menu", "applications"))
597 			== NULL || strtol(p, NULL, 0) != 0)
598 	{
599 		menuitem = _menu_menuitem_stock("gnome-applications",
600 				_("A_pplications"), TRUE);
601 		widget = _menu_applications(menu);
602 		gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), widget);
603 		gtk_menu_shell_append(GTK_MENU_SHELL(menushell), menuitem);
604 		menuitem = gtk_separator_menu_item_new();
605 		gtk_menu_shell_append(GTK_MENU_SHELL(menushell), menuitem);
606 	}
607 	if((p = helper->config_get(helper->panel, "menu", "run")) == NULL
608 			|| strtol(p, NULL, 0) != 0)
609 	{
610 		menuitem = _menu_menuitem_stock(GTK_STOCK_EXECUTE, _("_Run..."),
611 				TRUE);
612 		g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
613 					_menu_on_run), menu);
614 		gtk_menu_shell_append(GTK_MENU_SHELL(menushell), menuitem);
615 		menuitem = gtk_separator_menu_item_new();
616 		gtk_menu_shell_append(GTK_MENU_SHELL(menushell), menuitem);
617 	}
618 	if((p = helper->config_get(helper->panel, "menu", "about")) == NULL
619 			|| strtol(p, NULL, 0) != 0)
620 	{
621 #if GTK_CHECK_VERSION(3, 10, 0)
622 		menuitem = gtk_image_menu_item_new_with_mnemonic(_("_About"));
623 		gtk_image_menu_item_set_image(GTK_MENU_ITEM(menuitem),
624 				gtk_image_new_from_icon_name(GTK_STOCK_ABOUT,
625 					GTK_ICON_SIZE_MENU));
626 #else
627 		menuitem = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT,
628 				NULL);
629 #endif
630 		g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
631 					_menu_on_about), menu);
632 		gtk_menu_shell_append(GTK_MENU_SHELL(menushell), menuitem);
633 		menuitem = gtk_separator_menu_item_new();
634 		gtk_menu_shell_append(GTK_MENU_SHELL(menushell), menuitem);
635 	}
636 	/* lock screen */
637 	menuitem = _menu_menuitem_stock("gnome-lockscreen", _("_Lock screen"),
638 			TRUE);
639 	g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
640 				_menu_on_lock), menu);
641 	gtk_menu_shell_append(GTK_MENU_SHELL(menushell), menuitem);
642 #ifdef EMBEDDED
643 	/* rotate screen */
644 	/* XXX find a more appropriate icon */
645 	menuitem = _menu_menuitem_stock(GTK_STOCK_REFRESH, _("R_otate"), TRUE);
646 	g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
647 				_menu_on_rotate), data);
648 	gtk_menu_shell_append(GTK_MENU_SHELL(menushell), menuitem);
649 #endif
650 	/* logout */
651 	if(menu->helper->logout_dialog != NULL)
652 	{
653 		menuitem = _menu_menuitem_stock("gnome-logout", _("Lo_gout..."),
654 				TRUE);
655 		g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
656 					_menu_on_logout), data);
657 		gtk_menu_shell_append(GTK_MENU_SHELL(menushell), menuitem);
658 	}
659 	/* suspend */
660 	if(menu->helper->suspend != NULL)
661 	{
662 		menuitem = _menu_menuitem_stock("gtk-media-pause",
663 				_("S_uspend"), TRUE);
664 		g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
665 					_menu_on_suspend), data);
666 		gtk_menu_shell_append(GTK_MENU_SHELL(menushell), menuitem);
667 	}
668 	/* shutdown */
669 	menuitem = _menu_menuitem_stock("gnome-shutdown", _("_Shutdown..."),
670 			TRUE);
671 	g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
672 				_menu_on_shutdown), data);
673 	gtk_menu_shell_append(GTK_MENU_SHELL(menushell), menuitem);
674 	gtk_widget_show_all(menushell);
675 	gtk_menu_popup(GTK_MENU(menushell), NULL, NULL, _clicked_position_menu,
676 			menu, 0, gtk_get_current_event_time());
677 }
678 
_clicked_position_menu(GtkMenu * widget,gint * x,gint * y,gboolean * push_in,gpointer data)679 static void _clicked_position_menu(GtkMenu * widget, gint * x, gint * y,
680 		gboolean * push_in, gpointer data)
681 {
682 	Menu * menu = data;
683 	GtkAllocation a;
684 
685 #if GTK_CHECK_VERSION(2, 18, 0)
686 	gtk_widget_get_allocation(menu->widget, &a);
687 #else
688 	a = menu->widget->allocation;
689 #endif
690 	*x = a.x;
691 	*y = a.y;
692 	menu->helper->position_menu(menu->helper->panel, widget, x, y, push_in);
693 }
694 
695 
696 /* menu_on_idle */
697 static int _idle_access(Menu * menu, char const * path, int mode);
698 static int _idle_access_path(Menu * menu, char const * path,
699 		char const * filename, int mode);
700 static gint _idle_apps_compare(gconstpointer a, gconstpointer b);
701 static void _idle_path(Menu * menu, char const * path, char const * apppath);
702 
_menu_on_idle(gpointer data)703 static gboolean _menu_on_idle(gpointer data)
704 {
705 	const int timeout = 10000;
706 	Menu * menu = data;
707 
708 	if(menu->apps != NULL)
709 	{
710 		menu->idle = 0;
711 		return FALSE;
712 	}
713 	_menu_xdg_dirs(menu, _idle_path);
714 	menu->idle = g_timeout_add(timeout, _menu_on_timeout, menu);
715 	return FALSE;
716 }
717 
_idle_access(Menu * menu,char const * path,int mode)718 static int _idle_access(Menu * menu, char const * path, int mode)
719 {
720 	int ret = -1;
721 	char const * p;
722 	char * q;
723 	size_t i;
724 	size_t j;
725 
726 	if(path[0] == '/')
727 		return access(path, mode);
728 	if((p = getenv("PATH")) == NULL)
729 		return 0;
730 	if((q = strdup(p)) == NULL)
731 	{
732 		menu->helper->error(NULL, path, 1);
733 		return 0;
734 	}
735 	errno = ENOENT;
736 	for(i = 0, j = 0;; i++)
737 		if(q[i] == '\0')
738 		{
739 			ret = _idle_access_path(menu, &q[j], path, mode);
740 			break;
741 		}
742 		else if(q[i] == ':')
743 		{
744 			q[i] = '\0';
745 			if((ret = _idle_access_path(menu, &q[j], path, mode))
746 					== 0)
747 				break;
748 			j = i + 1;
749 		}
750 	free(q);
751 	return ret;
752 }
753 
_idle_access_path(Menu * menu,char const * path,char const * filename,int mode)754 static int _idle_access_path(Menu * menu, char const * path,
755 		char const * filename, int mode)
756 {
757 	int ret;
758 	char * p;
759 	size_t len;
760 
761 	len = strlen(path) + 1 + strlen(filename) + 1;
762 	if((p = malloc(len)) == NULL)
763 		return -menu->helper->error(NULL, path, 1);
764 	snprintf(p, len, "%s/%s", path, filename);
765 	ret = access(p, mode);
766 	free(p);
767 	return ret;
768 }
769 
_idle_apps_compare(gconstpointer a,gconstpointer b)770 static gint _idle_apps_compare(gconstpointer a, gconstpointer b)
771 {
772 	const char section[] = "Desktop Entry";
773 	const char generic[] = "GenericName";
774 	const char name[] = "Name";
775 	Config * ca = (Config *)a;
776 	Config * cb = (Config *)b;
777 	char const * cap;
778 	char const * cbp;
779 
780 	if((cap = config_get(ca, section, generic)) == NULL)
781 		cap = config_get(ca, section, name);
782 	if((cbp = config_get(cb, section, generic)) == NULL)
783 		cbp = config_get(cb, section, name);
784 	return string_compare(cap, cbp);
785 }
786 
_idle_path(Menu * menu,char const * path,char const * apppath)787 static void _idle_path(Menu * menu, char const * path, char const * apppath)
788 {
789 	DIR * dir;
790 	int fd;
791 	struct stat st;
792 	struct dirent * de;
793 	size_t len;
794 	const char ext[] = ".desktop";
795 	const char section[] = "Desktop Entry";
796 	char * name = NULL;
797 	char * p;
798 	Config * config = NULL;
799 	String const * q;
800 
801 #if defined(__sun)
802 	if((fd = open(apppath, O_RDONLY)) < 0
803 			|| fstat(fd, &st) != 0
804 			|| (dir = fdopendir(fd)) == NULL)
805 #else
806 	if((dir = opendir(apppath)) == NULL
807 			|| (fd = dirfd(dir)) < 0
808 			|| fstat(fd, &st) != 0)
809 #endif
810 	{
811 		if(errno != ENOENT)
812 			menu->helper->error(NULL, apppath, 1);
813 		return;
814 	}
815 	if(st.st_mtime > menu->refresh_mti)
816 		menu->refresh_mti = st.st_mtime;
817 	while((de = readdir(dir)) != NULL)
818 	{
819 		if(de->d_name[0] == '.')
820 			if(de->d_name[1] == '\0' || (de->d_name[1] == '.'
821 						&& de->d_name[2] == '\0'))
822 				continue;
823 		len = strlen(de->d_name);
824 		if(len < sizeof(ext))
825 			continue;
826 		if(strncmp(&de->d_name[len - sizeof(ext) + 1], ext,
827 					sizeof(ext)) != 0)
828 			continue;
829 		if((p = realloc(name, strlen(apppath) + len + 2)) == NULL)
830 		{
831 			menu->helper->error(NULL, apppath, 1);
832 			continue;
833 		}
834 		name = p;
835 		snprintf(name, strlen(apppath) + len + 2, "%s/%s", apppath,
836 				de->d_name);
837 #ifdef DEBUG
838 		fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, name);
839 #endif
840 		if(config == NULL)
841 			config = config_new();
842 		else
843 			config_reset(config);
844 		if(config == NULL || config_load(config, name) != 0)
845 		{
846 			menu->helper->error(NULL, error_get(NULL), 1);
847 			continue;
848 		}
849 		/* skip this entry if it is deleted */
850 		if((q = config_get(config, section, "Hidden")) != NULL
851 				&& strcmp(q, "true") == 0)
852 			continue;
853 		/* skip this entry if it has an unknown type */
854 		if((q = config_get(config, section, "Type")) == NULL)
855 			continue;
856 		if(strcmp(q, "Application") != 0
857 				&& strcmp(q, "Directory") != 0
858 				&& strcmp(q, "URL") != 0)
859 			continue;
860 		/* skip this entry if there is no name defined */
861 		if((q = config_get(config, section, "Name")) == NULL)
862 			continue;
863 		/* skip this entry if should not be displayed at all */
864 		if((q = config_get(config, section, "NoDisplay")) != NULL
865 				&& strcmp(q, "true") == 0)
866 			continue;
867 		/* skip this entry if the binary cannot be executed */
868 		if((q = config_get(config, section, "TryExec")) != NULL
869 				&& _idle_access(menu, q, X_OK) != 0
870 				&& errno == ENOENT)
871 			continue;
872 		/* remember the path */
873 		config_set(config, NULL, "path", path);
874 		menu->apps = g_slist_insert_sorted(menu->apps, config,
875 				_idle_apps_compare);
876 		config = NULL;
877 	}
878 	free(name);
879 	closedir(dir);
880 	if(config != NULL)
881 		config_delete(config);
882 }
883 
884 
885 /* menu_on_lock */
_menu_on_lock(gpointer data)886 static void _menu_on_lock(gpointer data)
887 {
888 	Menu * menu = data;
889 
890 	menu->helper->lock(menu->helper->panel);
891 }
892 
893 
894 /* menu_on_logout */
_menu_on_logout(gpointer data)895 static void _menu_on_logout(gpointer data)
896 {
897 	Menu * menu = data;
898 
899 	menu->helper->logout_dialog(menu->helper->panel);
900 }
901 
902 
903 #ifdef EMBEDDED
904 /* menu_on_rotate */
_menu_on_rotate(gpointer data)905 static void _menu_on_rotate(gpointer data)
906 {
907 	Menu * menu = data;
908 
909 	menu->helper->rotate_screen(menu->helper->panel);
910 }
911 #endif
912 
913 
914 /* menu_on_run */
_menu_on_run(gpointer data)915 static void _menu_on_run(gpointer data)
916 {
917 	Menu * menu = data;
918 	char * argv[] = { BINDIR "/run", NULL };
919 	const unsigned int flags = G_SPAWN_STDOUT_TO_DEV_NULL
920 		| G_SPAWN_STDERR_TO_DEV_NULL;
921 	GError * error = NULL;
922 
923 	if(g_spawn_async(NULL, argv, NULL, flags, NULL, NULL, NULL, &error)
924 			!= TRUE)
925 	{
926 		menu->helper->error(menu->helper->panel, error->message, 1);
927 		g_error_free(error);
928 	}
929 }
930 
931 
932 /* menu_on_shutdown */
_menu_on_shutdown(gpointer data)933 static void _menu_on_shutdown(gpointer data)
934 {
935 	Menu * menu = data;
936 
937 	menu->helper->shutdown_dialog(menu->helper->panel);
938 }
939 
940 
941 /* menu_on_suspend */
_menu_on_suspend(gpointer data)942 static void _menu_on_suspend(gpointer data)
943 {
944 	Menu * menu = data;
945 
946 	menu->helper->suspend(menu->helper->panel);
947 }
948 
949 
950 /* menu_on_timeout */
951 static void _timeout_path(Menu * menu, char const * path, char const * apppath);
952 
_menu_on_timeout(gpointer data)953 static gboolean _menu_on_timeout(gpointer data)
954 {
955 	Menu * menu = data;
956 
957 	menu->refresh = FALSE;
958 	_menu_xdg_dirs(menu, _timeout_path);
959 	if(menu->refresh == FALSE)
960 		return TRUE;
961 #ifdef DEBUG
962 	fprintf(stderr, "DEBUG: %s() resetting the menu\n", __func__);
963 #endif
964 	g_slist_foreach(menu->apps, (GFunc)config_delete, NULL);
965 	g_slist_free(menu->apps);
966 	menu->apps = NULL;
967 	menu->idle = g_idle_add(_menu_on_idle, menu);
968 	return FALSE;
969 }
970 
_timeout_path(Menu * menu,char const * path,char const * apppath)971 static void _timeout_path(Menu * menu, char const * path, char const * apppath)
972 {
973 	struct stat st;
974 
975 	if(menu->refresh != TRUE
976 			&& stat(apppath, &st) == 0
977 			&& st.st_mtime > menu->refresh_mti)
978 		menu->refresh = TRUE;
979 }
980