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 "ui_fileops.h"
21 #include "ui_spinner.h"
22 #include "ui_utildlg.h"
28 #define EDITOR_WINDOW_WIDTH 500
29 #define EDITOR_WINDOW_HEIGHT 300
33 typedef struct _EditorVerboseData EditorVerboseData;
34 struct _EditorVerboseData {
36 GtkWidget *button_close;
37 GtkWidget *button_stop;
43 typedef struct _EditorData EditorData;
51 EditorVerboseData *vd;
52 EditorCallback callback;
54 const EditorDescription *editor;
55 gchar *working_directory; /* fallback if no files are given (editor_no_param) */
59 static void editor_verbose_window_progress(EditorData *ed, const gchar *text);
60 static EditorFlags editor_command_next_start(EditorData *ed);
61 static EditorFlags editor_command_next_finish(EditorData *ed, gint status);
62 static EditorFlags editor_command_done(EditorData *ed);
65 *-----------------------------------------------------------------------------
66 * external editor routines
67 *-----------------------------------------------------------------------------
70 GHashTable *editors = NULL;
71 GtkListStore *desktop_file_list;
74 #ifdef G_KEY_FILE_DESKTOP_GROUP
75 #define DESKTOP_GROUP G_KEY_FILE_DESKTOP_GROUP
77 #define DESKTOP_GROUP "Desktop Entry"
80 void editor_description_free(EditorDescription *editor)
88 g_free(editor->menu_path);
89 g_free(editor->hotkey);
90 g_free(editor->comment);
91 string_list_free(editor->ext_list);
96 static GList *editor_mime_types_to_extensions(gchar **mime_types)
98 /* FIXME: this should be rewritten to use the shared mime database, as soon as we switch to gio */
100 static const gchar *conv_table[][2] = {
101 {"application/x-ufraw", ".ufraw"},
103 {"image/bmp", ".bmp"},
104 {"image/gif", ".gif"},
105 {"image/jpeg", ".jpeg;.jpg"},
106 {"image/jpg", ".jpg;.jpeg"},
107 {"image/pcx", ".pcx"},
108 {"image/png", ".png"},
109 {"image/svg", ".svg"},
110 {"image/svg+xml", ".svg"},
111 {"image/svg+xml-compressed", ".svg"},
112 {"image/tiff", ".tiff;.tif"},
113 {"image/x-bmp", ".bmp"},
114 {"image/x-canon-crw", ".crw"},
115 {"image/x-cr2", ".cr2"},
116 {"image/x-dcraw", "%raw"},
117 {"image/x-ico", ".ico"},
118 {"image/x-mrw", ".mrw"},
119 {"image/x-MS-bmp", ".bmp"},
120 {"image/x-nef", ".nef"},
121 {"image/x-orf", ".orf"},
122 {"image/x-pcx", ".pcx"},
123 {"image/xpm", ".xpm"},
124 {"image/x-png", ".png"},
125 {"image/x-portable-anymap", ".pam"},
126 {"image/x-portable-bitmap", ".pbm"},
127 {"image/x-portable-graymap", ".pgm"},
128 {"image/x-portable-pixmap", ".ppm"},
129 {"image/x-psd", ".psd"},
130 {"image/x-raf", ".raf"},
131 {"image/x-sgi", ".sgi"},
132 {"image/x-tga", ".tga"},
133 {"image/x-xbitmap", ".xbm"},
134 {"image/x-xcf", ".xcf"},
135 {"image/x-xpixmap", ".xpm"},
136 {"image/x-x3f", ".x3f"},
142 for (i = 0; mime_types[i]; i++)
143 for (j = 0; conv_table[j][0]; j++)
144 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
145 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
150 static gboolean editor_read_desktop_file(const gchar *path)
153 EditorDescription *editor;
156 const gchar *key = filename_from_path(path);
157 gchar **categories, **only_show_in, **not_show_in;
160 gboolean category_geeqie = FALSE;
162 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
164 key_file = g_key_file_new();
165 if (!g_key_file_load_from_file(key_file, path, 0, NULL))
167 g_key_file_free(key_file);
171 type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
172 if (!type || strcmp(type, "Application") != 0)
174 /* We only consider desktop entries of Application type */
175 g_key_file_free(key_file);
181 editor = g_new0(EditorDescription, 1);
183 editor->key = g_strdup(key);
184 editor->file = g_strdup(path);
186 g_hash_table_insert(editors, editor->key, editor);
188 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
189 || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
191 editor->hidden = TRUE;
194 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
197 gboolean found = FALSE;
199 for (i = 0; categories[i]; i++)
201 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
202 if (strcmp(categories[i], "Graphics") == 0)
206 if (strcmp(categories[i], "X-Geeqie") == 0)
209 category_geeqie = TRUE;
213 if (!found) editor->ignored = TRUE;
214 g_strfreev(categories);
218 editor->ignored = TRUE;
221 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
224 gboolean found = FALSE;
226 for (i = 0; only_show_in[i]; i++)
227 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
232 if (!found) editor->ignored = TRUE;
233 g_strfreev(only_show_in);
236 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
239 gboolean found = FALSE;
241 for (i = 0; not_show_in[i]; i++)
242 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
247 if (found) editor->ignored = TRUE;
248 g_strfreev(not_show_in);
252 try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
253 if (try_exec && !editor->hidden && !editor->ignored)
255 gchar *try_exec_res = g_find_program_in_path(try_exec);
256 if (!try_exec_res) editor->hidden = TRUE;
257 g_free(try_exec_res);
263 /* ignored editors will be deleted, no need to parse the rest */
264 g_key_file_free(key_file);
268 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
269 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
271 /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
272 if (editor->icon && !g_path_is_absolute(editor->icon))
274 gchar *ext = strrchr(editor->icon, '.');
276 if (ext && strlen(ext) == 4 &&
277 (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
279 log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
280 editor->file, editor->icon);
287 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
289 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
290 if (!editor->menu_path) editor->menu_path = g_strdup("EditMenu/ExternalMenu");
292 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
294 editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
296 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
298 editor->ext_list = filter_to_list(extensions);
301 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
304 editor->ext_list = editor_mime_types_to_extensions(mime_types);
305 g_strfreev(mime_types);
306 if (!editor->ext_list) editor->hidden = TRUE;
310 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
311 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
312 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
313 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
314 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
316 editor->flags |= editor_command_parse(editor, NULL, NULL);
318 if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
320 g_key_file_free(key_file);
322 if (editor->ignored) return TRUE;
324 gtk_list_store_append(desktop_file_list, &iter);
325 gtk_list_store_set(desktop_file_list, &iter,
326 DESKTOP_FILE_COLUMN_KEY, key,
327 DESKTOP_FILE_COLUMN_NAME, editor->name,
328 DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden,
329 DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
330 DESKTOP_FILE_COLUMN_PATH, path, -1);
335 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
337 EditorDescription *editor = value;
338 return editor->hidden || editor->ignored;
341 static void editor_read_desktop_dir(const gchar *path)
347 pathl = path_from_utf8(path);
355 while ((dir = readdir(dp)) != NULL)
357 gchar *namel = dir->d_name;
359 if (g_str_has_suffix(namel, ".desktop"))
361 gchar *name = path_to_utf8(namel);
362 gchar *dpath = g_build_filename(path, name, NULL);
363 editor_read_desktop_file(dpath);
371 void editor_load_descriptions(void)
374 gchar *xdg_data_dirs;
379 if (desktop_file_list)
381 gtk_list_store_clear(desktop_file_list);
385 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);
389 g_hash_table_destroy(editors);
391 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
393 xdg_data_dirs = getenv("XDG_DATA_DIRS");
394 if (xdg_data_dirs && xdg_data_dirs[0])
395 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
397 xdg_data_dirs = g_strdup("/usr/share");
399 all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
401 g_free(xdg_data_dirs);
403 split_dirs = g_strsplit(all_dirs, ":", 0);
407 for (i = 0; split_dirs[i]; i++)
409 path = g_build_filename(split_dirs[i], "applications", NULL);
410 editor_read_desktop_dir(path);
414 g_strfreev(split_dirs);
416 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
419 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
421 GList **listp = data;
422 EditorDescription *editor = value;
424 /* do not show the special commands in any list, they are called explicitly */
425 if (strcmp(editor->key, CMD_COPY) == 0 ||
426 strcmp(editor->key, CMD_MOVE) == 0 ||
427 strcmp(editor->key, CMD_RENAME) == 0 ||
428 strcmp(editor->key, CMD_DELETE) == 0 ||
429 strcmp(editor->key, CMD_FOLDER) == 0) return;
431 *listp = g_list_prepend(*listp, editor);
434 static gint editor_sort(gconstpointer a, gconstpointer b)
436 const EditorDescription *ea = a;
437 const EditorDescription *eb = b;
440 ret = strcmp(ea->menu_path, eb->menu_path);
441 if (ret != 0) return ret;
443 return g_utf8_collate(ea->name, eb->name);
446 GList *editor_list_get(void)
448 GList *editors_list = NULL;
449 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
450 editors_list = g_list_sort(editors_list, editor_sort);
455 /* ------------------------------ */
458 static void editor_verbose_data_free(EditorData *ed)
465 static void editor_data_free(EditorData *ed)
467 editor_verbose_data_free(ed);
468 g_free(ed->working_directory);
472 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
474 EditorData *ed = data;
476 generic_dialog_close(gd);
477 editor_verbose_data_free(ed);
478 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
481 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
483 EditorData *ed = data;
486 editor_verbose_window_progress(ed, _("stopping..."));
489 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
491 vd->gd->cancel_cb = editor_verbose_window_close;
493 spinner_set_interval(vd->spinner, -1);
494 gtk_widget_set_sensitive(vd->button_stop, FALSE);
495 gtk_widget_set_sensitive(vd->button_close, TRUE);
498 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
500 EditorVerboseData *vd;
505 vd = g_new0(EditorVerboseData, 1);
507 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
510 buf = g_strdup_printf(_("Output of %s"), text);
511 generic_dialog_add_message(vd->gd, NULL, buf, NULL);
513 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
514 editor_verbose_window_stop, FALSE);
515 gtk_widget_set_sensitive(vd->button_stop, FALSE);
516 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
517 editor_verbose_window_close, TRUE);
518 gtk_widget_set_sensitive(vd->button_close, FALSE);
520 scrolled = gtk_scrolled_window_new(NULL, NULL);
521 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
522 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
523 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
524 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
525 gtk_widget_show(scrolled);
527 vd->text = gtk_text_view_new();
528 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
529 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
530 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
531 gtk_widget_show(vd->text);
533 hbox = gtk_hbox_new(FALSE, 0);
534 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
535 gtk_widget_show(hbox);
537 vd->progress = gtk_progress_bar_new();
538 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
539 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
540 gtk_widget_show(vd->progress);
542 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
543 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
544 gtk_widget_show(vd->spinner);
546 gtk_widget_show(vd->gd->dialog);
552 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
554 GtkTextBuffer *buffer;
557 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
558 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
559 gtk_text_buffer_insert(buffer, &iter, text, len);
562 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
568 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
571 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
574 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
576 EditorData *ed = data;
580 if (condition & G_IO_IN)
582 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
584 if (!g_utf8_validate(buf, count, NULL))
588 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
591 editor_verbose_window_fill(ed->vd, utf8, -1);
596 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
601 editor_verbose_window_fill(ed->vd, buf, count);
606 if (condition & (G_IO_ERR | G_IO_HUP))
608 g_io_channel_shutdown(source, TRUE, NULL);
622 static gchar *editor_command_path_parse(const FileData *fd, PathType type, const EditorDescription *editor)
626 const gchar *p = NULL;
628 string = g_string_new("");
630 if (type == PATH_FILE || type == PATH_FILE_URL)
632 GList *work = editor->ext_list;
641 gchar *ext = work->data;
644 if (strcmp(ext, "*") == 0 ||
645 g_ascii_strcasecmp(ext, fd->extension) == 0)
651 work2 = fd->sidecar_files;
654 FileData *sfd = work2->data;
657 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
668 else if (type == PATH_DEST)
670 if (fd->change && fd->change->dest)
671 p = fd->change->dest;
677 string = g_string_append(string, p);
679 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
680 pathl = path_from_utf8(string->str);
681 g_string_free(string, TRUE);
683 if (pathl && !pathl[0]) /* empty string case */
692 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
699 g_string_append_c(str, '\'');
701 g_string_append(str, "\"'");
704 for (p = s; *p != '\0'; p++)
707 g_string_append(str, "'\\''");
709 g_string_append_c(str, *p);
715 g_string_append_c(str, '\'');
717 g_string_append(str, "'\"");
724 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gchar **output)
726 EditorFlags flags = 0;
728 GString *result = NULL;
729 gboolean escape = FALSE;
730 gboolean single_quotes = FALSE;
731 gboolean double_quotes = FALSE;
734 result = g_string_new("");
736 if (editor->exec[0] == '\0')
738 flags |= EDITOR_ERROR_EMPTY;
743 /* skip leading whitespaces if any */
744 while (g_ascii_isspace(*p)) p++;
753 if (output) result = g_string_append_c(result, *p);
757 if (!single_quotes) escape = TRUE;
758 if (output) result = g_string_append_c(result, *p);
762 if (output) result = g_string_append_c(result, *p);
763 if (!single_quotes && !double_quotes)
764 single_quotes = TRUE;
765 else if (single_quotes)
766 single_quotes = FALSE;
770 if (output) result = g_string_append_c(result, *p);
771 if (!single_quotes && !double_quotes)
772 double_quotes = TRUE;
773 else if (double_quotes)
774 double_quotes = FALSE;
776 else if (*p == '%' && p[1])
784 case 'f': /* single file */
785 case 'u': /* single url */
786 flags |= EDITOR_FOR_EACH;
787 if (flags & EDITOR_SINGLE_COMMAND)
789 flags |= EDITOR_ERROR_INCOMPATIBLE;
794 /* use the first file from the list */
797 flags |= EDITOR_ERROR_NO_FILE;
800 pathl = editor_command_path_parse((FileData *)list->data,
801 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
805 flags |= EDITOR_ERROR_NO_FILE;
810 result = append_quoted(result, pathl, single_quotes, double_quotes);
818 flags |= EDITOR_SINGLE_COMMAND;
819 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
821 flags |= EDITOR_ERROR_INCOMPATIBLE;
833 FileData *fd = work->data;
834 pathl = editor_command_path_parse(fd, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
842 if (work != list) g_string_append_c(result, ' ');
843 result = append_quoted(result, pathl, single_quotes, double_quotes);
851 flags |= EDITOR_ERROR_NO_FILE;
857 if (editor->icon && *editor->icon)
861 result = g_string_append(result, "--icon ");
862 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
869 result = append_quoted(result, editor->name, single_quotes, double_quotes);
875 result = append_quoted(result, editor->file, single_quotes, double_quotes);
879 /* %% = % escaping */
880 if (output) result = g_string_append_c(result, *p);
888 /* deprecated according to spec, ignore */
891 flags |= EDITOR_ERROR_SYNTAX;
897 if (output) result = g_string_append_c(result, *p);
902 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
906 *output = g_string_free(result, FALSE);
907 DEBUG_3("Editor cmd: %s", *output);
916 g_string_free(result, TRUE);
923 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
925 EditorData *ed = data;
926 g_spawn_close_pid(pid);
929 editor_command_next_finish(ed, status);
933 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
936 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
938 gint standard_output;
943 ed->flags = editor->flags;
944 ed->flags |= editor_command_parse(editor, list, &command);
946 ok = !EDITOR_ERRORS(ed->flags);
950 ok = (options->shell.path && *options->shell.path);
951 if (!ok) log_printf("ERROR: empty shell command\n");
955 ok = (access(options->shell.path, X_OK) == 0);
956 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
959 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
964 gchar *working_directory;
968 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
969 args[n++] = options->shell.path;
970 if (options->shell.options && *options->shell.options)
971 args[n++] = options->shell.options;
975 if ((ed->flags & EDITOR_DEST) && fd->change && fd->change->dest) /* FIXME: error handling */
977 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
981 g_unsetenv("GEEQIE_DESTINATION");
984 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
985 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
989 ed->vd ? &standard_output : NULL,
990 ed->vd ? &standard_error : NULL,
993 g_free(working_directory);
995 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1000 g_child_watch_add(pid, editor_child_exit_cb, ed);
1010 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1011 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1017 GIOChannel *channel_output;
1018 GIOChannel *channel_error;
1020 channel_output = g_io_channel_unix_new(standard_output);
1021 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1022 g_io_channel_set_encoding(channel_output, NULL, NULL);
1024 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1025 editor_verbose_io_cb, ed, NULL);
1026 g_io_channel_unref(channel_output);
1028 channel_error = g_io_channel_unix_new(standard_error);
1029 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1030 g_io_channel_set_encoding(channel_error, NULL, NULL);
1032 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1033 editor_verbose_io_cb, ed, NULL);
1034 g_io_channel_unref(channel_error);
1040 return EDITOR_ERRORS(ed->flags);
1043 static EditorFlags editor_command_next_start(EditorData *ed)
1045 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1047 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1052 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1056 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1057 editor_verbose_window_progress(ed, fd->path);
1059 editor_verbose_window_progress(ed, _("running..."));
1063 error = editor_command_one(ed->editor, ed->list, ed);
1064 if (!error && ed->vd)
1066 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1067 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1069 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1070 editor_verbose_window_fill(ed->vd, "\n", 1);
1077 /* command was not started, call the finish immediately */
1078 return editor_command_next_finish(ed, 0);
1081 /* everything is done */
1082 return editor_command_done(ed);
1085 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1087 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1090 ed->flags |= EDITOR_ERROR_STATUS;
1092 if (ed->flags & EDITOR_FOR_EACH)
1094 /* handle the first element from the list */
1095 GList *fd_element = ed->list;
1097 ed->list = g_list_remove_link(ed->list, fd_element);
1100 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1101 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1103 filelist_free(fd_element);
1107 /* handle whole list */
1109 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1110 filelist_free(ed->list);
1116 case EDITOR_CB_SUSPEND:
1117 return EDITOR_ERRORS(ed->flags);
1118 case EDITOR_CB_SKIP:
1119 return editor_command_done(ed);
1122 return editor_command_next_start(ed);
1125 static EditorFlags editor_command_done(EditorData *ed)
1131 if (ed->count == ed->total)
1133 editor_verbose_window_progress(ed, _("done"));
1137 editor_verbose_window_progress(ed, _("stopped by user"));
1139 editor_verbose_window_enable_close(ed->vd);
1142 /* free the not-handled items */
1145 ed->flags |= EDITOR_ERROR_SKIPPED;
1146 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1147 filelist_free(ed->list);
1153 flags = EDITOR_ERRORS(ed->flags);
1155 if (!ed->vd) editor_data_free(ed);
1160 void editor_resume(gpointer ed)
1162 editor_command_next_start(ed);
1165 void editor_skip(gpointer ed)
1167 editor_command_done(ed);
1170 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1173 EditorFlags flags = editor->flags;
1175 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1177 ed = g_new0(EditorData, 1);
1178 ed->list = filelist_copy(list);
1180 ed->editor = editor;
1181 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1184 ed->working_directory = g_strdup(working_directory);
1186 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1187 flags |= EDITOR_VERBOSE;
1189 if (flags & EDITOR_VERBOSE)
1190 editor_verbose_window(ed, text);
1192 editor_command_next_start(ed);
1193 /* errors from editor_command_next_start will be handled via callback */
1194 return EDITOR_ERRORS(flags);
1197 gboolean is_valid_editor_command(const gchar *key)
1199 if (!key) return FALSE;
1200 return g_hash_table_lookup(editors, key) != NULL;
1203 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1206 EditorDescription *editor;
1207 if (!key) return FALSE;
1209 editor = g_hash_table_lookup(editors, key);
1211 if (!editor) return FALSE;
1212 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return FALSE;
1214 error = editor_command_start(editor, editor->name, list, working_directory, cb, data);
1216 if (EDITOR_ERRORS(error))
1218 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1220 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1227 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1229 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1232 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1237 if (!fd) return FALSE;
1239 list = g_list_append(NULL, fd);
1240 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1245 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1247 return start_editor_from_file_full(key, fd, NULL, NULL);
1250 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1252 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1255 gboolean editor_window_flag_set(const gchar *key)
1257 EditorDescription *editor;
1258 if (!key) return TRUE;
1260 editor = g_hash_table_lookup(editors, key);
1261 if (!editor) return TRUE;
1263 return !!(editor->flags & EDITOR_KEEP_FS);
1266 gboolean editor_is_filter(const gchar *key)
1268 EditorDescription *editor;
1269 if (!key) return TRUE;
1271 editor = g_hash_table_lookup(editors, key);
1272 if (!editor) return TRUE;
1274 return !!(editor->flags & EDITOR_DEST);
1277 gboolean editor_no_param(const gchar *key)
1279 EditorDescription *editor;
1280 if (!key) return FALSE;
1282 editor = g_hash_table_lookup(editors, key);
1283 if (!editor) return FALSE;
1285 return !!(editor->flags & EDITOR_NO_PARAM);
1288 gboolean editor_blocks_file(const gchar *key)
1290 EditorDescription *editor;
1291 if (!key) return FALSE;
1293 editor = g_hash_table_lookup(editors, key);
1294 if (!editor) return FALSE;
1296 /* Decide if the image file should be blocked during editor execution
1297 Editors like gimp can be used long time after the original file was
1298 saved, for editing unrelated files.
1299 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1302 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1305 const gchar *editor_get_error_str(EditorFlags flags)
1307 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1308 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1309 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1310 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1311 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1312 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1313 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1314 return _("Unknown error.");
1317 const gchar *editor_get_name(const gchar *key)
1319 EditorDescription *editor = g_hash_table_lookup(editors, key);
1321 if (!editor) return NULL;
1323 return editor->name;
1325 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */