From 0d90bd57abe304ffca4bf5cd1a647d30dea882b7 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Thu, 25 Mar 2010 21:10:45 -0400 Subject: Add a new ObtLink type, which is going to be a generalization of a .desktop entry. Move the .desktop file parsing details into obt/ddparse.c, which generates some hash tables holding the data of the file. Add a new obt/link.c which will build an ObtLink from a parsed .desktop file, and may support other ways to create these links in the unforseeable future --- obt/ddfile.c | 571 ---------------------------------------------------------- obt/ddfile.h | 100 ---------- obt/ddparse.c | 507 +++++++++++++++++++++++++++++++++++++++++++++++++++ obt/ddparse.h | 52 ++++++ obt/link.c | 87 +++++++++ obt/link.h | 100 ++++++++++ 6 files changed, 746 insertions(+), 671 deletions(-) delete mode 100644 obt/ddfile.c delete mode 100644 obt/ddfile.h create mode 100644 obt/ddparse.c create mode 100644 obt/ddparse.h create mode 100644 obt/link.c create mode 100644 obt/link.h (limited to 'obt') diff --git a/obt/ddfile.c b/obt/ddfile.c deleted file mode 100644 index 4ef8114d..00000000 --- a/obt/ddfile.c +++ /dev/null @@ -1,571 +0,0 @@ -/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- - - obt/ddfile.c for the Openbox window manager - Copyright (c) 2009 Dana Jansens - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - See the COPYING file for a copy of the GNU General Public License. -*/ - -#include "obt/ddfile.h" -#include -#ifdef HAVE_STRING_H -#include -#endif -#ifdef HAVE_STDIO_H -#include -#endif - -typedef struct _ObtDDParse ObtDDParse; -typedef struct _ObtDDParseGroup ObtDDParseGroup; - -typedef void (*ObtDDParseGroupFunc)(gchar *key, const gchar *val, - ObtDDParse *parse, gboolean *error); - -struct _ObtDDParseGroup { - gchar *name; - gboolean seen; - ObtDDParseGroupFunc key_func; - /* the key is a string (a key inside the group in the .desktop). - the value is an ObtDDParseValue */ - GHashTable *key_hash; -}; - -struct _ObtDDParse { - gchar *filename; - gulong lineno; - ObtDDParseGroup *group; - /* the key is a group name, the value is a ObtDDParseGroup */ - GHashTable *group_hash; -}; - -typedef enum { - DATA_STRING, - DATA_LOCALESTRING, - DATA_STRINGS, - DATA_LOCALESTRINGS, - DATA_BOOLEAN, - DATA_NUMERIC, - NUM_DATA_TYPES -} ObtDDDataType; - -typedef struct _ObtDDParseValue { - ObtDDDataType type; - union _ObtDDParseValueValue { - gchar *string; - struct _ObtDDParseValueStrings { - gchar *s; - gulong n; - } strings; - gboolean boolean; - gfloat numeric; - } value; -} ObtDDParseValue; - -struct _ObtDDFile { - guint ref; - - ObtDDFileType type; - gchar *name; /*!< Specific name for the object (eg Firefox) */ - gchar *generic; /*!< Generic name for the object (eg Web Browser) */ - gchar *comment; /*!< Comment/description to display for the object */ - gchar *icon; /*!< Name/path for an icon for the object */ - - union _ObtDDFileData { - struct _ObtDDFileApp { - gchar *exec; /*!< Executable to run for the app */ - gchar *wdir; /*!< Working dir to run the app in */ - gboolean term; /*!< Run the app in a terminal or not */ - ObtDDFileAppOpen open; - - /* XXX gchar**? or something better, a mime struct.. maybe - glib has something i can use. */ - gchar **mime; /*!< Mime types the app can open */ - - ObtDDFileAppStartup startup; - gchar *startup_wmclass; - } app; - struct _ObtDDFileLink { - gchar *url; - } link; - struct _ObtDDFileDir { - } dir; - } d; -}; - -static void value_free(ObtDDParseValue *v) -{ - switch (v->type) { - case DATA_STRING: - case DATA_LOCALESTRING: - g_free(v->value.string); break; - case DATA_STRINGS: - case DATA_LOCALESTRINGS: - g_free(v->value.strings.s); - v->value.strings.n = 0; - break; - case DATA_BOOLEAN: - break; - case DATA_NUMERIC: - break; - default: - g_assert_not_reached(); - } - g_slice_free(ObtDDParseValue, v); -} - -static ObtDDParseGroup* group_new(gchar *name, ObtDDParseGroupFunc f) -{ - ObtDDParseGroup *g = g_slice_new(ObtDDParseGroup); - g->name = name; - g->key_func = f; - g->seen = FALSE; - g->key_hash = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, (GDestroyNotify)value_free); - return g; -} - -static void group_free(ObtDDParseGroup *g) -{ - g_free(g->name); - g_hash_table_destroy(g->key_hash); - g_slice_free(ObtDDParseGroup, g); -} - -/* Displays a warning message including the file name and line number, and - sets the boolean @error to true if it points to a non-NULL address. -*/ -static void parse_error(const gchar *m, const ObtDDParse *const parse, - gboolean *error) -{ - if (!parse->filename) - g_warning("%s at line %lu of input", m, parse->lineno); - else - g_warning("%s at line %lu of file %s", - m, parse->lineno, parse->filename); - if (error) *error = TRUE; -} - -/* reads an input string, strips out invalid stuff, and parses - backslash-stuff - if @nstrings is not NULL, then it splits the output string at ';' - characters. they are all returned in the same string with null zeros - between them, @nstrings is set to the number of such strings. - */ -static gchar* parse_string(const gchar *in, - gboolean locale, - gulong *nstrings, - const ObtDDParse *const parse, - gboolean *error) -{ - const gint bytes = strlen(in); - gboolean backslash; - gchar *out, *o; - const gchar *end, *i; - - g_return_val_if_fail(in != NULL, NULL); - - if (!locale) { - end = in + bytes; - for (i = in; i < end; ++i) { - if ((guchar)*i > 126 || (guchar)*i < 32) { - /* non-control character ascii */ - end = i; - parse_error("Invalid bytes in string", parse, error); - break; - } - } - } - else if (!g_utf8_validate(in, bytes, &end)) - parse_error("Invalid bytes in localestring", parse, error); - - if (nstrings) *nstrings = 1; - - out = g_new(char, bytes + 1); - i = in; o = out; - backslash = FALSE; - while (i < end) { - const gchar *next = locale ? g_utf8_find_next_char(i, end) : i+1; - if (backslash) { - switch(*i) { - case 's': *o++ = ' '; break; - case 'n': *o++ = '\n'; break; - case 't': *o++ = '\t'; break; - case 'r': *o++ = '\r'; break; - case ';': *o++ = ';'; break; - case '\\': *o++ = '\\'; break; - default: - parse_error((locale ? - "Invalid escape sequence in localestring" : - "Invalid escape sequence in string"), - parse, error); - } - backslash = FALSE; - } - else if (*i == '\\') - backslash = TRUE; - else if (*i == ';' && nstrings) { - ++nstrings; - *o = '\0'; - } - else if ((guchar)*i >= 127 || (guchar)*i < 32) { - /* avoid ascii control characters */ - parse_error("Found control character in string", parse, error); - break; - } - else { - memcpy(o, i, next-i); - o += next-i; - } - i = next; - } - *o = '\0'; - return o; -} - -static gboolean parse_bool(const gchar *in, const ObtDDParse *const parse, - gboolean *error) -{ - if (strcmp(in, "true") == 0) - return TRUE; - else if (strcmp(in, "false") != 0) - parse_error("Invalid boolean value", parse, error); - return FALSE; -} - -static gfloat parse_numeric(const gchar *in, const ObtDDParse *const parse, - gboolean *error) -{ - gfloat out = 0; - if (sscanf(in, "%f", &out) == 0) - parse_error("Invalid numeric value", parse, error); - return out; -} - -static void parse_group_desktop_entry(gchar *key, const gchar *val, - ObtDDParse *parse, gboolean *error) -{ - ObtDDParseValue v, *pv; - - /* figure out value type */ - v.type = NUM_DATA_TYPES; - - /* parse the value */ - - switch (v.type) { - case DATA_STRING: - v.value.string = parse_string(val, FALSE, NULL, parse, error); - g_assert(v.value.string); - break; - case DATA_LOCALESTRING: - v.value.string = parse_string(val, TRUE, NULL, parse, error); - g_assert(v.value.string); - break; - case DATA_STRINGS: - v.value.strings.s = parse_string(val, FALSE, &v.value.strings.n, - parse, error); - g_assert(v.value.strings.s); - g_assert(v.value.strings.n); - break; - case DATA_LOCALESTRINGS: - v.value.strings.s = parse_string(val, TRUE, &v.value.strings.n, - parse, error); - g_assert(v.value.strings.s); - g_assert(v.value.strings.n); - break; - case DATA_BOOLEAN: - v.value.boolean = parse_bool(val, parse, error); - break; - case DATA_NUMERIC: - v.value.numeric = parse_numeric(val, parse, error); - break; - default: - g_assert_not_reached(); - } - - pv = g_slice_new(ObtDDParseValue); - *pv = v; - g_hash_table_insert(parse->group->key_hash, key, pv); -} - -static gboolean parse_file_line(FILE *f, gchar **buf, - gulong *size, gulong *read, - ObtDDParse *parse, gboolean *error) -{ - const gulong BUFMUL = 80; - size_t ret; - gulong i, null; - - if (*size == 0) { - g_assert(*read == 0); - *size = BUFMUL; - *buf = g_new(char, *size); - } - - /* remove everything up to a null zero already in the buffer and shift - the rest to the front */ - null = *size; - for (i = 0; i < *read; ++i) { - if (null < *size) - (*buf)[i-null-1] = (*buf)[i]; - else if ((*buf)[i] == '\0') - null = i; - } - if (null < *size) - *read -= null + 1; - - /* is there already a newline in the buffer? */ - for (i = 0; i < *read; ++i) - if ((*buf)[i] == '\n') { - /* turn it into a null zero and done */ - (*buf)[i] = '\0'; - return TRUE; - } - - /* we need to read some more to find a newline */ - while (TRUE) { - gulong eol; - gchar *newread; - - newread = *buf + *read; - ret = fread(newread, sizeof(char), *size-*read, f); - if (ret < *size - *read && !feof(f)) { - parse_error("Error reading", parse, error); - return FALSE; - } - *read += ret; - - /* strip out null zeros in the input and look for an endofline */ - null = 0; - eol = *size; - for (i = newread-*buf; i < *read; ++i) { - if (null > 0) - (*buf)[i] = (*buf)[i+null]; - if ((*buf)[i] == '\0') { - ++null; - --(*read); - --i; /* try again */ - } - else if ((*buf)[i] == '\n' && eol == *size) { - eol = i; - /* turn it into a null zero */ - (*buf)[i] = '\0'; - } - } - - if (eol != *size) - /* found an endofline, done */ - break; - else if (feof(f) && *read < *size) { - /* found the endoffile, done (if there is space) */ - if (*read > 0) { - /* stick a null zero on if there is test on the last line */ - (*buf)[(*read)++] = '\0'; - } - break; - } - else { - /* read more */ - size += BUFMUL; - *buf = g_renew(char, *buf, *size); - } - } - return *read > 0; -} - -static void parse_group(const gchar *buf, gulong len, - ObtDDParse *parse, gboolean *error) -{ - ObtDDParseGroup *g; - gchar *group; - gulong i; - - /* get the group name */ - group = g_strndup(buf+1, len-2); - for (i = 0; i < len-2; ++i) - if ((guchar)group[i] < 32 || (guchar)group[i] >= 127) { - /* valid ASCII only */ - parse_error("Invalid character found", parse, NULL); - group[i] = '\0'; /* stopping before this character */ - break; - } - - /* make sure it's a new group */ - g = g_hash_table_lookup(parse->group_hash, group); - if (g && g->seen) { - parse_error("Duplicate group found", parse, error); - g_free(group); - return; - } - /* if it's the first group, make sure it's named Desktop Entry */ - else if (!parse->group && strcmp(group, "Desktop Entry") != 0) - { - parse_error("Incorrect group found, " - "expected [Desktop Entry]", - parse, error); - g_free(group); - return; - } - else { - if (!g) { - g = group_new(group, NULL); - g_hash_table_insert(parse->group_hash, g->name, g); - } - else - g_free(group); - - g->seen = TRUE; - parse->group = g; - g_print("Found group %s\n", g->name); - } -} - -static void parse_key_value(const gchar *buf, gulong len, - ObtDDParse *parse, gboolean *error) -{ - gulong i, keyend, valstart, eq; - char *key; - - /* find the end of the key */ - for (i = 0; i < len; ++i) - if (!(((guchar)buf[i] >= 'A' && (guchar)buf[i] <= 'Z') || - ((guchar)buf[i] >= 'a' && (guchar)buf[i] <= 'z') || - ((guchar)buf[i] >= '0' && (guchar)buf[i] <= '9') || - ((guchar)buf[i] == '-'))) { - /* not part of the key */ - keyend = i; - break; - } - if (keyend < 1) { - parse_error("Empty key", parse, error); - return; - } - /* find the = character */ - for (i = keyend; i < len; ++i) { - if (buf[i] == '=') { - eq = i; - break; - } - else if (buf[i] != ' ') { - parse_error("Invalid character in key name", parse, error); - return ; - } - } - if (i == len) { - parse_error("Key without value found", parse, error); - return; - } - /* find the start of the value */ - for (i = eq+1; i < len; ++i) - if (buf[i] != ' ') { - valstart = i; - break; - } - if (i == len) { - parse_error("Empty value found", parse, error); - return; - } - - key = g_strndup(buf, keyend); - if (g_hash_table_lookup(parse->group->key_hash, key)) { - parse_error("Duplicate key found", parse, error); - g_free(key); - return; - } - g_print("Found key/value %s=%s.\n", key, buf+valstart); - if (parse->group->key_func) - parse->group->key_func(key, buf+valstart, parse, error); -} - -static gboolean parse_file(ObtDDFile *dd, FILE *f, ObtDDParse *parse) -{ - gchar *buf = NULL; - gulong bytes = 0, read = 0; - gboolean error = FALSE; - - while (!error && parse_file_line(f, &buf, &bytes, &read, parse, &error)) { - /* XXX use the string in buf */ - gulong len = strlen(buf); - if (buf[0] == '#' || buf[0] == '\0') - ; /* ignore comment lines */ - else if (buf[0] == '[' && buf[len-1] == ']') - parse_group(buf, len, parse, &error); - else if (!parse->group) - /* just ignore keys outside of groups */ - parse_error("Key found before group", parse, NULL); - else - /* ignore errors in key-value pairs and continue */ - parse_key_value(buf, len, parse, NULL); - ++parse->lineno; - } - - if (buf) g_free(buf); - return !error; -} - -ObtDDFile* obt_ddfile_new_from_file(const gchar *name, GSList *paths) -{ - ObtDDFile *dd; - ObtDDParse parse; - ObtDDParseGroup *desktop_entry; - GSList *it; - FILE *f; - gboolean success; - - dd = g_slice_new(ObtDDFile); - dd->ref = 1; - - parse.filename = NULL; - parse.lineno = 0; - parse.group = NULL; - parse.group_hash = g_hash_table_new_full(g_str_hash, - g_str_equal, - NULL, - (GDestroyNotify)group_free); - - /* set up the groups (there's only one right now) */ - desktop_entry = group_new(g_strdup("Desktop Entry"), - parse_group_desktop_entry); - g_hash_table_insert(parse.group_hash, desktop_entry->name, desktop_entry); - - success = FALSE; - for (it = paths; it && !success; it = g_slist_next(it)) { - gchar *path = g_strdup_printf("%s/%s", (char*)it->data, name); - if ((f = fopen(path, "r"))) { - parse.filename = path; - parse.lineno = 1; - success = parse_file(dd, f, &parse); - fclose(f); - } - g_free(path); - } - if (!success) { - obt_ddfile_unref(dd); - dd = NULL; - } - - g_hash_table_destroy(parse.group_hash); - - return dd; -} - -void obt_ddfile_ref(ObtDDFile *dd) -{ - ++dd->ref; -} - -void obt_ddfile_unref(ObtDDFile *dd) -{ - if (--dd->ref < 1) { - g_slice_free(ObtDDFile, dd); - } -} diff --git a/obt/ddfile.h b/obt/ddfile.h deleted file mode 100644 index 5d5cf8db..00000000 --- a/obt/ddfile.h +++ /dev/null @@ -1,100 +0,0 @@ -/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- - - obt/ddfile.h for the Openbox window manager - Copyright (c) 2009 Dana Jansens - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - See the COPYING file for a copy of the GNU General Public License. -*/ - -#ifndef __obt_ddfile_h -#define __obt_ddfile_h - -#include - -G_BEGIN_DECLS - -typedef enum { - OBT_DDFILE_TYPE_APPLICATION = 1, - OBT_DDFILE_TYPE_LINK = 2, - OBT_DDFILE_TYPE_DIRECTORY = 3 -} ObtDDFileType; - -typedef enum { - OBT_DDFILE_APP_STARTUP_NO_SUPPORT, - OBT_DDFILE_APP_STARTUP_PROTOCOL_SUPPORT, - OBT_DDFILE_APP_STARTUP_LEGACY_SUPPORT -} ObtDDFileAppStartup; - -typedef enum { - /*! The app can be launched with a single local file */ - OBT_DDFILE_APP_SINGLE_LOCAL = 1 << 0, - /*! The app can be launched with multiple local files */ - OBT_DDFILE_APP_MULTI_LOCAL = 1 << 1, - /*! The app can be launched with a single URL */ - OBT_DDFILE_APP_SINGLE_URL = 1 << 2, - /*! The app can be launched with multiple URLs */ - OBT_DDFILE_APP_MULTI_URL = 1 << 3 -} ObtDDFileAppOpen; - -typedef struct _ObtDDFile ObtDDFile; - -ObtDDFile* obt_ddfile_new_from_file(const gchar *name, GSList *paths); - -void obt_ddfile_ref(ObtDDFile *e); -void obt_ddfile_unref(ObtDDFile *e); - -/*! Returns TRUE if the file exists but says it should be ignored, with - the Hidden flag. No other functions can be used for the ObtDDFile - in this case. */ -gboolean obt_ddfile_deleted (ObtDDFile *e); - -/*! Returns the type of object refered to by the .desktop file. */ -ObtDDFileType obt_ddfile_type (ObtDDFile *e); - -/*! Returns TRUE if the .desktop file should be displayed to users, given the - current environment. If FALSE, the .desktop file should not be showed. - This also uses the TryExec option if it is present. - @env A semicolon-deliminated list of environemnts. Can be one or more of: - GNOME, KDE, ROX, XFCE. Other environments not listed here may also - be supported. This can be null also if not listing any environment. */ -gboolean obt_ddfile_display(ObtDDFile *e, const gchar *env); - -const gchar* obt_ddfile_name (ObtDDFile *e); -const gchar* obt_ddfile_generic_name (ObtDDFile *e); -const gchar* obt_ddfile_comment (ObtDDFile *e); -/*! Returns the icon for the object referred to by the .desktop file. - Returns either an absolute path, or a string which can be used to find the - icon using the algorithm given by: - http://freedesktop.org/wiki/Specifications/icon-theme-spec?action=show&redirect=Standards/icon-theme-spec -*/ -const gchar* obt_ddfile_icon (ObtDDFile *e); - -const gchar *obt_ddfile_link_url(ObtDDFile *e); - -const gchar* obt_ddfile_app_executable (ObtDDFile *e); -/*! Returns the path in which the application should be run */ -const gchar* obt_ddfile_app_path (ObtDDFile *e); -gboolean obt_ddfile_app_run_in_terminal (ObtDDFile *e); -const gchar** obt_ddfile_app_mime_types (ObtDDFile *e); -/*! Returns a combination of values in the ObtDDFileAppOpen enum, - specifying if the application can be launched to open one or more files - and URLs. */ -ObtDDFileAppOpen obt_ddfile_app_open(ObtDDFile *e); - -ObtDDFileAppStartup obt_ddfile_app_startup_notify(ObtDDFile *e); -const gchar* obt_ddfile_app_startup_wmclass(ObtDDFile *e); - - -G_END_DECLS - -#endif diff --git a/obt/ddparse.c b/obt/ddparse.c new file mode 100644 index 00000000..8da048d3 --- /dev/null +++ b/obt/ddparse.c @@ -0,0 +1,507 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- + + obt/ddparse.c for the Openbox window manager + Copyright (c) 2009 Dana Jansens + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + See the COPYING file for a copy of the GNU General Public License. +*/ + +#include "obt/ddparse.h" +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif + +typedef struct _ObtDDParse ObtDDParse; + +/* Parses the value and adds it to the group's key_hash, with the given + key */ +typedef void (*ObtDDParseValueFunc)(gchar *key, const gchar *val, + ObtDDParse *parse, gboolean *error); + + +struct _ObtDDParse { + gchar *filename; + gulong lineno; + ObtDDParseGroup *group; + /* the key is a group name, the value is a ObtDDParseGroup */ + GHashTable *group_hash; +}; + +struct _ObtDDParseGroup { + gchar *name; + gboolean seen; + ObtDDParseValueFunc value_func; + /* the key is a string (a key inside the group in the .desktop). + the value is an ObtDDParseValue */ + GHashTable *key_hash; +}; + +/* Displays a warning message including the file name and line number, and + sets the boolean @error to true if it points to a non-NULL address. +*/ +static void parse_error(const gchar *m, const ObtDDParse *const parse, + gboolean *error) +{ + if (!parse->filename) + g_warning("%s at line %lu of input", m, parse->lineno); + else + g_warning("%s at line %lu of file %s", + m, parse->lineno, parse->filename); + if (error) *error = TRUE; +} + +static void parse_value_free(ObtDDParseValue *v) +{ + switch (v->type) { + case OBT_DDPARSE_STRING: + case OBT_DDPARSE_LOCALESTRING: + g_free(v->value.string); break; + case OBT_DDPARSE_STRINGS: + case OBT_DDPARSE_LOCALESTRINGS: + g_free(v->value.strings.s); + v->value.strings.n = 0; + break; + case OBT_DDPARSE_BOOLEAN: + break; + case OBT_DDPARSE_NUMERIC: + break; + default: + g_assert_not_reached(); + } + g_slice_free(ObtDDParseValue, v); +} + +static ObtDDParseGroup* parse_group_new(gchar *name, ObtDDParseValueFunc f) +{ + ObtDDParseGroup *g = g_slice_new(ObtDDParseGroup); + g->name = name; + g->value_func = f; + g->seen = FALSE; + g->key_hash = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, + (GDestroyNotify)parse_value_free); + return g; +} + +static void parse_group_free(ObtDDParseGroup *g) +{ + g_free(g->name); + g_hash_table_destroy(g->key_hash); + g_slice_free(ObtDDParseGroup, g); +} + +/*! Reads an input string, strips out invalid stuff, and parses + backslash-stuff. + If @nstrings is not NULL, then it splits the output string at ';' + characters. They are all returned in the same string with null zeros + between them, @nstrings is set to the number of such strings. + */ +static gchar* parse_value_string(const gchar *in, + gboolean locale, + gulong *nstrings, + const ObtDDParse *const parse, + gboolean *error) +{ + const gint bytes = strlen(in); + gboolean backslash; + gchar *out, *o; + const gchar *end, *i; + + g_return_val_if_fail(in != NULL, NULL); + + if (!locale) { + end = in + bytes; + for (i = in; i < end; ++i) { + if ((guchar)*i >= 127 || (guchar)*i < 32) { + /* non-control character ascii */ + end = i; + parse_error("Invalid bytes in string", parse, error); + break; + } + } + } + else if (!g_utf8_validate(in, bytes, &end)) + parse_error("Invalid bytes in localestring", parse, error); + + if (nstrings) *nstrings = 1; + + out = g_new(char, bytes + 1); + i = in; o = out; + backslash = FALSE; + while (i < end) { + const gchar *next = locale ? g_utf8_find_next_char(i, end) : i+1; + if (backslash) { + switch(*i) { + case 's': *o++ = ' '; break; + case 'n': *o++ = '\n'; break; + case 't': *o++ = '\t'; break; + case 'r': *o++ = '\r'; break; + case ';': *o++ = ';'; break; + case '\\': *o++ = '\\'; break; + default: + parse_error((locale ? + "Invalid escape sequence in localestring" : + "Invalid escape sequence in string"), + parse, error); + } + backslash = FALSE; + } + else if (*i == '\\') + backslash = TRUE; + else if (*i == ';' && nstrings) { + ++nstrings; + *o = '\0'; + } + else if ((guchar)*i == 127 || (guchar)*i < 32) { + /* avoid ascii control characters */ + parse_error("Found control character in string", parse, error); + break; + } + else { + memcpy(o, i, next-i); + o += next-i; + } + i = next; + } + *o = '\0'; + return o; +} + +static gboolean parse_value_boolean(const gchar *in, + const ObtDDParse *const parse, + gboolean *error) +{ + if (strcmp(in, "true") == 0) + return TRUE; + else if (strcmp(in, "false") != 0) + parse_error("Invalid boolean value", parse, error); + return FALSE; +} + +static gfloat parse_value_numeric(const gchar *in, + const ObtDDParse *const parse, + gboolean *error) +{ + gfloat out = 0; + if (sscanf(in, "%f", &out) == 0) + parse_error("Invalid numeric value", parse, error); + return out; +} + +static gboolean parse_file_line(FILE *f, gchar **buf, + gulong *size, gulong *read, + ObtDDParse *parse, gboolean *error) +{ + const gulong BUFMUL = 80; + size_t ret; + gulong i, null; + + if (*size == 0) { + g_assert(*read == 0); + *size = BUFMUL; + *buf = g_new(char, *size); + } + + /* remove everything up to a null zero already in the buffer and shift + the rest to the front */ + null = *size; + for (i = 0; i < *read; ++i) { + if (null < *size) + (*buf)[i-null-1] = (*buf)[i]; + else if ((*buf)[i] == '\0') + null = i; + } + if (null < *size) + *read -= null + 1; + + /* is there already a newline in the buffer? */ + for (i = 0; i < *read; ++i) + if ((*buf)[i] == '\n') { + /* turn it into a null zero and done */ + (*buf)[i] = '\0'; + return TRUE; + } + + /* we need to read some more to find a newline */ + while (TRUE) { + gulong eol; + gchar *newread; + + newread = *buf + *read; + ret = fread(newread, sizeof(char), *size-*read, f); + if (ret < *size - *read && !feof(f)) { + parse_error("Error reading", parse, error); + return FALSE; + } + *read += ret; + + /* strip out null zeros in the input and look for an endofline */ + null = 0; + eol = *size; + for (i = newread-*buf; i < *read; ++i) { + if (null > 0) + (*buf)[i] = (*buf)[i+null]; + if ((*buf)[i] == '\0') { + ++null; + --(*read); + --i; /* try again */ + } + else if ((*buf)[i] == '\n' && eol == *size) { + eol = i; + /* turn it into a null zero */ + (*buf)[i] = '\0'; + } + } + + if (eol != *size) + /* found an endofline, done */ + break; + else if (feof(f) && *read < *size) { + /* found the endoffile, done (if there is space) */ + if (*read > 0) { + /* stick a null zero on if there is test on the last line */ + (*buf)[(*read)++] = '\0'; + } + break; + } + else { + /* read more */ + size += BUFMUL; + *buf = g_renew(char, *buf, *size); + } + } + return *read > 0; +} + +static void parse_group(const gchar *buf, gulong len, + ObtDDParse *parse, gboolean *error) +{ + ObtDDParseGroup *g; + gchar *group; + gulong i; + + /* get the group name */ + group = g_strndup(buf+1, len-2); + for (i = 0; i < len-2; ++i) + if ((guchar)group[i] < 32 || (guchar)group[i] >= 127) { + /* valid ASCII only */ + parse_error("Invalid character found", parse, NULL); + group[i] = '\0'; /* stopping before this character */ + break; + } + + /* make sure it's a new group */ + g = g_hash_table_lookup(parse->group_hash, group); + if (g && g->seen) { + parse_error("Duplicate group found", parse, error); + g_free(group); + return; + } + /* if it's the first group, make sure it's named Desktop Entry */ + else if (!parse->group && strcmp(group, "Desktop Entry") != 0) + { + parse_error("Incorrect group found, " + "expected [Desktop Entry]", + parse, error); + g_free(group); + return; + } + else { + if (!g) { + g = parse_group_new(group, NULL); + g_hash_table_insert(parse->group_hash, g->name, g); + } + else + g_free(group); + + g->seen = TRUE; + parse->group = g; + g_print("Found group %s\n", g->name); + } +} + +static void parse_key_value(const gchar *buf, gulong len, + ObtDDParse *parse, gboolean *error) +{ + gulong i, keyend, valstart, eq; + char *key; + + /* find the end of the key */ + for (i = 0; i < len; ++i) + if (!(((guchar)buf[i] >= 'A' && (guchar)buf[i] <= 'Z') || + ((guchar)buf[i] >= 'a' && (guchar)buf[i] <= 'z') || + ((guchar)buf[i] >= '0' && (guchar)buf[i] <= '9') || + ((guchar)buf[i] == '-'))) { + /* not part of the key */ + keyend = i; + break; + } + if (keyend < 1) { + parse_error("Empty key", parse, error); + return; + } + /* find the = character */ + for (i = keyend; i < len; ++i) { + if (buf[i] == '=') { + eq = i; + break; + } + else if (buf[i] != ' ') { + parse_error("Invalid character in key name", parse, error); + return ; + } + } + if (i == len) { + parse_error("Key without value found", parse, error); + return; + } + /* find the start of the value */ + for (i = eq+1; i < len; ++i) + if (buf[i] != ' ') { + valstart = i; + break; + } + if (i == len) { + parse_error("Empty value found", parse, error); + return; + } + + key = g_strndup(buf, keyend); + if (g_hash_table_lookup(parse->group->key_hash, key)) { + parse_error("Duplicate key found", parse, error); + g_free(key); + return; + } + g_print("Found key/value %s=%s.\n", key, buf+valstart); + if (parse->group->value_func) + parse->group->value_func(key, buf+valstart, parse, error); +} + +static gboolean parse_file(FILE *f, ObtDDParse *parse) +{ + gchar *buf = NULL; + gulong bytes = 0, read = 0; + gboolean error = FALSE; + + while (!error && parse_file_line(f, &buf, &bytes, &read, parse, &error)) { + gulong len = strlen(buf); + if (buf[0] == '#' || buf[0] == '\0') + ; /* ignore comment lines */ + else if (buf[0] == '[' && buf[len-1] == ']') + parse_group(buf, len, parse, &error); + else if (!parse->group) + /* just ignore keys outside of groups */ + parse_error("Key found before group", parse, NULL); + else + /* ignore errors in key-value pairs and continue */ + parse_key_value(buf, len, parse, NULL); + ++parse->lineno; + } + + if (buf) g_free(buf); + return !error; +} + +static void parse_desktop_entry_value(gchar *key, const gchar *val, + ObtDDParse *parse, gboolean *error) +{ + ObtDDParseValue v, *pv; + + /* figure out value type */ + v.type = OBT_DDPARSE_NUM_VALUE_TYPES; + /* XXX do this part */ + + /* parse the value */ + switch (v.type) { + case OBT_DDPARSE_STRING: + v.value.string = parse_value_string(val, FALSE, NULL, parse, error); + g_assert(v.value.string); + break; + case OBT_DDPARSE_LOCALESTRING: + v.value.string = parse_value_string(val, TRUE, NULL, parse, error); + g_assert(v.value.string); + break; + case OBT_DDPARSE_STRINGS: + v.value.strings.s = parse_value_string(val, FALSE, &v.value.strings.n, + parse, error); + g_assert(v.value.strings.s); + g_assert(v.value.strings.n); + break; + case OBT_DDPARSE_LOCALESTRINGS: + v.value.strings.s = parse_value_string(val, TRUE, &v.value.strings.n, + parse, error); + g_assert(v.value.strings.s); + g_assert(v.value.strings.n); + break; + case OBT_DDPARSE_BOOLEAN: + v.value.boolean = parse_value_boolean(val, parse, error); + break; + case OBT_DDPARSE_NUMERIC: + v.value.numeric = parse_value_numeric(val, parse, error); + break; + default: + g_assert_not_reached(); + } + + pv = g_slice_new(ObtDDParseValue); + *pv = v; + g_hash_table_insert(parse->group->key_hash, key, pv); +} + +GHashTable* obt_ddparse_file(const gchar *name, GSList *paths) +{ + ObtDDParse parse; + ObtDDParseGroup *desktop_entry; + GSList *it; + FILE *f; + gboolean success; + + parse.filename = NULL; + parse.lineno = 0; + parse.group = NULL; + parse.group_hash = g_hash_table_new_full(g_str_hash, + g_str_equal, + NULL, + (GDestroyNotify)parse_group_free); + + /* set up the groups (there's only one right now) */ + desktop_entry = parse_group_new(g_strdup("Desktop Entry"), + parse_desktop_entry_value); + g_hash_table_insert(parse.group_hash, desktop_entry->name, desktop_entry); + + success = FALSE; + for (it = paths; it && !success; it = g_slist_next(it)) { + gchar *path = g_strdup_printf("%s/%s", (char*)it->data, name); + if ((f = fopen(path, "r"))) { + parse.filename = path; + parse.lineno = 1; + success = parse_file(f, &parse); + fclose(f); + } + g_free(path); + } + if (!success) { + g_hash_table_destroy(parse.group_hash); + return NULL; + } + else + return parse.group_hash; +} + +GHashTable* obt_ddparse_group_keys(ObtDDParseGroup *g) +{ + return g->key_hash; +} diff --git a/obt/ddparse.h b/obt/ddparse.h new file mode 100644 index 00000000..b4e0bf41 --- /dev/null +++ b/obt/ddparse.h @@ -0,0 +1,52 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- + + obt/ddparse.h for the Openbox window manager + Copyright (c) 2009 Dana Jansens + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + See the COPYING file for a copy of the GNU General Public License. +*/ + +#include + +typedef struct _ObtDDParseGroup ObtDDParseGroup; + +typedef enum { + OBT_DDPARSE_STRING, + OBT_DDPARSE_LOCALESTRING, + OBT_DDPARSE_STRINGS, + OBT_DDPARSE_LOCALESTRINGS, + OBT_DDPARSE_BOOLEAN, + OBT_DDPARSE_NUMERIC, + OBT_DDPARSE_NUM_VALUE_TYPES +} ObtDDParseValueType; + +typedef struct _ObtDDParseValue { + ObtDDParseValueType type; + union _ObtDDParseValueValue { + gchar *string; + struct _ObtDDParseValueStrings { + gchar *s; + gulong n; + } strings; + gboolean boolean; + gfloat numeric; + } value; +} ObtDDParseValue; + +/* Returns a hash table where the keys are groups, and the values are + ObtDDParseGroups */ +GHashTable* obt_ddparse_file(const gchar *name, GSList *paths); + +/* Returns a hash table where the keys are "keys" in the .desktop file, + and the values are "values" in the .desktop file, for the group @g. */ +GHashTable* obt_ddparse_group_keys(ObtDDParseGroup *g); diff --git a/obt/link.c b/obt/link.c new file mode 100644 index 00000000..8249118d --- /dev/null +++ b/obt/link.c @@ -0,0 +1,87 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- + + obt/link.c for the Openbox window manager + Copyright (c) 2009 Dana Jansens + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + See the COPYING file for a copy of the GNU General Public License. +*/ + +#include "obt/link.h" +#include "obt/ddparse.h" +#include + +struct _ObtLink { + guint ref; + + ObtLinkType type; + gchar *name; /*!< Specific name for the object (eg Firefox) */ + gchar *generic; /*!< Generic name for the object (eg Web Browser) */ + gchar *comment; /*!< Comment/description to display for the object */ + gchar *icon; /*!< Name/path for an icon for the object */ + + union _ObtLinkData { + struct _ObtLinkApp { + gchar *exec; /*!< Executable to run for the app */ + gchar *wdir; /*!< Working dir to run the app in */ + gboolean term; /*!< Run the app in a terminal or not */ + ObtLinkAppOpen open; + + /* XXX gchar**? or something better, a mime struct.. maybe + glib has something i can use. */ + gchar **mime; /*!< Mime types the app can open */ + + ObtLinkAppStartup startup; + gchar *startup_wmclass; + } app; + struct _ObtLinkLink { + gchar *url; + } link; + struct _ObtLinkDir { + } dir; + } d; +}; + +ObtLink* obt_link_from_ddfile(const gchar *name, GSList *paths) +{ + ObtLink *lnk; + GHashTable *groups, *keys; + ObtDDParseGroup *g; + + groups = obt_ddparse_file(name, paths); + if (!groups) return NULL; + g = g_hash_table_lookup(groups, "Desktop Entry"); + if (!g) { + g_hash_table_destroy(groups); + return NULL; + } + + keys = obt_ddparse_group_keys(g); + + lnk = g_slice_new(ObtLink); + lnk->ref = 1; + /* XXX turn the values in the .desktop file into an ObtLink */ + + return lnk; +} + +void obt_link_ref(ObtLink *dd) +{ + ++dd->ref; +} + +void obt_link_unref(ObtLink *dd) +{ + if (--dd->ref < 1) { + g_slice_free(ObtLink, dd); + } +} diff --git a/obt/link.h b/obt/link.h new file mode 100644 index 00000000..bcac9d99 --- /dev/null +++ b/obt/link.h @@ -0,0 +1,100 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- + + obt/link.h for the Openbox window manager + Copyright (c) 2009 Dana Jansens + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + See the COPYING file for a copy of the GNU General Public License. +*/ + +#ifndef __obt_link_h +#define __obt_link_h + +#include + +G_BEGIN_DECLS + +typedef enum { + OBT_LINK_TYPE_APPLICATION = 1, + OBT_LINK_TYPE_URL = 2, + OBT_LINK_TYPE_DIRECTORY = 3 +} ObtLinkType; + +typedef enum { + OBT_LINK_APP_STARTUP_NO_SUPPORT, + OBT_LINK_APP_STARTUP_PROTOCOL_SUPPORT, + OBT_LINK_APP_STARTUP_LEGACY_SUPPORT +} ObtLinkAppStartup; + +typedef enum { + /*! The app can be launched with a single local file */ + OBT_LINK_APP_SINGLE_LOCAL = 1 << 0, + /*! The app can be launched with multiple local files */ + OBT_LINK_APP_MULTI_LOCAL = 1 << 1, + /*! The app can be launched with a single URL */ + OBT_LINK_APP_SINGLE_URL = 1 << 2, + /*! The app can be launched with multiple URLs */ + OBT_LINK_APP_MULTI_URL = 1 << 3 +} ObtLinkAppOpen; + +typedef struct _ObtLink ObtLink; + +ObtLink* obt_link_from_ddfile(const gchar *name, GSList *paths); + +void obt_link_ref(ObtLink *e); +void obt_link_unref(ObtLink *e); + +/*! Returns TRUE if the file exists but says it should be ignored, with + the Hidden flag. No other functions can be used for the ObtLink + in this case. */ +gboolean obt_link_deleted (ObtLink *e); + +/*! Returns the type of object refered to by the .desktop file. */ +ObtLinkType obt_link_type (ObtLink *e); + +/*! Returns TRUE if the .desktop file should be displayed to users, given the + current environment. If FALSE, the .desktop file should not be showed. + This also uses the TryExec option if it is present. + @env A semicolon-deliminated list of environemnts. Can be one or more of: + GNOME, KDE, ROX, XFCE. Other environments not listed here may also + be supported. This can be null also if not listing any environment. */ +gboolean obt_link_display(ObtLink *e, const gchar *env); + +const gchar* obt_link_name (ObtLink *e); +const gchar* obt_link_generic_name (ObtLink *e); +const gchar* obt_link_comment (ObtLink *e); +/*! Returns the icon for the object referred to by the .desktop file. + Returns either an absolute path, or a string which can be used to find the + icon using the algorithm given by: + http://freedesktop.org/wiki/Specifications/icon-theme-spec?action=show&redirect=Standards/icon-theme-spec +*/ +const gchar* obt_link_icon (ObtLink *e); + +const gchar *obt_link_url_path(ObtLink *e); + +const gchar* obt_link_app_executable (ObtLink *e); +/*! Returns the path in which the application should be run */ +const gchar* obt_link_app_path (ObtLink *e); +gboolean obt_link_app_run_in_terminal (ObtLink *e); +const gchar** obt_link_app_mime_types (ObtLink *e); +/*! Returns a combination of values in the ObtLinkAppOpen enum, + specifying if the application can be launched to open one or more files + and URLs. */ +ObtLinkAppOpen obt_link_app_open(ObtLink *e); + +ObtLinkAppStartup obt_link_app_startup_notify(ObtLink *e); +const gchar* obt_link_app_startup_wmclass(ObtLink *e); + + +G_END_DECLS + +#endif -- cgit v1.2.3