From d0da99beca585f040d6564f2341949222afc6060 Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Sat, 16 Aug 2014 23:31:55 +0100 Subject: extend gtk viewdata to open files in an editor using the freedesktop default app specification --- gtk/Makefile.target | 2 +- gtk/gui.c | 2 +- gtk/viewdata.c | 315 +++++++++++++++++++++++++++++++++++++++++++++++++--- utils/config.h | 2 +- 4 files changed, 300 insertions(+), 21 deletions(-) diff --git a/gtk/Makefile.target b/gtk/Makefile.target index f69a73a95..6e71ff3ee 100644 --- a/gtk/Makefile.target +++ b/gtk/Makefile.target @@ -48,7 +48,7 @@ GTKCFLAGS := -std=c99 -Dgtk -Dnsgtk \ $(GTKDEPFLAGS) \ -D_BSD_SOURCE \ -D_XOPEN_SOURCE=600 \ - -D_POSIX_C_SOURCE=200112L \ + -D_POSIX_C_SOURCE=200809L \ -D_NETBSD_SOURCE \ -DGTK_RESPATH=\"$(NETSURF_GTK_RESOURCES)\" \ $(WARNFLAGS) -g diff --git a/gtk/gui.c b/gtk/gui.c index 4d2848366..bbe6cbaf2 100644 --- a/gtk/gui.c +++ b/gtk/gui.c @@ -976,7 +976,7 @@ check_dirname(const char *path, const char *leaf, char **dirname_out) free(dirname); - return ret;; + return ret; } /** diff --git a/gtk/viewdata.c b/gtk/viewdata.c index 6989b9a16..fd6a6b5bf 100644 --- a/gtk/viewdata.c +++ b/gtk/viewdata.c @@ -26,6 +26,7 @@ #include #include +#include #include #include "utils/log.h" @@ -35,6 +36,8 @@ #include "utils/url.h" #include "utils/utils.h" #include "utils/file.h" +#include "utils/filepath.h" + #include "desktop/netsurf.h" #include "desktop/browser.h" #include "render/html.h" @@ -553,41 +556,317 @@ tab_init(const char *title, return ret; } -/** - * open an editor from an existing file. - */ -static nserror -editor_init_fname(const char *title, - const char *leafname, - const char *fname) -{ -/* find user configured app for opening text/plain */ -/* - * serach path is ${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share} +/** + * Build string vector of search path. + * + * ${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share} * * $XDG_DATA_HOME if empty use $HOME/.local/share * * XDG_DATA_DIRS if empty use /usr/local/share/:/usr/share/ * - * search path looking for applications/defaults.list + * \return string vector of search pathnames or NULL on error. + */ +static char** xdg_data_strvec(void) +{ + const char *xdg_data_dirs; + const char *xdg_data_home; + const char *home_dir; + char *xdg_data_path; + int xdg_data_size; + char **svec; + + xdg_data_dirs = getenv("XDG_DATA_DIRS"); + if ((xdg_data_dirs == NULL) || (*xdg_data_dirs == 0)) { + xdg_data_dirs = "/usr/local/share/:/usr/share/"; + } + + xdg_data_home = getenv("XDG_DATA_HOME"); + if ((xdg_data_home == NULL) || (*xdg_data_home == 0)) { + /* $XDG_DATA_HOME is empty use $HOME/.local/share */ + + home_dir = getenv("HOME"); + if ((home_dir != NULL) && (*home_dir != 0)) { + xdg_data_size = strlen(home_dir) + SLEN("/.local/share:" ) + strlen(xdg_data_dirs) + 1; + xdg_data_path = malloc(xdg_data_size); + snprintf(xdg_data_path, xdg_data_size , + "%s/.local/share/:%s", home_dir, xdg_data_dirs); + } else { + xdg_data_path = strdup(xdg_data_dirs); + } + } else { + xdg_data_size = strlen(xdg_data_home) + strlen(xdg_data_dirs) + 2; + xdg_data_path = malloc(xdg_data_size); + snprintf(xdg_data_path, xdg_data_size , "%s:%s", xdg_data_home, xdg_data_dirs); + } + + LOG(("%s", xdg_data_path)); + + svec = filepath_path_to_strvec(xdg_data_path); + free(xdg_data_path); + + return svec; +} + +/** + * Search application defaults file for matching mime type. + * + * create filename form path and applications/defaults.list * * look for [Default Applications] * search lines looking like mime/type=Desktop * - * if mimetype is found - * use search path with applications/application.desktop + * \param path The base path. + * \param mimetype The mimetype to search for. + * \return The desktop file associated with the mime type or NULL if not found. + */ +static char *xdg_get_default_app(const char *path, const char *mimetype) +{ + FILE *fp; + char *line = NULL; + size_t len = 0; + ssize_t rd; + int fname_len; + char *fname; + int mimetype_len; + char *ret = NULL; + + fname_len = strlen(path) + SLEN("/applications/defaults.list") + 1; + fname = malloc(fname_len); + snprintf(fname, fname_len, "%s/applications/defaults.list", path); + + LOG(("Checking %s", fname)); + + fp = fopen(fname, "r"); + free(fname); + if (fp == NULL) { + return NULL; + } + + mimetype_len = strlen(mimetype); + while ((rd = getline(&line, &len, fp)) != -1) { + /* line includes line endings if present, remove them */ + while ((line[rd - 1] == '\n') || (line[rd - 1] == '\r')) { + rd--; + } + line[rd] = 0; + + /* look for mimetype */ + if ((rd > mimetype_len) && + (line[mimetype_len] == '=') && + (strncmp(line, mimetype, mimetype_len) == 0)) { + + ret = strdup(line + mimetype_len + 1); + + LOG(("Found line match for %s length %zu\n", mimetype, rd)); + LOG(("Result %s", ret)); + + break; + } + } + + free(line); + fclose(fp); + + return ret; +} + +/** + * Search desktop file for an Exec line. + * + * search path is combined with applications/application.desktop to + * create a filename. * - * search desktop file for: - * Exec=gedit %U + * Desktop file format http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html + * + * \todo The parsing of the desktop file is badly incomplete and needs + * improving. For example the handling of the = delimiter is wrong and + * selection from the "Desktop Entry" group is completely absent. * - * execute target app on saved data */ +static char *xdg_get_exec_cmd(const char *path, const char *desktop) +{ + FILE *fp; + char *line = NULL; + size_t len = 0; + ssize_t rd; + int fname_len; + char *fname; + char *ret = NULL; + + fname_len = strlen(path) + SLEN("/applications/") + strlen(desktop) + 1; + fname = malloc(fname_len); + snprintf(fname, fname_len, "%s/applications/%s", path, desktop); + + LOG(("Checking %s", fname)); + + fp = fopen(fname, "r"); + free(fname); + if (fp == NULL) { + return NULL; + } + + while ((rd = getline(&line, &len, fp)) != -1) { + /* line includes line endings if present, remove them */ + while ((line[rd - 1] == '\n') || (line[rd - 1] == '\r')) { + rd--; + } + line[rd] = 0; + + /* look for mimetype */ + if ((rd > (ssize_t)SLEN("Exec=")) && + (strncmp(line, "Exec=", SLEN("Exec=")) == 0)) { + + ret = strdup(line + SLEN("Exec=")); + + LOG(("Found Exec length %zu", rd)); + LOG(("Result %s", ret)); + + break; + } + } + + free(line); + fclose(fp); + + return ret; +} + +static char *exec_arg(const char *arg, int len, const char *fname) +{ + char *res = NULL; + + if (*arg == '%') { + arg++; + if ((*arg == 'f') || (*arg == 'F') || + (*arg == 'u') || (*arg == 'U')) { + res = strdup(fname); + } + } else { + res = calloc(1, len + 1); + if (res != NULL) { + memcpy(res, arg, len); + } + } + + return res; +} + +/** + * Build vector for executing app. + */ +static char **build_exec_argv(const char *fname, const char *exec_cmd) +{ + char **argv; + const char *start; /* current arguments start */ + const char *cur; /* current ptr within exec cmd */ + int aidx = 0; /* argv index */ + + argv = calloc(10, sizeof(char *)); + if (argv == NULL) { + return NULL; + } + + cur = exec_cmd; + while (*cur != 0) { + /* skip whitespace */ + while ((*cur != 0) && (*cur == ' ')) { + cur++; + } + if (*cur == 0) { + break; + } + start = cur; + + /* find end of element */ + while ((*cur != 0) && (*cur != ' ')) { + cur++; + } + + argv[aidx] = exec_arg(start, cur - start, fname); + if (argv[aidx] != NULL) { + LOG(("adding \"%s\"", argv[aidx])); + aidx++; + } + } + + /* if no arguments were found there was nothing to execute */ + if (aidx == 0) { + free(argv); + return NULL; + } + + return argv; +} + + +/** + * open an editor from an existing file. + */ +static nserror +editor_init_fname(const char *title, + const char *leafname, + const char *fname) +{ + char **xdg_data_vec; + int veci; + char *default_app; /* desktop file of default app for mimetype */ + char *exec_cmd; + char **argv; + + /* build string vector of search path */ + xdg_data_vec = xdg_data_strvec(); + + /* find user configured app for opening text/plain */ + veci = 0; + while (xdg_data_vec[veci] != NULL) { + default_app = xdg_get_default_app(xdg_data_vec[veci], + "text/plain"); + if (default_app != NULL) { + break; + } + veci++; + } + + if (default_app == NULL) { + /* no default app */ + filepath_free_strvec(xdg_data_vec); + return NSERROR_NOT_FOUND; + } + + /* find app to execute */ + veci = 0; + while (xdg_data_vec[veci] != NULL) { + exec_cmd = xdg_get_exec_cmd(xdg_data_vec[veci], default_app); + if (exec_cmd != NULL) { + break; + } + veci++; + } + filepath_free_strvec(xdg_data_vec); + + if (exec_cmd == NULL) { + /* no exec entry */ + return NSERROR_NOT_FOUND; + } + + /* build exec vector */ + argv = build_exec_argv(fname, exec_cmd); + free(exec_cmd); + + /* execute target app on saved data */ + if (g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, + NULL, NULL) != TRUE) { + return NSERROR_NOT_FOUND; + } + filepath_free_strvec(argv); + return NSERROR_OK; } /** - * create a new tab with page source + * open an editor with data. */ static nserror editor_init(const char *title, diff --git a/utils/config.h b/utils/config.h index d44359576..21a2e4a5a 100644 --- a/utils/config.h +++ b/utils/config.h @@ -24,7 +24,7 @@ /* Try to detect which features the target OS supports */ -#if (defined(_GNU_SOURCE) && !defined(__APPLE__) || defined(__HAIKU__)) +#if (defined(_GNU_SOURCE) && !defined(__APPLE__) || defined(__HAIKU__) || (defined(_POSIX_C_SOURCE) && ((_POSIX_C_SOURCE - 0) >= 200809L))) #define HAVE_STRNDUP #else #undef HAVE_STRNDUP -- cgit v1.2.3