diff options
Diffstat (limited to 'libmate-desktop/mate-desktop-item.c')
-rw-r--r-- | libmate-desktop/mate-desktop-item.c | 3872 |
1 files changed, 3872 insertions, 0 deletions
diff --git a/libmate-desktop/mate-desktop-item.c b/libmate-desktop/mate-desktop-item.c new file mode 100644 index 0000000..cf86a6d --- /dev/null +++ b/libmate-desktop/mate-desktop-item.c @@ -0,0 +1,3872 @@ +/* -*- Mode: C; c-set-style: linux indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* mate-desktop-item.c - MATE Desktop File Representation + + Copyright (C) 1999, 2000 Red Hat Inc. + Copyright (C) 2001 Sid Vicious + All rights reserved. + + This file is part of the Mate Library. + + Developed by Elliot Lee <[email protected]> and Sid Vicious + + The Mate Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Mate Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Mate Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ +/* + @NOTATION@ + */ + +#include "config.h" + +#include <limits.h> +#include <ctype.h> +#include <stdio.h> +#include <glib.h> +#include <sys/types.h> +#include <dirent.h> +#include <unistd.h> +#include <time.h> +#include <string.h> +#include <glib/gi18n-lib.h> +#include <locale.h> +#include <stdlib.h> + +#include <gio/gio.h> + +#ifdef HAVE_STARTUP_NOTIFICATION +#define SN_API_NOT_YET_FROZEN +#include <libsn/sn.h> +#include <gdk/gdk.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> +#endif + +#define sure_string(s) ((s)!=NULL?(s):"") + +#define MATE_DESKTOP_USE_UNSTABLE_API +#undef MATE_DISABLE_DEPRECATED +#include <libmate/mate-desktop-item.h> +#include <libmate/mate-desktop-utils.h> + +#include "private.h" + +struct _MateDesktopItem { + int refcount; + + /* all languages used */ + GList *languages; + + MateDesktopItemType type; + + /* `modified' means that the ditem has been + * modified since the last save. */ + gboolean modified; + + /* Keys of the main section only */ + GList *keys; + + GList *sections; + + /* This includes ALL keys, including + * other sections, separated by '/' */ + GHashTable *main_hash; + + char *location; + + time_t mtime; + + guint32 launch_time; +}; + +/* If mtime is set to this, set_location won't update mtime, + * this is to be used internally only. */ +#define DONT_UPDATE_MTIME ((time_t)-2) + +typedef struct { + char *name; + GList *keys; +} Section; + +typedef enum { + ENCODING_UNKNOWN, + ENCODING_UTF8, + ENCODING_LEGACY_MIXED +} Encoding; + +/* + * IO reading utils, that look like the libc buffered io stuff + */ + +#define READ_BUF_SIZE (32 * 1024) + +typedef struct { + GFile *file; + GFileInputStream *stream; + char *uri; + char *buf; + gboolean buf_needs_free; + gboolean past_first_read; + gboolean eof; + guint64 size; + gsize pos; +} ReadBuf; + +static MateDesktopItem *ditem_load (ReadBuf *rb, + gboolean no_translations, + GError **error); +static gboolean ditem_save (MateDesktopItem *item, + const char *uri, + GError **error); + +static void mate_desktop_item_set_location_gfile (MateDesktopItem *item, + GFile *file); + +static MateDesktopItem *mate_desktop_item_new_from_gfile (GFile *file, + MateDesktopItemLoadFlags flags, + GError **error); + +static int +readbuf_getc (ReadBuf *rb) +{ + if (rb->eof) + return EOF; + + if (rb->size == 0 || + rb->pos == rb->size) { + gssize bytes_read; + + if (rb->stream == NULL) + bytes_read = 0; + else + bytes_read = g_input_stream_read (G_INPUT_STREAM (rb->stream), + rb->buf, + READ_BUF_SIZE, + NULL, NULL); + + /* FIXME: handle errors other than EOF */ + if (bytes_read <= 0) { + rb->eof = TRUE; + return EOF; + } + + if (rb->size != 0) + rb->past_first_read = TRUE; + rb->size = bytes_read; + rb->pos = 0; + + } + + return (guchar) rb->buf[rb->pos++]; +} + +/* Note, does not include the trailing \n */ +static char * +readbuf_gets (char *buf, gsize bufsize, ReadBuf *rb) +{ + int c; + gsize pos; + + g_return_val_if_fail (buf != NULL, NULL); + g_return_val_if_fail (rb != NULL, NULL); + + pos = 0; + buf[0] = '\0'; + + do { + c = readbuf_getc (rb); + if (c == EOF || c == '\n') + break; + buf[pos++] = c; + } while (pos < bufsize-1); + + if (c == EOF && pos == 0) + return NULL; + + buf[pos++] = '\0'; + + return buf; +} + +static ReadBuf * +readbuf_open (GFile *file, GError **error) +{ + GError *local_error; + GFileInputStream *stream; + char *uri; + ReadBuf *rb; + + g_return_val_if_fail (file != NULL, NULL); + + uri = g_file_get_uri (file); + local_error = NULL; + stream = g_file_read (file, NULL, &local_error); + + if (stream == NULL) { + g_set_error (error, + /* FIXME: better errors */ + MATE_DESKTOP_ITEM_ERROR, + MATE_DESKTOP_ITEM_ERROR_CANNOT_OPEN, + _("Error reading file '%s': %s"), + uri, local_error->message); + g_error_free (local_error); + g_free (uri); + return NULL; + } + + rb = g_new0 (ReadBuf, 1); + rb->stream = stream; + rb->file = g_file_dup (file); + rb->uri = uri; + rb->buf = g_malloc (READ_BUF_SIZE); + rb->buf_needs_free = TRUE; + /* rb->past_first_read = FALSE; */ + /* rb->eof = FALSE; */ + /* rb->size = 0; */ + /* rb->pos = 0; */ + + return rb; +} + +static ReadBuf * +readbuf_new_from_string (const char *uri, const char *string, gssize length) +{ + ReadBuf *rb; + + g_return_val_if_fail (string != NULL, NULL); + g_return_val_if_fail (length >= 0, NULL); + + rb = g_new0 (ReadBuf, 1); + /* rb->file = NULL; */ + /* rb->stream = NULL; */ + rb->uri = g_strdup (uri); + rb->buf = (char *) string; + /* rb->buf_needs_free = FALSE; */ + /* rb->past_first_read = FALSE; */ + /* rb->eof = FALSE; */ + rb->size = length; + /* rb->pos = 0; */ + + return rb; +} + +static gboolean +readbuf_rewind (ReadBuf *rb, GError **error) +{ + GError *local_error; + + rb->eof = FALSE; + rb->pos = 0; + + if (!rb->past_first_read) + return TRUE; + + rb->size = 0; + + if (g_seekable_seek (G_SEEKABLE (rb->stream), + 0, G_SEEK_SET, NULL, NULL)) + return TRUE; + + g_object_unref (rb->stream); + local_error = NULL; + rb->stream = g_file_read (rb->file, NULL, &local_error); + + if (rb->stream == NULL) { + g_set_error ( + error, MATE_DESKTOP_ITEM_ERROR, + MATE_DESKTOP_ITEM_ERROR_CANNOT_OPEN, + _("Error rewinding file '%s': %s"), + rb->uri, local_error->message); + g_error_free (local_error); + + return FALSE; + } + + return TRUE; +} + +static void +readbuf_close (ReadBuf *rb) +{ + if (rb->stream != NULL) + g_object_unref (rb->stream); + if (rb->file != NULL) + g_object_unref (rb->file); + g_free (rb->uri); + if (rb->buf_needs_free) + g_free (rb->buf); + g_free (rb); +} + +static MateDesktopItemType +type_from_string (const char *type) +{ + if (!type) + return MATE_DESKTOP_ITEM_TYPE_NULL; + + switch (type [0]) { + case 'A': + if (!strcmp (type, "Application")) + return MATE_DESKTOP_ITEM_TYPE_APPLICATION; + break; + case 'L': + if (!strcmp (type, "Link")) + return MATE_DESKTOP_ITEM_TYPE_LINK; + break; + case 'F': + if (!strcmp (type, "FSDevice")) + return MATE_DESKTOP_ITEM_TYPE_FSDEVICE; + break; + case 'M': + if (!strcmp (type, "MimeType")) + return MATE_DESKTOP_ITEM_TYPE_MIME_TYPE; + break; + case 'D': + if (!strcmp (type, "Directory")) + return MATE_DESKTOP_ITEM_TYPE_DIRECTORY; + break; + case 'S': + if (!strcmp (type, "Service")) + return MATE_DESKTOP_ITEM_TYPE_SERVICE; + + else if (!strcmp (type, "ServiceType")) + return MATE_DESKTOP_ITEM_TYPE_SERVICE_TYPE; + break; + default: + break; + } + + return MATE_DESKTOP_ITEM_TYPE_OTHER; +} + +/** + * mate_desktop_item_new: + * + * Creates a MateDesktopItem object. The reference count on the returned value is set to '1'. + * + * Returns: The new MateDesktopItem + */ +MateDesktopItem * +mate_desktop_item_new (void) +{ + MateDesktopItem *retval; + + _mate_desktop_init_i18n (); + + retval = g_new0 (MateDesktopItem, 1); + + retval->refcount++; + + retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + /* These are guaranteed to be set */ + mate_desktop_item_set_string (retval, + MATE_DESKTOP_ITEM_NAME, + /* Translators: the "name" mentioned + * here is the name of an application or + * a document */ + _("No name")); + mate_desktop_item_set_string (retval, + MATE_DESKTOP_ITEM_ENCODING, + "UTF-8"); + mate_desktop_item_set_string (retval, + MATE_DESKTOP_ITEM_VERSION, + "1.0"); + + retval->launch_time = 0; + + return retval; +} + +static Section * +dup_section (Section *sec) +{ + GList *li; + Section *retval = g_new0 (Section, 1); + + retval->name = g_strdup (sec->name); + + retval->keys = g_list_copy (sec->keys); + for (li = retval->keys; li != NULL; li = li->next) + li->data = g_strdup (li->data); + + return retval; +} + +static void +copy_string_hash (gpointer key, gpointer value, gpointer user_data) +{ + GHashTable *copy = user_data; + g_hash_table_replace (copy, + g_strdup (key), + g_strdup (value)); +} + + +/** + * mate_desktop_item_copy: + * @item: The item to be copied + * + * Creates a copy of a MateDesktopItem. The new copy has a refcount of 1. + * Note: Section stack is NOT copied. + * + * Returns: The new copy + */ +MateDesktopItem * +mate_desktop_item_copy (const MateDesktopItem *item) +{ + GList *li; + MateDesktopItem *retval; + + g_return_val_if_fail (item != NULL, NULL); + g_return_val_if_fail (item->refcount > 0, NULL); + + retval = mate_desktop_item_new (); + + retval->type = item->type; + retval->modified = item->modified; + retval->location = g_strdup (item->location); + retval->mtime = item->mtime; + retval->launch_time = item->launch_time; + + /* Languages */ + retval->languages = g_list_copy (item->languages); + for (li = retval->languages; li != NULL; li = li->next) + li->data = g_strdup (li->data); + + /* Keys */ + retval->keys = g_list_copy (item->keys); + for (li = retval->keys; li != NULL; li = li->next) + li->data = g_strdup (li->data); + + /* Sections */ + retval->sections = g_list_copy (item->sections); + for (li = retval->sections; li != NULL; li = li->next) + li->data = dup_section (li->data); + + retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + g_hash_table_foreach (item->main_hash, + copy_string_hash, + retval->main_hash); + + return retval; +} + +static void +read_sort_order (MateDesktopItem *item, GFile *dir) +{ + GFile *child; + char buf[BUFSIZ]; + GString *str; + ReadBuf *rb; + + child = g_file_get_child (dir, ".order"); + + rb = readbuf_open (child, NULL); + g_object_unref (child); + + if (rb == NULL) + return; + + str = NULL; + while (readbuf_gets (buf, sizeof (buf), rb) != NULL) { + if (str == NULL) + str = g_string_new (buf); + else + g_string_append (str, buf); + g_string_append_c (str, ';'); + } + readbuf_close (rb); + if (str != NULL) { + mate_desktop_item_set_string (item, MATE_DESKTOP_ITEM_SORT_ORDER, + str->str); + g_string_free (str, TRUE); + } +} + +static MateDesktopItem * +make_fake_directory (GFile *dir) +{ + MateDesktopItem *item; + GFile *child; + + item = mate_desktop_item_new (); + mate_desktop_item_set_entry_type (item, + MATE_DESKTOP_ITEM_TYPE_DIRECTORY); + + + item->mtime = DONT_UPDATE_MTIME; /* it doesn't exist, we know that */ + child = g_file_get_child (dir, ".directory"); + mate_desktop_item_set_location_gfile (item, child); + item->mtime = 0; + g_object_unref (child); + + read_sort_order (item, dir); + + return item; +} + +/** + * mate_desktop_item_new_from_file: + * @file: The filename or directory path to load the MateDesktopItem from + * @flags: Flags to influence the loading process + * + * This function loads 'file' and turns it into a MateDesktopItem. + * + * Returns: The newly loaded item. + */ +MateDesktopItem * +mate_desktop_item_new_from_file (const char *file, + MateDesktopItemLoadFlags flags, + GError **error) +{ + MateDesktopItem *retval; + GFile *gfile; + + g_return_val_if_fail (file != NULL, NULL); + + gfile = g_file_new_for_path (file); + retval = mate_desktop_item_new_from_gfile (gfile, flags, error); + g_object_unref (gfile); + + return retval; +} + +/** + * mate_desktop_item_new_from_uri: + * @uri: URI to load the MateDesktopItem from + * @flags: Flags to influence the loading process + * + * This function loads 'uri' and turns it into a MateDesktopItem. + * + * Returns: The newly loaded item. + */ +MateDesktopItem * +mate_desktop_item_new_from_uri (const char *uri, + MateDesktopItemLoadFlags flags, + GError **error) +{ + MateDesktopItem *retval; + GFile *file; + + g_return_val_if_fail (uri != NULL, NULL); + + file = g_file_new_for_uri (uri); + retval = mate_desktop_item_new_from_gfile (file, flags, error); + g_object_unref (file); + + return retval; +} + +static MateDesktopItem * +mate_desktop_item_new_from_gfile (GFile *file, + MateDesktopItemLoadFlags flags, + GError **error) +{ + MateDesktopItem *retval; + GFile *subfn; + GFileInfo *info; + GFileType type; + GFile *parent; + time_t mtime = 0; + ReadBuf *rb; + + g_return_val_if_fail (file != NULL, NULL); + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_TYPE","G_FILE_ATTRIBUTE_TIME_MODIFIED, + G_FILE_QUERY_INFO_NONE, NULL, error); + if (info == NULL) + return NULL; + + type = g_file_info_get_file_type (info); + + if (type != G_FILE_TYPE_REGULAR && type != G_FILE_TYPE_DIRECTORY) { + char *uri; + + uri = g_file_get_uri (file); + g_set_error (error, + /* FIXME: better errors */ + MATE_DESKTOP_ITEM_ERROR, + MATE_DESKTOP_ITEM_ERROR_INVALID_TYPE, + _("File '%s' is not a regular file or directory."), + uri); + + g_free (uri); + g_object_unref (info); + + return NULL; + } + + mtime = g_file_info_get_attribute_uint64 (info, + G_FILE_ATTRIBUTE_TIME_MODIFIED); + + g_object_unref (info); + + if (type == G_FILE_TYPE_DIRECTORY) { + GFile *child; + GFileInfo *child_info; + + child = g_file_get_child (file, ".directory"); + child_info = g_file_query_info (child, + G_FILE_ATTRIBUTE_TIME_MODIFIED, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + + if (child_info == NULL) { + g_object_unref (child); + + if (flags & MATE_DESKTOP_ITEM_LOAD_ONLY_IF_EXISTS) { + return NULL; + } else { + return make_fake_directory (file); + } + } + + mtime = g_file_info_get_attribute_uint64 (child_info, + G_FILE_ATTRIBUTE_TIME_MODIFIED); + g_object_unref (child_info); + + subfn = child; + } else { + subfn = g_file_dup (file); + } + + rb = readbuf_open (subfn, error); + + if (rb == NULL) { + g_object_unref (subfn); + return NULL; + } + + retval = ditem_load (rb, + (flags & MATE_DESKTOP_ITEM_LOAD_NO_TRANSLATIONS) != 0, + error); + + if (retval == NULL) { + g_object_unref (subfn); + return NULL; + } + + if (flags & MATE_DESKTOP_ITEM_LOAD_ONLY_IF_EXISTS && + ! mate_desktop_item_exists (retval)) { + mate_desktop_item_unref (retval); + g_object_unref (subfn); + return NULL; + } + + retval->mtime = DONT_UPDATE_MTIME; + mate_desktop_item_set_location_gfile (retval, subfn); + retval->mtime = mtime; + + parent = g_file_get_parent (file); + if (parent != NULL) { + read_sort_order (retval, parent); + g_object_unref (parent); + } + + g_object_unref (subfn); + + return retval; +} + +/** + * mate_desktop_item_new_from_string: + * @string: string to load the MateDesktopItem from + * @length: length of string, or -1 to use strlen + * @flags: Flags to influence the loading process + * @error: place to put errors + * + * This function turns the contents of the string into a MateDesktopItem. + * + * Returns: The newly loaded item. + */ +MateDesktopItem * +mate_desktop_item_new_from_string (const char *uri, + const char *string, + gssize length, + MateDesktopItemLoadFlags flags, + GError **error) +{ + MateDesktopItem *retval; + ReadBuf *rb; + + g_return_val_if_fail (string != NULL, NULL); + g_return_val_if_fail (length >= -1, NULL); + + if (length == -1) { + length = strlen (string); + } + + rb = readbuf_new_from_string (uri, string, length); + + retval = ditem_load (rb, + (flags & MATE_DESKTOP_ITEM_LOAD_NO_TRANSLATIONS) != 0, + error); + + if (retval == NULL) { + return NULL; + } + + /* FIXME: Sort order? */ + + return retval; +} + +static char * +lookup_desktop_file_in_data_dir (const char *desktop_file, + const char *data_dir) +{ + char *path; + + path = g_build_filename (data_dir, "applications", desktop_file, NULL); + if (!g_file_test (path, G_FILE_TEST_EXISTS)) { + g_free (path); + return NULL; + } + return path; +} + +static char * +file_from_basename (const char *basename) +{ + const char * const *system_data_dirs; + const char *user_data_dir; + char *retval; + int i; + + user_data_dir = g_get_user_data_dir (); + system_data_dirs = g_get_system_data_dirs (); + + if ((retval = lookup_desktop_file_in_data_dir (basename, user_data_dir))) { + return retval; + } + for (i = 0; system_data_dirs[i]; i++) { + if ((retval = lookup_desktop_file_in_data_dir (basename, system_data_dirs[i]))) { + return retval; + } + } + return NULL; +} + +/** + * mate_desktop_item_new_from_basename: + * @basename: The basename of the MateDesktopItem to load. + * @flags: Flags to influence the loading process + * + * This function loads 'basename' from a system data directory and + * returns its MateDesktopItem. + * + * Returns: The newly loaded item. + */ +MateDesktopItem * +mate_desktop_item_new_from_basename (const char *basename, + MateDesktopItemLoadFlags flags, + GError **error) +{ + MateDesktopItem *retval; + char *file; + + g_return_val_if_fail (basename != NULL, NULL); + + if (!(file = file_from_basename (basename))) { + g_set_error (error, + MATE_DESKTOP_ITEM_ERROR, + MATE_DESKTOP_ITEM_ERROR_CANNOT_OPEN, + _("Cannot find file '%s'"), + basename); + return NULL; + } + + retval = mate_desktop_item_new_from_file (file, flags, error); + g_free (file); + + return retval; +} + +/** + * mate_desktop_item_save: + * @item: A desktop item + * @under: A new uri (location) for this #MateDesktopItem + * @force: Save even if it wasn't modified + * @error: #GError return + * + * Writes the specified item to disk. If the 'under' is NULL, the original + * location is used. It sets the location of this entry to point to the + * new location. + * + * Returns: boolean. %TRUE if the file was saved, %FALSE otherwise + */ +gboolean +mate_desktop_item_save (MateDesktopItem *item, + const char *under, + gboolean force, + GError **error) +{ + const char *uri; + + if (under == NULL && + ! force && + ! item->modified) + return TRUE; + + if (under == NULL) + uri = item->location; + else + uri = under; + + if (uri == NULL) { + g_set_error (error, + MATE_DESKTOP_ITEM_ERROR, + MATE_DESKTOP_ITEM_ERROR_NO_FILENAME, + _("No filename to save to")); + return FALSE; + } + + if ( ! ditem_save (item, uri, error)) + return FALSE; + + item->modified = FALSE; + item->mtime = time (NULL); + + return TRUE; +} + +/** + * mate_desktop_item_ref: + * @item: A desktop item + * + * Description: Increases the reference count of the specified item. + * + * Returns: the newly referenced @item + */ +MateDesktopItem * +mate_desktop_item_ref (MateDesktopItem *item) +{ + g_return_val_if_fail (item != NULL, NULL); + + item->refcount++; + + return item; +} + +static void +free_section (gpointer data, gpointer user_data) +{ + Section *section = data; + + g_free (section->name); + section->name = NULL; + + g_list_foreach (section->keys, (GFunc)g_free, NULL); + g_list_free (section->keys); + section->keys = NULL; + + g_free (section); +} + +/** + * mate_desktop_item_unref: + * @item: A desktop item + * + * Decreases the reference count of the specified item, and destroys the item if there are no more references left. + */ +void +mate_desktop_item_unref (MateDesktopItem *item) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + + item->refcount--; + + if(item->refcount != 0) + return; + + g_list_foreach (item->languages, (GFunc)g_free, NULL); + g_list_free (item->languages); + item->languages = NULL; + + g_list_foreach (item->keys, (GFunc)g_free, NULL); + g_list_free (item->keys); + item->keys = NULL; + + g_list_foreach (item->sections, free_section, NULL); + g_list_free (item->sections); + item->sections = NULL; + + g_hash_table_destroy (item->main_hash); + item->main_hash = NULL; + + g_free (item->location); + item->location = NULL; + + g_free (item); +} + +static Section * +find_section (MateDesktopItem *item, const char *section) +{ + GList *li; + Section *sec; + + if (section == NULL) + return NULL; + if (strcmp (section, "Desktop Entry") == 0) + return NULL; + + for (li = item->sections; li != NULL; li = li->next) { + sec = li->data; + if (strcmp (sec->name, section) == 0) + return sec; + } + + sec = g_new0 (Section, 1); + sec->name = g_strdup (section); + sec->keys = NULL; + + item->sections = g_list_append (item->sections, sec); + + /* Don't mark the item modified, this is just an empty section, + * it won't be saved even */ + + return sec; +} + +static Section * +section_from_key (MateDesktopItem *item, const char *key) +{ + char *p; + char *name; + Section *sec; + + if (key == NULL) + return NULL; + + p = strchr (key, '/'); + if (p == NULL) + return NULL; + + name = g_strndup (key, p - key); + + sec = find_section (item, name); + + g_free (name); + + return sec; +} + +static const char * +key_basename (const char *key) +{ + char *p = strrchr (key, '/'); + if (p != NULL) + return p+1; + else + return key; +} + + +static const char * +lookup (const MateDesktopItem *item, const char *key) +{ + return g_hash_table_lookup (item->main_hash, key); +} + +static const char * +lookup_locale (const MateDesktopItem *item, const char *key, const char *locale) +{ + if (locale == NULL || + strcmp (locale, "C") == 0) { + return lookup (item, key); + } else { + const char *ret; + char *full = g_strdup_printf ("%s[%s]", key, locale); + ret = lookup (item, full); + g_free (full); + return ret; + } +} + +static const char * +lookup_best_locale (const MateDesktopItem *item, const char *key) +{ + const char * const *langs_pointer; + int i; + + langs_pointer = g_get_language_names (); + for (i = 0; langs_pointer[i] != NULL; i++) { + const char *ret = NULL; + + ret = lookup_locale (item, key, langs_pointer[i]); + if (ret != NULL) + return ret; + } + + return NULL; +} + +static void +set (MateDesktopItem *item, const char *key, const char *value) +{ + Section *sec = section_from_key (item, key); + + if (sec != NULL) { + if (value != NULL) { + if (g_hash_table_lookup (item->main_hash, key) == NULL) + sec->keys = g_list_append + (sec->keys, + g_strdup (key_basename (key))); + + g_hash_table_replace (item->main_hash, + g_strdup (key), + g_strdup (value)); + } else { + GList *list = g_list_find_custom + (sec->keys, key_basename (key), + (GCompareFunc)strcmp); + if (list != NULL) { + g_free (list->data); + sec->keys = + g_list_delete_link (sec->keys, list); + } + g_hash_table_remove (item->main_hash, key); + } + } else { + if (value != NULL) { + if (g_hash_table_lookup (item->main_hash, key) == NULL) + item->keys = g_list_append (item->keys, + g_strdup (key)); + + g_hash_table_replace (item->main_hash, + g_strdup (key), + g_strdup (value)); + } else { + GList *list = g_list_find_custom + (item->keys, key, (GCompareFunc)strcmp); + if (list != NULL) { + g_free (list->data); + item->keys = + g_list_delete_link (item->keys, list); + } + g_hash_table_remove (item->main_hash, key); + } + } + item->modified = TRUE; +} + +static void +set_locale (MateDesktopItem *item, const char *key, + const char *locale, const char *value) +{ + if (locale == NULL || + strcmp (locale, "C") == 0) { + set (item, key, value); + } else { + char *full = g_strdup_printf ("%s[%s]", key, locale); + set (item, full, value); + g_free (full); + + /* add the locale to the list of languages if it wasn't there + * before */ + if (g_list_find_custom (item->languages, locale, + (GCompareFunc)strcmp) == NULL) + item->languages = g_list_prepend (item->languages, + g_strdup (locale)); + } +} + +static char ** +list_to_vector (GSList *list) +{ + int len = g_slist_length (list); + char **argv; + int i; + GSList *li; + + argv = g_new0 (char *, len+1); + + for (i = 0, li = list; + li != NULL; + li = li->next, i++) { + argv[i] = g_strdup (li->data); + } + argv[i] = NULL; + + return argv; +} + +static GSList * +make_args (GList *files) +{ + GSList *list = NULL; + GList *li; + + for (li = files; li != NULL; li = li->next) { + GFile *gfile; + const char *file = li->data; + if (file == NULL) + continue; + gfile = g_file_new_for_uri (file); + list = g_slist_prepend (list, gfile); + } + + return g_slist_reverse (list); +} + +static void +free_args (GSList *list) +{ + GSList *li; + + for (li = list; li != NULL; li = li->next) { + g_object_unref (G_FILE (li->data)); + li->data = NULL; + } + g_slist_free (list); +} + +static char * +escape_single_quotes (const char *s, + gboolean in_single_quotes, + gboolean in_double_quotes) +{ + const char *p; + GString *gs; + const char *pre = ""; + const char *post = ""; + + if ( ! in_single_quotes && ! in_double_quotes) { + pre = "'"; + post = "'"; + } else if ( ! in_single_quotes && in_double_quotes) { + pre = "\"'"; + post = "'\""; + } + + if (strchr (s, '\'') == NULL) { + return g_strconcat (pre, s, post, NULL); + } + + gs = g_string_new (pre); + + for (p = s; *p != '\0'; p++) { + if (*p == '\'') + g_string_append (gs, "'\\''"); + else + g_string_append_c (gs, *p); + } + + g_string_append (gs, post); + + return g_string_free (gs, FALSE); +} + +typedef enum { + URI_TO_STRING, + URI_TO_LOCAL_PATH, + URI_TO_LOCAL_DIRNAME, + URI_TO_LOCAL_BASENAME +} ConversionType; + +static char * +convert_uri (GFile *file, + ConversionType conversion) +{ + char *retval = NULL; + + switch (conversion) { + case URI_TO_STRING: + retval = g_file_get_uri (file); + break; + case URI_TO_LOCAL_PATH: + retval = g_file_get_path (file); + break; + case URI_TO_LOCAL_DIRNAME: + { + char *local_path; + + local_path = g_file_get_path (file); + retval = g_path_get_dirname (local_path); + g_free (local_path); + } + break; + case URI_TO_LOCAL_BASENAME: + retval = g_file_get_basename (file); + break; + default: + g_assert_not_reached (); + } + + return retval; +} + +typedef enum { + ADDED_NONE = 0, + ADDED_SINGLE, + ADDED_ALL +} AddedStatus; + +static AddedStatus +append_all_converted (GString *str, + ConversionType conversion, + GSList *args, + gboolean in_single_quotes, + gboolean in_double_quotes, + AddedStatus added_status) +{ + GSList *l; + + for (l = args; l; l = l->next) { + char *converted; + char *escaped; + + if (!(converted = convert_uri (l->data, conversion))) + continue; + + g_string_append (str, " "); + + escaped = escape_single_quotes (converted, + in_single_quotes, + in_double_quotes); + g_string_append (str, escaped); + + g_free (escaped); + g_free (converted); + } + + return ADDED_ALL; +} + +static AddedStatus +append_first_converted (GString *str, + ConversionType conversion, + GSList **arg_ptr, + gboolean in_single_quotes, + gboolean in_double_quotes, + AddedStatus added_status) +{ + GSList *l; + char *converted = NULL; + char *escaped; + + for (l = *arg_ptr; l; l = l->next) { + if ((converted = convert_uri (l->data, conversion))) + break; + + *arg_ptr = l->next; + } + + if (!converted) + return added_status; + + escaped = escape_single_quotes (converted, in_single_quotes, in_double_quotes); + g_string_append (str, escaped); + g_free (escaped); + g_free (converted); + + return added_status != ADDED_ALL ? ADDED_SINGLE : added_status; +} + +static gboolean +do_percent_subst (const MateDesktopItem *item, + const char *arg, + GString *str, + gboolean in_single_quotes, + gboolean in_double_quotes, + GSList *args, + GSList **arg_ptr, + AddedStatus *added_status) +{ + char *esc; + const char *cs; + + if (arg[0] != '%' || arg[1] == '\0') { + return FALSE; + } + + switch (arg[1]) { + case '%': + g_string_append_c (str, '%'); + break; + case 'U': + *added_status = append_all_converted (str, + URI_TO_STRING, + args, + in_single_quotes, + in_double_quotes, + *added_status); + break; + case 'F': + *added_status = append_all_converted (str, + URI_TO_LOCAL_PATH, + args, + in_single_quotes, + in_double_quotes, + *added_status); + break; + case 'N': + *added_status = append_all_converted (str, + URI_TO_LOCAL_BASENAME, + args, + in_single_quotes, + in_double_quotes, + *added_status); + break; + case 'D': + *added_status = append_all_converted (str, + URI_TO_LOCAL_DIRNAME, + args, + in_single_quotes, + in_double_quotes, + *added_status); + break; + case 'f': + *added_status = append_first_converted (str, + URI_TO_LOCAL_PATH, + arg_ptr, + in_single_quotes, + in_double_quotes, + *added_status); + break; + case 'u': + *added_status = append_first_converted (str, + URI_TO_STRING, + arg_ptr, + in_single_quotes, + in_double_quotes, + *added_status); + break; + case 'd': + *added_status = append_first_converted (str, + URI_TO_LOCAL_DIRNAME, + arg_ptr, + in_single_quotes, + in_double_quotes, + *added_status); + break; + case 'n': + *added_status = append_first_converted (str, + URI_TO_LOCAL_BASENAME, + arg_ptr, + in_single_quotes, + in_double_quotes, + *added_status); + break; + case 'm': + /* Note: v0.9.4 of the spec says this is deprecated + * and replace with --miniicon iconname */ + cs = mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_MINI_ICON); + if (cs != NULL) { + g_string_append (str, "--miniicon="); + esc = escape_single_quotes (cs, in_single_quotes, in_double_quotes); + g_string_append (str, esc); + } + break; + case 'i': + /* Note: v0.9.4 of the spec says replace with --icon iconname */ + cs = mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_ICON); + if (cs != NULL) { + g_string_append (str, "--icon="); + esc = escape_single_quotes (cs, in_single_quotes, in_double_quotes); + g_string_append (str, esc); + } + break; + case 'c': + cs = mate_desktop_item_get_localestring (item, MATE_DESKTOP_ITEM_NAME); + if (cs != NULL) { + esc = escape_single_quotes (cs, in_single_quotes, in_double_quotes); + g_string_append (str, esc); + g_free (esc); + } + break; + case 'k': + if (item->location != NULL) { + esc = escape_single_quotes (item->location, in_single_quotes, in_double_quotes); + g_string_append (str, esc); + g_free (esc); + } + break; + case 'v': + cs = mate_desktop_item_get_localestring (item, MATE_DESKTOP_ITEM_DEV); + if (cs != NULL) { + esc = escape_single_quotes (cs, in_single_quotes, in_double_quotes); + g_string_append (str, esc); + g_free (esc); + } + break; + default: + /* Maintain special characters - e.g. "%20" */ + if (g_ascii_isdigit (arg [1])) + g_string_append_c (str, '%'); + return FALSE; + } + + return TRUE; +} + +static char * +expand_string (const MateDesktopItem *item, + const char *s, + GSList *args, + GSList **arg_ptr, + AddedStatus *added_status) +{ + const char *p; + gboolean escape = FALSE; + gboolean single_quot = FALSE; + gboolean double_quot = FALSE; + GString *gs = g_string_new (NULL); + + for (p = s; *p != '\0'; p++) { + if (escape) { + escape = FALSE; + g_string_append_c (gs, *p); + } else if (*p == '\\') { + if ( ! single_quot) + escape = TRUE; + g_string_append_c (gs, *p); + } else if (*p == '\'') { + g_string_append_c (gs, *p); + if ( ! single_quot && ! double_quot) { + single_quot = TRUE; + } else if (single_quot) { + single_quot = FALSE; + } + } else if (*p == '"') { + g_string_append_c (gs, *p); + if ( ! single_quot && ! double_quot) { + double_quot = TRUE; + } else if (double_quot) { + double_quot = FALSE; + } + } else if (*p == '%') { + if (do_percent_subst (item, p, gs, + single_quot, double_quot, + args, arg_ptr, + added_status)) { + p++; + } + } else { + g_string_append_c (gs, *p); + } + } + return g_string_free (gs, FALSE); +} + +#ifdef HAVE_STARTUP_NOTIFICATION +static void +sn_error_trap_push (SnDisplay *display, + Display *xdisplay) +{ + gdk_error_trap_push (); +} + +static void +sn_error_trap_pop (SnDisplay *display, + Display *xdisplay) +{ + gdk_error_trap_pop (); +} + +static char ** +make_spawn_environment_for_sn_context (SnLauncherContext *sn_context, + char **envp) +{ + char **retval; + char **freeme; + int i, j; + int desktop_startup_id_len; + + retval = freeme = NULL; + + if (envp == NULL) { + envp = freeme = g_listenv (); + for (i = 0; envp[i]; i++) { + char *name = envp[i]; + + envp[i] = g_strjoin ("=", name, g_getenv (name), NULL); + g_free (name); + } + } else { + for (i = 0; envp[i]; i++) + ; + } + + retval = g_new (char *, i + 2); + + desktop_startup_id_len = strlen ("DESKTOP_STARTUP_ID"); + + for (i = 0, j = 0; envp[i]; i++) { + if (strncmp (envp[i], "DESKTOP_STARTUP_ID", desktop_startup_id_len) != 0) { + retval[j] = g_strdup (envp[i]); + ++j; + } + } + + retval[j] = g_strdup_printf ("DESKTOP_STARTUP_ID=%s", + sn_launcher_context_get_startup_id (sn_context)); + ++j; + retval[j] = NULL; + + g_strfreev (freeme); + + return retval; +} + +/* This should be fairly long, as it's confusing to users if a startup + * ends when it shouldn't (it appears that the startup failed, and + * they have to relaunch the app). Also the timeout only matters when + * there are bugs and apps don't end their own startup sequence. + * + * This timeout is a "last resort" timeout that ignores whether the + * startup sequence has shown activity or not. Marco and the + * tasklist have smarter, and correspondingly able-to-be-shorter + * timeouts. The reason our timeout is dumb is that we don't monitor + * the sequence (don't use an SnMonitorContext) + */ +#define STARTUP_TIMEOUT_LENGTH_SEC 30 /* seconds */ +#define STARTUP_TIMEOUT_LENGTH (STARTUP_TIMEOUT_LENGTH_SEC * 1000) + +typedef struct +{ + GdkScreen *screen; + GSList *contexts; + guint timeout_id; +} StartupTimeoutData; + +static void +free_startup_timeout (void *data) +{ + StartupTimeoutData *std = data; + + g_slist_foreach (std->contexts, + (GFunc) sn_launcher_context_unref, + NULL); + g_slist_free (std->contexts); + + if (std->timeout_id != 0) { + g_source_remove (std->timeout_id); + std->timeout_id = 0; + } + + g_free (std); +} + +static gboolean +startup_timeout (void *data) +{ + StartupTimeoutData *std = data; + GSList *tmp; + GTimeVal now; + int min_timeout; + + min_timeout = STARTUP_TIMEOUT_LENGTH; + + g_get_current_time (&now); + + tmp = std->contexts; + while (tmp != NULL) { + SnLauncherContext *sn_context = tmp->data; + GSList *next = tmp->next; + long tv_sec, tv_usec; + double elapsed; + + sn_launcher_context_get_last_active_time (sn_context, + &tv_sec, &tv_usec); + + elapsed = + ((((double)now.tv_sec - tv_sec) * G_USEC_PER_SEC + + (now.tv_usec - tv_usec))) / 1000.0; + + if (elapsed >= STARTUP_TIMEOUT_LENGTH) { + std->contexts = g_slist_remove (std->contexts, + sn_context); + sn_launcher_context_complete (sn_context); + sn_launcher_context_unref (sn_context); + } else { + min_timeout = MIN (min_timeout, (STARTUP_TIMEOUT_LENGTH - elapsed)); + } + + tmp = next; + } + + /* we'll use seconds for the timeout */ + if (min_timeout < 1000) + min_timeout = 1000; + + if (std->contexts == NULL) { + std->timeout_id = 0; + } else { + std->timeout_id = g_timeout_add_seconds (min_timeout / 1000, + startup_timeout, + std); + } + + /* always remove this one, but we may have reinstalled another one. */ + return FALSE; +} + +static void +add_startup_timeout (GdkScreen *screen, + SnLauncherContext *sn_context) +{ + StartupTimeoutData *data; + + data = g_object_get_data (G_OBJECT (screen), "mate-startup-data"); + if (data == NULL) { + data = g_new (StartupTimeoutData, 1); + data->screen = screen; + data->contexts = NULL; + data->timeout_id = 0; + + g_object_set_data_full (G_OBJECT (screen), "mate-startup-data", + data, free_startup_timeout); + } + + sn_launcher_context_ref (sn_context); + data->contexts = g_slist_prepend (data->contexts, sn_context); + + if (data->timeout_id == 0) { + data->timeout_id = g_timeout_add_seconds ( + STARTUP_TIMEOUT_LENGTH_SEC, + startup_timeout, + data); + } +} +#endif /* HAVE_STARTUP_NOTIFICATION */ + +static inline char * +stringify_uris (GSList *args) +{ + GString *str; + + str = g_string_new (NULL); + + append_all_converted (str, URI_TO_STRING, args, FALSE, FALSE, ADDED_NONE); + + return g_string_free (str, FALSE); +} + +static inline char * +stringify_files (GSList *args) +{ + GString *str; + + str = g_string_new (NULL); + + append_all_converted (str, URI_TO_LOCAL_PATH, args, FALSE, FALSE, ADDED_NONE); + + return g_string_free (str, FALSE); +} + +static char ** +make_environment_for_screen (GdkScreen *screen, + char **envp) +{ + char **retval; + char **freeme; + char *display_name; + int display_index = -1; + int i, env_len; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + + retval = freeme = NULL; + + if (envp == NULL) { + envp = freeme = g_listenv (); + for (i = 0; envp [i]; i++) { + char *name = envp[i]; + + envp[i] = g_strjoin ("=", name, g_getenv (name), NULL); + g_free (name); + } + } + + for (env_len = 0; envp [env_len]; env_len++) + if (strncmp (envp [env_len], "DISPLAY", strlen ("DISPLAY")) == 0) + display_index = env_len; + + retval = g_new (char *, env_len + 1); + retval [env_len] = NULL; + + display_name = gdk_screen_make_display_name (screen); + + for (i = 0; i < env_len; i++) + if (i == display_index) + retval [i] = g_strconcat ("DISPLAY=", display_name, NULL); + else + retval [i] = g_strdup (envp[i]); + + g_assert (i == env_len); + + g_free (display_name); + g_strfreev (freeme); + + return retval; +} + +static int +ditem_execute (const MateDesktopItem *item, + const char *exec, + GList *file_list, + GdkScreen *screen, + int workspace, + char **envp, + gboolean launch_only_one, + gboolean use_current_dir, + gboolean append_uris, + gboolean append_paths, + gboolean do_not_reap_child, + GError **error) +{ + char **free_me = NULL; + char **real_argv; + int i, ret; + char **term_argv = NULL; + int term_argc = 0; + GSList *vector_list; + GSList *args, *arg_ptr; + AddedStatus added_status; + const char *working_dir = NULL; + char **temp_argv = NULL; + int temp_argc = 0; + char *new_exec, *uris, *temp; + char *exec_locale; + int launched = 0; +#ifdef HAVE_STARTUP_NOTIFICATION + GdkDisplay *gdkdisplay; + SnLauncherContext *sn_context; + SnDisplay *sn_display; + const char *startup_class; +#endif + + g_return_val_if_fail (item, -1); + + if (item->type == MATE_DESKTOP_ITEM_TYPE_APPLICATION) { + working_dir = mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_PATH); + if (working_dir && + !g_file_test (working_dir, G_FILE_TEST_IS_DIR)) + working_dir = NULL; + } + + if (working_dir == NULL && !use_current_dir) + working_dir = g_get_home_dir (); + + if (mate_desktop_item_get_boolean (item, MATE_DESKTOP_ITEM_TERMINAL)) { + const char *options = + mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_TERMINAL_OPTIONS); + + if (options != NULL) { + g_shell_parse_argv (options, + &term_argc, + &term_argv, + NULL /* error */); + /* ignore errors */ + } + + mate_desktop_prepend_terminal_to_vector (&term_argc, &term_argv); + } + + args = make_args (file_list); + arg_ptr = make_args (file_list); + +#ifdef HAVE_STARTUP_NOTIFICATION + if (screen) + gdkdisplay = gdk_screen_get_display (screen); + else + gdkdisplay = gdk_display_get_default (); + + sn_display = sn_display_new (GDK_DISPLAY_XDISPLAY (gdkdisplay), + sn_error_trap_push, + sn_error_trap_pop); + + + /* Only initiate notification if desktop file supports it. + * (we could avoid setting up the SnLauncherContext if we aren't going + * to initiate, but why bother) + */ + + startup_class = mate_desktop_item_get_string (item, + "StartupWMClass"); + if (startup_class || + mate_desktop_item_get_boolean (item, "StartupNotify")) { + const char *name; + const char *icon; + + sn_context = sn_launcher_context_new (sn_display, + screen ? gdk_screen_get_number (screen) : + DefaultScreen (GDK_DISPLAY_XDISPLAY (gdkdisplay))); + + name = mate_desktop_item_get_localestring (item, + MATE_DESKTOP_ITEM_NAME); + + if (name == NULL) + name = mate_desktop_item_get_localestring (item, + MATE_DESKTOP_ITEM_GENERIC_NAME); + + if (name != NULL) { + char *description; + + sn_launcher_context_set_name (sn_context, name); + + description = g_strdup_printf (_("Starting %s"), name); + + sn_launcher_context_set_description (sn_context, description); + + g_free (description); + } + + icon = mate_desktop_item_get_string (item, + MATE_DESKTOP_ITEM_ICON); + + if (icon != NULL) + sn_launcher_context_set_icon_name (sn_context, icon); + + sn_launcher_context_set_workspace (sn_context, workspace); + + if (startup_class != NULL) + sn_launcher_context_set_wmclass (sn_context, + startup_class); + } else { + sn_context = NULL; + } +#endif + + if (screen) { + envp = make_environment_for_screen (screen, envp); + if (free_me) + g_strfreev (free_me); + free_me = envp; + } + + exec_locale = g_filename_from_utf8 (exec, -1, NULL, NULL, NULL); + + if (exec_locale == NULL) { + exec_locale = g_strdup (""); + } + + do { + added_status = ADDED_NONE; + new_exec = expand_string (item, + exec_locale, + args, &arg_ptr, &added_status); + + if (launched == 0 && added_status == ADDED_NONE && append_uris) { + uris = stringify_uris (args); + temp = g_strconcat (new_exec, " ", uris, NULL); + g_free (uris); + g_free (new_exec); + new_exec = temp; + added_status = ADDED_ALL; + } + + /* append_uris and append_paths are mutually exlusive */ + if (launched == 0 && added_status == ADDED_NONE && append_paths) { + uris = stringify_files (args); + temp = g_strconcat (new_exec, " ", uris, NULL); + g_free (uris); + g_free (new_exec); + new_exec = temp; + added_status = ADDED_ALL; + } + + if (launched > 0 && added_status == ADDED_NONE) { + g_free (new_exec); + break; + } + + if ( ! g_shell_parse_argv (new_exec, + &temp_argc, &temp_argv, error)) { + /* The error now comes from g_shell_parse_argv */ + g_free (new_exec); + ret = -1; + break; + } + g_free (new_exec); + + vector_list = NULL; + for(i = 0; i < term_argc; i++) + vector_list = g_slist_append (vector_list, + g_strdup (term_argv[i])); + + for(i = 0; i < temp_argc; i++) + vector_list = g_slist_append (vector_list, + g_strdup (temp_argv[i])); + + g_strfreev (temp_argv); + + real_argv = list_to_vector (vector_list); + g_slist_foreach (vector_list, (GFunc)g_free, NULL); + g_slist_free (vector_list); + +#ifdef HAVE_STARTUP_NOTIFICATION + if (sn_context != NULL && + !sn_launcher_context_get_initiated (sn_context)) { + guint32 launch_time; + + /* This means that we always use the first real_argv[0] + * we select for the "binary name", but it's probably + * OK to do that. Binary name isn't super-important + * anyway, and we can't initiate twice, and we + * must initiate prior to fork/exec. + */ + + sn_launcher_context_set_binary_name (sn_context, + real_argv[0]); + + if (item->launch_time > 0) + launch_time = item->launch_time; + else + launch_time = gdk_x11_display_get_user_time (gdkdisplay); + + sn_launcher_context_initiate (sn_context, + g_get_prgname () ? g_get_prgname () : "unknown", + real_argv[0], + launch_time); + + /* Don't allow accidental reuse of same timestamp */ + ((MateDesktopItem *)item)->launch_time = 0; + + envp = make_spawn_environment_for_sn_context (sn_context, envp); + if (free_me) + g_strfreev (free_me); + free_me = envp; + } +#endif + + + if ( ! g_spawn_async (working_dir, + real_argv, + envp, + (do_not_reap_child ? G_SPAWN_DO_NOT_REAP_CHILD : 0) | G_SPAWN_SEARCH_PATH /* flags */, + NULL, /* child_setup_func */ + NULL, /* child_setup_func_data */ + &ret /* child_pid */, + error)) { + /* The error was set for us, + * we just can't launch this thingie */ + ret = -1; + g_strfreev (real_argv); + break; + } + launched ++; + + g_strfreev (real_argv); + + if (arg_ptr != NULL) + arg_ptr = arg_ptr->next; + + /* rinse, repeat until we run out of arguments (That + * is if we were adding singles anyway) */ + } while (added_status == ADDED_SINGLE && + arg_ptr != NULL && + ! launch_only_one); + + g_free (exec_locale); +#ifdef HAVE_STARTUP_NOTIFICATION + if (sn_context != NULL) { + if (ret < 0) + sn_launcher_context_complete (sn_context); /* end sequence */ + else + add_startup_timeout (screen ? screen : + gdk_display_get_default_screen (gdk_display_get_default ()), + sn_context); + sn_launcher_context_unref (sn_context); + } + + sn_display_unref (sn_display); +#endif /* HAVE_STARTUP_NOTIFICATION */ + + free_args (args); + + if (term_argv) + g_strfreev (term_argv); + + if (free_me) + g_strfreev (free_me); + + return ret; +} + +/* strip any trailing &, return FALSE if bad things happen and + we end up with an empty string */ +static gboolean +strip_the_amp (char *exec) +{ + size_t exec_len; + + g_strstrip (exec); + if (*exec == '\0') + return FALSE; + + exec_len = strlen (exec); + /* kill any trailing '&' */ + if (exec[exec_len-1] == '&') { + exec[exec_len-1] = '\0'; + g_strchomp (exec); + } + + /* can't exactly launch an empty thing */ + if (*exec == '\0') + return FALSE; + + return TRUE; +} + + +static int +mate_desktop_item_launch_on_screen_with_env ( + const MateDesktopItem *item, + GList *file_list, + MateDesktopItemLaunchFlags flags, + GdkScreen *screen, + int workspace, + char **envp, + GError **error) +{ + const char *exec; + char *the_exec; + int ret; + + exec = mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_EXEC); + /* This is a URL, so launch it as a url */ + if (item->type == MATE_DESKTOP_ITEM_TYPE_LINK) { + const char *url; + gboolean retval; + + url = mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_URL); + /* Mate panel used to put this in Exec */ + if (!(url && url[0] != '\0')) + url = exec; + + if (!(url && url[0] != '\0')) { + g_set_error (error, + MATE_DESKTOP_ITEM_ERROR, + MATE_DESKTOP_ITEM_ERROR_NO_URL, + _("No URL to launch")); + return -1; + } + + retval = gtk_show_uri (screen, + url, + GDK_CURRENT_TIME, + error); + return retval ? 0 : -1; + } + + /* check the type, if there is one set */ + if (item->type != MATE_DESKTOP_ITEM_TYPE_APPLICATION) { + g_set_error (error, + MATE_DESKTOP_ITEM_ERROR, + MATE_DESKTOP_ITEM_ERROR_NOT_LAUNCHABLE, + _("Not a launchable item")); + return -1; + } + + + if (exec == NULL || + exec[0] == '\0') { + g_set_error (error, + MATE_DESKTOP_ITEM_ERROR, + MATE_DESKTOP_ITEM_ERROR_NO_EXEC_STRING, + _("No command (Exec) to launch")); + return -1; + } + + + /* make a new copy and get rid of spaces */ + the_exec = g_alloca (strlen (exec) + 1); + strcpy (the_exec, exec); + + if ( ! strip_the_amp (the_exec)) { + g_set_error (error, + MATE_DESKTOP_ITEM_ERROR, + MATE_DESKTOP_ITEM_ERROR_BAD_EXEC_STRING, + _("Bad command (Exec) to launch")); + return -1; + } + + ret = ditem_execute (item, the_exec, file_list, screen, workspace, envp, + (flags & MATE_DESKTOP_ITEM_LAUNCH_ONLY_ONE), + (flags & MATE_DESKTOP_ITEM_LAUNCH_USE_CURRENT_DIR), + (flags & MATE_DESKTOP_ITEM_LAUNCH_APPEND_URIS), + (flags & MATE_DESKTOP_ITEM_LAUNCH_APPEND_PATHS), + (flags & MATE_DESKTOP_ITEM_LAUNCH_DO_NOT_REAP_CHILD), + error); + + return ret; +} + +/** + * mate_desktop_item_launch: + * @item: A desktop item + * @file_list: Files/URIs to launch this item with, can be %NULL + * @flags: FIXME + * @error: FIXME + * + * This function runs the program listed in the specified 'item', + * optionally appending additional arguments to its command line. It uses + * #g_shell_parse_argv to parse the the exec string into a vector which is + * then passed to #g_spawn_async for execution. This can return all + * the errors from MateURL, #g_shell_parse_argv and #g_spawn_async, + * in addition to it's own. The files are + * only added if the entry defines one of the standard % strings in it's + * Exec field. + * + * Returns: The the pid of the process spawned. If more then one + * process was spawned the last pid is returned. On error -1 + * is returned and @error is set. + */ +int +mate_desktop_item_launch (const MateDesktopItem *item, + GList *file_list, + MateDesktopItemLaunchFlags flags, + GError **error) +{ + return mate_desktop_item_launch_on_screen_with_env ( + item, file_list, flags, NULL, -1, NULL, error); +} + +/** + * mate_desktop_item_launch_with_env: + * @item: A desktop item + * @file_list: Files/URIs to launch this item with, can be %NULL + * @flags: FIXME + * @envp: child's environment, or %NULL to inherit parent's + * @error: FIXME + * + * See mate_desktop_item_launch for a full description. This function + * additionally passes an environment vector for the child process + * which is to be launched. + * + * Returns: The the pid of the process spawned. If more then one + * process was spawned the last pid is returned. On error -1 + * is returned and @error is set. + */ +int +mate_desktop_item_launch_with_env (const MateDesktopItem *item, + GList *file_list, + MateDesktopItemLaunchFlags flags, + char **envp, + GError **error) +{ + return mate_desktop_item_launch_on_screen_with_env ( + item, file_list, flags, + NULL, -1, envp, error); +} + +/** + * mate_desktop_item_launch_on_screen: + * @item: A desktop item + * @file_list: Files/URIs to launch this item with, can be %NULL + * @flags: FIXME + * @screen: the %GdkScreen on which the application should be launched + * @workspace: the workspace on which the app should be launched (-1 for current) + * @error: FIXME + * + * See mate_desktop_item_launch for a full description. This function + * additionally attempts to launch the application on a given screen + * and workspace. + * + * Returns: The the pid of the process spawned. If more then one + * process was spawned the last pid is returned. On error -1 + * is returned and @error is set. + */ +int +mate_desktop_item_launch_on_screen (const MateDesktopItem *item, + GList *file_list, + MateDesktopItemLaunchFlags flags, + GdkScreen *screen, + int workspace, + GError **error) +{ + return mate_desktop_item_launch_on_screen_with_env ( + item, file_list, flags, + screen, workspace, NULL, error); +} + +/** + * mate_desktop_item_drop_uri_list: + * @item: A desktop item + * @uri_list: text as gotten from a text/uri-list + * @flags: FIXME + * @error: FIXME + * + * A list of files or urls dropped onto an icon, the proper (Url or File) + * exec is run you can pass directly string that you got as the + * text/uri-list. This just parses the list and calls + * + * Returns: The value returned by #mate_execute_async() upon execution of + * the specified item or -1 on error. If multiple instances are run, the + * return of the last one is returned. + */ +int +mate_desktop_item_drop_uri_list (const MateDesktopItem *item, + const char *uri_list, + MateDesktopItemLaunchFlags flags, + GError **error) +{ + return mate_desktop_item_drop_uri_list_with_env (item, uri_list, + flags, NULL, error); +} + +/** +* mate_desktop_item_drop_uri_list_with_env: +* @item: A desktop item +* @uri_list: text as gotten from a text/uri-list +* @flags: FIXME +* @envp: child's environment +* @error: FIXME +* +* See mate_desktop_item_drop_uri_list for a full description. This function +* additionally passes an environment vector for the child process +* which is to be launched. +* +* Returns: The value returned by #mate_execute_async() upon execution of +* the specified item or -1 on error. If multiple instances are run, the +* return of the last one is returned. +*/ +int +mate_desktop_item_drop_uri_list_with_env (const MateDesktopItem *item, + const char *uri_list, + MateDesktopItemLaunchFlags flags, + char **envp, + GError **error) +{ + int ret; + char *uri; + char **uris; + GList *list = NULL; + + uris = g_uri_list_extract_uris (uri_list); + + for (uri = uris[0]; uri != NULL; uri++) { + list = g_list_prepend (list, uri); + } + list = g_list_reverse (list); + + ret = mate_desktop_item_launch_with_env ( + item, list, flags, envp, error); + + g_strfreev (uris); + g_list_free (list); + + return ret; +} + +static gboolean +exec_exists (const char *exec) +{ + if (g_path_is_absolute (exec)) { + if (access (exec, X_OK) == 0) + return TRUE; + else + return FALSE; + } else { + char *tryme; + + tryme = g_find_program_in_path (exec); + if (tryme != NULL) { + g_free (tryme); + return TRUE; + } + return FALSE; + } +} + +/** + * mate_desktop_item_exists: + * @item: A desktop item + * + * Attempt to figure out if the program that can be executed by this item + * actually exists. First it tries the TryExec attribute to see if that + * contains a program that is in the path. Then if there is no such + * attribute, it tries the first word of the Exec attribute. + * + * Returns: A boolean, %TRUE if it exists, %FALSE otherwise. + */ +gboolean +mate_desktop_item_exists (const MateDesktopItem *item) +{ + const char *try_exec; + const char *exec; + + g_return_val_if_fail (item != NULL, FALSE); + + try_exec = lookup (item, MATE_DESKTOP_ITEM_TRY_EXEC); + + if (try_exec != NULL && + ! exec_exists (try_exec)) { + return FALSE; + } + + if (item->type == MATE_DESKTOP_ITEM_TYPE_APPLICATION) { + int argc; + char **argv; + const char *exe; + + exec = lookup (item, MATE_DESKTOP_ITEM_EXEC); + if (exec == NULL) + return FALSE; + + if ( ! g_shell_parse_argv (exec, &argc, &argv, NULL)) + return FALSE; + + if (argc < 1) { + g_strfreev (argv); + return FALSE; + } + + exe = argv[0]; + + if ( ! exec_exists (exe)) { + g_strfreev (argv); + return FALSE; + } + g_strfreev (argv); + } + + return TRUE; +} + +/** + * mate_desktop_item_get_entry_type: + * @item: A desktop item + * + * Gets the type attribute (the 'Type' field) of the item. This should + * usually be 'Application' for an application, but it can be 'Directory' + * for a directory description. There are other types available as well. + * The type usually indicates how the desktop item should be handeled and + * how the 'Exec' field should be handeled. + * + * Returns: The type of the specified 'item'. The returned + * memory remains owned by the MateDesktopItem and should not be freed. + */ +MateDesktopItemType +mate_desktop_item_get_entry_type (const MateDesktopItem *item) +{ + g_return_val_if_fail (item != NULL, 0); + g_return_val_if_fail (item->refcount > 0, 0); + + return item->type; +} + +void +mate_desktop_item_set_entry_type (MateDesktopItem *item, + MateDesktopItemType type) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + + item->type = type; + + switch (type) { + case MATE_DESKTOP_ITEM_TYPE_NULL: + set (item, MATE_DESKTOP_ITEM_TYPE, NULL); + break; + case MATE_DESKTOP_ITEM_TYPE_APPLICATION: + set (item, MATE_DESKTOP_ITEM_TYPE, "Application"); + break; + case MATE_DESKTOP_ITEM_TYPE_LINK: + set (item, MATE_DESKTOP_ITEM_TYPE, "Link"); + break; + case MATE_DESKTOP_ITEM_TYPE_FSDEVICE: + set (item, MATE_DESKTOP_ITEM_TYPE, "FSDevice"); + break; + case MATE_DESKTOP_ITEM_TYPE_MIME_TYPE: + set (item, MATE_DESKTOP_ITEM_TYPE, "MimeType"); + break; + case MATE_DESKTOP_ITEM_TYPE_DIRECTORY: + set (item, MATE_DESKTOP_ITEM_TYPE, "Directory"); + break; + case MATE_DESKTOP_ITEM_TYPE_SERVICE: + set (item, MATE_DESKTOP_ITEM_TYPE, "Service"); + break; + case MATE_DESKTOP_ITEM_TYPE_SERVICE_TYPE: + set (item, MATE_DESKTOP_ITEM_TYPE, "ServiceType"); + break; + default: + break; + } +} + + + +/** + * mate_desktop_item_get_file_status: + * @item: A desktop item + * + * This function checks the modification time of the on-disk file to + * see if it is more recent than the in-memory data. + * + * Returns: An enum value that specifies whether the item has changed since being loaded. + */ +MateDesktopItemStatus +mate_desktop_item_get_file_status (const MateDesktopItem *item) +{ + MateDesktopItemStatus retval; + GFile *file; + GFileInfo *info; + + g_return_val_if_fail (item != NULL, MATE_DESKTOP_ITEM_DISAPPEARED); + g_return_val_if_fail (item->refcount > 0, MATE_DESKTOP_ITEM_DISAPPEARED); + + if (item->location == NULL) + return MATE_DESKTOP_ITEM_DISAPPEARED; + + file = g_file_new_for_uri (item->location); + info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, + G_FILE_QUERY_INFO_NONE, NULL, NULL); + + retval = MATE_DESKTOP_ITEM_UNCHANGED; + + if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED)) + retval = MATE_DESKTOP_ITEM_DISAPPEARED; + else if (item->mtime < g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED)) + retval = MATE_DESKTOP_ITEM_CHANGED; + + g_object_unref (info); + g_object_unref (file); + + return retval; +} + +/** + * mate_desktop_item_find_icon: + * @icon_theme: a #GtkIconTheme + * @icon: icon name, something you'd get out of the Icon key + * @desired_size: FIXME + * @flags: FIXME + * + * Description: This function goes and looks for the icon file. If the icon + * is not an absolute filename, this will look for it in the standard places. + * If it can't find the icon, it will return %NULL + * + * Returns: A newly allocated string + */ +char * +mate_desktop_item_find_icon (GtkIconTheme *icon_theme, + const char *icon, + int desired_size, + int flags) +{ + GtkIconInfo *info; + char *full = NULL; + + g_return_val_if_fail (icon_theme == NULL || + GTK_IS_ICON_THEME (icon_theme), NULL); + + if (icon == NULL || strcmp(icon,"") == 0) { + return NULL; + } else if (g_path_is_absolute (icon)) { + if (g_file_test (icon, G_FILE_TEST_EXISTS)) { + return g_strdup (icon); + } else { + return NULL; + } + } else { + char *icon_no_extension; + char *p; + + if (icon_theme == NULL) + icon_theme = gtk_icon_theme_get_default (); + + icon_no_extension = g_strdup (icon); + p = strrchr (icon_no_extension, '.'); + if (p && + (strcmp (p, ".png") == 0 || + strcmp (p, ".xpm") == 0 || + strcmp (p, ".svg") == 0)) { + *p = 0; + } + + + info = gtk_icon_theme_lookup_icon (icon_theme, + icon_no_extension, + desired_size, + 0); + + full = NULL; + if (info) { + full = g_strdup (gtk_icon_info_get_filename (info)); + gtk_icon_info_free (info); + } + g_free (icon_no_extension); + } + + return full; + +} + +/** + * mate_desktop_item_get_icon: + * @icon_theme: a #GtkIconTheme + * @item: A desktop item + * + * Description: This function goes and looks for the icon file. If the icon + * is not set as an absolute filename, this will look for it in the standard places. + * If it can't find the icon, it will return %NULL + * + * Returns: A newly allocated string + */ +char * +mate_desktop_item_get_icon (const MateDesktopItem *item, + GtkIconTheme *icon_theme) +{ + /* maybe this function should be deprecated in favour of find icon + * -George */ + const char *icon; + + g_return_val_if_fail (item != NULL, NULL); + g_return_val_if_fail (item->refcount > 0, NULL); + + icon = mate_desktop_item_get_string (item, MATE_DESKTOP_ITEM_ICON); + + return mate_desktop_item_find_icon (icon_theme, icon, + 48 /* desired_size */, + 0 /* flags */); +} + +/** + * mate_desktop_item_get_location: + * @item: A desktop item + * + * Returns: The file location associated with 'item'. + * + */ +const char * +mate_desktop_item_get_location (const MateDesktopItem *item) +{ + g_return_val_if_fail (item != NULL, NULL); + g_return_val_if_fail (item->refcount > 0, NULL); + + return item->location; +} + +/** + * mate_desktop_item_set_location: + * @item: A desktop item + * @location: A uri string specifying the file location of this particular item. + * + * Set's the 'location' uri of this item. + */ +void +mate_desktop_item_set_location (MateDesktopItem *item, const char *location) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + + if (item->location != NULL && + location != NULL && + strcmp (item->location, location) == 0) + return; + + g_free (item->location); + item->location = g_strdup (location); + + /* This is ugly, but useful internally */ + if (item->mtime != DONT_UPDATE_MTIME) { + item->mtime = 0; + + if (item->location) { + GFile *file; + GFileInfo *info; + + file = g_file_new_for_uri (item->location); + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_TIME_MODIFIED, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + if (info) { + if (g_file_info_has_attribute (info, + G_FILE_ATTRIBUTE_TIME_MODIFIED)) + item->mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); + g_object_unref (info); + } + + g_object_unref (file); + } + } + + /* Make sure that save actually saves */ + item->modified = TRUE; +} + +/** + * mate_desktop_item_set_location_file: + * @item: A desktop item + * @file: A local filename specifying the file location of this particular item. + * + * Set's the 'location' uri of this item to the given @file. + */ +void +mate_desktop_item_set_location_file (MateDesktopItem *item, const char *file) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + + if (file != NULL) { + GFile *gfile; + + gfile = g_file_new_for_path (file); + mate_desktop_item_set_location_gfile (item, gfile); + g_object_unref (gfile); + } else { + mate_desktop_item_set_location (item, NULL); + } +} + +static void +mate_desktop_item_set_location_gfile (MateDesktopItem *item, GFile *file) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + + if (file != NULL) { + char *uri; + + uri = g_file_get_uri (file); + mate_desktop_item_set_location (item, uri); + g_free (uri); + } else { + mate_desktop_item_set_location (item, NULL); + } +} + +/* + * Reading/Writing different sections, NULL is the standard section + */ + +gboolean +mate_desktop_item_attr_exists (const MateDesktopItem *item, + const char *attr) +{ + g_return_val_if_fail (item != NULL, FALSE); + g_return_val_if_fail (item->refcount > 0, FALSE); + g_return_val_if_fail (attr != NULL, FALSE); + + return lookup (item, attr) != NULL; +} + +/* + * String type + */ +const char * +mate_desktop_item_get_string (const MateDesktopItem *item, + const char *attr) +{ + g_return_val_if_fail (item != NULL, NULL); + g_return_val_if_fail (item->refcount > 0, NULL); + g_return_val_if_fail (attr != NULL, NULL); + + return lookup (item, attr); +} + +void +mate_desktop_item_set_string (MateDesktopItem *item, + const char *attr, + const char *value) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + g_return_if_fail (attr != NULL); + + set (item, attr, value); + + if (strcmp (attr, MATE_DESKTOP_ITEM_TYPE) == 0) + item->type = type_from_string (value); +} + +/* + * LocaleString type + */ +const char* mate_desktop_item_get_localestring(const MateDesktopItem* item, const char* attr) +{ + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->refcount > 0, NULL); + g_return_val_if_fail(attr != NULL, NULL); + + return lookup_best_locale(item, attr); +} + +const char* mate_desktop_item_get_localestring_lang(const MateDesktopItem* item, const char* attr, const char* language) +{ + g_return_val_if_fail(item != NULL, NULL); + g_return_val_if_fail(item->refcount > 0, NULL); + g_return_val_if_fail(attr != NULL, NULL); + + return lookup_locale(item, attr, language); +} + +/** + * mate_desktop_item_get_string_locale: + * @item: A desktop item + * @attr: An attribute name + * + * Returns the current locale that is used for the given attribute. + * This might not be the same for all attributes. For example, if your + * locale is "en_US.ISO8859-1" but attribute FOO only has "en_US" then + * that would be returned for attr = "FOO". If attribute BAR has + * "en_US.ISO8859-1" then that would be returned for "BAR". + * + * Returns: a string equal to the current locale or NULL + * if the attribute is invalid or there is no matching locale. + */ +const char * +mate_desktop_item_get_attr_locale (const MateDesktopItem *item, + const char *attr) +{ + const char * const *langs_pointer; + int i; + + langs_pointer = g_get_language_names (); + for (i = 0; langs_pointer[i] != NULL; i++) { + const char *value = NULL; + + value = lookup_locale (item, attr, langs_pointer[i]); + if (value) + return langs_pointer[i]; + } + + return NULL; +} + +GList * +mate_desktop_item_get_languages (const MateDesktopItem *item, + const char *attr) +{ + GList *li; + GList *list = NULL; + + g_return_val_if_fail (item != NULL, NULL); + g_return_val_if_fail (item->refcount > 0, NULL); + + for (li = item->languages; li != NULL; li = li->next) { + char *language = li->data; + if (attr == NULL || + lookup_locale (item, attr, language) != NULL) { + list = g_list_prepend (list, language); + } + } + + return g_list_reverse (list); +} + +static const char * +get_language (void) +{ + const char * const *langs_pointer; + int i; + + langs_pointer = g_get_language_names (); + for (i = 0; langs_pointer[i] != NULL; i++) { + /* find first without encoding */ + if (strchr (langs_pointer[i], '.') == NULL) { + return langs_pointer[i]; + } + } + return NULL; +} + +void +mate_desktop_item_set_localestring (MateDesktopItem *item, + const char *attr, + const char *value) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + g_return_if_fail (attr != NULL); + + set_locale (item, attr, get_language (), value); +} + +void +mate_desktop_item_set_localestring_lang (MateDesktopItem *item, + const char *attr, + const char *language, + const char *value) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + g_return_if_fail (attr != NULL); + + set_locale (item, attr, language, value); +} + +void +mate_desktop_item_clear_localestring (MateDesktopItem *item, + const char *attr) +{ + GList *l; + + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + g_return_if_fail (attr != NULL); + + for (l = item->languages; l != NULL; l = l->next) + set_locale (item, attr, l->data, NULL); + + set (item, attr, NULL); +} + +/* + * Strings, Regexps types + */ + +char ** +mate_desktop_item_get_strings (const MateDesktopItem *item, + const char *attr) +{ + const char *value; + + g_return_val_if_fail (item != NULL, NULL); + g_return_val_if_fail (item->refcount > 0, NULL); + g_return_val_if_fail (attr != NULL, NULL); + + value = lookup (item, attr); + if (value == NULL) + return NULL; + + /* FIXME: there's no way to escape semicolons apparently */ + return g_strsplit (value, ";", -1); +} + +void +mate_desktop_item_set_strings (MateDesktopItem *item, + const char *attr, + char **strings) +{ + char *str, *str2; + + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + g_return_if_fail (attr != NULL); + + str = g_strjoinv (";", strings); + str2 = g_strconcat (str, ";", NULL); + /* FIXME: there's no way to escape semicolons apparently */ + set (item, attr, str2); + g_free (str); + g_free (str2); +} + +/* + * Boolean type + */ +gboolean +mate_desktop_item_get_boolean (const MateDesktopItem *item, + const char *attr) +{ + const char *value; + + g_return_val_if_fail (item != NULL, FALSE); + g_return_val_if_fail (item->refcount > 0, FALSE); + g_return_val_if_fail (attr != NULL, FALSE); + + value = lookup (item, attr); + if (value == NULL) + return FALSE; + + return (value[0] == 'T' || + value[0] == 't' || + value[0] == 'Y' || + value[0] == 'y' || + atoi (value) != 0); +} + +void +mate_desktop_item_set_boolean (MateDesktopItem *item, + const char *attr, + gboolean value) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + g_return_if_fail (attr != NULL); + + set (item, attr, value ? "true" : "false"); +} + +void +mate_desktop_item_set_launch_time (MateDesktopItem *item, + guint32 timestamp) +{ + g_return_if_fail (item != NULL); + + item->launch_time = timestamp; +} + +/* + * Clearing attributes + */ +void +mate_desktop_item_clear_section (MateDesktopItem *item, + const char *section) +{ + Section *sec; + GList *li; + + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + + sec = find_section (item, section); + + if (sec == NULL) { + for (li = item->keys; li != NULL; li = li->next) { + g_hash_table_remove (item->main_hash, li->data); + g_free (li->data); + li->data = NULL; + } + g_list_free (item->keys); + item->keys = NULL; + } else { + for (li = sec->keys; li != NULL; li = li->next) { + char *key = li->data; + char *full = g_strdup_printf ("%s/%s", + sec->name, key); + g_hash_table_remove (item->main_hash, full); + g_free (full); + g_free (key); + li->data = NULL; + } + g_list_free (sec->keys); + sec->keys = NULL; + } + item->modified = TRUE; +} + +/************************************************************ + * Parser: * + ************************************************************/ + +static gboolean G_GNUC_CONST +standard_is_boolean (const char * key) +{ + static GHashTable *bools = NULL; + + if (bools == NULL) { + bools = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (bools, + MATE_DESKTOP_ITEM_NO_DISPLAY, + MATE_DESKTOP_ITEM_NO_DISPLAY); + g_hash_table_insert (bools, + MATE_DESKTOP_ITEM_HIDDEN, + MATE_DESKTOP_ITEM_HIDDEN); + g_hash_table_insert (bools, + MATE_DESKTOP_ITEM_TERMINAL, + MATE_DESKTOP_ITEM_TERMINAL); + g_hash_table_insert (bools, + MATE_DESKTOP_ITEM_READ_ONLY, + MATE_DESKTOP_ITEM_READ_ONLY); + } + + return g_hash_table_lookup (bools, key) != NULL; +} + +static gboolean G_GNUC_CONST +standard_is_strings (const char *key) +{ + static GHashTable *strings = NULL; + + if (strings == NULL) { + strings = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (strings, + MATE_DESKTOP_ITEM_FILE_PATTERN, + MATE_DESKTOP_ITEM_FILE_PATTERN); + g_hash_table_insert (strings, + MATE_DESKTOP_ITEM_ACTIONS, + MATE_DESKTOP_ITEM_ACTIONS); + g_hash_table_insert (strings, + MATE_DESKTOP_ITEM_MIME_TYPE, + MATE_DESKTOP_ITEM_MIME_TYPE); + g_hash_table_insert (strings, + MATE_DESKTOP_ITEM_PATTERNS, + MATE_DESKTOP_ITEM_PATTERNS); + g_hash_table_insert (strings, + MATE_DESKTOP_ITEM_SORT_ORDER, + MATE_DESKTOP_ITEM_SORT_ORDER); + } + + return g_hash_table_lookup (strings, key) != NULL; +} + +/* If no need to cannonize, returns NULL */ +static char * +cannonize (const char *key, const char *value) +{ + if (standard_is_boolean (key)) { + if (value[0] == 'T' || + value[0] == 't' || + value[0] == 'Y' || + value[0] == 'y' || + atoi (value) != 0) { + return g_strdup ("true"); + } else { + return g_strdup ("false"); + } + } else if (standard_is_strings (key)) { + int len = strlen (value); + if (len == 0 || value[len-1] != ';') { + return g_strconcat (value, ";", NULL); + } + } + /* XXX: Perhaps we should canonize numeric values as well, but this + * has caused some subtle problems before so it needs to be done + * carefully if at all */ + return NULL; +} + + +static char * +decode_string_and_dup (const char *s) +{ + char *p = g_malloc (strlen (s) + 1); + char *q = p; + + do { + if (*s == '\\'){ + switch (*(++s)){ + case 's': + *p++ = ' '; + break; + case 't': + *p++ = '\t'; + break; + case 'n': + *p++ = '\n'; + break; + case '\\': + *p++ = '\\'; + break; + case 'r': + *p++ = '\r'; + break; + default: + *p++ = '\\'; + *p++ = *s; + break; + } + } else { + *p++ = *s; + } + } while (*s++); + + return q; +} + +static char * +escape_string_and_dup (const char *s) +{ + char *return_value, *p; + const char *q; + int len = 0; + + if (s == NULL) + return g_strdup(""); + + q = s; + while (*q){ + len++; + if (strchr ("\n\r\t\\", *q) != NULL) + len++; + q++; + } + return_value = p = (char *) g_malloc (len + 1); + do { + switch (*s){ + case '\t': + *p++ = '\\'; + *p++ = 't'; + break; + case '\n': + *p++ = '\\'; + *p++ = 'n'; + break; + case '\r': + *p++ = '\\'; + *p++ = 'r'; + break; + case '\\': + *p++ = '\\'; + *p++ = '\\'; + break; + default: + *p++ = *s; + } + } while (*s++); + return return_value; +} + +static gboolean +check_locale (const char *locale) +{ + GIConv cd = g_iconv_open ("UTF-8", locale); + if ((GIConv)-1 == cd) + return FALSE; + g_iconv_close (cd); + return TRUE; +} + +static void +insert_locales (GHashTable *encodings, char *enc, ...) +{ + va_list args; + char *s; + + va_start (args, enc); + for (;;) { + s = va_arg (args, char *); + if (s == NULL) + break; + g_hash_table_insert (encodings, s, enc); + } + va_end (args); +} + +/* make a standard conversion table from the desktop standard spec */ +static GHashTable * +init_encodings (void) +{ + GHashTable *encodings = g_hash_table_new (g_str_hash, g_str_equal); + + /* "C" is plain ascii */ + insert_locales (encodings, "ASCII", "C", NULL); + + insert_locales (encodings, "ARMSCII-8", "by", NULL); + insert_locales (encodings, "BIG5", "zh_TW", NULL); + insert_locales (encodings, "CP1251", "be", "bg", NULL); + if (check_locale ("EUC-CN")) { + insert_locales (encodings, "EUC-CN", "zh_CN", NULL); + } else { + insert_locales (encodings, "GB2312", "zh_CN", NULL); + } + insert_locales (encodings, "EUC-JP", "ja", NULL); + insert_locales (encodings, "EUC-KR", "ko", NULL); + /*insert_locales (encodings, "GEORGIAN-ACADEMY", NULL);*/ + insert_locales (encodings, "GEORGIAN-PS", "ka", NULL); + insert_locales (encodings, "ISO-8859-1", "br", "ca", "da", "de", "en", "es", "eu", "fi", "fr", "gl", "it", "nl", "wa", "no", "pt", "pt", "sv", NULL); + insert_locales (encodings, "ISO-8859-2", "cs", "hr", "hu", "pl", "ro", "sk", "sl", "sq", "sr", NULL); + insert_locales (encodings, "ISO-8859-3", "eo", NULL); + insert_locales (encodings, "ISO-8859-5", "mk", "sp", NULL); + insert_locales (encodings, "ISO-8859-7", "el", NULL); + insert_locales (encodings, "ISO-8859-9", "tr", NULL); + insert_locales (encodings, "ISO-8859-13", "lt", "lv", "mi", NULL); + insert_locales (encodings, "ISO-8859-14", "ga", "cy", NULL); + insert_locales (encodings, "ISO-8859-15", "et", NULL); + insert_locales (encodings, "KOI8-R", "ru", NULL); + insert_locales (encodings, "KOI8-U", "uk", NULL); + if (check_locale ("TCVN-5712")) { + insert_locales (encodings, "TCVN-5712", "vi", NULL); + } else { + insert_locales (encodings, "TCVN", "vi", NULL); + } + insert_locales (encodings, "TIS-620", "th", NULL); + /*insert_locales (encodings, "VISCII", NULL);*/ + + return encodings; +} + +static const char * +get_encoding_from_locale (const char *locale) +{ + char lang[3]; + const char *encoding; + static GHashTable *encodings = NULL; + + if (locale == NULL) + return NULL; + + /* if locale includes encoding, use it */ + encoding = strchr (locale, '.'); + if (encoding != NULL) { + return encoding+1; + } + + if (encodings == NULL) + encodings = init_encodings (); + + /* first try the entire locale (at this point ll_CC) */ + encoding = g_hash_table_lookup (encodings, locale); + if (encoding != NULL) + return encoding; + + /* Try just the language */ + strncpy (lang, locale, 2); + lang[2] = '\0'; + return g_hash_table_lookup (encodings, lang); +} + +static Encoding +get_encoding (ReadBuf *rb) +{ + gboolean old_kde = FALSE; + char buf [BUFSIZ]; + gboolean all_valid_utf8 = TRUE; + + while (readbuf_gets (buf, sizeof (buf), rb) != NULL) { + if (strncmp (MATE_DESKTOP_ITEM_ENCODING, + buf, + strlen (MATE_DESKTOP_ITEM_ENCODING)) == 0) { + char *p = &buf[strlen (MATE_DESKTOP_ITEM_ENCODING)]; + if (*p == ' ') + p++; + if (*p != '=') + continue; + p++; + if (*p == ' ') + p++; + if (strcmp (p, "UTF-8") == 0) { + return ENCODING_UTF8; + } else if (strcmp (p, "Legacy-Mixed") == 0) { + return ENCODING_LEGACY_MIXED; + } else { + /* According to the spec we're not supposed + * to read a file like this */ + return ENCODING_UNKNOWN; + } + } else if (strcmp ("[KDE Desktop Entry]", buf) == 0) { + old_kde = TRUE; + /* don't break yet, we still want to support + * Encoding even here */ + } + if (all_valid_utf8 && ! g_utf8_validate (buf, -1, NULL)) + all_valid_utf8 = FALSE; + } + + if (old_kde) + return ENCODING_LEGACY_MIXED; + + /* try to guess by location */ + if (rb->uri != NULL && strstr (rb->uri, "mate/apps/") != NULL) { + /* old mate */ + return ENCODING_LEGACY_MIXED; + } + + /* A dilemma, new KDE files are in UTF-8 but have no Encoding + * info, at this time we really can't tell. The best thing to + * do right now is to just assume UTF-8 if the whole file + * validates as utf8 I suppose */ + + if (all_valid_utf8) + return ENCODING_UTF8; + else + return ENCODING_LEGACY_MIXED; +} + +static char * +decode_string (const char *value, Encoding encoding, const char *locale) +{ + char *retval = NULL; + + /* if legacy mixed, then convert */ + if (locale != NULL && encoding == ENCODING_LEGACY_MIXED) { + const char *char_encoding = get_encoding_from_locale (locale); + char *utf8_string; + if (char_encoding == NULL) + return NULL; + if (strcmp (char_encoding, "ASCII") == 0) { + return decode_string_and_dup (value); + } + utf8_string = g_convert (value, -1, "UTF-8", char_encoding, + NULL, NULL, NULL); + if (utf8_string == NULL) + return NULL; + retval = decode_string_and_dup (utf8_string); + g_free (utf8_string); + return retval; + /* if utf8, then validate */ + } else if (locale != NULL && encoding == ENCODING_UTF8) { + if ( ! g_utf8_validate (value, -1, NULL)) + /* invalid utf8, ignore this key */ + return NULL; + return decode_string_and_dup (value); + } else { + /* Meaning this is not a localized string */ + return decode_string_and_dup (value); + } +} + +static char * +snarf_locale_from_key (const char *key) +{ + const char *brace; + char *locale, *p; + + brace = strchr (key, '['); + if (brace == NULL) + return NULL; + + locale = g_strdup (brace + 1); + if (*locale == '\0') { + g_free (locale); + return NULL; + } + p = strchr (locale, ']'); + if (p == NULL) { + g_free (locale); + return NULL; + } + *p = '\0'; + return locale; +} + +static void +insert_key (MateDesktopItem *item, + Section *cur_section, + Encoding encoding, + const char *key, + const char *value, + gboolean old_kde, + gboolean no_translations) +{ + char *k; + char *val; + /* we always store everything in UTF-8 */ + if (cur_section == NULL && + strcmp (key, MATE_DESKTOP_ITEM_ENCODING) == 0) { + k = g_strdup (key); + val = g_strdup ("UTF-8"); + } else { + char *locale = snarf_locale_from_key (key); + /* If we're ignoring translations */ + if (no_translations && locale != NULL) { + g_free (locale); + return; + } + val = decode_string (value, encoding, locale); + + /* Ignore this key, it's whacked */ + if (val == NULL) { + g_free (locale); + return; + } + + g_strchomp (val); + + /* For old KDE entries, we can also split by a comma + * on sort order, so convert to semicolons */ + if (old_kde && + cur_section == NULL && + strcmp (key, MATE_DESKTOP_ITEM_SORT_ORDER) == 0 && + strchr (val, ';') == NULL) { + int i; + for (i = 0; val[i] != '\0'; i++) { + if (val[i] == ',') + val[i] = ';'; + } + } + + /* Check some types, not perfect, but catches a lot + * of things */ + if (cur_section == NULL) { + char *cannon = cannonize (key, val); + if (cannon != NULL) { + g_free (val); + val = cannon; + } + } + + k = g_strdup (key); + + /* Take care of the language part */ + if (locale != NULL && + strcmp (locale, "C") == 0) { + char *p; + /* Whack C locale */ + p = strchr (k, '['); + *p = '\0'; + g_free (locale); + } else if (locale != NULL) { + char *p, *brace; + + /* Whack the encoding part */ + p = strchr (locale, '.'); + if (p != NULL) + *p = '\0'; + + if (g_list_find_custom (item->languages, locale, + (GCompareFunc)strcmp) == NULL) { + item->languages = g_list_prepend + (item->languages, locale); + } else { + g_free (locale); + } + + /* Whack encoding from encoding in the key */ + brace = strchr (k, '['); + p = strchr (brace, '.'); + if (p != NULL) { + *p = ']'; + *(p+1) = '\0'; + } + } + } + + + if (cur_section == NULL) { + /* only add to list if we haven't seen it before */ + if (g_hash_table_lookup (item->main_hash, k) == NULL) { + item->keys = g_list_prepend (item->keys, + g_strdup (k)); + } + /* later duplicates override earlier ones */ + g_hash_table_replace (item->main_hash, k, val); + } else { + char *full = g_strdup_printf + ("%s/%s", + cur_section->name, k); + /* only add to list if we haven't seen it before */ + if (g_hash_table_lookup (item->main_hash, full) == NULL) { + cur_section->keys = + g_list_prepend (cur_section->keys, k); + } + /* later duplicates override earlier ones */ + g_hash_table_replace (item->main_hash, + full, val); + } +} + +static void +setup_type (MateDesktopItem *item, const char *uri) +{ + const char *type = g_hash_table_lookup (item->main_hash, + MATE_DESKTOP_ITEM_TYPE); + if (type == NULL && uri != NULL) { + char *base = g_path_get_basename (uri); + if (base != NULL && + strcmp (base, ".directory") == 0) { + /* This gotta be a directory */ + g_hash_table_replace (item->main_hash, + g_strdup (MATE_DESKTOP_ITEM_TYPE), + g_strdup ("Directory")); + item->keys = g_list_prepend + (item->keys, g_strdup (MATE_DESKTOP_ITEM_TYPE)); + item->type = MATE_DESKTOP_ITEM_TYPE_DIRECTORY; + } else { + item->type = MATE_DESKTOP_ITEM_TYPE_NULL; + } + g_free (base); + } else { + item->type = type_from_string (type); + } +} + +/* fallback to find something suitable for C locale */ +static char * +try_english_key (MateDesktopItem *item, const char *key) +{ + char *str; + char *locales[] = { "en_US", "en_GB", "en_AU", "en", NULL }; + int i; + + str = NULL; + for (i = 0; locales[i] != NULL && str == NULL; i++) { + str = g_strdup (lookup_locale (item, key, locales[i])); + } + if (str != NULL) { + /* We need a 7-bit ascii string, so whack all + * above 127 chars */ + guchar *p; + for (p = (guchar *)str; *p != '\0'; p++) { + if (*p > 127) + *p = '?'; + } + } + return str; +} + + +static void +sanitize (MateDesktopItem *item, const char *uri) +{ + const char *type; + + type = lookup (item, MATE_DESKTOP_ITEM_TYPE); + + /* understand old mate style url exec thingies */ + if (type != NULL && strcmp (type, "URL") == 0) { + const char *exec = lookup (item, MATE_DESKTOP_ITEM_EXEC); + set (item, MATE_DESKTOP_ITEM_TYPE, "Link"); + if (exec != NULL) { + /* Note, this must be in this order */ + set (item, MATE_DESKTOP_ITEM_URL, exec); + set (item, MATE_DESKTOP_ITEM_EXEC, NULL); + } + } + + /* we make sure we have Name, Encoding and Version */ + if (lookup (item, MATE_DESKTOP_ITEM_NAME) == NULL) { + char *name = try_english_key (item, MATE_DESKTOP_ITEM_NAME); + /* If no name, use the basename */ + if (name == NULL && uri != NULL) + name = g_path_get_basename (uri); + /* If no uri either, use same default as mate_desktop_item_new */ + if (name == NULL) { + /* Translators: the "name" mentioned here is the name of + * an application or a document */ + name = g_strdup (_("No name")); + } + g_hash_table_replace (item->main_hash, + g_strdup (MATE_DESKTOP_ITEM_NAME), + name); + item->keys = g_list_prepend + (item->keys, g_strdup (MATE_DESKTOP_ITEM_NAME)); + } + if (lookup (item, MATE_DESKTOP_ITEM_ENCODING) == NULL) { + /* We store everything in UTF-8 so write that down */ + g_hash_table_replace (item->main_hash, + g_strdup (MATE_DESKTOP_ITEM_ENCODING), + g_strdup ("UTF-8")); + item->keys = g_list_prepend + (item->keys, g_strdup (MATE_DESKTOP_ITEM_ENCODING)); + } + if (lookup (item, MATE_DESKTOP_ITEM_VERSION) == NULL) { + /* this is the version that we follow, so write it down */ + g_hash_table_replace (item->main_hash, + g_strdup (MATE_DESKTOP_ITEM_VERSION), + g_strdup ("1.0")); + item->keys = g_list_prepend + (item->keys, g_strdup (MATE_DESKTOP_ITEM_VERSION)); + } +} + +enum { + FirstBrace, + OnSecHeader, + IgnoreToEOL, + IgnoreToEOLFirst, + KeyDef, + KeyDefOnKey, + KeyValue +}; + +static MateDesktopItem * +ditem_load (ReadBuf *rb, + gboolean no_translations, + GError **error) +{ + int state; + char CharBuffer [1024]; + char *next = CharBuffer; + int c; + Encoding encoding; + MateDesktopItem *item; + Section *cur_section = NULL; + char *key = NULL; + gboolean old_kde = FALSE; + + encoding = get_encoding (rb); + if (encoding == ENCODING_UNKNOWN) { + /* spec says, don't read this file */ + g_set_error (error, + MATE_DESKTOP_ITEM_ERROR, + MATE_DESKTOP_ITEM_ERROR_UNKNOWN_ENCODING, + _("Unknown encoding of: %s"), + rb->uri); + readbuf_close (rb); + return NULL; + } + + /* Rewind since get_encoding goes through the file */ + if (! readbuf_rewind (rb, error)) { + readbuf_close (rb); + /* spec says, don't read this file */ + return NULL; + } + + item = mate_desktop_item_new (); + item->modified = FALSE; + + /* Note: location and mtime are filled in by the new_from_file + * function since it has those values */ + +#define OVERFLOW (next == &CharBuffer [sizeof(CharBuffer)-1]) + + state = FirstBrace; + while ((c = readbuf_getc (rb)) != EOF) { + if (c == '\r') /* Ignore Carriage Return */ + continue; + + switch (state) { + + case OnSecHeader: + if (c == ']' || OVERFLOW) { + *next = '\0'; + next = CharBuffer; + + /* keys were inserted in reverse */ + if (cur_section != NULL && + cur_section->keys != NULL) { + cur_section->keys = g_list_reverse + (cur_section->keys); + } + if (strcmp (CharBuffer, + "KDE Desktop Entry") == 0) { + /* Main section */ + cur_section = NULL; + old_kde = TRUE; + } else if (strcmp (CharBuffer, + "Desktop Entry") == 0) { + /* Main section */ + cur_section = NULL; + } else { + cur_section = g_new0 (Section, 1); + cur_section->name = + g_strdup (CharBuffer); + cur_section->keys = NULL; + item->sections = g_list_prepend + (item->sections, cur_section); + } + state = IgnoreToEOL; + } else if (c == '[') { + /* FIXME: probably error out instead of ignoring this */ + } else { + *next++ = c; + } + break; + + case IgnoreToEOL: + case IgnoreToEOLFirst: + if (c == '\n'){ + if (state == IgnoreToEOLFirst) + state = FirstBrace; + else + state = KeyDef; + next = CharBuffer; + } + break; + + case FirstBrace: + case KeyDef: + case KeyDefOnKey: + if (c == '#') { + if (state == FirstBrace) + state = IgnoreToEOLFirst; + else + state = IgnoreToEOL; + break; + } + + if (c == '[' && state != KeyDefOnKey){ + state = OnSecHeader; + next = CharBuffer; + g_free (key); + key = NULL; + break; + } + /* On first pass, don't allow dangling keys */ + if (state == FirstBrace) + break; + + if ((c == ' ' && state != KeyDefOnKey) || c == '\t') + break; + + if (c == '\n' || OVERFLOW) { /* Abort Definition */ + next = CharBuffer; + state = KeyDef; + break; + } + + if (c == '=' || OVERFLOW){ + *next = '\0'; + + g_free (key); + key = g_strdup (CharBuffer); + state = KeyValue; + next = CharBuffer; + } else { + *next++ = c; + state = KeyDefOnKey; + } + break; + + case KeyValue: + if (OVERFLOW || c == '\n'){ + *next = '\0'; + + insert_key (item, cur_section, encoding, + key, CharBuffer, old_kde, + no_translations); + + g_free (key); + key = NULL; + + state = (c == '\n') ? KeyDef : IgnoreToEOL; + next = CharBuffer; + } else { + *next++ = c; + } + break; + + } /* switch */ + + } /* while ((c = getc_unlocked (f)) != EOF) */ + if (c == EOF && state == KeyValue) { + *next = '\0'; + + insert_key (item, cur_section, encoding, + key, CharBuffer, old_kde, + no_translations); + + g_free (key); + key = NULL; + } + +#undef OVERFLOW + + /* keys were inserted in reverse */ + if (cur_section != NULL && + cur_section->keys != NULL) { + cur_section->keys = g_list_reverse (cur_section->keys); + } + /* keys were inserted in reverse */ + item->keys = g_list_reverse (item->keys); + /* sections were inserted in reverse */ + item->sections = g_list_reverse (item->sections); + + /* sanitize some things */ + sanitize (item, rb->uri); + + /* make sure that we set up the type */ + setup_type (item, rb->uri); + + readbuf_close (rb); + + return item; +} + +static void stream_printf (GFileOutputStream *stream, + const char *format, ...) G_GNUC_PRINTF (2, 3); + +static void +stream_printf (GFileOutputStream *stream, const char *format, ...) +{ + va_list args; + gchar *s; + + va_start (args, format); + s = g_strdup_vprintf (format, args); + va_end (args); + + /* FIXME: what about errors */ + g_output_stream_write (G_OUTPUT_STREAM (stream), s, strlen (s), + NULL, NULL); + g_free (s); +} + +static void +dump_section (MateDesktopItem *item, GFileOutputStream *stream, Section *section) +{ + GList *li; + + stream_printf (stream, "[%s]\n", section->name); + for (li = section->keys; li != NULL; li = li->next) { + const char *key = li->data; + char *full = g_strdup_printf ("%s/%s", section->name, key); + const char *value = g_hash_table_lookup (item->main_hash, full); + if (value != NULL) { + char *val = escape_string_and_dup (value); + stream_printf (stream, "%s=%s\n", key, val); + g_free (val); + } + g_free (full); + } +} + +static gboolean +ditem_save (MateDesktopItem *item, const char *uri, GError **error) +{ + GList *li; + GFile *file; + GFileOutputStream *stream; + + file = g_file_new_for_uri (uri); + stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error); + if (stream == NULL) + return FALSE; + + stream_printf (stream, "[Desktop Entry]\n"); + for (li = item->keys; li != NULL; li = li->next) { + const char *key = li->data; + const char *value = g_hash_table_lookup (item->main_hash, key); + if (value != NULL) { + char *val = escape_string_and_dup (value); + stream_printf (stream, "%s=%s\n", key, val); + g_free (val); + } + } + + if (item->sections != NULL) + stream_printf (stream, "\n"); + + for (li = item->sections; li != NULL; li = li->next) { + Section *section = li->data; + + /* Don't write empty sections */ + if (section->keys == NULL) + continue; + + dump_section (item, stream, section); + + if (li->next != NULL) + stream_printf (stream, "\n"); + } + + g_object_unref (stream); + g_object_unref (file); + + return TRUE; +} + +static gpointer +_mate_desktop_item_copy (gpointer boxed) +{ + return mate_desktop_item_copy (boxed); +} + +static void +_mate_desktop_item_free (gpointer boxed) +{ + mate_desktop_item_unref (boxed); +} + +GType +mate_desktop_item_get_type (void) +{ + static GType type = 0; + + if (type == 0) { + type = g_boxed_type_register_static ("MateDesktopItem", + _mate_desktop_item_copy, + _mate_desktop_item_free); + } + + return type; +} + +GQuark +mate_desktop_item_error_quark (void) +{ + static GQuark q = 0; + if (q == 0) + q = g_quark_from_static_string ("mate-desktop-item-error-quark"); + + return q; +} |