1 /*
2  *  Copyright (C) 2008 Giuseppe Torelli - <colossus73@gmail.com>
3  *
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; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17  *  MA 02110-1301 USA.
18  */
19 
20 #include <dirent.h>
21 #include <string.h>
22 #include <gtk/gtk.h>
23 #include "open-with-dlg.h"
24 #include "main.h"
25 #include "mime.h"
26 #include "pref_dialog.h"
27 #include "support.h"
28 #include "window.h"
29 
30 typedef struct
31 {
32 	gchar *name;
33 	gchar *exec;
34 	gboolean multiple;
35 	GdkPixbuf *icon;
36 } App;
37 
38 typedef struct
39 {
40 	GtkWidget *dialog1;
41 	GtkWidget *custom_command_entry;
42 	gchar *files;
43 	GSList *apps;
44 } Open_with_data;
45 
xa_app_compare(const App * a,const App * b)46 static gint xa_app_compare (const App *a, const App *b)
47 {
48 	if (strcmp(a->exec, b->exec) == 0)
49 	{
50 		if (a->name == b->name)
51 			return 0;
52 
53 		if (a->name && b->name)
54 			return strcmp(a->name, b->name);
55 	}
56 
57 	return 1;
58 }
59 
xa_app_free(App * app)60 static void xa_app_free (App *app)
61 {
62 	g_free(app->name);
63 	g_free(app->exec);
64 	g_object_unref(app->icon);
65 	g_free(app);
66 }
67 
xa_open_with_dialog_expander_expanded(GObject * object,GParamSpec * param_spec,Open_with_data * data)68 static void xa_open_with_dialog_expander_expanded (GObject *object, GParamSpec *param_spec, Open_with_data *data)
69 {
70 	gchar *cmd;
71 
72 	if (gtk_expander_get_expanded(GTK_EXPANDER(object)))
73 	{
74 		cmd = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(prefs_window->combo_prefered_custom_cmd));
75 
76 		if (cmd && *cmd)
77 			gtk_entry_set_text(GTK_ENTRY(data->custom_command_entry), cmd);
78 	}
79 }
80 
xa_open_with_dialog_selection_changed(GtkTreeSelection * selection,Open_with_data * data)81 static void xa_open_with_dialog_selection_changed (GtkTreeSelection *selection, Open_with_data *data)
82 {
83 	gchar *exec;
84 	GtkTreeIter iter;
85 	GtkTreeModel *model;
86 
87 	if (gtk_tree_selection_get_selected(selection,&model,&iter))
88 	{
89 		gtk_tree_model_get(model,&iter,2,&exec,-1);
90 		gtk_entry_set_text(GTK_ENTRY(data->custom_command_entry),exec);
91 		g_free(exec);
92 	}
93 }
94 
xa_open_with_dialog_execute_command(GtkButton * button,Open_with_data * data)95 static void xa_open_with_dialog_execute_command (GtkButton *button, Open_with_data *data)
96 {
97 	const char *application;
98 
99 	application = gtk_entry_get_text(GTK_ENTRY(data->custom_command_entry));
100 	xa_launch_external_program(application, data->files);
101 	gtk_widget_destroy(data->dialog1);
102 }
103 
xa_open_with_dialog_row_selected(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column,Open_with_data * data)104 static void xa_open_with_dialog_row_selected (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, Open_with_data *data)
105 {
106 	xa_open_with_dialog_execute_command(NULL, data);
107 }
108 
xa_open_with_dialog_custom_entry_activated(GtkEntry * entry,Open_with_data * data)109 static void xa_open_with_dialog_custom_entry_activated (GtkEntry *entry, Open_with_data *data)
110 {
111 	xa_open_with_dialog_execute_command(NULL, data);
112 }
113 
xa_open_with_dialog_mouse_button_event(GtkWidget * widget,GdkEventButton * event,Open_with_data * data)114 static gboolean xa_open_with_dialog_mouse_button_event (GtkWidget *widget, GdkEventButton *event, Open_with_data *data)
115 {
116 	if (event->type == GDK_BUTTON_RELEASE && event->button == 2)
117 	{
118 		xa_open_with_dialog_execute_command(NULL, data);
119 		return TRUE;
120 	}
121 
122 	return FALSE;
123 }
124 
xa_destroy_open_with_dialog(GTK_COMPAT_DESTROY_TYPE object,Open_with_data * data)125 static void xa_destroy_open_with_dialog (GTK_COMPAT_DESTROY_TYPE object, Open_with_data *data)
126 {
127 	g_free(data->files);
128 	g_slist_free_full(data->apps, (GDestroyNotify) xa_app_free);
129 	g_free(data);
130 }
131 
xa_open_with_dialog_browse_custom_command(GtkButton * button,Open_with_data * data)132 static void xa_open_with_dialog_browse_custom_command (GtkButton *button, Open_with_data *data)
133 {
134 	GtkWidget *file_selector;
135 	gchar *dest_dir, *dest_dir_utf8;
136 	gint response;
137 
138 	file_selector = gtk_file_chooser_dialog_new (_("Select an application"),
139 							GTK_WINDOW (xa_main_window),
140 							GTK_FILE_CHOOSER_ACTION_OPEN,
141 							GTK_STOCK_CANCEL,
142 							GTK_RESPONSE_CANCEL,
143 							GTK_STOCK_OPEN,
144 							GTK_RESPONSE_ACCEPT,
145 							NULL);
146 
147 	gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER (file_selector),"/usr/bin");
148 	response = gtk_dialog_run (GTK_DIALOG(file_selector));
149 	if (response == GTK_RESPONSE_ACCEPT)
150 	{
151 		dest_dir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (file_selector));
152 		dest_dir_utf8 = g_filename_display_name(dest_dir);
153 		gtk_entry_set_text(GTK_ENTRY(data->custom_command_entry), dest_dir_utf8);
154 		g_free(dest_dir_utf8);
155 		g_free(dest_dir);
156 	}
157 	gtk_widget_destroy(file_selector);
158 }
159 
xa_parse_desktop_file(const gchar * path,const gchar * name,Open_with_data * data)160 static void xa_parse_desktop_file (const gchar *path, const gchar *name, Open_with_data *data)
161 {
162 	gchar *filename, *line, *key;
163 	gchar *app_name = NULL, *app_exec = NULL, *app_icon = NULL;
164 	GIOStatus status;
165 	GIOChannel *file;
166 	gboolean group_line = FALSE, app_multiple = FALSE, has_mimetype = FALSE;
167 	const gchar * const *langs, * const *l;
168 	gint size;
169 
170 	filename = g_strconcat(path,"/",name,NULL);
171 	file = g_io_channel_new_file(filename,"r",NULL);
172 	g_free(filename);
173 	if (file == NULL)
174 		return;
175 	langs = g_get_language_names();
176 	g_io_channel_set_encoding(file,NULL,NULL);
177 	do
178 	{
179 		status = g_io_channel_read_line (file, &line, NULL, NULL, NULL);
180 		if (line != NULL)
181 		{
182 			if (strcmp(g_strchomp(line), "[Desktop Entry]") == 0)
183 			{
184 				group_line = TRUE;
185 				continue;
186 			}
187 			if (group_line && *line == '[')
188 				break;
189 			if (!group_line)
190 				continue;
191 			if (g_str_has_prefix(line, "Name["))
192 			{
193 				l = langs;
194 
195 				while (*l)
196 				{
197 					key = g_strconcat("Name[", *l, "]=", NULL);
198 
199 					if (g_str_has_prefix(line, key))
200 					{
201 						g_free(app_name);
202 						app_name = g_strndup(line + strlen(key), strlen(line) - strlen(key));
203 						g_free(key);
204 						break;
205 					}
206 
207 					g_free(key);
208 					l++;
209 				}
210 			}
211 			if (!app_name && g_str_has_prefix(line, "Name="))
212 			{
213 				app_name = g_strndup(line + 5, strlen(line) - 5);
214 				continue;
215 			}
216 			if (g_str_has_prefix(line,"Exec="))
217 			{
218 				app_exec = strstr(line, " %");
219 				if (app_exec)
220 				{
221 					app_multiple = (app_exec[2] == 'F' || app_exec[2] == 'U');
222 					app_exec = g_strndup(line + 5, app_exec - (line + 5));
223 				}
224 				else
225 					app_exec = g_strndup(line + 5, strlen(line) - 5);
226 				continue;
227 			}
228 			if (g_str_has_prefix(line,"Icon="))
229 			{
230 				app_icon = strrchr(line, '.');
231 				if (app_icon)
232 					app_icon = g_strndup(line + 5,app_icon - (line+5));
233 				else
234 					app_icon = g_strndup(line + 5, strlen(line) - 5);
235 				continue;
236 			}
237 			if (g_str_has_prefix(line,"MimeType="))
238 				has_mimetype = TRUE;
239 			g_free(line);
240 		}
241 	}
242 	while (status != G_IO_STATUS_EOF);
243 
244 	g_io_channel_shutdown(file, FALSE, NULL);
245 
246 	if (app_exec && has_mimetype)
247 	{
248 		App app;
249 		GSList *duplicate;
250 
251 		app.name = app_name;
252 		app.exec = app_exec;
253 		app.icon = NULL;
254 
255 		duplicate = g_slist_find_custom(data->apps, &app, (GCompareFunc) xa_app_compare);
256 
257 		if (!duplicate)
258 		{
259 			App *app;
260 			GdkPixbuf *pixbuf = NULL;
261 
262 			app = g_new0(App, 1);
263 
264 			app->name = app_name;
265 			app->exec = app_exec;
266 			app->multiple = app_multiple;
267 
268 			switch (gtk_combo_box_get_active(GTK_COMBO_BOX(prefs_window->combo_icon_size)))
269 			{
270 				case 0:
271 					size = 24;
272 					break;
273 
274 				case 1:
275 				case 2:
276 					size = 32;
277 					break;
278 
279 				case 3:
280 					size = 40;
281 					break;
282 
283 				default:
284 					size = 48;
285 					break;
286 			}
287 
288 			if (app_icon)
289 			{
290 				pixbuf = gtk_icon_theme_load_icon(icon_theme, app_icon, size, GTK_ICON_LOOKUP_FORCE_SIZE, NULL);
291 				g_free(app_icon);
292 			}
293 
294 			if (!pixbuf)
295 			{
296 				pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, size, size);
297 				gdk_pixbuf_fill(pixbuf, 0x00000000);
298 			}
299 
300 			app->icon = pixbuf;
301 
302 			data->apps = g_slist_prepend(data->apps, app);
303 
304 			return;
305 		}
306 	}
307 
308 	g_free(app_name);
309 	g_free(app_exec);
310 	g_free(app_icon);
311 }
312 
xa_read_desktop_directory(const gchar * dirname,GtkListStore * liststore,Open_with_data * data)313 static void xa_read_desktop_directory (const gchar *dirname, GtkListStore *liststore, Open_with_data *data)
314 {
315 	DIR *dir;
316 	gchar *filename = NULL;
317 	struct dirent *dirlist;
318 
319 	filename = g_build_filename(dirname,"applications",NULL);
320 	dir = opendir(filename);
321 
322 	if (dir == NULL)
323 	{
324 		g_free(filename);
325 		return;
326 	}
327 
328 	while ((dirlist = readdir(dir)))
329 	{
330 		if (g_str_has_suffix(dirlist->d_name,".desktop"))
331 			xa_parse_desktop_file(filename, dirlist->d_name, data);
332 	}
333 
334 	closedir(dir);
335 	g_free(filename);
336 }
337 
xa_create_open_with_dialog(const gchar * filename,gchar * filenames,gint nr)338 void xa_create_open_with_dialog (const gchar *filename, gchar *filenames, gint nr)
339 {
340 	Open_with_data *data = NULL;
341 	GtkListStore *apps_liststore;
342 	GtkWidget *dialog_vbox1, *vbox1, *hbox1, *mime_icon, *open_text, *scrolledwindow1, *apps_treeview,
343 				*custom_command_expander,*hbox_expander,*browse,*cancelbutton1,*okbutton1;
344 	GtkCellRenderer		*renderer;
345 	GtkTreeViewColumn	*column;
346 	GtkTreeIter iter;
347 	GdkPixbuf *pixbuf;
348 	GSList *apps;
349 	gchar *text = NULL;
350 	gchar *title;
351 	const gchar *icon_name = NULL;
352 	const gchar *desktop_dir;
353 	const gchar* const *desktop_dirs;
354 	gint x = 0;
355 
356 	data = g_new0(Open_with_data,1);
357 	data->files = filenames;
358 	data->dialog1 = gtk_dialog_new ();
359 	if (nr == 1)
360 		title = _("Open With");
361 	else
362 		title = _("Open the selected files with");
363 
364 	gtk_window_set_title (GTK_WINDOW (data->dialog1),title);
365 	gtk_window_set_position (GTK_WINDOW (data->dialog1), GTK_WIN_POS_CENTER_ON_PARENT);
366 	gtk_window_set_modal (GTK_WINDOW (data->dialog1), TRUE);
367 	gtk_window_set_type_hint (GTK_WINDOW (data->dialog1), GDK_WINDOW_TYPE_HINT_DIALOG);
368 	gtk_window_set_transient_for(GTK_WINDOW(data->dialog1),GTK_WINDOW(xa_main_window));
369 	gtk_container_set_border_width (GTK_CONTAINER (data->dialog1),5);
370 	gtk_widget_set_size_request(data->dialog1, 380, 460);
371 	dialog_vbox1 = gtk_dialog_get_content_area(GTK_DIALOG(data->dialog1));
372 
373 	vbox1 = gtk_vbox_new (FALSE, 5);
374 	gtk_box_pack_start (GTK_BOX (dialog_vbox1),vbox1,TRUE,TRUE,0);
375 
376 	hbox1 = gtk_hbox_new (FALSE, 1);
377 	gtk_box_pack_start (GTK_BOX (vbox1),hbox1,FALSE,FALSE,0);
378 
379 	if (nr == 1)
380 	{
381 		icon_name = xa_get_stock_mime_icon(filename);
382 		pixbuf = gtk_icon_theme_load_icon(icon_theme, icon_name, 40, (GtkIconLookupFlags) 0, NULL);
383 		mime_icon = gtk_image_new_from_pixbuf(pixbuf);
384 		if (pixbuf)
385 			g_object_unref(pixbuf);
386 		gtk_box_pack_start (GTK_BOX (hbox1),mime_icon,FALSE,TRUE,0);
387 		gtk_misc_set_alignment (GTK_MISC (mime_icon),0,0);
388 
389 		open_text = gtk_label_new("");
390 		gtk_box_pack_start (GTK_BOX (hbox1),open_text,FALSE,FALSE,10);
391 		text = g_strdup_printf(_("Open <i>%s</i> with:"),filename);
392 		gtk_label_set_use_markup (GTK_LABEL (open_text),TRUE);
393 		gtk_label_set_markup (GTK_LABEL (open_text),text);
394 		g_free(text);
395 	}
396 	scrolledwindow1 = gtk_scrolled_window_new (NULL,NULL);
397 	gtk_box_pack_start (GTK_BOX (vbox1),scrolledwindow1,TRUE,TRUE,0);
398 	g_object_set (G_OBJECT (scrolledwindow1),"hscrollbar-policy",GTK_POLICY_AUTOMATIC,"shadow-type",GTK_SHADOW_IN,"vscrollbar-policy",GTK_POLICY_AUTOMATIC,NULL);
399 
400 	apps_liststore = gtk_list_store_new(3, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING);
401 	apps_treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(apps_liststore));
402 	gtk_container_add (GTK_CONTAINER (scrolledwindow1),apps_treeview);
403 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(apps_treeview),FALSE);
404 	GtkTreeSelection *sel = gtk_tree_view_get_selection( GTK_TREE_VIEW (apps_treeview));
405 	g_signal_connect(sel, "changed", G_CALLBACK(xa_open_with_dialog_selection_changed), data);
406 
407 	/* First column: icon + text */
408 	column = gtk_tree_view_column_new();
409 	renderer = gtk_cell_renderer_pixbuf_new();
410 	gtk_tree_view_column_pack_start(column,renderer, FALSE);
411 	gtk_tree_view_column_set_attributes(column,renderer,"pixbuf",0,NULL);
412 
413 	renderer = gtk_cell_renderer_text_new ();
414 	gtk_tree_view_column_pack_start(column,renderer, TRUE);
415 	gtk_tree_view_column_set_attributes( column,renderer,"text",1,NULL);
416 	gtk_tree_view_column_set_resizable (column, TRUE);
417 	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(apps_liststore), 1, GTK_SORT_ASCENDING);
418 	gtk_tree_view_append_column (GTK_TREE_VIEW (apps_treeview), column);
419 	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
420 
421 	/* Hidden column with the application executable name */
422 	column = gtk_tree_view_column_new();
423 	gtk_tree_view_column_set_visible(column,FALSE);
424 	gtk_tree_view_append_column (GTK_TREE_VIEW (apps_treeview), column);
425 
426 	custom_command_expander = gtk_expander_new_with_mnemonic(_("Use a custom command:"));
427 	g_signal_connect(G_OBJECT(custom_command_expander), "notify::expanded", G_CALLBACK(xa_open_with_dialog_expander_expanded), data);
428 	gtk_box_pack_start (GTK_BOX (vbox1),custom_command_expander,FALSE,FALSE,0);
429 
430 	hbox_expander = gtk_hbox_new(FALSE,5);
431 
432 	data->custom_command_entry = gtk_entry_new();
433 	g_signal_connect (G_OBJECT (data->custom_command_entry),"activate",G_CALLBACK (xa_open_with_dialog_custom_entry_activated),data);
434 
435 	browse = gtk_button_new_with_label(_("Browse"));
436 	g_signal_connect (G_OBJECT (browse),"clicked",G_CALLBACK (xa_open_with_dialog_browse_custom_command),data);
437 
438 	gtk_box_pack_start (GTK_BOX (hbox_expander),data->custom_command_entry,TRUE,TRUE,0);
439 	gtk_box_pack_start (GTK_BOX (hbox_expander),browse,FALSE,TRUE,0);
440 	gtk_container_add(GTK_CONTAINER(custom_command_expander),hbox_expander);
441 
442 	cancelbutton1 = gtk_button_new_from_stock ("gtk-cancel");
443 	gtk_widget_show (cancelbutton1);
444 	gtk_dialog_add_action_widget (GTK_DIALOG (data->dialog1),cancelbutton1,GTK_RESPONSE_CANCEL);
445 	g_signal_connect_swapped (G_OBJECT (cancelbutton1),"clicked",G_CALLBACK (gtk_widget_destroy),G_OBJECT(data->dialog1));
446 
447 	okbutton1 = gtk_button_new_from_stock ("gtk-open");
448 	gtk_widget_show (okbutton1);
449 	gtk_dialog_add_action_widget (GTK_DIALOG (data->dialog1),okbutton1,GTK_RESPONSE_OK);
450 	g_signal_connect (G_OBJECT (okbutton1),"clicked",G_CALLBACK (xa_open_with_dialog_execute_command),data);
451 	gtk_widget_set_can_default(okbutton1, TRUE);
452 	gtk_widget_grab_default(okbutton1);
453 	gtk_widget_show_all(data->dialog1);
454 
455 	/* let's parse the desktop files in the user data dir */
456 	desktop_dir = g_get_user_data_dir();
457 
458 	if (desktop_dir)
459 		xa_read_desktop_directory(desktop_dir, apps_liststore, data);
460 
461 	/* let's parse the desktop files in all the system data dirs */
462 	desktop_dirs = g_get_system_data_dirs();
463 
464 	while (desktop_dirs[x])
465 	{
466 		xa_read_desktop_directory(desktop_dirs[x], apps_liststore, data);
467 		x++;
468 	}
469 
470 	apps = data->apps;
471 
472 	while (apps)
473 	{
474 		App *app = apps->data;
475 
476 		if (app->multiple || (nr == 1))
477 		{
478 			gtk_list_store_append(apps_liststore, &iter);
479 			gtk_list_store_set(apps_liststore, &iter, 0, app->icon, 1, app->name, 2, app->exec, -1);
480 		}
481 
482 		apps = apps->next;
483 	}
484 
485 	if (data->apps)
486 	{
487 		gtk_tree_model_get_iter_first(GTK_TREE_MODEL(apps_liststore), &iter);
488 		gtk_tree_selection_select_iter(gtk_tree_view_get_selection(GTK_TREE_VIEW(apps_treeview)), &iter);
489 	}
490 	else
491 		gtk_widget_set_sensitive(okbutton1, FALSE);
492 
493 	g_signal_connect (G_OBJECT (apps_treeview),	"row-activated",G_CALLBACK(xa_open_with_dialog_row_selected),data);
494 	g_signal_connect(G_OBJECT(apps_treeview), "button-release-event", G_CALLBACK(xa_open_with_dialog_mouse_button_event), data);
495 	g_signal_connect (G_OBJECT (data->dialog1),	"destroy",		G_CALLBACK(xa_destroy_open_with_dialog),data);
496 }
497