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/vnd-ms.dds", ".dds"},
123 {"image/x-adobe-dng", ".dng"},
124 {"image/x-bmp", ".bmp"},
125 {"image/x-canon-crw", ".crw"},
126 {"image/x-canon-cr2", ".cr2"},
127 {"image/x-canon-cr3", ".cr3"},
128 {"image/x-cr2", ".cr2"},
129 {"image/x-dcraw", "%raw;.mos"},
130 {"image/x-epson-erf", "%erf"},
131 {"image/x-ico", ".ico"},
132 {"image/x-kodak-kdc", ".kdc"},
133 {"image/x-mrw", ".mrw"},
134 {"image/x-minolta-mrw", ".mrw"},
135 {"image/x-MS-bmp", ".bmp"},
136 {"image/x-nef", ".nef"},
137 {"image/x-nikon-nef", ".nef"},
138 {"image/x-panasonic-raw", ".raw"},
139 {"image/x-panasonic-rw2", ".rw2"},
140 {"image/x-pentax-pef", ".pef"},
141 {"image/x-orf", ".orf"},
142 {"image/x-olympus-orf", ".orf"},
143 {"image/x-pcx", ".pcx"},
144 {"image/xpm", ".xpm"},
145 {"image/x-png", ".png"},
146 {"image/x-portable-anymap", ".pam"},
147 {"image/x-portable-bitmap", ".pbm"},
148 {"image/x-portable-graymap", ".pgm"},
149 {"image/x-portable-pixmap", ".ppm"},
150 {"image/x-psd", ".psd"},
151 {"image/x-raf", ".raf"},
152 {"image/x-fuji-raf", ".raf"},
153 {"image/x-sgi", ".sgi"},
154 {"image/x-sony-arw", ".arw"},
155 {"image/x-sony-sr2", ".sr2"},
156 {"image/x-sony-srf", ".srf"},
157 {"image/x-tga", ".tga"},
158 {"image/x-xbitmap", ".xbm"},
159 {"image/x-xcf", ".xcf"},
160 {"image/x-xpixmap", ".xpm"},
161 {"image/x-x3f", ".x3f"},
162 {"application/x-navi-animation", ".ani"},
163 {"application/x-ptoptimizer-script", ".pto"},
169 for (i = 0; mime_types[i]; i++)
170 for (j = 0; conv_table[j][0]; j++)
171 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
172 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
177 gboolean editor_read_desktop_file(const gchar *path)
180 EditorDescription *editor;
183 const gchar *key = filename_from_path(path);
184 gchar **categories, **only_show_in, **not_show_in;
187 gboolean category_geeqie = FALSE;
191 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
193 key_file = g_key_file_new();
194 if (!g_key_file_load_from_file(key_file, path, 0, NULL))
196 g_key_file_free(key_file);
200 type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
201 if (!type || strcmp(type, "Application") != 0)
203 /* We only consider desktop entries of Application type */
204 g_key_file_free(key_file);
210 editor = g_new0(EditorDescription, 1);
212 editor->key = g_strdup(key);
213 editor->file = g_strdup(path);
215 g_hash_table_insert(editors, editor->key, editor);
217 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
218 || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
220 editor->hidden = TRUE;
223 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
226 gboolean found = FALSE;
228 for (i = 0; categories[i]; i++)
230 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
231 if (strcmp(categories[i], "Graphics") == 0)
235 if (strcmp(categories[i], "X-Geeqie") == 0)
238 category_geeqie = TRUE;
242 if (!found) editor->ignored = TRUE;
243 g_strfreev(categories);
247 editor->ignored = TRUE;
250 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
253 gboolean found = FALSE;
255 for (i = 0; only_show_in[i]; i++)
256 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
261 if (!found) editor->ignored = TRUE;
262 g_strfreev(only_show_in);
265 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
268 gboolean found = FALSE;
270 for (i = 0; not_show_in[i]; i++)
271 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
276 if (found) editor->ignored = TRUE;
277 g_strfreev(not_show_in);
281 try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
282 if (try_exec && !editor->hidden && !editor->ignored)
284 gchar *try_exec_res = g_find_program_in_path(try_exec);
285 if (!try_exec_res) editor->hidden = TRUE;
286 g_free(try_exec_res);
292 /* ignored editors will be deleted, no need to parse the rest */
293 g_key_file_free(key_file);
297 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
298 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
300 /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
301 if (editor->icon && !g_path_is_absolute(editor->icon))
303 gchar *ext = strrchr(editor->icon, '.');
305 if (ext && strlen(ext) == 4 &&
306 (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
308 log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
309 editor->file, editor->icon);
315 if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon))
317 g_free(editor->icon);
321 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
323 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
324 if (!editor->menu_path) editor->menu_path = g_strdup("PluginsMenu");
326 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
328 editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
330 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
332 editor->ext_list = filter_to_list(extensions);
335 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
338 editor->ext_list = editor_mime_types_to_extensions(mime_types);
339 g_strfreev(mime_types);
340 if (!editor->ext_list) editor->hidden = TRUE;
344 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
345 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
346 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
347 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
348 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
350 editor->flags |= editor_command_parse(editor, NULL, FALSE, NULL);
352 if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
354 g_key_file_free(key_file);
356 if (editor->ignored) return TRUE;
358 work = options->disabled_plugins;
363 if (g_strcmp0(path, work->data) == 0)
371 editor->disabled = disabled;
373 gtk_list_store_append(desktop_file_list, &iter);
374 gtk_list_store_set(desktop_file_list, &iter,
375 DESKTOP_FILE_COLUMN_KEY, key,
376 DESKTOP_FILE_COLUMN_DISABLED, editor->disabled,
377 DESKTOP_FILE_COLUMN_NAME, editor->name,
378 DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden ? _("yes") : _("no"),
379 DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
380 DESKTOP_FILE_COLUMN_PATH, path, -1);
385 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
387 EditorDescription *editor = value;
388 return editor->hidden || editor->ignored;
391 void editor_table_finish(void)
393 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
394 editors_finished = TRUE;
397 void editor_table_clear(void)
399 if (desktop_file_list)
401 gtk_list_store_clear(desktop_file_list);
405 desktop_file_list = gtk_list_store_new(DESKTOP_FILE_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING);
409 g_hash_table_destroy(editors);
411 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
412 editors_finished = FALSE;
415 static GList *editor_add_desktop_dir(GList *list, const gchar *path)
421 pathl = path_from_utf8(path);
429 while ((dir = readdir(dp)) != NULL)
431 gchar *namel = dir->d_name;
433 if (g_str_has_suffix(namel, ".desktop"))
435 gchar *name = path_to_utf8(namel);
436 gchar *dpath = g_build_filename(path, name, NULL);
437 list = g_list_prepend(list, dpath);
445 GList *editor_get_desktop_files(void)
448 gchar *xdg_data_dirs;
454 xdg_data_dirs = getenv("XDG_DATA_DIRS");
455 if (xdg_data_dirs && xdg_data_dirs[0])
456 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
458 xdg_data_dirs = g_strdup("/usr/share");
460 all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
462 g_free(xdg_data_dirs);
464 split_dirs = g_strsplit(all_dirs, ":", 0);
468 for (i = 0; split_dirs[i]; i++);
469 for (--i; i >= 0; i--)
471 path = g_build_filename(split_dirs[i], "applications", NULL);
472 list = editor_add_desktop_dir(list, path);
476 g_strfreev(split_dirs);
480 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
482 GList **listp = data;
483 EditorDescription *editor = value;
485 /* do not show the special commands in any list, they are called explicitly */
486 if (strcmp(editor->key, CMD_COPY) == 0 ||
487 strcmp(editor->key, CMD_MOVE) == 0 ||
488 strcmp(editor->key, CMD_RENAME) == 0 ||
489 strcmp(editor->key, CMD_DELETE) == 0 ||
490 strcmp(editor->key, CMD_FOLDER) == 0) return;
492 if (editor->disabled)
497 *listp = g_list_prepend(*listp, editor);
500 static gint editor_sort(gconstpointer a, gconstpointer b)
502 const EditorDescription *ea = a;
503 const EditorDescription *eb = b;
506 ret = strcmp(ea->menu_path, eb->menu_path);
507 if (ret != 0) return ret;
509 return g_utf8_collate(ea->name, eb->name);
512 GList *editor_list_get(void)
514 GList *editors_list = NULL;
516 if (!editors_finished) return NULL;
518 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
519 editors_list = g_list_sort(editors_list, editor_sort);
524 /* ------------------------------ */
527 static void editor_verbose_data_free(EditorData *ed)
534 static void editor_data_free(EditorData *ed)
536 editor_verbose_data_free(ed);
537 g_free(ed->working_directory);
541 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
543 EditorData *ed = data;
545 generic_dialog_close(gd);
546 editor_verbose_data_free(ed);
547 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
550 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
552 EditorData *ed = data;
555 editor_verbose_window_progress(ed, _("stopping..."));
558 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
560 vd->gd->cancel_cb = editor_verbose_window_close;
562 spinner_set_interval(vd->spinner, -1);
563 gtk_widget_set_sensitive(vd->button_stop, FALSE);
564 gtk_widget_set_sensitive(vd->button_close, TRUE);
567 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
569 EditorVerboseData *vd;
574 vd = g_new0(EditorVerboseData, 1);
576 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
579 buf = g_strdup_printf(_("Output of %s"), text);
580 generic_dialog_add_message(vd->gd, NULL, buf, NULL, FALSE);
582 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
583 editor_verbose_window_stop, FALSE);
584 gtk_widget_set_sensitive(vd->button_stop, FALSE);
585 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
586 editor_verbose_window_close, TRUE);
587 gtk_widget_set_sensitive(vd->button_close, FALSE);
589 scrolled = gtk_scrolled_window_new(NULL, NULL);
590 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
591 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
592 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
593 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
594 gtk_widget_show(scrolled);
596 vd->text = gtk_text_view_new();
597 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
598 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
599 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
600 gtk_widget_show(vd->text);
602 hbox = gtk_hbox_new(FALSE, 0);
603 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
604 gtk_widget_show(hbox);
606 vd->progress = gtk_progress_bar_new();
607 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
608 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
609 #if GTK_CHECK_VERSION(3,0,0)
610 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), "");
611 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(vd->progress), TRUE);
613 gtk_widget_show(vd->progress);
615 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
616 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
617 gtk_widget_show(vd->spinner);
619 gtk_widget_show(vd->gd->dialog);
625 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
627 GtkTextBuffer *buffer;
630 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
631 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
632 gtk_text_buffer_insert(buffer, &iter, text, len);
635 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
641 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
644 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
647 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
649 EditorData *ed = data;
653 if (condition & G_IO_IN)
655 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
657 if (!g_utf8_validate(buf, count, NULL))
661 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
664 editor_verbose_window_fill(ed->vd, utf8, -1);
669 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
674 editor_verbose_window_fill(ed->vd, buf, count);
679 if (condition & (G_IO_ERR | G_IO_HUP))
681 g_io_channel_shutdown(source, TRUE, NULL);
695 static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
699 const gchar *p = NULL;
701 DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
703 string = g_string_new("");
705 if (type == PATH_FILE || type == PATH_FILE_URL)
707 GList *work = editor->ext_list;
716 gchar *ext = work->data;
719 if (strcmp(ext, "*") == 0 ||
720 g_ascii_strcasecmp(ext, fd->extension) == 0)
726 work2 = consider_sidecars ? fd->sidecar_files : NULL;
729 FileData *sfd = work2->data;
732 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
743 else if (type == PATH_DEST)
745 if (fd->change && fd->change->dest)
746 p = fd->change->dest;
752 string = g_string_append(string, p);
754 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
755 pathl = path_from_utf8(string->str);
756 g_string_free(string, TRUE);
758 if (pathl && !pathl[0]) /* empty string case */
764 DEBUG_2("editor_command_path_parse: return %s", pathl);
768 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
775 g_string_append_c(str, '\'');
777 g_string_append(str, "\"'");
780 for (p = s; *p != '\0'; p++)
783 g_string_append(str, "'\\''");
785 g_string_append_c(str, *p);
791 g_string_append_c(str, '\'');
793 g_string_append(str, "'\"");
800 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
802 EditorFlags flags = 0;
804 GString *result = NULL;
805 gboolean escape = FALSE;
806 gboolean single_quotes = FALSE;
807 gboolean double_quotes = FALSE;
809 DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
812 result = g_string_new("");
814 if (editor->exec == NULL || editor->exec[0] == '\0')
816 flags |= EDITOR_ERROR_EMPTY;
821 /* skip leading whitespaces if any */
822 while (g_ascii_isspace(*p)) p++;
831 if (output) result = g_string_append_c(result, *p);
835 if (!single_quotes) escape = TRUE;
836 if (output) result = g_string_append_c(result, *p);
840 if (output) result = g_string_append_c(result, *p);
841 if (!single_quotes && !double_quotes)
842 single_quotes = TRUE;
843 else if (single_quotes)
844 single_quotes = FALSE;
848 if (output) result = g_string_append_c(result, *p);
849 if (!single_quotes && !double_quotes)
850 double_quotes = TRUE;
851 else if (double_quotes)
852 double_quotes = FALSE;
854 else if (*p == '%' && p[1])
862 case 'f': /* single file */
863 case 'u': /* single url */
864 flags |= EDITOR_FOR_EACH;
865 if (flags & EDITOR_SINGLE_COMMAND)
867 flags |= EDITOR_ERROR_INCOMPATIBLE;
872 /* use the first file from the list */
875 flags |= EDITOR_ERROR_NO_FILE;
878 pathl = editor_command_path_parse((FileData *)list->data,
880 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
884 /* just testing, check also the rest of the list (like with F and U)
885 any matching file is OK */
886 GList *work = list->next;
888 while (!pathl && work)
890 FileData *fd = work->data;
891 pathl = editor_command_path_parse(fd,
893 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
901 flags |= EDITOR_ERROR_NO_FILE;
906 result = append_quoted(result, pathl, single_quotes, double_quotes);
914 flags |= EDITOR_SINGLE_COMMAND;
915 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
917 flags |= EDITOR_ERROR_INCOMPATIBLE;
929 FileData *fd = work->data;
930 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
938 if (work != list) g_string_append_c(result, ' ');
939 result = append_quoted(result, pathl, single_quotes, double_quotes);
947 flags |= EDITOR_ERROR_NO_FILE;
953 if (editor->icon && *editor->icon)
957 result = g_string_append(result, "--icon ");
958 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
965 result = append_quoted(result, editor->name, single_quotes, double_quotes);
971 result = append_quoted(result, editor->file, single_quotes, double_quotes);
975 /* %% = % escaping */
976 if (output) result = g_string_append_c(result, *p);
984 /* deprecated according to spec, ignore */
987 flags |= EDITOR_ERROR_SYNTAX;
993 if (output) result = g_string_append_c(result, *p);
998 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
1002 *output = g_string_free(result, FALSE);
1003 DEBUG_3("Editor cmd: %s", *output);
1012 g_string_free(result, TRUE);
1019 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
1021 EditorData *ed = data;
1022 g_spawn_close_pid(pid);
1025 editor_command_next_finish(ed, status);
1029 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
1032 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
1034 gint standard_output;
1035 gint standard_error;
1039 ed->flags = editor->flags;
1040 ed->flags |= editor_command_parse(editor, list, TRUE, &command);
1042 ok = !EDITOR_ERRORS(ed->flags);
1046 ok = (options->shell.path && *options->shell.path);
1047 if (!ok) log_printf("ERROR: empty shell command\n");
1051 ok = (access(options->shell.path, X_OK) == 0);
1052 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1055 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1060 gchar *working_directory;
1064 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1065 args[n++] = options->shell.path;
1066 if (options->shell.options && *options->shell.options)
1067 args[n++] = options->shell.options;
1068 args[n++] = command;
1071 if ((ed->flags & EDITOR_DEST) && fd && fd->change && fd->change->dest) /* FIXME: error handling */
1073 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1077 g_unsetenv("GEEQIE_DESTINATION");
1080 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1081 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1085 ed->vd ? &standard_output : NULL,
1086 ed->vd ? &standard_error : NULL,
1089 g_free(working_directory);
1091 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1096 g_child_watch_add(pid, editor_child_exit_cb, ed);
1106 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1107 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1113 GIOChannel *channel_output;
1114 GIOChannel *channel_error;
1116 channel_output = g_io_channel_unix_new(standard_output);
1117 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1118 g_io_channel_set_encoding(channel_output, NULL, NULL);
1120 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1121 editor_verbose_io_cb, ed, NULL);
1122 g_io_channel_unref(channel_output);
1124 channel_error = g_io_channel_unix_new(standard_error);
1125 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1126 g_io_channel_set_encoding(channel_error, NULL, NULL);
1128 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1129 editor_verbose_io_cb, ed, NULL);
1130 g_io_channel_unref(channel_error);
1136 return EDITOR_ERRORS(ed->flags);
1139 static EditorFlags editor_command_next_start(EditorData *ed)
1141 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1143 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1148 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1152 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1153 editor_verbose_window_progress(ed, fd->path);
1155 editor_verbose_window_progress(ed, _("running..."));
1159 error = editor_command_one(ed->editor, ed->list, ed);
1160 if (!error && ed->vd)
1162 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1163 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1165 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1166 editor_verbose_window_fill(ed->vd, "\n", 1);
1173 /* command was not started, call the finish immediately */
1174 return editor_command_next_finish(ed, 0);
1177 /* everything is done */
1178 return editor_command_done(ed);
1181 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1183 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1186 ed->flags |= EDITOR_ERROR_STATUS;
1188 if (ed->flags & EDITOR_FOR_EACH)
1190 /* handle the first element from the list */
1191 GList *fd_element = ed->list;
1193 ed->list = g_list_remove_link(ed->list, fd_element);
1196 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1197 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1199 filelist_free(fd_element);
1203 /* handle whole list */
1205 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1206 filelist_free(ed->list);
1212 case EDITOR_CB_SUSPEND:
1213 return EDITOR_ERRORS(ed->flags);
1214 case EDITOR_CB_SKIP:
1215 return editor_command_done(ed);
1218 return editor_command_next_start(ed);
1221 static EditorFlags editor_command_done(EditorData *ed)
1227 if (ed->count == ed->total)
1229 editor_verbose_window_progress(ed, _("done"));
1233 editor_verbose_window_progress(ed, _("stopped by user"));
1235 editor_verbose_window_enable_close(ed->vd);
1238 /* free the not-handled items */
1241 ed->flags |= EDITOR_ERROR_SKIPPED;
1242 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1243 filelist_free(ed->list);
1249 flags = EDITOR_ERRORS(ed->flags);
1251 if (!ed->vd) editor_data_free(ed);
1256 void editor_resume(gpointer ed)
1258 editor_command_next_start(ed);
1261 void editor_skip(gpointer ed)
1263 editor_command_done(ed);
1266 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1269 EditorFlags flags = editor->flags;
1271 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1273 ed = g_new0(EditorData, 1);
1274 ed->list = filelist_copy(list);
1276 ed->editor = editor;
1277 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1280 ed->working_directory = g_strdup(working_directory);
1282 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1283 flags |= EDITOR_VERBOSE;
1285 if (flags & EDITOR_VERBOSE)
1286 editor_verbose_window(ed, text);
1288 editor_command_next_start(ed);
1289 /* errors from editor_command_next_start will be handled via callback */
1290 return EDITOR_ERRORS(flags);
1293 gboolean is_valid_editor_command(const gchar *key)
1295 if (!key) return FALSE;
1296 return g_hash_table_lookup(editors, key) != NULL;
1299 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1302 EditorDescription *editor;
1303 if (!key) return EDITOR_ERROR_EMPTY;
1305 editor = g_hash_table_lookup(editors, key);
1307 if (!editor) return EDITOR_ERROR_EMPTY;
1308 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1310 error = editor_command_parse(editor, list, TRUE, NULL);
1312 if (EDITOR_ERRORS(error)) return error;
1314 error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1316 if (EDITOR_ERRORS(error))
1318 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1320 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1324 return EDITOR_ERRORS(error);
1327 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1329 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1332 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1337 if (!fd) return FALSE;
1339 list = g_list_append(NULL, fd);
1340 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1345 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1347 return start_editor_from_file_full(key, fd, NULL, NULL);
1350 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1352 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1355 gboolean editor_window_flag_set(const gchar *key)
1357 EditorDescription *editor;
1358 if (!key) return TRUE;
1360 editor = g_hash_table_lookup(editors, key);
1361 if (!editor) return TRUE;
1363 return !!(editor->flags & EDITOR_KEEP_FS);
1366 gboolean editor_is_filter(const gchar *key)
1368 EditorDescription *editor;
1369 if (!key) return TRUE;
1371 editor = g_hash_table_lookup(editors, key);
1372 if (!editor) return TRUE;
1374 return !!(editor->flags & EDITOR_DEST);
1377 gboolean editor_no_param(const gchar *key)
1379 EditorDescription *editor;
1380 if (!key) return FALSE;
1382 editor = g_hash_table_lookup(editors, key);
1383 if (!editor) return FALSE;
1385 return !!(editor->flags & EDITOR_NO_PARAM);
1388 gboolean editor_blocks_file(const gchar *key)
1390 EditorDescription *editor;
1391 if (!key) return FALSE;
1393 editor = g_hash_table_lookup(editors, key);
1394 if (!editor) return FALSE;
1396 /* Decide if the image file should be blocked during editor execution
1397 Editors like gimp can be used long time after the original file was
1398 saved, for editing unrelated files.
1399 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1402 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1405 const gchar *editor_get_error_str(EditorFlags flags)
1407 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1408 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1409 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1410 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1411 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1412 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1413 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1414 return _("Unknown error.");
1417 const gchar *editor_get_name(const gchar *key)
1419 EditorDescription *editor = g_hash_table_lookup(editors, key);
1421 if (!editor) return NULL;
1423 return editor->name;
1425 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */