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;
504 gchar *caseless_name_ea;
505 gchar *caseless_name_eb;
506 gchar *collate_key_ea;
507 gchar *collate_key_eb;
510 ret = strcmp(ea->menu_path, eb->menu_path);
511 if (ret != 0) return ret;
513 caseless_name_ea = g_utf8_casefold(ea->name, -1);
514 caseless_name_eb = g_utf8_casefold(eb->name, -1);
515 collate_key_ea = g_utf8_collate_key_for_filename(caseless_name_ea, -1);
516 collate_key_eb = g_utf8_collate_key_for_filename(caseless_name_eb, -1);
517 ret = g_strcmp0(collate_key_ea, collate_key_eb);
519 g_free(collate_key_ea);
520 g_free(collate_key_eb);
521 g_free(caseless_name_ea);
522 g_free(caseless_name_eb);
527 GList *editor_list_get(void)
529 GList *editors_list = NULL;
531 if (!editors_finished) return NULL;
533 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
534 editors_list = g_list_sort(editors_list, editor_sort);
539 /* ------------------------------ */
542 static void editor_verbose_data_free(EditorData *ed)
549 static void editor_data_free(EditorData *ed)
551 editor_verbose_data_free(ed);
552 g_free(ed->working_directory);
556 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
558 EditorData *ed = data;
560 generic_dialog_close(gd);
561 editor_verbose_data_free(ed);
562 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
565 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
567 EditorData *ed = data;
570 editor_verbose_window_progress(ed, _("stopping..."));
573 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
575 vd->gd->cancel_cb = editor_verbose_window_close;
577 spinner_set_interval(vd->spinner, -1);
578 gtk_widget_set_sensitive(vd->button_stop, FALSE);
579 gtk_widget_set_sensitive(vd->button_close, TRUE);
582 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
584 EditorVerboseData *vd;
589 vd = g_new0(EditorVerboseData, 1);
591 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
594 buf = g_strdup_printf(_("Output of %s"), text);
595 generic_dialog_add_message(vd->gd, NULL, buf, NULL, FALSE);
597 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
598 editor_verbose_window_stop, FALSE);
599 gtk_widget_set_sensitive(vd->button_stop, FALSE);
600 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
601 editor_verbose_window_close, TRUE);
602 gtk_widget_set_sensitive(vd->button_close, FALSE);
604 scrolled = gtk_scrolled_window_new(NULL, NULL);
605 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
606 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
607 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
608 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
609 gtk_widget_show(scrolled);
611 vd->text = gtk_text_view_new();
612 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
613 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
614 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
615 gtk_widget_show(vd->text);
617 hbox = gtk_hbox_new(FALSE, 0);
618 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
619 gtk_widget_show(hbox);
621 vd->progress = gtk_progress_bar_new();
622 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
623 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
624 #if GTK_CHECK_VERSION(3,0,0)
625 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), "");
626 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(vd->progress), TRUE);
628 gtk_widget_show(vd->progress);
630 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
631 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
632 gtk_widget_show(vd->spinner);
634 gtk_widget_show(vd->gd->dialog);
640 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
642 GtkTextBuffer *buffer;
645 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
646 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
647 gtk_text_buffer_insert(buffer, &iter, text, len);
650 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
656 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
659 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
662 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
664 EditorData *ed = data;
668 if (condition & G_IO_IN)
670 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
672 if (!g_utf8_validate(buf, count, NULL))
676 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
679 editor_verbose_window_fill(ed->vd, utf8, -1);
684 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
689 editor_verbose_window_fill(ed->vd, buf, count);
694 if (condition & (G_IO_ERR | G_IO_HUP))
696 g_io_channel_shutdown(source, TRUE, NULL);
710 static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
714 const gchar *p = NULL;
716 DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
718 string = g_string_new("");
720 if (type == PATH_FILE || type == PATH_FILE_URL)
722 GList *work = editor->ext_list;
731 gchar *ext = work->data;
734 if (strcmp(ext, "*") == 0 ||
735 g_ascii_strcasecmp(ext, fd->extension) == 0)
741 work2 = consider_sidecars ? fd->sidecar_files : NULL;
744 FileData *sfd = work2->data;
747 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
758 else if (type == PATH_DEST)
760 if (fd->change && fd->change->dest)
761 p = fd->change->dest;
767 string = g_string_append(string, p);
769 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
770 pathl = path_from_utf8(string->str);
771 g_string_free(string, TRUE);
773 if (pathl && !pathl[0]) /* empty string case */
779 DEBUG_2("editor_command_path_parse: return %s", pathl);
783 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
790 g_string_append_c(str, '\'');
792 g_string_append(str, "\"'");
795 for (p = s; *p != '\0'; p++)
798 g_string_append(str, "'\\''");
800 g_string_append_c(str, *p);
806 g_string_append_c(str, '\'');
808 g_string_append(str, "'\"");
815 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
817 EditorFlags flags = 0;
819 GString *result = NULL;
820 gboolean escape = FALSE;
821 gboolean single_quotes = FALSE;
822 gboolean double_quotes = FALSE;
824 DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
827 result = g_string_new("");
829 if (editor->exec == NULL || editor->exec[0] == '\0')
831 flags |= EDITOR_ERROR_EMPTY;
836 /* skip leading whitespaces if any */
837 while (g_ascii_isspace(*p)) p++;
846 if (output) result = g_string_append_c(result, *p);
850 if (!single_quotes) escape = TRUE;
851 if (output) result = g_string_append_c(result, *p);
855 if (output) result = g_string_append_c(result, *p);
856 if (!single_quotes && !double_quotes)
857 single_quotes = TRUE;
858 else if (single_quotes)
859 single_quotes = FALSE;
863 if (output) result = g_string_append_c(result, *p);
864 if (!single_quotes && !double_quotes)
865 double_quotes = TRUE;
866 else if (double_quotes)
867 double_quotes = FALSE;
869 else if (*p == '%' && p[1])
877 case 'f': /* single file */
878 case 'u': /* single url */
879 flags |= EDITOR_FOR_EACH;
880 if (flags & EDITOR_SINGLE_COMMAND)
882 flags |= EDITOR_ERROR_INCOMPATIBLE;
887 /* use the first file from the list */
890 flags |= EDITOR_ERROR_NO_FILE;
893 pathl = editor_command_path_parse((FileData *)list->data,
895 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
899 /* just testing, check also the rest of the list (like with F and U)
900 any matching file is OK */
901 GList *work = list->next;
903 while (!pathl && work)
905 FileData *fd = work->data;
906 pathl = editor_command_path_parse(fd,
908 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
916 flags |= EDITOR_ERROR_NO_FILE;
921 result = append_quoted(result, pathl, single_quotes, double_quotes);
929 flags |= EDITOR_SINGLE_COMMAND;
930 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
932 flags |= EDITOR_ERROR_INCOMPATIBLE;
944 FileData *fd = work->data;
945 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
953 if (work != list) g_string_append_c(result, ' ');
954 result = append_quoted(result, pathl, single_quotes, double_quotes);
962 flags |= EDITOR_ERROR_NO_FILE;
968 if (editor->icon && *editor->icon)
972 result = g_string_append(result, "--icon ");
973 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
980 result = append_quoted(result, editor->name, single_quotes, double_quotes);
986 result = append_quoted(result, editor->file, single_quotes, double_quotes);
990 /* %% = % escaping */
991 if (output) result = g_string_append_c(result, *p);
999 /* deprecated according to spec, ignore */
1002 flags |= EDITOR_ERROR_SYNTAX;
1008 if (output) result = g_string_append_c(result, *p);
1013 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
1017 *output = g_string_free(result, FALSE);
1018 DEBUG_3("Editor cmd: %s", *output);
1027 g_string_free(result, TRUE);
1034 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
1036 EditorData *ed = data;
1037 g_spawn_close_pid(pid);
1040 editor_command_next_finish(ed, status);
1044 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
1047 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
1049 gint standard_output;
1050 gint standard_error;
1054 ed->flags = editor->flags;
1055 ed->flags |= editor_command_parse(editor, list, TRUE, &command);
1057 ok = !EDITOR_ERRORS(ed->flags);
1061 ok = (options->shell.path && *options->shell.path);
1062 if (!ok) log_printf("ERROR: empty shell command\n");
1066 ok = (access(options->shell.path, X_OK) == 0);
1067 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1070 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1075 gchar *working_directory;
1079 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1080 args[n++] = options->shell.path;
1081 if (options->shell.options && *options->shell.options)
1082 args[n++] = options->shell.options;
1083 args[n++] = command;
1086 if ((ed->flags & EDITOR_DEST) && fd && fd->change && fd->change->dest) /* FIXME: error handling */
1088 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1092 g_unsetenv("GEEQIE_DESTINATION");
1095 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1096 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1100 ed->vd ? &standard_output : NULL,
1101 ed->vd ? &standard_error : NULL,
1104 g_free(working_directory);
1106 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1111 g_child_watch_add(pid, editor_child_exit_cb, ed);
1121 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1122 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1128 GIOChannel *channel_output;
1129 GIOChannel *channel_error;
1131 channel_output = g_io_channel_unix_new(standard_output);
1132 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1133 g_io_channel_set_encoding(channel_output, NULL, NULL);
1135 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1136 editor_verbose_io_cb, ed, NULL);
1137 g_io_channel_unref(channel_output);
1139 channel_error = g_io_channel_unix_new(standard_error);
1140 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1141 g_io_channel_set_encoding(channel_error, NULL, NULL);
1143 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1144 editor_verbose_io_cb, ed, NULL);
1145 g_io_channel_unref(channel_error);
1151 return EDITOR_ERRORS(ed->flags);
1154 static EditorFlags editor_command_next_start(EditorData *ed)
1156 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1158 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1163 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1167 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1168 editor_verbose_window_progress(ed, fd->path);
1170 editor_verbose_window_progress(ed, _("running..."));
1174 error = editor_command_one(ed->editor, ed->list, ed);
1175 if (!error && ed->vd)
1177 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1178 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1180 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1181 editor_verbose_window_fill(ed->vd, "\n", 1);
1188 /* command was not started, call the finish immediately */
1189 return editor_command_next_finish(ed, 0);
1192 /* everything is done */
1193 return editor_command_done(ed);
1196 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1198 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1201 ed->flags |= EDITOR_ERROR_STATUS;
1203 if (ed->flags & EDITOR_FOR_EACH)
1205 /* handle the first element from the list */
1206 GList *fd_element = ed->list;
1208 ed->list = g_list_remove_link(ed->list, fd_element);
1211 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1212 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1214 filelist_free(fd_element);
1218 /* handle whole list */
1220 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1221 filelist_free(ed->list);
1227 case EDITOR_CB_SUSPEND:
1228 return EDITOR_ERRORS(ed->flags);
1229 case EDITOR_CB_SKIP:
1230 return editor_command_done(ed);
1233 return editor_command_next_start(ed);
1236 static EditorFlags editor_command_done(EditorData *ed)
1242 if (ed->count == ed->total)
1244 editor_verbose_window_progress(ed, _("done"));
1248 editor_verbose_window_progress(ed, _("stopped by user"));
1250 editor_verbose_window_enable_close(ed->vd);
1253 /* free the not-handled items */
1256 ed->flags |= EDITOR_ERROR_SKIPPED;
1257 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1258 filelist_free(ed->list);
1264 flags = EDITOR_ERRORS(ed->flags);
1266 if (!ed->vd) editor_data_free(ed);
1271 void editor_resume(gpointer ed)
1273 editor_command_next_start(ed);
1276 void editor_skip(gpointer ed)
1278 editor_command_done(ed);
1281 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1284 EditorFlags flags = editor->flags;
1286 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1288 ed = g_new0(EditorData, 1);
1289 ed->list = filelist_copy(list);
1291 ed->editor = editor;
1292 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1295 ed->working_directory = g_strdup(working_directory);
1297 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1298 flags |= EDITOR_VERBOSE;
1300 if (flags & EDITOR_VERBOSE)
1301 editor_verbose_window(ed, text);
1303 editor_command_next_start(ed);
1304 /* errors from editor_command_next_start will be handled via callback */
1305 return EDITOR_ERRORS(flags);
1308 gboolean is_valid_editor_command(const gchar *key)
1310 if (!key) return FALSE;
1311 return g_hash_table_lookup(editors, key) != NULL;
1314 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1317 EditorDescription *editor;
1318 if (!key) return EDITOR_ERROR_EMPTY;
1320 editor = g_hash_table_lookup(editors, key);
1322 if (!editor) return EDITOR_ERROR_EMPTY;
1323 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1325 error = editor_command_parse(editor, list, TRUE, NULL);
1327 if (EDITOR_ERRORS(error)) return error;
1329 error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1331 if (EDITOR_ERRORS(error))
1333 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1335 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1339 return EDITOR_ERRORS(error);
1342 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1344 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1347 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1352 if (!fd) return FALSE;
1354 list = g_list_append(NULL, fd);
1355 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1360 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1362 return start_editor_from_file_full(key, fd, NULL, NULL);
1365 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1367 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1370 gboolean editor_window_flag_set(const gchar *key)
1372 EditorDescription *editor;
1373 if (!key) return TRUE;
1375 editor = g_hash_table_lookup(editors, key);
1376 if (!editor) return TRUE;
1378 return !!(editor->flags & EDITOR_KEEP_FS);
1381 gboolean editor_is_filter(const gchar *key)
1383 EditorDescription *editor;
1384 if (!key) return TRUE;
1386 editor = g_hash_table_lookup(editors, key);
1387 if (!editor) return TRUE;
1389 return !!(editor->flags & EDITOR_DEST);
1392 gboolean editor_no_param(const gchar *key)
1394 EditorDescription *editor;
1395 if (!key) return FALSE;
1397 editor = g_hash_table_lookup(editors, key);
1398 if (!editor) return FALSE;
1400 return !!(editor->flags & EDITOR_NO_PARAM);
1403 gboolean editor_blocks_file(const gchar *key)
1405 EditorDescription *editor;
1406 if (!key) return FALSE;
1408 editor = g_hash_table_lookup(editors, key);
1409 if (!editor) return FALSE;
1411 /* Decide if the image file should be blocked during editor execution
1412 Editors like gimp can be used long time after the original file was
1413 saved, for editing unrelated files.
1414 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1417 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1420 const gchar *editor_get_error_str(EditorFlags flags)
1422 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1423 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1424 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1425 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1426 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1427 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1428 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1429 return _("Unknown error.");
1432 const gchar *editor_get_name(const gchar *key)
1434 EditorDescription *editor = g_hash_table_lookup(editors, key);
1436 if (!editor) return NULL;
1438 return editor->name;
1440 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */