X-Git-Url: http://geeqie.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Feditors.c;h=1bf2553f76139c2e401969b66d11e290ca218cd5;hb=cf90f247aceadb444297c3c66e2215e8c209ca69;hp=c77e14b017bee88ae52c7d69dc0ba82a1f922fb5;hpb=0cd8f42464c6e1e152f1bb9e79b1144f4529b4d1;p=geeqie.git diff --git a/src/editors.c b/src/editors.c index c77e14b0..1bf2553f 100644 --- a/src/editors.c +++ b/src/editors.c @@ -1,7 +1,7 @@ /* * Geeqie * (C) 2006 John Ellis - * Copyright (C) 2008 The Geeqie Team + * Copyright (C) 2008 - 2012 The Geeqie Team * * Author: John Ellis * @@ -17,6 +17,7 @@ #include "filedata.h" #include "filefilter.h" #include "misc.h" +#include "pixbuf_util.h" #include "ui_fileops.h" #include "ui_spinner.h" #include "ui_utildlg.h" @@ -42,9 +43,8 @@ struct _EditorVerboseData { typedef struct _EditorData EditorData; struct _EditorData { - gint flags; + EditorFlags flags; GPid pid; - gchar *command_template; GList *list; gint count; gint total; @@ -52,41 +52,15 @@ struct _EditorData { EditorVerboseData *vd; EditorCallback callback; gpointer data; + const EditorDescription *editor; + gchar *working_directory; /* fallback if no files are given (editor_no_param) */ }; -static Editor editor_slot_defaults[GQ_EDITOR_SLOTS] = { - { N_("The Gimp"), "gimp-remote %{.cr2;.crw;.nef;.raw;*}f" }, - { N_("XV"), "xv %f" }, - { N_("Xpaint"), "xpaint %f" }, - { N_("UFraw"), "ufraw %{.cr2;.crw;.nef;.raw}p" }, - { N_("Add XMP sidecar"), "%vFILE=%{.cr2;.crw;.nef;.raw}p;XMP=`echo \"$FILE\"|sed -e 's|\\.[^.]*$|.xmp|'`; exiftool -tagsfromfile \"$FILE\" \"$XMP\"" }, - { N_("Symlink"), "ln -s %p %d"}, - { NULL, NULL }, - { NULL, NULL }, - { N_("Rotate jpeg clockwise"), "%vif jpegtran -rotate 90 -copy all -outfile %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p; then mv %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p;else rm %{.jpg;.jpeg}p_tmp;fi" }, - { N_("Rotate jpeg counterclockwise"), "%vif jpegtran -rotate 270 -copy all -outfile %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p; then mv %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p;else rm %{.jpg;.jpeg}p_tmp;fi" }, - /* special slots */ -#if 1 - /* for testing */ - { N_("External Copy command"), "%vset -x;cp %p %d" }, - { N_("External Move command"), "%vset -x;mv %p %d" }, - { N_("External Rename command"), "%vset -x;mv %p %d" }, - { N_("External Delete command"), NULL }, - { N_("External New Folder command"), NULL }, -#else - { N_("External Copy command"), NULL }, - { N_("External Move command"), NULL }, - { N_("External Rename command"), NULL }, - { N_("External Delete command"), NULL }, - { N_("External New Folder command"), NULL }, -#endif -}; - static void editor_verbose_window_progress(EditorData *ed, const gchar *text); -static gint editor_command_next_start(EditorData *ed); -static gint editor_command_next_finish(EditorData *ed, gint status); -static gint editor_command_done(EditorData *ed); +static EditorFlags editor_command_next_start(EditorData *ed); +static EditorFlags editor_command_next_finish(EditorData *ed, gint status); +static EditorFlags editor_command_done(EditorData *ed); /* *----------------------------------------------------------------------------- @@ -94,34 +68,414 @@ static gint editor_command_done(EditorData *ed); *----------------------------------------------------------------------------- */ -void editor_set_name(gint n, gchar *name) +GHashTable *editors = NULL; +GtkListStore *desktop_file_list; +gboolean editors_finished = FALSE; + +#ifdef G_KEY_FILE_DESKTOP_GROUP +#define DESKTOP_GROUP G_KEY_FILE_DESKTOP_GROUP +#else +#define DESKTOP_GROUP "Desktop Entry" +#endif + +void editor_description_free(EditorDescription *editor) { - if (n < 0 || n >= GQ_EDITOR_SLOTS) return; + if (!editor) return; + + g_free(editor->key); + g_free(editor->name); + g_free(editor->icon); + g_free(editor->exec); + g_free(editor->menu_path); + g_free(editor->hotkey); + g_free(editor->comment); + string_list_free(editor->ext_list); + g_free(editor->file); + g_free(editor); +} - g_free(options->editor[n].name); - - options->editor[n].name = name ? utf8_validate_or_convert(name) : NULL; +static GList *editor_mime_types_to_extensions(gchar **mime_types) +{ + /* FIXME: this should be rewritten to use the shared mime database, as soon as we switch to gio */ + + static const gchar *conv_table[][2] = { + {"application/x-ufraw", ".ufraw"}, + {"image/*", "*"}, + {"image/bmp", ".bmp"}, + {"image/gif", ".gif"}, + {"image/jpeg", ".jpeg;.jpg"}, + {"image/jpg", ".jpg;.jpeg"}, + {"image/pcx", ".pcx"}, + {"image/png", ".png"}, + {"image/svg", ".svg"}, + {"image/svg+xml", ".svg"}, + {"image/svg+xml-compressed", ".svg"}, + {"image/tiff", ".tiff;.tif"}, + {"image/x-bmp", ".bmp"}, + {"image/x-canon-crw", ".crw"}, + {"image/x-cr2", ".cr2"}, + {"image/x-dcraw", "%raw"}, + {"image/x-ico", ".ico"}, + {"image/x-mrw", ".mrw"}, + {"image/x-MS-bmp", ".bmp"}, + {"image/x-nef", ".nef"}, + {"image/x-orf", ".orf"}, + {"image/x-pcx", ".pcx"}, + {"image/xpm", ".xpm"}, + {"image/x-png", ".png"}, + {"image/x-portable-anymap", ".pam"}, + {"image/x-portable-bitmap", ".pbm"}, + {"image/x-portable-graymap", ".pgm"}, + {"image/x-portable-pixmap", ".ppm"}, + {"image/x-psd", ".psd"}, + {"image/x-raf", ".raf"}, + {"image/x-sgi", ".sgi"}, + {"image/x-tga", ".tga"}, + {"image/x-xbitmap", ".xbm"}, + {"image/x-xcf", ".xcf"}, + {"image/x-xpixmap", ".xpm"}, + {"image/x-x3f", ".x3f"}, + {"application/x-ptoptimizer-script", ".pto"}, + {NULL, NULL}}; + + gint i, j; + GList *list = NULL; + + for (i = 0; mime_types[i]; i++) + for (j = 0; conv_table[j][0]; j++) + if (strcmp(mime_types[i], conv_table[j][0]) == 0) + list = g_list_concat(list, filter_to_list(conv_table[j][1])); + + return list; } -void editor_set_command(gint n, gchar *command) +gboolean editor_read_desktop_file(const gchar *path) { - if (n < 0 || n >= GQ_EDITOR_SLOTS) return; + GKeyFile *key_file; + EditorDescription *editor; + gchar *extensions; + gchar *type; + const gchar *key = filename_from_path(path); + gchar **categories, **only_show_in, **not_show_in; + gchar *try_exec; + GtkTreeIter iter; + gboolean category_geeqie = FALSE; + + if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */ + + key_file = g_key_file_new(); + if (!g_key_file_load_from_file(key_file, path, 0, NULL)) + { + g_key_file_free(key_file); + return FALSE; + } + + type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL); + if (!type || strcmp(type, "Application") != 0) + { + /* We only consider desktop entries of Application type */ + g_key_file_free(key_file); + g_free(type); + return FALSE; + } + g_free(type); + + editor = g_new0(EditorDescription, 1); + + editor->key = g_strdup(key); + editor->file = g_strdup(path); + + g_hash_table_insert(editors, editor->key, editor); + + if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL) + || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL)) + { + editor->hidden = TRUE; + } + + categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL); + if (categories) + { + gboolean found = FALSE; + gint i; + for (i = 0; categories[i]; i++) + { + /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */ + if (strcmp(categories[i], "Graphics") == 0) + { + found = TRUE; + } + if (strcmp(categories[i], "X-Geeqie") == 0) + { + found = TRUE; + category_geeqie = TRUE; + break; + } + } + if (!found) editor->ignored = TRUE; + g_strfreev(categories); + } + else + { + editor->ignored = TRUE; + } - g_free(options->editor[n].command); - options->editor[n].command = command ? utf8_validate_or_convert(command) : NULL; + only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL); + if (only_show_in) + { + gboolean found = FALSE; + gint i; + for (i = 0; only_show_in[i]; i++) + if (strcmp(only_show_in[i], "X-Geeqie") == 0) + { + found = TRUE; + break; + } + if (!found) editor->ignored = TRUE; + g_strfreev(only_show_in); + } + + not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL); + if (not_show_in) + { + gboolean found = FALSE; + gint i; + for (i = 0; not_show_in[i]; i++) + if (strcmp(not_show_in[i], "X-Geeqie") == 0) + { + found = TRUE; + break; + } + if (found) editor->ignored = TRUE; + g_strfreev(not_show_in); + } + + + try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL); + if (try_exec && !editor->hidden && !editor->ignored) + { + gchar *try_exec_res = g_find_program_in_path(try_exec); + if (!try_exec_res) editor->hidden = TRUE; + g_free(try_exec_res); + g_free(try_exec); + } + + if (editor->ignored) + { + /* ignored editors will be deleted, no need to parse the rest */ + g_key_file_free(key_file); + return TRUE; + } + + editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL); + editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL); + + /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */ + if (editor->icon && !g_path_is_absolute(editor->icon)) + { + gchar *ext = strrchr(editor->icon, '.'); + + if (ext && strlen(ext) == 4 && + (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg"))) + { + log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"), + editor->file, editor->icon); + + // drop extension + *ext = '\0'; + } + } + if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon)) + { + g_free(editor->icon); + editor->icon = NULL; + } + + editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL); + + editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL); + if (!editor->menu_path) editor->menu_path = g_strdup("EditMenu/ExternalMenu"); + + editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL); + + editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL); + + extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL); + if (extensions) + editor->ext_list = filter_to_list(extensions); + else + { + gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL); + if (mime_types) + { + editor->ext_list = editor_mime_types_to_extensions(mime_types); + g_strfreev(mime_types); + if (!editor->ext_list) editor->hidden = TRUE; + } + } + + if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS; + if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE; + if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI; + if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST; + if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL; + + editor->flags |= editor_command_parse(editor, NULL, FALSE, NULL); + + if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE; + + g_key_file_free(key_file); + + if (editor->ignored) return TRUE; + + gtk_list_store_append(desktop_file_list, &iter); + gtk_list_store_set(desktop_file_list, &iter, + DESKTOP_FILE_COLUMN_KEY, key, + DESKTOP_FILE_COLUMN_NAME, editor->name, + DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden ? _("yes") : _("no"), + DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK), + DESKTOP_FILE_COLUMN_PATH, path, -1); + + return TRUE; +} + +static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data) +{ + EditorDescription *editor = value; + return editor->hidden || editor->ignored; +} + +void editor_table_finish(void) +{ + g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL); + editors_finished = TRUE; +} + +void editor_table_clear(void) +{ + if (desktop_file_list) + { + gtk_list_store_clear(desktop_file_list); + } + else + { + 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); + } + if (editors) + { + g_hash_table_destroy(editors); + } + editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free); + editors_finished = FALSE; +} + +static GList *editor_add_desktop_dir(GList *list, const gchar *path) +{ + DIR *dp; + struct dirent *dir; + gchar *pathl; + + pathl = path_from_utf8(path); + dp = opendir(pathl); + g_free(pathl); + if (!dp) + { + /* dir not found */ + return list; + } + while ((dir = readdir(dp)) != NULL) + { + gchar *namel = dir->d_name; + + if (g_str_has_suffix(namel, ".desktop")) + { + gchar *name = path_to_utf8(namel); + gchar *dpath = g_build_filename(path, name, NULL); + list = g_list_prepend(list, dpath); + g_free(name); + } + } + closedir(dp); + return list; } -void editor_reset_defaults(void) +GList *editor_get_desktop_files(void) { + gchar *path; + gchar *xdg_data_dirs; + gchar *all_dirs; + gchar **split_dirs; gint i; + GList *list = NULL; + + xdg_data_dirs = getenv("XDG_DATA_DIRS"); + if (xdg_data_dirs && xdg_data_dirs[0]) + xdg_data_dirs = path_to_utf8(xdg_data_dirs); + else + xdg_data_dirs = g_strdup("/usr/share"); + + all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL); - for (i = 0; i < GQ_EDITOR_SLOTS; i++) + g_free(xdg_data_dirs); + + split_dirs = g_strsplit(all_dirs, ":", 0); + + g_free(all_dirs); + + for (i = 0; split_dirs[i]; i++); + for (--i; i >= 0; i--) { - editor_set_name(i, _(editor_slot_defaults[i].name)); - editor_set_command(i, _(editor_slot_defaults[i].command)); + path = g_build_filename(split_dirs[i], "applications", NULL); + list = editor_add_desktop_dir(list, path); + g_free(path); } + + g_strfreev(split_dirs); + return list; +} + +static void editor_list_add_cb(gpointer key, gpointer value, gpointer data) +{ + GList **listp = data; + EditorDescription *editor = value; + + /* do not show the special commands in any list, they are called explicitly */ + if (strcmp(editor->key, CMD_COPY) == 0 || + strcmp(editor->key, CMD_MOVE) == 0 || + strcmp(editor->key, CMD_RENAME) == 0 || + strcmp(editor->key, CMD_DELETE) == 0 || + strcmp(editor->key, CMD_FOLDER) == 0) return; + + *listp = g_list_prepend(*listp, editor); } +static gint editor_sort(gconstpointer a, gconstpointer b) +{ + const EditorDescription *ea = a; + const EditorDescription *eb = b; + gint ret; + + ret = strcmp(ea->menu_path, eb->menu_path); + if (ret != 0) return ret; + + return g_utf8_collate(ea->name, eb->name); +} + +GList *editor_list_get(void) +{ + GList *editors_list = NULL; + + if (!editors_finished) return NULL; + + g_hash_table_foreach(editors, editor_list_add_cb, &editors_list); + editors_list = g_list_sort(editors_list, editor_sort); + + return editors_list; +} + +/* ------------------------------ */ + + static void editor_verbose_data_free(EditorData *ed) { if (!ed->vd) return; @@ -132,7 +486,7 @@ static void editor_verbose_data_free(EditorData *ed) static void editor_data_free(EditorData *ed) { editor_verbose_data_free(ed); - g_free(ed->command_template); + g_free(ed->working_directory); g_free(ed); } @@ -171,7 +525,7 @@ static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *tex vd = g_new0(EditorVerboseData, 1); - vd->gd = file_util_gen_dlg(_("Edit command results"), GQ_WMCLASS, "editor_results", + vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results", NULL, FALSE, NULL, ed); buf = g_strdup_printf(_("Output of %s"), text); @@ -281,22 +635,24 @@ static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, typedef enum { PATH_FILE, + PATH_FILE_URL, PATH_DEST } PathType; -static gchar *editor_command_path_parse(const FileData *fd, PathType type, const gchar *extensions) +static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor) { GString *string; gchar *pathl; const gchar *p = NULL; + DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key); + string = g_string_new(""); - if (type == PATH_FILE) + if (type == PATH_FILE || type == PATH_FILE_URL) { - GList *ext_list = filter_to_list(extensions); - GList *work = ext_list; + GList *work = editor->ext_list; if (!work) p = fd->path; @@ -309,19 +665,19 @@ static gchar *editor_command_path_parse(const FileData *fd, PathType type, const work = work->next; if (strcmp(ext, "*") == 0 || - strcasecmp(ext, fd->extension) == 0) + g_ascii_strcasecmp(ext, fd->extension) == 0) { p = fd->path; break; } - work2 = fd->sidecar_files; + work2 = consider_sidecars ? fd->sidecar_files : NULL; while (work2) { FileData *sfd = work2->data; work2 = work2->next; - if (strcasecmp(ext, sfd->extension) == 0) + if (g_ascii_strcasecmp(ext, sfd->extension) == 0) { p = sfd->path; break; @@ -329,7 +685,6 @@ static gchar *editor_command_path_parse(const FileData *fd, PathType type, const } if (p) break; } - string_list_free(ext_list); if (!p) return NULL; } } @@ -341,168 +696,169 @@ static gchar *editor_command_path_parse(const FileData *fd, PathType type, const p = ""; } - while (*p != '\0') - { - /* must escape \, ", `, and $ to avoid problems, - * we assume system shell supports bash-like escaping - */ - if (strchr("\\\"`$", *p) != NULL) - { - string = g_string_append_c(string, '\\'); - } - string = g_string_append_c(string, *p); - p++; - } + g_assert(p); + string = g_string_append(string, p); + if (type == PATH_FILE_URL) g_string_prepend(string, "file://"); pathl = path_from_utf8(string->str); g_string_free(string, TRUE); + if (pathl && !pathl[0]) /* empty string case */ + { + g_free(pathl); + pathl = NULL; + } + + DEBUG_2("editor_command_path_parse: return %s", pathl); return pathl; } +static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes) +{ + const char *p; -/* - * The supported macros for editor commands: - * - * %f first occurence replaced by quoted sequence of filenames, command is run once. - * only one occurence of this macro is supported. - * ([ls %f] results in [ls "file1" "file2" ... "lastfile"]) - * %p command is run for each filename in turn, each instance replaced with single filename. - * multiple occurences of this macro is supported for complex shell commands. - * This macro will BLOCK THE APPLICATION until it completes, since command is run once - * for every file in syncronous order. To avoid blocking add the %v macro, below. - * ([ls %p] results in [ls "file1"], [ls "file2"] ... [ls "lastfile"]) - * none if no macro is supplied, the result is equivalent to "command %f" - * ([ls] results in [ls "file1" "file2" ... "lastfile"]) - * - * Only one of the macros %f or %p may be used in a given commmand. - * - * %v must be the first two characters[1] in a command, causes a window to display - * showing the output of the command(s). - * %V same as %v except in the case of %p only displays a window for multiple files, - * operating on a single file is suppresses the output dialog. - * - * %w must be first two characters in a command, presence will disable full screen - * from exiting upon invocation. - * - * - * [1] Note: %v,%V may also be preceded by "%w". - */ + if (!single_quotes) + { + if (!double_quotes) + g_string_append_c(str, '\''); + else + g_string_append(str, "\"'"); + } + for (p = s; *p != '\0'; p++) + { + if (*p == '\'') + g_string_append(str, "'\\''"); + else + g_string_append_c(str, *p); + } -gint editor_command_parse(const gchar *template, GList *list, gchar **output) + if (!single_quotes) + { + if (!double_quotes) + g_string_append_c(str, '\''); + else + g_string_append(str, "'\""); + } + + return str; +} + + +EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output) { - gint flags = 0; - const gchar *p = template; + EditorFlags flags = 0; + const gchar *p; GString *result = NULL; - gchar *extensions = NULL; + gboolean escape = FALSE; + gboolean single_quotes = FALSE; + gboolean double_quotes = FALSE; + + DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output); if (output) result = g_string_new(""); - if (!template || template[0] == '\0') + if (editor->exec == NULL || editor->exec[0] == '\0') { flags |= EDITOR_ERROR_EMPTY; goto err; } - - /* skip leading whitespaces if any */ - while (g_ascii_isspace(*p)) p++; - - /* global flags */ - while (*p == '%') - { - switch (*++p) - { - case 'w': - flags |= EDITOR_KEEP_FS; - p++; - break; - case 'v': - flags |= EDITOR_VERBOSE; - p++; - break; - case 'V': - flags |= EDITOR_VERBOSE_MULTI; - p++; - break; - default: - flags |= EDITOR_ERROR_SYNTAX; - goto err; - } - } - /* skip whitespaces if any */ + p = editor->exec; + /* skip leading whitespaces if any */ while (g_ascii_isspace(*p)) p++; /* command */ while (*p) { - if (*p != '%') + if (escape) + { + escape = FALSE; + if (output) result = g_string_append_c(result, *p); + } + else if (*p == '\\') { + if (!single_quotes) escape = TRUE; if (output) result = g_string_append_c(result, *p); } - else /* *p == '%' */ + else if (*p == '\'') + { + if (output) result = g_string_append_c(result, *p); + if (!single_quotes && !double_quotes) + single_quotes = TRUE; + else if (single_quotes) + single_quotes = FALSE; + } + else if (*p == '"') + { + if (output) result = g_string_append_c(result, *p); + if (!single_quotes && !double_quotes) + double_quotes = TRUE; + else if (double_quotes) + double_quotes = FALSE; + } + else if (*p == '%' && p[1]) { - extensions = NULL; gchar *pathl = NULL; p++; - /* for example "%f" or "%{.crw;.raw;.cr2}f" */ - if (*p == '{') - { - gchar *end; - - p++; - end = strchr(p, '}'); - if (!end) - { - flags |= EDITOR_ERROR_SYNTAX; - goto err; - } - - extensions = g_strndup(p, end - p); - p = end + 1; - } - switch (*p) { - case 'd': - flags |= EDITOR_DEST; - /* fall through */ - case 'p': + case 'f': /* single file */ + case 'u': /* single url */ flags |= EDITOR_FOR_EACH; if (flags & EDITOR_SINGLE_COMMAND) { flags |= EDITOR_ERROR_INCOMPATIBLE; goto err; } - if (output) + if (list) { /* use the first file from the list */ - if (!list || !list->data) + if (!list->data) { flags |= EDITOR_ERROR_NO_FILE; goto err; } pathl = editor_command_path_parse((FileData *)list->data, - (flags & EDITOR_DEST) ? PATH_DEST : PATH_FILE, - extensions); + consider_sidecars, + (*p == 'f') ? PATH_FILE : PATH_FILE_URL, + editor); + if (!output) + { + /* just testing, check also the rest of the list (like with F and U) + any matching file is OK */ + GList *work = list->next; + + while (!pathl && work) + { + FileData *fd = work->data; + pathl = editor_command_path_parse(fd, + consider_sidecars, + (*p == 'f') ? PATH_FILE : PATH_FILE_URL, + editor); + work = work->next; + } + } + if (!pathl) { flags |= EDITOR_ERROR_NO_FILE; goto err; } - result = g_string_append_c(result, '"'); - result = g_string_append(result, pathl); + if (output) + { + result = append_quoted(result, pathl, single_quotes, double_quotes); + } g_free(pathl); - result = g_string_append_c(result, '"'); } break; - case 'f': + case 'F': + case 'U': flags |= EDITOR_SINGLE_COMMAND; if (flags & (EDITOR_FOR_EACH | EDITOR_DEST)) { @@ -510,7 +866,7 @@ gint editor_command_parse(const gchar *template, GList *list, gchar **output) goto err; } - if (output) + if (list) { /* use whole list */ GList *work = list; @@ -519,16 +875,18 @@ gint editor_command_parse(const gchar *template, GList *list, gchar **output) while (work) { FileData *fd = work->data; - pathl = editor_command_path_parse(fd, PATH_FILE, extensions); - + pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor); if (pathl) { ok = TRUE; - if (work != list) g_string_append_c(result, ' '); - result = g_string_append_c(result, '"'); - result = g_string_append(result, pathl); + + if (output) + { + ok = TRUE; + if (work != list) g_string_append_c(result, ' '); + result = append_quoted(result, pathl, single_quotes, double_quotes); + } g_free(pathl); - result = g_string_append_c(result, '"'); } work = work->next; } @@ -539,21 +897,60 @@ gint editor_command_parse(const gchar *template, GList *list, gchar **output) } } break; + case 'i': + if (editor->icon && *editor->icon) + { + if (output) + { + result = g_string_append(result, "--icon "); + result = append_quoted(result, editor->icon, single_quotes, double_quotes); + } + } + break; + case 'c': + if (output) + { + result = append_quoted(result, editor->name, single_quotes, double_quotes); + } + break; + case 'k': + if (output) + { + result = append_quoted(result, editor->file, single_quotes, double_quotes); + } + break; case '%': /* %% = % escaping */ if (output) result = g_string_append_c(result, *p); break; + case 'd': + case 'D': + case 'n': + case 'N': + case 'v': + case 'm': + /* deprecated according to spec, ignore */ + break; default: flags |= EDITOR_ERROR_SYNTAX; goto err; } - if (extensions) g_free(extensions); - extensions = NULL; + } + else + { + if (output) result = g_string_append_c(result, *p); } p++; } - if (output) *output = g_string_free(result, FALSE); + if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM; + + if (output) + { + *output = g_string_free(result, FALSE); + DEBUG_3("Editor cmd: %s", *output); + } + return flags; @@ -563,11 +960,11 @@ err: g_string_free(result, TRUE); *output = NULL; } - if (extensions) g_free(extensions); return flags; } -static void editor_child_exit_cb (GPid pid, gint status, gpointer data) + +static void editor_child_exit_cb(GPid pid, gint status, gpointer data) { EditorData *ed = data; g_spawn_close_pid(pid); @@ -577,25 +974,26 @@ static void editor_child_exit_cb (GPid pid, gint status, gpointer data) } -static gint editor_command_one(const gchar *template, GList *list, EditorData *ed) +static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed) { gchar *command; - FileData *fd = list->data; + FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;; GPid pid; gint standard_output; gint standard_error; gboolean ok; ed->pid = -1; - ed->flags = editor_command_parse(template, list, &command); + ed->flags = editor->flags; + ed->flags |= editor_command_parse(editor, list, TRUE, &command); - ok = !(ed->flags & EDITOR_ERROR_MASK); + ok = !EDITOR_ERRORS(ed->flags); if (ok) { ok = (options->shell.path && *options->shell.path); if (!ok) log_printf("ERROR: empty shell command\n"); - + if (ok) { ok = (access(options->shell.path, X_OK) == 0); @@ -611,13 +1009,22 @@ static gint editor_command_one(const gchar *template, GList *list, EditorData *e gchar *args[4]; guint n = 0; - working_directory = remove_level_from_path(fd->path); + working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory); args[n++] = options->shell.path; if (options->shell.options && *options->shell.options) args[n++] = options->shell.options; args[n++] = command; args[n] = NULL; + if ((ed->flags & EDITOR_DEST) && fd->change && fd->change->dest) /* FIXME: error handling */ + { + g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE); + } + else + { + g_unsetenv("GEEQIE_DESTINATION"); + } + ok = g_spawn_async_with_pipes(working_directory, args, NULL, G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */ NULL, NULL, @@ -626,7 +1033,7 @@ static gint editor_command_one(const gchar *template, GList *list, EditorData *e ed->vd ? &standard_output : NULL, ed->vd ? &standard_error : NULL, NULL); - + g_free(working_directory); if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC; @@ -644,7 +1051,7 @@ static gint editor_command_one(const gchar *template, GList *list, EditorData *e { gchar *buf; - buf = g_strdup_printf(_("Failed to run command:\n%s\n"), template); + buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file); editor_verbose_window_fill(ed->vd, buf, strlen(buf)); g_free(buf); @@ -674,31 +1081,34 @@ static gint editor_command_one(const gchar *template, GList *list, EditorData *e g_free(command); - return ed->flags & EDITOR_ERROR_MASK; + return EDITOR_ERRORS(ed->flags); } -static gint editor_command_next_start(EditorData *ed) +static EditorFlags editor_command_next_start(EditorData *ed) { if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1); - if (ed->list && ed->count < ed->total) + if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total) { FileData *fd; - gint error; + EditorFlags error; - fd = ed->list->data; + fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data; if (ed->vd) { - editor_verbose_window_progress(ed, (ed->flags & EDITOR_FOR_EACH) ? fd->path : _("running...")); + if ((ed->flags & EDITOR_FOR_EACH) && fd) + editor_verbose_window_progress(ed, fd->path); + else + editor_verbose_window_progress(ed, _("running...")); } ed->count++; - error = editor_command_one(ed->command_template, ed->list, ed); + error = editor_command_one(ed->editor, ed->list, ed); if (!error && ed->vd) { gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) ); - if (ed->flags & EDITOR_FOR_EACH) + if ((ed->flags & EDITOR_FOR_EACH) && fd) { editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path)); editor_verbose_window_fill(ed->vd, "\n", 1); @@ -707,16 +1117,16 @@ static gint editor_command_next_start(EditorData *ed) if (!error) return 0; - else - /* command was not started, call the finish immediately */ - return editor_command_next_finish(ed, 0); + + /* command was not started, call the finish immediately */ + return editor_command_next_finish(ed, 0); } /* everything is done */ return editor_command_done(ed); } -static gint editor_command_next_finish(EditorData *ed, gint status) +static EditorFlags editor_command_next_finish(EditorData *ed, gint status) { gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE; @@ -745,31 +1155,31 @@ static gint editor_command_next_finish(EditorData *ed, gint status) ed->list = NULL; } - if (cont == EDITOR_CB_SUSPEND) - return ed->flags & EDITOR_ERROR_MASK; - else if (cont == EDITOR_CB_SKIP) - return editor_command_done(ed); - else - return editor_command_next_start(ed); + switch (cont) + { + case EDITOR_CB_SUSPEND: + return EDITOR_ERRORS(ed->flags); + case EDITOR_CB_SKIP: + return editor_command_done(ed); + } + + return editor_command_next_start(ed); } -static gint editor_command_done(EditorData *ed) +static EditorFlags editor_command_done(EditorData *ed) { - gint flags; + EditorFlags flags; if (ed->vd) { - const gchar *text; - if (ed->count == ed->total) { - text = _("done"); + editor_verbose_window_progress(ed, _("done")); } else { - text = _("stopped by user"); + editor_verbose_window_progress(ed, _("stopped by user")); } - editor_verbose_window_progress(ed, text); editor_verbose_window_enable_close(ed->vd); } @@ -784,7 +1194,7 @@ static gint editor_command_done(EditorData *ed) ed->count = 0; - flags = ed->flags & EDITOR_ERROR_MASK; + flags = EDITOR_ERRORS(ed->flags); if (!ed->vd) editor_data_free(ed); @@ -801,22 +1211,21 @@ void editor_skip(gpointer ed) editor_command_done(ed); } -static gint editor_command_start(const gchar *template, const gchar *text, GList *list, EditorCallback cb, gpointer data) +static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data) { EditorData *ed; - gint flags = editor_command_parse(template, NULL, NULL); + EditorFlags flags = editor->flags; - if (flags & EDITOR_ERROR_MASK) return flags & EDITOR_ERROR_MASK; + if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags); ed = g_new0(EditorData, 1); ed->list = filelist_copy(list); ed->flags = flags; - ed->command_template = g_strdup(template); - ed->total = (flags & EDITOR_SINGLE_COMMAND) ? 1 : g_list_length(list); - ed->count = 0; - ed->stopping = FALSE; + ed->editor = editor; + ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list); ed->callback = cb; - ed->data = data; + ed->data = data; + ed->working_directory = g_strdup(working_directory); if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next) flags |= EDITOR_VERBOSE; @@ -826,78 +1235,122 @@ static gint editor_command_start(const gchar *template, const gchar *text, GList editor_command_next_start(ed); /* errors from editor_command_next_start will be handled via callback */ - return flags & EDITOR_ERROR_MASK; + return EDITOR_ERRORS(flags); } -gboolean is_valid_editor_command(gint n) +gboolean is_valid_editor_command(const gchar *key) { - return (n >= 0 && n < GQ_EDITOR_SLOTS - && options->editor[n].command - && strlen(options->editor[n].command) > 0); + if (!key) return FALSE; + return g_hash_table_lookup(editors, key) != NULL; } -gint start_editor_from_filelist_full(gint n, GList *list, EditorCallback cb, gpointer data) +EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data) { - gchar *command; - gint error; + EditorFlags error; + EditorDescription *editor; + if (!key) return EDITOR_ERROR_EMPTY; - if (!list) return FALSE; - if (!is_valid_editor_command(n)) return FALSE; + editor = g_hash_table_lookup(editors, key); - command = g_locale_from_utf8(options->editor[n].command, -1, NULL, NULL, NULL); - error = editor_command_start(command, options->editor[n].name, list, cb, data); - g_free(command); + if (!editor) return EDITOR_ERROR_EMPTY; + if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE; + + error = editor_command_parse(editor, list, TRUE, NULL); - if (n < GQ_EDITOR_GENERIC_SLOTS && (error & EDITOR_ERROR_MASK)) + if (EDITOR_ERRORS(error)) return error; + + error |= editor_command_start(editor, editor->name, list, working_directory, cb, data); + + if (EDITOR_ERRORS(error)) { - gchar *text = g_strdup_printf(_("%s\n#%d \"%s\":\n%s"), editor_get_error_str(error), n+1, - options->editor[n].name, options->editor[n].command); - + gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file); + file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL); g_free(text); } - return error; + return EDITOR_ERRORS(error); } -gint start_editor_from_filelist(gint n, GList *list) +EditorFlags start_editor_from_filelist(const gchar *key, GList *list) { - return start_editor_from_filelist_full(n, list, NULL, NULL); + return start_editor_from_filelist_full(key, list, NULL, NULL, NULL); } -gint start_editor_from_file_full(gint n, FileData *fd, EditorCallback cb, gpointer data) +EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data) { GList *list; - gint error; + EditorFlags error; if (!fd) return FALSE; list = g_list_append(NULL, fd); - error = start_editor_from_filelist_full(n, list, cb, data); + error = start_editor_from_filelist_full(key, list, NULL, cb, data); g_list_free(list); return error; } -gint start_editor_from_file(gint n, FileData *fd) +EditorFlags start_editor_from_file(const gchar *key, FileData *fd) { - return start_editor_from_file_full(n, fd, NULL, NULL); + return start_editor_from_file_full(key, fd, NULL, NULL); } -gint editor_window_flag_set(gint n) +EditorFlags start_editor(const gchar *key, const gchar *working_directory) { - if (!is_valid_editor_command(n)) return TRUE; + return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL); +} + +gboolean editor_window_flag_set(const gchar *key) +{ + EditorDescription *editor; + if (!key) return TRUE; - return (editor_command_parse(options->editor[n].command, NULL, NULL) & EDITOR_KEEP_FS); + editor = g_hash_table_lookup(editors, key); + if (!editor) return TRUE; + + return !!(editor->flags & EDITOR_KEEP_FS); } -gint editor_is_filter(gint n) +gboolean editor_is_filter(const gchar *key) { - if (!is_valid_editor_command(n)) return FALSE; + EditorDescription *editor; + if (!key) return TRUE; + + editor = g_hash_table_lookup(editors, key); + if (!editor) return TRUE; - return (editor_command_parse(options->editor[n].command, NULL, NULL) & EDITOR_DEST); + return !!(editor->flags & EDITOR_DEST); } -const gchar *editor_get_error_str(gint flags) +gboolean editor_no_param(const gchar *key) +{ + EditorDescription *editor; + if (!key) return FALSE; + + editor = g_hash_table_lookup(editors, key); + if (!editor) return FALSE; + + return !!(editor->flags & EDITOR_NO_PARAM); +} + +gboolean editor_blocks_file(const gchar *key) +{ + EditorDescription *editor; + if (!key) return FALSE; + + editor = g_hash_table_lookup(editors, key); + if (!editor) return FALSE; + + /* Decide if the image file should be blocked during editor execution + Editors like gimp can be used long time after the original file was + saved, for editing unrelated files. + %f vs. %F seems to be a good heuristic to detect this kind of editors. + */ + + return !(editor->flags & EDITOR_SINGLE_COMMAND); +} + +const gchar *editor_get_error_str(EditorFlags flags) { if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty."); if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax."); @@ -909,13 +1362,12 @@ const gchar *editor_get_error_str(gint flags) return _("Unknown error."); } -const gchar *editor_get_name(gint n) +const gchar *editor_get_name(const gchar *key) { - if (!is_valid_editor_command(n)) return NULL; + EditorDescription *editor = g_hash_table_lookup(editors, key); + + if (!editor) return NULL; - if (options->editor[n].name && strlen(options->editor[n].name) > 0) - return options->editor[n].name; - - return _("(unknown)"); + return editor->name; } /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */