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"
27 #include "pixbuf-util.h"
28 #include "ui-fileops.h"
29 #include "ui-spinner.h"
32 #define EDITOR_WINDOW_WIDTH 500
33 #define EDITOR_WINDOW_HEIGHT 300
37 typedef struct _EditorVerboseData EditorVerboseData;
38 struct _EditorVerboseData {
40 GtkWidget *button_close;
41 GtkWidget *button_stop;
47 typedef struct _EditorData EditorData;
55 EditorVerboseData *vd;
56 EditorCallback callback;
58 const EditorDescription *editor;
59 gchar *working_directory; /* fallback if no files are given (editor_no_param) */
63 static void editor_verbose_window_progress(EditorData *ed, const gchar *text);
64 static EditorFlags editor_command_next_start(EditorData *ed);
65 static EditorFlags editor_command_next_finish(EditorData *ed, gint status);
66 static EditorFlags editor_command_done(EditorData *ed);
69 *-----------------------------------------------------------------------------
70 * external editor routines
71 *-----------------------------------------------------------------------------
74 GHashTable *editors = NULL;
75 GtkListStore *desktop_file_list;
76 gboolean editors_finished = FALSE;
78 #ifdef G_KEY_FILE_DESKTOP_GROUP
79 #define DESKTOP_GROUP G_KEY_FILE_DESKTOP_GROUP
81 #define DESKTOP_GROUP "Desktop Entry"
84 void editor_description_free(EditorDescription *editor)
92 g_free(editor->menu_path);
93 g_free(editor->hotkey);
94 g_free(editor->comment);
95 string_list_free(editor->ext_list);
100 static GList *editor_mime_types_to_extensions(gchar **mime_types)
102 /** @FIXME this should be rewritten to use the shared mime database, as soon as we switch to gio */
104 static const gchar *conv_table[][2] = {
106 {"image/bmp", ".bmp"},
107 {"image/gif", ".gif"},
108 {"image/heic", ".heic"},
109 {"image/jpeg", ".jpeg;.jpg;.mpo"},
110 {"image/jpg", ".jpg;.jpeg"},
111 {"image/webp", ".webp"},
112 {"image/pcx", ".pcx"},
113 {"image/png", ".png"},
114 {"image/svg", ".svg"},
115 {"image/svg+xml", ".svg"},
116 {"image/svg+xml-compressed", ".svg"},
117 {"image/tiff", ".tiff;.tif;.mef"},
118 {"image/vnd-ms.dds", ".dds"},
119 {"image/x-adobe-dng", ".dng"},
120 {"image/x-bmp", ".bmp"},
121 {"image/x-canon-crw", ".crw"},
122 {"image/x-canon-cr2", ".cr2"},
123 {"image/x-canon-cr3", ".cr3"},
124 {"image/x-cr2", ".cr2"},
125 {"image/x-dcraw", "%raw;.mos"},
126 {"image/x-epson-erf", "%erf"},
127 {"image/x-ico", ".ico"},
128 {"image/x-kodak-kdc", ".kdc"},
129 {"image/x-mrw", ".mrw"},
130 {"image/x-minolta-mrw", ".mrw"},
131 {"image/x-MS-bmp", ".bmp"},
132 {"image/x-nef", ".nef"},
133 {"image/x-nikon-nef", ".nef"},
134 {"image/x-panasonic-raw", ".raw"},
135 {"image/x-panasonic-rw2", ".rw2"},
136 {"image/x-pentax-pef", ".pef"},
137 {"image/x-orf", ".orf"},
138 {"image/x-olympus-orf", ".orf"},
139 {"image/x-pcx", ".pcx"},
140 {"image/xpm", ".xpm"},
141 {"image/x-png", ".png"},
142 {"image/x-portable-anymap", ".pam"},
143 {"image/x-portable-bitmap", ".pbm"},
144 {"image/x-portable-graymap", ".pgm"},
145 {"image/x-portable-pixmap", ".ppm"},
146 {"image/x-psd", ".psd"},
147 {"image/x-raf", ".raf"},
148 {"image/x-fuji-raf", ".raf"},
149 {"image/x-sgi", ".sgi"},
150 {"image/x-sony-arw", ".arw"},
151 {"image/x-sony-sr2", ".sr2"},
152 {"image/x-sony-srf", ".srf"},
153 {"image/x-tga", ".tga"},
154 {"image/x-xbitmap", ".xbm"},
155 {"image/x-xcf", ".xcf"},
156 {"image/x-xpixmap", ".xpm"},
157 {"image/x-x3f", ".x3f"},
158 {"application/x-navi-animation", ".ani"},
159 {"application/x-ptoptimizer-script", ".pto"},
165 for (i = 0; mime_types[i]; i++)
166 for (j = 0; conv_table[j][0]; j++)
167 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
168 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
173 gboolean editor_read_desktop_file(const gchar *path)
176 EditorDescription *editor;
179 const gchar *key = filename_from_path(path);
180 gchar **categories, **only_show_in, **not_show_in;
183 gboolean category_geeqie = FALSE;
187 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
189 key_file = g_key_file_new();
190 if (!g_key_file_load_from_file(key_file, path, static_cast<GKeyFileFlags>(0), NULL))
192 g_key_file_free(key_file);
196 type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
197 if (!type || strcmp(type, "Application") != 0)
199 /* We only consider desktop entries of Application type */
200 g_key_file_free(key_file);
206 editor = g_new0(EditorDescription, 1);
208 editor->key = g_strdup(key);
209 editor->file = g_strdup(path);
211 g_hash_table_insert(editors, editor->key, editor);
213 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
214 || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
216 editor->hidden = TRUE;
219 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
222 gboolean found = FALSE;
224 for (i = 0; categories[i]; i++)
226 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
227 if (strcmp(categories[i], "Graphics") == 0)
231 if (strcmp(categories[i], "X-Geeqie") == 0)
234 category_geeqie = TRUE;
238 if (!found) editor->ignored = TRUE;
239 g_strfreev(categories);
243 editor->ignored = TRUE;
246 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
249 gboolean found = FALSE;
251 for (i = 0; only_show_in[i]; i++)
252 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
257 if (!found) editor->ignored = TRUE;
258 g_strfreev(only_show_in);
261 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
264 gboolean found = FALSE;
266 for (i = 0; not_show_in[i]; i++)
267 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
272 if (found) editor->ignored = TRUE;
273 g_strfreev(not_show_in);
277 try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
278 if (try_exec && !editor->hidden && !editor->ignored)
280 gchar *try_exec_res = g_find_program_in_path(try_exec);
281 if (!try_exec_res) editor->hidden = TRUE;
282 g_free(try_exec_res);
288 /* ignored editors will be deleted, no need to parse the rest */
289 g_key_file_free(key_file);
293 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
294 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
296 /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
297 if (editor->icon && !g_path_is_absolute(editor->icon))
299 gchar *ext = strrchr(editor->icon, '.');
301 if (ext && strlen(ext) == 4 &&
302 (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
304 log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
305 editor->file, editor->icon);
311 if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon))
313 g_free(editor->icon);
317 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
319 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
320 if (!editor->menu_path) editor->menu_path = g_strdup("PluginsMenu");
322 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
324 editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
326 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
328 editor->ext_list = filter_to_list(extensions);
331 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
334 editor->ext_list = editor_mime_types_to_extensions(mime_types);
335 g_strfreev(mime_types);
336 if (!editor->ext_list) editor->hidden = TRUE;
340 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags = static_cast<EditorFlags>(editor->flags | EDITOR_KEEP_FS);
341 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags = static_cast<EditorFlags>(editor->flags | EDITOR_VERBOSE);
342 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags = static_cast<EditorFlags>(editor->flags | EDITOR_VERBOSE_MULTI);
343 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags = static_cast<EditorFlags>(editor->flags | EDITOR_DEST);
344 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags = static_cast<EditorFlags>(editor->flags | EDITOR_TERMINAL);
346 editor->flags = static_cast<EditorFlags>(editor->flags | editor_command_parse(editor, NULL, FALSE, NULL));
348 if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
350 g_key_file_free(key_file);
352 if (editor->ignored) return TRUE;
354 work = options->disabled_plugins;
359 if (g_strcmp0(path, static_cast<const gchar *>(work->data)) == 0)
367 editor->disabled = disabled;
369 gtk_list_store_append(desktop_file_list, &iter);
370 gtk_list_store_set(desktop_file_list, &iter,
371 DESKTOP_FILE_COLUMN_KEY, key,
372 DESKTOP_FILE_COLUMN_DISABLED, editor->disabled,
373 DESKTOP_FILE_COLUMN_NAME, editor->name,
374 DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden ? _("yes") : _("no"),
375 DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
376 DESKTOP_FILE_COLUMN_PATH, path, -1);
381 static gboolean editor_remove_desktop_file_cb(gpointer UNUSED(key), gpointer value, gpointer UNUSED(user_data))
383 EditorDescription *editor = static_cast<EditorDescription *>(value);
384 return editor->hidden || editor->ignored;
387 void editor_table_finish(void)
389 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
390 editors_finished = TRUE;
393 void editor_table_clear(void)
395 if (desktop_file_list)
397 gtk_list_store_clear(desktop_file_list);
401 desktop_file_list = gtk_list_store_new(DESKTOP_FILE_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING);
405 g_hash_table_destroy(editors);
407 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
408 editors_finished = FALSE;
411 static GList *editor_add_desktop_dir(GList *list, const gchar *path)
417 pathl = path_from_utf8(path);
425 while ((dir = readdir(dp)) != NULL)
427 gchar *namel = dir->d_name;
429 if (g_str_has_suffix(namel, ".desktop"))
431 gchar *name = path_to_utf8(namel);
432 gchar *dpath = g_build_filename(path, name, NULL);
433 list = g_list_prepend(list, dpath);
441 GList *editor_get_desktop_files(void)
444 gchar *xdg_data_dirs;
450 xdg_data_dirs = getenv("XDG_DATA_DIRS");
451 if (xdg_data_dirs && xdg_data_dirs[0])
452 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
454 xdg_data_dirs = g_strdup("/usr/share");
456 all_dirs = g_strconcat(get_rc_dir(), ":", gq_appdir, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
458 g_free(xdg_data_dirs);
460 split_dirs = g_strsplit(all_dirs, ":", 0);
464 for (i = 0; split_dirs[i]; i++);
465 for (--i; i >= 0; i--)
467 path = g_build_filename(split_dirs[i], "applications", NULL);
468 list = editor_add_desktop_dir(list, path);
472 g_strfreev(split_dirs);
476 static void editor_list_add_cb(gpointer UNUSED(key), gpointer value, gpointer data)
478 GList **listp = static_cast<GList **>(data);
479 EditorDescription *editor = static_cast<EditorDescription *>(value);
481 /* do not show the special commands in any list, they are called explicitly */
482 if (strcmp(editor->key, CMD_COPY) == 0 ||
483 strcmp(editor->key, CMD_MOVE) == 0 ||
484 strcmp(editor->key, CMD_RENAME) == 0 ||
485 strcmp(editor->key, CMD_DELETE) == 0 ||
486 strcmp(editor->key, CMD_FOLDER) == 0) return;
488 if (editor->disabled)
493 *listp = g_list_prepend(*listp, editor);
496 static gint editor_sort(gconstpointer a, gconstpointer b)
498 const EditorDescription *ea = static_cast<const EditorDescription *>(a);
499 const EditorDescription *eb = static_cast<const EditorDescription *>(b);
500 gchar *caseless_name_ea;
501 gchar *caseless_name_eb;
502 gchar *collate_key_ea;
503 gchar *collate_key_eb;
506 ret = strcmp(ea->menu_path, eb->menu_path);
507 if (ret != 0) return ret;
509 caseless_name_ea = g_utf8_casefold(ea->name, -1);
510 caseless_name_eb = g_utf8_casefold(eb->name, -1);
511 collate_key_ea = g_utf8_collate_key_for_filename(caseless_name_ea, -1);
512 collate_key_eb = g_utf8_collate_key_for_filename(caseless_name_eb, -1);
513 ret = g_strcmp0(collate_key_ea, collate_key_eb);
515 g_free(collate_key_ea);
516 g_free(collate_key_eb);
517 g_free(caseless_name_ea);
518 g_free(caseless_name_eb);
523 GList *editor_list_get(void)
525 GList *editors_list = NULL;
527 if (!editors_finished) return NULL;
529 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
530 editors_list = g_list_sort(editors_list, editor_sort);
535 /* ------------------------------ */
538 static void editor_verbose_data_free(EditorData *ed)
545 static void editor_data_free(EditorData *ed)
547 editor_verbose_data_free(ed);
548 g_free(ed->working_directory);
552 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
554 EditorData *ed = static_cast<EditorData *>(data);
556 generic_dialog_close(gd);
557 editor_verbose_data_free(ed);
558 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
561 static void editor_verbose_window_stop(GenericDialog *UNUSED(gd), gpointer data)
563 EditorData *ed = static_cast<EditorData *>(data);
566 editor_verbose_window_progress(ed, _("stopping..."));
569 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
571 vd->gd->cancel_cb = editor_verbose_window_close;
573 spinner_set_interval(vd->spinner, -1);
574 gtk_widget_set_sensitive(vd->button_stop, FALSE);
575 gtk_widget_set_sensitive(vd->button_close, TRUE);
578 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
580 EditorVerboseData *vd;
585 vd = g_new0(EditorVerboseData, 1);
587 vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
590 buf = g_strdup_printf(_("Output of %s"), text);
591 generic_dialog_add_message(vd->gd, NULL, buf, NULL, FALSE);
593 //~ vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
594 //~ editor_verbose_window_stop, FALSE);
595 vd->button_stop = generic_dialog_add_button(vd->gd, "process-stop", NULL,
596 editor_verbose_window_stop, FALSE);
597 gtk_widget_set_sensitive(vd->button_stop, FALSE);
598 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
599 editor_verbose_window_close, TRUE);
600 gtk_widget_set_sensitive(vd->button_close, FALSE);
602 scrolled = gtk_scrolled_window_new(NULL, NULL);
603 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
604 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
605 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
606 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
607 gtk_widget_show(scrolled);
609 vd->text = gtk_text_view_new();
610 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
611 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
612 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
613 gtk_widget_show(vd->text);
615 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
616 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
617 gtk_widget_show(hbox);
619 vd->progress = gtk_progress_bar_new();
620 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
621 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
622 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), "");
623 gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(vd->progress), TRUE);
624 gtk_widget_show(vd->progress);
626 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
627 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
628 gtk_widget_show(vd->spinner);
630 gtk_widget_show(vd->gd->dialog);
636 static void editor_verbose_window_fill(EditorVerboseData *vd, const gchar *text, gint len)
638 GtkTextBuffer *buffer;
641 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
642 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
643 gtk_text_buffer_insert(buffer, &iter, text, len);
646 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
652 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
655 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
658 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
660 EditorData *ed = static_cast<EditorData *>(data);
664 if (condition & G_IO_IN)
666 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
668 if (!g_utf8_validate(buf, count, NULL))
672 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
675 editor_verbose_window_fill(ed->vd, utf8, -1);
680 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
685 editor_verbose_window_fill(ed->vd, buf, count);
690 if (condition & (G_IO_ERR | G_IO_HUP))
692 g_io_channel_shutdown(source, TRUE, NULL);
706 static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
710 const gchar *p = NULL;
712 DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
714 string = g_string_new("");
716 if (type == PATH_FILE || type == PATH_FILE_URL)
718 GList *work = editor->ext_list;
727 gchar *ext = static_cast<gchar *>(work->data);
730 if (strcmp(ext, "*") == 0 ||
731 g_ascii_strcasecmp(ext, fd->extension) == 0)
737 work2 = consider_sidecars ? fd->sidecar_files : NULL;
740 FileData *sfd = static_cast<FileData *>(work2->data);
743 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
754 else if (type == PATH_DEST)
756 if (fd->change && fd->change->dest)
757 p = fd->change->dest;
763 string = g_string_append(string, p);
765 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
766 pathl = path_from_utf8(string->str);
767 g_string_free(string, TRUE);
769 if (pathl && !pathl[0]) /* empty string case */
775 DEBUG_2("editor_command_path_parse: return %s", pathl);
779 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
786 g_string_append_c(str, '\'');
788 g_string_append(str, "\"'");
791 for (p = s; *p != '\0'; p++)
794 g_string_append(str, "'\\''");
796 g_string_append_c(str, *p);
802 g_string_append_c(str, '\'');
804 g_string_append(str, "'\"");
811 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
813 EditorFlags flags = static_cast<EditorFlags>(0);
815 GString *result = NULL;
816 gboolean escape = FALSE;
817 gboolean single_quotes = FALSE;
818 gboolean double_quotes = FALSE;
820 DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
823 result = g_string_new("");
825 if (editor->exec == NULL || editor->exec[0] == '\0')
827 flags = static_cast<EditorFlags>(flags | EDITOR_ERROR_EMPTY);
832 /* skip leading whitespaces if any */
833 while (g_ascii_isspace(*p)) p++;
842 if (output) result = g_string_append_c(result, *p);
846 if (!single_quotes) escape = TRUE;
847 if (output) result = g_string_append_c(result, *p);
851 if (output) result = g_string_append_c(result, *p);
852 if (!single_quotes && !double_quotes)
853 single_quotes = TRUE;
854 else if (single_quotes)
855 single_quotes = FALSE;
859 if (output) result = g_string_append_c(result, *p);
860 if (!single_quotes && !double_quotes)
861 double_quotes = TRUE;
862 else if (double_quotes)
863 double_quotes = FALSE;
865 else if (*p == '%' && p[1])
873 case 'f': /* single file */
874 case 'u': /* single url */
875 flags = static_cast<EditorFlags>(flags | EDITOR_FOR_EACH);
876 if (flags & EDITOR_SINGLE_COMMAND)
878 flags = static_cast<EditorFlags>(flags | EDITOR_ERROR_INCOMPATIBLE);
883 /* use the first file from the list */
886 flags = static_cast<EditorFlags>(flags | EDITOR_ERROR_NO_FILE);
889 pathl = editor_command_path_parse((FileData *)list->data,
891 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
895 /* just testing, check also the rest of the list (like with F and U)
896 any matching file is OK */
897 GList *work = list->next;
899 while (!pathl && work)
901 FileData *fd = static_cast<FileData *>(work->data);
902 pathl = editor_command_path_parse(fd,
904 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
912 flags = static_cast<EditorFlags>(flags | EDITOR_ERROR_NO_FILE);
917 result = append_quoted(result, pathl, single_quotes, double_quotes);
925 flags = static_cast<EditorFlags>(flags | EDITOR_SINGLE_COMMAND);
926 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
928 flags = static_cast<EditorFlags>(flags | EDITOR_ERROR_INCOMPATIBLE);
940 FileData *fd = static_cast<FileData *>(work->data);
941 pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
949 if (work != list) g_string_append_c(result, ' ');
950 result = append_quoted(result, pathl, single_quotes, double_quotes);
958 flags = static_cast<EditorFlags>(flags | EDITOR_ERROR_NO_FILE);
964 if (editor->icon && *editor->icon)
968 result = g_string_append(result, "--icon ");
969 result = append_quoted(result, editor->icon, single_quotes, double_quotes);
976 result = append_quoted(result, editor->name, single_quotes, double_quotes);
982 result = append_quoted(result, editor->file, single_quotes, double_quotes);
986 /* %% = % escaping */
987 if (output) result = g_string_append_c(result, *p);
995 /* deprecated according to spec, ignore */
998 flags = static_cast<EditorFlags>(flags | EDITOR_ERROR_SYNTAX);
1004 if (output) result = g_string_append_c(result, *p);
1009 if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags = static_cast<EditorFlags>(flags | EDITOR_NO_PARAM);
1013 *output = g_string_free(result, FALSE);
1014 DEBUG_3("Editor cmd: %s", *output);
1023 g_string_free(result, TRUE);
1030 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
1032 EditorData *ed = static_cast<EditorData *>(data);
1033 g_spawn_close_pid(pid);
1036 editor_command_next_finish(ed, status);
1040 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
1043 FileData *fd = static_cast<FileData *>((ed->flags & EDITOR_NO_PARAM) ? NULL : list->data);;
1045 gint standard_output;
1046 gint standard_error;
1050 ed->flags = editor->flags;
1051 ed->flags = static_cast<EditorFlags>(ed->flags | editor_command_parse(editor, list, TRUE, &command));
1053 ok = !EDITOR_ERRORS(ed->flags);
1057 ok = (options->shell.path && *options->shell.path);
1058 if (!ok) log_printf("ERROR: empty shell command\n");
1062 ok = (access(options->shell.path, X_OK) == 0);
1063 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1066 if (!ok) ed->flags = static_cast<EditorFlags>(ed->flags | EDITOR_ERROR_CANT_EXEC);
1071 gchar *working_directory;
1075 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1076 args[n++] = options->shell.path;
1077 if (options->shell.options && *options->shell.options)
1078 args[n++] = options->shell.options;
1079 args[n++] = command;
1082 if ((ed->flags & EDITOR_DEST) && fd && fd->change && fd->change->dest) /** @FIXME error handling */
1084 g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1088 g_unsetenv("GEEQIE_DESTINATION");
1091 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1092 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1096 ed->vd ? &standard_output : NULL,
1097 ed->vd ? &standard_error : NULL,
1100 g_free(working_directory);
1102 if (!ok) ed->flags = static_cast<EditorFlags>(ed->flags | EDITOR_ERROR_CANT_EXEC);
1107 g_child_watch_add(pid, editor_child_exit_cb, ed);
1117 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1118 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1124 GIOChannel *channel_output;
1125 GIOChannel *channel_error;
1127 channel_output = g_io_channel_unix_new(standard_output);
1128 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1129 g_io_channel_set_encoding(channel_output, NULL, NULL);
1131 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, static_cast<GIOCondition>(G_IO_IN | G_IO_ERR | G_IO_HUP),
1132 editor_verbose_io_cb, ed, NULL);
1133 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, static_cast<GIOCondition>(G_IO_IN | G_IO_ERR | G_IO_HUP),
1134 editor_verbose_io_cb, ed, NULL);
1135 g_io_channel_unref(channel_output);
1137 channel_error = g_io_channel_unix_new(standard_error);
1138 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1139 g_io_channel_set_encoding(channel_error, NULL, NULL);
1141 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, static_cast<GIOCondition>(G_IO_IN | G_IO_ERR | G_IO_HUP),
1142 editor_verbose_io_cb, ed, NULL);
1143 g_io_channel_unref(channel_error);
1149 return static_cast<EditorFlags>(EDITOR_ERRORS(ed->flags));
1152 static EditorFlags editor_command_next_start(EditorData *ed)
1154 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1156 if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1161 fd = static_cast<FileData *>((ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data);
1165 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1166 editor_verbose_window_progress(ed, fd->path);
1168 editor_verbose_window_progress(ed, _("running..."));
1172 error = editor_command_one(ed->editor, ed->list, ed);
1173 if (!error && ed->vd)
1175 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1176 if ((ed->flags & EDITOR_FOR_EACH) && fd)
1178 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1179 editor_verbose_window_fill(ed->vd, "\n", 1);
1184 return static_cast<EditorFlags>(0);
1186 /* command was not started, call the finish immediately */
1187 return editor_command_next_finish(ed, 0);
1190 /* everything is done */
1191 return editor_command_done(ed);
1194 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1196 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1199 ed->flags = static_cast<EditorFlags>(ed->flags | EDITOR_ERROR_STATUS);
1201 if (ed->flags & EDITOR_FOR_EACH)
1203 /* handle the first element from the list */
1204 GList *fd_element = ed->list;
1206 ed->list = g_list_remove_link(ed->list, fd_element);
1209 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1210 if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1212 filelist_free(fd_element);
1216 /* handle whole list */
1218 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1219 filelist_free(ed->list);
1225 case EDITOR_CB_SUSPEND:
1226 return static_cast<EditorFlags>(EDITOR_ERRORS(ed->flags));
1227 case EDITOR_CB_SKIP:
1228 return editor_command_done(ed);
1231 return editor_command_next_start(ed);
1234 static EditorFlags editor_command_done(EditorData *ed)
1240 if (ed->count == ed->total)
1242 editor_verbose_window_progress(ed, _("done"));
1246 editor_verbose_window_progress(ed, _("stopped by user"));
1248 editor_verbose_window_enable_close(ed->vd);
1251 /* free the not-handled items */
1254 ed->flags = static_cast<EditorFlags>(ed->flags | EDITOR_ERROR_SKIPPED);
1255 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1256 filelist_free(ed->list);
1262 flags = static_cast<EditorFlags>(EDITOR_ERRORS(ed->flags));
1264 if (!ed->vd) editor_data_free(ed);
1269 void editor_resume(gpointer ed)
1271 editor_command_next_start(reinterpret_cast<EditorData *>(ed));
1274 void editor_skip(gpointer ed)
1276 editor_command_done(static_cast<EditorData *>(ed));
1279 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1282 EditorFlags flags = editor->flags;
1284 if (EDITOR_ERRORS(flags)) return static_cast<EditorFlags>(EDITOR_ERRORS(flags));
1286 ed = g_new0(EditorData, 1);
1287 ed->list = filelist_copy(list);
1289 ed->editor = editor;
1290 ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1293 ed->working_directory = g_strdup(working_directory);
1295 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1296 flags = static_cast<EditorFlags>(flags | EDITOR_VERBOSE);
1298 if (flags & EDITOR_VERBOSE)
1299 editor_verbose_window(ed, text);
1301 editor_command_next_start(ed);
1302 /* errors from editor_command_next_start will be handled via callback */
1303 return static_cast<EditorFlags>(EDITOR_ERRORS(flags));
1306 gboolean is_valid_editor_command(const gchar *key)
1308 if (!key) return FALSE;
1309 return g_hash_table_lookup(editors, key) != NULL;
1312 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1315 EditorDescription *editor;
1316 if (!key) return EDITOR_ERROR_EMPTY;
1318 editor = static_cast<EditorDescription *>(g_hash_table_lookup(editors, key));
1320 if (!editor) return EDITOR_ERROR_EMPTY;
1321 if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1323 error = editor_command_parse(editor, list, TRUE, NULL);
1325 if (EDITOR_ERRORS(error)) return error;
1327 error = static_cast<EditorFlags>(error | editor_command_start(editor, editor->name, list, working_directory, cb, data));
1329 if (EDITOR_ERRORS(error))
1331 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1333 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1337 return static_cast<EditorFlags>(EDITOR_ERRORS(error));
1340 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1342 return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1345 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1350 if (!fd) return static_cast<EditorFlags>(FALSE);
1352 list = g_list_append(NULL, fd);
1353 error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1358 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1360 return start_editor_from_file_full(key, fd, NULL, NULL);
1363 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1365 return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1368 gboolean editor_window_flag_set(const gchar *key)
1370 EditorDescription *editor;
1371 if (!key) return TRUE;
1373 editor = static_cast<EditorDescription *>(g_hash_table_lookup(editors, key));
1374 if (!editor) return TRUE;
1376 return !!(editor->flags & EDITOR_KEEP_FS);
1379 gboolean editor_is_filter(const gchar *key)
1381 EditorDescription *editor;
1382 if (!key) return TRUE;
1384 editor = static_cast<EditorDescription *>(g_hash_table_lookup(editors, key));
1385 if (!editor) return TRUE;
1387 return !!(editor->flags & EDITOR_DEST);
1390 gboolean editor_no_param(const gchar *key)
1392 EditorDescription *editor;
1393 if (!key) return FALSE;
1395 editor = static_cast<EditorDescription *>(g_hash_table_lookup(editors, key));
1396 if (!editor) return FALSE;
1398 return !!(editor->flags & EDITOR_NO_PARAM);
1401 gboolean editor_blocks_file(const gchar *key)
1403 EditorDescription *editor;
1404 if (!key) return FALSE;
1406 editor = static_cast<EditorDescription *>(g_hash_table_lookup(editors, key));
1407 if (!editor) return FALSE;
1409 /* Decide if the image file should be blocked during editor execution
1410 Editors like gimp can be used long time after the original file was
1411 saved, for editing unrelated files.
1412 %f vs. %F seems to be a good heuristic to detect this kind of editors.
1415 return !(editor->flags & EDITOR_SINGLE_COMMAND);
1418 const gchar *editor_get_error_str(EditorFlags flags)
1420 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1421 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1422 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1423 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1424 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1425 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1426 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1427 return _("Unknown error.");
1430 //const gchar *editor_get_name(const gchar *key)
1432 //EditorDescription *editor = g_hash_table_lookup(editors, key);
1434 //if (!editor) return NULL;
1436 //return editor->name;
1438 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */