4 * Copyright (C) 2008 - 2012 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"},
138 {"application/x-ptoptimizer-script", ".pto"},
144 for (i = 0; mime_types[i]; i++)
145 for (j = 0; conv_table[j][0]; j++)
146 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
147 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
152 gboolean editor_read_desktop_file(const gchar *path)
155 EditorDescription *editor;
158 const gchar *key = filename_from_path(path);
159 gchar **categories, **only_show_in, **not_show_in;
162 gboolean category_geeqie = FALSE;
164 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
166 key_file = g_key_file_new();
167 if (!g_key_file_load_from_file(key_file, path, 0, NULL))
169 g_key_file_free(key_file);
173 type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
174 if (!type || strcmp(type, "Application") != 0)
176 /* We only consider desktop entries of Application type */
177 g_key_file_free(key_file);
183 editor = g_new0(EditorDescription, 1);
185 editor->key = g_strdup(key);
186 editor->file = g_strdup(path);
188 g_hash_table_insert(editors, editor->key, editor);
190 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
191 || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
193 editor->hidden = TRUE;
196 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
199 gboolean found = FALSE;
201 for (i = 0; categories[i]; i++)
203 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
204 if (strcmp(categories[i], "Graphics") == 0)
208 if (strcmp(categories[i], "X-Geeqie") == 0)
211 category_geeqie = TRUE;
215 if (!found) editor->ignored = TRUE;
216 g_strfreev(categories);
220 editor->ignored = TRUE;
223 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
226 gboolean found = FALSE;
228 for (i = 0; only_show_in[i]; i++)
229 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
234 if (!found) editor->ignored = TRUE;
235 g_strfreev(only_show_in);
238 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
241 gboolean found = FALSE;
243 for (i = 0; not_show_in[i]; i++)
244 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
249 if (found) editor->ignored = TRUE;
250 g_strfreev(not_show_in);
254 try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
255 if (try_exec && !editor->hidden && !editor->ignored)
257 gchar *try_exec_res = g_find_program_in_path(try_exec);
258 if (!try_exec_res) editor->hidden = TRUE;
259 g_free(try_exec_res);
265 /* ignored editors will be deleted, no need to parse the rest */
266 g_key_file_free(key_file);
270 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
271 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
273 /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
274 if (editor->icon && !g_path_is_absolute(editor->icon))
276 gchar *ext = strrchr(editor->icon, '.');
278 if (ext && strlen(ext) == 4 &&
279 (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
281 log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
282 editor->file, editor->icon);
288 if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon))
290 g_free(editor->icon);
294 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
296 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
297 if (!editor->menu_path) editor->menu_path = g_strdup("EditMenu/ExternalMenu");
299 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
301 editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
303 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
305 editor->ext_list = filter_to_list(extensions);
308 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
311 editor->ext_list = editor_mime_types_to_extensions(mime_types);
312 g_strfreev(mime_types);
313 if (!editor->ext_list) editor->hidden = TRUE;
317 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
318 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
319 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
320 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
321 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
323 editor->flags |= editor_command_parse(editor, NULL, FALSE, NULL);
325 if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
327 g_key_file_free(key_file);
329 if (editor->ignored) return TRUE;
331 gtk_list_store_append(desktop_file_list, &iter);
332 gtk_list_store_set(desktop_file_list, &iter,
333 DESKTOP_FILE_COLUMN_KEY, key,
334 DESKTOP_FILE_COLUMN_NAME, editor->name,
335 DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden ? _("yes") : _("no"),
336 DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
337 DESKTOP_FILE_COLUMN_PATH, path, -1);
342 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
344 EditorDescription *editor = value;
345 return editor->hidden || editor->ignored;
348 void editor_table_finish(void)
350 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
351 editors_finished = TRUE;
354 void editor_table_clear(void)
356 if (desktop_file_list)
358 gtk_list_store_clear(desktop_file_list);
362 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);
366 g_hash_table_destroy(editors);
368 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
369 editors_finished = FALSE;
372 static GList *editor_add_desktop_dir(GList *list, const gchar *path)
378 pathl = path_from_utf8(path);
386 while ((dir = readdir(dp)) != NULL)
388 gchar *namel = dir->d_name;
390 if (g_str_has_suffix(namel, ".desktop"))
392 gchar *name = path_to_utf8(namel);
393 gchar *dpath = g_build_filename(path, name, NULL);
394 list = g_list_prepend(list, dpath);
402 GList *editor_get_desktop_files(void)
405 gchar *xdg_data_dirs;
411 xdg_data_dirs = getenv("XDG_DATA_DIRS");
412 if (xdg_data_dirs && xdg_data_dirs[0])
413 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
415 xdg_data_dirs = g_strdup("/usr/share");
417 all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
419 g_free(xdg_data_dirs);
421 split_dirs = g_strsplit(all_dirs, ":", 0);
425 for (i = 0; split_dirs[i]; i++);
426 for (--i; i >= 0; i--)
428 path = g_build_filename(split_dirs[i], "applications", NULL);
429 list = editor_add_desktop_dir(list, path);
433 g_strfreev(split_dirs);
437 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
439 GList **listp = data;
440 EditorDescription *editor = value;
442 /* do not show the special commands in any list, they are called explicitly */
443 if (strcmp(editor->key, CMD_COPY) == 0 ||
444 strcmp(editor->key, CMD_MOVE) == 0 ||
445 strcmp(editor->key, CMD_RENAME) == 0 ||
446 strcmp(editor->key, CMD_DELETE) == 0 ||
447 strcmp(editor->key, CMD_FOLDER) == 0) return;
449 *listp = g_list_prepend(*listp, editor);
452 static gint editor_sort(gconstpointer a, gconstpointer b)
454 const EditorDescription *ea = a;
455 const EditorDescription *eb = b;
458 ret = strcmp(ea->menu_path, eb->menu_path);
459 if (ret != 0) return ret;
461 return g_utf8_collate(ea->name, eb->name);
464 GList *editor_list_get(void)
466 GList *editors_list = NULL;
468 if (!editors_finished) return NULL;
470 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
471 editors_list = g_list_sort(editors_list, editor_sort);
476 /* ------------------------------ */
479 static void editor_verbose_data_free(EditorData *ed)
486 static void editor_data_free(EditorData *ed)
488 editor_verbose_data_free(ed);
489 g_free(ed->working_directory);
493 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
495 EditorData *ed = data;
497 generic_dialog_close(gd);
498 editor_verbose_data_free(ed);
499 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
502 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
504 EditorData *ed = data;
507 editor_verbose_window_progress(ed, _("stopping..."));
510 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
512 vd->gd->cancel_cb = editor_verbose_window_close;
514 spinner_set_interval(vd->spinner, -1);
515 gtk_widget_set_sensitive(vd->button_stop, FALSE);
516 gtk_widget_set_sensitive(vd->button_close, TRUE);
519 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
521 EditorVerboseData *vd;
526 vd = g_new0(EditorVerboseData, 1);
528 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
531 buf = g_strdup_printf(_("Output of %s"), text);
532 generic_dialog_add_message(vd->gd, NULL, buf, NULL);
534 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
535 editor_verbose_window_stop, FALSE);
536 gtk_widget_set_sensitive(vd->button_stop, FALSE);
537 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
538 editor_verbose_window_close, TRUE);
539 gtk_widget_set_sensitive(vd->button_close, FALSE);
541 scrolled = gtk_scrolled_window_new(NULL, NULL);
542 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
543 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
544 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
545 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
546 gtk_widget_show(scrolled);
548 vd->text = gtk_text_view_new();
549 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
550 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
551 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
552 gtk_widget_show(vd->text);
554 hbox = gtk_hbox_new(FALSE, 0);
555 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
556 gtk_widget_show(hbox);
558 vd->progress = gtk_progress_bar_new();
559 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
560 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
561 gtk_widget_show(vd->progress);
563 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
564 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
565 gtk_widget_show(vd->spinner);
567 gtk_widget_show(vd->gd->dialog);
573 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
575 GtkTextBuffer *buffer;
578 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
579 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
580 gtk_text_buffer_insert(buffer, &iter, text, len);
583 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
589 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
592 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
595 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
597 EditorData *ed = data;
601 if (condition & G_IO_IN)
603 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
605 if (!g_utf8_validate(buf, count, NULL))
609 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
612 editor_verbose_window_fill(ed->vd, utf8, -1);
617 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
622 editor_verbose_window_fill(ed->vd, buf, count);
627 if (condition & (G_IO_ERR | G_IO_HUP))
629 g_io_channel_shutdown(source, TRUE, NULL);
643 static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
647 const gchar *p = NULL;
649 DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
651 string = g_string_new("");
653 if (type == PATH_FILE || type == PATH_FILE_URL)
655 GList *work = editor->ext_list;
664 gchar *ext = work->data;
667 if (strcmp(ext, "*") == 0 ||
668 g_ascii_strcasecmp(ext, fd->extension) == 0)
674 work2 = consider_sidecars ? fd->sidecar_files : NULL;
677 FileData *sfd = work2->data;
680 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
691 else if (type == PATH_DEST)
693 if (fd->change && fd->change->dest)
694 p = fd->change->dest;
700 string = g_string_append(string, p);
702 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
703 pathl = path_from_utf8(string->str);
704 g_string_free(string, TRUE);
706 if (pathl && !pathl[0]) /* empty string case */
712 DEBUG_2("editor_command_path_parse: return %s", pathl);
716 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
723 g_string_append_c(str, '\'');
725 g_string_append(str, "\"'");
728 for (p = s; *p != '\0'; p++)
731 g_string_append(str, "'\\''");
733 g_string_append_c(str, *p);
739 g_string_append_c(str, '\'');
741 g_string_append(str, "'\"");
748 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
750 EditorFlags flags = 0;
752 GString *result = NULL;
753 gboolean escape = FALSE;
754 gboolean single_quotes = FALSE;
755 gboolean double_quotes = FALSE;
757 DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
760 result = g_string_new("");
762 if (editor->exec[0] == '\0')
764 flags |= EDITOR_ERROR_EMPTY;
769 /* skip leading whitespaces if any */
770 while (g_ascii_isspace(*p)) p++;
779 if (output) result = g_string_append_c(result, *p);
783 if (!single_quotes) escape = TRUE;
784 if (output) result = g_string_append_c(result, *p);
788 if (output) result = g_string_append_c(result, *p);
789 if (!single_quotes && !double_quotes)
790 single_quotes = TRUE;
791 else if (single_quotes)
792 single_quotes = FALSE;
796 if (output) result = g_string_append_c(result, *p);
797 if (!single_quotes && !double_quotes)
798 double_quotes = TRUE;
799 else if (double_quotes)
800 double_quotes = FALSE;
802 else if (*p == '%' && p[1])
810 case 'f': /* single file */
811 case 'u': /* single url */
812 flags |= EDITOR_FOR_EACH;
813 if (flags & EDITOR_SINGLE_COMMAND)
815 flags |= EDITOR_ERROR_INCOMPATIBLE;
820 /* use the first file from the list */
823 flags |= EDITOR_ERROR_NO_FILE;
826 pathl = editor_command_path_parse((FileData *)list->data,
828 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
832 /* just testing, check also the rest of the list (like with F and U)
833 any matching file is OK */
834 GList *work = list->next;
836 while (!pathl && work)
838 FileData *fd = work->data;
839 pathl = editor_command_path_parse(fd,
841 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
849 flags |= EDITOR_ERROR_NO_FILE;
854 result = append_quoted(result, pathl, single_quotes, double_quotes);
862 flags |= EDITOR_SINGLE_COMMAND;
863 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
865 flags |= EDITOR_ERROR_INCOMPATIBLE;
877 FileData *fd = work->data;
878 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
886 if (work != list) g_string_append_c(result, ' ');
887 result = append_quoted(result, pathl, single_quotes, double_quotes);
895 flags |= EDITOR_ERROR_NO_FILE;
901 if (editor->icon && *editor->icon)
905 result = g_string_append(result, "--icon ");
906 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
913 result = append_quoted(result, editor->name, single_quotes, double_quotes);
919 result = append_quoted(result, editor->file, single_quotes, double_quotes);
923 /* %% = % escaping */
924 if (output) result = g_string_append_c(result, *p);
932 /* deprecated according to spec, ignore */
935 flags |= EDITOR_ERROR_SYNTAX;
941 if (output) result = g_string_append_c(result, *p);
946 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
950 *output = g_string_free(result, FALSE);
951 DEBUG_3("Editor cmd: %s", *output);
960 g_string_free(result, TRUE);
967 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
969 EditorData *ed = data;
970 g_spawn_close_pid(pid);
973 editor_command_next_finish(ed, status);
977 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
980 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
982 gint standard_output;
987 ed->flags = editor->flags;
988 ed->flags |= editor_command_parse(editor, list, TRUE, &command);
990 ok = !EDITOR_ERRORS(ed->flags);
994 ok = (options->shell.path && *options->shell.path);
995 if (!ok) log_printf("ERROR: empty shell command\n");
999 ok = (access(options->shell.path, X_OK) == 0);
1000 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1003 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1008 gchar *working_directory;
1012 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1013 args[n++] = options->shell.path;
1014 if (options->shell.options && *options->shell.options)
1015 args[n++] = options->shell.options;
1016 args[n++] = command;
1019 if ((ed->flags & EDITOR_DEST) && fd->change && fd->change->dest) /* FIXME: error handling */
1021 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1025 g_unsetenv("GEEQIE_DESTINATION");
1028 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1029 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1033 ed->vd ? &standard_output : NULL,
1034 ed->vd ? &standard_error : NULL,
1037 g_free(working_directory);
1039 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1044 g_child_watch_add(pid, editor_child_exit_cb, ed);
1054 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1055 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1061 GIOChannel *channel_output;
1062 GIOChannel *channel_error;
1064 channel_output = g_io_channel_unix_new(standard_output);
1065 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1066 g_io_channel_set_encoding(channel_output, NULL, NULL);
1068 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1069 editor_verbose_io_cb, ed, NULL);
1070 g_io_channel_unref(channel_output);
1072 channel_error = g_io_channel_unix_new(standard_error);
1073 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1074 g_io_channel_set_encoding(channel_error, NULL, NULL);
1076 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1077 editor_verbose_io_cb, ed, NULL);
1078 g_io_channel_unref(channel_error);
1084 return EDITOR_ERRORS(ed->flags);
1087 static EditorFlags editor_command_next_start(EditorData *ed)
1089 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1091 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1096 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1100 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1101 editor_verbose_window_progress(ed, fd->path);
1103 editor_verbose_window_progress(ed, _("running..."));
1107 error = editor_command_one(ed->editor, ed->list, ed);
1108 if (!error && ed->vd)
1110 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1111 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1113 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1114 editor_verbose_window_fill(ed->vd, "\n", 1);
1121 /* command was not started, call the finish immediately */
1122 return editor_command_next_finish(ed, 0);
1125 /* everything is done */
1126 return editor_command_done(ed);
1129 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1131 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1134 ed->flags |= EDITOR_ERROR_STATUS;
1136 if (ed->flags & EDITOR_FOR_EACH)
1138 /* handle the first element from the list */
1139 GList *fd_element = ed->list;
1141 ed->list = g_list_remove_link(ed->list, fd_element);
1144 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1145 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1147 filelist_free(fd_element);
1151 /* handle whole list */
1153 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1154 filelist_free(ed->list);
1160 case EDITOR_CB_SUSPEND:
1161 return EDITOR_ERRORS(ed->flags);
1162 case EDITOR_CB_SKIP:
1163 return editor_command_done(ed);
1166 return editor_command_next_start(ed);
1169 static EditorFlags editor_command_done(EditorData *ed)
1175 if (ed->count == ed->total)
1177 editor_verbose_window_progress(ed, _("done"));
1181 editor_verbose_window_progress(ed, _("stopped by user"));
1183 editor_verbose_window_enable_close(ed->vd);
1186 /* free the not-handled items */
1189 ed->flags |= EDITOR_ERROR_SKIPPED;
1190 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1191 filelist_free(ed->list);
1197 flags = EDITOR_ERRORS(ed->flags);
1199 if (!ed->vd) editor_data_free(ed);
1204 void editor_resume(gpointer ed)
1206 editor_command_next_start(ed);
1209 void editor_skip(gpointer ed)
1211 editor_command_done(ed);
1214 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1217 EditorFlags flags = editor->flags;
1219 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1221 ed = g_new0(EditorData, 1);
1222 ed->list = filelist_copy(list);
1224 ed->editor = editor;
1225 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1228 ed->working_directory = g_strdup(working_directory);
1230 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1231 flags |= EDITOR_VERBOSE;
1233 if (flags & EDITOR_VERBOSE)
1234 editor_verbose_window(ed, text);
1236 editor_command_next_start(ed);
1237 /* errors from editor_command_next_start will be handled via callback */
1238 return EDITOR_ERRORS(flags);
1241 gboolean is_valid_editor_command(const gchar *key)
1243 if (!key) return FALSE;
1244 return g_hash_table_lookup(editors, key) != NULL;
1247 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1250 EditorDescription *editor;
1251 if (!key) return EDITOR_ERROR_EMPTY;
1253 editor = g_hash_table_lookup(editors, key);
1255 if (!editor) return EDITOR_ERROR_EMPTY;
1256 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1258 error = editor_command_parse(editor, list, TRUE, NULL);
1260 if (EDITOR_ERRORS(error)) return error;
1262 error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1264 if (EDITOR_ERRORS(error))
1266 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1268 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1272 return EDITOR_ERRORS(error);
1275 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1277 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1280 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1285 if (!fd) return FALSE;
1287 list = g_list_append(NULL, fd);
1288 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1293 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1295 return start_editor_from_file_full(key, fd, NULL, NULL);
1298 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1300 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1303 gboolean editor_window_flag_set(const gchar *key)
1305 EditorDescription *editor;
1306 if (!key) return TRUE;
1308 editor = g_hash_table_lookup(editors, key);
1309 if (!editor) return TRUE;
1311 return !!(editor->flags & EDITOR_KEEP_FS);
1314 gboolean editor_is_filter(const gchar *key)
1316 EditorDescription *editor;
1317 if (!key) return TRUE;
1319 editor = g_hash_table_lookup(editors, key);
1320 if (!editor) return TRUE;
1322 return !!(editor->flags & EDITOR_DEST);
1325 gboolean editor_no_param(const gchar *key)
1327 EditorDescription *editor;
1328 if (!key) return FALSE;
1330 editor = g_hash_table_lookup(editors, key);
1331 if (!editor) return FALSE;
1333 return !!(editor->flags & EDITOR_NO_PARAM);
1336 gboolean editor_blocks_file(const gchar *key)
1338 EditorDescription *editor;
1339 if (!key) return FALSE;
1341 editor = g_hash_table_lookup(editors, key);
1342 if (!editor) return FALSE;
1344 /* Decide if the image file should be blocked during editor execution
1345 Editors like gimp can be used long time after the original file was
1346 saved, for editing unrelated files.
1347 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1350 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1353 const gchar *editor_get_error_str(EditorFlags flags)
1355 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1356 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1357 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1358 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1359 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1360 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1361 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1362 return _("Unknown error.");
1365 const gchar *editor_get_name(const gchar *key)
1367 EditorDescription *editor = g_hash_table_lookup(editors, key);
1369 if (!editor) return NULL;
1371 return editor->name;
1373 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */