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;
58 static void editor_verbose_window_progress(EditorData *ed, const gchar *text);
59 static EditorFlags editor_command_next_start(EditorData *ed);
60 static EditorFlags editor_command_next_finish(EditorData *ed, gint status);
61 static EditorFlags editor_command_done(EditorData *ed);
64 *-----------------------------------------------------------------------------
65 * external editor routines
66 *-----------------------------------------------------------------------------
69 GHashTable *editors = NULL;
70 GtkListStore *desktop_file_list;
73 #ifdef G_KEY_FILE_DESKTOP_GROUP
74 #define DESKTOP_GROUP G_KEY_FILE_DESKTOP_GROUP
76 #define DESKTOP_GROUP "Desktop Entry"
79 void editor_description_free(EditorDescription *editor)
87 g_free(editor->menu_path);
88 g_free(editor->hotkey);
89 g_free(editor->comment);
90 string_list_free(editor->ext_list);
95 static GList *editor_mime_types_to_extensions(gchar **mime_types)
97 /* FIXME: this should be rewritten to use the shared mime database, as soon as we switch to gio */
99 static const gchar *conv_table[][2] = {
100 {"application/x-ufraw", ".ufraw"},
102 {"image/bmp", ".bmp"},
103 {"image/gif", ".gif"},
104 {"image/jpeg", ".jpeg;.jpg"},
105 {"image/jpg", ".jpg;.jpeg"},
106 {"image/pcx", ".pcx"},
107 {"image/png", ".png"},
108 {"image/svg", ".svg"},
109 {"image/svg+xml", ".svg"},
110 {"image/svg+xml-compressed", ".svg"},
111 {"image/tiff", ".tiff;.tif"},
112 {"image/x-bmp", ".bmp"},
113 {"image/x-canon-crw", ".crw"},
114 {"image/x-cr2", ".cr2"},
115 {"image/x-dcraw", "%raw"},
116 {"image/x-ico", ".ico"},
117 {"image/x-mrw", ".mrw"},
118 {"image/x-MS-bmp", ".bmp"},
119 {"image/x-nef", ".nef"},
120 {"image/x-orf", ".orf"},
121 {"image/x-pcx", ".pcx"},
122 {"image/xpm", ".xpm"},
123 {"image/x-png", ".png"},
124 {"image/x-portable-anymap", ".pam"},
125 {"image/x-portable-bitmap", ".pbm"},
126 {"image/x-portable-graymap", ".pgm"},
127 {"image/x-portable-pixmap", ".ppm"},
128 {"image/x-psd", ".psd"},
129 {"image/x-raf", ".raf"},
130 {"image/x-sgi", ".sgi"},
131 {"image/x-tga", ".tga"},
132 {"image/x-xbitmap", ".xbm"},
133 {"image/x-xcf", ".xcf"},
134 {"image/x-xpixmap", ".xpm"},
135 {"image/x-x3f", ".x3f"},
141 for (i = 0; mime_types[i]; i++)
142 for (j = 0; conv_table[j][0]; j++)
143 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
144 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
149 static gboolean editor_read_desktop_file(const gchar *path)
152 EditorDescription *editor;
155 const gchar *key = filename_from_path(path);
156 gchar **categories, **only_show_in, **not_show_in;
159 gboolean category_geeqie = FALSE;
161 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
163 key_file = g_key_file_new();
164 if (!g_key_file_load_from_file(key_file, path, 0, NULL))
166 g_key_file_free(key_file);
170 type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
171 if (!type || strcmp(type, "Application") != 0)
173 /* We only consider desktop entries of Application type */
174 g_key_file_free(key_file);
180 editor = g_new0(EditorDescription, 1);
182 editor->key = g_strdup(key);
183 editor->file = g_strdup(path);
185 g_hash_table_insert(editors, editor->key, editor);
187 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
188 || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
190 editor->hidden = TRUE;
193 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
196 gboolean found = FALSE;
198 for (i = 0; categories[i]; i++)
200 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
201 if (strcmp(categories[i], "Graphics") == 0)
205 if (strcmp(categories[i], "X-Geeqie") == 0)
208 category_geeqie = TRUE;
212 if (!found) editor->ignored = TRUE;
213 g_strfreev(categories);
217 editor->ignored = TRUE;
220 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
223 gboolean found = FALSE;
225 for (i = 0; only_show_in[i]; i++)
226 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
231 if (!found) editor->ignored = TRUE;
232 g_strfreev(only_show_in);
235 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
238 gboolean found = FALSE;
240 for (i = 0; not_show_in[i]; i++)
241 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
246 if (found) editor->ignored = TRUE;
247 g_strfreev(not_show_in);
251 try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
252 if (try_exec && !editor->hidden && !editor->ignored)
254 gchar *try_exec_res = g_find_program_in_path(try_exec);
255 if (!try_exec_res) editor->hidden = TRUE;
256 g_free(try_exec_res);
262 /* ignored editors will be deleted, no need to parse the rest */
263 g_key_file_free(key_file);
267 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
268 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
270 /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
271 if (editor->icon && !g_path_is_absolute(editor->icon))
273 gchar *ext = strrchr(editor->icon, '.');
275 if (ext && strlen(ext) == 4 &&
276 (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
278 log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
279 editor->file, editor->icon);
286 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
288 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
289 if (!editor->menu_path) editor->menu_path = g_strdup("EditMenu/ExternalMenu");
291 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
293 editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
295 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
297 editor->ext_list = filter_to_list(extensions);
300 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
303 editor->ext_list = editor_mime_types_to_extensions(mime_types);
304 g_strfreev(mime_types);
305 if (!editor->ext_list) editor->hidden = TRUE;
309 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
310 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
311 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
312 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
313 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
315 editor->flags |= editor_command_parse(editor, NULL, NULL);
317 if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
319 g_key_file_free(key_file);
321 if (editor->ignored) return TRUE;
323 gtk_list_store_append(desktop_file_list, &iter);
324 gtk_list_store_set(desktop_file_list, &iter,
325 DESKTOP_FILE_COLUMN_KEY, key,
326 DESKTOP_FILE_COLUMN_NAME, editor->name,
327 DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden,
328 DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
329 DESKTOP_FILE_COLUMN_PATH, path, -1);
334 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
336 EditorDescription *editor = value;
337 return editor->hidden || editor->ignored;
340 static void editor_read_desktop_dir(const gchar *path)
346 pathl = path_from_utf8(path);
354 while ((dir = readdir(dp)) != NULL)
356 gchar *namel = dir->d_name;
358 if (g_str_has_suffix(namel, ".desktop"))
360 gchar *name = path_to_utf8(namel);
361 gchar *dpath = g_build_filename(path, name, NULL);
362 editor_read_desktop_file(dpath);
370 void editor_load_descriptions(void)
373 gchar *xdg_data_dirs;
378 if (desktop_file_list)
380 gtk_list_store_clear(desktop_file_list);
384 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);
388 g_hash_table_destroy(editors);
390 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
392 xdg_data_dirs = getenv("XDG_DATA_DIRS");
393 if (xdg_data_dirs && xdg_data_dirs[0])
394 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
396 xdg_data_dirs = g_strdup("/usr/share");
398 all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
400 g_free(xdg_data_dirs);
402 split_dirs = g_strsplit(all_dirs, ":", 0);
406 for (i = 0; split_dirs[i]; i++)
408 path = g_build_filename(split_dirs[i], "applications", NULL);
409 editor_read_desktop_dir(path);
413 g_strfreev(split_dirs);
415 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
418 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
420 GList **listp = data;
421 EditorDescription *editor = value;
423 /* do not show the special commands in any list, they are called explicitly */
424 if (strcmp(editor->key, CMD_COPY) == 0 ||
425 strcmp(editor->key, CMD_MOVE) == 0 ||
426 strcmp(editor->key, CMD_RENAME) == 0 ||
427 strcmp(editor->key, CMD_DELETE) == 0 ||
428 strcmp(editor->key, CMD_FOLDER) == 0) return;
430 *listp = g_list_prepend(*listp, editor);
433 static gint editor_sort(gconstpointer a, gconstpointer b)
435 const EditorDescription *ea = a;
436 const EditorDescription *eb = b;
439 ret = strcmp(ea->menu_path, eb->menu_path);
440 if (ret != 0) return ret;
442 return g_utf8_collate(ea->name, eb->name);
445 GList *editor_list_get(void)
447 GList *editors_list = NULL;
448 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
449 editors_list = g_list_sort(editors_list, editor_sort);
454 /* ------------------------------ */
457 static void editor_verbose_data_free(EditorData *ed)
464 static void editor_data_free(EditorData *ed)
466 editor_verbose_data_free(ed);
470 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
472 EditorData *ed = data;
474 generic_dialog_close(gd);
475 editor_verbose_data_free(ed);
476 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
479 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
481 EditorData *ed = data;
484 editor_verbose_window_progress(ed, _("stopping..."));
487 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
489 vd->gd->cancel_cb = editor_verbose_window_close;
491 spinner_set_interval(vd->spinner, -1);
492 gtk_widget_set_sensitive(vd->button_stop, FALSE);
493 gtk_widget_set_sensitive(vd->button_close, TRUE);
496 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
498 EditorVerboseData *vd;
503 vd = g_new0(EditorVerboseData, 1);
505 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
508 buf = g_strdup_printf(_("Output of %s"), text);
509 generic_dialog_add_message(vd->gd, NULL, buf, NULL);
511 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
512 editor_verbose_window_stop, FALSE);
513 gtk_widget_set_sensitive(vd->button_stop, FALSE);
514 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
515 editor_verbose_window_close, TRUE);
516 gtk_widget_set_sensitive(vd->button_close, FALSE);
518 scrolled = gtk_scrolled_window_new(NULL, NULL);
519 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
520 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
521 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
522 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
523 gtk_widget_show(scrolled);
525 vd->text = gtk_text_view_new();
526 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
527 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
528 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
529 gtk_widget_show(vd->text);
531 hbox = gtk_hbox_new(FALSE, 0);
532 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
533 gtk_widget_show(hbox);
535 vd->progress = gtk_progress_bar_new();
536 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
537 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
538 gtk_widget_show(vd->progress);
540 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
541 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
542 gtk_widget_show(vd->spinner);
544 gtk_widget_show(vd->gd->dialog);
550 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
552 GtkTextBuffer *buffer;
555 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
556 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
557 gtk_text_buffer_insert(buffer, &iter, text, len);
560 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
566 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
569 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
572 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
574 EditorData *ed = data;
578 if (condition & G_IO_IN)
580 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
582 if (!g_utf8_validate(buf, count, NULL))
586 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
589 editor_verbose_window_fill(ed->vd, utf8, -1);
594 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
599 editor_verbose_window_fill(ed->vd, buf, count);
604 if (condition & (G_IO_ERR | G_IO_HUP))
606 g_io_channel_shutdown(source, TRUE, NULL);
620 static gchar *editor_command_path_parse(const FileData *fd, PathType type, const EditorDescription *editor)
624 const gchar *p = NULL;
626 string = g_string_new("");
628 if (type == PATH_FILE || type == PATH_FILE_URL)
630 GList *work = editor->ext_list;
639 gchar *ext = work->data;
642 if (strcmp(ext, "*") == 0 ||
643 g_ascii_strcasecmp(ext, fd->extension) == 0)
649 work2 = fd->sidecar_files;
652 FileData *sfd = work2->data;
655 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
666 else if (type == PATH_DEST)
668 if (fd->change && fd->change->dest)
669 p = fd->change->dest;
675 string = g_string_append(string, p);
677 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
678 pathl = path_from_utf8(string->str);
679 g_string_free(string, TRUE);
681 if (pathl && !pathl[0]) /* empty string case */
690 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
697 g_string_append_c(str, '\'');
699 g_string_append(str, "\"'");
702 for (p = s; *p != '\0'; p++)
705 g_string_append(str, "'\\''");
707 g_string_append_c(str, *p);
713 g_string_append_c(str, '\'');
715 g_string_append(str, "'\"");
722 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gchar **output)
724 EditorFlags flags = 0;
726 GString *result = NULL;
727 gboolean escape = FALSE;
728 gboolean single_quotes = FALSE;
729 gboolean double_quotes = FALSE;
732 result = g_string_new("");
734 if (editor->exec[0] == '\0')
736 flags |= EDITOR_ERROR_EMPTY;
741 /* skip leading whitespaces if any */
742 while (g_ascii_isspace(*p)) p++;
751 if (output) result = g_string_append_c(result, *p);
755 if (!single_quotes) escape = TRUE;
756 if (output) result = g_string_append_c(result, *p);
760 if (output) result = g_string_append_c(result, *p);
761 if (!single_quotes && !double_quotes)
762 single_quotes = TRUE;
763 else if (single_quotes)
764 single_quotes = FALSE;
768 if (output) result = g_string_append_c(result, *p);
769 if (!single_quotes && !double_quotes)
770 double_quotes = TRUE;
771 else if (double_quotes)
772 double_quotes = FALSE;
774 else if (*p == '%' && p[1])
782 case 'f': /* single file */
783 case 'u': /* single url */
784 flags |= EDITOR_FOR_EACH;
785 if (flags & EDITOR_SINGLE_COMMAND)
787 flags |= EDITOR_ERROR_INCOMPATIBLE;
792 /* use the first file from the list */
795 flags |= EDITOR_ERROR_NO_FILE;
798 pathl = editor_command_path_parse((FileData *)list->data,
799 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
803 flags |= EDITOR_ERROR_NO_FILE;
808 result = append_quoted(result, pathl, single_quotes, double_quotes);
816 flags |= EDITOR_SINGLE_COMMAND;
817 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
819 flags |= EDITOR_ERROR_INCOMPATIBLE;
831 FileData *fd = work->data;
832 pathl = editor_command_path_parse(fd, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
840 if (work != list) g_string_append_c(result, ' ');
841 result = append_quoted(result, pathl, single_quotes, double_quotes);
849 flags |= EDITOR_ERROR_NO_FILE;
855 if (editor->icon && *editor->icon)
859 result = g_string_append(result, "--icon ");
860 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
867 result = append_quoted(result, editor->name, single_quotes, double_quotes);
873 result = append_quoted(result, editor->file, single_quotes, double_quotes);
877 /* %% = % escaping */
878 if (output) result = g_string_append_c(result, *p);
886 /* deprecated according to spec, ignore */
889 flags |= EDITOR_ERROR_SYNTAX;
895 if (output) result = g_string_append_c(result, *p);
900 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
904 *output = g_string_free(result, FALSE);
905 DEBUG_3("Editor cmd: %s", *output);
914 g_string_free(result, TRUE);
921 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
923 EditorData *ed = data;
924 g_spawn_close_pid(pid);
927 editor_command_next_finish(ed, status);
931 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
934 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
936 gint standard_output;
941 ed->flags = editor->flags;
942 ed->flags |= editor_command_parse(editor, list, &command);
944 ok = !EDITOR_ERRORS(ed->flags);
948 ok = (options->shell.path && *options->shell.path);
949 if (!ok) log_printf("ERROR: empty shell command\n");
953 ok = (access(options->shell.path, X_OK) == 0);
954 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
957 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
962 gchar *working_directory;
966 working_directory = fd ? remove_level_from_path(fd->path) : NULL;
967 args[n++] = options->shell.path;
968 if (options->shell.options && *options->shell.options)
969 args[n++] = options->shell.options;
973 if ((ed->flags & EDITOR_DEST) && fd->change && fd->change->dest) /* FIXME: error handling */
975 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
979 g_unsetenv("GEEQIE_DESTINATION");
982 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
983 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
987 ed->vd ? &standard_output : NULL,
988 ed->vd ? &standard_error : NULL,
991 g_free(working_directory);
993 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
998 g_child_watch_add(pid, editor_child_exit_cb, ed);
1008 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1009 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1015 GIOChannel *channel_output;
1016 GIOChannel *channel_error;
1018 channel_output = g_io_channel_unix_new(standard_output);
1019 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1020 g_io_channel_set_encoding(channel_output, NULL, NULL);
1022 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1023 editor_verbose_io_cb, ed, NULL);
1024 g_io_channel_unref(channel_output);
1026 channel_error = g_io_channel_unix_new(standard_error);
1027 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1028 g_io_channel_set_encoding(channel_error, NULL, NULL);
1030 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1031 editor_verbose_io_cb, ed, NULL);
1032 g_io_channel_unref(channel_error);
1038 return EDITOR_ERRORS(ed->flags);
1041 static EditorFlags editor_command_next_start(EditorData *ed)
1043 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1045 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1050 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1054 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1055 editor_verbose_window_progress(ed, fd->path);
1057 editor_verbose_window_progress(ed, _("running..."));
1061 error = editor_command_one(ed->editor, ed->list, ed);
1062 if (!error && ed->vd)
1064 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1065 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1067 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1068 editor_verbose_window_fill(ed->vd, "\n", 1);
1075 /* command was not started, call the finish immediately */
1076 return editor_command_next_finish(ed, 0);
1079 /* everything is done */
1080 return editor_command_done(ed);
1083 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1085 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1088 ed->flags |= EDITOR_ERROR_STATUS;
1090 if (ed->flags & EDITOR_FOR_EACH)
1092 /* handle the first element from the list */
1093 GList *fd_element = ed->list;
1095 ed->list = g_list_remove_link(ed->list, fd_element);
1098 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1099 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1101 filelist_free(fd_element);
1105 /* handle whole list */
1107 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1108 filelist_free(ed->list);
1114 case EDITOR_CB_SUSPEND:
1115 return EDITOR_ERRORS(ed->flags);
1116 case EDITOR_CB_SKIP:
1117 return editor_command_done(ed);
1120 return editor_command_next_start(ed);
1123 static EditorFlags editor_command_done(EditorData *ed)
1129 if (ed->count == ed->total)
1131 editor_verbose_window_progress(ed, _("done"));
1135 editor_verbose_window_progress(ed, _("stopped by user"));
1137 editor_verbose_window_enable_close(ed->vd);
1140 /* free the not-handled items */
1143 ed->flags |= EDITOR_ERROR_SKIPPED;
1144 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1145 filelist_free(ed->list);
1151 flags = EDITOR_ERRORS(ed->flags);
1153 if (!ed->vd) editor_data_free(ed);
1158 void editor_resume(gpointer ed)
1160 editor_command_next_start(ed);
1163 void editor_skip(gpointer ed)
1165 editor_command_done(ed);
1168 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, EditorCallback cb, gpointer data)
1171 EditorFlags flags = editor->flags;
1173 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1175 ed = g_new0(EditorData, 1);
1176 ed->list = filelist_copy(list);
1178 ed->editor = editor;
1179 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1183 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1184 flags |= EDITOR_VERBOSE;
1186 if (flags & EDITOR_VERBOSE)
1187 editor_verbose_window(ed, text);
1189 editor_command_next_start(ed);
1190 /* errors from editor_command_next_start will be handled via callback */
1191 return EDITOR_ERRORS(flags);
1194 gboolean is_valid_editor_command(const gchar *key)
1196 if (!key) return FALSE;
1197 return g_hash_table_lookup(editors, key) != NULL;
1200 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, EditorCallback cb, gpointer data)
1203 EditorDescription *editor;
1204 if (!key) return FALSE;
1206 editor = g_hash_table_lookup(editors, key);
1208 if (!editor) return FALSE;
1209 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return FALSE;
1211 error = editor_command_start(editor, editor->name, list, cb, data);
1213 if (EDITOR_ERRORS(error))
1215 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1217 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1224 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1226 return start_editor_from_filelist_full(key, list, NULL, NULL);
1229 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1234 if (!fd) return FALSE;
1236 list = g_list_append(NULL, fd);
1237 error = start_editor_from_filelist_full(key, list, cb, data);
1242 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1244 return start_editor_from_file_full(key, fd, NULL, NULL);
1247 EditorFlags start_editor(const gchar *key)
1249 return start_editor_from_filelist_full(key, NULL, NULL, NULL);
1252 gboolean editor_window_flag_set(const gchar *key)
1254 EditorDescription *editor;
1255 if (!key) return TRUE;
1257 editor = g_hash_table_lookup(editors, key);
1258 if (!editor) return TRUE;
1260 return !!(editor->flags & EDITOR_KEEP_FS);
1263 gboolean editor_is_filter(const gchar *key)
1265 EditorDescription *editor;
1266 if (!key) return TRUE;
1268 editor = g_hash_table_lookup(editors, key);
1269 if (!editor) return TRUE;
1271 return !!(editor->flags & EDITOR_DEST);
1274 gboolean editor_no_param(const gchar *key)
1276 EditorDescription *editor;
1277 if (!key) return FALSE;
1279 editor = g_hash_table_lookup(editors, key);
1280 if (!editor) return FALSE;
1282 return !!(editor->flags & EDITOR_NO_PARAM);
1285 gboolean editor_blocks_file(const gchar *key)
1287 EditorDescription *editor;
1288 if (!key) return FALSE;
1290 editor = g_hash_table_lookup(editors, key);
1291 if (!editor) return FALSE;
1293 /* Decide if the image file should be blocked during editor execution
1294 Editors like gimp can be used long time after the original file was
1295 saved, for editing unrelated files.
1296 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1299 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1302 const gchar *editor_get_error_str(EditorFlags flags)
1304 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1305 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1306 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1307 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1308 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1309 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1310 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1311 return _("Unknown error.");
1314 const gchar *editor_get_name(const gchar *key)
1316 EditorDescription *editor = g_hash_table_lookup(editors, key);
1318 if (!editor) return NULL;
1320 return editor->name;
1322 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */