1 /* $Id$ */
2 /* Copyright (c) 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 <dirent.h>
19 #include <unistd.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <locale.h>
25 #include <libintl.h>
26 #include <gdk/gdkkeysyms.h>
27 #include <System.h>
28 #include <Desktop.h>
29 #include "../config.h"
30 #define _(string) gettext(string)
31 
32 /* constants */
33 #ifndef PROGNAME
34 # define PROGNAME	"settings"
35 #endif
36 
37 #ifndef PREFIX
38 # define PREFIX		"/usr/local"
39 #endif
40 #ifndef BINDIR
41 # define BINDIR		PREFIX "/bin"
42 #endif
43 #ifndef DATADIR
44 # define DATADIR	PREFIX "/share"
45 #endif
46 #ifndef LOCALEDIR
47 # define LOCALEDIR	DATADIR "/locale"
48 #endif
49 
50 
51 /* settings */
52 /* private */
53 /* types */
54 typedef struct _Settings
55 {
56 	GtkWidget * window;
57 	GtkWidget * view;
58 } Settings;
59 
60 /* constants */
61 typedef enum _SettingsColumn
62 {
63 	SC_ICON = 0,
64 	SC_NAME,
65 	SC_EXEC,
66 	SC_PRIVILEGED
67 } SettingsColumn;
68 #define SC_LAST SC_PRIVILEGED
69 #define SC_COUNT (SC_LAST + 1)
70 
71 /* prototypes */
72 static int _settings(void);
73 
74 /* accessors */
75 static gboolean _settings_get_iter(Settings * settings, GtkTreeIter * iter,
76 		GtkTreePath * path);
77 static GtkTreeModel * _settings_get_model(Settings * settings);
78 
79 /* useful */
80 static int _settings_browse(Settings * settings);
81 
82 static int _settings_error(char const * message, int ret);
83 static int _settings_usage(void);
84 
85 /* callbacks */
86 static void _settings_on_close(gpointer data);
87 
88 
89 /* constants */
90 static const DesktopAccel _settings_accel[] =
91 {
92 	{ G_CALLBACK(_settings_on_close), GDK_CONTROL_MASK, GDK_KEY_W },
93 	{ NULL, 0, 0 }
94 };
95 
96 
97 /* functions */
98 /* settings */
99 /* callbacks */
100 static gboolean _settings_on_closex(gpointer data);
101 static gboolean _settings_on_filter_view(GtkTreeModel * model,
102 		GtkTreeIter * iter, gpointer data);
103 static gboolean _settings_on_idle(gpointer data);
104 #if GTK_CHECK_VERSION(2, 10, 0)
105 static void _settings_on_item_activated(GtkWidget * widget, GtkTreePath * path,
106 		gpointer data);
107 #endif
108 
_settings(void)109 static int _settings(void)
110 {
111 	Settings settings;
112 	GtkAccelGroup * accel;
113 	GtkWidget * widget;
114 	GtkListStore * store;
115 	GtkTreeModel * model;
116 
117 	accel = gtk_accel_group_new();
118 	settings.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
119 	gtk_window_add_accel_group(GTK_WINDOW(settings.window), accel);
120 	desktop_accel_create(_settings_accel, &settings, accel);
121 	g_object_unref(accel);
122 	gtk_window_set_default_size(GTK_WINDOW(settings.window), 400, 300);
123 	gtk_window_set_icon_name(GTK_WINDOW(settings.window),
124 			GTK_STOCK_PREFERENCES);
125 	gtk_window_set_title(GTK_WINDOW(settings.window),
126 			_("System preferences"));
127 	g_signal_connect_swapped(settings.window, "delete-event", G_CALLBACK(
128 				_settings_on_closex), NULL);
129 	widget = gtk_scrolled_window_new(NULL, NULL);
130 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget),
131 			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
132 	store = gtk_list_store_new(SC_COUNT,
133 			GDK_TYPE_PIXBUF,	/* SC_ICON */
134 			G_TYPE_STRING,		/* SC_NAME */
135 			G_TYPE_STRING,		/* SC_EXEC */
136 			G_TYPE_BOOLEAN);	/* SC_PRIVILEGED */
137 	model = gtk_tree_model_filter_new(GTK_TREE_MODEL(store), NULL);
138 	gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(model),
139 			_settings_on_filter_view, &settings, NULL);
140 	model = gtk_tree_model_sort_new_with_model(model);
141 	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), SC_NAME,
142 			GTK_SORT_ASCENDING);
143 	settings.view = gtk_icon_view_new_with_model(model);
144 	gtk_icon_view_set_item_width(GTK_ICON_VIEW(settings.view), 96);
145 	gtk_icon_view_set_pixbuf_column(GTK_ICON_VIEW(settings.view), SC_ICON);
146 	gtk_icon_view_set_text_column(GTK_ICON_VIEW(settings.view), SC_NAME);
147 	gtk_icon_view_set_selection_mode(GTK_ICON_VIEW(settings.view),
148 			GTK_SELECTION_SINGLE);
149 #if GTK_CHECK_VERSION(2, 10, 0)
150 	g_signal_connect(settings.view, "item-activated", G_CALLBACK(
151 				_settings_on_item_activated), &settings);
152 #endif
153 	gtk_container_add(GTK_CONTAINER(widget), settings.view);
154 	gtk_container_add(GTK_CONTAINER(settings.window), widget);
155 	gtk_widget_show_all(settings.window);
156 	g_idle_add(_settings_on_idle, &settings);
157 	gtk_main();
158 	return 0;
159 }
160 
_settings_on_closex(gpointer data)161 static gboolean _settings_on_closex(gpointer data)
162 {
163 	Settings * settings = data;
164 
165 	_settings_on_close(settings);
166 	return FALSE;
167 }
168 
_settings_on_filter_view(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)169 static gboolean _settings_on_filter_view(GtkTreeModel * model,
170 		GtkTreeIter * iter, gpointer data)
171 {
172 	gboolean privileged;
173 
174 	if(geteuid() == 0)
175 		return TRUE;
176 	gtk_tree_model_get(model, iter, SC_PRIVILEGED, &privileged, -1);
177 	return (SC_PRIVILEGED == TRUE) ? FALSE : TRUE;
178 }
179 
_settings_on_idle(gpointer data)180 static gboolean _settings_on_idle(gpointer data)
181 {
182 	Settings * settings = data;
183 
184 	_settings_browse(settings);
185 	return FALSE;
186 }
187 
188 #if GTK_CHECK_VERSION(2, 10, 0)
_settings_on_item_activated(GtkWidget * widget,GtkTreePath * path,gpointer data)189 static void _settings_on_item_activated(GtkWidget * widget, GtkTreePath * path,
190 		gpointer data)
191 {
192 	Settings * settings = data;
193 	const unsigned int flags = G_SPAWN_FILE_AND_ARGV_ZERO;
194 	GtkTreeModel * model;
195 	GtkTreeIter iter;
196 	gchar * exec;
197 	GError * error = NULL;
198 	char * argv[] = { "/bin/sh", "sh", "-c", NULL, NULL };
199 
200 	if(_settings_get_iter(settings, &iter, path) == FALSE)
201 		return;
202 	model = _settings_get_model(settings);
203 	gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, SC_EXEC, &exec, -1);
204 # ifdef DEBUG
205 	fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, exec);
206 # endif
207 	argv[3] = exec;
208 	if(g_spawn_async(NULL, argv, NULL, flags, NULL, NULL, NULL, &error)
209 			== FALSE)
210 		_settings_error(error->message, 1);
211 	g_free(exec);
212 }
213 #endif
214 
215 
216 /* accessors */
217 /* settings_get_iter */
_settings_get_iter(Settings * settings,GtkTreeIter * iter,GtkTreePath * path)218 static gboolean _settings_get_iter(Settings * settings, GtkTreeIter * iter,
219 		GtkTreePath * path)
220 {
221 	GtkTreeModel * model;
222 	GtkTreeIter p;
223 
224 	model = gtk_icon_view_get_model(GTK_ICON_VIEW(settings->view));
225 	if(gtk_tree_model_get_iter(model, iter, path) == FALSE)
226 		return FALSE;
227 	gtk_tree_model_sort_convert_iter_to_child_iter(GTK_TREE_MODEL_SORT(
228 				model), &p, iter);
229 	model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(model));
230 	gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(
231 				model), iter, &p);
232 	return TRUE;
233 }
234 
235 
236 /* settings_get_model */
_settings_get_model(Settings * settings)237 static GtkTreeModel * _settings_get_model(Settings * settings)
238 {
239 	GtkTreeModel * model;
240 
241 	model = gtk_icon_view_get_model(GTK_ICON_VIEW(settings->view));
242 	model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(model));
243 	return gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model));
244 }
245 
246 
247 /* useful */
248 /* settings_browse */
249 static int _settings_browse_folder(Settings * settings, Config * config,
250 		char const * folder);
251 static int _settings_browse_folder_access(char const * filename, int mode);
252 static int _settings_browse_folder_access_path(char const * path,
253 		char const * filename, int mode);
254 static int _settings_browse_home(Settings * settings, Config * config);
255 
_settings_browse(Settings * settings)256 static int _settings_browse(Settings * settings)
257 {
258 	int ret = 0;
259 	Config * config;
260 	GtkTreeModel * model;
261 	char const * path;
262 	char * p;
263 	size_t i;
264 	size_t j;
265 	int datadir = 1;
266 
267 	if((config = config_new()) == NULL)
268 		return -_settings_error(error_get(NULL), 1);
269 	model = _settings_get_model(settings);
270 	gtk_list_store_clear(GTK_LIST_STORE(model));
271 	/* read through every XDG application folder */
272 	if((path = getenv("XDG_DATA_DIRS")) == NULL || strlen(path) == 0)
273 	{
274 		path = "/usr/local/share:/usr/share";
275 		datadir = 0;
276 	}
277 	if((p = strdup(path)) == NULL)
278 		_settings_error(error_get(NULL), 1);
279 	else
280 		for(i = 0, j = 0;; i++)
281 			if(p[i] == '\0')
282 			{
283 				string_rtrim(&p[j], "/");
284 				_settings_browse_folder(settings, config,
285 						&p[j]);
286 				datadir |= (strcmp(&p[j], DATADIR) == 0);
287 				break;
288 			}
289 			else if(p[i] == ':')
290 			{
291 				p[i] = '\0';
292 				string_rtrim(&p[j], "/");
293 				_settings_browse_folder(settings, config,
294 						&p[j]);
295 				datadir |= (strcmp(&p[j], DATADIR) == 0);
296 				j = i + 1;
297 			}
298 	free(p);
299 	if(datadir == 0)
300 		ret = _settings_browse_folder(settings, config, DATADIR);
301 	ret |= _settings_browse_home(settings, config);
302 	config_delete(config);
303 	return ret;
304 }
305 
_settings_browse_folder(Settings * settings,Config * config,char const * folder)306 static int _settings_browse_folder(Settings * settings, Config * config,
307 		char const * folder)
308 {
309 	const char ext[8] = ".desktop";
310 	const char section[] = "Desktop Entry";
311 	const char application[] = "Application";
312 	const int flags = GTK_ICON_LOOKUP_FORCE_SIZE;
313 	const gint iconsize = 48;
314 	GtkIconTheme * theme;
315 	GtkTreeModel * model;
316 	GtkListStore * store;
317 	DIR * dir;
318 	struct dirent * de;
319 	size_t len;
320 	String * path;
321 	int res;
322 	String const * name;
323 	String const * icon;
324 	String const * exec;
325 	String const * p;
326 	GdkPixbuf * pixbuf;
327 	GtkTreeIter iter;
328 	GError * error = NULL;
329 
330 #ifdef DEBUG
331 	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, folder);
332 #endif
333 	if((path = string_new_append(folder, "/applications", NULL)) == NULL)
334 		return -_settings_error(error_get(NULL), 1);
335 	dir = opendir(path);
336 	string_delete(path);
337 	if(dir == NULL)
338 		return -_settings_error(strerror(errno), 1);
339 	theme = gtk_icon_theme_get_default();
340 	model = _settings_get_model(settings);
341 	store = GTK_LIST_STORE(model);
342 	while((de = readdir(dir)) != NULL)
343 	{
344 		if((len = strlen(de->d_name)) <= sizeof(ext))
345 			continue;
346 		if(strncmp(&de->d_name[len - sizeof(ext)], ext, sizeof(ext))
347 				!= 0)
348 			continue;
349 		if((path = string_new_append(folder, "/applications/",
350 						de->d_name, NULL)) == NULL)
351 		{
352 			_settings_error(error_get(NULL), 1);
353 			continue;
354 		}
355 #ifdef DEBUG
356 		fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, path);
357 #endif
358 		config_reset(config);
359 		res = config_load(config, path);
360 		string_delete(path);
361 		if(res != 0)
362 		{
363 			_settings_error(error_get(NULL), 1);
364 			continue;
365 		}
366 		p = config_get(config, section, "Type");
367 		name = config_get(config, section, "Name");
368 		exec = config_get(config, section, "Exec");
369 		if(p == NULL || name == NULL || exec == NULL
370 				|| strcmp(exec, PROGNAME) == 0)
371 			continue;
372 		if(strcmp(p, application) != 0)
373 			continue;
374 		if((p = config_get(config, section, "Categories")) == NULL
375 				|| string_find(p, "Settings") == NULL)
376 			continue;
377 		if((p = config_get(config, section, "TryExec")) != NULL
378 				&& _settings_browse_folder_access(path, X_OK)
379 				!= 0 && errno == ENOENT)
380 				continue;
381 		if((icon = config_get(config, section, "Icon")) == NULL)
382 			icon = GTK_STOCK_PREFERENCES;
383 #ifdef DEBUG
384 		fprintf(stderr, "DEBUG: %s() \"%s\" %s\n", __func__, name,
385 				icon);
386 #endif
387 		if((pixbuf = gtk_icon_theme_load_icon(theme, icon, iconsize,
388 						flags, &error)) == NULL)
389 		{
390 			_settings_error(error->message, 0);
391 			g_error_free(error);
392 			error = NULL;
393 		}
394 #if GTK_CHECK_VERSION(2, 6, 0)
395 		gtk_list_store_insert_with_values(store, &iter, -1,
396 #else
397 		gtk_list_store_append(store, &iter);
398 		gtk_list_store_set(store, &iter,
399 #endif
400 				SC_ICON, pixbuf, SC_NAME, name, SC_EXEC, exec,
401 				/* FIXME detect privileged settings */
402 				SC_PRIVILEGED, FALSE, -1);
403 	}
404 	closedir(dir);
405 	return FALSE;
406 }
407 
_settings_browse_folder_access(char const * path,int mode)408 static int _settings_browse_folder_access(char const * path, int mode)
409 {
410 	int ret = -1;
411 	char const * p;
412 	char * q;
413 	size_t i;
414 	size_t j;
415 
416 #ifdef DEBUG
417 	fprintf(stderr, "DEBUG: %s(\"%s\", %d)\n", __func__, path, mode);
418 #endif
419 	if(path[0] == '/')
420 		return access(path, mode);
421 	if((p = getenv("PATH")) == NULL)
422 		return 0;
423 	if((q = string_new(p)) == NULL)
424 		return -1;
425 	errno = ENOENT;
426 	for(i = 0, j = 0;; i++)
427 		if(q[i] == '\0')
428 		{
429 			ret = _settings_browse_folder_access_path(&q[j], path,
430 					mode);
431 			break;
432 		}
433 		else if(q[i] == ':')
434 		{
435 			q[i] = '\0';
436 			if((ret = _settings_browse_folder_access_path(&q[j],
437 							path, mode)) == 0)
438 				break;
439 			j = i + 1;
440 		}
441 	string_delete(q);
442 	return ret;
443 }
444 
_settings_browse_folder_access_path(char const * path,char const * filename,int mode)445 static int _settings_browse_folder_access_path(char const * path,
446 		char const * filename, int mode)
447 {
448 	int ret;
449 	String * p;
450 
451 #ifdef DEBUG
452 	fprintf(stderr, "DEBUG: %s(\"%s\", \"%s\", %d)\n", __func__, path,
453 			filename, mode);
454 #endif
455 	if((p = string_new_append(path, "/", filename, NULL)) == NULL)
456 		return -1;
457 	ret = access(p, mode);
458 	string_delete(p);
459 	return ret;
460 }
461 
_settings_browse_home(Settings * settings,Config * config)462 static int _settings_browse_home(Settings * settings, Config * config)
463 {
464 	int ret;
465 	char const fallback[] = ".local/share";
466 	char const * path;
467 	char const * homedir;
468 	String * p;
469 
470 	/* use $XDG_DATA_HOME if set and not empty */
471 	if((path = getenv("XDG_DATA_HOME")) != NULL && strlen(path) > 0)
472 		return _settings_browse_folder(settings, config, path);
473 	/* fallback to "$HOME/.local/share" */
474 	if((homedir = getenv("HOME")) == NULL)
475 		homedir = g_get_home_dir();
476 	if((p = string_new_append(homedir, "/", fallback, NULL)) == NULL)
477 		return -_settings_error(error_get(NULL), 1);
478 	ret = _settings_browse_folder(settings, config, p);
479 	free(p);
480 	return ret;
481 }
482 
483 
484 /* settings_error */
_settings_error(char const * message,int ret)485 static int _settings_error(char const * message, int ret)
486 {
487 	fprintf(stderr, "%s: %s\n", PROGNAME, message);
488 	return ret;
489 }
490 
491 
492 /* settings_usage */
_settings_usage(void)493 static int _settings_usage(void)
494 {
495 	fputs("Usage: " PROGNAME "\n", stderr);
496 	return 1;
497 }
498 
499 
500 /* callbacks */
_settings_on_close(gpointer data)501 static void _settings_on_close(gpointer data)
502 {
503 	Settings * settings = data;
504 
505 	gtk_widget_hide(settings->window);
506 	gtk_main_quit();
507 }
508 
509 
510 /* public */
511 /* main */
main(int argc,char * argv[])512 int main(int argc, char * argv[])
513 {
514 	int o;
515 
516 	if(setlocale(LC_ALL, "") == NULL)
517 		_settings_error(strerror(errno), 1); /* XXX mention setlocale */
518 	bindtextdomain(PACKAGE, LOCALEDIR);
519 	textdomain(PACKAGE);
520 	gtk_init(&argc, &argv);
521 	while((o = getopt(argc, argv, "")) != -1)
522 		switch(o)
523 		{
524 			default:
525 				return _settings_usage();
526 		}
527 	if(optind != argc)
528 		return _settings_usage();
529 	return (_settings() == 0) ? 0 : 2;
530 }
531