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"},
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"},
122 {"image/x-bmp", ".bmp"},
123 {"image/x-canon-crw", ".crw"},
124 {"image/x-canon-cr2", ".cr2"},
125 {"image/x-cr2", ".cr2"},
126 {"image/x-dcraw", "%raw"},
127 {"image/x-epson-erf", "%erf"},
128 {"image/x-ico", ".ico"},
129 {"image/x-mrw", ".mrw"},
130 {"image/x-minolta-mrw", ".mrw"},
131 {"image/x-MS-bmp", ".bmp"},
132 {"image/x-nef", ".nef"},
133 {"image/x-nikon-nef", ".nef"},
134 {"image/x-orf", ".orf"},
135 {"image/x-olympus-orf", ".orf"},
136 {"image/x-pcx", ".pcx"},
137 {"image/xpm", ".xpm"},
138 {"image/x-png", ".png"},
139 {"image/x-portable-anymap", ".pam"},
140 {"image/x-portable-bitmap", ".pbm"},
141 {"image/x-portable-graymap", ".pgm"},
142 {"image/x-portable-pixmap", ".ppm"},
143 {"image/x-psd", ".psd"},
144 {"image/x-raf", ".raf"},
145 {"image/x-fuji-raf", ".raf"},
146 {"image/x-sgi", ".sgi"},
147 {"image/x-tga", ".tga"},
148 {"image/x-xbitmap", ".xbm"},
149 {"image/x-xcf", ".xcf"},
150 {"image/x-xpixmap", ".xpm"},
151 {"image/x-x3f", ".x3f"},
152 {"application/x-ptoptimizer-script", ".pto"},
158 for (i = 0; mime_types[i]; i++)
159 for (j = 0; conv_table[j][0]; j++)
160 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
161 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
166 gboolean editor_read_desktop_file(const gchar *path)
169 EditorDescription *editor;
172 const gchar *key = filename_from_path(path);
173 gchar **categories, **only_show_in, **not_show_in;
176 gboolean category_geeqie = FALSE;
178 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
180 key_file = g_key_file_new();
181 if (!g_key_file_load_from_file(key_file, path, 0, NULL))
183 g_key_file_free(key_file);
187 type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
188 if (!type || strcmp(type, "Application") != 0)
190 /* We only consider desktop entries of Application type */
191 g_key_file_free(key_file);
197 editor = g_new0(EditorDescription, 1);
199 editor->key = g_strdup(key);
200 editor->file = g_strdup(path);
202 g_hash_table_insert(editors, editor->key, editor);
204 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
205 || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
207 editor->hidden = TRUE;
210 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
213 gboolean found = FALSE;
215 for (i = 0; categories[i]; i++)
217 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
218 if (strcmp(categories[i], "Graphics") == 0)
222 if (strcmp(categories[i], "X-Geeqie") == 0)
225 category_geeqie = TRUE;
229 if (!found) editor->ignored = TRUE;
230 g_strfreev(categories);
234 editor->ignored = TRUE;
237 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
240 gboolean found = FALSE;
242 for (i = 0; only_show_in[i]; i++)
243 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
248 if (!found) editor->ignored = TRUE;
249 g_strfreev(only_show_in);
252 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
255 gboolean found = FALSE;
257 for (i = 0; not_show_in[i]; i++)
258 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
263 if (found) editor->ignored = TRUE;
264 g_strfreev(not_show_in);
268 try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
269 if (try_exec && !editor->hidden && !editor->ignored)
271 gchar *try_exec_res = g_find_program_in_path(try_exec);
272 if (!try_exec_res) editor->hidden = TRUE;
273 g_free(try_exec_res);
279 /* ignored editors will be deleted, no need to parse the rest */
280 g_key_file_free(key_file);
284 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
285 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
287 /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
288 if (editor->icon && !g_path_is_absolute(editor->icon))
290 gchar *ext = strrchr(editor->icon, '.');
292 if (ext && strlen(ext) == 4 &&
293 (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
295 log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
296 editor->file, editor->icon);
302 if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon))
304 g_free(editor->icon);
308 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
310 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
311 if (!editor->menu_path) editor->menu_path = g_strdup("PluginsMenu");
313 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
315 editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
317 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
319 editor->ext_list = filter_to_list(extensions);
322 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
325 editor->ext_list = editor_mime_types_to_extensions(mime_types);
326 g_strfreev(mime_types);
327 if (!editor->ext_list) editor->hidden = TRUE;
331 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
332 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
333 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
334 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
335 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
337 editor->flags |= editor_command_parse(editor, NULL, FALSE, NULL);
339 if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
341 g_key_file_free(key_file);
343 if (editor->ignored) return TRUE;
345 gtk_list_store_append(desktop_file_list, &iter);
346 gtk_list_store_set(desktop_file_list, &iter,
347 DESKTOP_FILE_COLUMN_KEY, key,
348 DESKTOP_FILE_COLUMN_NAME, editor->name,
349 DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden ? _("yes") : _("no"),
350 DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
351 DESKTOP_FILE_COLUMN_PATH, path, -1);
356 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
358 EditorDescription *editor = value;
359 return editor->hidden || editor->ignored;
362 void editor_table_finish(void)
364 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
365 editors_finished = TRUE;
368 void editor_table_clear(void)
370 if (desktop_file_list)
372 gtk_list_store_clear(desktop_file_list);
376 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);
380 g_hash_table_destroy(editors);
382 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
383 editors_finished = FALSE;
386 static GList *editor_add_desktop_dir(GList *list, const gchar *path)
392 pathl = path_from_utf8(path);
400 while ((dir = readdir(dp)) != NULL)
402 gchar *namel = dir->d_name;
404 if (g_str_has_suffix(namel, ".desktop"))
406 gchar *name = path_to_utf8(namel);
407 gchar *dpath = g_build_filename(path, name, NULL);
408 list = g_list_prepend(list, dpath);
416 GList *editor_get_desktop_files(void)
419 gchar *xdg_data_dirs;
425 xdg_data_dirs = getenv("XDG_DATA_DIRS");
426 if (xdg_data_dirs && xdg_data_dirs[0])
427 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
429 xdg_data_dirs = g_strdup("/usr/share");
431 all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
433 g_free(xdg_data_dirs);
435 split_dirs = g_strsplit(all_dirs, ":", 0);
439 for (i = 0; split_dirs[i]; i++);
440 for (--i; i >= 0; i--)
442 path = g_build_filename(split_dirs[i], "applications", NULL);
443 list = editor_add_desktop_dir(list, path);
447 g_strfreev(split_dirs);
451 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
453 GList **listp = data;
454 EditorDescription *editor = value;
456 /* do not show the special commands in any list, they are called explicitly */
457 if (strcmp(editor->key, CMD_COPY) == 0 ||
458 strcmp(editor->key, CMD_MOVE) == 0 ||
459 strcmp(editor->key, CMD_RENAME) == 0 ||
460 strcmp(editor->key, CMD_DELETE) == 0 ||
461 strcmp(editor->key, CMD_FOLDER) == 0) return;
463 *listp = g_list_prepend(*listp, editor);
466 static gint editor_sort(gconstpointer a, gconstpointer b)
468 const EditorDescription *ea = a;
469 const EditorDescription *eb = b;
472 ret = strcmp(ea->menu_path, eb->menu_path);
473 if (ret != 0) return ret;
475 return g_utf8_collate(ea->name, eb->name);
478 GList *editor_list_get(void)
480 GList *editors_list = NULL;
482 if (!editors_finished) return NULL;
484 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
485 editors_list = g_list_sort(editors_list, editor_sort);
490 /* ------------------------------ */
493 static void editor_verbose_data_free(EditorData *ed)
500 static void editor_data_free(EditorData *ed)
502 editor_verbose_data_free(ed);
503 g_free(ed->working_directory);
507 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
509 EditorData *ed = data;
511 generic_dialog_close(gd);
512 editor_verbose_data_free(ed);
513 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
516 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
518 EditorData *ed = data;
521 editor_verbose_window_progress(ed, _("stopping..."));
524 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
526 vd->gd->cancel_cb = editor_verbose_window_close;
528 spinner_set_interval(vd->spinner, -1);
529 gtk_widget_set_sensitive(vd->button_stop, FALSE);
530 gtk_widget_set_sensitive(vd->button_close, TRUE);
533 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
535 EditorVerboseData *vd;
540 vd = g_new0(EditorVerboseData, 1);
542 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
545 buf = g_strdup_printf(_("Output of %s"), text);
546 generic_dialog_add_message(vd->gd, NULL, buf, NULL, TRUE);
548 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
549 editor_verbose_window_stop, FALSE);
550 gtk_widget_set_sensitive(vd->button_stop, FALSE);
551 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
552 editor_verbose_window_close, TRUE);
553 gtk_widget_set_sensitive(vd->button_close, FALSE);
555 scrolled = gtk_scrolled_window_new(NULL, NULL);
556 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
557 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
558 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
559 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
560 gtk_widget_show(scrolled);
562 vd->text = gtk_text_view_new();
563 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
564 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
565 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
566 gtk_widget_show(vd->text);
568 hbox = gtk_hbox_new(FALSE, 0);
569 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
570 gtk_widget_show(hbox);
572 vd->progress = gtk_progress_bar_new();
573 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
574 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
575 #if GTK_CHECK_VERSION(3,0,0)
576 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), "");
577 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(vd->progress), TRUE);
579 gtk_widget_show(vd->progress);
581 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
582 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
583 gtk_widget_show(vd->spinner);
585 gtk_widget_show(vd->gd->dialog);
591 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
593 GtkTextBuffer *buffer;
596 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
597 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
598 gtk_text_buffer_insert(buffer, &iter, text, len);
601 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
607 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
610 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
613 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
615 EditorData *ed = data;
619 if (condition & G_IO_IN)
621 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
623 if (!g_utf8_validate(buf, count, NULL))
627 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
630 editor_verbose_window_fill(ed->vd, utf8, -1);
635 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
640 editor_verbose_window_fill(ed->vd, buf, count);
645 if (condition & (G_IO_ERR | G_IO_HUP))
647 g_io_channel_shutdown(source, TRUE, NULL);
661 static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
665 const gchar *p = NULL;
667 DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
669 string = g_string_new("");
671 if (type == PATH_FILE || type == PATH_FILE_URL)
673 GList *work = editor->ext_list;
682 gchar *ext = work->data;
685 if (strcmp(ext, "*") == 0 ||
686 g_ascii_strcasecmp(ext, fd->extension) == 0)
692 work2 = consider_sidecars ? fd->sidecar_files : NULL;
695 FileData *sfd = work2->data;
698 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
709 else if (type == PATH_DEST)
711 if (fd->change && fd->change->dest)
712 p = fd->change->dest;
718 string = g_string_append(string, p);
720 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
721 pathl = path_from_utf8(string->str);
722 g_string_free(string, TRUE);
724 if (pathl && !pathl[0]) /* empty string case */
730 DEBUG_2("editor_command_path_parse: return %s", pathl);
734 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
741 g_string_append_c(str, '\'');
743 g_string_append(str, "\"'");
746 for (p = s; *p != '\0'; p++)
749 g_string_append(str, "'\\''");
751 g_string_append_c(str, *p);
757 g_string_append_c(str, '\'');
759 g_string_append(str, "'\"");
766 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
768 EditorFlags flags = 0;
770 GString *result = NULL;
771 gboolean escape = FALSE;
772 gboolean single_quotes = FALSE;
773 gboolean double_quotes = FALSE;
775 DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
778 result = g_string_new("");
780 if (editor->exec == NULL || editor->exec[0] == '\0')
782 flags |= EDITOR_ERROR_EMPTY;
787 /* skip leading whitespaces if any */
788 while (g_ascii_isspace(*p)) p++;
797 if (output) result = g_string_append_c(result, *p);
801 if (!single_quotes) escape = TRUE;
802 if (output) result = g_string_append_c(result, *p);
806 if (output) result = g_string_append_c(result, *p);
807 if (!single_quotes && !double_quotes)
808 single_quotes = TRUE;
809 else if (single_quotes)
810 single_quotes = FALSE;
814 if (output) result = g_string_append_c(result, *p);
815 if (!single_quotes && !double_quotes)
816 double_quotes = TRUE;
817 else if (double_quotes)
818 double_quotes = FALSE;
820 else if (*p == '%' && p[1])
828 case 'f': /* single file */
829 case 'u': /* single url */
830 flags |= EDITOR_FOR_EACH;
831 if (flags & EDITOR_SINGLE_COMMAND)
833 flags |= EDITOR_ERROR_INCOMPATIBLE;
838 /* use the first file from the list */
841 flags |= EDITOR_ERROR_NO_FILE;
844 pathl = editor_command_path_parse((FileData *)list->data,
846 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
850 /* just testing, check also the rest of the list (like with F and U)
851 any matching file is OK */
852 GList *work = list->next;
854 while (!pathl && work)
856 FileData *fd = work->data;
857 pathl = editor_command_path_parse(fd,
859 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
867 flags |= EDITOR_ERROR_NO_FILE;
872 result = append_quoted(result, pathl, single_quotes, double_quotes);
880 flags |= EDITOR_SINGLE_COMMAND;
881 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
883 flags |= EDITOR_ERROR_INCOMPATIBLE;
895 FileData *fd = work->data;
896 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
904 if (work != list) g_string_append_c(result, ' ');
905 result = append_quoted(result, pathl, single_quotes, double_quotes);
913 flags |= EDITOR_ERROR_NO_FILE;
919 if (editor->icon && *editor->icon)
923 result = g_string_append(result, "--icon ");
924 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
931 result = append_quoted(result, editor->name, single_quotes, double_quotes);
937 result = append_quoted(result, editor->file, single_quotes, double_quotes);
941 /* %% = % escaping */
942 if (output) result = g_string_append_c(result, *p);
950 /* deprecated according to spec, ignore */
953 flags |= EDITOR_ERROR_SYNTAX;
959 if (output) result = g_string_append_c(result, *p);
964 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
968 *output = g_string_free(result, FALSE);
969 DEBUG_3("Editor cmd: %s", *output);
978 g_string_free(result, TRUE);
985 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
987 EditorData *ed = data;
988 g_spawn_close_pid(pid);
991 editor_command_next_finish(ed, status);
995 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
998 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
1000 gint standard_output;
1001 gint standard_error;
1005 ed->flags = editor->flags;
1006 ed->flags |= editor_command_parse(editor, list, TRUE, &command);
1008 ok = !EDITOR_ERRORS(ed->flags);
1012 ok = (options->shell.path && *options->shell.path);
1013 if (!ok) log_printf("ERROR: empty shell command\n");
1017 ok = (access(options->shell.path, X_OK) == 0);
1018 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1021 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1026 gchar *working_directory;
1030 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1031 args[n++] = options->shell.path;
1032 if (options->shell.options && *options->shell.options)
1033 args[n++] = options->shell.options;
1034 args[n++] = command;
1037 if ((ed->flags & EDITOR_DEST) && fd && fd->change && fd->change->dest) /* FIXME: error handling */
1039 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1043 g_unsetenv("GEEQIE_DESTINATION");
1046 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1047 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1051 ed->vd ? &standard_output : NULL,
1052 ed->vd ? &standard_error : NULL,
1055 g_free(working_directory);
1057 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1062 g_child_watch_add(pid, editor_child_exit_cb, ed);
1072 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1073 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1079 GIOChannel *channel_output;
1080 GIOChannel *channel_error;
1082 channel_output = g_io_channel_unix_new(standard_output);
1083 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1084 g_io_channel_set_encoding(channel_output, NULL, NULL);
1086 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1087 editor_verbose_io_cb, ed, NULL);
1088 g_io_channel_unref(channel_output);
1090 channel_error = g_io_channel_unix_new(standard_error);
1091 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1092 g_io_channel_set_encoding(channel_error, NULL, NULL);
1094 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1095 editor_verbose_io_cb, ed, NULL);
1096 g_io_channel_unref(channel_error);
1102 return EDITOR_ERRORS(ed->flags);
1105 static EditorFlags editor_command_next_start(EditorData *ed)
1107 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1109 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1114 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1118 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1119 editor_verbose_window_progress(ed, fd->path);
1121 editor_verbose_window_progress(ed, _("running..."));
1125 error = editor_command_one(ed->editor, ed->list, ed);
1126 if (!error && ed->vd)
1128 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1129 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1131 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1132 editor_verbose_window_fill(ed->vd, "\n", 1);
1139 /* command was not started, call the finish immediately */
1140 return editor_command_next_finish(ed, 0);
1143 /* everything is done */
1144 return editor_command_done(ed);
1147 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1149 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1152 ed->flags |= EDITOR_ERROR_STATUS;
1154 if (ed->flags & EDITOR_FOR_EACH)
1156 /* handle the first element from the list */
1157 GList *fd_element = ed->list;
1159 ed->list = g_list_remove_link(ed->list, fd_element);
1162 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1163 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1165 filelist_free(fd_element);
1169 /* handle whole list */
1171 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1172 filelist_free(ed->list);
1178 case EDITOR_CB_SUSPEND:
1179 return EDITOR_ERRORS(ed->flags);
1180 case EDITOR_CB_SKIP:
1181 return editor_command_done(ed);
1184 return editor_command_next_start(ed);
1187 static EditorFlags editor_command_done(EditorData *ed)
1193 if (ed->count == ed->total)
1195 editor_verbose_window_progress(ed, _("done"));
1199 editor_verbose_window_progress(ed, _("stopped by user"));
1201 editor_verbose_window_enable_close(ed->vd);
1204 /* free the not-handled items */
1207 ed->flags |= EDITOR_ERROR_SKIPPED;
1208 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1209 filelist_free(ed->list);
1215 flags = EDITOR_ERRORS(ed->flags);
1217 if (!ed->vd) editor_data_free(ed);
1222 void editor_resume(gpointer ed)
1224 editor_command_next_start(ed);
1227 void editor_skip(gpointer ed)
1229 editor_command_done(ed);
1232 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1235 EditorFlags flags = editor->flags;
1237 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1239 ed = g_new0(EditorData, 1);
1240 ed->list = filelist_copy(list);
1242 ed->editor = editor;
1243 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1246 ed->working_directory = g_strdup(working_directory);
1248 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1249 flags |= EDITOR_VERBOSE;
1251 if (flags & EDITOR_VERBOSE)
1252 editor_verbose_window(ed, text);
1254 editor_command_next_start(ed);
1255 /* errors from editor_command_next_start will be handled via callback */
1256 return EDITOR_ERRORS(flags);
1259 gboolean is_valid_editor_command(const gchar *key)
1261 if (!key) return FALSE;
1262 return g_hash_table_lookup(editors, key) != NULL;
1265 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1268 EditorDescription *editor;
1269 if (!key) return EDITOR_ERROR_EMPTY;
1271 editor = g_hash_table_lookup(editors, key);
1273 if (!editor) return EDITOR_ERROR_EMPTY;
1274 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1276 error = editor_command_parse(editor, list, TRUE, NULL);
1278 if (EDITOR_ERRORS(error)) return error;
1280 error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1282 if (EDITOR_ERRORS(error))
1284 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1286 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1290 return EDITOR_ERRORS(error);
1293 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1295 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1298 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1303 if (!fd) return FALSE;
1305 list = g_list_append(NULL, fd);
1306 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1311 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1313 return start_editor_from_file_full(key, fd, NULL, NULL);
1316 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1318 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1321 gboolean editor_window_flag_set(const gchar *key)
1323 EditorDescription *editor;
1324 if (!key) return TRUE;
1326 editor = g_hash_table_lookup(editors, key);
1327 if (!editor) return TRUE;
1329 return !!(editor->flags & EDITOR_KEEP_FS);
1332 gboolean editor_is_filter(const gchar *key)
1334 EditorDescription *editor;
1335 if (!key) return TRUE;
1337 editor = g_hash_table_lookup(editors, key);
1338 if (!editor) return TRUE;
1340 return !!(editor->flags & EDITOR_DEST);
1343 gboolean editor_no_param(const gchar *key)
1345 EditorDescription *editor;
1346 if (!key) return FALSE;
1348 editor = g_hash_table_lookup(editors, key);
1349 if (!editor) return FALSE;
1351 return !!(editor->flags & EDITOR_NO_PARAM);
1354 gboolean editor_blocks_file(const gchar *key)
1356 EditorDescription *editor;
1357 if (!key) return FALSE;
1359 editor = g_hash_table_lookup(editors, key);
1360 if (!editor) return FALSE;
1362 /* Decide if the image file should be blocked during editor execution
1363 Editors like gimp can be used long time after the original file was
1364 saved, for editing unrelated files.
1365 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1368 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1371 const gchar *editor_get_error_str(EditorFlags flags)
1373 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1374 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1375 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1376 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1377 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1378 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1379 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1380 return _("Unknown error.");
1383 const gchar *editor_get_name(const gchar *key)
1385 EditorDescription *editor = g_hash_table_lookup(editors, key);
1387 if (!editor) return NULL;
1389 return editor->name;
1391 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */