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/heic", ".heic"},
115 {"image/jpeg", ".jpeg;.jpg;.mpo"},
116 {"image/jpg", ".jpg;.jpeg"},
117 {"image/webp", ".webp"},
118 {"image/pcx", ".pcx"},
119 {"image/png", ".png"},
120 {"image/svg", ".svg"},
121 {"image/svg+xml", ".svg"},
122 {"image/svg+xml-compressed", ".svg"},
123 {"image/tiff", ".tiff;.tif;.mef"},
124 {"image/vnd-ms.dds", ".dds"},
125 {"image/x-adobe-dng", ".dng"},
126 {"image/x-bmp", ".bmp"},
127 {"image/x-canon-crw", ".crw"},
128 {"image/x-canon-cr2", ".cr2"},
129 {"image/x-canon-cr3", ".cr3"},
130 {"image/x-cr2", ".cr2"},
131 {"image/x-dcraw", "%raw;.mos"},
132 {"image/x-epson-erf", "%erf"},
133 {"image/x-ico", ".ico"},
134 {"image/x-kodak-kdc", ".kdc"},
135 {"image/x-mrw", ".mrw"},
136 {"image/x-minolta-mrw", ".mrw"},
137 {"image/x-MS-bmp", ".bmp"},
138 {"image/x-nef", ".nef"},
139 {"image/x-nikon-nef", ".nef"},
140 {"image/x-panasonic-raw", ".raw"},
141 {"image/x-panasonic-rw2", ".rw2"},
142 {"image/x-pentax-pef", ".pef"},
143 {"image/x-orf", ".orf"},
144 {"image/x-olympus-orf", ".orf"},
145 {"image/x-pcx", ".pcx"},
146 {"image/xpm", ".xpm"},
147 {"image/x-png", ".png"},
148 {"image/x-portable-anymap", ".pam"},
149 {"image/x-portable-bitmap", ".pbm"},
150 {"image/x-portable-graymap", ".pgm"},
151 {"image/x-portable-pixmap", ".ppm"},
152 {"image/x-psd", ".psd"},
153 {"image/x-raf", ".raf"},
154 {"image/x-fuji-raf", ".raf"},
155 {"image/x-sgi", ".sgi"},
156 {"image/x-sony-arw", ".arw"},
157 {"image/x-sony-sr2", ".sr2"},
158 {"image/x-sony-srf", ".srf"},
159 {"image/x-tga", ".tga"},
160 {"image/x-xbitmap", ".xbm"},
161 {"image/x-xcf", ".xcf"},
162 {"image/x-xpixmap", ".xpm"},
163 {"image/x-x3f", ".x3f"},
164 {"application/x-navi-animation", ".ani"},
165 {"application/x-ptoptimizer-script", ".pto"},
171 for (i = 0; mime_types[i]; i++)
172 for (j = 0; conv_table[j][0]; j++)
173 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
174 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
179 gboolean editor_read_desktop_file(const gchar *path)
182 EditorDescription *editor;
185 const gchar *key = filename_from_path(path);
186 gchar **categories, **only_show_in, **not_show_in;
189 gboolean category_geeqie = FALSE;
193 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
195 key_file = g_key_file_new();
196 if (!g_key_file_load_from_file(key_file, path, 0, NULL))
198 g_key_file_free(key_file);
202 type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
203 if (!type || strcmp(type, "Application") != 0)
205 /* We only consider desktop entries of Application type */
206 g_key_file_free(key_file);
212 editor = g_new0(EditorDescription, 1);
214 editor->key = g_strdup(key);
215 editor->file = g_strdup(path);
217 g_hash_table_insert(editors, editor->key, editor);
219 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
220 || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
222 editor->hidden = TRUE;
225 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
228 gboolean found = FALSE;
230 for (i = 0; categories[i]; i++)
232 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
233 if (strcmp(categories[i], "Graphics") == 0)
237 if (strcmp(categories[i], "X-Geeqie") == 0)
240 category_geeqie = TRUE;
244 if (!found) editor->ignored = TRUE;
245 g_strfreev(categories);
249 editor->ignored = TRUE;
252 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
255 gboolean found = FALSE;
257 for (i = 0; only_show_in[i]; i++)
258 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
263 if (!found) editor->ignored = TRUE;
264 g_strfreev(only_show_in);
267 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
270 gboolean found = FALSE;
272 for (i = 0; not_show_in[i]; i++)
273 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
278 if (found) editor->ignored = TRUE;
279 g_strfreev(not_show_in);
283 try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
284 if (try_exec && !editor->hidden && !editor->ignored)
286 gchar *try_exec_res = g_find_program_in_path(try_exec);
287 if (!try_exec_res) editor->hidden = TRUE;
288 g_free(try_exec_res);
294 /* ignored editors will be deleted, no need to parse the rest */
295 g_key_file_free(key_file);
299 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
300 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
302 /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
303 if (editor->icon && !g_path_is_absolute(editor->icon))
305 gchar *ext = strrchr(editor->icon, '.');
307 if (ext && strlen(ext) == 4 &&
308 (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
310 log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
311 editor->file, editor->icon);
317 if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon))
319 g_free(editor->icon);
323 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
325 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
326 if (!editor->menu_path) editor->menu_path = g_strdup("PluginsMenu");
328 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
330 editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
332 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
334 editor->ext_list = filter_to_list(extensions);
337 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
340 editor->ext_list = editor_mime_types_to_extensions(mime_types);
341 g_strfreev(mime_types);
342 if (!editor->ext_list) editor->hidden = TRUE;
346 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
347 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
348 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
349 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
350 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
352 editor->flags |= editor_command_parse(editor, NULL, FALSE, NULL);
354 if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
356 g_key_file_free(key_file);
358 if (editor->ignored) return TRUE;
360 work = options->disabled_plugins;
365 if (g_strcmp0(path, work->data) == 0)
373 editor->disabled = disabled;
375 gtk_list_store_append(desktop_file_list, &iter);
376 gtk_list_store_set(desktop_file_list, &iter,
377 DESKTOP_FILE_COLUMN_KEY, key,
378 DESKTOP_FILE_COLUMN_DISABLED, editor->disabled,
379 DESKTOP_FILE_COLUMN_NAME, editor->name,
380 DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden ? _("yes") : _("no"),
381 DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
382 DESKTOP_FILE_COLUMN_PATH, path, -1);
387 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
389 EditorDescription *editor = value;
390 return editor->hidden || editor->ignored;
393 void editor_table_finish(void)
395 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
396 editors_finished = TRUE;
399 void editor_table_clear(void)
401 if (desktop_file_list)
403 gtk_list_store_clear(desktop_file_list);
407 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);
411 g_hash_table_destroy(editors);
413 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
414 editors_finished = FALSE;
417 static GList *editor_add_desktop_dir(GList *list, const gchar *path)
423 pathl = path_from_utf8(path);
431 while ((dir = readdir(dp)) != NULL)
433 gchar *namel = dir->d_name;
435 if (g_str_has_suffix(namel, ".desktop"))
437 gchar *name = path_to_utf8(namel);
438 gchar *dpath = g_build_filename(path, name, NULL);
439 list = g_list_prepend(list, dpath);
447 GList *editor_get_desktop_files(void)
450 gchar *xdg_data_dirs;
456 xdg_data_dirs = getenv("XDG_DATA_DIRS");
457 if (xdg_data_dirs && xdg_data_dirs[0])
458 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
460 xdg_data_dirs = g_strdup("/usr/share");
462 all_dirs = g_strconcat(get_rc_dir(), ":", gq_app_dir, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
464 g_free(xdg_data_dirs);
466 split_dirs = g_strsplit(all_dirs, ":", 0);
470 for (i = 0; split_dirs[i]; i++);
471 for (--i; i >= 0; i--)
473 path = g_build_filename(split_dirs[i], "applications", NULL);
474 list = editor_add_desktop_dir(list, path);
478 g_strfreev(split_dirs);
482 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
484 GList **listp = data;
485 EditorDescription *editor = value;
487 /* do not show the special commands in any list, they are called explicitly */
488 if (strcmp(editor->key, CMD_COPY) == 0 ||
489 strcmp(editor->key, CMD_MOVE) == 0 ||
490 strcmp(editor->key, CMD_RENAME) == 0 ||
491 strcmp(editor->key, CMD_DELETE) == 0 ||
492 strcmp(editor->key, CMD_FOLDER) == 0) return;
494 if (editor->disabled)
499 *listp = g_list_prepend(*listp, editor);
502 static gint editor_sort(gconstpointer a, gconstpointer b)
504 const EditorDescription *ea = a;
505 const EditorDescription *eb = b;
506 gchar *caseless_name_ea;
507 gchar *caseless_name_eb;
508 gchar *collate_key_ea;
509 gchar *collate_key_eb;
512 ret = strcmp(ea->menu_path, eb->menu_path);
513 if (ret != 0) return ret;
515 caseless_name_ea = g_utf8_casefold(ea->name, -1);
516 caseless_name_eb = g_utf8_casefold(eb->name, -1);
517 collate_key_ea = g_utf8_collate_key_for_filename(caseless_name_ea, -1);
518 collate_key_eb = g_utf8_collate_key_for_filename(caseless_name_eb, -1);
519 ret = g_strcmp0(collate_key_ea, collate_key_eb);
521 g_free(collate_key_ea);
522 g_free(collate_key_eb);
523 g_free(caseless_name_ea);
524 g_free(caseless_name_eb);
529 GList *editor_list_get(void)
531 GList *editors_list = NULL;
533 if (!editors_finished) return NULL;
535 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
536 editors_list = g_list_sort(editors_list, editor_sort);
541 /* ------------------------------ */
544 static void editor_verbose_data_free(EditorData *ed)
551 static void editor_data_free(EditorData *ed)
553 editor_verbose_data_free(ed);
554 g_free(ed->working_directory);
558 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
560 EditorData *ed = data;
562 generic_dialog_close(gd);
563 editor_verbose_data_free(ed);
564 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
567 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
569 EditorData *ed = data;
572 editor_verbose_window_progress(ed, _("stopping..."));
575 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
577 vd->gd->cancel_cb = editor_verbose_window_close;
579 spinner_set_interval(vd->spinner, -1);
580 gtk_widget_set_sensitive(vd->button_stop, FALSE);
581 gtk_widget_set_sensitive(vd->button_close, TRUE);
584 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
586 EditorVerboseData *vd;
591 vd = g_new0(EditorVerboseData, 1);
593 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
596 buf = g_strdup_printf(_("Output of %s"), text);
597 generic_dialog_add_message(vd->gd, NULL, buf, NULL, FALSE);
599 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
600 editor_verbose_window_stop, FALSE);
601 gtk_widget_set_sensitive(vd->button_stop, FALSE);
602 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
603 editor_verbose_window_close, TRUE);
604 gtk_widget_set_sensitive(vd->button_close, FALSE);
606 scrolled = gtk_scrolled_window_new(NULL, NULL);
607 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
608 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
609 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
610 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
611 gtk_widget_show(scrolled);
613 vd->text = gtk_text_view_new();
614 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
615 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
616 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
617 gtk_widget_show(vd->text);
619 hbox = gtk_hbox_new(FALSE, 0);
620 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
621 gtk_widget_show(hbox);
623 vd->progress = gtk_progress_bar_new();
624 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
625 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
626 #if GTK_CHECK_VERSION(3,0,0)
627 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), "");
628 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(vd->progress), TRUE);
630 gtk_widget_show(vd->progress);
632 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
633 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
634 gtk_widget_show(vd->spinner);
636 gtk_widget_show(vd->gd->dialog);
642 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
644 GtkTextBuffer *buffer;
647 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
648 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
649 gtk_text_buffer_insert(buffer, &iter, text, len);
652 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
658 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
661 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
664 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
666 EditorData *ed = data;
670 if (condition & G_IO_IN)
672 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
674 if (!g_utf8_validate(buf, count, NULL))
678 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
681 editor_verbose_window_fill(ed->vd, utf8, -1);
686 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
691 editor_verbose_window_fill(ed->vd, buf, count);
696 if (condition & (G_IO_ERR | G_IO_HUP))
698 g_io_channel_shutdown(source, TRUE, NULL);
712 static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
716 const gchar *p = NULL;
718 DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
720 string = g_string_new("");
722 if (type == PATH_FILE || type == PATH_FILE_URL)
724 GList *work = editor->ext_list;
733 gchar *ext = work->data;
736 if (strcmp(ext, "*") == 0 ||
737 g_ascii_strcasecmp(ext, fd->extension) == 0)
743 work2 = consider_sidecars ? fd->sidecar_files : NULL;
746 FileData *sfd = work2->data;
749 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
760 else if (type == PATH_DEST)
762 if (fd->change && fd->change->dest)
763 p = fd->change->dest;
769 string = g_string_append(string, p);
771 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
772 pathl = path_from_utf8(string->str);
773 g_string_free(string, TRUE);
775 if (pathl && !pathl[0]) /* empty string case */
781 DEBUG_2("editor_command_path_parse: return %s", pathl);
785 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
792 g_string_append_c(str, '\'');
794 g_string_append(str, "\"'");
797 for (p = s; *p != '\0'; p++)
800 g_string_append(str, "'\\''");
802 g_string_append_c(str, *p);
808 g_string_append_c(str, '\'');
810 g_string_append(str, "'\"");
817 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
819 EditorFlags flags = 0;
821 GString *result = NULL;
822 gboolean escape = FALSE;
823 gboolean single_quotes = FALSE;
824 gboolean double_quotes = FALSE;
826 DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
829 result = g_string_new("");
831 if (editor->exec == NULL || editor->exec[0] == '\0')
833 flags |= EDITOR_ERROR_EMPTY;
838 /* skip leading whitespaces if any */
839 while (g_ascii_isspace(*p)) p++;
848 if (output) result = g_string_append_c(result, *p);
852 if (!single_quotes) escape = TRUE;
853 if (output) result = g_string_append_c(result, *p);
857 if (output) result = g_string_append_c(result, *p);
858 if (!single_quotes && !double_quotes)
859 single_quotes = TRUE;
860 else if (single_quotes)
861 single_quotes = FALSE;
865 if (output) result = g_string_append_c(result, *p);
866 if (!single_quotes && !double_quotes)
867 double_quotes = TRUE;
868 else if (double_quotes)
869 double_quotes = FALSE;
871 else if (*p == '%' && p[1])
879 case 'f': /* single file */
880 case 'u': /* single url */
881 flags |= EDITOR_FOR_EACH;
882 if (flags & EDITOR_SINGLE_COMMAND)
884 flags |= EDITOR_ERROR_INCOMPATIBLE;
889 /* use the first file from the list */
892 flags |= EDITOR_ERROR_NO_FILE;
895 pathl = editor_command_path_parse((FileData *)list->data,
897 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
901 /* just testing, check also the rest of the list (like with F and U)
902 any matching file is OK */
903 GList *work = list->next;
905 while (!pathl && work)
907 FileData *fd = work->data;
908 pathl = editor_command_path_parse(fd,
910 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
918 flags |= EDITOR_ERROR_NO_FILE;
923 result = append_quoted(result, pathl, single_quotes, double_quotes);
931 flags |= EDITOR_SINGLE_COMMAND;
932 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
934 flags |= EDITOR_ERROR_INCOMPATIBLE;
946 FileData *fd = work->data;
947 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
955 if (work != list) g_string_append_c(result, ' ');
956 result = append_quoted(result, pathl, single_quotes, double_quotes);
964 flags |= EDITOR_ERROR_NO_FILE;
970 if (editor->icon && *editor->icon)
974 result = g_string_append(result, "--icon ");
975 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
982 result = append_quoted(result, editor->name, single_quotes, double_quotes);
988 result = append_quoted(result, editor->file, single_quotes, double_quotes);
992 /* %% = % escaping */
993 if (output) result = g_string_append_c(result, *p);
1001 /* deprecated according to spec, ignore */
1004 flags |= EDITOR_ERROR_SYNTAX;
1010 if (output) result = g_string_append_c(result, *p);
1015 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
1019 *output = g_string_free(result, FALSE);
1020 DEBUG_3("Editor cmd: %s", *output);
1029 g_string_free(result, TRUE);
1036 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
1038 EditorData *ed = data;
1039 g_spawn_close_pid(pid);
1042 editor_command_next_finish(ed, status);
1046 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
1049 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
1051 gint standard_output;
1052 gint standard_error;
1056 ed->flags = editor->flags;
1057 ed->flags |= editor_command_parse(editor, list, TRUE, &command);
1059 ok = !EDITOR_ERRORS(ed->flags);
1063 ok = (options->shell.path && *options->shell.path);
1064 if (!ok) log_printf("ERROR: empty shell command\n");
1068 ok = (access(options->shell.path, X_OK) == 0);
1069 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1072 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1077 gchar *working_directory;
1081 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1082 args[n++] = options->shell.path;
1083 if (options->shell.options && *options->shell.options)
1084 args[n++] = options->shell.options;
1085 args[n++] = command;
1088 if ((ed->flags & EDITOR_DEST) && fd && fd->change && fd->change->dest) /** @FIXME error handling */
1090 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1094 g_unsetenv("GEEQIE_DESTINATION");
1097 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1098 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1102 ed->vd ? &standard_output : NULL,
1103 ed->vd ? &standard_error : NULL,
1106 g_free(working_directory);
1108 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1113 g_child_watch_add(pid, editor_child_exit_cb, ed);
1123 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1124 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1130 GIOChannel *channel_output;
1131 GIOChannel *channel_error;
1133 channel_output = g_io_channel_unix_new(standard_output);
1134 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1135 g_io_channel_set_encoding(channel_output, NULL, NULL);
1137 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1138 editor_verbose_io_cb, ed, NULL);
1139 g_io_channel_unref(channel_output);
1141 channel_error = g_io_channel_unix_new(standard_error);
1142 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1143 g_io_channel_set_encoding(channel_error, NULL, NULL);
1145 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1146 editor_verbose_io_cb, ed, NULL);
1147 g_io_channel_unref(channel_error);
1153 return EDITOR_ERRORS(ed->flags);
1156 static EditorFlags editor_command_next_start(EditorData *ed)
1158 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1160 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1165 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1169 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1170 editor_verbose_window_progress(ed, fd->path);
1172 editor_verbose_window_progress(ed, _("running..."));
1176 error = editor_command_one(ed->editor, ed->list, ed);
1177 if (!error && ed->vd)
1179 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1180 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1182 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1183 editor_verbose_window_fill(ed->vd, "\n", 1);
1190 /* command was not started, call the finish immediately */
1191 return editor_command_next_finish(ed, 0);
1194 /* everything is done */
1195 return editor_command_done(ed);
1198 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1200 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1203 ed->flags |= EDITOR_ERROR_STATUS;
1205 if (ed->flags & EDITOR_FOR_EACH)
1207 /* handle the first element from the list */
1208 GList *fd_element = ed->list;
1210 ed->list = g_list_remove_link(ed->list, fd_element);
1213 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1214 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1216 filelist_free(fd_element);
1220 /* handle whole list */
1222 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1223 filelist_free(ed->list);
1229 case EDITOR_CB_SUSPEND:
1230 return EDITOR_ERRORS(ed->flags);
1231 case EDITOR_CB_SKIP:
1232 return editor_command_done(ed);
1235 return editor_command_next_start(ed);
1238 static EditorFlags editor_command_done(EditorData *ed)
1244 if (ed->count == ed->total)
1246 editor_verbose_window_progress(ed, _("done"));
1250 editor_verbose_window_progress(ed, _("stopped by user"));
1252 editor_verbose_window_enable_close(ed->vd);
1255 /* free the not-handled items */
1258 ed->flags |= EDITOR_ERROR_SKIPPED;
1259 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1260 filelist_free(ed->list);
1266 flags = EDITOR_ERRORS(ed->flags);
1268 if (!ed->vd) editor_data_free(ed);
1273 void editor_resume(gpointer ed)
1275 editor_command_next_start(ed);
1278 void editor_skip(gpointer ed)
1280 editor_command_done(ed);
1283 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1286 EditorFlags flags = editor->flags;
1288 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1290 ed = g_new0(EditorData, 1);
1291 ed->list = filelist_copy(list);
1293 ed->editor = editor;
1294 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1297 ed->working_directory = g_strdup(working_directory);
1299 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1300 flags |= EDITOR_VERBOSE;
1302 if (flags & EDITOR_VERBOSE)
1303 editor_verbose_window(ed, text);
1305 editor_command_next_start(ed);
1306 /* errors from editor_command_next_start will be handled via callback */
1307 return EDITOR_ERRORS(flags);
1310 gboolean is_valid_editor_command(const gchar *key)
1312 if (!key) return FALSE;
1313 return g_hash_table_lookup(editors, key) != NULL;
1316 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1319 EditorDescription *editor;
1320 if (!key) return EDITOR_ERROR_EMPTY;
1322 editor = g_hash_table_lookup(editors, key);
1324 if (!editor) return EDITOR_ERROR_EMPTY;
1325 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1327 error = editor_command_parse(editor, list, TRUE, NULL);
1329 if (EDITOR_ERRORS(error)) return error;
1331 error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1333 if (EDITOR_ERRORS(error))
1335 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1337 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1341 return EDITOR_ERRORS(error);
1344 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1346 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1349 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1354 if (!fd) return FALSE;
1356 list = g_list_append(NULL, fd);
1357 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1362 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1364 return start_editor_from_file_full(key, fd, NULL, NULL);
1367 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1369 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1372 gboolean editor_window_flag_set(const gchar *key)
1374 EditorDescription *editor;
1375 if (!key) return TRUE;
1377 editor = g_hash_table_lookup(editors, key);
1378 if (!editor) return TRUE;
1380 return !!(editor->flags & EDITOR_KEEP_FS);
1383 gboolean editor_is_filter(const gchar *key)
1385 EditorDescription *editor;
1386 if (!key) return TRUE;
1388 editor = g_hash_table_lookup(editors, key);
1389 if (!editor) return TRUE;
1391 return !!(editor->flags & EDITOR_DEST);
1394 gboolean editor_no_param(const gchar *key)
1396 EditorDescription *editor;
1397 if (!key) return FALSE;
1399 editor = g_hash_table_lookup(editors, key);
1400 if (!editor) return FALSE;
1402 return !!(editor->flags & EDITOR_NO_PARAM);
1405 gboolean editor_blocks_file(const gchar *key)
1407 EditorDescription *editor;
1408 if (!key) return FALSE;
1410 editor = g_hash_table_lookup(editors, key);
1411 if (!editor) return FALSE;
1413 /* Decide if the image file should be blocked during editor execution
1414 Editors like gimp can be used long time after the original file was
1415 saved, for editing unrelated files.
1416 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1419 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1422 const gchar *editor_get_error_str(EditorFlags flags)
1424 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1425 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1426 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1427 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1428 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1429 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1430 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1431 return _("Unknown error.");
1434 const gchar *editor_get_name(const gchar *key)
1436 EditorDescription *editor = g_hash_table_lookup(editors, key);
1438 if (!editor) return NULL;
1440 return editor->name;
1442 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */