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] = {
111 {"image/bmp", ".bmp"},
112 {"image/gif", ".gif"},
113 {"image/heic", ".heic"},
114 {"image/jpeg", ".jpeg;.jpg;.mpo"},
115 {"image/jpg", ".jpg;.jpeg"},
116 {"image/webp", ".webp"},
117 {"image/pcx", ".pcx"},
118 {"image/png", ".png"},
119 {"image/svg", ".svg"},
120 {"image/svg+xml", ".svg"},
121 {"image/svg+xml-compressed", ".svg"},
122 {"image/tiff", ".tiff;.tif;.mef"},
123 {"image/vnd-ms.dds", ".dds"},
124 {"image/x-adobe-dng", ".dng"},
125 {"image/x-bmp", ".bmp"},
126 {"image/x-canon-crw", ".crw"},
127 {"image/x-canon-cr2", ".cr2"},
128 {"image/x-canon-cr3", ".cr3"},
129 {"image/x-cr2", ".cr2"},
130 {"image/x-dcraw", "%raw;.mos"},
131 {"image/x-epson-erf", "%erf"},
132 {"image/x-ico", ".ico"},
133 {"image/x-kodak-kdc", ".kdc"},
134 {"image/x-mrw", ".mrw"},
135 {"image/x-minolta-mrw", ".mrw"},
136 {"image/x-MS-bmp", ".bmp"},
137 {"image/x-nef", ".nef"},
138 {"image/x-nikon-nef", ".nef"},
139 {"image/x-panasonic-raw", ".raw"},
140 {"image/x-panasonic-rw2", ".rw2"},
141 {"image/x-pentax-pef", ".pef"},
142 {"image/x-orf", ".orf"},
143 {"image/x-olympus-orf", ".orf"},
144 {"image/x-pcx", ".pcx"},
145 {"image/xpm", ".xpm"},
146 {"image/x-png", ".png"},
147 {"image/x-portable-anymap", ".pam"},
148 {"image/x-portable-bitmap", ".pbm"},
149 {"image/x-portable-graymap", ".pgm"},
150 {"image/x-portable-pixmap", ".ppm"},
151 {"image/x-psd", ".psd"},
152 {"image/x-raf", ".raf"},
153 {"image/x-fuji-raf", ".raf"},
154 {"image/x-sgi", ".sgi"},
155 {"image/x-sony-arw", ".arw"},
156 {"image/x-sony-sr2", ".sr2"},
157 {"image/x-sony-srf", ".srf"},
158 {"image/x-tga", ".tga"},
159 {"image/x-xbitmap", ".xbm"},
160 {"image/x-xcf", ".xcf"},
161 {"image/x-xpixmap", ".xpm"},
162 {"image/x-x3f", ".x3f"},
163 {"application/x-navi-animation", ".ani"},
164 {"application/x-ptoptimizer-script", ".pto"},
170 for (i = 0; mime_types[i]; i++)
171 for (j = 0; conv_table[j][0]; j++)
172 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
173 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
178 gboolean editor_read_desktop_file(const gchar *path)
181 EditorDescription *editor;
184 const gchar *key = filename_from_path(path);
185 gchar **categories, **only_show_in, **not_show_in;
188 gboolean category_geeqie = FALSE;
192 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
194 key_file = g_key_file_new();
195 if (!g_key_file_load_from_file(key_file, path, 0, NULL))
197 g_key_file_free(key_file);
201 type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
202 if (!type || strcmp(type, "Application") != 0)
204 /* We only consider desktop entries of Application type */
205 g_key_file_free(key_file);
211 editor = g_new0(EditorDescription, 1);
213 editor->key = g_strdup(key);
214 editor->file = g_strdup(path);
216 g_hash_table_insert(editors, editor->key, editor);
218 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
219 || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
221 editor->hidden = TRUE;
224 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
227 gboolean found = FALSE;
229 for (i = 0; categories[i]; i++)
231 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
232 if (strcmp(categories[i], "Graphics") == 0)
236 if (strcmp(categories[i], "X-Geeqie") == 0)
239 category_geeqie = TRUE;
243 if (!found) editor->ignored = TRUE;
244 g_strfreev(categories);
248 editor->ignored = TRUE;
251 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
254 gboolean found = FALSE;
256 for (i = 0; only_show_in[i]; i++)
257 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
262 if (!found) editor->ignored = TRUE;
263 g_strfreev(only_show_in);
266 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
269 gboolean found = FALSE;
271 for (i = 0; not_show_in[i]; i++)
272 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
277 if (found) editor->ignored = TRUE;
278 g_strfreev(not_show_in);
282 try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
283 if (try_exec && !editor->hidden && !editor->ignored)
285 gchar *try_exec_res = g_find_program_in_path(try_exec);
286 if (!try_exec_res) editor->hidden = TRUE;
287 g_free(try_exec_res);
293 /* ignored editors will be deleted, no need to parse the rest */
294 g_key_file_free(key_file);
298 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
299 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
301 /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
302 if (editor->icon && !g_path_is_absolute(editor->icon))
304 gchar *ext = strrchr(editor->icon, '.');
306 if (ext && strlen(ext) == 4 &&
307 (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
309 log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
310 editor->file, editor->icon);
316 if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon))
318 g_free(editor->icon);
322 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
324 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
325 if (!editor->menu_path) editor->menu_path = g_strdup("PluginsMenu");
327 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
329 editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
331 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
333 editor->ext_list = filter_to_list(extensions);
336 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
339 editor->ext_list = editor_mime_types_to_extensions(mime_types);
340 g_strfreev(mime_types);
341 if (!editor->ext_list) editor->hidden = TRUE;
345 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
346 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
347 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
348 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
349 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
351 editor->flags |= editor_command_parse(editor, NULL, FALSE, NULL);
353 if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
355 g_key_file_free(key_file);
357 if (editor->ignored) return TRUE;
359 work = options->disabled_plugins;
364 if (g_strcmp0(path, work->data) == 0)
372 editor->disabled = disabled;
374 gtk_list_store_append(desktop_file_list, &iter);
375 gtk_list_store_set(desktop_file_list, &iter,
376 DESKTOP_FILE_COLUMN_KEY, key,
377 DESKTOP_FILE_COLUMN_DISABLED, editor->disabled,
378 DESKTOP_FILE_COLUMN_NAME, editor->name,
379 DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden ? _("yes") : _("no"),
380 DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
381 DESKTOP_FILE_COLUMN_PATH, path, -1);
386 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
388 EditorDescription *editor = value;
389 return editor->hidden || editor->ignored;
392 void editor_table_finish(void)
394 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
395 editors_finished = TRUE;
398 void editor_table_clear(void)
400 if (desktop_file_list)
402 gtk_list_store_clear(desktop_file_list);
406 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);
410 g_hash_table_destroy(editors);
412 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
413 editors_finished = FALSE;
416 static GList *editor_add_desktop_dir(GList *list, const gchar *path)
422 pathl = path_from_utf8(path);
430 while ((dir = readdir(dp)) != NULL)
432 gchar *namel = dir->d_name;
434 if (g_str_has_suffix(namel, ".desktop"))
436 gchar *name = path_to_utf8(namel);
437 gchar *dpath = g_build_filename(path, name, NULL);
438 list = g_list_prepend(list, dpath);
446 GList *editor_get_desktop_files(void)
449 gchar *xdg_data_dirs;
455 xdg_data_dirs = getenv("XDG_DATA_DIRS");
456 if (xdg_data_dirs && xdg_data_dirs[0])
457 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
459 xdg_data_dirs = g_strdup("/usr/share");
461 all_dirs = g_strconcat(get_rc_dir(), ":", gq_app_dir, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
463 g_free(xdg_data_dirs);
465 split_dirs = g_strsplit(all_dirs, ":", 0);
469 for (i = 0; split_dirs[i]; i++);
470 for (--i; i >= 0; i--)
472 path = g_build_filename(split_dirs[i], "applications", NULL);
473 list = editor_add_desktop_dir(list, path);
477 g_strfreev(split_dirs);
481 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
483 GList **listp = data;
484 EditorDescription *editor = value;
486 /* do not show the special commands in any list, they are called explicitly */
487 if (strcmp(editor->key, CMD_COPY) == 0 ||
488 strcmp(editor->key, CMD_MOVE) == 0 ||
489 strcmp(editor->key, CMD_RENAME) == 0 ||
490 strcmp(editor->key, CMD_DELETE) == 0 ||
491 strcmp(editor->key, CMD_FOLDER) == 0) return;
493 if (editor->disabled)
498 *listp = g_list_prepend(*listp, editor);
501 static gint editor_sort(gconstpointer a, gconstpointer b)
503 const EditorDescription *ea = a;
504 const EditorDescription *eb = b;
505 gchar *caseless_name_ea;
506 gchar *caseless_name_eb;
507 gchar *collate_key_ea;
508 gchar *collate_key_eb;
511 ret = strcmp(ea->menu_path, eb->menu_path);
512 if (ret != 0) return ret;
514 caseless_name_ea = g_utf8_casefold(ea->name, -1);
515 caseless_name_eb = g_utf8_casefold(eb->name, -1);
516 collate_key_ea = g_utf8_collate_key_for_filename(caseless_name_ea, -1);
517 collate_key_eb = g_utf8_collate_key_for_filename(caseless_name_eb, -1);
518 ret = g_strcmp0(collate_key_ea, collate_key_eb);
520 g_free(collate_key_ea);
521 g_free(collate_key_eb);
522 g_free(caseless_name_ea);
523 g_free(caseless_name_eb);
528 GList *editor_list_get(void)
530 GList *editors_list = NULL;
532 if (!editors_finished) return NULL;
534 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
535 editors_list = g_list_sort(editors_list, editor_sort);
540 /* ------------------------------ */
543 static void editor_verbose_data_free(EditorData *ed)
550 static void editor_data_free(EditorData *ed)
552 editor_verbose_data_free(ed);
553 g_free(ed->working_directory);
557 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
559 EditorData *ed = data;
561 generic_dialog_close(gd);
562 editor_verbose_data_free(ed);
563 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
566 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
568 EditorData *ed = data;
571 editor_verbose_window_progress(ed, _("stopping..."));
574 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
576 vd->gd->cancel_cb = editor_verbose_window_close;
578 spinner_set_interval(vd->spinner, -1);
579 gtk_widget_set_sensitive(vd->button_stop, FALSE);
580 gtk_widget_set_sensitive(vd->button_close, TRUE);
583 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
585 EditorVerboseData *vd;
590 vd = g_new0(EditorVerboseData, 1);
592 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
595 buf = g_strdup_printf(_("Output of %s"), text);
596 generic_dialog_add_message(vd->gd, NULL, buf, NULL, FALSE);
598 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
599 editor_verbose_window_stop, FALSE);
600 gtk_widget_set_sensitive(vd->button_stop, FALSE);
601 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
602 editor_verbose_window_close, TRUE);
603 gtk_widget_set_sensitive(vd->button_close, FALSE);
605 scrolled = gtk_scrolled_window_new(NULL, NULL);
606 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
607 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
608 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
609 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
610 gtk_widget_show(scrolled);
612 vd->text = gtk_text_view_new();
613 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
614 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
615 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
616 gtk_widget_show(vd->text);
618 hbox = gtk_hbox_new(FALSE, 0);
619 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
620 gtk_widget_show(hbox);
622 vd->progress = gtk_progress_bar_new();
623 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
624 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
625 #if GTK_CHECK_VERSION(3,0,0)
626 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), "");
627 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(vd->progress), TRUE);
629 gtk_widget_show(vd->progress);
631 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
632 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
633 gtk_widget_show(vd->spinner);
635 gtk_widget_show(vd->gd->dialog);
641 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
643 GtkTextBuffer *buffer;
646 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
647 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
648 gtk_text_buffer_insert(buffer, &iter, text, len);
651 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
657 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
660 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
663 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
665 EditorData *ed = data;
669 if (condition & G_IO_IN)
671 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
673 if (!g_utf8_validate(buf, count, NULL))
677 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
680 editor_verbose_window_fill(ed->vd, utf8, -1);
685 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
690 editor_verbose_window_fill(ed->vd, buf, count);
695 if (condition & (G_IO_ERR | G_IO_HUP))
697 g_io_channel_shutdown(source, TRUE, NULL);
711 static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
715 const gchar *p = NULL;
717 DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
719 string = g_string_new("");
721 if (type == PATH_FILE || type == PATH_FILE_URL)
723 GList *work = editor->ext_list;
732 gchar *ext = work->data;
735 if (strcmp(ext, "*") == 0 ||
736 g_ascii_strcasecmp(ext, fd->extension) == 0)
742 work2 = consider_sidecars ? fd->sidecar_files : NULL;
745 FileData *sfd = work2->data;
748 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
759 else if (type == PATH_DEST)
761 if (fd->change && fd->change->dest)
762 p = fd->change->dest;
768 string = g_string_append(string, p);
770 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
771 pathl = path_from_utf8(string->str);
772 g_string_free(string, TRUE);
774 if (pathl && !pathl[0]) /* empty string case */
780 DEBUG_2("editor_command_path_parse: return %s", pathl);
784 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
791 g_string_append_c(str, '\'');
793 g_string_append(str, "\"'");
796 for (p = s; *p != '\0'; p++)
799 g_string_append(str, "'\\''");
801 g_string_append_c(str, *p);
807 g_string_append_c(str, '\'');
809 g_string_append(str, "'\"");
816 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
818 EditorFlags flags = 0;
820 GString *result = NULL;
821 gboolean escape = FALSE;
822 gboolean single_quotes = FALSE;
823 gboolean double_quotes = FALSE;
825 DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
828 result = g_string_new("");
830 if (editor->exec == NULL || editor->exec[0] == '\0')
832 flags |= EDITOR_ERROR_EMPTY;
837 /* skip leading whitespaces if any */
838 while (g_ascii_isspace(*p)) p++;
847 if (output) result = g_string_append_c(result, *p);
851 if (!single_quotes) escape = TRUE;
852 if (output) result = g_string_append_c(result, *p);
856 if (output) result = g_string_append_c(result, *p);
857 if (!single_quotes && !double_quotes)
858 single_quotes = TRUE;
859 else if (single_quotes)
860 single_quotes = FALSE;
864 if (output) result = g_string_append_c(result, *p);
865 if (!single_quotes && !double_quotes)
866 double_quotes = TRUE;
867 else if (double_quotes)
868 double_quotes = FALSE;
870 else if (*p == '%' && p[1])
878 case 'f': /* single file */
879 case 'u': /* single url */
880 flags |= EDITOR_FOR_EACH;
881 if (flags & EDITOR_SINGLE_COMMAND)
883 flags |= EDITOR_ERROR_INCOMPATIBLE;
888 /* use the first file from the list */
891 flags |= EDITOR_ERROR_NO_FILE;
894 pathl = editor_command_path_parse((FileData *)list->data,
896 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
900 /* just testing, check also the rest of the list (like with F and U)
901 any matching file is OK */
902 GList *work = list->next;
904 while (!pathl && work)
906 FileData *fd = work->data;
907 pathl = editor_command_path_parse(fd,
909 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
917 flags |= EDITOR_ERROR_NO_FILE;
922 result = append_quoted(result, pathl, single_quotes, double_quotes);
930 flags |= EDITOR_SINGLE_COMMAND;
931 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
933 flags |= EDITOR_ERROR_INCOMPATIBLE;
945 FileData *fd = work->data;
946 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
954 if (work != list) g_string_append_c(result, ' ');
955 result = append_quoted(result, pathl, single_quotes, double_quotes);
963 flags |= EDITOR_ERROR_NO_FILE;
969 if (editor->icon && *editor->icon)
973 result = g_string_append(result, "--icon ");
974 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
981 result = append_quoted(result, editor->name, single_quotes, double_quotes);
987 result = append_quoted(result, editor->file, single_quotes, double_quotes);
991 /* %% = % escaping */
992 if (output) result = g_string_append_c(result, *p);
1000 /* deprecated according to spec, ignore */
1003 flags |= EDITOR_ERROR_SYNTAX;
1009 if (output) result = g_string_append_c(result, *p);
1014 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
1018 *output = g_string_free(result, FALSE);
1019 DEBUG_3("Editor cmd: %s", *output);
1028 g_string_free(result, TRUE);
1035 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
1037 EditorData *ed = data;
1038 g_spawn_close_pid(pid);
1041 editor_command_next_finish(ed, status);
1045 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
1048 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
1050 gint standard_output;
1051 gint standard_error;
1055 ed->flags = editor->flags;
1056 ed->flags |= editor_command_parse(editor, list, TRUE, &command);
1058 ok = !EDITOR_ERRORS(ed->flags);
1062 ok = (options->shell.path && *options->shell.path);
1063 if (!ok) log_printf("ERROR: empty shell command\n");
1067 ok = (access(options->shell.path, X_OK) == 0);
1068 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1071 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1076 gchar *working_directory;
1080 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1081 args[n++] = options->shell.path;
1082 if (options->shell.options && *options->shell.options)
1083 args[n++] = options->shell.options;
1084 args[n++] = command;
1087 if ((ed->flags & EDITOR_DEST) && fd && fd->change && fd->change->dest) /** @FIXME error handling */
1089 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1093 g_unsetenv("GEEQIE_DESTINATION");
1096 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1097 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1101 ed->vd ? &standard_output : NULL,
1102 ed->vd ? &standard_error : NULL,
1105 g_free(working_directory);
1107 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1112 g_child_watch_add(pid, editor_child_exit_cb, ed);
1122 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1123 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1129 GIOChannel *channel_output;
1130 GIOChannel *channel_error;
1132 channel_output = g_io_channel_unix_new(standard_output);
1133 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1134 g_io_channel_set_encoding(channel_output, NULL, NULL);
1136 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1137 editor_verbose_io_cb, ed, NULL);
1138 g_io_channel_unref(channel_output);
1140 channel_error = g_io_channel_unix_new(standard_error);
1141 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1142 g_io_channel_set_encoding(channel_error, NULL, NULL);
1144 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1145 editor_verbose_io_cb, ed, NULL);
1146 g_io_channel_unref(channel_error);
1152 return EDITOR_ERRORS(ed->flags);
1155 static EditorFlags editor_command_next_start(EditorData *ed)
1157 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1159 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1164 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1168 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1169 editor_verbose_window_progress(ed, fd->path);
1171 editor_verbose_window_progress(ed, _("running..."));
1175 error = editor_command_one(ed->editor, ed->list, ed);
1176 if (!error && ed->vd)
1178 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1179 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1181 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1182 editor_verbose_window_fill(ed->vd, "\n", 1);
1189 /* command was not started, call the finish immediately */
1190 return editor_command_next_finish(ed, 0);
1193 /* everything is done */
1194 return editor_command_done(ed);
1197 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1199 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1202 ed->flags |= EDITOR_ERROR_STATUS;
1204 if (ed->flags & EDITOR_FOR_EACH)
1206 /* handle the first element from the list */
1207 GList *fd_element = ed->list;
1209 ed->list = g_list_remove_link(ed->list, fd_element);
1212 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1213 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1215 filelist_free(fd_element);
1219 /* handle whole list */
1221 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1222 filelist_free(ed->list);
1228 case EDITOR_CB_SUSPEND:
1229 return EDITOR_ERRORS(ed->flags);
1230 case EDITOR_CB_SKIP:
1231 return editor_command_done(ed);
1234 return editor_command_next_start(ed);
1237 static EditorFlags editor_command_done(EditorData *ed)
1243 if (ed->count == ed->total)
1245 editor_verbose_window_progress(ed, _("done"));
1249 editor_verbose_window_progress(ed, _("stopped by user"));
1251 editor_verbose_window_enable_close(ed->vd);
1254 /* free the not-handled items */
1257 ed->flags |= EDITOR_ERROR_SKIPPED;
1258 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1259 filelist_free(ed->list);
1265 flags = EDITOR_ERRORS(ed->flags);
1267 if (!ed->vd) editor_data_free(ed);
1272 void editor_resume(gpointer ed)
1274 editor_command_next_start(ed);
1277 void editor_skip(gpointer ed)
1279 editor_command_done(ed);
1282 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1285 EditorFlags flags = editor->flags;
1287 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1289 ed = g_new0(EditorData, 1);
1290 ed->list = filelist_copy(list);
1292 ed->editor = editor;
1293 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1296 ed->working_directory = g_strdup(working_directory);
1298 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1299 flags |= EDITOR_VERBOSE;
1301 if (flags & EDITOR_VERBOSE)
1302 editor_verbose_window(ed, text);
1304 editor_command_next_start(ed);
1305 /* errors from editor_command_next_start will be handled via callback */
1306 return EDITOR_ERRORS(flags);
1309 gboolean is_valid_editor_command(const gchar *key)
1311 if (!key) return FALSE;
1312 return g_hash_table_lookup(editors, key) != NULL;
1315 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1318 EditorDescription *editor;
1319 if (!key) return EDITOR_ERROR_EMPTY;
1321 editor = g_hash_table_lookup(editors, key);
1323 if (!editor) return EDITOR_ERROR_EMPTY;
1324 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1326 error = editor_command_parse(editor, list, TRUE, NULL);
1328 if (EDITOR_ERRORS(error)) return error;
1330 error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1332 if (EDITOR_ERRORS(error))
1334 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1336 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1340 return EDITOR_ERRORS(error);
1343 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1345 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1348 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1353 if (!fd) return FALSE;
1355 list = g_list_append(NULL, fd);
1356 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1361 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1363 return start_editor_from_file_full(key, fd, NULL, NULL);
1366 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1368 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1371 gboolean editor_window_flag_set(const gchar *key)
1373 EditorDescription *editor;
1374 if (!key) return TRUE;
1376 editor = g_hash_table_lookup(editors, key);
1377 if (!editor) return TRUE;
1379 return !!(editor->flags & EDITOR_KEEP_FS);
1382 gboolean editor_is_filter(const gchar *key)
1384 EditorDescription *editor;
1385 if (!key) return TRUE;
1387 editor = g_hash_table_lookup(editors, key);
1388 if (!editor) return TRUE;
1390 return !!(editor->flags & EDITOR_DEST);
1393 gboolean editor_no_param(const gchar *key)
1395 EditorDescription *editor;
1396 if (!key) return FALSE;
1398 editor = g_hash_table_lookup(editors, key);
1399 if (!editor) return FALSE;
1401 return !!(editor->flags & EDITOR_NO_PARAM);
1404 gboolean editor_blocks_file(const gchar *key)
1406 EditorDescription *editor;
1407 if (!key) return FALSE;
1409 editor = g_hash_table_lookup(editors, key);
1410 if (!editor) return FALSE;
1412 /* Decide if the image file should be blocked during editor execution
1413 Editors like gimp can be used long time after the original file was
1414 saved, for editing unrelated files.
1415 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1418 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1421 const gchar *editor_get_error_str(EditorFlags flags)
1423 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1424 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1425 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1426 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1427 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1428 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1429 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1430 return _("Unknown error.");
1433 const gchar *editor_get_name(const gchar *key)
1435 EditorDescription *editor = g_hash_table_lookup(editors, key);
1437 if (!editor) return NULL;
1439 return editor->name;
1441 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */