2 * Copyright (C) 2006 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include "filefilter.h"
28 #include "pixbuf_util.h"
29 #include "ui_fileops.h"
30 #include "ui_spinner.h"
31 #include "ui_utildlg.h"
37 #define EDITOR_WINDOW_WIDTH 500
38 #define EDITOR_WINDOW_HEIGHT 300
42 typedef struct _EditorVerboseData EditorVerboseData;
43 struct _EditorVerboseData {
45 GtkWidget *button_close;
46 GtkWidget *button_stop;
52 typedef struct _EditorData EditorData;
60 EditorVerboseData *vd;
61 EditorCallback callback;
63 const EditorDescription *editor;
64 gchar *working_directory; /* fallback if no files are given (editor_no_param) */
68 static void editor_verbose_window_progress(EditorData *ed, const gchar *text);
69 static EditorFlags editor_command_next_start(EditorData *ed);
70 static EditorFlags editor_command_next_finish(EditorData *ed, gint status);
71 static EditorFlags editor_command_done(EditorData *ed);
74 *-----------------------------------------------------------------------------
75 * external editor routines
76 *-----------------------------------------------------------------------------
79 GHashTable *editors = NULL;
80 GtkListStore *desktop_file_list;
81 gboolean editors_finished = FALSE;
83 #ifdef G_KEY_FILE_DESKTOP_GROUP
84 #define DESKTOP_GROUP G_KEY_FILE_DESKTOP_GROUP
86 #define DESKTOP_GROUP "Desktop Entry"
89 void editor_description_free(EditorDescription *editor)
97 g_free(editor->menu_path);
98 g_free(editor->hotkey);
99 g_free(editor->comment);
100 string_list_free(editor->ext_list);
101 g_free(editor->file);
105 static GList *editor_mime_types_to_extensions(gchar **mime_types)
107 /* FIXME: this should be rewritten to use the shared mime database, as soon as we switch to gio */
109 static const gchar *conv_table[][2] = {
110 {"application/x-ufraw", ".ufraw"},
112 {"image/bmp", ".bmp"},
113 {"image/gif", ".gif"},
114 {"image/jpeg", ".jpeg;.jpg;.mpo"},
115 {"image/jpg", ".jpg;.jpeg"},
116 {"image/pcx", ".pcx"},
117 {"image/png", ".png"},
118 {"image/svg", ".svg"},
119 {"image/svg+xml", ".svg"},
120 {"image/svg+xml-compressed", ".svg"},
121 {"image/tiff", ".tiff;.tif;.mef"},
122 {"image/vnd-ms.dds", ".dds"},
123 {"image/x-adobe-dng", ".dng"},
124 {"image/x-bmp", ".bmp"},
125 {"image/x-canon-crw", ".crw"},
126 {"image/x-canon-cr2", ".cr2"},
127 {"image/x-cr2", ".cr2"},
128 {"image/x-dcraw", "%raw;.mos"},
129 {"image/x-epson-erf", "%erf"},
130 {"image/x-ico", ".ico"},
131 {"image/x-kodak-kdc", ".kdc"},
132 {"image/x-mrw", ".mrw"},
133 {"image/x-minolta-mrw", ".mrw"},
134 {"image/x-MS-bmp", ".bmp"},
135 {"image/x-nef", ".nef"},
136 {"image/x-nikon-nef", ".nef"},
137 {"image/x-panasonic-raw", ".raw"},
138 {"image/x-panasonic-rw2", ".rw2"},
139 {"image/x-pentax-pef", ".pef"},
140 {"image/x-orf", ".orf"},
141 {"image/x-olympus-orf", ".orf"},
142 {"image/x-pcx", ".pcx"},
143 {"image/xpm", ".xpm"},
144 {"image/x-png", ".png"},
145 {"image/x-portable-anymap", ".pam"},
146 {"image/x-portable-bitmap", ".pbm"},
147 {"image/x-portable-graymap", ".pgm"},
148 {"image/x-portable-pixmap", ".ppm"},
149 {"image/x-psd", ".psd"},
150 {"image/x-raf", ".raf"},
151 {"image/x-fuji-raf", ".raf"},
152 {"image/x-sgi", ".sgi"},
153 {"image/x-sony-arw", ".arw"},
154 {"image/x-sony-sr2", ".sr2"},
155 {"image/x-sony-srf", ".srf"},
156 {"image/x-tga", ".tga"},
157 {"image/x-xbitmap", ".xbm"},
158 {"image/x-xcf", ".xcf"},
159 {"image/x-xpixmap", ".xpm"},
160 {"image/x-x3f", ".x3f"},
161 {"application/x-navi-animation", ".ani"},
162 {"application/x-ptoptimizer-script", ".pto"},
168 for (i = 0; mime_types[i]; i++)
169 for (j = 0; conv_table[j][0]; j++)
170 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
171 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
176 gboolean editor_read_desktop_file(const gchar *path)
179 EditorDescription *editor;
182 const gchar *key = filename_from_path(path);
183 gchar **categories, **only_show_in, **not_show_in;
186 gboolean category_geeqie = FALSE;
188 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
190 key_file = g_key_file_new();
191 if (!g_key_file_load_from_file(key_file, path, 0, NULL))
193 g_key_file_free(key_file);
197 type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
198 if (!type || strcmp(type, "Application") != 0)
200 /* We only consider desktop entries of Application type */
201 g_key_file_free(key_file);
207 editor = g_new0(EditorDescription, 1);
209 editor->key = g_strdup(key);
210 editor->file = g_strdup(path);
212 g_hash_table_insert(editors, editor->key, editor);
214 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
215 || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
217 editor->hidden = TRUE;
220 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
223 gboolean found = FALSE;
225 for (i = 0; categories[i]; i++)
227 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
228 if (strcmp(categories[i], "Graphics") == 0)
232 if (strcmp(categories[i], "X-Geeqie") == 0)
235 category_geeqie = TRUE;
239 if (!found) editor->ignored = TRUE;
240 g_strfreev(categories);
244 editor->ignored = TRUE;
247 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
250 gboolean found = FALSE;
252 for (i = 0; only_show_in[i]; i++)
253 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
258 if (!found) editor->ignored = TRUE;
259 g_strfreev(only_show_in);
262 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
265 gboolean found = FALSE;
267 for (i = 0; not_show_in[i]; i++)
268 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
273 if (found) editor->ignored = TRUE;
274 g_strfreev(not_show_in);
278 try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
279 if (try_exec && !editor->hidden && !editor->ignored)
281 gchar *try_exec_res = g_find_program_in_path(try_exec);
282 if (!try_exec_res) editor->hidden = TRUE;
283 g_free(try_exec_res);
289 /* ignored editors will be deleted, no need to parse the rest */
290 g_key_file_free(key_file);
294 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
295 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
297 /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
298 if (editor->icon && !g_path_is_absolute(editor->icon))
300 gchar *ext = strrchr(editor->icon, '.');
302 if (ext && strlen(ext) == 4 &&
303 (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
305 log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
306 editor->file, editor->icon);
312 if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon))
314 g_free(editor->icon);
318 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
320 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
321 if (!editor->menu_path) editor->menu_path = g_strdup("PluginsMenu");
323 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
325 editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
327 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
329 editor->ext_list = filter_to_list(extensions);
332 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
335 editor->ext_list = editor_mime_types_to_extensions(mime_types);
336 g_strfreev(mime_types);
337 if (!editor->ext_list) editor->hidden = TRUE;
341 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
342 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
343 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
344 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
345 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
347 editor->flags |= editor_command_parse(editor, NULL, FALSE, NULL);
349 if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
351 g_key_file_free(key_file);
353 if (editor->ignored) return TRUE;
355 gtk_list_store_append(desktop_file_list, &iter);
356 gtk_list_store_set(desktop_file_list, &iter,
357 DESKTOP_FILE_COLUMN_KEY, key,
358 DESKTOP_FILE_COLUMN_NAME, editor->name,
359 DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden ? _("yes") : _("no"),
360 DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
361 DESKTOP_FILE_COLUMN_PATH, path, -1);
366 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
368 EditorDescription *editor = value;
369 return editor->hidden || editor->ignored;
372 void editor_table_finish(void)
374 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
375 editors_finished = TRUE;
378 void editor_table_clear(void)
380 if (desktop_file_list)
382 gtk_list_store_clear(desktop_file_list);
386 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);
390 g_hash_table_destroy(editors);
392 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
393 editors_finished = FALSE;
396 static GList *editor_add_desktop_dir(GList *list, const gchar *path)
402 pathl = path_from_utf8(path);
410 while ((dir = readdir(dp)) != NULL)
412 gchar *namel = dir->d_name;
414 if (g_str_has_suffix(namel, ".desktop"))
416 gchar *name = path_to_utf8(namel);
417 gchar *dpath = g_build_filename(path, name, NULL);
418 list = g_list_prepend(list, dpath);
426 GList *editor_get_desktop_files(void)
429 gchar *xdg_data_dirs;
435 xdg_data_dirs = getenv("XDG_DATA_DIRS");
436 if (xdg_data_dirs && xdg_data_dirs[0])
437 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
439 xdg_data_dirs = g_strdup("/usr/share");
441 all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
443 g_free(xdg_data_dirs);
445 split_dirs = g_strsplit(all_dirs, ":", 0);
449 for (i = 0; split_dirs[i]; i++);
450 for (--i; i >= 0; i--)
452 path = g_build_filename(split_dirs[i], "applications", NULL);
453 list = editor_add_desktop_dir(list, path);
457 g_strfreev(split_dirs);
461 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
463 GList **listp = data;
464 EditorDescription *editor = value;
466 /* do not show the special commands in any list, they are called explicitly */
467 if (strcmp(editor->key, CMD_COPY) == 0 ||
468 strcmp(editor->key, CMD_MOVE) == 0 ||
469 strcmp(editor->key, CMD_RENAME) == 0 ||
470 strcmp(editor->key, CMD_DELETE) == 0 ||
471 strcmp(editor->key, CMD_FOLDER) == 0) return;
473 *listp = g_list_prepend(*listp, editor);
476 static gint editor_sort(gconstpointer a, gconstpointer b)
478 const EditorDescription *ea = a;
479 const EditorDescription *eb = b;
482 ret = strcmp(ea->menu_path, eb->menu_path);
483 if (ret != 0) return ret;
485 return g_utf8_collate(ea->name, eb->name);
488 GList *editor_list_get(void)
490 GList *editors_list = NULL;
492 if (!editors_finished) return NULL;
494 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
495 editors_list = g_list_sort(editors_list, editor_sort);
500 /* ------------------------------ */
503 static void editor_verbose_data_free(EditorData *ed)
510 static void editor_data_free(EditorData *ed)
512 editor_verbose_data_free(ed);
513 g_free(ed->working_directory);
517 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
519 EditorData *ed = data;
521 generic_dialog_close(gd);
522 editor_verbose_data_free(ed);
523 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
526 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
528 EditorData *ed = data;
531 editor_verbose_window_progress(ed, _("stopping..."));
534 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
536 vd->gd->cancel_cb = editor_verbose_window_close;
538 spinner_set_interval(vd->spinner, -1);
539 gtk_widget_set_sensitive(vd->button_stop, FALSE);
540 gtk_widget_set_sensitive(vd->button_close, TRUE);
543 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
545 EditorVerboseData *vd;
550 vd = g_new0(EditorVerboseData, 1);
552 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
555 buf = g_strdup_printf(_("Output of %s"), text);
556 generic_dialog_add_message(vd->gd, NULL, buf, NULL, FALSE);
558 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
559 editor_verbose_window_stop, FALSE);
560 gtk_widget_set_sensitive(vd->button_stop, FALSE);
561 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
562 editor_verbose_window_close, TRUE);
563 gtk_widget_set_sensitive(vd->button_close, FALSE);
565 scrolled = gtk_scrolled_window_new(NULL, NULL);
566 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
567 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
568 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
569 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
570 gtk_widget_show(scrolled);
572 vd->text = gtk_text_view_new();
573 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
574 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
575 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
576 gtk_widget_show(vd->text);
578 hbox = gtk_hbox_new(FALSE, 0);
579 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
580 gtk_widget_show(hbox);
582 vd->progress = gtk_progress_bar_new();
583 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
584 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
585 #if GTK_CHECK_VERSION(3,0,0)
586 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), "");
587 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(vd->progress), TRUE);
589 gtk_widget_show(vd->progress);
591 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
592 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
593 gtk_widget_show(vd->spinner);
595 gtk_widget_show(vd->gd->dialog);
601 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
603 GtkTextBuffer *buffer;
606 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
607 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
608 gtk_text_buffer_insert(buffer, &iter, text, len);
611 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
617 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
620 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
623 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
625 EditorData *ed = data;
629 if (condition & G_IO_IN)
631 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
633 if (!g_utf8_validate(buf, count, NULL))
637 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
640 editor_verbose_window_fill(ed->vd, utf8, -1);
645 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
650 editor_verbose_window_fill(ed->vd, buf, count);
655 if (condition & (G_IO_ERR | G_IO_HUP))
657 g_io_channel_shutdown(source, TRUE, NULL);
671 static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
675 const gchar *p = NULL;
677 DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
679 string = g_string_new("");
681 if (type == PATH_FILE || type == PATH_FILE_URL)
683 GList *work = editor->ext_list;
692 gchar *ext = work->data;
695 if (strcmp(ext, "*") == 0 ||
696 g_ascii_strcasecmp(ext, fd->extension) == 0)
702 work2 = consider_sidecars ? fd->sidecar_files : NULL;
705 FileData *sfd = work2->data;
708 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
719 else if (type == PATH_DEST)
721 if (fd->change && fd->change->dest)
722 p = fd->change->dest;
728 string = g_string_append(string, p);
730 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
731 pathl = path_from_utf8(string->str);
732 g_string_free(string, TRUE);
734 if (pathl && !pathl[0]) /* empty string case */
740 DEBUG_2("editor_command_path_parse: return %s", pathl);
744 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
751 g_string_append_c(str, '\'');
753 g_string_append(str, "\"'");
756 for (p = s; *p != '\0'; p++)
759 g_string_append(str, "'\\''");
761 g_string_append_c(str, *p);
767 g_string_append_c(str, '\'');
769 g_string_append(str, "'\"");
776 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
778 EditorFlags flags = 0;
780 GString *result = NULL;
781 gboolean escape = FALSE;
782 gboolean single_quotes = FALSE;
783 gboolean double_quotes = FALSE;
785 DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
788 result = g_string_new("");
790 if (editor->exec == NULL || editor->exec[0] == '\0')
792 flags |= EDITOR_ERROR_EMPTY;
797 /* skip leading whitespaces if any */
798 while (g_ascii_isspace(*p)) p++;
807 if (output) result = g_string_append_c(result, *p);
811 if (!single_quotes) escape = TRUE;
812 if (output) result = g_string_append_c(result, *p);
816 if (output) result = g_string_append_c(result, *p);
817 if (!single_quotes && !double_quotes)
818 single_quotes = TRUE;
819 else if (single_quotes)
820 single_quotes = FALSE;
824 if (output) result = g_string_append_c(result, *p);
825 if (!single_quotes && !double_quotes)
826 double_quotes = TRUE;
827 else if (double_quotes)
828 double_quotes = FALSE;
830 else if (*p == '%' && p[1])
838 case 'f': /* single file */
839 case 'u': /* single url */
840 flags |= EDITOR_FOR_EACH;
841 if (flags & EDITOR_SINGLE_COMMAND)
843 flags |= EDITOR_ERROR_INCOMPATIBLE;
848 /* use the first file from the list */
851 flags |= EDITOR_ERROR_NO_FILE;
854 pathl = editor_command_path_parse((FileData *)list->data,
856 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
860 /* just testing, check also the rest of the list (like with F and U)
861 any matching file is OK */
862 GList *work = list->next;
864 while (!pathl && work)
866 FileData *fd = work->data;
867 pathl = editor_command_path_parse(fd,
869 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
877 flags |= EDITOR_ERROR_NO_FILE;
882 result = append_quoted(result, pathl, single_quotes, double_quotes);
890 flags |= EDITOR_SINGLE_COMMAND;
891 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
893 flags |= EDITOR_ERROR_INCOMPATIBLE;
905 FileData *fd = work->data;
906 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
914 if (work != list) g_string_append_c(result, ' ');
915 result = append_quoted(result, pathl, single_quotes, double_quotes);
923 flags |= EDITOR_ERROR_NO_FILE;
929 if (editor->icon && *editor->icon)
933 result = g_string_append(result, "--icon ");
934 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
941 result = append_quoted(result, editor->name, single_quotes, double_quotes);
947 result = append_quoted(result, editor->file, single_quotes, double_quotes);
951 /* %% = % escaping */
952 if (output) result = g_string_append_c(result, *p);
960 /* deprecated according to spec, ignore */
963 flags |= EDITOR_ERROR_SYNTAX;
969 if (output) result = g_string_append_c(result, *p);
974 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
978 *output = g_string_free(result, FALSE);
979 DEBUG_3("Editor cmd: %s", *output);
988 g_string_free(result, TRUE);
995 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
997 EditorData *ed = data;
998 g_spawn_close_pid(pid);
1001 editor_command_next_finish(ed, status);
1005 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
1008 FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
1010 gint standard_output;
1011 gint standard_error;
1015 ed->flags = editor->flags;
1016 ed->flags |= editor_command_parse(editor, list, TRUE, &command);
1018 ok = !EDITOR_ERRORS(ed->flags);
1022 ok = (options->shell.path && *options->shell.path);
1023 if (!ok) log_printf("ERROR: empty shell command\n");
1027 ok = (access(options->shell.path, X_OK) == 0);
1028 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1031 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1036 gchar *working_directory;
1040 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1041 args[n++] = options->shell.path;
1042 if (options->shell.options && *options->shell.options)
1043 args[n++] = options->shell.options;
1044 args[n++] = command;
1047 if ((ed->flags & EDITOR_DEST) && fd && fd->change && fd->change->dest) /* FIXME: error handling */
1049 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1053 g_unsetenv("GEEQIE_DESTINATION");
1056 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1057 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1061 ed->vd ? &standard_output : NULL,
1062 ed->vd ? &standard_error : NULL,
1065 g_free(working_directory);
1067 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1072 g_child_watch_add(pid, editor_child_exit_cb, ed);
1082 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1083 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1089 GIOChannel *channel_output;
1090 GIOChannel *channel_error;
1092 channel_output = g_io_channel_unix_new(standard_output);
1093 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1094 g_io_channel_set_encoding(channel_output, NULL, NULL);
1096 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1097 editor_verbose_io_cb, ed, NULL);
1098 g_io_channel_unref(channel_output);
1100 channel_error = g_io_channel_unix_new(standard_error);
1101 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1102 g_io_channel_set_encoding(channel_error, NULL, NULL);
1104 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1105 editor_verbose_io_cb, ed, NULL);
1106 g_io_channel_unref(channel_error);
1112 return EDITOR_ERRORS(ed->flags);
1115 static EditorFlags editor_command_next_start(EditorData *ed)
1117 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1119 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1124 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1128 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1129 editor_verbose_window_progress(ed, fd->path);
1131 editor_verbose_window_progress(ed, _("running..."));
1135 error = editor_command_one(ed->editor, ed->list, ed);
1136 if (!error && ed->vd)
1138 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1139 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1141 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1142 editor_verbose_window_fill(ed->vd, "\n", 1);
1149 /* command was not started, call the finish immediately */
1150 return editor_command_next_finish(ed, 0);
1153 /* everything is done */
1154 return editor_command_done(ed);
1157 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1159 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1162 ed->flags |= EDITOR_ERROR_STATUS;
1164 if (ed->flags & EDITOR_FOR_EACH)
1166 /* handle the first element from the list */
1167 GList *fd_element = ed->list;
1169 ed->list = g_list_remove_link(ed->list, fd_element);
1172 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1173 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1175 filelist_free(fd_element);
1179 /* handle whole list */
1181 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1182 filelist_free(ed->list);
1188 case EDITOR_CB_SUSPEND:
1189 return EDITOR_ERRORS(ed->flags);
1190 case EDITOR_CB_SKIP:
1191 return editor_command_done(ed);
1194 return editor_command_next_start(ed);
1197 static EditorFlags editor_command_done(EditorData *ed)
1203 if (ed->count == ed->total)
1205 editor_verbose_window_progress(ed, _("done"));
1209 editor_verbose_window_progress(ed, _("stopped by user"));
1211 editor_verbose_window_enable_close(ed->vd);
1214 /* free the not-handled items */
1217 ed->flags |= EDITOR_ERROR_SKIPPED;
1218 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1219 filelist_free(ed->list);
1225 flags = EDITOR_ERRORS(ed->flags);
1227 if (!ed->vd) editor_data_free(ed);
1232 void editor_resume(gpointer ed)
1234 editor_command_next_start(ed);
1237 void editor_skip(gpointer ed)
1239 editor_command_done(ed);
1242 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1245 EditorFlags flags = editor->flags;
1247 if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1249 ed = g_new0(EditorData, 1);
1250 ed->list = filelist_copy(list);
1252 ed->editor = editor;
1253 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1256 ed->working_directory = g_strdup(working_directory);
1258 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1259 flags |= EDITOR_VERBOSE;
1261 if (flags & EDITOR_VERBOSE)
1262 editor_verbose_window(ed, text);
1264 editor_command_next_start(ed);
1265 /* errors from editor_command_next_start will be handled via callback */
1266 return EDITOR_ERRORS(flags);
1269 gboolean is_valid_editor_command(const gchar *key)
1271 if (!key) return FALSE;
1272 return g_hash_table_lookup(editors, key) != NULL;
1275 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1278 EditorDescription *editor;
1279 if (!key) return EDITOR_ERROR_EMPTY;
1281 editor = g_hash_table_lookup(editors, key);
1283 if (!editor) return EDITOR_ERROR_EMPTY;
1284 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1286 error = editor_command_parse(editor, list, TRUE, NULL);
1288 if (EDITOR_ERRORS(error)) return error;
1290 error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1292 if (EDITOR_ERRORS(error))
1294 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1296 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1300 return EDITOR_ERRORS(error);
1303 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1305 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1308 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1313 if (!fd) return FALSE;
1315 list = g_list_append(NULL, fd);
1316 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1321 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1323 return start_editor_from_file_full(key, fd, NULL, NULL);
1326 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1328 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1331 gboolean editor_window_flag_set(const gchar *key)
1333 EditorDescription *editor;
1334 if (!key) return TRUE;
1336 editor = g_hash_table_lookup(editors, key);
1337 if (!editor) return TRUE;
1339 return !!(editor->flags & EDITOR_KEEP_FS);
1342 gboolean editor_is_filter(const gchar *key)
1344 EditorDescription *editor;
1345 if (!key) return TRUE;
1347 editor = g_hash_table_lookup(editors, key);
1348 if (!editor) return TRUE;
1350 return !!(editor->flags & EDITOR_DEST);
1353 gboolean editor_no_param(const gchar *key)
1355 EditorDescription *editor;
1356 if (!key) return FALSE;
1358 editor = g_hash_table_lookup(editors, key);
1359 if (!editor) return FALSE;
1361 return !!(editor->flags & EDITOR_NO_PARAM);
1364 gboolean editor_blocks_file(const gchar *key)
1366 EditorDescription *editor;
1367 if (!key) return FALSE;
1369 editor = g_hash_table_lookup(editors, key);
1370 if (!editor) return FALSE;
1372 /* Decide if the image file should be blocked during editor execution
1373 Editors like gimp can be used long time after the original file was
1374 saved, for editing unrelated files.
1375 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1378 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1381 const gchar *editor_get_error_str(EditorFlags flags)
1383 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1384 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1385 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1386 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1387 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1388 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1389 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1390 return _("Unknown error.");
1393 const gchar *editor_get_name(const gchar *key)
1395 EditorDescription *editor = g_hash_table_lookup(editors, key);
1397 if (!editor) return NULL;
1399 return editor->name;
1401 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */