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 #if GTK_CHECK_VERSION(3,0,0)
570 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), "");
571 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(vd->progress), TRUE);
573 gtk_widget_show(vd->progress);
575 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
576 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
577 gtk_widget_show(vd->spinner);
579 gtk_widget_show(vd->gd->dialog);
585 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
587 GtkTextBuffer *buffer;
590 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
591 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
592 gtk_text_buffer_insert(buffer, &iter, text, len);
595 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
601 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
604 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
607 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
609 EditorData *ed = data;
613 if (condition & G_IO_IN)
615 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
617 if (!g_utf8_validate(buf, count, NULL))
621 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
624 editor_verbose_window_fill(ed->vd, utf8, -1);
629 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
634 editor_verbose_window_fill(ed->vd, buf, count);
639 if (condition & (G_IO_ERR | G_IO_HUP))
641 g_io_channel_shutdown(source, TRUE, NULL);
655 static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
659 const gchar *p = NULL;
661 DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
663 string = g_string_new("");
665 if (type == PATH_FILE || type == PATH_FILE_URL)
667 GList *work = editor->ext_list;
676 gchar *ext = work->data;
679 if (strcmp(ext, "*") == 0 ||
680 g_ascii_strcasecmp(ext, fd->extension) == 0)
686 work2 = consider_sidecars ? fd->sidecar_files : NULL;
689 FileData *sfd = work2->data;
692 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
703 else if (type == PATH_DEST)
705 if (fd->change && fd->change->dest)
706 p = fd->change->dest;
712 string = g_string_append(string, p);
714 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
715 pathl = path_from_utf8(string->str);
716 g_string_free(string, TRUE);
718 if (pathl && !pathl[0]) /* empty string case */
724 DEBUG_2("editor_command_path_parse: return %s", pathl);
728 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
735 g_string_append_c(str, '\'');
737 g_string_append(str, "\"'");
740 for (p = s; *p != '\0'; p++)
743 g_string_append(str, "'\\''");
745 g_string_append_c(str, *p);
751 g_string_append_c(str, '\'');
753 g_string_append(str, "'\"");
760 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
762 EditorFlags flags = 0;
764 GString *result = NULL;
765 gboolean escape = FALSE;
766 gboolean single_quotes = FALSE;
767 gboolean double_quotes = FALSE;
769 DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
772 result = g_string_new("");
774 if (editor->exec == NULL || editor->exec[0] == '\0')
776 flags |= EDITOR_ERROR_EMPTY;
781 /* skip leading whitespaces if any */
782 while (g_ascii_isspace(*p)) p++;
791 if (output) result = g_string_append_c(result, *p);
795 if (!single_quotes) escape = TRUE;
796 if (output) result = g_string_append_c(result, *p);
800 if (output) result = g_string_append_c(result, *p);
801 if (!single_quotes && !double_quotes)
802 single_quotes = TRUE;
803 else if (single_quotes)
804 single_quotes = FALSE;
808 if (output) result = g_string_append_c(result, *p);
809 if (!single_quotes && !double_quotes)
810 double_quotes = TRUE;
811 else if (double_quotes)
812 double_quotes = FALSE;
814 else if (*p == '%' && p[1])
822 case 'f': /* single file */
823 case 'u': /* single url */
824 flags |= EDITOR_FOR_EACH;
825 if (flags & EDITOR_SINGLE_COMMAND)
827 flags |= EDITOR_ERROR_INCOMPATIBLE;
832 /* use the first file from the list */
835 flags |= EDITOR_ERROR_NO_FILE;
838 pathl = editor_command_path_parse((FileData *)list->data,
840 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
844 /* just testing, check also the rest of the list (like with F and U)
845 any matching file is OK */
846 GList *work = list->next;
848 while (!pathl && work)
850 FileData *fd = work->data;
851 pathl = editor_command_path_parse(fd,
853 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
861 flags |= EDITOR_ERROR_NO_FILE;
866 result = append_quoted(result, pathl, single_quotes, double_quotes);
874 flags |= EDITOR_SINGLE_COMMAND;
875 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
877 flags |= EDITOR_ERROR_INCOMPATIBLE;
889 FileData *fd = work->data;
890 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
898 if (work != list) g_string_append_c(result, ' ');
899 result = append_quoted(result, pathl, single_quotes, double_quotes);
907 flags |= EDITOR_ERROR_NO_FILE;
913 if (editor->icon && *editor->icon)
917 result = g_string_append(result, "--icon ");
918 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
925 result = append_quoted(result, editor->name, single_quotes, double_quotes);
931 result = append_quoted(result, editor->file, single_quotes, double_quotes);
935 /* %% = % escaping */
936 if (output) result = g_string_append_c(result, *p);
944 /* deprecated according to spec, ignore */
947 flags |= EDITOR_ERROR_SYNTAX;
953 if (output) result = g_string_append_c(result, *p);
958 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
962 *output = g_string_free(result, FALSE);
963 DEBUG_3("Editor cmd: %s", *output);
972 g_string_free(result, TRUE);
979 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
981 EditorData *ed = data;
982 g_spawn_close_pid(pid);
985 editor_command_next_finish(ed, status);
989 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
992 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
994 gint standard_output;
999 ed->flags = editor->flags;
1000 ed->flags |= editor_command_parse(editor, list, TRUE, &command);
1002 ok = !EDITOR_ERRORS(ed->flags);
1006 ok = (options->shell.path && *options->shell.path);
1007 if (!ok) log_printf("ERROR: empty shell command\n");
1011 ok = (access(options->shell.path, X_OK) == 0);
1012 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1015 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1020 gchar *working_directory;
1024 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1025 args[n++] = options->shell.path;
1026 if (options->shell.options && *options->shell.options)
1027 args[n++] = options->shell.options;
1028 args[n++] = command;
1031 if ((ed->flags & EDITOR_DEST) && fd->change && fd->change->dest) /* FIXME: error handling */
1033 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1037 g_unsetenv("GEEQIE_DESTINATION");
1040 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1041 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1045 ed->vd ? &standard_output : NULL,
1046 ed->vd ? &standard_error : NULL,
1049 g_free(working_directory);
1051 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1056 g_child_watch_add(pid, editor_child_exit_cb, ed);
1066 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1067 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1073 GIOChannel *channel_output;
1074 GIOChannel *channel_error;
1076 channel_output = g_io_channel_unix_new(standard_output);
1077 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1078 g_io_channel_set_encoding(channel_output, NULL, NULL);
1080 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1081 editor_verbose_io_cb, ed, NULL);
1082 g_io_channel_unref(channel_output);
1084 channel_error = g_io_channel_unix_new(standard_error);
1085 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1086 g_io_channel_set_encoding(channel_error, NULL, NULL);
1088 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1089 editor_verbose_io_cb, ed, NULL);
1090 g_io_channel_unref(channel_error);
1096 return EDITOR_ERRORS(ed->flags);
1099 static EditorFlags editor_command_next_start(EditorData *ed)
1101 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1103 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1108 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1112 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1113 editor_verbose_window_progress(ed, fd->path);
1115 editor_verbose_window_progress(ed, _("running..."));
1119 error = editor_command_one(ed->editor, ed->list, ed);
1120 if (!error && ed->vd)
1122 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1123 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1125 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1126 editor_verbose_window_fill(ed->vd, "\n", 1);
1133 /* command was not started, call the finish immediately */
1134 return editor_command_next_finish(ed, 0);
1137 /* everything is done */
1138 return editor_command_done(ed);
1141 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1143 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1146 ed->flags |= EDITOR_ERROR_STATUS;
1148 if (ed->flags & EDITOR_FOR_EACH)
1150 /* handle the first element from the list */
1151 GList *fd_element = ed->list;
1153 ed->list = g_list_remove_link(ed->list, fd_element);
1156 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1157 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1159 filelist_free(fd_element);
1163 /* handle whole list */
1165 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1166 filelist_free(ed->list);
1172 case EDITOR_CB_SUSPEND:
1173 return EDITOR_ERRORS(ed->flags);
1174 case EDITOR_CB_SKIP:
1175 return editor_command_done(ed);
1178 return editor_command_next_start(ed);
1181 static EditorFlags editor_command_done(EditorData *ed)
1187 if (ed->count == ed->total)
1189 editor_verbose_window_progress(ed, _("done"));
1193 editor_verbose_window_progress(ed, _("stopped by user"));
1195 editor_verbose_window_enable_close(ed->vd);
1198 /* free the not-handled items */
1201 ed->flags |= EDITOR_ERROR_SKIPPED;
1202 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1203 filelist_free(ed->list);
1209 flags = EDITOR_ERRORS(ed->flags);
1211 if (!ed->vd) editor_data_free(ed);
1216 void editor_resume(gpointer ed)
1218 editor_command_next_start(ed);
1221 void editor_skip(gpointer ed)
1223 editor_command_done(ed);
1226 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1229 EditorFlags flags = editor->flags;
1231 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1233 ed = g_new0(EditorData, 1);
1234 ed->list = filelist_copy(list);
1236 ed->editor = editor;
1237 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1240 ed->working_directory = g_strdup(working_directory);
1242 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1243 flags |= EDITOR_VERBOSE;
1245 if (flags & EDITOR_VERBOSE)
1246 editor_verbose_window(ed, text);
1248 editor_command_next_start(ed);
1249 /* errors from editor_command_next_start will be handled via callback */
1250 return EDITOR_ERRORS(flags);
1253 gboolean is_valid_editor_command(const gchar *key)
1255 if (!key) return FALSE;
1256 return g_hash_table_lookup(editors, key) != NULL;
1259 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1262 EditorDescription *editor;
1263 if (!key) return EDITOR_ERROR_EMPTY;
1265 editor = g_hash_table_lookup(editors, key);
1267 if (!editor) return EDITOR_ERROR_EMPTY;
1268 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1270 error = editor_command_parse(editor, list, TRUE, NULL);
1272 if (EDITOR_ERRORS(error)) return error;
1274 error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1276 if (EDITOR_ERRORS(error))
1278 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1280 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1284 return EDITOR_ERRORS(error);
1287 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1289 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1292 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1297 if (!fd) return FALSE;
1299 list = g_list_append(NULL, fd);
1300 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1305 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1307 return start_editor_from_file_full(key, fd, NULL, NULL);
1310 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1312 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1315 gboolean editor_window_flag_set(const gchar *key)
1317 EditorDescription *editor;
1318 if (!key) return TRUE;
1320 editor = g_hash_table_lookup(editors, key);
1321 if (!editor) return TRUE;
1323 return !!(editor->flags & EDITOR_KEEP_FS);
1326 gboolean editor_is_filter(const gchar *key)
1328 EditorDescription *editor;
1329 if (!key) return TRUE;
1331 editor = g_hash_table_lookup(editors, key);
1332 if (!editor) return TRUE;
1334 return !!(editor->flags & EDITOR_DEST);
1337 gboolean editor_no_param(const gchar *key)
1339 EditorDescription *editor;
1340 if (!key) return FALSE;
1342 editor = g_hash_table_lookup(editors, key);
1343 if (!editor) return FALSE;
1345 return !!(editor->flags & EDITOR_NO_PARAM);
1348 gboolean editor_blocks_file(const gchar *key)
1350 EditorDescription *editor;
1351 if (!key) return FALSE;
1353 editor = g_hash_table_lookup(editors, key);
1354 if (!editor) return FALSE;
1356 /* Decide if the image file should be blocked during editor execution
1357 Editors like gimp can be used long time after the original file was
1358 saved, for editing unrelated files.
1359 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1362 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1365 const gchar *editor_get_error_str(EditorFlags flags)
1367 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1368 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1369 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1370 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1371 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1372 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1373 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1374 return _("Unknown error.");
1377 const gchar *editor_get_name(const gchar *key)
1379 EditorDescription *editor = g_hash_table_lookup(editors, key);
1381 if (!editor) return NULL;
1383 return editor->name;
1385 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */