2 * Copyright (C) 2006 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include "filefilter.h"
28 #include "pixbuf_util.h"
29 #include "ui_fileops.h"
30 #include "ui_spinner.h"
31 #include "ui_utildlg.h"
37 #define EDITOR_WINDOW_WIDTH 500
38 #define EDITOR_WINDOW_HEIGHT 300
42 typedef struct _EditorVerboseData EditorVerboseData;
43 struct _EditorVerboseData {
45 GtkWidget *button_close;
46 GtkWidget *button_stop;
52 typedef struct _EditorData EditorData;
60 EditorVerboseData *vd;
61 EditorCallback callback;
63 const EditorDescription *editor;
64 gchar *working_directory; /* fallback if no files are given (editor_no_param) */
68 static void editor_verbose_window_progress(EditorData *ed, const gchar *text);
69 static EditorFlags editor_command_next_start(EditorData *ed);
70 static EditorFlags editor_command_next_finish(EditorData *ed, gint status);
71 static EditorFlags editor_command_done(EditorData *ed);
74 *-----------------------------------------------------------------------------
75 * external editor routines
76 *-----------------------------------------------------------------------------
79 GHashTable *editors = NULL;
80 GtkListStore *desktop_file_list;
81 gboolean editors_finished = FALSE;
83 #ifdef G_KEY_FILE_DESKTOP_GROUP
84 #define DESKTOP_GROUP G_KEY_FILE_DESKTOP_GROUP
86 #define DESKTOP_GROUP "Desktop Entry"
89 void editor_description_free(EditorDescription *editor)
97 g_free(editor->menu_path);
98 g_free(editor->hotkey);
99 g_free(editor->comment);
100 string_list_free(editor->ext_list);
101 g_free(editor->file);
105 static GList *editor_mime_types_to_extensions(gchar **mime_types)
107 /* FIXME: this should be rewritten to use the shared mime database, as soon as we switch to gio */
109 static const gchar *conv_table[][2] = {
110 {"application/x-ufraw", ".ufraw"},
112 {"image/bmp", ".bmp"},
113 {"image/gif", ".gif"},
114 {"image/jpeg", ".jpeg;.jpg;.mpo"},
115 {"image/jpg", ".jpg;.jpeg"},
116 {"image/pcx", ".pcx"},
117 {"image/png", ".png"},
118 {"image/svg", ".svg"},
119 {"image/svg+xml", ".svg"},
120 {"image/svg+xml-compressed", ".svg"},
121 {"image/tiff", ".tiff;.tif;.mef"},
122 {"image/x-adobe-dng", ".dng"},
123 {"image/x-bmp", ".bmp"},
124 {"image/x-canon-crw", ".crw"},
125 {"image/x-canon-cr2", ".cr2"},
126 {"image/x-cr2", ".cr2"},
127 {"image/x-dcraw", "%raw;.mos"},
128 {"image/x-epson-erf", "%erf"},
129 {"image/x-ico", ".ico"},
130 {"image/x-kodak-kdc", ".kdc"},
131 {"image/x-mrw", ".mrw"},
132 {"image/x-minolta-mrw", ".mrw"},
133 {"image/x-MS-bmp", ".bmp"},
134 {"image/x-nef", ".nef"},
135 {"image/x-nikon-nef", ".nef"},
136 {"image/x-panasonic-raw", ".raw"},
137 {"image/x-panasonic-rw2", ".rw2"},
138 {"image/x-pentax-pef", ".pef"},
139 {"image/x-orf", ".orf"},
140 {"image/x-olympus-orf", ".orf"},
141 {"image/x-pcx", ".pcx"},
142 {"image/xpm", ".xpm"},
143 {"image/x-png", ".png"},
144 {"image/x-portable-anymap", ".pam"},
145 {"image/x-portable-bitmap", ".pbm"},
146 {"image/x-portable-graymap", ".pgm"},
147 {"image/x-portable-pixmap", ".ppm"},
148 {"image/x-psd", ".psd"},
149 {"image/x-raf", ".raf"},
150 {"image/x-fuji-raf", ".raf"},
151 {"image/x-sgi", ".sgi"},
152 {"image/x-sony-arw", ".arw"},
153 {"image/x-sony-sr2", ".sr2"},
154 {"image/x-sony-srf", ".srf"},
155 {"image/x-tga", ".tga"},
156 {"image/x-xbitmap", ".xbm"},
157 {"image/x-xcf", ".xcf"},
158 {"image/x-xpixmap", ".xpm"},
159 {"image/x-x3f", ".x3f"},
160 {"application/x-navi-animation", ".ani"},
161 {"application/x-ptoptimizer-script", ".pto"},
167 for (i = 0; mime_types[i]; i++)
168 for (j = 0; conv_table[j][0]; j++)
169 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
170 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
175 gboolean editor_read_desktop_file(const gchar *path)
178 EditorDescription *editor;
181 const gchar *key = filename_from_path(path);
182 gchar **categories, **only_show_in, **not_show_in;
185 gboolean category_geeqie = FALSE;
187 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
189 key_file = g_key_file_new();
190 if (!g_key_file_load_from_file(key_file, path, 0, NULL))
192 g_key_file_free(key_file);
196 type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
197 if (!type || strcmp(type, "Application") != 0)
199 /* We only consider desktop entries of Application type */
200 g_key_file_free(key_file);
206 editor = g_new0(EditorDescription, 1);
208 editor->key = g_strdup(key);
209 editor->file = g_strdup(path);
211 g_hash_table_insert(editors, editor->key, editor);
213 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
214 || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
216 editor->hidden = TRUE;
219 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
222 gboolean found = FALSE;
224 for (i = 0; categories[i]; i++)
226 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
227 if (strcmp(categories[i], "Graphics") == 0)
231 if (strcmp(categories[i], "X-Geeqie") == 0)
234 category_geeqie = TRUE;
238 if (!found) editor->ignored = TRUE;
239 g_strfreev(categories);
243 editor->ignored = TRUE;
246 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
249 gboolean found = FALSE;
251 for (i = 0; only_show_in[i]; i++)
252 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
257 if (!found) editor->ignored = TRUE;
258 g_strfreev(only_show_in);
261 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
264 gboolean found = FALSE;
266 for (i = 0; not_show_in[i]; i++)
267 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
272 if (found) editor->ignored = TRUE;
273 g_strfreev(not_show_in);
277 try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
278 if (try_exec && !editor->hidden && !editor->ignored)
280 gchar *try_exec_res = g_find_program_in_path(try_exec);
281 if (!try_exec_res) editor->hidden = TRUE;
282 g_free(try_exec_res);
288 /* ignored editors will be deleted, no need to parse the rest */
289 g_key_file_free(key_file);
293 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
294 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
296 /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
297 if (editor->icon && !g_path_is_absolute(editor->icon))
299 gchar *ext = strrchr(editor->icon, '.');
301 if (ext && strlen(ext) == 4 &&
302 (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
304 log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
305 editor->file, editor->icon);
311 if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon))
313 g_free(editor->icon);
317 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
319 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
320 if (!editor->menu_path) editor->menu_path = g_strdup("PluginsMenu");
322 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
324 editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
326 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
328 editor->ext_list = filter_to_list(extensions);
331 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
334 editor->ext_list = editor_mime_types_to_extensions(mime_types);
335 g_strfreev(mime_types);
336 if (!editor->ext_list) editor->hidden = TRUE;
340 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
341 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
342 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
343 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
344 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
346 editor->flags |= editor_command_parse(editor, NULL, FALSE, NULL);
348 if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
350 g_key_file_free(key_file);
352 if (editor->ignored) return TRUE;
354 gtk_list_store_append(desktop_file_list, &iter);
355 gtk_list_store_set(desktop_file_list, &iter,
356 DESKTOP_FILE_COLUMN_KEY, key,
357 DESKTOP_FILE_COLUMN_NAME, editor->name,
358 DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden ? _("yes") : _("no"),
359 DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
360 DESKTOP_FILE_COLUMN_PATH, path, -1);
365 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
367 EditorDescription *editor = value;
368 return editor->hidden || editor->ignored;
371 void editor_table_finish(void)
373 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
374 editors_finished = TRUE;
377 void editor_table_clear(void)
379 if (desktop_file_list)
381 gtk_list_store_clear(desktop_file_list);
385 desktop_file_list = gtk_list_store_new(DESKTOP_FILE_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING);
389 g_hash_table_destroy(editors);
391 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
392 editors_finished = FALSE;
395 static GList *editor_add_desktop_dir(GList *list, const gchar *path)
401 pathl = path_from_utf8(path);
409 while ((dir = readdir(dp)) != NULL)
411 gchar *namel = dir->d_name;
413 if (g_str_has_suffix(namel, ".desktop"))
415 gchar *name = path_to_utf8(namel);
416 gchar *dpath = g_build_filename(path, name, NULL);
417 list = g_list_prepend(list, dpath);
425 GList *editor_get_desktop_files(void)
428 gchar *xdg_data_dirs;
434 xdg_data_dirs = getenv("XDG_DATA_DIRS");
435 if (xdg_data_dirs && xdg_data_dirs[0])
436 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
438 xdg_data_dirs = g_strdup("/usr/share");
440 all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
442 g_free(xdg_data_dirs);
444 split_dirs = g_strsplit(all_dirs, ":", 0);
448 for (i = 0; split_dirs[i]; i++);
449 for (--i; i >= 0; i--)
451 path = g_build_filename(split_dirs[i], "applications", NULL);
452 list = editor_add_desktop_dir(list, path);
456 g_strfreev(split_dirs);
460 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
462 GList **listp = data;
463 EditorDescription *editor = value;
465 /* do not show the special commands in any list, they are called explicitly */
466 if (strcmp(editor->key, CMD_COPY) == 0 ||
467 strcmp(editor->key, CMD_MOVE) == 0 ||
468 strcmp(editor->key, CMD_RENAME) == 0 ||
469 strcmp(editor->key, CMD_DELETE) == 0 ||
470 strcmp(editor->key, CMD_FOLDER) == 0) return;
472 *listp = g_list_prepend(*listp, editor);
475 static gint editor_sort(gconstpointer a, gconstpointer b)
477 const EditorDescription *ea = a;
478 const EditorDescription *eb = b;
481 ret = strcmp(ea->menu_path, eb->menu_path);
482 if (ret != 0) return ret;
484 return g_utf8_collate(ea->name, eb->name);
487 GList *editor_list_get(void)
489 GList *editors_list = NULL;
491 if (!editors_finished) return NULL;
493 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
494 editors_list = g_list_sort(editors_list, editor_sort);
499 /* ------------------------------ */
502 static void editor_verbose_data_free(EditorData *ed)
509 static void editor_data_free(EditorData *ed)
511 editor_verbose_data_free(ed);
512 g_free(ed->working_directory);
516 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
518 EditorData *ed = data;
520 generic_dialog_close(gd);
521 editor_verbose_data_free(ed);
522 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
525 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
527 EditorData *ed = data;
530 editor_verbose_window_progress(ed, _("stopping..."));
533 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
535 vd->gd->cancel_cb = editor_verbose_window_close;
537 spinner_set_interval(vd->spinner, -1);
538 gtk_widget_set_sensitive(vd->button_stop, FALSE);
539 gtk_widget_set_sensitive(vd->button_close, TRUE);
542 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
544 EditorVerboseData *vd;
549 vd = g_new0(EditorVerboseData, 1);
551 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
554 buf = g_strdup_printf(_("Output of %s"), text);
555 generic_dialog_add_message(vd->gd, NULL, buf, NULL, TRUE);
557 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
558 editor_verbose_window_stop, FALSE);
559 gtk_widget_set_sensitive(vd->button_stop, FALSE);
560 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
561 editor_verbose_window_close, TRUE);
562 gtk_widget_set_sensitive(vd->button_close, FALSE);
564 scrolled = gtk_scrolled_window_new(NULL, NULL);
565 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
566 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
567 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
568 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
569 gtk_widget_show(scrolled);
571 vd->text = gtk_text_view_new();
572 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
573 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
574 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
575 gtk_widget_show(vd->text);
577 hbox = gtk_hbox_new(FALSE, 0);
578 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
579 gtk_widget_show(hbox);
581 vd->progress = gtk_progress_bar_new();
582 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
583 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
584 #if GTK_CHECK_VERSION(3,0,0)
585 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), "");
586 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(vd->progress), TRUE);
588 gtk_widget_show(vd->progress);
590 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
591 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
592 gtk_widget_show(vd->spinner);
594 gtk_widget_show(vd->gd->dialog);
600 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
602 GtkTextBuffer *buffer;
605 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
606 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
607 gtk_text_buffer_insert(buffer, &iter, text, len);
610 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
616 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
619 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
622 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
624 EditorData *ed = data;
628 if (condition & G_IO_IN)
630 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
632 if (!g_utf8_validate(buf, count, NULL))
636 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
639 editor_verbose_window_fill(ed->vd, utf8, -1);
644 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
649 editor_verbose_window_fill(ed->vd, buf, count);
654 if (condition & (G_IO_ERR | G_IO_HUP))
656 g_io_channel_shutdown(source, TRUE, NULL);
670 static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
674 const gchar *p = NULL;
676 DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
678 string = g_string_new("");
680 if (type == PATH_FILE || type == PATH_FILE_URL)
682 GList *work = editor->ext_list;
691 gchar *ext = work->data;
694 if (strcmp(ext, "*") == 0 ||
695 g_ascii_strcasecmp(ext, fd->extension) == 0)
701 work2 = consider_sidecars ? fd->sidecar_files : NULL;
704 FileData *sfd = work2->data;
707 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
718 else if (type == PATH_DEST)
720 if (fd->change && fd->change->dest)
721 p = fd->change->dest;
727 string = g_string_append(string, p);
729 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
730 pathl = path_from_utf8(string->str);
731 g_string_free(string, TRUE);
733 if (pathl && !pathl[0]) /* empty string case */
739 DEBUG_2("editor_command_path_parse: return %s", pathl);
743 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
750 g_string_append_c(str, '\'');
752 g_string_append(str, "\"'");
755 for (p = s; *p != '\0'; p++)
758 g_string_append(str, "'\\''");
760 g_string_append_c(str, *p);
766 g_string_append_c(str, '\'');
768 g_string_append(str, "'\"");
775 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
777 EditorFlags flags = 0;
779 GString *result = NULL;
780 gboolean escape = FALSE;
781 gboolean single_quotes = FALSE;
782 gboolean double_quotes = FALSE;
784 DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
787 result = g_string_new("");
789 if (editor->exec == NULL || editor->exec[0] == '\0')
791 flags |= EDITOR_ERROR_EMPTY;
796 /* skip leading whitespaces if any */
797 while (g_ascii_isspace(*p)) p++;
806 if (output) result = g_string_append_c(result, *p);
810 if (!single_quotes) escape = TRUE;
811 if (output) result = g_string_append_c(result, *p);
815 if (output) result = g_string_append_c(result, *p);
816 if (!single_quotes && !double_quotes)
817 single_quotes = TRUE;
818 else if (single_quotes)
819 single_quotes = FALSE;
823 if (output) result = g_string_append_c(result, *p);
824 if (!single_quotes && !double_quotes)
825 double_quotes = TRUE;
826 else if (double_quotes)
827 double_quotes = FALSE;
829 else if (*p == '%' && p[1])
837 case 'f': /* single file */
838 case 'u': /* single url */
839 flags |= EDITOR_FOR_EACH;
840 if (flags & EDITOR_SINGLE_COMMAND)
842 flags |= EDITOR_ERROR_INCOMPATIBLE;
847 /* use the first file from the list */
850 flags |= EDITOR_ERROR_NO_FILE;
853 pathl = editor_command_path_parse((FileData *)list->data,
855 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
859 /* just testing, check also the rest of the list (like with F and U)
860 any matching file is OK */
861 GList *work = list->next;
863 while (!pathl && work)
865 FileData *fd = work->data;
866 pathl = editor_command_path_parse(fd,
868 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
876 flags |= EDITOR_ERROR_NO_FILE;
881 result = append_quoted(result, pathl, single_quotes, double_quotes);
889 flags |= EDITOR_SINGLE_COMMAND;
890 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
892 flags |= EDITOR_ERROR_INCOMPATIBLE;
904 FileData *fd = work->data;
905 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
913 if (work != list) g_string_append_c(result, ' ');
914 result = append_quoted(result, pathl, single_quotes, double_quotes);
922 flags |= EDITOR_ERROR_NO_FILE;
928 if (editor->icon && *editor->icon)
932 result = g_string_append(result, "--icon ");
933 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
940 result = append_quoted(result, editor->name, single_quotes, double_quotes);
946 result = append_quoted(result, editor->file, single_quotes, double_quotes);
950 /* %% = % escaping */
951 if (output) result = g_string_append_c(result, *p);
959 /* deprecated according to spec, ignore */
962 flags |= EDITOR_ERROR_SYNTAX;
968 if (output) result = g_string_append_c(result, *p);
973 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
977 *output = g_string_free(result, FALSE);
978 DEBUG_3("Editor cmd: %s", *output);
987 g_string_free(result, TRUE);
994 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
996 EditorData *ed = data;
997 g_spawn_close_pid(pid);
1000 editor_command_next_finish(ed, status);
1004 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
1007 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
1009 gint standard_output;
1010 gint standard_error;
1014 ed->flags = editor->flags;
1015 ed->flags |= editor_command_parse(editor, list, TRUE, &command);
1017 ok = !EDITOR_ERRORS(ed->flags);
1021 ok = (options->shell.path && *options->shell.path);
1022 if (!ok) log_printf("ERROR: empty shell command\n");
1026 ok = (access(options->shell.path, X_OK) == 0);
1027 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1030 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1035 gchar *working_directory;
1039 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1040 args[n++] = options->shell.path;
1041 if (options->shell.options && *options->shell.options)
1042 args[n++] = options->shell.options;
1043 args[n++] = command;
1046 if ((ed->flags & EDITOR_DEST) && fd && fd->change && fd->change->dest) /* FIXME: error handling */
1048 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1052 g_unsetenv("GEEQIE_DESTINATION");
1055 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1056 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1060 ed->vd ? &standard_output : NULL,
1061 ed->vd ? &standard_error : NULL,
1064 g_free(working_directory);
1066 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1071 g_child_watch_add(pid, editor_child_exit_cb, ed);
1081 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1082 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1088 GIOChannel *channel_output;
1089 GIOChannel *channel_error;
1091 channel_output = g_io_channel_unix_new(standard_output);
1092 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1093 g_io_channel_set_encoding(channel_output, NULL, NULL);
1095 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1096 editor_verbose_io_cb, ed, NULL);
1097 g_io_channel_unref(channel_output);
1099 channel_error = g_io_channel_unix_new(standard_error);
1100 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1101 g_io_channel_set_encoding(channel_error, NULL, NULL);
1103 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1104 editor_verbose_io_cb, ed, NULL);
1105 g_io_channel_unref(channel_error);
1111 return EDITOR_ERRORS(ed->flags);
1114 static EditorFlags editor_command_next_start(EditorData *ed)
1116 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1118 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1123 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1127 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1128 editor_verbose_window_progress(ed, fd->path);
1130 editor_verbose_window_progress(ed, _("running..."));
1134 error = editor_command_one(ed->editor, ed->list, ed);
1135 if (!error && ed->vd)
1137 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1138 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1140 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1141 editor_verbose_window_fill(ed->vd, "\n", 1);
1148 /* command was not started, call the finish immediately */
1149 return editor_command_next_finish(ed, 0);
1152 /* everything is done */
1153 return editor_command_done(ed);
1156 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1158 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1161 ed->flags |= EDITOR_ERROR_STATUS;
1163 if (ed->flags & EDITOR_FOR_EACH)
1165 /* handle the first element from the list */
1166 GList *fd_element = ed->list;
1168 ed->list = g_list_remove_link(ed->list, fd_element);
1171 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1172 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1174 filelist_free(fd_element);
1178 /* handle whole list */
1180 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1181 filelist_free(ed->list);
1187 case EDITOR_CB_SUSPEND:
1188 return EDITOR_ERRORS(ed->flags);
1189 case EDITOR_CB_SKIP:
1190 return editor_command_done(ed);
1193 return editor_command_next_start(ed);
1196 static EditorFlags editor_command_done(EditorData *ed)
1202 if (ed->count == ed->total)
1204 editor_verbose_window_progress(ed, _("done"));
1208 editor_verbose_window_progress(ed, _("stopped by user"));
1210 editor_verbose_window_enable_close(ed->vd);
1213 /* free the not-handled items */
1216 ed->flags |= EDITOR_ERROR_SKIPPED;
1217 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1218 filelist_free(ed->list);
1224 flags = EDITOR_ERRORS(ed->flags);
1226 if (!ed->vd) editor_data_free(ed);
1231 void editor_resume(gpointer ed)
1233 editor_command_next_start(ed);
1236 void editor_skip(gpointer ed)
1238 editor_command_done(ed);
1241 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1244 EditorFlags flags = editor->flags;
1246 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1248 ed = g_new0(EditorData, 1);
1249 ed->list = filelist_copy(list);
1251 ed->editor = editor;
1252 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1255 ed->working_directory = g_strdup(working_directory);
1257 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1258 flags |= EDITOR_VERBOSE;
1260 if (flags & EDITOR_VERBOSE)
1261 editor_verbose_window(ed, text);
1263 editor_command_next_start(ed);
1264 /* errors from editor_command_next_start will be handled via callback */
1265 return EDITOR_ERRORS(flags);
1268 gboolean is_valid_editor_command(const gchar *key)
1270 if (!key) return FALSE;
1271 return g_hash_table_lookup(editors, key) != NULL;
1274 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1277 EditorDescription *editor;
1278 if (!key) return EDITOR_ERROR_EMPTY;
1280 editor = g_hash_table_lookup(editors, key);
1282 if (!editor) return EDITOR_ERROR_EMPTY;
1283 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1285 error = editor_command_parse(editor, list, TRUE, NULL);
1287 if (EDITOR_ERRORS(error)) return error;
1289 error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1291 if (EDITOR_ERRORS(error))
1293 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1295 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1299 return EDITOR_ERRORS(error);
1302 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1304 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1307 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1312 if (!fd) return FALSE;
1314 list = g_list_append(NULL, fd);
1315 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1320 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1322 return start_editor_from_file_full(key, fd, NULL, NULL);
1325 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1327 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1330 gboolean editor_window_flag_set(const gchar *key)
1332 EditorDescription *editor;
1333 if (!key) return TRUE;
1335 editor = g_hash_table_lookup(editors, key);
1336 if (!editor) return TRUE;
1338 return !!(editor->flags & EDITOR_KEEP_FS);
1341 gboolean editor_is_filter(const gchar *key)
1343 EditorDescription *editor;
1344 if (!key) return TRUE;
1346 editor = g_hash_table_lookup(editors, key);
1347 if (!editor) return TRUE;
1349 return !!(editor->flags & EDITOR_DEST);
1352 gboolean editor_no_param(const gchar *key)
1354 EditorDescription *editor;
1355 if (!key) return FALSE;
1357 editor = g_hash_table_lookup(editors, key);
1358 if (!editor) return FALSE;
1360 return !!(editor->flags & EDITOR_NO_PARAM);
1363 gboolean editor_blocks_file(const gchar *key)
1365 EditorDescription *editor;
1366 if (!key) return FALSE;
1368 editor = g_hash_table_lookup(editors, key);
1369 if (!editor) return FALSE;
1371 /* Decide if the image file should be blocked during editor execution
1372 Editors like gimp can be used long time after the original file was
1373 saved, for editing unrelated files.
1374 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1377 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1380 const gchar *editor_get_error_str(EditorFlags flags)
1382 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1383 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1384 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1385 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1386 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1387 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1388 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1389 return _("Unknown error.");
1392 const gchar *editor_get_name(const gchar *key)
1394 EditorDescription *editor = g_hash_table_lookup(editors, key);
1396 if (!editor) return NULL;
1398 return editor->name;
1400 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */