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-cr2", ".cr2"},
128 {"image/x-dcraw", "%raw;.mos"},
129 {"image/x-epson-erf", "%erf"},
130 {"image/x-ico", ".ico"},
131 {"image/x-kodak-kdc", ".kdc"},
132 {"image/x-mrw", ".mrw"},
133 {"image/x-minolta-mrw", ".mrw"},
134 {"image/x-MS-bmp", ".bmp"},
135 {"image/x-nef", ".nef"},
136 {"image/x-nikon-nef", ".nef"},
137 {"image/x-panasonic-raw", ".raw"},
138 {"image/x-panasonic-rw2", ".rw2"},
139 {"image/x-pentax-pef", ".pef"},
140 {"image/x-orf", ".orf"},
141 {"image/x-olympus-orf", ".orf"},
142 {"image/x-pcx", ".pcx"},
143 {"image/xpm", ".xpm"},
144 {"image/x-png", ".png"},
145 {"image/x-portable-anymap", ".pam"},
146 {"image/x-portable-bitmap", ".pbm"},
147 {"image/x-portable-graymap", ".pgm"},
148 {"image/x-portable-pixmap", ".ppm"},
149 {"image/x-psd", ".psd"},
150 {"image/x-raf", ".raf"},
151 {"image/x-fuji-raf", ".raf"},
152 {"image/x-sgi", ".sgi"},
153 {"image/x-sony-arw", ".arw"},
154 {"image/x-sony-sr2", ".sr2"},
155 {"image/x-sony-srf", ".srf"},
156 {"image/x-tga", ".tga"},
157 {"image/x-xbitmap", ".xbm"},
158 {"image/x-xcf", ".xcf"},
159 {"image/x-xpixmap", ".xpm"},
160 {"image/x-x3f", ".x3f"},
161 {"application/x-navi-animation", ".ani"},
162 {"application/x-ptoptimizer-script", ".pto"},
168 for (i = 0; mime_types[i]; i++)
169 for (j = 0; conv_table[j][0]; j++)
170 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
171 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
176 gboolean editor_read_desktop_file(const gchar *path)
179 EditorDescription *editor;
182 const gchar *key = filename_from_path(path);
183 gchar **categories, **only_show_in, **not_show_in;
186 gboolean category_geeqie = FALSE;
190 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
192 key_file = g_key_file_new();
193 if (!g_key_file_load_from_file(key_file, path, 0, NULL))
195 g_key_file_free(key_file);
199 type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
200 if (!type || strcmp(type, "Application") != 0)
202 /* We only consider desktop entries of Application type */
203 g_key_file_free(key_file);
209 editor = g_new0(EditorDescription, 1);
211 editor->key = g_strdup(key);
212 editor->file = g_strdup(path);
214 g_hash_table_insert(editors, editor->key, editor);
216 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
217 || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
219 editor->hidden = TRUE;
222 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
225 gboolean found = FALSE;
227 for (i = 0; categories[i]; i++)
229 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
230 if (strcmp(categories[i], "Graphics") == 0)
234 if (strcmp(categories[i], "X-Geeqie") == 0)
237 category_geeqie = TRUE;
241 if (!found) editor->ignored = TRUE;
242 g_strfreev(categories);
246 editor->ignored = TRUE;
249 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
252 gboolean found = FALSE;
254 for (i = 0; only_show_in[i]; i++)
255 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
260 if (!found) editor->ignored = TRUE;
261 g_strfreev(only_show_in);
264 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
267 gboolean found = FALSE;
269 for (i = 0; not_show_in[i]; i++)
270 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
275 if (found) editor->ignored = TRUE;
276 g_strfreev(not_show_in);
280 try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
281 if (try_exec && !editor->hidden && !editor->ignored)
283 gchar *try_exec_res = g_find_program_in_path(try_exec);
284 if (!try_exec_res) editor->hidden = TRUE;
285 g_free(try_exec_res);
291 /* ignored editors will be deleted, no need to parse the rest */
292 g_key_file_free(key_file);
296 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
297 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
299 /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
300 if (editor->icon && !g_path_is_absolute(editor->icon))
302 gchar *ext = strrchr(editor->icon, '.');
304 if (ext && strlen(ext) == 4 &&
305 (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
307 log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
308 editor->file, editor->icon);
314 if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon))
316 g_free(editor->icon);
320 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
322 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
323 if (!editor->menu_path) editor->menu_path = g_strdup("PluginsMenu");
325 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
327 editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
329 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
331 editor->ext_list = filter_to_list(extensions);
334 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
337 editor->ext_list = editor_mime_types_to_extensions(mime_types);
338 g_strfreev(mime_types);
339 if (!editor->ext_list) editor->hidden = TRUE;
343 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
344 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
345 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
346 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
347 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
349 editor->flags |= editor_command_parse(editor, NULL, FALSE, NULL);
351 if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
353 g_key_file_free(key_file);
355 if (editor->ignored) return TRUE;
357 work = options->disabled_plugins;
362 if (g_strcmp0(path, work->data) == 0)
370 editor->disabled = disabled;
372 gtk_list_store_append(desktop_file_list, &iter);
373 gtk_list_store_set(desktop_file_list, &iter,
374 DESKTOP_FILE_COLUMN_KEY, key,
375 DESKTOP_FILE_COLUMN_DISABLED, editor->disabled,
376 DESKTOP_FILE_COLUMN_NAME, editor->name,
377 DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden ? _("yes") : _("no"),
378 DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
379 DESKTOP_FILE_COLUMN_PATH, path, -1);
384 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
386 EditorDescription *editor = value;
387 return editor->hidden || editor->ignored;
390 void editor_table_finish(void)
392 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
393 editors_finished = TRUE;
396 void editor_table_clear(void)
398 if (desktop_file_list)
400 gtk_list_store_clear(desktop_file_list);
404 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);
408 g_hash_table_destroy(editors);
410 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
411 editors_finished = FALSE;
414 static GList *editor_add_desktop_dir(GList *list, const gchar *path)
420 pathl = path_from_utf8(path);
428 while ((dir = readdir(dp)) != NULL)
430 gchar *namel = dir->d_name;
432 if (g_str_has_suffix(namel, ".desktop"))
434 gchar *name = path_to_utf8(namel);
435 gchar *dpath = g_build_filename(path, name, NULL);
436 list = g_list_prepend(list, dpath);
444 GList *editor_get_desktop_files(void)
447 gchar *xdg_data_dirs;
453 xdg_data_dirs = getenv("XDG_DATA_DIRS");
454 if (xdg_data_dirs && xdg_data_dirs[0])
455 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
457 xdg_data_dirs = g_strdup("/usr/share");
459 all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
461 g_free(xdg_data_dirs);
463 split_dirs = g_strsplit(all_dirs, ":", 0);
467 for (i = 0; split_dirs[i]; i++);
468 for (--i; i >= 0; i--)
470 path = g_build_filename(split_dirs[i], "applications", NULL);
471 list = editor_add_desktop_dir(list, path);
475 g_strfreev(split_dirs);
479 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
481 GList **listp = data;
482 EditorDescription *editor = value;
484 /* do not show the special commands in any list, they are called explicitly */
485 if (strcmp(editor->key, CMD_COPY) == 0 ||
486 strcmp(editor->key, CMD_MOVE) == 0 ||
487 strcmp(editor->key, CMD_RENAME) == 0 ||
488 strcmp(editor->key, CMD_DELETE) == 0 ||
489 strcmp(editor->key, CMD_FOLDER) == 0) return;
491 if (editor->disabled)
496 *listp = g_list_prepend(*listp, editor);
499 static gint editor_sort(gconstpointer a, gconstpointer b)
501 const EditorDescription *ea = a;
502 const EditorDescription *eb = b;
505 ret = strcmp(ea->menu_path, eb->menu_path);
506 if (ret != 0) return ret;
508 return g_utf8_collate(ea->name, eb->name);
511 GList *editor_list_get(void)
513 GList *editors_list = NULL;
515 if (!editors_finished) return NULL;
517 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
518 editors_list = g_list_sort(editors_list, editor_sort);
523 /* ------------------------------ */
526 static void editor_verbose_data_free(EditorData *ed)
533 static void editor_data_free(EditorData *ed)
535 editor_verbose_data_free(ed);
536 g_free(ed->working_directory);
540 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
542 EditorData *ed = data;
544 generic_dialog_close(gd);
545 editor_verbose_data_free(ed);
546 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
549 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
551 EditorData *ed = data;
554 editor_verbose_window_progress(ed, _("stopping..."));
557 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
559 vd->gd->cancel_cb = editor_verbose_window_close;
561 spinner_set_interval(vd->spinner, -1);
562 gtk_widget_set_sensitive(vd->button_stop, FALSE);
563 gtk_widget_set_sensitive(vd->button_close, TRUE);
566 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
568 EditorVerboseData *vd;
573 vd = g_new0(EditorVerboseData, 1);
575 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
578 buf = g_strdup_printf(_("Output of %s"), text);
579 generic_dialog_add_message(vd->gd, NULL, buf, NULL, FALSE);
581 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
582 editor_verbose_window_stop, FALSE);
583 gtk_widget_set_sensitive(vd->button_stop, FALSE);
584 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
585 editor_verbose_window_close, TRUE);
586 gtk_widget_set_sensitive(vd->button_close, FALSE);
588 scrolled = gtk_scrolled_window_new(NULL, NULL);
589 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
590 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
591 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
592 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
593 gtk_widget_show(scrolled);
595 vd->text = gtk_text_view_new();
596 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
597 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
598 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
599 gtk_widget_show(vd->text);
601 hbox = gtk_hbox_new(FALSE, 0);
602 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
603 gtk_widget_show(hbox);
605 vd->progress = gtk_progress_bar_new();
606 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
607 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
608 #if GTK_CHECK_VERSION(3,0,0)
609 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), "");
610 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(vd->progress), TRUE);
612 gtk_widget_show(vd->progress);
614 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
615 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
616 gtk_widget_show(vd->spinner);
618 gtk_widget_show(vd->gd->dialog);
624 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
626 GtkTextBuffer *buffer;
629 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
630 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
631 gtk_text_buffer_insert(buffer, &iter, text, len);
634 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
640 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
643 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
646 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
648 EditorData *ed = data;
652 if (condition & G_IO_IN)
654 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
656 if (!g_utf8_validate(buf, count, NULL))
660 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
663 editor_verbose_window_fill(ed->vd, utf8, -1);
668 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
673 editor_verbose_window_fill(ed->vd, buf, count);
678 if (condition & (G_IO_ERR | G_IO_HUP))
680 g_io_channel_shutdown(source, TRUE, NULL);
694 static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
698 const gchar *p = NULL;
700 DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
702 string = g_string_new("");
704 if (type == PATH_FILE || type == PATH_FILE_URL)
706 GList *work = editor->ext_list;
715 gchar *ext = work->data;
718 if (strcmp(ext, "*") == 0 ||
719 g_ascii_strcasecmp(ext, fd->extension) == 0)
725 work2 = consider_sidecars ? fd->sidecar_files : NULL;
728 FileData *sfd = work2->data;
731 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
742 else if (type == PATH_DEST)
744 if (fd->change && fd->change->dest)
745 p = fd->change->dest;
751 string = g_string_append(string, p);
753 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
754 pathl = path_from_utf8(string->str);
755 g_string_free(string, TRUE);
757 if (pathl && !pathl[0]) /* empty string case */
763 DEBUG_2("editor_command_path_parse: return %s", pathl);
767 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
774 g_string_append_c(str, '\'');
776 g_string_append(str, "\"'");
779 for (p = s; *p != '\0'; p++)
782 g_string_append(str, "'\\''");
784 g_string_append_c(str, *p);
790 g_string_append_c(str, '\'');
792 g_string_append(str, "'\"");
799 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
801 EditorFlags flags = 0;
803 GString *result = NULL;
804 gboolean escape = FALSE;
805 gboolean single_quotes = FALSE;
806 gboolean double_quotes = FALSE;
808 DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
811 result = g_string_new("");
813 if (editor->exec == NULL || editor->exec[0] == '\0')
815 flags |= EDITOR_ERROR_EMPTY;
820 /* skip leading whitespaces if any */
821 while (g_ascii_isspace(*p)) p++;
830 if (output) result = g_string_append_c(result, *p);
834 if (!single_quotes) escape = TRUE;
835 if (output) result = g_string_append_c(result, *p);
839 if (output) result = g_string_append_c(result, *p);
840 if (!single_quotes && !double_quotes)
841 single_quotes = TRUE;
842 else if (single_quotes)
843 single_quotes = FALSE;
847 if (output) result = g_string_append_c(result, *p);
848 if (!single_quotes && !double_quotes)
849 double_quotes = TRUE;
850 else if (double_quotes)
851 double_quotes = FALSE;
853 else if (*p == '%' && p[1])
861 case 'f': /* single file */
862 case 'u': /* single url */
863 flags |= EDITOR_FOR_EACH;
864 if (flags & EDITOR_SINGLE_COMMAND)
866 flags |= EDITOR_ERROR_INCOMPATIBLE;
871 /* use the first file from the list */
874 flags |= EDITOR_ERROR_NO_FILE;
877 pathl = editor_command_path_parse((FileData *)list->data,
879 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
883 /* just testing, check also the rest of the list (like with F and U)
884 any matching file is OK */
885 GList *work = list->next;
887 while (!pathl && work)
889 FileData *fd = work->data;
890 pathl = editor_command_path_parse(fd,
892 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
900 flags |= EDITOR_ERROR_NO_FILE;
905 result = append_quoted(result, pathl, single_quotes, double_quotes);
913 flags |= EDITOR_SINGLE_COMMAND;
914 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
916 flags |= EDITOR_ERROR_INCOMPATIBLE;
928 FileData *fd = work->data;
929 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
937 if (work != list) g_string_append_c(result, ' ');
938 result = append_quoted(result, pathl, single_quotes, double_quotes);
946 flags |= EDITOR_ERROR_NO_FILE;
952 if (editor->icon && *editor->icon)
956 result = g_string_append(result, "--icon ");
957 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
964 result = append_quoted(result, editor->name, single_quotes, double_quotes);
970 result = append_quoted(result, editor->file, single_quotes, double_quotes);
974 /* %% = % escaping */
975 if (output) result = g_string_append_c(result, *p);
983 /* deprecated according to spec, ignore */
986 flags |= EDITOR_ERROR_SYNTAX;
992 if (output) result = g_string_append_c(result, *p);
997 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
1001 *output = g_string_free(result, FALSE);
1002 DEBUG_3("Editor cmd: %s", *output);
1011 g_string_free(result, TRUE);
1018 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
1020 EditorData *ed = data;
1021 g_spawn_close_pid(pid);
1024 editor_command_next_finish(ed, status);
1028 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
1031 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
1033 gint standard_output;
1034 gint standard_error;
1038 ed->flags = editor->flags;
1039 ed->flags |= editor_command_parse(editor, list, TRUE, &command);
1041 ok = !EDITOR_ERRORS(ed->flags);
1045 ok = (options->shell.path && *options->shell.path);
1046 if (!ok) log_printf("ERROR: empty shell command\n");
1050 ok = (access(options->shell.path, X_OK) == 0);
1051 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1054 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1059 gchar *working_directory;
1063 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1064 args[n++] = options->shell.path;
1065 if (options->shell.options && *options->shell.options)
1066 args[n++] = options->shell.options;
1067 args[n++] = command;
1070 if ((ed->flags & EDITOR_DEST) && fd && fd->change && fd->change->dest) /* FIXME: error handling */
1072 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1076 g_unsetenv("GEEQIE_DESTINATION");
1079 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1080 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1084 ed->vd ? &standard_output : NULL,
1085 ed->vd ? &standard_error : NULL,
1088 g_free(working_directory);
1090 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1095 g_child_watch_add(pid, editor_child_exit_cb, ed);
1105 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1106 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1112 GIOChannel *channel_output;
1113 GIOChannel *channel_error;
1115 channel_output = g_io_channel_unix_new(standard_output);
1116 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1117 g_io_channel_set_encoding(channel_output, NULL, NULL);
1119 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1120 editor_verbose_io_cb, ed, NULL);
1121 g_io_channel_unref(channel_output);
1123 channel_error = g_io_channel_unix_new(standard_error);
1124 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1125 g_io_channel_set_encoding(channel_error, NULL, NULL);
1127 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1128 editor_verbose_io_cb, ed, NULL);
1129 g_io_channel_unref(channel_error);
1135 return EDITOR_ERRORS(ed->flags);
1138 static EditorFlags editor_command_next_start(EditorData *ed)
1140 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1142 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1147 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1151 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1152 editor_verbose_window_progress(ed, fd->path);
1154 editor_verbose_window_progress(ed, _("running..."));
1158 error = editor_command_one(ed->editor, ed->list, ed);
1159 if (!error && ed->vd)
1161 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1162 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1164 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1165 editor_verbose_window_fill(ed->vd, "\n", 1);
1172 /* command was not started, call the finish immediately */
1173 return editor_command_next_finish(ed, 0);
1176 /* everything is done */
1177 return editor_command_done(ed);
1180 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1182 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1185 ed->flags |= EDITOR_ERROR_STATUS;
1187 if (ed->flags & EDITOR_FOR_EACH)
1189 /* handle the first element from the list */
1190 GList *fd_element = ed->list;
1192 ed->list = g_list_remove_link(ed->list, fd_element);
1195 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1196 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1198 filelist_free(fd_element);
1202 /* handle whole list */
1204 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1205 filelist_free(ed->list);
1211 case EDITOR_CB_SUSPEND:
1212 return EDITOR_ERRORS(ed->flags);
1213 case EDITOR_CB_SKIP:
1214 return editor_command_done(ed);
1217 return editor_command_next_start(ed);
1220 static EditorFlags editor_command_done(EditorData *ed)
1226 if (ed->count == ed->total)
1228 editor_verbose_window_progress(ed, _("done"));
1232 editor_verbose_window_progress(ed, _("stopped by user"));
1234 editor_verbose_window_enable_close(ed->vd);
1237 /* free the not-handled items */
1240 ed->flags |= EDITOR_ERROR_SKIPPED;
1241 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1242 filelist_free(ed->list);
1248 flags = EDITOR_ERRORS(ed->flags);
1250 if (!ed->vd) editor_data_free(ed);
1255 void editor_resume(gpointer ed)
1257 editor_command_next_start(ed);
1260 void editor_skip(gpointer ed)
1262 editor_command_done(ed);
1265 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1268 EditorFlags flags = editor->flags;
1270 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1272 ed = g_new0(EditorData, 1);
1273 ed->list = filelist_copy(list);
1275 ed->editor = editor;
1276 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1279 ed->working_directory = g_strdup(working_directory);
1281 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1282 flags |= EDITOR_VERBOSE;
1284 if (flags & EDITOR_VERBOSE)
1285 editor_verbose_window(ed, text);
1287 editor_command_next_start(ed);
1288 /* errors from editor_command_next_start will be handled via callback */
1289 return EDITOR_ERRORS(flags);
1292 gboolean is_valid_editor_command(const gchar *key)
1294 if (!key) return FALSE;
1295 return g_hash_table_lookup(editors, key) != NULL;
1298 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1301 EditorDescription *editor;
1302 if (!key) return EDITOR_ERROR_EMPTY;
1304 editor = g_hash_table_lookup(editors, key);
1306 if (!editor) return EDITOR_ERROR_EMPTY;
1307 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1309 error = editor_command_parse(editor, list, TRUE, NULL);
1311 if (EDITOR_ERRORS(error)) return error;
1313 error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1315 if (EDITOR_ERRORS(error))
1317 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1319 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1323 return EDITOR_ERRORS(error);
1326 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1328 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1331 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1336 if (!fd) return FALSE;
1338 list = g_list_append(NULL, fd);
1339 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1344 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1346 return start_editor_from_file_full(key, fd, NULL, NULL);
1349 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1351 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1354 gboolean editor_window_flag_set(const gchar *key)
1356 EditorDescription *editor;
1357 if (!key) return TRUE;
1359 editor = g_hash_table_lookup(editors, key);
1360 if (!editor) return TRUE;
1362 return !!(editor->flags & EDITOR_KEEP_FS);
1365 gboolean editor_is_filter(const gchar *key)
1367 EditorDescription *editor;
1368 if (!key) return TRUE;
1370 editor = g_hash_table_lookup(editors, key);
1371 if (!editor) return TRUE;
1373 return !!(editor->flags & EDITOR_DEST);
1376 gboolean editor_no_param(const gchar *key)
1378 EditorDescription *editor;
1379 if (!key) return FALSE;
1381 editor = g_hash_table_lookup(editors, key);
1382 if (!editor) return FALSE;
1384 return !!(editor->flags & EDITOR_NO_PARAM);
1387 gboolean editor_blocks_file(const gchar *key)
1389 EditorDescription *editor;
1390 if (!key) return FALSE;
1392 editor = g_hash_table_lookup(editors, key);
1393 if (!editor) return FALSE;
1395 /* Decide if the image file should be blocked during editor execution
1396 Editors like gimp can be used long time after the original file was
1397 saved, for editing unrelated files.
1398 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1401 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1404 const gchar *editor_get_error_str(EditorFlags flags)
1406 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1407 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1408 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1409 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1410 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1411 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1412 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1413 return _("Unknown error.");
1416 const gchar *editor_get_name(const gchar *key)
1418 EditorDescription *editor = g_hash_table_lookup(editors, key);
1420 if (!editor) return NULL;
1422 return editor->name;
1424 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */