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 ? _("yes") : _("no"),
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_STRING, 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 DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
650 string = g_string_new("");
652 if (type == PATH_FILE || type == PATH_FILE_URL)
654 GList *work = editor->ext_list;
663 gchar *ext = work->data;
666 if (strcmp(ext, "*") == 0 ||
667 g_ascii_strcasecmp(ext, fd->extension) == 0)
673 work2 = consider_sidecars ? fd->sidecar_files : NULL;
676 FileData *sfd = work2->data;
679 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
690 else if (type == PATH_DEST)
692 if (fd->change && fd->change->dest)
693 p = fd->change->dest;
699 string = g_string_append(string, p);
701 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
702 pathl = path_from_utf8(string->str);
703 g_string_free(string, TRUE);
705 if (pathl && !pathl[0]) /* empty string case */
711 DEBUG_2("editor_command_path_parse: return %s", pathl);
715 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
722 g_string_append_c(str, '\'');
724 g_string_append(str, "\"'");
727 for (p = s; *p != '\0'; p++)
730 g_string_append(str, "'\\''");
732 g_string_append_c(str, *p);
738 g_string_append_c(str, '\'');
740 g_string_append(str, "'\"");
747 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
749 EditorFlags flags = 0;
751 GString *result = NULL;
752 gboolean escape = FALSE;
753 gboolean single_quotes = FALSE;
754 gboolean double_quotes = FALSE;
756 DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
759 result = g_string_new("");
761 if (editor->exec[0] == '\0')
763 flags |= EDITOR_ERROR_EMPTY;
768 /* skip leading whitespaces if any */
769 while (g_ascii_isspace(*p)) p++;
778 if (output) result = g_string_append_c(result, *p);
782 if (!single_quotes) escape = TRUE;
783 if (output) result = g_string_append_c(result, *p);
787 if (output) result = g_string_append_c(result, *p);
788 if (!single_quotes && !double_quotes)
789 single_quotes = TRUE;
790 else if (single_quotes)
791 single_quotes = FALSE;
795 if (output) result = g_string_append_c(result, *p);
796 if (!single_quotes && !double_quotes)
797 double_quotes = TRUE;
798 else if (double_quotes)
799 double_quotes = FALSE;
801 else if (*p == '%' && p[1])
809 case 'f': /* single file */
810 case 'u': /* single url */
811 flags |= EDITOR_FOR_EACH;
812 if (flags & EDITOR_SINGLE_COMMAND)
814 flags |= EDITOR_ERROR_INCOMPATIBLE;
819 /* use the first file from the list */
822 flags |= EDITOR_ERROR_NO_FILE;
825 pathl = editor_command_path_parse((FileData *)list->data,
827 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
831 /* just testing, check also the rest of the list (like with F and U)
832 any matching file is OK */
833 GList *work = list->next;
835 while (!pathl && work)
837 FileData *fd = work->data;
838 pathl = editor_command_path_parse(fd,
840 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
848 flags |= EDITOR_ERROR_NO_FILE;
853 result = append_quoted(result, pathl, single_quotes, double_quotes);
861 flags |= EDITOR_SINGLE_COMMAND;
862 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
864 flags |= EDITOR_ERROR_INCOMPATIBLE;
876 FileData *fd = work->data;
877 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
885 if (work != list) g_string_append_c(result, ' ');
886 result = append_quoted(result, pathl, single_quotes, double_quotes);
894 flags |= EDITOR_ERROR_NO_FILE;
900 if (editor->icon && *editor->icon)
904 result = g_string_append(result, "--icon ");
905 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
912 result = append_quoted(result, editor->name, single_quotes, double_quotes);
918 result = append_quoted(result, editor->file, single_quotes, double_quotes);
922 /* %% = % escaping */
923 if (output) result = g_string_append_c(result, *p);
931 /* deprecated according to spec, ignore */
934 flags |= EDITOR_ERROR_SYNTAX;
940 if (output) result = g_string_append_c(result, *p);
945 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
949 *output = g_string_free(result, FALSE);
950 DEBUG_3("Editor cmd: %s", *output);
959 g_string_free(result, TRUE);
966 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
968 EditorData *ed = data;
969 g_spawn_close_pid(pid);
972 editor_command_next_finish(ed, status);
976 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
979 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
981 gint standard_output;
986 ed->flags = editor->flags;
987 ed->flags |= editor_command_parse(editor, list, TRUE, &command);
989 ok = !EDITOR_ERRORS(ed->flags);
993 ok = (options->shell.path && *options->shell.path);
994 if (!ok) log_printf("ERROR: empty shell command\n");
998 ok = (access(options->shell.path, X_OK) == 0);
999 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1002 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1007 gchar *working_directory;
1011 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1012 args[n++] = options->shell.path;
1013 if (options->shell.options && *options->shell.options)
1014 args[n++] = options->shell.options;
1015 args[n++] = command;
1018 if ((ed->flags & EDITOR_DEST) && fd->change && fd->change->dest) /* FIXME: error handling */
1020 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1024 g_unsetenv("GEEQIE_DESTINATION");
1027 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1028 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1032 ed->vd ? &standard_output : NULL,
1033 ed->vd ? &standard_error : NULL,
1036 g_free(working_directory);
1038 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1043 g_child_watch_add(pid, editor_child_exit_cb, ed);
1053 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1054 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1060 GIOChannel *channel_output;
1061 GIOChannel *channel_error;
1063 channel_output = g_io_channel_unix_new(standard_output);
1064 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1065 g_io_channel_set_encoding(channel_output, NULL, NULL);
1067 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1068 editor_verbose_io_cb, ed, NULL);
1069 g_io_channel_unref(channel_output);
1071 channel_error = g_io_channel_unix_new(standard_error);
1072 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1073 g_io_channel_set_encoding(channel_error, NULL, NULL);
1075 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1076 editor_verbose_io_cb, ed, NULL);
1077 g_io_channel_unref(channel_error);
1083 return EDITOR_ERRORS(ed->flags);
1086 static EditorFlags editor_command_next_start(EditorData *ed)
1088 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1090 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1095 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1099 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1100 editor_verbose_window_progress(ed, fd->path);
1102 editor_verbose_window_progress(ed, _("running..."));
1106 error = editor_command_one(ed->editor, ed->list, ed);
1107 if (!error && ed->vd)
1109 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1110 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1112 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1113 editor_verbose_window_fill(ed->vd, "\n", 1);
1120 /* command was not started, call the finish immediately */
1121 return editor_command_next_finish(ed, 0);
1124 /* everything is done */
1125 return editor_command_done(ed);
1128 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1130 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1133 ed->flags |= EDITOR_ERROR_STATUS;
1135 if (ed->flags & EDITOR_FOR_EACH)
1137 /* handle the first element from the list */
1138 GList *fd_element = ed->list;
1140 ed->list = g_list_remove_link(ed->list, fd_element);
1143 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1144 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1146 filelist_free(fd_element);
1150 /* handle whole list */
1152 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1153 filelist_free(ed->list);
1159 case EDITOR_CB_SUSPEND:
1160 return EDITOR_ERRORS(ed->flags);
1161 case EDITOR_CB_SKIP:
1162 return editor_command_done(ed);
1165 return editor_command_next_start(ed);
1168 static EditorFlags editor_command_done(EditorData *ed)
1174 if (ed->count == ed->total)
1176 editor_verbose_window_progress(ed, _("done"));
1180 editor_verbose_window_progress(ed, _("stopped by user"));
1182 editor_verbose_window_enable_close(ed->vd);
1185 /* free the not-handled items */
1188 ed->flags |= EDITOR_ERROR_SKIPPED;
1189 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1190 filelist_free(ed->list);
1196 flags = EDITOR_ERRORS(ed->flags);
1198 if (!ed->vd) editor_data_free(ed);
1203 void editor_resume(gpointer ed)
1205 editor_command_next_start(ed);
1208 void editor_skip(gpointer ed)
1210 editor_command_done(ed);
1213 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1216 EditorFlags flags = editor->flags;
1218 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1220 ed = g_new0(EditorData, 1);
1221 ed->list = filelist_copy(list);
1223 ed->editor = editor;
1224 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1227 ed->working_directory = g_strdup(working_directory);
1229 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1230 flags |= EDITOR_VERBOSE;
1232 if (flags & EDITOR_VERBOSE)
1233 editor_verbose_window(ed, text);
1235 editor_command_next_start(ed);
1236 /* errors from editor_command_next_start will be handled via callback */
1237 return EDITOR_ERRORS(flags);
1240 gboolean is_valid_editor_command(const gchar *key)
1242 if (!key) return FALSE;
1243 return g_hash_table_lookup(editors, key) != NULL;
1246 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1249 EditorDescription *editor;
1250 if (!key) return EDITOR_ERROR_EMPTY;
1252 editor = g_hash_table_lookup(editors, key);
1254 if (!editor) return EDITOR_ERROR_EMPTY;
1255 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1257 error = editor_command_parse(editor, list, TRUE, NULL);
1259 if (EDITOR_ERRORS(error)) return error;
1261 error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1263 if (EDITOR_ERRORS(error))
1265 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1267 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1271 return EDITOR_ERRORS(error);
1274 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1276 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1279 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1284 if (!fd) return FALSE;
1286 list = g_list_append(NULL, fd);
1287 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1292 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1294 return start_editor_from_file_full(key, fd, NULL, NULL);
1297 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1299 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1302 gboolean editor_window_flag_set(const gchar *key)
1304 EditorDescription *editor;
1305 if (!key) return TRUE;
1307 editor = g_hash_table_lookup(editors, key);
1308 if (!editor) return TRUE;
1310 return !!(editor->flags & EDITOR_KEEP_FS);
1313 gboolean editor_is_filter(const gchar *key)
1315 EditorDescription *editor;
1316 if (!key) return TRUE;
1318 editor = g_hash_table_lookup(editors, key);
1319 if (!editor) return TRUE;
1321 return !!(editor->flags & EDITOR_DEST);
1324 gboolean editor_no_param(const gchar *key)
1326 EditorDescription *editor;
1327 if (!key) return FALSE;
1329 editor = g_hash_table_lookup(editors, key);
1330 if (!editor) return FALSE;
1332 return !!(editor->flags & EDITOR_NO_PARAM);
1335 gboolean editor_blocks_file(const gchar *key)
1337 EditorDescription *editor;
1338 if (!key) return FALSE;
1340 editor = g_hash_table_lookup(editors, key);
1341 if (!editor) return FALSE;
1343 /* Decide if the image file should be blocked during editor execution
1344 Editors like gimp can be used long time after the original file was
1345 saved, for editing unrelated files.
1346 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1349 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1352 const gchar *editor_get_error_str(EditorFlags flags)
1354 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1355 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1356 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1357 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1358 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1359 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1360 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1361 return _("Unknown error.");
1364 const gchar *editor_get_name(const gchar *key)
1366 EditorDescription *editor = g_hash_table_lookup(editors, key);
1368 if (!editor) return NULL;
1370 return editor->name;
1372 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */