2 * Copyright (C) 2006 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include "filefilter.h"
28 #include "pixbuf_util.h"
29 #include "ui_fileops.h"
30 #include "ui_spinner.h"
31 #include "ui_utildlg.h"
37 #define EDITOR_WINDOW_WIDTH 500
38 #define EDITOR_WINDOW_HEIGHT 300
42 typedef struct _EditorVerboseData EditorVerboseData;
43 struct _EditorVerboseData {
45 GtkWidget *button_close;
46 GtkWidget *button_stop;
52 typedef struct _EditorData EditorData;
60 EditorVerboseData *vd;
61 EditorCallback callback;
63 const EditorDescription *editor;
64 gchar *working_directory; /* fallback if no files are given (editor_no_param) */
68 static void editor_verbose_window_progress(EditorData *ed, const gchar *text);
69 static EditorFlags editor_command_next_start(EditorData *ed);
70 static EditorFlags editor_command_next_finish(EditorData *ed, gint status);
71 static EditorFlags editor_command_done(EditorData *ed);
74 *-----------------------------------------------------------------------------
75 * external editor routines
76 *-----------------------------------------------------------------------------
79 GHashTable *editors = NULL;
80 GtkListStore *desktop_file_list;
81 gboolean editors_finished = FALSE;
83 #ifdef G_KEY_FILE_DESKTOP_GROUP
84 #define DESKTOP_GROUP G_KEY_FILE_DESKTOP_GROUP
86 #define DESKTOP_GROUP "Desktop Entry"
89 void editor_description_free(EditorDescription *editor)
97 g_free(editor->menu_path);
98 g_free(editor->hotkey);
99 g_free(editor->comment);
100 string_list_free(editor->ext_list);
101 g_free(editor->file);
105 static GList *editor_mime_types_to_extensions(gchar **mime_types)
107 /* FIXME: this should be rewritten to use the shared mime database, as soon as we switch to gio */
109 static const gchar *conv_table[][2] = {
110 {"application/x-ufraw", ".ufraw"},
112 {"image/bmp", ".bmp"},
113 {"image/gif", ".gif"},
114 {"image/jpeg", ".jpeg;.jpg"},
115 {"image/jpg", ".jpg;.jpeg"},
116 {"image/pcx", ".pcx"},
117 {"image/png", ".png"},
118 {"image/svg", ".svg"},
119 {"image/svg+xml", ".svg"},
120 {"image/svg+xml-compressed", ".svg"},
121 {"image/tiff", ".tiff;.tif"},
122 {"image/x-bmp", ".bmp"},
123 {"image/x-canon-crw", ".crw"},
124 {"image/x-cr2", ".cr2"},
125 {"image/x-dcraw", "%raw"},
126 {"image/x-ico", ".ico"},
127 {"image/x-mrw", ".mrw"},
128 {"image/x-MS-bmp", ".bmp"},
129 {"image/x-nef", ".nef"},
130 {"image/x-orf", ".orf"},
131 {"image/x-pcx", ".pcx"},
132 {"image/xpm", ".xpm"},
133 {"image/x-png", ".png"},
134 {"image/x-portable-anymap", ".pam"},
135 {"image/x-portable-bitmap", ".pbm"},
136 {"image/x-portable-graymap", ".pgm"},
137 {"image/x-portable-pixmap", ".ppm"},
138 {"image/x-psd", ".psd"},
139 {"image/x-raf", ".raf"},
140 {"image/x-sgi", ".sgi"},
141 {"image/x-tga", ".tga"},
142 {"image/x-xbitmap", ".xbm"},
143 {"image/x-xcf", ".xcf"},
144 {"image/x-xpixmap", ".xpm"},
145 {"image/x-x3f", ".x3f"},
146 {"application/x-ptoptimizer-script", ".pto"},
152 for (i = 0; mime_types[i]; i++)
153 for (j = 0; conv_table[j][0]; j++)
154 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
155 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
160 gboolean editor_read_desktop_file(const gchar *path)
163 EditorDescription *editor;
166 const gchar *key = filename_from_path(path);
167 gchar **categories, **only_show_in, **not_show_in;
170 gboolean category_geeqie = FALSE;
172 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
174 key_file = g_key_file_new();
175 if (!g_key_file_load_from_file(key_file, path, 0, NULL))
177 g_key_file_free(key_file);
181 type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
182 if (!type || strcmp(type, "Application") != 0)
184 /* We only consider desktop entries of Application type */
185 g_key_file_free(key_file);
191 editor = g_new0(EditorDescription, 1);
193 editor->key = g_strdup(key);
194 editor->file = g_strdup(path);
196 g_hash_table_insert(editors, editor->key, editor);
198 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
199 || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
201 editor->hidden = TRUE;
204 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
207 gboolean found = FALSE;
209 for (i = 0; categories[i]; i++)
211 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
212 if (strcmp(categories[i], "Graphics") == 0)
216 if (strcmp(categories[i], "X-Geeqie") == 0)
219 category_geeqie = TRUE;
223 if (!found) editor->ignored = TRUE;
224 g_strfreev(categories);
228 editor->ignored = TRUE;
231 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
234 gboolean found = FALSE;
236 for (i = 0; only_show_in[i]; i++)
237 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
242 if (!found) editor->ignored = TRUE;
243 g_strfreev(only_show_in);
246 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
249 gboolean found = FALSE;
251 for (i = 0; not_show_in[i]; i++)
252 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
257 if (found) editor->ignored = TRUE;
258 g_strfreev(not_show_in);
262 try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
263 if (try_exec && !editor->hidden && !editor->ignored)
265 gchar *try_exec_res = g_find_program_in_path(try_exec);
266 if (!try_exec_res) editor->hidden = TRUE;
267 g_free(try_exec_res);
273 /* ignored editors will be deleted, no need to parse the rest */
274 g_key_file_free(key_file);
278 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
279 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
281 /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
282 if (editor->icon && !g_path_is_absolute(editor->icon))
284 gchar *ext = strrchr(editor->icon, '.');
286 if (ext && strlen(ext) == 4 &&
287 (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
289 log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
290 editor->file, editor->icon);
296 if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon))
298 g_free(editor->icon);
302 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
304 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
305 if (!editor->menu_path) editor->menu_path = g_strdup("EditMenu/ExternalMenu");
307 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
309 editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
311 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
313 editor->ext_list = filter_to_list(extensions);
316 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
319 editor->ext_list = editor_mime_types_to_extensions(mime_types);
320 g_strfreev(mime_types);
321 if (!editor->ext_list) editor->hidden = TRUE;
325 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
326 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
327 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
328 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
329 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
331 editor->flags |= editor_command_parse(editor, NULL, FALSE, NULL);
333 if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
335 g_key_file_free(key_file);
337 if (editor->ignored) return TRUE;
339 gtk_list_store_append(desktop_file_list, &iter);
340 gtk_list_store_set(desktop_file_list, &iter,
341 DESKTOP_FILE_COLUMN_KEY, key,
342 DESKTOP_FILE_COLUMN_NAME, editor->name,
343 DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden ? _("yes") : _("no"),
344 DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
345 DESKTOP_FILE_COLUMN_PATH, path, -1);
350 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
352 EditorDescription *editor = value;
353 return editor->hidden || editor->ignored;
356 void editor_table_finish(void)
358 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
359 editors_finished = TRUE;
362 void editor_table_clear(void)
364 if (desktop_file_list)
366 gtk_list_store_clear(desktop_file_list);
370 desktop_file_list = gtk_list_store_new(DESKTOP_FILE_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING);
374 g_hash_table_destroy(editors);
376 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
377 editors_finished = FALSE;
380 static GList *editor_add_desktop_dir(GList *list, const gchar *path)
386 pathl = path_from_utf8(path);
394 while ((dir = readdir(dp)) != NULL)
396 gchar *namel = dir->d_name;
398 if (g_str_has_suffix(namel, ".desktop"))
400 gchar *name = path_to_utf8(namel);
401 gchar *dpath = g_build_filename(path, name, NULL);
402 list = g_list_prepend(list, dpath);
410 GList *editor_get_desktop_files(void)
413 gchar *xdg_data_dirs;
419 xdg_data_dirs = getenv("XDG_DATA_DIRS");
420 if (xdg_data_dirs && xdg_data_dirs[0])
421 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
423 xdg_data_dirs = g_strdup("/usr/share");
425 all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
427 g_free(xdg_data_dirs);
429 split_dirs = g_strsplit(all_dirs, ":", 0);
433 for (i = 0; split_dirs[i]; i++);
434 for (--i; i >= 0; i--)
436 path = g_build_filename(split_dirs[i], "applications", NULL);
437 list = editor_add_desktop_dir(list, path);
441 g_strfreev(split_dirs);
445 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
447 GList **listp = data;
448 EditorDescription *editor = value;
450 /* do not show the special commands in any list, they are called explicitly */
451 if (strcmp(editor->key, CMD_COPY) == 0 ||
452 strcmp(editor->key, CMD_MOVE) == 0 ||
453 strcmp(editor->key, CMD_RENAME) == 0 ||
454 strcmp(editor->key, CMD_DELETE) == 0 ||
455 strcmp(editor->key, CMD_FOLDER) == 0) return;
457 *listp = g_list_prepend(*listp, editor);
460 static gint editor_sort(gconstpointer a, gconstpointer b)
462 const EditorDescription *ea = a;
463 const EditorDescription *eb = b;
466 ret = strcmp(ea->menu_path, eb->menu_path);
467 if (ret != 0) return ret;
469 return g_utf8_collate(ea->name, eb->name);
472 GList *editor_list_get(void)
474 GList *editors_list = NULL;
476 if (!editors_finished) return NULL;
478 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
479 editors_list = g_list_sort(editors_list, editor_sort);
484 /* ------------------------------ */
487 static void editor_verbose_data_free(EditorData *ed)
494 static void editor_data_free(EditorData *ed)
496 editor_verbose_data_free(ed);
497 g_free(ed->working_directory);
501 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
503 EditorData *ed = data;
505 generic_dialog_close(gd);
506 editor_verbose_data_free(ed);
507 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
510 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
512 EditorData *ed = data;
515 editor_verbose_window_progress(ed, _("stopping..."));
518 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
520 vd->gd->cancel_cb = editor_verbose_window_close;
522 spinner_set_interval(vd->spinner, -1);
523 gtk_widget_set_sensitive(vd->button_stop, FALSE);
524 gtk_widget_set_sensitive(vd->button_close, TRUE);
527 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
529 EditorVerboseData *vd;
534 vd = g_new0(EditorVerboseData, 1);
536 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
539 buf = g_strdup_printf(_("Output of %s"), text);
540 generic_dialog_add_message(vd->gd, NULL, buf, NULL);
542 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
543 editor_verbose_window_stop, FALSE);
544 gtk_widget_set_sensitive(vd->button_stop, FALSE);
545 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
546 editor_verbose_window_close, TRUE);
547 gtk_widget_set_sensitive(vd->button_close, FALSE);
549 scrolled = gtk_scrolled_window_new(NULL, NULL);
550 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
551 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
552 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
553 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
554 gtk_widget_show(scrolled);
556 vd->text = gtk_text_view_new();
557 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
558 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
559 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
560 gtk_widget_show(vd->text);
562 hbox = gtk_hbox_new(FALSE, 0);
563 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
564 gtk_widget_show(hbox);
566 vd->progress = gtk_progress_bar_new();
567 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
568 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
569 gtk_widget_show(vd->progress);
571 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
572 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
573 gtk_widget_show(vd->spinner);
575 gtk_widget_show(vd->gd->dialog);
581 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
583 GtkTextBuffer *buffer;
586 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
587 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
588 gtk_text_buffer_insert(buffer, &iter, text, len);
591 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
597 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
600 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
603 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
605 EditorData *ed = data;
609 if (condition & G_IO_IN)
611 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
613 if (!g_utf8_validate(buf, count, NULL))
617 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
620 editor_verbose_window_fill(ed->vd, utf8, -1);
625 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
630 editor_verbose_window_fill(ed->vd, buf, count);
635 if (condition & (G_IO_ERR | G_IO_HUP))
637 g_io_channel_shutdown(source, TRUE, NULL);
651 static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
655 const gchar *p = NULL;
657 DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
659 string = g_string_new("");
661 if (type == PATH_FILE || type == PATH_FILE_URL)
663 GList *work = editor->ext_list;
672 gchar *ext = work->data;
675 if (strcmp(ext, "*") == 0 ||
676 g_ascii_strcasecmp(ext, fd->extension) == 0)
682 work2 = consider_sidecars ? fd->sidecar_files : NULL;
685 FileData *sfd = work2->data;
688 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
699 else if (type == PATH_DEST)
701 if (fd->change && fd->change->dest)
702 p = fd->change->dest;
708 string = g_string_append(string, p);
710 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
711 pathl = path_from_utf8(string->str);
712 g_string_free(string, TRUE);
714 if (pathl && !pathl[0]) /* empty string case */
720 DEBUG_2("editor_command_path_parse: return %s", pathl);
724 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
731 g_string_append_c(str, '\'');
733 g_string_append(str, "\"'");
736 for (p = s; *p != '\0'; p++)
739 g_string_append(str, "'\\''");
741 g_string_append_c(str, *p);
747 g_string_append_c(str, '\'');
749 g_string_append(str, "'\"");
756 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
758 EditorFlags flags = 0;
760 GString *result = NULL;
761 gboolean escape = FALSE;
762 gboolean single_quotes = FALSE;
763 gboolean double_quotes = FALSE;
765 DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
768 result = g_string_new("");
770 if (editor->exec == NULL || editor->exec[0] == '\0')
772 flags |= EDITOR_ERROR_EMPTY;
777 /* skip leading whitespaces if any */
778 while (g_ascii_isspace(*p)) p++;
787 if (output) result = g_string_append_c(result, *p);
791 if (!single_quotes) escape = TRUE;
792 if (output) result = g_string_append_c(result, *p);
796 if (output) result = g_string_append_c(result, *p);
797 if (!single_quotes && !double_quotes)
798 single_quotes = TRUE;
799 else if (single_quotes)
800 single_quotes = FALSE;
804 if (output) result = g_string_append_c(result, *p);
805 if (!single_quotes && !double_quotes)
806 double_quotes = TRUE;
807 else if (double_quotes)
808 double_quotes = FALSE;
810 else if (*p == '%' && p[1])
818 case 'f': /* single file */
819 case 'u': /* single url */
820 flags |= EDITOR_FOR_EACH;
821 if (flags & EDITOR_SINGLE_COMMAND)
823 flags |= EDITOR_ERROR_INCOMPATIBLE;
828 /* use the first file from the list */
831 flags |= EDITOR_ERROR_NO_FILE;
834 pathl = editor_command_path_parse((FileData *)list->data,
836 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
840 /* just testing, check also the rest of the list (like with F and U)
841 any matching file is OK */
842 GList *work = list->next;
844 while (!pathl && work)
846 FileData *fd = work->data;
847 pathl = editor_command_path_parse(fd,
849 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
857 flags |= EDITOR_ERROR_NO_FILE;
862 result = append_quoted(result, pathl, single_quotes, double_quotes);
870 flags |= EDITOR_SINGLE_COMMAND;
871 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
873 flags |= EDITOR_ERROR_INCOMPATIBLE;
885 FileData *fd = work->data;
886 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
894 if (work != list) g_string_append_c(result, ' ');
895 result = append_quoted(result, pathl, single_quotes, double_quotes);
903 flags |= EDITOR_ERROR_NO_FILE;
909 if (editor->icon && *editor->icon)
913 result = g_string_append(result, "--icon ");
914 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
921 result = append_quoted(result, editor->name, single_quotes, double_quotes);
927 result = append_quoted(result, editor->file, single_quotes, double_quotes);
931 /* %% = % escaping */
932 if (output) result = g_string_append_c(result, *p);
940 /* deprecated according to spec, ignore */
943 flags |= EDITOR_ERROR_SYNTAX;
949 if (output) result = g_string_append_c(result, *p);
954 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
958 *output = g_string_free(result, FALSE);
959 DEBUG_3("Editor cmd: %s", *output);
968 g_string_free(result, TRUE);
975 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
977 EditorData *ed = data;
978 g_spawn_close_pid(pid);
981 editor_command_next_finish(ed, status);
985 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
988 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
990 gint standard_output;
995 ed->flags = editor->flags;
996 ed->flags |= editor_command_parse(editor, list, TRUE, &command);
998 ok = !EDITOR_ERRORS(ed->flags);
1002 ok = (options->shell.path && *options->shell.path);
1003 if (!ok) log_printf("ERROR: empty shell command\n");
1007 ok = (access(options->shell.path, X_OK) == 0);
1008 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1011 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1016 gchar *working_directory;
1020 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1021 args[n++] = options->shell.path;
1022 if (options->shell.options && *options->shell.options)
1023 args[n++] = options->shell.options;
1024 args[n++] = command;
1027 if ((ed->flags & EDITOR_DEST) && fd->change && fd->change->dest) /* FIXME: error handling */
1029 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1033 g_unsetenv("GEEQIE_DESTINATION");
1036 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1037 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1041 ed->vd ? &standard_output : NULL,
1042 ed->vd ? &standard_error : NULL,
1045 g_free(working_directory);
1047 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1052 g_child_watch_add(pid, editor_child_exit_cb, ed);
1062 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1063 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1069 GIOChannel *channel_output;
1070 GIOChannel *channel_error;
1072 channel_output = g_io_channel_unix_new(standard_output);
1073 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1074 g_io_channel_set_encoding(channel_output, NULL, NULL);
1076 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1077 editor_verbose_io_cb, ed, NULL);
1078 g_io_channel_unref(channel_output);
1080 channel_error = g_io_channel_unix_new(standard_error);
1081 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1082 g_io_channel_set_encoding(channel_error, NULL, NULL);
1084 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1085 editor_verbose_io_cb, ed, NULL);
1086 g_io_channel_unref(channel_error);
1092 return EDITOR_ERRORS(ed->flags);
1095 static EditorFlags editor_command_next_start(EditorData *ed)
1097 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1099 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1104 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1108 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1109 editor_verbose_window_progress(ed, fd->path);
1111 editor_verbose_window_progress(ed, _("running..."));
1115 error = editor_command_one(ed->editor, ed->list, ed);
1116 if (!error && ed->vd)
1118 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1119 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1121 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1122 editor_verbose_window_fill(ed->vd, "\n", 1);
1129 /* command was not started, call the finish immediately */
1130 return editor_command_next_finish(ed, 0);
1133 /* everything is done */
1134 return editor_command_done(ed);
1137 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1139 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1142 ed->flags |= EDITOR_ERROR_STATUS;
1144 if (ed->flags & EDITOR_FOR_EACH)
1146 /* handle the first element from the list */
1147 GList *fd_element = ed->list;
1149 ed->list = g_list_remove_link(ed->list, fd_element);
1152 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1153 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1155 filelist_free(fd_element);
1159 /* handle whole list */
1161 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1162 filelist_free(ed->list);
1168 case EDITOR_CB_SUSPEND:
1169 return EDITOR_ERRORS(ed->flags);
1170 case EDITOR_CB_SKIP:
1171 return editor_command_done(ed);
1174 return editor_command_next_start(ed);
1177 static EditorFlags editor_command_done(EditorData *ed)
1183 if (ed->count == ed->total)
1185 editor_verbose_window_progress(ed, _("done"));
1189 editor_verbose_window_progress(ed, _("stopped by user"));
1191 editor_verbose_window_enable_close(ed->vd);
1194 /* free the not-handled items */
1197 ed->flags |= EDITOR_ERROR_SKIPPED;
1198 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1199 filelist_free(ed->list);
1205 flags = EDITOR_ERRORS(ed->flags);
1207 if (!ed->vd) editor_data_free(ed);
1212 void editor_resume(gpointer ed)
1214 editor_command_next_start(ed);
1217 void editor_skip(gpointer ed)
1219 editor_command_done(ed);
1222 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1225 EditorFlags flags = editor->flags;
1227 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1229 ed = g_new0(EditorData, 1);
1230 ed->list = filelist_copy(list);
1232 ed->editor = editor;
1233 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1236 ed->working_directory = g_strdup(working_directory);
1238 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1239 flags |= EDITOR_VERBOSE;
1241 if (flags & EDITOR_VERBOSE)
1242 editor_verbose_window(ed, text);
1244 editor_command_next_start(ed);
1245 /* errors from editor_command_next_start will be handled via callback */
1246 return EDITOR_ERRORS(flags);
1249 gboolean is_valid_editor_command(const gchar *key)
1251 if (!key) return FALSE;
1252 return g_hash_table_lookup(editors, key) != NULL;
1255 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1258 EditorDescription *editor;
1259 if (!key) return EDITOR_ERROR_EMPTY;
1261 editor = g_hash_table_lookup(editors, key);
1263 if (!editor) return EDITOR_ERROR_EMPTY;
1264 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1266 error = editor_command_parse(editor, list, TRUE, NULL);
1268 if (EDITOR_ERRORS(error)) return error;
1270 error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1272 if (EDITOR_ERRORS(error))
1274 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1276 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1280 return EDITOR_ERRORS(error);
1283 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1285 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1288 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1293 if (!fd) return FALSE;
1295 list = g_list_append(NULL, fd);
1296 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1301 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1303 return start_editor_from_file_full(key, fd, NULL, NULL);
1306 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1308 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1311 gboolean editor_window_flag_set(const gchar *key)
1313 EditorDescription *editor;
1314 if (!key) return TRUE;
1316 editor = g_hash_table_lookup(editors, key);
1317 if (!editor) return TRUE;
1319 return !!(editor->flags & EDITOR_KEEP_FS);
1322 gboolean editor_is_filter(const gchar *key)
1324 EditorDescription *editor;
1325 if (!key) return TRUE;
1327 editor = g_hash_table_lookup(editors, key);
1328 if (!editor) return TRUE;
1330 return !!(editor->flags & EDITOR_DEST);
1333 gboolean editor_no_param(const gchar *key)
1335 EditorDescription *editor;
1336 if (!key) return FALSE;
1338 editor = g_hash_table_lookup(editors, key);
1339 if (!editor) return FALSE;
1341 return !!(editor->flags & EDITOR_NO_PARAM);
1344 gboolean editor_blocks_file(const gchar *key)
1346 EditorDescription *editor;
1347 if (!key) return FALSE;
1349 editor = g_hash_table_lookup(editors, key);
1350 if (!editor) return FALSE;
1352 /* Decide if the image file should be blocked during editor execution
1353 Editors like gimp can be used long time after the original file was
1354 saved, for editing unrelated files.
1355 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1358 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1361 const gchar *editor_get_error_str(EditorFlags flags)
1363 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1364 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1365 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1366 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1367 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1368 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1369 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1370 return _("Unknown error.");
1373 const gchar *editor_get_name(const gchar *key)
1375 EditorDescription *editor = g_hash_table_lookup(editors, key);
1377 if (!editor) return NULL;
1379 return editor->name;
1381 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */