read external editors from .desktop files
[geeqie.git] / src / editors.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "editors.h"
16
17 #include "filedata.h"
18 #include "filefilter.h"
19 #include "misc.h"
20 #include "ui_fileops.h"
21 #include "ui_spinner.h"
22 #include "ui_utildlg.h"
23 #include "utilops.h"
24
25 #include <errno.h>
26
27
28 #define EDITOR_WINDOW_WIDTH 500
29 #define EDITOR_WINDOW_HEIGHT 300
30
31
32
33 typedef struct _EditorVerboseData EditorVerboseData;
34 struct _EditorVerboseData {
35         GenericDialog *gd;
36         GtkWidget *button_close;
37         GtkWidget *button_stop;
38         GtkWidget *text;
39         GtkWidget *progress;
40         GtkWidget *spinner;
41 };
42
43 typedef struct _EditorData EditorData;
44 struct _EditorData {
45         gint flags;
46         GPid pid;
47         GList *list;
48         gint count;
49         gint total;
50         gboolean stopping;
51         EditorVerboseData *vd;
52         EditorCallback callback;
53         gpointer data;
54         const EditorDescription *editor;
55 };
56
57
58 static void editor_verbose_window_progress(EditorData *ed, const gchar *text);
59 static gint editor_command_next_start(EditorData *ed);
60 static gint editor_command_next_finish(EditorData *ed, gint status);
61 static gint editor_command_done(EditorData *ed);
62 static gint editor_command_parse(const EditorDescription *editor, GList *list, gchar **output);
63
64 /*
65  *-----------------------------------------------------------------------------
66  * external editor routines
67  *-----------------------------------------------------------------------------
68  */
69
70 GHashTable *editors = NULL;
71
72 #ifdef G_KEY_FILE_DESKTOP_GROUP
73 #define DESKTOP_GROUP G_KEY_FILE_DESKTOP_GROUP
74 #else
75 #define DESKTOP_GROUP "Desktop Entry"
76 #endif
77
78 void editor_description_free(EditorDescription *editor)
79 {
80         if (!editor) return;
81         
82         g_free(editor->key);
83         g_free(editor->name);
84         g_free(editor->exec);
85         g_free(editor->menu_path);
86         g_free(editor->hotkey);
87         string_list_free(editor->ext_list);
88         g_free(editor->icon);
89         g_free(editor->file);
90         g_free(editor);
91 }
92
93 static GList *editor_mime_types_to_extensions(gchar **mime_types)
94 {
95         /* FIXME: this should be rewritten to use the shared mime database, as soon as we switch to gio */
96         
97         static const gchar *conv_table[][2] = {
98                 {"application/x-ufraw", "%raw"},
99                 {"image/*",             "*"},
100                 {"image/bmp",           ".bmp"},
101                 {"image/gif",           ".gif"},
102                 {"image/jpeg",          ".jpeg;.jpg"},
103                 {"image/jpg",           ".jpg;.jpeg"},
104                 {"image/pcx",           ".pcx"},
105                 {"image/png",           ".png"},
106                 {"image/svg",           ".svg"},
107                 {"image/svg+xml",       ".svg"},
108                 {"image/svg+xml-compressed",    ".svg"},        
109                 {"image/tiff",          ".tiff;.tif"},
110                 {"image/x-bmp",         ".bmp"},
111                 {"image/x-canon-crw",   ".crw"},
112                 {"image/x-cr2",         ".cr2"},
113                 {"image/x-dcraw",       "%raw"},
114                 {"image/x-ico",         ".ico"},
115                 {"image/x-mrw",         ".mrw"},
116                 {"image/x-MS-bmp",      ".bmp"},
117                 {"image/x-nef",         ".nef"},
118                 {"image/x-orf",         ".orf"},
119                 {"image/x-pcx",         ".pcx"},
120                 {"image/xpm",           ".xpm"},
121                 {"image/x-png",         ".png"},
122                 {"image/x-portable-anymap",     ".pam"},        
123                 {"image/x-portable-bitmap",     ".pbm"},
124                 {"image/x-portable-graymap",    ".pgm"},
125                 {"image/x-portable-pixmap",     ".ppm"},
126                 {"image/x-psd",         ".psd"},
127                 {"image/x-raf",         ".raf"},
128                 {"image/x-sgi",         ".sgi"},
129                 {"image/x-tga",         ".tga"},
130                 {"image/x-xbitmap",     ".xbm"},
131                 {"image/x-xcf",         ".xcf"},
132                 {"image/x-xpixmap",     ".xpm"},
133                 {"image/x-x3f",         ".x3f"},
134                 {NULL, NULL}};
135         
136         gint i, j;
137         GList *list = NULL;
138         
139         for (i = 0; mime_types[i]; i++) 
140                 for (j = 0; conv_table[j][0]; j++)
141                         if (strcmp(mime_types[i], conv_table[j][0]) == 0)
142                                 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
143         
144         return list;
145 }
146
147 static gboolean editor_read_desktop_file(const gchar *path)
148 {
149         GKeyFile *key_file;
150         EditorDescription *editor;
151         gchar *extensions;
152         const gchar *key = filename_from_path(path);
153         gchar **categories, **only_show_in, **not_show_in;
154
155         if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
156         
157         key_file = g_key_file_new();
158         if (!g_key_file_load_from_file(key_file, path, 0, NULL))
159                 {
160                 g_key_file_free(key_file);
161                 return FALSE;
162                 }
163         
164         editor = g_new0(EditorDescription, 1);
165         
166         editor->key = g_strdup(key);
167         editor->file = g_strdup(path);
168
169         g_hash_table_insert(editors, editor->key, editor);
170
171         if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)) editor->hidden = TRUE;
172
173         categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
174         if (categories)
175                 {
176                 gboolean found = FALSE;
177                 gint i;
178                 for (i = 0; categories[i]; i++) 
179                         /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
180                         if (strcmp(categories[i], "Graphics") == 0 ||
181                             strcmp(categories[i], "X-Geeqie") == 0) 
182                                 {
183                                 found = TRUE;
184                                 break;
185                                 }
186                 if (!found) editor->hidden = TRUE;
187                 g_strfreev(categories);
188                 }
189         else
190                 {
191                 editor->hidden = TRUE;
192                 }
193
194         only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
195         if (only_show_in)
196                 {
197                 gboolean found = FALSE;
198                 gint i;
199                 for (i = 0; only_show_in[i]; i++) 
200                         if (strcmp(only_show_in[i], "X-Geeqie") == 0)
201                                 {
202                                 found = TRUE;
203                                 break;
204                                 }
205                 if (!found) editor->hidden = TRUE;
206                 g_strfreev(only_show_in);
207                 }
208
209         not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
210         if (not_show_in)
211                 {
212                 gboolean found = FALSE;
213                 gint i;
214                 for (i = 0; not_show_in[i]; i++) 
215                         if (strcmp(not_show_in[i], "X-Geeqie") == 0)
216                                 {
217                                 found = TRUE;
218                                 break;
219                                 }
220                 if (found) editor->hidden = TRUE;
221                 g_strfreev(not_show_in);
222                 }
223                 
224         if (editor->hidden) 
225                 {
226                 /* hidden editors will be deleted, no need to parse the rest */
227                 g_key_file_free(key_file);
228                 return TRUE;
229                 }
230         
231         editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
232         editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
233
234         editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
235         
236         /* we take only editors that accept parameters, FIXME: the test can be improved */
237         if (!strchr(editor->exec, '%')) editor->hidden = TRUE; 
238         
239         editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
240         if (!editor->menu_path) editor->menu_path = g_strdup("EditMenu/ExternalMenu");
241         
242         editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
243
244         extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
245         if (extensions)
246                 editor->ext_list = filter_to_list(extensions);
247         else
248                 {
249                 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
250                 if (mime_types)
251                         {
252                         editor->ext_list = editor_mime_types_to_extensions(mime_types);
253                         g_strfreev(mime_types);
254                         if (!editor->ext_list) editor->hidden = TRUE; 
255                         }
256                 
257                 }
258                 
259
260         if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
261         if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
262         if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
263         if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
264         if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
265
266         
267         editor->flags |= editor_command_parse(editor, NULL, NULL);
268         g_key_file_free(key_file);
269         
270         return TRUE;    
271 }
272
273 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
274 {
275         EditorDescription *editor = value;
276         return editor->hidden;
277 }
278
279 static void editor_read_desktop_dir(const gchar *path)
280 {
281         DIR *dp;
282         struct dirent *dir;
283         gchar *pathl;
284
285         pathl = path_from_utf8(path);
286         dp = opendir(pathl);
287         g_free(pathl);
288         if (!dp)
289                 {
290                 /* dir not found */
291                 return;
292                 }
293         while ((dir = readdir(dp)) != NULL)
294                 {
295                 gchar *namel = dir->d_name;
296                 size_t len = strlen(namel);
297                 
298                 if (len > 8 && strncasecmp(namel + len - 8, ".desktop", 8) == 0)
299                         {
300                         gchar *name = path_to_utf8(namel);
301                         gchar *dpath = g_build_filename(path, name, NULL);
302                         editor_read_desktop_file(dpath);
303                         g_free(dpath);
304                         g_free(name);
305                         }       
306                 }
307         closedir(dp);
308 }
309
310 void editor_load_descriptions(void)
311 {
312         gchar *path;
313         gchar *xdg_data_dirs;
314         gchar *all_dirs;
315         gchar **split_dirs;
316         gint i;
317         
318         if (!editors)
319                 {
320                 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
321                 }
322
323         xdg_data_dirs = getenv("XDG_DATA_DIRS");
324         if (xdg_data_dirs && xdg_data_dirs[0])
325                 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
326         else
327                 xdg_data_dirs = g_strdup("/usr/share");
328         
329         all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_dirs, NULL);
330         
331         g_free(xdg_data_dirs);
332
333         split_dirs = g_strsplit(all_dirs, ":", 0);
334         
335         g_free(all_dirs);
336
337         for (i = 0; split_dirs[i]; i++)
338                 {
339                 path = g_build_filename(split_dirs[i], "applications", NULL);
340                 editor_read_desktop_dir(path);
341                 g_free(path);
342                 }
343                 
344         g_strfreev(split_dirs);
345         
346         g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
347 }
348
349 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
350 {
351         GList **listp = data;
352         EditorDescription *editor = value;
353         
354         /* do not show the special commands in any list, they are called explicitelly */ 
355         if (strcmp(editor->key, CMD_COPY) == 0 ||
356             strcmp(editor->key, CMD_MOVE) == 0 ||  
357             strcmp(editor->key, CMD_RENAME) == 0 ||
358             strcmp(editor->key, CMD_DELETE) == 0 ||
359             strcmp(editor->key, CMD_FOLDER) == 0) return;
360
361         *listp = g_list_prepend(*listp, editor);
362 }
363
364 GList *editor_list_get(void)
365 {
366         GList *editors_list = NULL;
367         g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
368         return editors_list;
369 }
370
371 /* ------------------------------ */
372
373
374 static void editor_verbose_data_free(EditorData *ed)
375 {
376         if (!ed->vd) return;
377         g_free(ed->vd);
378         ed->vd = NULL;
379 }
380
381 static void editor_data_free(EditorData *ed)
382 {
383         editor_verbose_data_free(ed);
384         g_free(ed);
385 }
386
387 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
388 {
389         EditorData *ed = data;
390
391         generic_dialog_close(gd);
392         editor_verbose_data_free(ed);
393         if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
394 }
395
396 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
397 {
398         EditorData *ed = data;
399         ed->stopping = TRUE;
400         ed->count = 0;
401         editor_verbose_window_progress(ed, _("stopping..."));
402 }
403
404 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
405 {
406         vd->gd->cancel_cb = editor_verbose_window_close;
407
408         spinner_set_interval(vd->spinner, -1);
409         gtk_widget_set_sensitive(vd->button_stop, FALSE);
410         gtk_widget_set_sensitive(vd->button_close, TRUE);
411 }
412
413 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
414 {
415         EditorVerboseData *vd;
416         GtkWidget *scrolled;
417         GtkWidget *hbox;
418         gchar *buf;
419
420         vd = g_new0(EditorVerboseData, 1);
421
422         vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
423                                    NULL, FALSE,
424                                    NULL, ed);
425         buf = g_strdup_printf(_("Output of %s"), text);
426         generic_dialog_add_message(vd->gd, NULL, buf, NULL);
427         g_free(buf);
428         vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
429                                                    editor_verbose_window_stop, FALSE);
430         gtk_widget_set_sensitive(vd->button_stop, FALSE);
431         vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
432                                                     editor_verbose_window_close, TRUE);
433         gtk_widget_set_sensitive(vd->button_close, FALSE);
434
435         scrolled = gtk_scrolled_window_new(NULL, NULL);
436         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
437         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
438                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
439         gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
440         gtk_widget_show(scrolled);
441
442         vd->text = gtk_text_view_new();
443         gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
444         gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
445         gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
446         gtk_widget_show(vd->text);
447
448         hbox = gtk_hbox_new(FALSE, 0);
449         gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
450         gtk_widget_show(hbox);
451
452         vd->progress = gtk_progress_bar_new();
453         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
454         gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
455         gtk_widget_show(vd->progress);
456
457         vd->spinner = spinner_new(NULL, SPINNER_SPEED);
458         gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
459         gtk_widget_show(vd->spinner);
460
461         gtk_widget_show(vd->gd->dialog);
462
463         ed->vd = vd;
464         return vd;
465 }
466
467 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
468 {
469         GtkTextBuffer *buffer;
470         GtkTextIter iter;
471
472         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
473         gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
474         gtk_text_buffer_insert(buffer, &iter, text, len);
475 }
476
477 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
478 {
479         if (!ed->vd) return;
480
481         if (ed->total)
482                 {
483                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
484                 }
485
486         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
487 }
488
489 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
490 {
491         EditorData *ed = data;
492         gchar buf[512];
493         gsize count;
494
495         if (condition & G_IO_IN)
496                 {
497                 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
498                         {
499                         if (!g_utf8_validate(buf, count, NULL))
500                                 {
501                                 gchar *utf8;
502
503                                 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
504                                 if (utf8)
505                                         {
506                                         editor_verbose_window_fill(ed->vd, utf8, -1);
507                                         g_free(utf8);
508                                         }
509                                 else
510                                         {
511                                         editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
512                                         }
513                                 }
514                         else
515                                 {
516                                 editor_verbose_window_fill(ed->vd, buf, count);
517                                 }
518                         }
519                 }
520
521         if (condition & (G_IO_ERR | G_IO_HUP))
522                 {
523                 g_io_channel_shutdown(source, TRUE, NULL);
524                 return FALSE;
525                 }
526
527         return TRUE;
528 }
529
530 typedef enum {
531         PATH_FILE,
532         PATH_FILE_URL,
533         PATH_DEST
534 } PathType;
535
536
537 static gchar *editor_command_path_parse(const FileData *fd, PathType type, const EditorDescription *editor)
538 {
539         GString *string;
540         gchar *pathl;
541         const gchar *p = NULL;
542
543         string = g_string_new("");
544
545         if (type == PATH_FILE || type == PATH_FILE_URL)
546                 {
547                 GList *work = editor->ext_list;
548
549                 if (!work)
550                         p = fd->path;
551                 else
552                         {
553                         while (work)
554                                 {
555                                 GList *work2;
556                                 gchar *ext = work->data;
557                                 work = work->next;
558
559                                 if (strcmp(ext, "*") == 0 ||
560                                     strcasecmp(ext, fd->extension) == 0)
561                                         {
562                                         p = fd->path;
563                                         break;
564                                         }
565
566                                 work2 = fd->sidecar_files;
567                                 while (work2)
568                                         {
569                                         FileData *sfd = work2->data;
570                                         work2 = work2->next;
571
572                                         if (strcasecmp(ext, sfd->extension) == 0)
573                                                 {
574                                                 p = sfd->path;
575                                                 break;
576                                                 }
577                                         }
578                                 if (p) break;
579                                 }
580                         if (!p) return NULL;
581                         }
582                 }
583         else if (type == PATH_DEST)
584                 {
585                 if (fd->change && fd->change->dest)
586                         p = fd->change->dest;
587                 else
588                         p = "";
589                 }
590
591         while (*p != '\0')
592                 {
593                 /* must escape \, ", `, and $ to avoid problems,
594                  * we assume system shell supports bash-like escaping
595                  */
596                 if (strchr("\\\"`$", *p) != NULL)
597                         {
598                         string = g_string_append_c(string, '\\');
599                         }
600                 string = g_string_append_c(string, *p);
601                 p++;
602                 }
603
604         if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
605         pathl = path_from_utf8(string->str);
606         g_string_free(string, TRUE);
607
608         return pathl;
609 }
610
611
612 static gint editor_command_parse(const EditorDescription *editor, GList *list, gchar **output)
613 {
614         gint flags = 0;
615         const gchar *p;
616         GString *result = NULL;
617
618         if (output)
619                 result = g_string_new("");
620
621         if (editor->exec[0] == '\0')
622                 {
623                 flags |= EDITOR_ERROR_EMPTY;
624                 goto err;
625                 }
626         
627         p = editor->exec;
628         /* skip leading whitespaces if any */
629         while (g_ascii_isspace(*p)) p++;
630
631         /* command */
632
633         while (*p)
634                 {
635                 if (*p != '%')
636                         {
637                         if (output) result = g_string_append_c(result, *p);
638                         }
639                 else /* *p == '%' */
640                         {
641                         gchar *pathl = NULL;
642
643                         p++;
644
645                         switch (*p)
646                                 {
647                                 case 'f': /* single file */
648                                 case 'u': /* single url */
649                                         flags |= EDITOR_FOR_EACH;
650                                         if (flags & EDITOR_SINGLE_COMMAND)
651                                                 {
652                                                 flags |= EDITOR_ERROR_INCOMPATIBLE;
653                                                 goto err;
654                                                 }
655                                         if (output)
656                                                 {
657                                                 /* use the first file from the list */
658                                                 if (!list || !list->data)
659                                                         {
660                                                         flags |= EDITOR_ERROR_NO_FILE;
661                                                         goto err;
662                                                         }
663                                                 pathl = editor_command_path_parse((FileData *)list->data,
664                                                                                   (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
665                                                                                   editor);
666                                                 if (!pathl)
667                                                         {
668                                                         flags |= EDITOR_ERROR_NO_FILE;
669                                                         goto err;
670                                                         }
671                                                 result = g_string_append_c(result, '"');
672                                                 result = g_string_append(result, pathl);
673                                                 g_free(pathl);
674                                                 result = g_string_append_c(result, '"');
675                                                 }
676                                         break;
677
678                                 case 'F':
679                                 case 'U':
680                                         flags |= EDITOR_SINGLE_COMMAND;
681                                         if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
682                                                 {
683                                                 flags |= EDITOR_ERROR_INCOMPATIBLE;
684                                                 goto err;
685                                                 }
686
687                                         if (output)
688                                                 {
689                                                 /* use whole list */
690                                                 GList *work = list;
691                                                 gboolean ok = FALSE;
692
693                                                 while (work)
694                                                         {
695                                                         FileData *fd = work->data;
696                                                         pathl = editor_command_path_parse(fd, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
697
698                                                         if (pathl)
699                                                                 {
700                                                                 ok = TRUE;
701                                                                 if (work != list) g_string_append_c(result, ' ');
702                                                                 result = g_string_append_c(result, '"');
703                                                                 result = g_string_append(result, pathl);
704                                                                 g_free(pathl);
705                                                                 result = g_string_append_c(result, '"');
706                                                                 }
707                                                         work = work->next;
708                                                         }
709                                                 if (!ok)
710                                                         {
711                                                         flags |= EDITOR_ERROR_NO_FILE;
712                                                         goto err;
713                                                         }
714                                                 }
715                                         break;
716                                 case 'i':
717                                         if (output)
718                                                 {
719                                                 result = g_string_append(result, editor->icon);
720                                                 }
721                                         break;
722                                 case 'c':
723                                         if (output)
724                                                 {
725                                                 result = g_string_append(result, editor->name);
726                                                 }
727                                         break;
728                                 case 'k':
729                                         if (output)
730                                                 {
731                                                 result = g_string_append(result, editor->file);
732                                                 }
733                                         break;
734                                 case '%':
735                                         /* %% = % escaping */
736                                         if (output) result = g_string_append_c(result, *p);
737                                         break;
738                                 case 'd':
739                                 case 'D':
740                                 case 'n':
741                                 case 'N':
742                                 case 'v':
743                                 case 'm':
744                                         /* deprecated according to spec, ignore */
745                                         break;
746                                 default:
747                                         flags |= EDITOR_ERROR_SYNTAX;
748                                         goto err;
749                                 }
750                         }
751                 p++;
752                 }
753
754         if (output) *output = g_string_free(result, FALSE);
755         return flags;
756
757
758 err:
759         if (output)
760                 {
761                 g_string_free(result, TRUE);
762                 *output = NULL;
763                 }
764         return flags;
765 }
766
767
768 static void editor_child_exit_cb (GPid pid, gint status, gpointer data)
769 {
770         EditorData *ed = data;
771         g_spawn_close_pid(pid);
772         ed->pid = -1;
773
774         editor_command_next_finish(ed, status);
775 }
776
777
778 static gint editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
779 {
780         gchar *command;
781         FileData *fd = list->data;
782         GPid pid;
783         gint standard_output;
784         gint standard_error;
785         gboolean ok;
786
787         ed->pid = -1;
788         ed->flags = editor->flags | editor_command_parse(editor, list, &command);
789
790         ok = !(ed->flags & EDITOR_ERROR_MASK);
791
792         if (ok)
793                 {
794                 ok = (options->shell.path && *options->shell.path);
795                 if (!ok) log_printf("ERROR: empty shell command\n");
796                         
797                 if (ok)
798                         {
799                         ok = (access(options->shell.path, X_OK) == 0);
800                         if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
801                         }
802
803                 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
804                 }
805
806         if (ok)
807                 {
808                 gchar *working_directory;
809                 gchar *args[4];
810                 guint n = 0;
811
812                 working_directory = remove_level_from_path(fd->path);
813                 args[n++] = options->shell.path;
814                 if (options->shell.options && *options->shell.options)
815                         args[n++] = options->shell.options;
816                 args[n++] = command;
817                 args[n] = NULL;
818
819                 if ((ed->flags & EDITOR_DEST) && fd->change && fd->change->dest) /* FIXME: error handling */
820                         {
821                         setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
822                         }
823                 else
824                         {
825                         unsetenv("GEEQIE_DESTINATION");
826                         }
827
828                 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
829                                       G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
830                                       NULL, NULL,
831                                       &pid,
832                                       NULL,
833                                       ed->vd ? &standard_output : NULL,
834                                       ed->vd ? &standard_error : NULL,
835                                       NULL);
836                 
837                 g_free(working_directory);
838
839                 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
840                 }
841
842         if (ok)
843                 {
844                 g_child_watch_add(pid, editor_child_exit_cb, ed);
845                 ed->pid = pid;
846                 }
847
848         if (ed->vd)
849                 {
850                 if (!ok)
851                         {
852                         gchar *buf;
853
854                         buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
855                         editor_verbose_window_fill(ed->vd, buf, strlen(buf));
856                         g_free(buf);
857
858                         }
859                 else
860                         {
861                         GIOChannel *channel_output;
862                         GIOChannel *channel_error;
863
864                         channel_output = g_io_channel_unix_new(standard_output);
865                         g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
866                         g_io_channel_set_encoding(channel_output, NULL, NULL);
867
868                         g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
869                                             editor_verbose_io_cb, ed, NULL);
870                         g_io_channel_unref(channel_output);
871
872                         channel_error = g_io_channel_unix_new(standard_error);
873                         g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
874                         g_io_channel_set_encoding(channel_error, NULL, NULL);
875
876                         g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
877                                             editor_verbose_io_cb, ed, NULL);
878                         g_io_channel_unref(channel_error);
879                         }
880                 }
881
882         g_free(command);
883
884         return ed->flags & EDITOR_ERROR_MASK;
885 }
886
887 static gint editor_command_next_start(EditorData *ed)
888 {
889         if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
890
891         if (ed->list && ed->count < ed->total)
892                 {
893                 FileData *fd;
894                 gint error;
895
896                 fd = ed->list->data;
897
898                 if (ed->vd)
899                         {
900                         editor_verbose_window_progress(ed, (ed->flags & EDITOR_FOR_EACH) ? fd->path : _("running..."));
901                         }
902                 ed->count++;
903
904                 error = editor_command_one(ed->editor, ed->list, ed);
905                 if (!error && ed->vd)
906                         {
907                         gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
908                         if (ed->flags & EDITOR_FOR_EACH)
909                                 {
910                                 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
911                                 editor_verbose_window_fill(ed->vd, "\n", 1);
912                                 }
913                         }
914
915                 if (!error)
916                         return 0;
917                 else
918                         /* command was not started, call the finish immediately */
919                         return editor_command_next_finish(ed, 0);
920                 }
921
922         /* everything is done */
923         return editor_command_done(ed);
924 }
925
926 static gint editor_command_next_finish(EditorData *ed, gint status)
927 {
928         gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
929
930         if (status)
931                 ed->flags |= EDITOR_ERROR_STATUS;
932
933         if (ed->flags & EDITOR_FOR_EACH)
934                 {
935                 /* handle the first element from the list */
936                 GList *fd_element = ed->list;
937
938                 ed->list = g_list_remove_link(ed->list, fd_element);
939                 if (ed->callback)
940                         {
941                         cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
942                         if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
943                         }
944                 filelist_free(fd_element);
945                 }
946         else
947                 {
948                 /* handle whole list */
949                 if (ed->callback)
950                         cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
951                 filelist_free(ed->list);
952                 ed->list = NULL;
953                 }
954
955         if (cont == EDITOR_CB_SUSPEND)
956                 return ed->flags & EDITOR_ERROR_MASK;
957         else if (cont == EDITOR_CB_SKIP)
958                 return editor_command_done(ed);
959         else
960                 return editor_command_next_start(ed);
961 }
962
963 static gint editor_command_done(EditorData *ed)
964 {
965         gint flags;
966
967         if (ed->vd)
968                 {
969                 const gchar *text;
970
971                 if (ed->count == ed->total)
972                         {
973                         text = _("done");
974                         }
975                 else
976                         {
977                         text = _("stopped by user");
978                         }
979                 editor_verbose_window_progress(ed, text);
980                 editor_verbose_window_enable_close(ed->vd);
981                 }
982
983         /* free the not-handled items */
984         if (ed->list)
985                 {
986                 ed->flags |= EDITOR_ERROR_SKIPPED;
987                 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
988                 filelist_free(ed->list);
989                 ed->list = NULL;
990                 }
991
992         ed->count = 0;
993
994         flags = ed->flags & EDITOR_ERROR_MASK;
995
996         if (!ed->vd) editor_data_free(ed);
997
998         return flags;
999 }
1000
1001 void editor_resume(gpointer ed)
1002 {
1003         editor_command_next_start(ed);
1004 }
1005
1006 void editor_skip(gpointer ed)
1007 {
1008         editor_command_done(ed);
1009 }
1010
1011 static gint editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, EditorCallback cb, gpointer data)
1012 {
1013         EditorData *ed;
1014         gint flags = editor->flags;
1015
1016         if (flags & EDITOR_ERROR_MASK) return flags & EDITOR_ERROR_MASK;
1017
1018         ed = g_new0(EditorData, 1);
1019         ed->list = filelist_copy(list);
1020         ed->flags = flags;
1021         ed->editor = editor;
1022         ed->total = (flags & EDITOR_SINGLE_COMMAND) ? 1 : g_list_length(list);
1023         ed->count = 0;
1024         ed->stopping = FALSE;
1025         ed->callback = cb;
1026         ed->data =  data;
1027
1028         if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1029                 flags |= EDITOR_VERBOSE;
1030
1031         if (flags & EDITOR_VERBOSE)
1032                 editor_verbose_window(ed, text);
1033
1034         editor_command_next_start(ed);
1035         /* errors from editor_command_next_start will be handled via callback */
1036         return flags & EDITOR_ERROR_MASK;
1037 }
1038
1039 gboolean is_valid_editor_command(const gchar *key)
1040 {
1041         if (!key) return FALSE;
1042         return g_hash_table_lookup(editors, key) != NULL;
1043 }
1044
1045 gint start_editor_from_filelist_full(const gchar *key, GList *list, EditorCallback cb, gpointer data)
1046 {
1047         gint error;
1048         EditorDescription *editor;
1049         if (!key) return FALSE;
1050         
1051         editor = g_hash_table_lookup(editors, key);
1052
1053         if (!list) return FALSE;
1054         if (!editor) return FALSE;
1055
1056         error = editor_command_start(editor, editor->name, list, cb, data);
1057
1058         if (error & EDITOR_ERROR_MASK)
1059                 {
1060                 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1061                 
1062                 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1063                 g_free(text);
1064                 }
1065
1066         return error;
1067 }
1068
1069 gint start_editor_from_filelist(const gchar *key, GList *list)
1070 {
1071         return start_editor_from_filelist_full(key, list,  NULL, NULL);
1072 }
1073
1074 gint start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1075 {
1076         GList *list;
1077         gint error;
1078
1079         if (!fd) return FALSE;
1080
1081         list = g_list_append(NULL, fd);
1082         error = start_editor_from_filelist_full(key, list, cb, data);
1083         g_list_free(list);
1084         return error;
1085 }
1086
1087 gint start_editor_from_file(const gchar *key, FileData *fd)
1088 {
1089         return start_editor_from_file_full(key, fd, NULL, NULL);
1090 }
1091
1092 gint editor_window_flag_set(const gchar *key)
1093 {
1094         EditorDescription *editor;
1095         if (!key) return TRUE;
1096         
1097         editor = g_hash_table_lookup(editors, key);
1098         if (!editor) return TRUE;
1099
1100         return (editor->flags & EDITOR_KEEP_FS);
1101 }
1102
1103 gint editor_is_filter(const gchar *key)
1104 {
1105         EditorDescription *editor;
1106         if (!key) return TRUE;
1107         
1108         editor = g_hash_table_lookup(editors, key);
1109         if (!editor) return TRUE;
1110
1111         return (editor->flags & EDITOR_DEST);
1112 }
1113
1114 const gchar *editor_get_error_str(gint flags)
1115 {
1116         if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1117         if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1118         if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1119         if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1120         if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1121         if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1122         if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1123         return _("Unknown error.");
1124 }
1125
1126 const gchar *editor_get_name(const gchar *key)
1127 {
1128         EditorDescription *editor = g_hash_table_lookup(editors, key);
1129
1130         if (!editor) return NULL;
1131
1132         return editor->name;
1133 }
1134 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */