4 * Copyright (C) 2008 - 2009 The Geeqie Team
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
18 #include "filefilter.h"
20 #include "pixbuf_util.h"
21 #include "ui_fileops.h"
22 #include "ui_spinner.h"
23 #include "ui_utildlg.h"
29 #define EDITOR_WINDOW_WIDTH 500
30 #define EDITOR_WINDOW_HEIGHT 300
34 typedef struct _EditorVerboseData EditorVerboseData;
35 struct _EditorVerboseData {
37 GtkWidget *button_close;
38 GtkWidget *button_stop;
44 typedef struct _EditorData EditorData;
52 EditorVerboseData *vd;
53 EditorCallback callback;
55 const EditorDescription *editor;
56 gchar *working_directory; /* fallback if no files are given (editor_no_param) */
60 static void editor_verbose_window_progress(EditorData *ed, const gchar *text);
61 static EditorFlags editor_command_next_start(EditorData *ed);
62 static EditorFlags editor_command_next_finish(EditorData *ed, gint status);
63 static EditorFlags editor_command_done(EditorData *ed);
66 *-----------------------------------------------------------------------------
67 * external editor routines
68 *-----------------------------------------------------------------------------
71 GHashTable *editors = NULL;
72 GtkListStore *desktop_file_list;
73 gboolean editors_finished = FALSE;
75 #ifdef G_KEY_FILE_DESKTOP_GROUP
76 #define DESKTOP_GROUP G_KEY_FILE_DESKTOP_GROUP
78 #define DESKTOP_GROUP "Desktop Entry"
81 void editor_description_free(EditorDescription *editor)
89 g_free(editor->menu_path);
90 g_free(editor->hotkey);
91 g_free(editor->comment);
92 string_list_free(editor->ext_list);
97 static GList *editor_mime_types_to_extensions(gchar **mime_types)
99 /* FIXME: this should be rewritten to use the shared mime database, as soon as we switch to gio */
101 static const gchar *conv_table[][2] = {
102 {"application/x-ufraw", ".ufraw"},
104 {"image/bmp", ".bmp"},
105 {"image/gif", ".gif"},
106 {"image/jpeg", ".jpeg;.jpg"},
107 {"image/jpg", ".jpg;.jpeg"},
108 {"image/pcx", ".pcx"},
109 {"image/png", ".png"},
110 {"image/svg", ".svg"},
111 {"image/svg+xml", ".svg"},
112 {"image/svg+xml-compressed", ".svg"},
113 {"image/tiff", ".tiff;.tif"},
114 {"image/x-bmp", ".bmp"},
115 {"image/x-canon-crw", ".crw"},
116 {"image/x-cr2", ".cr2"},
117 {"image/x-dcraw", "%raw"},
118 {"image/x-ico", ".ico"},
119 {"image/x-mrw", ".mrw"},
120 {"image/x-MS-bmp", ".bmp"},
121 {"image/x-nef", ".nef"},
122 {"image/x-orf", ".orf"},
123 {"image/x-pcx", ".pcx"},
124 {"image/xpm", ".xpm"},
125 {"image/x-png", ".png"},
126 {"image/x-portable-anymap", ".pam"},
127 {"image/x-portable-bitmap", ".pbm"},
128 {"image/x-portable-graymap", ".pgm"},
129 {"image/x-portable-pixmap", ".ppm"},
130 {"image/x-psd", ".psd"},
131 {"image/x-raf", ".raf"},
132 {"image/x-sgi", ".sgi"},
133 {"image/x-tga", ".tga"},
134 {"image/x-xbitmap", ".xbm"},
135 {"image/x-xcf", ".xcf"},
136 {"image/x-xpixmap", ".xpm"},
137 {"image/x-x3f", ".x3f"},
143 for (i = 0; mime_types[i]; i++)
144 for (j = 0; conv_table[j][0]; j++)
145 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
146 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
151 gboolean editor_read_desktop_file(const gchar *path)
154 EditorDescription *editor;
157 const gchar *key = filename_from_path(path);
158 gchar **categories, **only_show_in, **not_show_in;
161 gboolean category_geeqie = FALSE;
163 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
165 key_file = g_key_file_new();
166 if (!g_key_file_load_from_file(key_file, path, 0, NULL))
168 g_key_file_free(key_file);
172 type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
173 if (!type || strcmp(type, "Application") != 0)
175 /* We only consider desktop entries of Application type */
176 g_key_file_free(key_file);
182 editor = g_new0(EditorDescription, 1);
184 editor->key = g_strdup(key);
185 editor->file = g_strdup(path);
187 g_hash_table_insert(editors, editor->key, editor);
189 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
190 || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
192 editor->hidden = TRUE;
195 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
198 gboolean found = FALSE;
200 for (i = 0; categories[i]; i++)
202 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
203 if (strcmp(categories[i], "Graphics") == 0)
207 if (strcmp(categories[i], "X-Geeqie") == 0)
210 category_geeqie = TRUE;
214 if (!found) editor->ignored = TRUE;
215 g_strfreev(categories);
219 editor->ignored = TRUE;
222 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
225 gboolean found = FALSE;
227 for (i = 0; only_show_in[i]; i++)
228 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
233 if (!found) editor->ignored = TRUE;
234 g_strfreev(only_show_in);
237 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
240 gboolean found = FALSE;
242 for (i = 0; not_show_in[i]; i++)
243 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
248 if (found) editor->ignored = TRUE;
249 g_strfreev(not_show_in);
253 try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
254 if (try_exec && !editor->hidden && !editor->ignored)
256 gchar *try_exec_res = g_find_program_in_path(try_exec);
257 if (!try_exec_res) editor->hidden = TRUE;
258 g_free(try_exec_res);
264 /* ignored editors will be deleted, no need to parse the rest */
265 g_key_file_free(key_file);
269 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
270 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
272 /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
273 if (editor->icon && !g_path_is_absolute(editor->icon))
275 gchar *ext = strrchr(editor->icon, '.');
277 if (ext && strlen(ext) == 4 &&
278 (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
280 log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
281 editor->file, editor->icon);
287 if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon))
289 g_free(editor->icon);
293 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
295 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
296 if (!editor->menu_path) editor->menu_path = g_strdup("EditMenu/ExternalMenu");
298 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
300 editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
302 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
304 editor->ext_list = filter_to_list(extensions);
307 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
310 editor->ext_list = editor_mime_types_to_extensions(mime_types);
311 g_strfreev(mime_types);
312 if (!editor->ext_list) editor->hidden = TRUE;
316 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
317 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
318 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
319 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
320 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
322 editor->flags |= editor_command_parse(editor, NULL, NULL);
324 if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
326 g_key_file_free(key_file);
328 if (editor->ignored) return TRUE;
330 gtk_list_store_append(desktop_file_list, &iter);
331 gtk_list_store_set(desktop_file_list, &iter,
332 DESKTOP_FILE_COLUMN_KEY, key,
333 DESKTOP_FILE_COLUMN_NAME, editor->name,
334 DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden,
335 DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
336 DESKTOP_FILE_COLUMN_PATH, path, -1);
341 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
343 EditorDescription *editor = value;
344 return editor->hidden || editor->ignored;
347 void editor_table_finish(void)
349 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
350 editors_finished = TRUE;
353 void editor_table_clear(void)
355 if (desktop_file_list)
357 gtk_list_store_clear(desktop_file_list);
361 desktop_file_list = gtk_list_store_new(DESKTOP_FILE_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING);
365 g_hash_table_destroy(editors);
367 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
368 editors_finished = FALSE;
371 static GList *editor_add_desktop_dir(GList *list, const gchar *path)
377 pathl = path_from_utf8(path);
385 while ((dir = readdir(dp)) != NULL)
387 gchar *namel = dir->d_name;
389 if (g_str_has_suffix(namel, ".desktop"))
391 gchar *name = path_to_utf8(namel);
392 gchar *dpath = g_build_filename(path, name, NULL);
393 list = g_list_prepend(list, dpath);
401 GList *editor_get_desktop_files(void)
404 gchar *xdg_data_dirs;
410 xdg_data_dirs = getenv("XDG_DATA_DIRS");
411 if (xdg_data_dirs && xdg_data_dirs[0])
412 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
414 xdg_data_dirs = g_strdup("/usr/share");
416 all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
418 g_free(xdg_data_dirs);
420 split_dirs = g_strsplit(all_dirs, ":", 0);
424 for (i = 0; split_dirs[i]; i++);
425 for (--i; i >= 0; i--)
427 path = g_build_filename(split_dirs[i], "applications", NULL);
428 list = editor_add_desktop_dir(list, path);
432 g_strfreev(split_dirs);
433 return g_list_reverse(list);
436 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
438 GList **listp = data;
439 EditorDescription *editor = value;
441 /* do not show the special commands in any list, they are called explicitly */
442 if (strcmp(editor->key, CMD_COPY) == 0 ||
443 strcmp(editor->key, CMD_MOVE) == 0 ||
444 strcmp(editor->key, CMD_RENAME) == 0 ||
445 strcmp(editor->key, CMD_DELETE) == 0 ||
446 strcmp(editor->key, CMD_FOLDER) == 0) return;
448 *listp = g_list_prepend(*listp, editor);
451 static gint editor_sort(gconstpointer a, gconstpointer b)
453 const EditorDescription *ea = a;
454 const EditorDescription *eb = b;
457 ret = strcmp(ea->menu_path, eb->menu_path);
458 if (ret != 0) return ret;
460 return g_utf8_collate(ea->name, eb->name);
463 GList *editor_list_get(void)
465 GList *editors_list = NULL;
467 if (!editors_finished) return NULL;
469 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
470 editors_list = g_list_sort(editors_list, editor_sort);
475 /* ------------------------------ */
478 static void editor_verbose_data_free(EditorData *ed)
485 static void editor_data_free(EditorData *ed)
487 editor_verbose_data_free(ed);
488 g_free(ed->working_directory);
492 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
494 EditorData *ed = data;
496 generic_dialog_close(gd);
497 editor_verbose_data_free(ed);
498 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
501 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
503 EditorData *ed = data;
506 editor_verbose_window_progress(ed, _("stopping..."));
509 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
511 vd->gd->cancel_cb = editor_verbose_window_close;
513 spinner_set_interval(vd->spinner, -1);
514 gtk_widget_set_sensitive(vd->button_stop, FALSE);
515 gtk_widget_set_sensitive(vd->button_close, TRUE);
518 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
520 EditorVerboseData *vd;
525 vd = g_new0(EditorVerboseData, 1);
527 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
530 buf = g_strdup_printf(_("Output of %s"), text);
531 generic_dialog_add_message(vd->gd, NULL, buf, NULL);
533 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
534 editor_verbose_window_stop, FALSE);
535 gtk_widget_set_sensitive(vd->button_stop, FALSE);
536 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
537 editor_verbose_window_close, TRUE);
538 gtk_widget_set_sensitive(vd->button_close, FALSE);
540 scrolled = gtk_scrolled_window_new(NULL, NULL);
541 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
542 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
543 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
544 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
545 gtk_widget_show(scrolled);
547 vd->text = gtk_text_view_new();
548 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
549 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
550 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
551 gtk_widget_show(vd->text);
553 hbox = gtk_hbox_new(FALSE, 0);
554 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
555 gtk_widget_show(hbox);
557 vd->progress = gtk_progress_bar_new();
558 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
559 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
560 gtk_widget_show(vd->progress);
562 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
563 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
564 gtk_widget_show(vd->spinner);
566 gtk_widget_show(vd->gd->dialog);
572 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
574 GtkTextBuffer *buffer;
577 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
578 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
579 gtk_text_buffer_insert(buffer, &iter, text, len);
582 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
588 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
591 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
594 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
596 EditorData *ed = data;
600 if (condition & G_IO_IN)
602 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
604 if (!g_utf8_validate(buf, count, NULL))
608 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
611 editor_verbose_window_fill(ed->vd, utf8, -1);
616 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
621 editor_verbose_window_fill(ed->vd, buf, count);
626 if (condition & (G_IO_ERR | G_IO_HUP))
628 g_io_channel_shutdown(source, TRUE, NULL);
642 static gchar *editor_command_path_parse(const FileData *fd, PathType type, const EditorDescription *editor)
646 const gchar *p = NULL;
648 string = g_string_new("");
650 if (type == PATH_FILE || type == PATH_FILE_URL)
652 GList *work = editor->ext_list;
661 gchar *ext = work->data;
664 if (strcmp(ext, "*") == 0 ||
665 g_ascii_strcasecmp(ext, fd->extension) == 0)
671 work2 = fd->sidecar_files;
674 FileData *sfd = work2->data;
677 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
688 else if (type == PATH_DEST)
690 if (fd->change && fd->change->dest)
691 p = fd->change->dest;
697 string = g_string_append(string, p);
699 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
700 pathl = path_from_utf8(string->str);
701 g_string_free(string, TRUE);
703 if (pathl && !pathl[0]) /* empty string case */
712 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
719 g_string_append_c(str, '\'');
721 g_string_append(str, "\"'");
724 for (p = s; *p != '\0'; p++)
727 g_string_append(str, "'\\''");
729 g_string_append_c(str, *p);
735 g_string_append_c(str, '\'');
737 g_string_append(str, "'\"");
744 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gchar **output)
746 EditorFlags flags = 0;
748 GString *result = NULL;
749 gboolean escape = FALSE;
750 gboolean single_quotes = FALSE;
751 gboolean double_quotes = FALSE;
754 result = g_string_new("");
756 if (editor->exec[0] == '\0')
758 flags |= EDITOR_ERROR_EMPTY;
763 /* skip leading whitespaces if any */
764 while (g_ascii_isspace(*p)) p++;
773 if (output) result = g_string_append_c(result, *p);
777 if (!single_quotes) escape = TRUE;
778 if (output) result = g_string_append_c(result, *p);
782 if (output) result = g_string_append_c(result, *p);
783 if (!single_quotes && !double_quotes)
784 single_quotes = TRUE;
785 else if (single_quotes)
786 single_quotes = FALSE;
790 if (output) result = g_string_append_c(result, *p);
791 if (!single_quotes && !double_quotes)
792 double_quotes = TRUE;
793 else if (double_quotes)
794 double_quotes = FALSE;
796 else if (*p == '%' && p[1])
804 case 'f': /* single file */
805 case 'u': /* single url */
806 flags |= EDITOR_FOR_EACH;
807 if (flags & EDITOR_SINGLE_COMMAND)
809 flags |= EDITOR_ERROR_INCOMPATIBLE;
814 /* use the first file from the list */
817 flags |= EDITOR_ERROR_NO_FILE;
820 pathl = editor_command_path_parse((FileData *)list->data,
821 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
825 flags |= EDITOR_ERROR_NO_FILE;
830 result = append_quoted(result, pathl, single_quotes, double_quotes);
838 flags |= EDITOR_SINGLE_COMMAND;
839 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
841 flags |= EDITOR_ERROR_INCOMPATIBLE;
853 FileData *fd = work->data;
854 pathl = editor_command_path_parse(fd, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
862 if (work != list) g_string_append_c(result, ' ');
863 result = append_quoted(result, pathl, single_quotes, double_quotes);
871 flags |= EDITOR_ERROR_NO_FILE;
877 if (editor->icon && *editor->icon)
881 result = g_string_append(result, "--icon ");
882 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
889 result = append_quoted(result, editor->name, single_quotes, double_quotes);
895 result = append_quoted(result, editor->file, single_quotes, double_quotes);
899 /* %% = % escaping */
900 if (output) result = g_string_append_c(result, *p);
908 /* deprecated according to spec, ignore */
911 flags |= EDITOR_ERROR_SYNTAX;
917 if (output) result = g_string_append_c(result, *p);
922 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
926 *output = g_string_free(result, FALSE);
927 DEBUG_3("Editor cmd: %s", *output);
936 g_string_free(result, TRUE);
943 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
945 EditorData *ed = data;
946 g_spawn_close_pid(pid);
949 editor_command_next_finish(ed, status);
953 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
956 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
958 gint standard_output;
963 ed->flags = editor->flags;
964 ed->flags |= editor_command_parse(editor, list, &command);
966 ok = !EDITOR_ERRORS(ed->flags);
970 ok = (options->shell.path && *options->shell.path);
971 if (!ok) log_printf("ERROR: empty shell command\n");
975 ok = (access(options->shell.path, X_OK) == 0);
976 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
979 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
984 gchar *working_directory;
988 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
989 args[n++] = options->shell.path;
990 if (options->shell.options && *options->shell.options)
991 args[n++] = options->shell.options;
995 if ((ed->flags & EDITOR_DEST) && fd->change && fd->change->dest) /* FIXME: error handling */
997 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1001 g_unsetenv("GEEQIE_DESTINATION");
1004 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1005 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1009 ed->vd ? &standard_output : NULL,
1010 ed->vd ? &standard_error : NULL,
1013 g_free(working_directory);
1015 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1020 g_child_watch_add(pid, editor_child_exit_cb, ed);
1030 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1031 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1037 GIOChannel *channel_output;
1038 GIOChannel *channel_error;
1040 channel_output = g_io_channel_unix_new(standard_output);
1041 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1042 g_io_channel_set_encoding(channel_output, NULL, NULL);
1044 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1045 editor_verbose_io_cb, ed, NULL);
1046 g_io_channel_unref(channel_output);
1048 channel_error = g_io_channel_unix_new(standard_error);
1049 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1050 g_io_channel_set_encoding(channel_error, NULL, NULL);
1052 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1053 editor_verbose_io_cb, ed, NULL);
1054 g_io_channel_unref(channel_error);
1060 return EDITOR_ERRORS(ed->flags);
1063 static EditorFlags editor_command_next_start(EditorData *ed)
1065 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1067 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1072 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1076 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1077 editor_verbose_window_progress(ed, fd->path);
1079 editor_verbose_window_progress(ed, _("running..."));
1083 error = editor_command_one(ed->editor, ed->list, ed);
1084 if (!error && ed->vd)
1086 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1087 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1089 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1090 editor_verbose_window_fill(ed->vd, "\n", 1);
1097 /* command was not started, call the finish immediately */
1098 return editor_command_next_finish(ed, 0);
1101 /* everything is done */
1102 return editor_command_done(ed);
1105 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1107 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1110 ed->flags |= EDITOR_ERROR_STATUS;
1112 if (ed->flags & EDITOR_FOR_EACH)
1114 /* handle the first element from the list */
1115 GList *fd_element = ed->list;
1117 ed->list = g_list_remove_link(ed->list, fd_element);
1120 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1121 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1123 filelist_free(fd_element);
1127 /* handle whole list */
1129 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1130 filelist_free(ed->list);
1136 case EDITOR_CB_SUSPEND:
1137 return EDITOR_ERRORS(ed->flags);
1138 case EDITOR_CB_SKIP:
1139 return editor_command_done(ed);
1142 return editor_command_next_start(ed);
1145 static EditorFlags editor_command_done(EditorData *ed)
1151 if (ed->count == ed->total)
1153 editor_verbose_window_progress(ed, _("done"));
1157 editor_verbose_window_progress(ed, _("stopped by user"));
1159 editor_verbose_window_enable_close(ed->vd);
1162 /* free the not-handled items */
1165 ed->flags |= EDITOR_ERROR_SKIPPED;
1166 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1167 filelist_free(ed->list);
1173 flags = EDITOR_ERRORS(ed->flags);
1175 if (!ed->vd) editor_data_free(ed);
1180 void editor_resume(gpointer ed)
1182 editor_command_next_start(ed);
1185 void editor_skip(gpointer ed)
1187 editor_command_done(ed);
1190 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1193 EditorFlags flags = editor->flags;
1195 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1197 ed = g_new0(EditorData, 1);
1198 ed->list = filelist_copy(list);
1200 ed->editor = editor;
1201 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1204 ed->working_directory = g_strdup(working_directory);
1206 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1207 flags |= EDITOR_VERBOSE;
1209 if (flags & EDITOR_VERBOSE)
1210 editor_verbose_window(ed, text);
1212 editor_command_next_start(ed);
1213 /* errors from editor_command_next_start will be handled via callback */
1214 return EDITOR_ERRORS(flags);
1217 gboolean is_valid_editor_command(const gchar *key)
1219 if (!key) return FALSE;
1220 return g_hash_table_lookup(editors, key) != NULL;
1223 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1226 EditorDescription *editor;
1227 if (!key) return FALSE;
1229 editor = g_hash_table_lookup(editors, key);
1231 if (!editor) return FALSE;
1232 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return FALSE;
1234 error = editor_command_start(editor, editor->name, list, working_directory, cb, data);
1236 if (EDITOR_ERRORS(error))
1238 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1240 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1247 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1249 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1252 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1257 if (!fd) return FALSE;
1259 list = g_list_append(NULL, fd);
1260 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1265 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1267 return start_editor_from_file_full(key, fd, NULL, NULL);
1270 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1272 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1275 gboolean editor_window_flag_set(const gchar *key)
1277 EditorDescription *editor;
1278 if (!key) return TRUE;
1280 editor = g_hash_table_lookup(editors, key);
1281 if (!editor) return TRUE;
1283 return !!(editor->flags & EDITOR_KEEP_FS);
1286 gboolean editor_is_filter(const gchar *key)
1288 EditorDescription *editor;
1289 if (!key) return TRUE;
1291 editor = g_hash_table_lookup(editors, key);
1292 if (!editor) return TRUE;
1294 return !!(editor->flags & EDITOR_DEST);
1297 gboolean editor_no_param(const gchar *key)
1299 EditorDescription *editor;
1300 if (!key) return FALSE;
1302 editor = g_hash_table_lookup(editors, key);
1303 if (!editor) return FALSE;
1305 return !!(editor->flags & EDITOR_NO_PARAM);
1308 gboolean editor_blocks_file(const gchar *key)
1310 EditorDescription *editor;
1311 if (!key) return FALSE;
1313 editor = g_hash_table_lookup(editors, key);
1314 if (!editor) return FALSE;
1316 /* Decide if the image file should be blocked during editor execution
1317 Editors like gimp can be used long time after the original file was
1318 saved, for editing unrelated files.
1319 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1322 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1325 const gchar *editor_get_error_str(EditorFlags flags)
1327 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1328 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1329 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1330 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1331 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1332 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1333 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1334 return _("Unknown error.");
1337 const gchar *editor_get_name(const gchar *key)
1339 EditorDescription *editor = g_hash_table_lookup(editors, key);
1341 if (!editor) return NULL;
1343 return editor->name;
1345 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */