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, FALSE, 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);
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, gboolean consider_sidecars, 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 = consider_sidecars ? fd->sidecar_files : NULL;
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, gboolean consider_sidecars, 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,
822 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
826 flags |= EDITOR_ERROR_NO_FILE;
831 result = append_quoted(result, pathl, single_quotes, double_quotes);
839 flags |= EDITOR_SINGLE_COMMAND;
840 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
842 flags |= EDITOR_ERROR_INCOMPATIBLE;
854 FileData *fd = work->data;
855 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
863 if (work != list) g_string_append_c(result, ' ');
864 result = append_quoted(result, pathl, single_quotes, double_quotes);
872 flags |= EDITOR_ERROR_NO_FILE;
878 if (editor->icon && *editor->icon)
882 result = g_string_append(result, "--icon ");
883 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
890 result = append_quoted(result, editor->name, single_quotes, double_quotes);
896 result = append_quoted(result, editor->file, single_quotes, double_quotes);
900 /* %% = % escaping */
901 if (output) result = g_string_append_c(result, *p);
909 /* deprecated according to spec, ignore */
912 flags |= EDITOR_ERROR_SYNTAX;
918 if (output) result = g_string_append_c(result, *p);
923 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
927 *output = g_string_free(result, FALSE);
928 DEBUG_3("Editor cmd: %s", *output);
937 g_string_free(result, TRUE);
944 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
946 EditorData *ed = data;
947 g_spawn_close_pid(pid);
950 editor_command_next_finish(ed, status);
954 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
957 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
959 gint standard_output;
964 ed->flags = editor->flags;
965 ed->flags |= editor_command_parse(editor, list, TRUE, &command);
967 ok = !EDITOR_ERRORS(ed->flags);
971 ok = (options->shell.path && *options->shell.path);
972 if (!ok) log_printf("ERROR: empty shell command\n");
976 ok = (access(options->shell.path, X_OK) == 0);
977 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
980 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
985 gchar *working_directory;
989 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
990 args[n++] = options->shell.path;
991 if (options->shell.options && *options->shell.options)
992 args[n++] = options->shell.options;
996 if ((ed->flags & EDITOR_DEST) && fd->change && fd->change->dest) /* FIXME: error handling */
998 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1002 g_unsetenv("GEEQIE_DESTINATION");
1005 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1006 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1010 ed->vd ? &standard_output : NULL,
1011 ed->vd ? &standard_error : NULL,
1014 g_free(working_directory);
1016 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1021 g_child_watch_add(pid, editor_child_exit_cb, ed);
1031 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1032 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1038 GIOChannel *channel_output;
1039 GIOChannel *channel_error;
1041 channel_output = g_io_channel_unix_new(standard_output);
1042 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1043 g_io_channel_set_encoding(channel_output, NULL, NULL);
1045 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1046 editor_verbose_io_cb, ed, NULL);
1047 g_io_channel_unref(channel_output);
1049 channel_error = g_io_channel_unix_new(standard_error);
1050 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1051 g_io_channel_set_encoding(channel_error, NULL, NULL);
1053 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1054 editor_verbose_io_cb, ed, NULL);
1055 g_io_channel_unref(channel_error);
1061 return EDITOR_ERRORS(ed->flags);
1064 static EditorFlags editor_command_next_start(EditorData *ed)
1066 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1068 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1073 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1077 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1078 editor_verbose_window_progress(ed, fd->path);
1080 editor_verbose_window_progress(ed, _("running..."));
1084 error = editor_command_one(ed->editor, ed->list, ed);
1085 if (!error && ed->vd)
1087 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1088 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1090 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1091 editor_verbose_window_fill(ed->vd, "\n", 1);
1098 /* command was not started, call the finish immediately */
1099 return editor_command_next_finish(ed, 0);
1102 /* everything is done */
1103 return editor_command_done(ed);
1106 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1108 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1111 ed->flags |= EDITOR_ERROR_STATUS;
1113 if (ed->flags & EDITOR_FOR_EACH)
1115 /* handle the first element from the list */
1116 GList *fd_element = ed->list;
1118 ed->list = g_list_remove_link(ed->list, fd_element);
1121 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1122 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1124 filelist_free(fd_element);
1128 /* handle whole list */
1130 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1131 filelist_free(ed->list);
1137 case EDITOR_CB_SUSPEND:
1138 return EDITOR_ERRORS(ed->flags);
1139 case EDITOR_CB_SKIP:
1140 return editor_command_done(ed);
1143 return editor_command_next_start(ed);
1146 static EditorFlags editor_command_done(EditorData *ed)
1152 if (ed->count == ed->total)
1154 editor_verbose_window_progress(ed, _("done"));
1158 editor_verbose_window_progress(ed, _("stopped by user"));
1160 editor_verbose_window_enable_close(ed->vd);
1163 /* free the not-handled items */
1166 ed->flags |= EDITOR_ERROR_SKIPPED;
1167 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1168 filelist_free(ed->list);
1174 flags = EDITOR_ERRORS(ed->flags);
1176 if (!ed->vd) editor_data_free(ed);
1181 void editor_resume(gpointer ed)
1183 editor_command_next_start(ed);
1186 void editor_skip(gpointer ed)
1188 editor_command_done(ed);
1191 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1194 EditorFlags flags = editor->flags;
1196 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1198 ed = g_new0(EditorData, 1);
1199 ed->list = filelist_copy(list);
1201 ed->editor = editor;
1202 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1205 ed->working_directory = g_strdup(working_directory);
1207 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1208 flags |= EDITOR_VERBOSE;
1210 if (flags & EDITOR_VERBOSE)
1211 editor_verbose_window(ed, text);
1213 editor_command_next_start(ed);
1214 /* errors from editor_command_next_start will be handled via callback */
1215 return EDITOR_ERRORS(flags);
1218 gboolean is_valid_editor_command(const gchar *key)
1220 if (!key) return FALSE;
1221 return g_hash_table_lookup(editors, key) != NULL;
1224 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1227 EditorDescription *editor;
1228 if (!key) return EDITOR_ERROR_EMPTY;
1230 editor = g_hash_table_lookup(editors, key);
1232 if (!editor) return EDITOR_ERROR_EMPTY;
1233 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1235 error = editor_command_parse(editor, list, TRUE, NULL);
1237 if (EDITOR_ERRORS(error)) return error;
1239 error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1241 if (EDITOR_ERRORS(error))
1243 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1245 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1249 return EDITOR_ERRORS(error);
1252 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1254 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1257 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1262 if (!fd) return FALSE;
1264 list = g_list_append(NULL, fd);
1265 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1270 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1272 return start_editor_from_file_full(key, fd, NULL, NULL);
1275 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1277 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1280 gboolean editor_window_flag_set(const gchar *key)
1282 EditorDescription *editor;
1283 if (!key) return TRUE;
1285 editor = g_hash_table_lookup(editors, key);
1286 if (!editor) return TRUE;
1288 return !!(editor->flags & EDITOR_KEEP_FS);
1291 gboolean editor_is_filter(const gchar *key)
1293 EditorDescription *editor;
1294 if (!key) return TRUE;
1296 editor = g_hash_table_lookup(editors, key);
1297 if (!editor) return TRUE;
1299 return !!(editor->flags & EDITOR_DEST);
1302 gboolean editor_no_param(const gchar *key)
1304 EditorDescription *editor;
1305 if (!key) return FALSE;
1307 editor = g_hash_table_lookup(editors, key);
1308 if (!editor) return FALSE;
1310 return !!(editor->flags & EDITOR_NO_PARAM);
1313 gboolean editor_blocks_file(const gchar *key)
1315 EditorDescription *editor;
1316 if (!key) return FALSE;
1318 editor = g_hash_table_lookup(editors, key);
1319 if (!editor) return FALSE;
1321 /* Decide if the image file should be blocked during editor execution
1322 Editors like gimp can be used long time after the original file was
1323 saved, for editing unrelated files.
1324 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1327 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1330 const gchar *editor_get_error_str(EditorFlags flags)
1332 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1333 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1334 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1335 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1336 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1337 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1338 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1339 return _("Unknown error.");
1342 const gchar *editor_get_name(const gchar *key)
1344 EditorDescription *editor = g_hash_table_lookup(editors, key);
1346 if (!editor) return NULL;
1348 return editor->name;
1350 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */