[gnome-session] [capplet] Big rework of the code to clean it up, fix bugs, etc.



commit fe9f11b9bfefb3f12b048118a003b1c5ca6a9d38
Author: Vincent Untz <vuntz gnome org>
Date:   Sun Jun 21 17:09:21 2009 +0200

    [capplet] Big rework of the code to clean it up, fix bugs, etc.
    
    The core of this rework is to separate in a clean way the code that
    tracks the autostart files from the UI itself.
    
    Now it'd be possible to make an autostart file inherit the data from
    the autostart files with the same basename but in other directories.
    
    This work also fixes a few bugs:
     + monitor autostart files
       http://bugzilla.gnome.org/show_bug.cgi?id=437204
     + make name field optional and autofill it instead
       http://bugzilla.gnome.org/show_bug.cgi?id=502393

 capplet/Makefile.am             |    6 +
 capplet/gsm-app-dialog.c        |   96 +++-
 capplet/gsm-app-dialog.h        |    5 +
 capplet/gsm-properties-dialog.c | 1203 +++++++--------------------------------
 capplet/gsm-properties-dialog.h |    2 +
 capplet/gsp-app-manager.c       |  567 ++++++++++++++++++
 capplet/gsp-app-manager.h       |   81 +++
 capplet/gsp-app.c               | 1019 +++++++++++++++++++++++++++++++++
 capplet/gsp-app.h               |   96 ++++
 capplet/gsp-keyfile.c           |  149 +++++
 capplet/gsp-keyfile.h           |   63 ++
 11 files changed, 2281 insertions(+), 1006 deletions(-)
---
diff --git a/capplet/Makefile.am b/capplet/Makefile.am
index a45d0dc..cf1af9c 100644
--- a/capplet/Makefile.am
+++ b/capplet/Makefile.am
@@ -26,6 +26,12 @@ gnome_session_properties_SOURCES =			\
 	gsm-properties-dialog.c				\
 	gsm-app-dialog.h				\
 	gsm-app-dialog.c				\
+	gsp-app.h					\
+	gsp-app.c					\
+	gsp-app-manager.h				\
+	gsp-app-manager.c				\
+	gsp-keyfile.h					\
+	gsp-keyfile.c					\
 	$(NULL)
 
 -include $(top_srcdir)/git.mk
diff --git a/capplet/gsm-app-dialog.c b/capplet/gsm-app-dialog.c
index 64ab063..da5f709 100644
--- a/capplet/gsm-app-dialog.c
+++ b/capplet/gsm-app-dialog.c
@@ -20,17 +20,13 @@
 
 #include "config.h"
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-
 #include <glib.h>
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
 
 #include <glade/glade-xml.h>
-#include <gconf/gconf-client.h>
+
+#include "gsm-util.h"
 
 #include "gsm-app-dialog.h"
 
@@ -447,3 +443,91 @@ gsm_app_dialog_new (const char *name,
 
         return GTK_WIDGET (object);
 }
+
+gboolean
+gsm_app_dialog_run (GsmAppDialog  *dialog,
+                    char         **name_p,
+                    char         **command_p,
+                    char         **comment_p)
+{
+        gboolean retval;
+
+        retval = FALSE;
+
+        while (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
+                const char *name;
+                const char *exec;
+                const char *comment;
+                const char *error_msg;
+                GError     *error;
+                char      **argv;
+                int         argc;
+                gboolean    changed;
+
+                name = gsm_app_dialog_get_name (GSM_APP_DIALOG (dialog));
+                exec = gsm_app_dialog_get_command (GSM_APP_DIALOG (dialog));
+                comment = gsm_app_dialog_get_comment (GSM_APP_DIALOG (dialog));
+
+                error = NULL;
+                error_msg = NULL;
+
+                if (gsm_util_text_is_blank (exec)) {
+                        error_msg = _("The startup command cannot be empty");
+                } else {
+                        if (!g_shell_parse_argv (exec, &argc, &argv, &error)) {
+                                if (error != NULL) {
+                                        error_msg = error->message;
+                                } else {
+                                        error_msg = _("The startup command is not valid");
+                                }
+                        }
+                }
+
+                if (error_msg != NULL) {
+                        GtkWidget *msgbox;
+
+                        msgbox = gtk_message_dialog_new (GTK_WINDOW (dialog),
+                                                         GTK_DIALOG_MODAL,
+                                                         GTK_MESSAGE_ERROR,
+                                                         GTK_BUTTONS_CLOSE,
+                                                         "%s", error_msg);
+
+                        if (error != NULL) {
+                                g_error_free (error);
+                        }
+
+                        gtk_dialog_run (GTK_DIALOG (msgbox));
+
+                        gtk_widget_destroy (msgbox);
+
+                        continue;
+                }
+
+                changed = FALSE;
+
+                if (gsm_util_text_is_blank (name)) {
+                        name = argv[0];
+                }
+
+                if (name_p) {
+                        *name_p = g_strdup (name);
+                }
+
+                g_strfreev (argv);
+
+                if (command_p) {
+                        *command_p = g_strdup (exec);
+                }
+
+                if (comment_p) {
+                        *comment_p = g_strdup (comment);
+                }
+
+                retval = TRUE;
+                break;
+        }
+
+        gtk_widget_destroy (GTK_WIDGET (dialog));
+
+        return retval;
+}
diff --git a/capplet/gsm-app-dialog.h b/capplet/gsm-app-dialog.h
index 0093332..ced0628 100644
--- a/capplet/gsm-app-dialog.h
+++ b/capplet/gsm-app-dialog.h
@@ -52,6 +52,11 @@ GtkWidget            * gsm_app_dialog_new                (const char   *name,
                                                           const char   *command,
                                                           const char   *comment);
 
+gboolean               gsm_app_dialog_run               (GsmAppDialog  *dialog,
+                                                         char         **name_p,
+                                                         char         **command_p,
+                                                         char         **comment_p);
+
 const char *           gsm_app_dialog_get_name           (GsmAppDialog *dialog);
 const char *           gsm_app_dialog_get_command        (GsmAppDialog *dialog);
 const char *           gsm_app_dialog_get_comment        (GsmAppDialog *dialog);
diff --git a/capplet/gsm-properties-dialog.c b/capplet/gsm-properties-dialog.c
index c54eb60..c2606c0 100644
--- a/capplet/gsm-properties-dialog.c
+++ b/capplet/gsm-properties-dialog.c
@@ -23,15 +23,8 @@
 
 #include "config.h"
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-
 #include <glib.h>
 #include <glib/gi18n.h>
-#include <glib/gstdio.h>
-#include <gio/gio.h>
 #include <gtk/gtk.h>
 
 #include <glade/glade-xml.h>
@@ -41,6 +34,8 @@
 #include "gsm-app-dialog.h"
 #include "eggdesktopfile.h"
 #include "gsm-util.h"
+#include "gsp-app.h"
+#include "gsp-app-manager.h"
 
 #define GSM_PROPERTIES_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_PROPERTIES_DIALOG, GsmPropertiesDialogPrivate))
 
@@ -70,18 +65,26 @@ struct GsmPropertiesDialogPrivate
 {
         GladeXML          *xml;
         GtkListStore      *list_store;
+        GtkTreeModel      *tree_filter;
+
+        GtkTreeView       *treeview;
+        GtkWidget         *add_button;
+        GtkWidget         *delete_button;
+        GtkWidget         *edit_button;
+
+        GtkWidget         *remember_toggle;
+
+        GspAppManager     *manager;
 };
 
 enum {
-        STORE_COL_ENABLED = 0,
+        STORE_COL_VISIBLE = 0,
+        STORE_COL_ENABLED,
         STORE_COL_ICON_NAME,
+        STORE_COL_GICON,
+        STORE_COL_PIXBUF,
         STORE_COL_DESCRIPTION,
-        STORE_COL_NAME,
-        STORE_COL_COMMAND,
-        STORE_COL_COMMENT,
-        STORE_COL_DESKTOP_FILE,
-        STORE_COL_ID,
-        STORE_COL_ACTIVATABLE,
+        STORE_COL_APP,
         NUMBER_OF_COLUMNS
 };
 
@@ -91,594 +94,157 @@ static void     gsm_properties_dialog_finalize    (GObject                  *obj
 
 G_DEFINE_TYPE (GsmPropertiesDialog, gsm_properties_dialog, GTK_TYPE_DIALOG)
 
-static void
-on_response (GsmPropertiesDialog *dialog,
-             gint                 response_id)
-
-{
-}
-
 static gboolean
-find_by_id (GtkListStore *store,
-            const char   *id)
+find_by_app (GtkTreeModel *model,
+             GtkTreeIter  *iter,
+             GspApp       *app)
 {
-        GtkTreeIter iter;
-        char       *iter_id = NULL;
+        GspApp *iter_app = NULL;
 
-        if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) {
+        if (!gtk_tree_model_get_iter_first (model, iter)) {
                 return FALSE;
         }
 
         do {
-                gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
-                                    STORE_COL_ID, &iter_id,
+                gtk_tree_model_get (model, iter,
+                                    STORE_COL_APP, &iter_app,
                                     -1);
 
-                if (!strcmp (iter_id, id)) {
+                if (iter_app == app) {
+                        g_object_unref (iter_app);
                         return TRUE;
                 }
-        } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
+        } while (gtk_tree_model_iter_next (model, iter));
 
         return FALSE;
 }
 
-static char *
-get_app_description (const char *name,
-                     const char *comment)
-{
-        return g_markup_printf_escaped ("<b>%s</b>\n%s", name,
-                                        (!gsm_util_text_is_blank (comment) ?
-                                         comment : _("No description")));
-}
-
-static gboolean
-append_app (GsmPropertiesDialog *dialog,
-            EggDesktopFile      *desktop_file)
-{
-        GtkIconTheme *theme;
-        GtkTreeIter   iter;
-        GFile        *source;
-        char         *basename;
-        char         *description;
-        char         *name;
-        char         *comment;
-        char         *command;
-        char         *icon_name;
-        gboolean      enabled = TRUE;
-
-        source = g_file_new_for_uri (egg_desktop_file_get_source (desktop_file));
-
-        basename = g_file_get_basename (source);
-
-        if (egg_desktop_file_has_key (desktop_file,
-                                      G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL)) {
-                if (egg_desktop_file_get_boolean (desktop_file,
-                                                  G_KEY_FILE_DESKTOP_KEY_HIDDEN,
-                                                  NULL))
-                        return FALSE;
-        }
-
-        /* Check for duplicate apps */
-        if (find_by_id (dialog->priv->list_store, basename)) {
-                return TRUE;
-        }
-
-        name = egg_desktop_file_get_locale_string (desktop_file,
-                                                   G_KEY_FILE_DESKTOP_KEY_NAME,
-                                                   NULL, NULL);
-
-        comment = NULL;
-
-        if (egg_desktop_file_has_key (desktop_file,
-                                      "Comment", NULL)) {
-                comment =
-                        egg_desktop_file_get_locale_string (desktop_file,
-                                                            G_KEY_FILE_DESKTOP_KEY_COMMENT,
-                                                            NULL, NULL);
-        }
-
-        description = get_app_description (name, comment);
-
-        command = egg_desktop_file_get_string (desktop_file,
-                                               G_KEY_FILE_DESKTOP_KEY_EXEC,
-                                               NULL);
-
-        icon_name = NULL;
-
-        if (egg_desktop_file_has_key (desktop_file,
-                                      G_KEY_FILE_DESKTOP_KEY_ICON, NULL)) {
-                icon_name =
-                        egg_desktop_file_get_string (desktop_file,
-                                                     G_KEY_FILE_DESKTOP_KEY_ICON,
-                                                     NULL);
-        }
-
-        theme = gtk_icon_theme_get_default ();
-
-        if (icon_name == NULL || *icon_name == '\0' ||
-            !gtk_icon_theme_has_icon (theme, icon_name)) {
-                if (icon_name) {
-                        g_free (icon_name);
+static void
+_fill_iter_from_app (GtkListStore *list_store,
+                     GtkTreeIter  *iter,
+                     GspApp       *app)
+{
+        gboolean      hidden;
+        gboolean      enabled;
+        GIcon        *icon;
+        const char   *icon_name;
+        GdkPixbuf    *pixbuf;
+        const char   *description;
+
+        hidden      = gsp_app_get_hidden (app);
+        enabled     = gsp_app_get_enabled (app);
+        icon_name   = gsp_app_get_icon_name (app);
+        pixbuf      = gsp_app_get_pixbuf (app);
+        description = gsp_app_get_description (app);
+
+        icon = NULL;
+        if (!pixbuf) {
+#if 1
+                GtkIconTheme *theme;
+                theme = gtk_icon_theme_get_default ();
+                if (icon_name == NULL ||
+                    !gtk_icon_theme_has_icon (theme, icon_name)) {
+                        icon_name = STARTUP_APP_ICON;
                 }
-
-                icon_name = g_strdup (STARTUP_APP_ICON);
-        }
-
-        if (egg_desktop_file_has_key (desktop_file,
-                                      "X-GNOME-Autostart-enabled", NULL)) {
-                enabled = egg_desktop_file_get_boolean (desktop_file,
-                                                        "X-GNOME-Autostart-enabled",
-                                                        NULL);
+#else
+                /* the issue with this approach is that icons that live in
+                 * hicolor are ignored and STARTUP_APP_ICON is nearly always
+                 * used if it's in the main theme */
+                icon = g_themed_icon_new (STARTUP_APP_ICON);
+                if (icon_name != NULL) {
+                        g_themed_icon_prepend_name (G_THEMED_ICON (icon),
+                                                    icon_name);
+                }
+#endif
         }
 
-        gtk_list_store_append (dialog->priv->list_store, &iter);
-
-        gtk_list_store_set (dialog->priv->list_store,
-                            &iter,
+        gtk_list_store_set (list_store, iter,
+                            STORE_COL_VISIBLE, !hidden,
                             STORE_COL_ENABLED, enabled,
                             STORE_COL_ICON_NAME, icon_name,
+                            STORE_COL_GICON, icon,
+                            STORE_COL_PIXBUF, pixbuf,
                             STORE_COL_DESCRIPTION, description,
-                            STORE_COL_NAME, name,
-                            STORE_COL_COMMAND, command,
-                            STORE_COL_COMMENT, comment,
-                            STORE_COL_DESKTOP_FILE, desktop_file,
-                            STORE_COL_ID, basename,
-                            STORE_COL_ACTIVATABLE, TRUE,
+                            STORE_COL_APP, app,
                             -1);
-
-        g_object_unref (source);
-        g_free (basename);
-        g_free (name);
-        g_free (comment);
-        g_free (description);
-        g_free (icon_name);
-
-        return TRUE;
-}
-
-static int
-compare_app (gconstpointer a,
-             gconstpointer b)
-{
-        if (!strcmp (a, b)) {
-                return 0;
-        }
-
-        return 1;
 }
 
 static void
-append_autostart_apps (GsmPropertiesDialog *dialog,
-                       const char          *path,
-                       GList              **removed_apps)
+_app_changed (GsmPropertiesDialog *dialog,
+              GspApp              *app)
 {
-        GDir       *dir;
-        const char *name;
+        GtkTreeIter iter;
 
-        dir = g_dir_open (path, 0, NULL);
-        if (!dir) {
+        if (!find_by_app (GTK_TREE_MODEL (dialog->priv->list_store),
+                          &iter, app)) {
                 return;
         }
 
-        while ((name = g_dir_read_name (dir))) {
-                EggDesktopFile *desktop_file;
-                GError         *error;
-                char           *desktop_file_path;
-
-                if (!g_str_has_suffix (name, ".desktop")) {
-                        continue;
-                }
-
-                if (removed_apps &&
-                    g_list_find_custom (*removed_apps, name, compare_app)) {
-                        continue;
-                }
-
-                desktop_file_path = g_build_filename (path, name, NULL);
-
-                error = NULL;
-                desktop_file = egg_desktop_file_new (desktop_file_path, &error);
-                if (error == NULL) {
-                        if (!append_app (dialog, desktop_file)) {
-                                if (removed_apps) {
-                                        *removed_apps = g_list_prepend (*removed_apps, g_strdup (name));
-                                }
-                        }
-                } else {
-                        g_warning ("could not read %s: %s\n", desktop_file_path, error->message);
-
-                        g_error_free (error);
-                        error = NULL;
-                }
-
-                g_free (desktop_file_path);
-        }
-
-        g_dir_close (dir);
-}
-
-static void
-populate_model (GsmPropertiesDialog *dialog)
-{
-        GList        *removed_apps;
-        char        **autostart_dirs;
-        int           i;
-
-        autostart_dirs = gsm_util_get_autostart_dirs ();
-
-        removed_apps = NULL;
-        for (i = 0; autostart_dirs[i]; i++) {
-                append_autostart_apps (dialog, autostart_dirs[i], &removed_apps);
-        }
-
-        g_strfreev (autostart_dirs);
-        g_list_foreach (removed_apps, (GFunc) g_free, NULL);
-        g_list_free (removed_apps);
-}
-
-static void
-on_selection_changed (GtkTreeSelection    *selection,
-                      GsmPropertiesDialog *dialog)
-{
-        GtkWidget *edit_button;
-        GtkWidget *delete_button;
-        gboolean   sel;
-
-        edit_button = glade_xml_get_widget (dialog->priv->xml, CAPPLET_EDIT_WIDGET_NAME);
-        delete_button = glade_xml_get_widget (dialog->priv->xml, CAPPLET_DELETE_WIDGET_NAME);
-
-        sel = gtk_tree_selection_get_selected (selection, NULL, NULL);
-
-        if (edit_button) {
-                gtk_widget_set_sensitive (edit_button, sel);
-        }
-
-        if (delete_button) {
-                gtk_widget_set_sensitive (delete_button, sel);
-        }
-}
-
-
-static gboolean
-system_desktop_entry_exists (const char  *basename,
-                             char       **system_path)
-{
-        char *path;
-        char **autostart_dirs;
-        int i;
-
-        autostart_dirs = gsm_util_get_autostart_dirs ();
-
-        for (i = 0; autostart_dirs[i]; i++) {
-                path = g_build_filename (autostart_dirs[i], basename, NULL);
-
-                if (g_str_has_prefix (autostart_dirs[i], g_get_user_config_dir ())) {
-                        continue;
-                }
-
-                if (g_file_test (path, G_FILE_TEST_EXISTS)) {
-                        if (system_path) {
-                                *system_path = path;
-                        } else {
-                                g_free (path);
-                        }
-                        g_strfreev (autostart_dirs);
-
-                        return TRUE;
-                }
-
-                g_free (path);
-        }
-
-        g_strfreev (autostart_dirs);
-
-        return FALSE;
+        _fill_iter_from_app (dialog->priv->list_store, &iter, app);
 }
 
 static void
-update_desktop_file (GtkListStore   *store,
-                     GtkTreeIter    *iter,
-                     EggDesktopFile *new_desktop_file)
+append_app (GsmPropertiesDialog *dialog,
+            GspApp              *app)
 {
-        EggDesktopFile *old_desktop_file;
-
-        gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
-                            STORE_COL_DESKTOP_FILE, &old_desktop_file,
-                            -1);
+        GtkTreeIter   iter;
 
-        egg_desktop_file_free (old_desktop_file);
+        gtk_list_store_append (dialog->priv->list_store, &iter);
+        _fill_iter_from_app (dialog->priv->list_store, &iter, app);
 
-        gtk_list_store_set (store, iter,
-                            STORE_COL_DESKTOP_FILE, new_desktop_file,
-                            -1);
+        g_signal_connect_swapped (app, "changed",
+                                  G_CALLBACK (_app_changed), dialog);
 }
 
 static void
-ensure_user_autostart_dir ()
+_app_added (GsmPropertiesDialog *dialog,
+            GspApp              *app,
+            GspAppManager       *manager)
 {
-        char *dir;
-
-        dir = g_build_filename (g_get_user_config_dir (), "autostart", NULL);
-        g_mkdir_with_parents (dir, S_IRWXU);
-
-        g_free (dir);
+        append_app (dialog, app);
 }
 
 static void
-key_file_set_locale_string (GKeyFile   *keyfile,
-                            const char *group,
-                            const char *key,
-                            const char *value)
-{
-        const char         *locale;
-        const char * const *langs_pointer;
-        int                 i;
-
-        g_assert (key != NULL);
-
-        if (value == NULL) {
-                value = "";
-        }
-
-        locale = NULL;
-        langs_pointer = g_get_language_names ();
-
-        for (i = 0; langs_pointer[i] != NULL; i++) {
-                /* Find first without encoding  */
-                if (strchr (langs_pointer[i], '.') == NULL) {
-                        locale = langs_pointer[i];
-                        break;
-                }
-        }
-
-        if (locale != NULL) {
-                g_key_file_set_locale_string (keyfile,
-                                              group,
-                                              key,
-                                              locale,
-                                              value);
-        } else {
-                g_key_file_set_string (keyfile,
-                                       G_KEY_FILE_DESKTOP_GROUP,
-                                       key, value);
-        }
-}
-
-static gboolean
-key_file_to_file (GKeyFile   *keyfile,
-                  const char *file,
-                  GError    **error)
+_app_removed (GsmPropertiesDialog *dialog,
+              GspApp              *app,
+              GspAppManager       *manager)
 {
-        GError  *write_error;
-        char    *filename;
-        char    *data;
-        gsize    length;
-        gboolean res;
-
-        g_return_val_if_fail (keyfile != NULL, FALSE);
-        g_return_val_if_fail (file != NULL, FALSE);
-
-        write_error = NULL;
-
-        data = g_key_file_to_data (keyfile, &length, &write_error);
-
-        if (write_error) {
-                g_propagate_error (error, write_error);
-                return FALSE;
-        }
-
-        if (!g_path_is_absolute (file)) {
-                filename = g_filename_from_uri (file, NULL, &write_error);
-        } else {
-                filename = g_filename_from_utf8 (file, -1, NULL, NULL, &write_error);
-        }
-
-        if (write_error) {
-                g_propagate_error (error, write_error);
-                g_free (data);
-                return FALSE;
-        }
-
-        res = g_file_set_contents (filename, data, length, &write_error);
-
-        g_free (filename);
+        GtkTreeIter iter;
 
-        if (write_error) {
-                g_propagate_error (error, write_error);
-                g_free (data);
-                return FALSE;
+        if (!find_by_app (GTK_TREE_MODEL (dialog->priv->list_store),
+                          &iter, app)) {
+                return;
         }
 
-        g_free (data);
-
-        return res;
+        g_signal_handlers_disconnect_by_func (app,
+                                              _app_changed,
+                                              dialog);
+        gtk_list_store_remove (dialog->priv->list_store, &iter);
 }
 
 static void
-write_desktop_file (EggDesktopFile *desktop_file,
-                    GtkListStore   *store,
-                    GtkTreeIter    *iter,
-                    gboolean        enabled)
+populate_model (GsmPropertiesDialog *dialog)
 {
-        GKeyFile *keyfile;
-        GFile    *source;
-        GError   *error;
-        char     *path;
-        char     *name;
-        char     *command;
-        char     *comment;
-        gboolean  path_changed = FALSE;
-
-        ensure_user_autostart_dir ();
-
-        keyfile = g_key_file_new ();
-
-        source = g_file_new_for_uri (egg_desktop_file_get_source (desktop_file));
-
-        path = g_file_get_path (source);
-
-        error = NULL;
-        g_key_file_load_from_file (keyfile,
-                                   path,
-                                   G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
-                                   &error);
-        if (error != NULL) {
-                goto out;
-        }
-
-        if (!g_str_has_prefix (path, g_get_user_config_dir ())) {
-                /* It's a system-wide file, save it to the user's home */
-                char *basename;
-
-                basename = g_file_get_basename (source);
-
-                g_free (path);
-
-                path = g_build_filename (g_get_user_config_dir (),
-                                         "autostart", basename, NULL);
-
-                g_free (basename);
-
-                path_changed = TRUE;
-        }
-
-        gtk_tree_model_get (GTK_TREE_MODEL (store),
-                            iter,
-                            STORE_COL_NAME, &name,
-                            STORE_COL_COMMAND, &command,
-                            STORE_COL_COMMENT, &comment,
-                            -1);
-
-        key_file_set_locale_string (keyfile,
-                                    G_KEY_FILE_DESKTOP_GROUP,
-                                    G_KEY_FILE_DESKTOP_KEY_NAME,
-                                    name);
-
-        key_file_set_locale_string (keyfile,
-                                    G_KEY_FILE_DESKTOP_GROUP,
-                                    G_KEY_FILE_DESKTOP_KEY_COMMENT,
-                                    comment);
-
-        g_key_file_set_string (keyfile,
-                               G_KEY_FILE_DESKTOP_GROUP,
-                               G_KEY_FILE_DESKTOP_KEY_EXEC,
-                               command);
-
-        g_key_file_set_boolean (keyfile,
-                                G_KEY_FILE_DESKTOP_GROUP,
-                                "X-GNOME-Autostart-enabled",
-                                enabled);
-
-        if (!key_file_to_file (keyfile, path, &error)) {
-                goto out;
-        }
-
-        if (path_changed) {
-                EggDesktopFile *new_desktop_file;
-
-                new_desktop_file = egg_desktop_file_new (path, &error);
-
-                if (error) {
-                        goto out;
-                }
+        GSList *apps;
+        GSList *l;
 
-                update_desktop_file (store, iter, new_desktop_file);
+        apps = gsp_app_manager_get_apps (dialog->priv->manager);
+        for (l = apps; l != NULL; l = l->next) {
+                append_app (dialog, GSP_APP (l->data));
         }
-
- out:
-        if (error != NULL) {
-                g_warning ("Error when writing desktop file %s: %s",
-                           path, error->message);
-
-                g_error_free (error);
-        }
-
-        g_free (path);
-        g_free (name);
-        g_free (comment);
-        g_free (command);
-        g_object_unref (source);
-        g_key_file_free (keyfile);
+        g_slist_free (apps);
 }
 
-static gboolean
-toggle_app (GsmPropertiesDialog *dialog,
-            GtkTreeIter         *iter,
-            gboolean             enabled)
+static void
+on_selection_changed (GtkTreeSelection    *selection,
+                      GsmPropertiesDialog *dialog)
 {
-        EggDesktopFile *desktop_file;
-        GFile          *source;
-        char           *system_path;
-        char           *basename;
-        char           *path;
-        char           *name;
-        char           *comment;
-
-        gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->list_store),
-                            iter,
-                            STORE_COL_NAME, &name,
-                            STORE_COL_COMMENT, &comment,
-                            STORE_COL_DESKTOP_FILE, &desktop_file,
-                            -1);
-
-        source = g_file_new_for_uri (egg_desktop_file_get_source (desktop_file));
-
-        path = g_file_get_path (source);
-
-        basename = g_file_get_basename (source);
-
-        if (system_desktop_entry_exists (basename, &system_path)) {
-                EggDesktopFile *system_desktop_file;
-                char           *original_name;
-                char           *original_comment;
-                gboolean        original_enabled;
-
-                system_desktop_file = egg_desktop_file_new (system_path, NULL);
-
-                original_name = egg_desktop_file_get_locale_string (system_desktop_file,
-                                                                    "Name", NULL, NULL);
-
-                original_comment = egg_desktop_file_get_locale_string (system_desktop_file,
-                                                                       "Comment", NULL, NULL);
-
-                if (egg_desktop_file_has_key (system_desktop_file,
-                                              "X-GNOME-Autostart-enabled", NULL)) {
-                        original_enabled = egg_desktop_file_get_boolean (system_desktop_file,
-                                                                         "X-GNOME-Autostart-enabled", NULL);
-                } else {
-                        original_enabled = TRUE;
-                }
-
-                if (REALLY_IDENTICAL_STRING (name, original_name) &&
-                    REALLY_IDENTICAL_STRING (comment, original_comment) &&
-                    (enabled == original_enabled)) {
-                        char *user_file =
-                                g_build_filename (g_get_user_config_dir (),
-                                                  "autostart", basename, NULL);
-
-                        if (g_file_test (user_file, G_FILE_TEST_EXISTS)) {
-                                g_remove (user_file);
-                        }
+        gboolean sel;
 
-                        g_free (user_file);
-
-                        update_desktop_file (dialog->priv->list_store, iter, system_desktop_file);
-                } else {
-                        write_desktop_file (desktop_file, dialog->priv->list_store, iter, enabled);
-                        egg_desktop_file_free (system_desktop_file);
-                }
-
-                g_free (original_name);
-                g_free (original_comment);
-        } else {
-                write_desktop_file (desktop_file, dialog->priv->list_store, iter, enabled);
-        }
-
-        g_free (name);
-        g_free (comment);
-        g_free (basename);
+        sel = gtk_tree_selection_get_selected (selection, NULL, NULL);
 
-        return TRUE;
+        gtk_widget_set_sensitive (dialog->priv->edit_button, sel);
+        gtk_widget_set_sensitive (dialog->priv->delete_button, sel);
 }
 
 static void
@@ -687,216 +253,27 @@ on_startup_enabled_toggled (GtkCellRendererToggle *cell_renderer,
                             GsmPropertiesDialog   *dialog)
 {
         GtkTreeIter iter;
-        char       *desktop_file_path;
+        GspApp     *app;
         gboolean    active;
 
-        if (!gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (dialog->priv->list_store),
+        if (!gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (dialog->priv->tree_filter),
                                                   &iter, path)) {
                 return;
         }
 
-        gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->list_store),
+        app = NULL;
+        gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->tree_filter),
                             &iter,
-                            STORE_COL_DESKTOP_FILE, &desktop_file_path,
+                            STORE_COL_APP, &app,
                             -1);
 
         active = gtk_cell_renderer_toggle_get_active (cell_renderer);
         active = !active;
-        gtk_cell_renderer_toggle_set_active (cell_renderer, active);
-
-        if (toggle_app (dialog, &iter, active)) {
-                gtk_list_store_set (dialog->priv->list_store,
-                                    &iter,
-                                    STORE_COL_ENABLED, active,
-                                    -1);
-        }
-}
-
-static void
-add_app (GtkListStore *store,
-         GtkTreeIter  *iter)
-{
-        EggDesktopFile *desktop_file;
-        GKeyFile       *keyfile;
-        char          **argv;
-        char           *basename;
-        char           *orig_filename;
-        char           *filename;
-        char           *name;
-        char           *command;
-        char           *comment;
-        char           *description;
-        char           *icon;
-        int             argc;
-        int             i = 2;
-
-        gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
-                            STORE_COL_NAME, &name,
-                            STORE_COL_COMMAND, &command,
-                            STORE_COL_COMMENT, &comment,
-                            STORE_COL_ICON_NAME, &icon,
-                            -1);
-
-        g_assert (command != NULL);
-        g_shell_parse_argv (command, &argc, &argv, NULL);
-
-        basename = g_path_get_basename (argv[0]);
-
-        orig_filename = g_build_filename (g_get_user_config_dir (),
-                                          "autostart", basename, NULL);
-
-        filename = g_strdup_printf ("%s.desktop", orig_filename);
 
-        while (g_file_test (filename, G_FILE_TEST_EXISTS)) {
-                char *tmp = g_strdup_printf ("%s-%d.desktop", orig_filename, i);
-
-                g_free (filename);
-                filename = tmp;
-
-                i++;
-        }
-
-        g_free (orig_filename);
-
-        ensure_user_autostart_dir ();
-
-        keyfile = g_key_file_new ();
-
-        g_key_file_set_string (keyfile,
-                               G_KEY_FILE_DESKTOP_GROUP,
-                               G_KEY_FILE_DESKTOP_KEY_TYPE,
-                               "Application");
-
-        g_key_file_set_string (keyfile,
-                               G_KEY_FILE_DESKTOP_GROUP,
-                               G_KEY_FILE_DESKTOP_KEY_NAME,
-                               name);
-
-        g_key_file_set_string (keyfile,
-                               G_KEY_FILE_DESKTOP_GROUP,
-                               G_KEY_FILE_DESKTOP_KEY_EXEC,
-                               command);
-
-        if (icon == NULL) {
-                icon = g_strdup (STARTUP_APP_ICON);
+        if (app) {
+                gsp_app_set_enabled (app, active);
+                g_object_unref (app);
         }
-
-        g_key_file_set_string (keyfile,
-                               G_KEY_FILE_DESKTOP_GROUP,
-                               G_KEY_FILE_DESKTOP_KEY_ICON,
-                               icon);
-
-        if (comment) {
-                g_key_file_set_string (keyfile,
-                                       G_KEY_FILE_DESKTOP_GROUP,
-                                       G_KEY_FILE_DESKTOP_KEY_COMMENT,
-                                       comment);
-        }
-
-        description = get_app_description (name, comment);
-
-        if (!key_file_to_file (keyfile, filename, NULL)) {
-                g_warning ("Could not save %s file", filename);
-        }
-
-        desktop_file = egg_desktop_file_new_from_key_file (keyfile,
-                                                           filename,
-                                                           NULL);
-
-        g_free (basename);
-
-        basename = g_path_get_basename (filename);
-
-        gtk_list_store_set (store, iter,
-                            STORE_COL_ENABLED, TRUE,
-                            STORE_COL_ICON_NAME, icon,
-                            STORE_COL_DESKTOP_FILE, desktop_file,
-                            STORE_COL_ID, basename,
-                            STORE_COL_ACTIVATABLE, TRUE,
-                            -1);
-
-        g_key_file_free (keyfile);
-        g_strfreev (argv);
-        g_free (name);
-        g_free (command);
-        g_free (comment);
-        g_free (description);
-        g_free (basename);
-        g_free (icon);
-}
-
-static gboolean
-add_from_desktop_file (GtkTreeView *treeview,
-                       char        *filename)
-{
-        EggDesktopFile *desktop_file;
-        gboolean        success = FALSE;
-
-        /* Assume that the file is local */
-        GFile *file = g_file_new_for_uri (filename);
-        gchar *path = g_file_get_path (file);
-
-        if (path != NULL) {
-                desktop_file = egg_desktop_file_new (path, NULL);
-
-                if (desktop_file != NULL) {
-                        GtkTreeIter   iter;
-                        GtkTreeModel *model;
-                        const char   *name;
-                        char         *comment;
-                        char         *description;
-                        char         *command;
-                        char         *icon;
-
-                        model = gtk_tree_view_get_model (treeview);
-
-                        gtk_list_store_append (GTK_LIST_STORE (model), &iter);
-
-                        name = egg_desktop_file_get_name (desktop_file);
-
-                        comment = egg_desktop_file_get_locale_string (desktop_file,
-                                                                      EGG_DESKTOP_FILE_KEY_COMMENT,
-                                                                      NULL, NULL);
-                        if (comment == NULL) {
-                                comment = egg_desktop_file_get_string (desktop_file,
-                                                                       EGG_DESKTOP_FILE_KEY_COMMENT,
-                                                                       NULL);
-                        }
-
-                        description = get_app_description (name, comment);
-
-                        command = egg_desktop_file_get_string (desktop_file,
-                                                               EGG_DESKTOP_FILE_KEY_EXEC,
-                                                               NULL);
-
-                        icon = egg_desktop_file_get_string (desktop_file,
-                                                            EGG_DESKTOP_FILE_KEY_ICON,
-                                                            NULL);
-
-                        if (name && comment && description && command) {
-                                gtk_list_store_set (GTK_LIST_STORE (model), &iter,
-                                                    STORE_COL_DESCRIPTION, description,
-                                                    STORE_COL_NAME, name,
-                                                    STORE_COL_COMMAND, command,
-                                                    STORE_COL_COMMENT, comment,
-                                                    STORE_COL_ICON_NAME, icon,
-                                                    STORE_COL_DESKTOP_FILE, desktop_file,
-                                                    -1);
-
-                                add_app (GTK_LIST_STORE (model), &iter);
-                                success = TRUE;
-                        }
-
-                        g_free (comment);
-                        g_free (description);
-                        g_free (command);
-                        g_free (icon);
-                        egg_desktop_file_free (desktop_file);
-                }
-        }
-
-        g_free (path);
-        return success;
 }
 
 static gboolean
@@ -921,7 +298,7 @@ on_drag_data (GtkWidget           *widget,
                 for (i = 0; filenames[i] && filenames[i][0]; i++) {
                         /* Return success if at least one file succeeded */
                         gboolean file_success;
-                        file_success = add_from_desktop_file (GTK_TREE_VIEW (widget), filenames[i]);
+                        file_success = gsp_app_copy_desktop_file (filenames[i]);
                         dnd_success = dnd_success || file_success;
                 }
 
@@ -932,266 +309,77 @@ on_drag_data (GtkWidget           *widget,
         return TRUE;
 }
 
-static gboolean
-edit_app_dialog (GsmPropertiesDialog *dialog,
-                 GtkTreeIter         *iter)
-{
-        GtkWidget *dlg;
-        char      *c_name;
-        char      *c_command;
-        char      *c_comment;
-
-        gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->list_store),
-                            iter,
-                            STORE_COL_NAME, &c_name,
-                            STORE_COL_COMMAND, &c_command,
-                            STORE_COL_COMMENT, &c_comment,
-                            -1);
-
-        dlg = gsm_app_dialog_new (c_name, c_command, c_comment);
-        gtk_window_set_transient_for (GTK_WINDOW (dlg), GTK_WINDOW (dialog));
-        g_free (c_name);
-        g_free (c_command);
-        g_free (c_comment);
-
-        while (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_OK) {
-                const char *name;
-                const char *command;
-                const char *comment;
-                const char *error_msg;
-                char      **argv;
-                char       *description;
-                int         argc;
-                GError     *error;
-
-                name = gsm_app_dialog_get_name (GSM_APP_DIALOG (dlg));
-                command = gsm_app_dialog_get_command (GSM_APP_DIALOG (dlg));
-                comment = gsm_app_dialog_get_comment (GSM_APP_DIALOG (dlg));
-                error = NULL;
-
-                error_msg = NULL;
-                if (gsm_util_text_is_blank (name)) {
-                        error_msg = _("The name of the startup program cannot be empty");
-                }
-
-                if (gsm_util_text_is_blank (command)) {
-                        error_msg = _("The startup command cannot be empty");
-                } else {
-                        if (!g_shell_parse_argv (command, &argc, &argv, &error)) {
-                                if (error != NULL) {
-                                        error_msg = error->message;
-                                } else {
-                                        error_msg = _("The startup command is not valid");
-                                }
-                        }
-                }
-
-                if (error_msg != NULL) {
-                        GtkWidget *msgbox;
-
-                        gtk_widget_show (dlg);
-
-                        msgbox = gtk_message_dialog_new (GTK_WINDOW (dlg),
-                                                         GTK_DIALOG_MODAL,
-                                                         GTK_MESSAGE_ERROR,
-                                                         GTK_BUTTONS_CLOSE,
-                                                         "%s", error_msg);
-
-                        if (error != NULL) {
-                                g_error_free (error);
-                        }
-
-                        gtk_dialog_run (GTK_DIALOG (msgbox));
-
-                        gtk_widget_destroy (msgbox);
-                } else {
-                        g_strfreev (argv);
-
-                        description = get_app_description (name, comment);
-
-                        gtk_list_store_set (GTK_LIST_STORE (dialog->priv->list_store), iter,
-                                            STORE_COL_DESCRIPTION, description,
-                                            STORE_COL_NAME, name,
-                                            STORE_COL_COMMAND, command,
-                                            STORE_COL_COMMENT, comment,
-                                            -1);
-
-                        g_free (description);
-
-                        gtk_widget_destroy (dlg);
-
-                        return TRUE;
-                }
-        }
-
-        gtk_widget_destroy (dlg);
-
-        return FALSE;
-}
-
-
 static void
 on_add_app_clicked (GtkWidget           *widget,
                     GsmPropertiesDialog *dialog)
 {
-        GtkTreeView      *view;
-        GtkTreeSelection *selection;
-        GtkTreeModel     *model;
-        GtkTreeIter       iter;
+        GtkWidget *add_dialog;
+        char       *name;
+        char       *exec;
+        char       *comment;
 
-        view = GTK_TREE_VIEW (glade_xml_get_widget (dialog->priv->xml,
-                                                    CAPPLET_TREEVIEW_WIDGET_NAME));
+        add_dialog = gsm_app_dialog_new (NULL, NULL, NULL);
+        gtk_window_set_transient_for (GTK_WINDOW (add_dialog),
+                                      GTK_WINDOW (dialog));
 
-        selection = gtk_tree_view_get_selection (view);
-        model = gtk_tree_view_get_model (view);
-
-        gtk_list_store_append (GTK_LIST_STORE (model), &iter);
-
-        if (edit_app_dialog (dialog, &iter)) {
-                add_app (GTK_LIST_STORE (model), &iter);
-        } else {
-                gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+        if (gsm_app_dialog_run (GSM_APP_DIALOG (add_dialog),
+                                &name, &exec, &comment)) {
+                gsp_app_create (name, comment, exec);
+                g_free (name);
+                g_free (exec);
+                g_free (comment);
         }
 }
 
 static void
-delete_desktop_file (GtkListStore *store,
-                     GtkTreeIter  *iter)
-{
-        EggDesktopFile *desktop_file;
-        GFile          *source;
-        char           *basename;
-        char           *path;
-
-        gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
-                            STORE_COL_DESKTOP_FILE, &desktop_file,
-                            -1);
-
-        source = g_file_new_for_uri (egg_desktop_file_get_source (desktop_file));
-
-        path = g_file_get_path (source);
-        basename = g_file_get_basename (source);
-
-        if (g_str_has_prefix (path, g_get_user_config_dir ()) &&
-            !system_desktop_entry_exists (basename, NULL)) {
-                if (g_file_test (path, G_FILE_TEST_EXISTS))
-                        g_remove (path);
-        } else {
-                /* Two possible cases:
-                 * a) We want to remove a system wide startup desktop file.
-                 *    We can't do that, so we will create a user desktop file
-                 *    with the hidden flag set.
-                 * b) We want to remove a startup desktop file that is both
-                 *    system and user. So we have to mark it as hidden.
-                 */
-                GKeyFile *keyfile;
-                GError   *error;
-                char     *user_path;
-
-                ensure_user_autostart_dir ();
-
-                keyfile = g_key_file_new ();
-
-                error = NULL;
-
-                g_key_file_load_from_file (keyfile, path,
-                                           G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS,
-                                           &error);
-
-                if (error) {
-                        g_error_free (error);
-                        g_key_file_free (keyfile);
-                }
-
-                g_key_file_set_boolean (keyfile,
-                                        G_KEY_FILE_DESKTOP_GROUP,
-                                        G_KEY_FILE_DESKTOP_KEY_HIDDEN,
-                                        TRUE);
-
-                user_path = g_build_filename (g_get_user_config_dir (),
-                                              "autostart", basename, NULL);
-
-                if (!key_file_to_file (keyfile, user_path, NULL)) {
-                        g_warning ("Could not save %s file", user_path);
-                }
-
-                g_key_file_free (keyfile);
-
-                g_free (user_path);
-        }
-
-        g_object_unref (source);
-        g_free (path);
-        g_free (basename);
-}
-
-static void
-delete_app (GtkListStore *store,
-            GtkTreeIter  *iter)
-{
-        delete_desktop_file (store, iter);
-}
-
-static void
 on_delete_app_clicked (GtkWidget           *widget,
                        GsmPropertiesDialog *dialog)
 {
-        GtkTreeView      *view;
         GtkTreeSelection *selection;
-        GtkTreeModel     *model;
         GtkTreeIter       iter;
+        GspApp           *app;
 
-        view = GTK_TREE_VIEW (glade_xml_get_widget (dialog->priv->xml,
-                                                    CAPPLET_TREEVIEW_WIDGET_NAME));
-
-        selection = gtk_tree_view_get_selection (view);
-        model = gtk_tree_view_get_model (view);
+        selection = gtk_tree_view_get_selection (dialog->priv->treeview);
 
         if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) {
                 return;
         }
 
-        delete_app (GTK_LIST_STORE (model), &iter);
-
-        gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
-}
-
-static void
-update_app (GtkListStore *store,
-            GtkTreeIter *iter)
-{
-        EggDesktopFile *desktop_file;
-        gboolean        enabled;
-
-        gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
-                            STORE_COL_ENABLED, &enabled,
-                            STORE_COL_DESKTOP_FILE, &desktop_file,
+        app = NULL;
+        gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->tree_filter),
+                            &iter,
+                            STORE_COL_APP, &app,
                             -1);
 
-        write_desktop_file (desktop_file, store, iter, enabled);
+        if (app) {
+                gsp_app_delete (app);
+                g_object_unref (app);
+        }
 }
 
 static void
 on_edit_app_clicked (GtkWidget           *widget,
                      GsmPropertiesDialog *dialog)
 {
-        GtkTreeView      *view;
         GtkTreeSelection *selection;
-        GtkTreeModel     *model;
         GtkTreeIter       iter;
+        GspApp           *app;
 
-        view = GTK_TREE_VIEW (glade_xml_get_widget (dialog->priv->xml,
-                                                    CAPPLET_TREEVIEW_WIDGET_NAME));
-
-        selection = gtk_tree_view_get_selection (view);
-        model = gtk_tree_view_get_model (view);
+        selection = gtk_tree_view_get_selection (dialog->priv->treeview);
 
         if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) {
                 return;
         }
 
-        if (edit_app_dialog (dialog, &iter)) {
-                update_app (GTK_LIST_STORE (model), &iter);
+        app = NULL;
+        gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->tree_filter),
+                            &iter,
+                            STORE_COL_APP, &app,
+                            -1);
+
+        if (app) {
+                gsp_app_edit (app, GTK_WINDOW (dialog));
+                g_object_unref (app);
         }
 }
 
@@ -1212,18 +400,12 @@ on_autosave_value_notify (GConfClient         *client,
 {
         gboolean   gval;
         gboolean   bval;
-        GtkWidget *button;
-
-        button = glade_xml_get_widget (dialog->priv->xml, CAPPLET_REMEMBER_WIDGET_NAME);
-        if (button == NULL) {
-                return;
-        }
 
         gval = gconf_value_get_bool (entry->value);
-        bval = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+        bval = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->priv->remember_toggle));
 
         if (bval != gval) {
-                gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), gval);
+                gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->priv->remember_toggle), gval);
         }
 }
 
@@ -1255,8 +437,9 @@ on_save_session_clicked (GtkWidget           *widget,
 static void
 setup_dialog (GsmPropertiesDialog *dialog)
 {
-        GtkWidget         *treeview;
+        GtkTreeView       *treeview;
         GtkWidget         *button;
+        GtkTreeModel      *tree_filter;
         GtkTreeViewColumn *column;
         GtkCellRenderer   *renderer;
         GtkTreeSelection  *selection;
@@ -1266,32 +449,36 @@ setup_dialog (GsmPropertiesDialog *dialog)
         gtk_dialog_add_buttons (GTK_DIALOG (dialog),
                                 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
                                 NULL);
-        g_signal_connect (dialog,
-                          "response",
-                          G_CALLBACK (on_response),
-                          dialog);
 
         dialog->priv->list_store = gtk_list_store_new (NUMBER_OF_COLUMNS,
                                                        G_TYPE_BOOLEAN,
+                                                       G_TYPE_BOOLEAN,
                                                        G_TYPE_STRING,
+                                                       G_TYPE_ICON,
+                                                       GDK_TYPE_PIXBUF,
                                                        G_TYPE_STRING,
-                                                       G_TYPE_STRING,
-                                                       G_TYPE_STRING,
-                                                       G_TYPE_STRING,
-                                                       G_TYPE_POINTER,
-                                                       G_TYPE_STRING,
-                                                       G_TYPE_BOOLEAN);
+                                                       G_TYPE_OBJECT);
+        tree_filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (dialog->priv->list_store),
+                                                 NULL);
+        g_object_unref (dialog->priv->list_store);
+        dialog->priv->tree_filter = tree_filter;
+
+        gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (tree_filter),
+                                                  STORE_COL_VISIBLE);
 
-        treeview = glade_xml_get_widget (dialog->priv->xml, CAPPLET_TREEVIEW_WIDGET_NAME);
-        gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
-                                 GTK_TREE_MODEL (dialog->priv->list_store));
-        gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
+        treeview = GTK_TREE_VIEW (glade_xml_get_widget (dialog->priv->xml, CAPPLET_TREEVIEW_WIDGET_NAME));
+        dialog->priv->treeview = treeview;
+
+        gtk_tree_view_set_model (treeview, tree_filter);
+        g_object_unref (tree_filter);
+
+        gtk_tree_view_set_headers_visible (treeview, FALSE);
         g_signal_connect (treeview,
                           "row-activated",
                           G_CALLBACK (on_row_activated),
                           dialog);
 
-        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
+        selection = gtk_tree_view_get_selection (treeview);
         gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
         g_signal_connect (selection,
                           "changed",
@@ -1303,9 +490,8 @@ setup_dialog (GsmPropertiesDialog *dialog)
         column = gtk_tree_view_column_new_with_attributes (_("Enabled"),
                                                            renderer,
                                                            "active", STORE_COL_ENABLED,
-                                                           "activatable", STORE_COL_ACTIVATABLE,
                                                            NULL);
-        gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+        gtk_tree_view_append_column (treeview, column);
         g_signal_connect (renderer,
                           "toggled",
                           G_CALLBACK (on_startup_enabled_toggled),
@@ -1315,12 +501,14 @@ setup_dialog (GsmPropertiesDialog *dialog)
         renderer = gtk_cell_renderer_pixbuf_new ();
         column = gtk_tree_view_column_new_with_attributes (_("Icon"),
                                                            renderer,
-                                                           "icon-name", STORE_COL_ICON_NAME,
+                                                           "icon_name", STORE_COL_ICON_NAME,
+                                                           "gicon", STORE_COL_GICON,
+                                                           "pixbuf", STORE_COL_PIXBUF,
                                                            NULL);
         g_object_set (renderer,
-                      "stock-size", GTK_ICON_SIZE_LARGE_TOOLBAR,
+                      "stock-size", GSM_PROPERTIES_ICON_SIZE,
                       NULL);
-        gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+        gtk_tree_view_append_column (treeview, column);
 
         /* NAME COLUMN */
         renderer = gtk_cell_renderer_text_new ();
@@ -1331,14 +519,14 @@ setup_dialog (GsmPropertiesDialog *dialog)
         g_object_set (renderer,
                       "ellipsize", PANGO_ELLIPSIZE_END,
                       NULL);
-        gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+        gtk_tree_view_append_column (treeview, column);
 
 
-        gtk_tree_view_column_set_sort_column_id (column, STORE_COL_NAME);
-        gtk_tree_view_set_search_column (GTK_TREE_VIEW (treeview), STORE_COL_NAME);
-        gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (treeview), TRUE);
+        gtk_tree_view_column_set_sort_column_id (column, STORE_COL_DESCRIPTION);
+        gtk_tree_view_set_search_column (treeview, STORE_COL_DESCRIPTION);
+        gtk_tree_view_set_rules_hint (treeview, TRUE);
 
-        gtk_drag_dest_set (treeview,
+        gtk_drag_dest_set (GTK_WIDGET (treeview),
                            GTK_DEST_DEFAULT_ALL,
                            drag_targets,
                            G_N_ELEMENTS (drag_targets),
@@ -1350,23 +538,26 @@ setup_dialog (GsmPropertiesDialog *dialog)
                           dialog);
 
         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (dialog->priv->list_store),
-                                              STORE_COL_NAME,
+                                              STORE_COL_DESCRIPTION,
                                               GTK_SORT_ASCENDING);
 
 
         button = glade_xml_get_widget (dialog->priv->xml, CAPPLET_ADD_WIDGET_NAME);
+        dialog->priv->add_button = button;
         g_signal_connect (button,
                           "clicked",
                           G_CALLBACK (on_add_app_clicked),
                           dialog);
 
         button = glade_xml_get_widget (dialog->priv->xml, CAPPLET_DELETE_WIDGET_NAME);
+        dialog->priv->delete_button = button;
         g_signal_connect (button,
                           "clicked",
                           G_CALLBACK (on_delete_app_clicked),
                           dialog);
 
         button = glade_xml_get_widget (dialog->priv->xml, CAPPLET_EDIT_WIDGET_NAME);
+        dialog->priv->edit_button = button;
         g_signal_connect (button,
                           "clicked",
                           G_CALLBACK (on_edit_app_clicked),
@@ -1374,6 +565,7 @@ setup_dialog (GsmPropertiesDialog *dialog)
 
         client = gconf_client_get_default ();
         button = glade_xml_get_widget (dialog->priv->xml, CAPPLET_REMEMBER_WIDGET_NAME);
+        dialog->priv->remember_toggle = button;
         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
                                       gconf_client_get_bool (client, SPC_GCONF_AUTOSAVE_KEY, NULL));
         gconf_client_notify_add (client,
@@ -1395,6 +587,12 @@ setup_dialog (GsmPropertiesDialog *dialog)
                           G_CALLBACK (on_save_session_clicked),
                           dialog);
 
+        dialog->priv->manager = gsp_app_manager_get ();
+        gsp_app_manager_fill (dialog->priv->manager);
+        g_signal_connect_swapped (dialog->priv->manager, "added",
+                                  G_CALLBACK (_app_added), dialog);
+        g_signal_connect_swapped (dialog->priv->manager, "removed",
+                                  G_CALLBACK (_app_removed), dialog);
 
         populate_model (dialog);
 }
@@ -1427,6 +625,11 @@ gsm_properties_dialog_dispose (GObject *object)
 
         dialog = GSM_PROPERTIES_DIALOG (object);
 
+        if (dialog->priv->manager != NULL) {
+                g_object_unref (dialog->priv->manager);
+                dialog->priv->manager = NULL;
+        }
+
         if (dialog->priv->xml != NULL) {
                 g_object_unref (dialog->priv->xml);
                 dialog->priv->xml = NULL;
diff --git a/capplet/gsm-properties-dialog.h b/capplet/gsm-properties-dialog.h
index 6d7cd7c..df4915e 100644
--- a/capplet/gsm-properties-dialog.h
+++ b/capplet/gsm-properties-dialog.h
@@ -50,6 +50,8 @@ GType                  gsm_properties_dialog_get_type           (void);
 
 GtkWidget            * gsm_properties_dialog_new                (void);
 
+#define GSM_PROPERTIES_ICON_SIZE GTK_ICON_SIZE_LARGE_TOOLBAR
+
 G_END_DECLS
 
 #endif /* __GSM_PROPERTIES_DIALOG_H */
diff --git a/capplet/gsp-app-manager.c b/capplet/gsp-app-manager.c
new file mode 100644
index 0000000..201da48
--- /dev/null
+++ b/capplet/gsp-app-manager.c
@@ -0,0 +1,567 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 1999 Free Software Foundation, Inc.
+ * Copyright (C) 2007, 2009 Vincent Untz.
+ * Copyright (C) 2008 Lucas Rocha.
+ * Copyright (C) 2008 William Jon McCann <jmccann redhat com>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <string.h>
+
+#include "gsm-util.h"
+#include "gsp-app.h"
+
+#include "gsp-app-manager.h"
+
+static GspAppManager *manager = NULL;
+
+typedef struct {
+        char         *dir;
+        int           index;
+        GFileMonitor *monitor;
+} GspXdgDir;
+
+struct _GspAppManagerPrivate {
+        GSList *apps;
+        GSList *dirs;
+};
+
+#define GSP_APP_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSP_TYPE_APP_MANAGER, GspAppManagerPrivate))
+
+
+enum {
+        ADDED,
+        REMOVED,
+        LAST_SIGNAL
+};
+
+static guint gsp_app_manager_signals[LAST_SIGNAL] = { 0 };
+
+
+G_DEFINE_TYPE (GspAppManager, gsp_app_manager, G_TYPE_OBJECT)
+
+static void     gsp_app_manager_finalize     (GObject       *object);
+static void     _gsp_app_manager_app_unref   (GspApp        *app,
+                                              GspAppManager *manager);
+static void     _gsp_app_manager_app_removed (GspAppManager *manager,
+                                              GspApp        *app);
+
+static GspXdgDir *
+_gsp_xdg_dir_new (const char *dir,
+                  int         index)
+{
+        GspXdgDir *xdgdir;
+
+        xdgdir = g_slice_new (GspXdgDir);
+
+        xdgdir->dir = g_strdup (dir);
+        xdgdir->index = index;
+        xdgdir->monitor = NULL;
+
+        return xdgdir;
+}
+
+static void
+_gsp_xdg_dir_free (GspXdgDir *xdgdir)
+{
+        if (xdgdir->dir) {
+                g_free (xdgdir->dir);
+                xdgdir->dir = NULL;
+        }
+
+        if (xdgdir->monitor) {
+                g_file_monitor_cancel (xdgdir->monitor);
+                g_object_unref (xdgdir->monitor);
+                xdgdir->monitor = NULL;
+        }
+
+        g_slice_free (GspXdgDir, xdgdir);
+}
+
+static void
+gsp_app_manager_class_init (GspAppManagerClass *class)
+{
+        GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+        gobject_class->finalize = gsp_app_manager_finalize;
+
+        gsp_app_manager_signals[ADDED] =
+                g_signal_new ("added",
+                              G_TYPE_FROM_CLASS (gobject_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GspAppManagerClass,
+                                               added),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__OBJECT,
+                              G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+        gsp_app_manager_signals[REMOVED] =
+                g_signal_new ("removed",
+                              G_TYPE_FROM_CLASS (gobject_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GspAppManagerClass,
+                                               removed),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__OBJECT,
+                              G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+        g_type_class_add_private (class, sizeof (GspAppManagerPrivate));
+}
+
+static void
+gsp_app_manager_init (GspAppManager *manager)
+{
+        manager->priv = GSP_APP_MANAGER_GET_PRIVATE (manager);
+
+        memset (manager->priv, 0, sizeof (GspAppManagerPrivate));
+}
+
+static void
+gsp_app_manager_finalize (GObject *object)
+{
+        GspAppManager *manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSP_IS_APP_MANAGER (object));
+
+        manager = GSP_APP_MANAGER (object);
+
+        g_slist_foreach (manager->priv->apps,
+                         (GFunc) _gsp_app_manager_app_unref, manager);
+        g_slist_free (manager->priv->apps);
+        manager->priv->apps = NULL;
+
+        g_slist_foreach (manager->priv->dirs,
+                         (GFunc) _gsp_xdg_dir_free, NULL);
+        g_slist_free (manager->priv->dirs);
+        manager->priv->dirs = NULL;
+
+        G_OBJECT_CLASS (gsp_app_manager_parent_class)->finalize (object);
+
+        manager = NULL;
+}
+
+static void
+_gsp_app_manager_emit_added (GspAppManager *manager,
+                             GspApp        *app)
+{
+        g_signal_emit (G_OBJECT (manager), gsp_app_manager_signals[ADDED],
+                       0, app);
+}
+
+static void
+_gsp_app_manager_emit_removed (GspAppManager *manager,
+                               GspApp        *app)
+{
+        g_signal_emit (G_OBJECT (manager), gsp_app_manager_signals[REMOVED],
+                       0, app);
+}
+
+/*
+ * Directories
+ */
+
+static int
+gsp_app_manager_get_dir_index (GspAppManager *manager,
+                               const char    *dir)
+{
+        GSList    *l;
+        GspXdgDir *xdgdir;
+
+        g_return_val_if_fail (GSP_IS_APP_MANAGER (manager), -1);
+        g_return_val_if_fail (dir != NULL, -1);
+
+        for (l = manager->priv->dirs; l != NULL; l = l->next) {
+                xdgdir = l->data;
+                if (strcmp (dir, xdgdir->dir) == 0) {
+                        return xdgdir->index;
+                }
+        }
+
+        return -1;
+}
+
+const char *
+gsp_app_manager_get_dir (GspAppManager *manager,
+                         unsigned int   index)
+{
+        GSList    *l;
+        GspXdgDir *xdgdir;
+
+        g_return_val_if_fail (GSP_IS_APP_MANAGER (manager), NULL);
+
+        for (l = manager->priv->dirs; l != NULL; l = l->next) {
+                xdgdir = l->data;
+                if (index == xdgdir->index) {
+                        return xdgdir->dir;
+                }
+        }
+
+        return NULL;
+}
+
+static int
+_gsp_app_manager_find_dir_with_basename (GspAppManager *manager,
+                                         const char    *basename,
+                                         int            minimum_index)
+{
+        GSList    *l;
+        GspXdgDir *xdgdir;
+        char      *path;
+        GKeyFile  *keyfile;
+        int        result = -1;
+
+        path = NULL;
+        keyfile = g_key_file_new ();
+
+        for (l = manager->priv->dirs; l != NULL; l = l->next) {
+                xdgdir = l->data;
+
+                if (xdgdir->index <= minimum_index) {
+                        continue;
+                }
+
+                g_free (path);
+                path = g_build_filename (xdgdir->dir, basename, NULL);
+                if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
+                        continue;
+                }
+
+                if (!g_key_file_load_from_file (keyfile, path,
+                                                G_KEY_FILE_NONE, NULL)) {
+                        continue;
+                }
+
+                /* the file exists and is readable */
+                if (result == -1) {
+                        result = xdgdir->index;
+                } else {
+                        result = MIN (result, xdgdir->index);
+                }
+        }
+
+        g_key_file_free (keyfile);
+        g_free (path);
+
+        return result;
+}
+
+static void
+_gsp_app_manager_handle_delete (GspAppManager *manager,
+                                GspApp        *app,
+                                const char    *basename,
+                                int            index)
+{
+        unsigned int position;
+        unsigned int system_position;
+
+        position = gsp_app_get_xdg_position (app);
+        system_position = gsp_app_get_xdg_system_position (app);
+
+        if (system_position < index) {
+                /* it got deleted, but we don't even care about it */
+                return;
+        }
+
+        if (position == index &&
+            (system_position == index || system_position == G_MAXUINT)) {
+                /* the file used by the user was deleted, and there's no other
+                 * file in system directories. So it really got deleted. */
+                _gsp_app_manager_app_removed (manager, app);
+                return;
+        }
+
+        if (system_position == index) {
+                /* then we know that position != index; we just hae to tell
+                 * GspApp if there's still a system directory containing this
+                 * basename */
+                int new_system;
+
+                new_system = _gsp_app_manager_find_dir_with_basename (manager,
+                                                                      basename,
+                                                                      index);
+                if (new_system < 0) {
+                        gsp_app_set_xdg_system_position (app, G_MAXUINT);
+                } else {
+                        gsp_app_set_xdg_system_position (app, new_system);
+                }
+
+                return;
+        }
+
+        if (position == index) {
+                /* then we know that system_position != G_MAXUINT; we need to
+                 * tell GspApp to change position to system_position */
+                const char *dir;
+
+                dir = gsp_app_manager_get_dir (manager, system_position);
+                if (dir) {
+                        char *path;
+
+                        path = g_build_filename (dir, basename, NULL);
+                        gsp_app_reload_at (app, path,
+                                           (unsigned int) system_position);
+                        g_free (path);
+                } else {
+                        _gsp_app_manager_app_removed (manager, app);
+                }
+
+                return;
+        }
+
+        g_assert_not_reached ();
+}
+
+static gboolean
+gsp_app_manager_xdg_dir_monitor (GFileMonitor      *monitor,
+                                 GFile             *child,
+                                 GFile             *other_file,
+                                 GFileMonitorEvent  flags,
+                                 gpointer           data)
+{
+        GspAppManager *manager;
+        GspApp        *old_app;
+        GspApp        *app;
+        GFile         *parent;
+        char          *basename;
+        char          *dir;
+        char          *path;
+        int            index;
+
+        manager = GSP_APP_MANAGER (data);
+
+        basename = g_file_get_basename (child);
+        if (!g_str_has_suffix (basename, ".desktop")) {
+                /* not a desktop file, we can ignore */
+                g_free (basename);
+                return TRUE;
+        }
+        old_app = gsp_app_manager_find_app_with_basename (manager, basename);
+
+        parent = g_file_get_parent (child);
+        dir = g_file_get_path (parent);
+        g_object_unref (parent);
+
+        index = gsp_app_manager_get_dir_index (manager, dir);
+        if (index < 0) {
+                /* not a directory we know; should never happen, though */
+                g_free (dir);
+                return TRUE;
+        }
+
+        path = g_file_get_path (child);
+
+        switch (flags) {
+        case G_FILE_MONITOR_EVENT_CHANGED:
+        case G_FILE_MONITOR_EVENT_CREATED:
+                /* we just do as if it was a new file: GspApp is clever enough
+                 * to do the right thing */
+                app = gsp_app_new (path, (unsigned int) index);
+
+                /* we didn't have this app before, so add it */
+                if (old_app == NULL && app != NULL) {
+                        gsp_app_manager_add (manager, app);
+                        g_object_unref (app);
+                }
+                /* else: it was just updated, GspApp took care of
+                 * sending the event */
+                break;
+        case G_FILE_MONITOR_EVENT_DELETED:
+                if (!old_app) {
+                        /* it got deleted, but we don't know about it, so
+                         * nothing to do */
+                        break;
+                }
+
+                _gsp_app_manager_handle_delete (manager, old_app,
+                                                basename, index);
+                break;
+        default:
+                break;
+        }
+
+        g_free (path);
+        g_free (dir);
+        g_free (basename);
+
+        return TRUE;
+}
+
+/*
+ * Initialization
+ */
+
+static void
+_gsp_app_manager_fill_from_dir (GspAppManager *manager,
+                                GspXdgDir     *xdgdir)
+{
+        GFile      *file;
+        GDir       *dir;
+        const char *name;
+
+        file = g_file_new_for_path (xdgdir->dir);
+        xdgdir->monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE,
+                                                    NULL, NULL);
+        g_object_unref (file);
+
+        if (xdgdir->monitor) {
+                g_signal_connect (xdgdir->monitor, "changed",
+                                  G_CALLBACK (gsp_app_manager_xdg_dir_monitor),
+                                  manager);
+        }
+
+        dir = g_dir_open (xdgdir->dir, 0, NULL);
+        if (!dir) {
+                return;
+        }
+
+        while ((name = g_dir_read_name (dir))) {
+                GspApp *app;
+                char   *desktop_file_path;
+
+                if (!g_str_has_suffix (name, ".desktop")) {
+                        continue;
+                }
+
+                desktop_file_path = g_build_filename (xdgdir->dir, name, NULL);
+                app = gsp_app_new (desktop_file_path, xdgdir->index);
+
+                if (app != NULL) {
+                        gsp_app_manager_add (manager, app);
+                        g_object_unref (app);
+                }
+
+                g_free (desktop_file_path);
+        }
+
+        g_dir_close (dir);
+}
+
+void
+gsp_app_manager_fill (GspAppManager *manager)
+{
+        char **autostart_dirs;
+        int    i;
+
+        if (manager->priv->apps != NULL)
+                return;
+
+        autostart_dirs = gsm_util_get_autostart_dirs ();
+        /* we always assume that the first directory is the user one */
+        g_assert (g_str_has_prefix (autostart_dirs[0],
+                                    g_get_user_config_dir ()));
+
+        for (i = 0; autostart_dirs[i] != NULL; i++) {
+                GspXdgDir *xdgdir;
+
+                if (gsp_app_manager_get_dir_index (manager,
+                                                   autostart_dirs[i]) >= 0) {
+                        continue;
+                }
+
+                xdgdir = _gsp_xdg_dir_new (autostart_dirs[i], i);
+                manager->priv->dirs = g_slist_prepend (manager->priv->dirs,
+                                                       xdgdir);
+
+                _gsp_app_manager_fill_from_dir (manager, xdgdir);
+        }
+
+        g_strfreev (autostart_dirs);
+}
+
+/*
+ * App handling
+ */
+
+static void
+_gsp_app_manager_app_unref (GspApp        *app,
+                            GspAppManager *manager)
+{
+        g_signal_handlers_disconnect_by_func (app,
+                                              _gsp_app_manager_app_removed,
+                                              manager);
+        g_object_unref (app);
+}
+
+static void
+_gsp_app_manager_app_removed (GspAppManager *manager,
+                              GspApp        *app)
+{
+        _gsp_app_manager_emit_removed (manager, app);
+        manager->priv->apps = g_slist_remove (manager->priv->apps, app);
+        _gsp_app_manager_app_unref (app, manager);
+}
+
+void
+gsp_app_manager_add (GspAppManager *manager,
+                     GspApp        *app)
+{
+        g_return_if_fail (GSP_IS_APP_MANAGER (manager));
+        g_return_if_fail (GSP_IS_APP (app));
+
+        manager->priv->apps = g_slist_prepend (manager->priv->apps,
+                                               g_object_ref (app));
+        g_signal_connect_swapped (app, "removed",
+                                  G_CALLBACK (_gsp_app_manager_app_removed),
+                                  manager);
+        _gsp_app_manager_emit_added (manager, app);
+}
+
+GspApp *
+gsp_app_manager_find_app_with_basename (GspAppManager *manager,
+                                        const char    *basename)
+{
+        GSList *l;
+        GspApp *app;
+
+        g_return_val_if_fail (GSP_IS_APP_MANAGER (manager), NULL);
+        g_return_val_if_fail (basename != NULL, NULL);
+
+        for (l = manager->priv->apps; l != NULL; l = l->next) {
+                app = GSP_APP (l->data);
+                if (strcmp (basename, gsp_app_get_basename (app)) == 0)
+                        return app;
+        }
+
+        return NULL;
+}
+
+/*
+ * Singleton
+ */
+
+GspAppManager *
+gsp_app_manager_get (void)
+{
+        if (manager == NULL) {
+                manager = g_object_new (GSP_TYPE_APP_MANAGER, NULL);
+                return manager;
+        } else {
+                return g_object_ref (manager);
+        }
+}
+
+GSList *
+gsp_app_manager_get_apps (GspAppManager *manager)
+{
+        g_return_val_if_fail (GSP_IS_APP_MANAGER (manager), NULL);
+
+        return g_slist_copy (manager->priv->apps);
+}
diff --git a/capplet/gsp-app-manager.h b/capplet/gsp-app-manager.h
new file mode 100644
index 0000000..777f8d6
--- /dev/null
+++ b/capplet/gsp-app-manager.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 1999 Free Software Foundation, Inc.
+ * Copyright (C) 2007, 2009 Vincent Untz.
+ * Copyright (C) 2008 Lucas Rocha.
+ * Copyright (C) 2008 William Jon McCann <jmccann redhat com>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GSP_APP_MANAGER_H
+#define __GSP_APP_MANAGER_H
+
+#include <glib-object.h>
+
+#include <gsp-app.h>
+
+G_BEGIN_DECLS
+
+#define GSP_TYPE_APP_MANAGER            (gsp_app_manager_get_type ())
+#define GSP_APP_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSP_TYPE_APP_MANAGER, GspAppManager))
+#define GSP_APP_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GSP_TYPE_APP_MANAGER, GspAppManagerClass))
+#define GSP_IS_APP_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSP_TYPE_APP_MANAGER))
+#define GSP_IS_APP_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSP_TYPE_APP_MANAGER))
+#define GSP_APP_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GSP_TYPE_APP_MANAGER, GspAppManagerClass))
+
+typedef struct _GspAppManager        GspAppManager;
+typedef struct _GspAppManagerClass   GspAppManagerClass;
+
+typedef struct _GspAppManagerPrivate GspAppManagerPrivate;
+
+struct _GspAppManagerClass
+{
+        GObjectClass parent_class;
+
+        void (* added)   (GspAppManager *manager,
+                          GspApp        *app);
+        void (* removed) (GspAppManager *manager,
+                          GspApp        *app);
+};
+
+struct _GspAppManager
+{
+        GObject parent_instance;
+
+        GspAppManagerPrivate *priv;
+};
+
+GType           gsp_app_manager_get_type               (void);
+
+GspAppManager  *gsp_app_manager_get                    (void);
+
+void            gsp_app_manager_fill                   (GspAppManager *manager);
+
+GSList         *gsp_app_manager_get_apps               (GspAppManager *manager);
+
+GspApp         *gsp_app_manager_find_app_with_basename (GspAppManager *manager,
+                                                        const char    *basename);
+
+const char     *gsp_app_manager_get_dir                (GspAppManager *manager,
+                                                        unsigned int   index);
+
+void            gsp_app_manager_add                    (GspAppManager *manager,
+                                                        GspApp        *app);
+
+G_END_DECLS
+
+#endif /* __GSP_APP_MANAGER_H */
diff --git a/capplet/gsp-app.c b/capplet/gsp-app.c
new file mode 100644
index 0000000..165cf31
--- /dev/null
+++ b/capplet/gsp-app.c
@@ -0,0 +1,1019 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 1999 Free Software Foundation, Inc.
+ * Copyright (C) 2007, 2009 Vincent Untz.
+ * Copyright (C) 2008 Lucas Rocha.
+ * Copyright (C) 2008 William Jon McCann <jmccann redhat com>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <string.h>
+#include <sys/stat.h>
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include "gsm-app-dialog.h"
+#include "gsm-properties-dialog.h"
+#include "gsm-util.h"
+#include "gsp-app-manager.h"
+#include "gsp-keyfile.h"
+
+#include "gsp-app.h"
+
+#define GSP_APP_SAVE_DELAY 2
+
+#define GSP_ASP_SAVE_MASK_HIDDEN   0x0001
+#define GSP_ASP_SAVE_MASK_ENABLED  0x0002
+#define GSP_ASP_SAVE_MASK_NAME     0x0004
+#define GSP_ASP_SAVE_MASK_EXEC     0x0008
+#define GSP_ASP_SAVE_MASK_COMMENT  0x0010
+#define GSP_ASP_SAVE_MASK_ALL      0xffff
+
+struct _GspAppPrivate {
+        char         *basename;
+        char         *path;
+
+        gboolean      hidden;
+        gboolean      enabled;
+
+        char         *name;
+        char         *exec;
+        char         *comment;
+        char         *icon_name;
+
+        GdkPixbuf    *pixbuf;
+        char         *description;
+
+        /* position of the directory in the XDG environment variable */
+        unsigned int  xdg_position;
+        /* position of the first system directory in the XDG env var containing
+         * this autostart app too (G_MAXUINT means none) */
+        unsigned int  xdg_system_position;
+
+        unsigned int  save_timeout;
+        /* mask of what has changed */
+        unsigned int  save_mask;
+        /* path that contains the original file that needs to be saved */
+        char         *old_system_path;
+        /* after writing to file, we skip the next file monitor event of type
+         * CHANGED */
+        gboolean      skip_next_monitor_event;
+};
+
+#define GSP_APP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSP_TYPE_APP, GspAppPrivate))
+
+
+enum {
+        CHANGED,
+        REMOVED,
+        LAST_SIGNAL
+};
+
+static guint gsp_app_signals[LAST_SIGNAL] = { 0 };
+
+
+G_DEFINE_TYPE (GspApp, gsp_app, G_TYPE_OBJECT)
+
+static void     gsp_app_finalize (GObject *object);
+static gboolean _gsp_app_save    (gpointer data);
+
+
+static gboolean
+_gsp_str_equal (const char *a,
+                const char *b)
+{
+        if (g_strcmp0 (a, b) == 0) {
+                return TRUE;
+        }
+
+        if (a && !b && a[0] == '\0') {
+                return TRUE;
+        }
+
+        if (b && !a && b[0] == '\0') {
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+
+static void
+gsp_app_class_init (GspAppClass *class)
+{
+        GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+        gobject_class->finalize = gsp_app_finalize;
+
+        gsp_app_signals[CHANGED] =
+                g_signal_new ("changed",
+                              G_TYPE_FROM_CLASS (gobject_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GspAppClass,
+                                               changed),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+
+        gsp_app_signals[REMOVED] =
+                g_signal_new ("removed",
+                              G_TYPE_FROM_CLASS (gobject_class),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GspAppClass,
+                                               removed),
+                              NULL,
+                              NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE, 0);
+
+        g_type_class_add_private (class, sizeof (GspAppPrivate));
+}
+
+static void
+gsp_app_init (GspApp *app)
+{
+        app->priv = GSP_APP_GET_PRIVATE (app);
+
+        memset (app->priv, 0, sizeof (GspAppPrivate));
+        app->priv->xdg_position        = G_MAXUINT;
+        app->priv->xdg_system_position = G_MAXUINT;
+}
+
+static void
+_gsp_app_free_reusable_data (GspApp *app)
+{
+        if (app->priv->path) {
+                g_free (app->priv->path);
+                app->priv->path = NULL;
+        }
+
+        if (app->priv->name) {
+                g_free (app->priv->name);
+                app->priv->name = NULL;
+        }
+
+        if (app->priv->exec) {
+                g_free (app->priv->exec);
+                app->priv->exec = NULL;
+        }
+
+        if (app->priv->comment) {
+                g_free (app->priv->comment);
+                app->priv->comment = NULL;
+        }
+
+        if (app->priv->icon_name) {
+                g_free (app->priv->icon_name);
+                app->priv->icon_name = NULL;
+        }
+
+        if (app->priv->pixbuf) {
+                g_object_unref (app->priv->pixbuf);
+                app->priv->pixbuf = NULL;
+        }
+
+        if (app->priv->description) {
+                g_free (app->priv->description);
+                app->priv->description = NULL;
+        }
+
+        if (app->priv->old_system_path) {
+                g_free (app->priv->old_system_path);
+                app->priv->old_system_path = NULL;
+        }
+}
+
+static void
+gsp_app_finalize (GObject *object)
+{
+        GspApp *app;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSP_IS_APP (object));
+
+        app = GSP_APP (object);
+
+        if (app->priv->save_timeout) {
+                g_source_remove (app->priv->save_timeout);
+                app->priv->save_timeout = 0;
+
+                /* save now */
+                _gsp_app_save (app);
+        }
+
+        if (app->priv->basename) {
+                g_free (app->priv->basename);
+                app->priv->basename = NULL;
+        }
+
+        _gsp_app_free_reusable_data (app);
+
+        G_OBJECT_CLASS (gsp_app_parent_class)->finalize (object);
+}
+
+static void
+_gsp_app_emit_changed (GspApp *app)
+{
+        g_signal_emit (G_OBJECT (app), gsp_app_signals[CHANGED], 0);
+}
+
+static void
+_gsp_app_emit_removed (GspApp *app)
+{
+        g_signal_emit (G_OBJECT (app), gsp_app_signals[REMOVED], 0);
+}
+
+static void
+_gsp_app_update_description (GspApp *app)
+{
+        const char *primary;
+        const char *secondary;
+
+        if (!gsm_util_text_is_blank (app->priv->name)) {
+                primary = app->priv->name;
+        } else if (!gsm_util_text_is_blank (app->priv->exec)) {
+                primary = app->priv->exec;
+        } else {
+                primary = _("No name");
+        }
+
+        if (!gsm_util_text_is_blank (app->priv->comment)) {
+                secondary = app->priv->comment;
+        } else {
+                secondary = _("No description");
+        }
+
+        g_free (app->priv->description);
+        app->priv->description = g_markup_printf_escaped ("<b>%s</b>\n%s",
+                                                          primary,
+                                                          secondary);
+}
+
+/*
+ * Saving
+ */
+
+static void
+_gsp_ensure_user_autostart_dir (void)
+{
+        char *dir;
+
+        dir = g_build_filename (g_get_user_config_dir (), "autostart", NULL);
+        g_mkdir_with_parents (dir, S_IRWXU);
+
+        g_free (dir);
+}
+
+static gboolean
+_gsp_app_user_equal_system (GspApp  *app,
+                            char   **system_path)
+{
+        GspAppManager *manager;
+        const char    *system_dir;
+        char          *path;
+        char          *str;
+        GKeyFile      *keyfile;
+
+        manager = gsp_app_manager_get ();
+        system_dir = gsp_app_manager_get_dir (manager,
+                                              app->priv->xdg_system_position);
+        g_object_unref (manager);
+        if (!system_dir) {
+                return FALSE;
+        }
+
+        path = g_build_filename (system_dir, app->priv->basename, NULL);
+
+        keyfile = g_key_file_new ();
+        if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL)) {
+                g_free (path);
+                g_key_file_free (keyfile);
+                return FALSE;
+        }
+
+        if (gsp_key_file_get_boolean (keyfile,
+                                      G_KEY_FILE_DESKTOP_KEY_HIDDEN,
+                                      FALSE) != app->priv->hidden ||
+            gsp_key_file_get_boolean (keyfile,
+                                      GSP_KEY_FILE_DESKTOP_KEY_AUTOSTART_ENABLED,
+                                      TRUE) != app->priv->enabled) {
+                g_free (path);
+                g_key_file_free (keyfile);
+                return FALSE;
+        }
+
+        str = gsp_key_file_get_locale_string (keyfile,
+                                              G_KEY_FILE_DESKTOP_KEY_NAME);
+        if (!_gsp_str_equal (str, app->priv->name)) {
+                g_free (str);
+                g_free (path);
+                g_key_file_free (keyfile);
+                return FALSE;
+        }
+
+        str = gsp_key_file_get_locale_string (keyfile,
+                                              G_KEY_FILE_DESKTOP_KEY_COMMENT);
+        if (!_gsp_str_equal (str, app->priv->comment)) {
+                g_free (str);
+                g_free (path);
+                g_key_file_free (keyfile);
+                return FALSE;
+        }
+
+        str = gsp_key_file_get_string (keyfile,
+                                       G_KEY_FILE_DESKTOP_KEY_EXEC);
+        if (!_gsp_str_equal (str, app->priv->exec)) {
+                g_free (str);
+                g_free (path);
+                g_key_file_free (keyfile);
+                return FALSE;
+        }
+
+        g_key_file_free (keyfile);
+
+        *system_path = path;
+
+        return TRUE;
+}
+
+static inline void
+_gsp_app_save_done_success (GspApp *app)
+{
+        app->priv->save_mask = 0;
+
+        if (app->priv->old_system_path) {
+                g_free (app->priv->old_system_path);
+                app->priv->old_system_path = NULL;
+        }
+}
+
+static gboolean
+_gsp_app_save (gpointer data)
+{
+        GspApp   *app;
+        char     *use_path;
+        GKeyFile *keyfile;
+        GError   *error;
+
+        app = GSP_APP (data);
+
+        /* first check if removing the data from the user dir and using the
+         * data from the system dir is enough -- this helps us keep clean the
+         * user config dir by removing unneeded files */
+        if (_gsp_app_user_equal_system (app, &use_path)) {
+                if (g_file_test (app->priv->path, G_FILE_TEST_EXISTS)) {
+                        g_remove (app->priv->path);
+                }
+
+                g_free (app->priv->path);
+                app->priv->path = use_path;
+
+                app->priv->xdg_position = app->priv->xdg_system_position;
+
+                _gsp_app_save_done_success (app);
+                return FALSE;
+        }
+
+        if (app->priv->old_system_path)
+                use_path = app->priv->old_system_path;
+        else
+                use_path = app->priv->path;
+
+        keyfile = g_key_file_new ();
+
+        error = NULL;
+        g_key_file_load_from_file (keyfile, use_path,
+                                   G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS,
+                                   &error);
+
+        if (error) {
+                g_error_free (error);
+                gsp_key_file_populate (keyfile);
+        }
+
+        if (app->priv->save_mask & GSP_ASP_SAVE_MASK_HIDDEN) {
+                gsp_key_file_set_boolean (keyfile,
+                                          G_KEY_FILE_DESKTOP_KEY_HIDDEN,
+                                          app->priv->hidden);
+        }
+
+        if (app->priv->save_mask & GSP_ASP_SAVE_MASK_ENABLED) {
+                gsp_key_file_set_boolean (keyfile,
+                                          GSP_KEY_FILE_DESKTOP_KEY_AUTOSTART_ENABLED,
+                                          app->priv->enabled);
+        }
+
+        if (app->priv->save_mask & GSP_ASP_SAVE_MASK_NAME) {
+                gsp_key_file_set_locale_string (keyfile,
+                                                G_KEY_FILE_DESKTOP_KEY_NAME,
+                                                app->priv->name);
+                gsp_key_file_ensure_C_key (keyfile, G_KEY_FILE_DESKTOP_KEY_NAME);
+        }
+
+        if (app->priv->save_mask & GSP_ASP_SAVE_MASK_COMMENT) {
+                gsp_key_file_set_locale_string (keyfile,
+                                                G_KEY_FILE_DESKTOP_KEY_COMMENT,
+                                                app->priv->comment);
+                gsp_key_file_ensure_C_key (keyfile, G_KEY_FILE_DESKTOP_KEY_COMMENT);
+        }
+
+        if (app->priv->save_mask & GSP_ASP_SAVE_MASK_EXEC) {
+                gsp_key_file_set_string (keyfile,
+                                         G_KEY_FILE_DESKTOP_KEY_EXEC,
+                                         app->priv->exec);
+        }
+
+        _gsp_ensure_user_autostart_dir ();
+        if (gsp_key_file_to_file (keyfile, app->priv->path, NULL)) {
+                app->priv->skip_next_monitor_event = TRUE;
+                _gsp_app_save_done_success (app);
+        } else {
+                g_warning ("Could not save %s file", app->priv->path);
+        }
+
+        g_key_file_free (keyfile);
+
+        app->priv->save_timeout = 0;
+        return FALSE;
+}
+
+static void
+_gsp_app_queue_save (GspApp *app)
+{
+        if (app->priv->save_timeout) {
+                g_source_remove (app->priv->save_timeout);
+                app->priv->save_timeout = 0;
+        }
+
+        /* if the file was not in the user directory, then we'll create a copy
+         * there */
+        if (app->priv->xdg_position != 0) {
+                app->priv->xdg_position = 0;
+
+                if (app->priv->old_system_path == NULL) {
+                        app->priv->old_system_path = app->priv->path;
+                        /* if old_system_path was not NULL, then it means we
+                         * tried to save and we failed; in that case, we want
+                         * to try again and use the old file as a basis again */
+                }
+
+                app->priv->path = g_build_filename (g_get_user_config_dir (),
+                                                    "autostart",
+                                                    app->priv->basename, NULL);
+        }
+
+        app->priv->save_timeout = g_timeout_add_seconds (GSP_APP_SAVE_DELAY,
+                                                         _gsp_app_save,
+                                                         app);
+}
+
+/*
+ * Accessors
+ */
+
+const char *
+gsp_app_get_basename (GspApp *app)
+{
+        g_return_val_if_fail (GSP_IS_APP (app), NULL);
+
+        return app->priv->basename;
+}
+
+gboolean
+gsp_app_get_hidden (GspApp *app)
+{
+        g_return_val_if_fail (GSP_IS_APP (app), FALSE);
+
+        return app->priv->hidden;
+}
+
+gboolean
+gsp_app_get_enabled (GspApp *app)
+{
+        g_return_val_if_fail (GSP_IS_APP (app), FALSE);
+
+        return app->priv->enabled;
+}
+
+void
+gsp_app_set_enabled (GspApp   *app,
+                     gboolean  enabled)
+{
+        g_return_if_fail (GSP_IS_APP (app));
+
+        if (enabled == app->priv->enabled) {
+                return;
+        }
+
+        app->priv->enabled = enabled;
+        app->priv->save_mask |= GSP_ASP_SAVE_MASK_ENABLED;
+
+        _gsp_app_queue_save (app);
+        _gsp_app_emit_changed (app);
+}
+
+const char *
+gsp_app_get_icon_name (GspApp *app)
+{
+        g_return_val_if_fail (GSP_IS_APP (app), NULL);
+
+        return app->priv->icon_name;
+}
+
+GdkPixbuf *
+gsp_app_get_pixbuf (GspApp *app)
+{
+        g_return_val_if_fail (GSP_IS_APP (app), NULL);
+
+        return app->priv->pixbuf;
+}
+
+unsigned int
+gsp_app_get_xdg_position (GspApp *app)
+{
+        g_return_val_if_fail (GSP_IS_APP (app), G_MAXUINT);
+
+        return app->priv->xdg_position;
+}
+
+unsigned int
+gsp_app_get_xdg_system_position (GspApp *app)
+{
+        g_return_val_if_fail (GSP_IS_APP (app), G_MAXUINT);
+
+        return app->priv->xdg_system_position;
+}
+
+void
+gsp_app_set_xdg_system_position (GspApp       *app,
+                                 unsigned int  position)
+{
+        g_return_if_fail (GSP_IS_APP (app));
+
+        app->priv->xdg_system_position = position;
+}
+
+const char *
+gsp_app_get_description (GspApp *app)
+{
+        g_return_val_if_fail (GSP_IS_APP (app), NULL);
+
+        return app->priv->description;
+}
+
+/*
+ * High-level edition
+ */
+
+void
+gsp_app_edit (GspApp    *app,
+              GtkWindow *parent)
+{
+        GtkWidget *dialog;
+        char       *name;
+        char       *exec;
+        char       *comment;
+        gboolean    changed;
+
+        g_return_if_fail (GSP_IS_APP (app));
+
+        dialog = gsm_app_dialog_new (app->priv->name,
+                                     app->priv->exec,
+                                     app->priv->comment);
+        gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
+
+        if (gsm_app_dialog_run (GSM_APP_DIALOG (dialog),
+                                &name, &exec, &comment)) {
+                changed = FALSE;
+
+                if (!_gsp_str_equal (name, app->priv->name)) {
+                        changed = TRUE;
+                        g_free (app->priv->name);
+                        app->priv->name = name;
+                        app->priv->save_mask |= GSP_ASP_SAVE_MASK_NAME;
+                } else {
+                        g_free (name);
+                }
+
+                if (!_gsp_str_equal (comment, app->priv->comment)) {
+                        changed = TRUE;
+                        g_free (app->priv->comment);
+                        app->priv->comment = comment;
+                        app->priv->save_mask |= GSP_ASP_SAVE_MASK_COMMENT;
+                } else {
+                        g_free (comment);
+                }
+
+                if (changed) {
+                        _gsp_app_update_description (app);
+                }
+
+                if (!_gsp_str_equal (exec, app->priv->exec)) {
+                        changed = TRUE;
+                        g_free (app->priv->exec);
+                        app->priv->exec = exec;
+                        app->priv->save_mask |= GSP_ASP_SAVE_MASK_EXEC;
+                } else {
+                        g_free (exec);
+                }
+
+                if (changed) {
+                        _gsp_app_queue_save (app);
+                        _gsp_app_emit_changed (app);
+                }
+        }
+}
+
+void
+gsp_app_delete (GspApp *app)
+{
+        g_return_if_fail (GSP_IS_APP (app));
+
+        if (app->priv->xdg_position == 0 &&
+            app->priv->xdg_system_position == G_MAXUINT) {
+                /* exists in user directory only */
+                if (app->priv->save_timeout) {
+                        g_source_remove (app->priv->save_timeout);
+                        app->priv->save_timeout = 0;
+                }
+
+                if (g_file_test (app->priv->path, G_FILE_TEST_EXISTS)) {
+                        g_remove (app->priv->path);
+                }
+
+                /* for extra safety */
+                app->priv->hidden = TRUE;
+                app->priv->save_mask |= GSP_ASP_SAVE_MASK_HIDDEN;
+
+                _gsp_app_emit_removed (app);
+        } else {
+                /* also exists in system directory, so we have to keep a file
+                 * in the user directory */
+                app->priv->hidden = TRUE;
+                app->priv->save_mask |= GSP_ASP_SAVE_MASK_HIDDEN;
+
+                _gsp_app_queue_save (app);
+                _gsp_app_emit_changed (app);
+        }
+}
+
+/*
+ * New autostart app
+ */
+
+void
+gsp_app_reload_at (GspApp       *app,
+                   const char   *path,
+                   unsigned int  xdg_position)
+{
+        g_return_if_fail (GSP_IS_APP (app));
+
+        app->priv->xdg_position = G_MAXUINT;
+        gsp_app_new (path, xdg_position);
+}
+
+static GdkPixbuf *
+_gsp_app_load_pixbuf (const char *path)
+{
+        int width, height;
+
+        if (gtk_icon_size_lookup (GSM_PROPERTIES_ICON_SIZE,
+                                  &width, &height)) {
+                return gdk_pixbuf_new_from_file_at_size (path,
+                                                         width, height, NULL);
+        }
+
+        return NULL;
+}
+
+GspApp *
+gsp_app_new (const char   *path,
+             unsigned int  xdg_position)
+{
+        GspAppManager *manager;
+        GspApp        *app;
+        GKeyFile      *keyfile;
+        char          *basename;
+        gboolean       new;
+        char          *icon;
+
+        basename = g_path_get_basename (path);
+
+        manager = gsp_app_manager_get ();
+        app = gsp_app_manager_find_app_with_basename (manager, basename);
+        g_object_unref (manager);
+
+        new = (app == NULL);
+
+        if (!new) {
+                if (app->priv->xdg_position == xdg_position) {
+                        if (app->priv->skip_next_monitor_event) {
+                                app->priv->skip_next_monitor_event = FALSE;
+                                return NULL;
+                        }
+                        /* else: the file got changed but not by us, we'll
+                         * update our data from disk */
+                }
+
+                if (app->priv->xdg_position < xdg_position ||
+                    app->priv->save_timeout != 0) {
+                        /* we don't really care about this file, since we
+                         * already have something with a higher priority, or
+                         * we're going to write something in the user config
+                         * anyway.
+                         * Note: xdg_position >= 1 so it's a system dir */
+                        app->priv->xdg_system_position = MIN (xdg_position,
+                                                              app->priv->xdg_system_position);
+                        return NULL;
+                }
+        }
+
+        keyfile = g_key_file_new ();
+        if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL)) {
+                g_key_file_free (keyfile);
+                g_free (basename);
+                return NULL;
+        }
+
+        if (new) {
+                app = g_object_new (GSP_TYPE_APP, NULL);
+                app->priv->basename = basename;
+        } else {
+                g_free (basename);
+                _gsp_app_free_reusable_data (app);
+        }
+
+        app->priv->path = g_strdup (path);
+
+        app->priv->hidden = gsp_key_file_get_boolean (keyfile,
+                                                      G_KEY_FILE_DESKTOP_KEY_HIDDEN,
+                                                      FALSE);
+        app->priv->enabled = gsp_key_file_get_boolean (keyfile,
+                                                       GSP_KEY_FILE_DESKTOP_KEY_AUTOSTART_ENABLED,
+                                                       TRUE);
+
+        app->priv->name = gsp_key_file_get_locale_string (keyfile,
+                                                          G_KEY_FILE_DESKTOP_KEY_NAME);
+        app->priv->exec = gsp_key_file_get_string (keyfile,
+                                                   G_KEY_FILE_DESKTOP_KEY_EXEC);
+        app->priv->comment = gsp_key_file_get_locale_string (keyfile,
+                                                             G_KEY_FILE_DESKTOP_KEY_COMMENT);
+
+        if (gsm_util_text_is_blank (app->priv->name)) {
+                g_free (app->priv->name);
+                app->priv->name = g_strdup (app->priv->exec);
+        }
+
+        icon = gsp_key_file_get_locale_string (keyfile,
+                                               G_KEY_FILE_DESKTOP_KEY_ICON);
+
+        if (icon) {
+                /* look at icon and see if it's a themed icon or not */
+                if (g_path_is_absolute (icon)) {
+                        app->priv->pixbuf = _gsp_app_load_pixbuf (icon);
+                        g_free (icon);
+                } else {
+                        app->priv->icon_name = icon;
+                }
+        }
+
+        g_key_file_free (keyfile);
+
+        _gsp_app_update_description (app);
+
+        if (xdg_position > 0) {
+                g_assert (xdg_position <= app->priv->xdg_system_position);
+                app->priv->xdg_system_position = xdg_position;
+        }
+        /* else we keep the old value (which is G_MAXUINT if it wasn't set) */
+        app->priv->xdg_position = xdg_position;
+
+        g_assert (!new || app->priv->save_timeout == 0);
+        app->priv->save_timeout = 0;
+        app->priv->old_system_path = NULL;
+        app->priv->skip_next_monitor_event = FALSE;
+
+        if (!new) {
+                _gsp_app_emit_changed (app);
+        }
+
+        return app;
+}
+
+static char *
+_gsp_find_free_basename (const char *suggested_basename)
+{
+        GspAppManager *manager;
+        char          *base_path;
+        char          *filename;
+        char          *basename;
+        int            i;
+
+        if (g_str_has_suffix (suggested_basename, ".desktop")) {
+                char *basename_no_ext;
+
+                basename_no_ext = g_strndup (suggested_basename,
+                                             strlen (suggested_basename) - strlen (".desktop"));
+                base_path = g_build_filename (g_get_user_config_dir (),
+                                              "autostart",
+                                              basename_no_ext, NULL);
+                g_free (basename_no_ext);
+        } else {
+                base_path = g_build_filename (g_get_user_config_dir (),
+                                              "autostart",
+                                              suggested_basename, NULL);
+        }
+
+        filename = g_strdup_printf ("%s.desktop", base_path);
+        basename = g_path_get_basename (filename);
+
+        manager = gsp_app_manager_get ();
+
+        i = 1;
+#define _GSP_FIND_MAX_TRY 10000
+        while (gsp_app_manager_find_app_with_basename (manager,
+                                                       basename) != NULL &&
+               g_file_test (filename, G_FILE_TEST_EXISTS) &&
+               i < _GSP_FIND_MAX_TRY) {
+                g_free (filename);
+                g_free (basename);
+
+                filename = g_strdup_printf ("%s-%d.desktop", base_path, i);
+                basename = g_path_get_basename (filename);
+
+                i++;
+        }
+
+        g_object_unref (manager);
+
+        g_free (base_path);
+        g_free (filename);
+
+        if (i == _GSP_FIND_MAX_TRY) {
+                g_free (basename);
+                return NULL;
+        }
+
+        return basename;
+}
+
+void
+gsp_app_create (const char *name,
+                const char *comment,
+                const char *exec)
+{
+        GspAppManager  *manager;
+        GspApp         *app;
+        char           *basename;
+        char          **argv;
+        int             argc;
+
+        g_return_if_fail (!gsm_util_text_is_blank (exec));
+
+        if (!g_shell_parse_argv (exec, &argc, &argv, NULL)) {
+                return;
+        }
+
+        basename = _gsp_find_free_basename (argv[0]);
+        g_strfreev (argv);
+        if (basename == NULL) {
+                return;
+        }
+
+        app = g_object_new (GSP_TYPE_APP, NULL);
+
+        app->priv->basename = basename;
+        app->priv->path = g_build_filename (g_get_user_config_dir (),
+                                            "autostart",
+                                            app->priv->basename, NULL);
+
+        app->priv->hidden = FALSE;
+        app->priv->enabled = TRUE;
+
+        if (!gsm_util_text_is_blank (name)) {
+                app->priv->name = g_strdup (name);
+        } else {
+                app->priv->name = g_strdup (exec);
+        }
+        app->priv->exec = g_strdup (exec);
+        app->priv->comment = g_strdup (comment);
+        app->priv->icon_name = NULL;
+
+        app->priv->pixbuf = NULL;
+        _gsp_app_update_description (app);
+
+        /* by definition */
+        app->priv->xdg_position = 0;
+        app->priv->xdg_system_position = G_MAXUINT;
+
+        app->priv->save_timeout = 0;
+        app->priv->save_mask |= GSP_ASP_SAVE_MASK_ALL;
+        app->priv->old_system_path = NULL;
+        app->priv->skip_next_monitor_event = FALSE;
+
+        _gsp_app_queue_save (app);
+
+        manager = gsp_app_manager_get ();
+        gsp_app_manager_add (manager, app);
+        g_object_unref (app);
+        g_object_unref (manager);
+}
+
+gboolean
+gsp_app_copy_desktop_file (const char *uri)
+{
+        GspAppManager *manager;
+        GspApp        *app;
+        GFile         *src_file;
+        char          *src_basename;
+        char          *dst_basename;
+        char          *dst_path;
+        GFile         *dst_file;
+        gboolean       changed;
+
+        g_return_val_if_fail (uri != NULL, FALSE);
+
+        src_file = g_file_new_for_uri (uri);
+        src_basename = g_file_get_basename (src_file);
+
+        if (src_basename == NULL) {
+                g_object_unref (src_file);
+                return FALSE;
+        }
+
+        dst_basename = _gsp_find_free_basename (src_basename);
+        g_free (src_basename);
+
+        if (dst_basename == NULL) {
+                g_object_unref (src_file);
+                return FALSE;
+        }
+
+        dst_path = g_build_filename (g_get_user_config_dir (),
+                                     "autostart",
+                                     dst_basename, NULL);
+        g_free (dst_basename);
+
+        dst_file = g_file_new_for_path (dst_path);
+
+        _gsp_ensure_user_autostart_dir ();
+        if (!g_file_copy (src_file, dst_file, G_FILE_COPY_NONE,
+                          NULL, NULL, NULL, NULL)) {
+                g_object_unref (src_file);
+                g_object_unref (dst_file);
+                g_free (dst_path);
+                return FALSE;
+        }
+
+        g_object_unref (src_file);
+        g_object_unref (dst_file);
+
+        app = gsp_app_new (dst_path, 0);
+        if (!app) {
+                g_remove (dst_path);
+                g_free (dst_path);
+                return FALSE;
+        }
+
+        g_free (dst_path);
+
+        changed = FALSE;
+        if (app->priv->hidden) {
+                changed = TRUE;
+                app->priv->hidden = FALSE;
+                app->priv->save_mask |= GSP_ASP_SAVE_MASK_HIDDEN;
+        }
+
+        if (!app->priv->enabled) {
+                changed = TRUE;
+                app->priv->enabled = TRUE;
+                app->priv->save_mask |= GSP_ASP_SAVE_MASK_ENABLED;
+        }
+
+        if (changed) {
+                _gsp_app_queue_save (app);
+        }
+
+        manager = gsp_app_manager_get ();
+        gsp_app_manager_add (manager, app);
+        g_object_unref (app);
+        g_object_unref (manager);
+
+        return TRUE;
+}
diff --git a/capplet/gsp-app.h b/capplet/gsp-app.h
new file mode 100644
index 0000000..9221147
--- /dev/null
+++ b/capplet/gsp-app.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 1999 Free Software Foundation, Inc.
+ * Copyright (C) 2007, 2009 Vincent Untz.
+ * Copyright (C) 2008 Lucas Rocha.
+ * Copyright (C) 2008 William Jon McCann <jmccann redhat com>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GSP_APP_H
+#define __GSP_APP_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GSP_TYPE_APP            (gsp_app_get_type ())
+#define GSP_APP(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSP_TYPE_APP, GspApp))
+#define GSP_APP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GSP_TYPE_APP, GspAppClass))
+#define GSP_IS_APP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSP_TYPE_APP))
+#define GSP_IS_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSP_TYPE_APP))
+#define GSP_APP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GSP_TYPE_APP, GspAppClass))
+
+typedef struct _GspApp        GspApp;
+typedef struct _GspAppClass   GspAppClass;
+
+typedef struct _GspAppPrivate GspAppPrivate;
+
+struct _GspAppClass
+{
+        GObjectClass parent_class;
+
+        void (* changed) (GspApp *app);
+        void (* removed) (GspApp *app);
+};
+
+struct _GspApp
+{
+        GObject parent_instance;
+
+        GspAppPrivate *priv;
+};
+
+GType            gsp_app_get_type          (void);
+
+GspApp          *gsp_app_new               (const char   *path,
+                                            unsigned int  xdg_position);
+void             gsp_app_create            (const char   *name,
+                                            const char   *comment,
+                                            const char   *exec);
+
+gboolean         gsp_app_copy_desktop_file (const char   *uri);
+
+void             gsp_app_reload_at         (GspApp       *app,
+                                            const char   *path,
+                                            unsigned int  xdg_position);
+
+void             gsp_app_edit              (GspApp       *app,
+                                            GtkWindow    *parent);
+void             gsp_app_delete            (GspApp       *app);
+
+const char      *gsp_app_get_basename      (GspApp       *app);
+
+gboolean         gsp_app_get_hidden        (GspApp       *app);
+
+gboolean         gsp_app_get_enabled       (GspApp       *app);
+void             gsp_app_set_enabled       (GspApp       *app,
+                                            gboolean      enabled);
+
+const char      *gsp_app_get_description   (GspApp       *app);
+const char      *gsp_app_get_icon_name     (GspApp       *app);
+GdkPixbuf       *gsp_app_get_pixbuf        (GspApp       *app);
+
+unsigned int     gsp_app_get_xdg_position  (GspApp       *app);
+unsigned int     gsp_app_get_xdg_system_position  (GspApp       *app);
+void             gsp_app_set_xdg_system_position  (GspApp       *app,
+						   unsigned int  position);
+
+G_END_DECLS
+
+#endif /* __GSP_APP_H */
diff --git a/capplet/gsp-keyfile.c b/capplet/gsp-keyfile.c
new file mode 100644
index 0000000..fb28230
--- /dev/null
+++ b/capplet/gsp-keyfile.c
@@ -0,0 +1,149 @@
+/*
+ * gsp-keyfile.c: GKeyFile extensions
+ *
+ * Copyright (C) 2008, 2009 Novell, Inc.
+ *
+ * Based on code from panel-keyfile.c (from gnome-panel)
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Authors:
+ *        Vincent Untz <vuntz gnome org>
+ */
+
+#include <string.h>
+
+#include <glib.h>
+
+#include "gsp-keyfile.h"
+
+void
+gsp_key_file_populate (GKeyFile *keyfile)
+{
+        gsp_key_file_set_string (keyfile,
+                                 G_KEY_FILE_DESKTOP_KEY_TYPE,
+                                 "Application");
+
+        gsp_key_file_set_string (keyfile,
+                                 G_KEY_FILE_DESKTOP_KEY_EXEC,
+                                 "/bin/false");
+}
+
+//FIXME: kill this when bug #309224 is fixed
+gboolean
+gsp_key_file_to_file (GKeyFile     *keyfile,
+                      const gchar  *path,
+                      GError      **error)
+{
+        GError  *write_error;
+        gchar   *data;
+        gsize    length;
+        gboolean res;
+
+        g_return_val_if_fail (keyfile != NULL, FALSE);
+        g_return_val_if_fail (path != NULL, FALSE);
+
+        write_error = NULL;
+        data = g_key_file_to_data (keyfile, &length, &write_error);
+
+        if (write_error) {
+                g_propagate_error (error, write_error);
+                return FALSE;
+        }
+
+        res = g_file_set_contents (path, data, length, &write_error);
+        g_free (data);
+
+        if (write_error) {
+                g_propagate_error (error, write_error);
+                return FALSE;
+        }
+
+        return res;
+}
+
+gboolean
+gsp_key_file_get_boolean (GKeyFile    *keyfile,
+                          const gchar *key,
+                          gboolean     default_value)
+{
+        GError   *error;
+        gboolean  retval;
+
+        error = NULL;
+        retval = g_key_file_get_boolean (keyfile, G_KEY_FILE_DESKTOP_GROUP,
+                                         key, &error);
+        if (error != NULL) {
+                retval = default_value;
+                g_error_free (error);
+        }
+
+        return retval;
+}
+
+void
+gsp_key_file_set_locale_string (GKeyFile    *keyfile,
+                                const gchar *key,
+                                const gchar *value)
+{
+        const char         *locale;
+        const char * const *langs_pointer;
+        int                 i;
+
+        if (value == NULL) {
+                value = "";
+        }
+
+        locale = NULL;
+        langs_pointer = g_get_language_names ();
+        for (i = 0; langs_pointer[i] != NULL; i++) {
+                /* find first without encoding  */
+                if (strchr (langs_pointer[i], '.') == NULL) {
+                        locale = langs_pointer[i]; 
+                        break;
+                }
+        }
+
+        if (locale != NULL) {
+                g_key_file_set_locale_string (keyfile, G_KEY_FILE_DESKTOP_GROUP,
+                                              key, locale, value);
+        } else {
+                g_key_file_set_string (keyfile, G_KEY_FILE_DESKTOP_GROUP,
+                                       key, value);
+        }
+}
+
+void
+gsp_key_file_ensure_C_key (GKeyFile   *keyfile,
+                           const char *key)
+{
+        char *C_value;
+        char *buffer;
+
+        /* Make sure we set the "C" locale strings to the terms we set here.
+         * This is so that if the user logs into another locale they get their
+         * own description there rather then empty. It is not the C locale
+         * however, but the user created this entry herself so it's OK */
+        C_value = gsp_key_file_get_string (keyfile, key);
+        if (C_value == NULL || C_value [0] == '\0') {
+                buffer = gsp_key_file_get_locale_string (keyfile, key);
+                if (buffer) {
+                        gsp_key_file_set_string (keyfile, key, buffer);
+                        g_free (buffer);
+                }
+        }
+        g_free (C_value);
+}
diff --git a/capplet/gsp-keyfile.h b/capplet/gsp-keyfile.h
new file mode 100644
index 0000000..51a56e0
--- /dev/null
+++ b/capplet/gsp-keyfile.h
@@ -0,0 +1,63 @@
+/*
+ * gsp-keyfile.h: GKeyFile extensions
+ *
+ * Copyright (C) 2008, 2009 Novell, Inc.
+ *
+ * Based on code from panel-keyfile.h (from gnome-panel)
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Authors:
+ *        Vincent Untz <vuntz gnome org>
+ */
+
+#ifndef GSP_KEYFILE_H
+#define GSP_KEYFILE_H
+
+#include "glib.h"
+
+G_BEGIN_DECLS
+
+#define GSP_KEY_FILE_DESKTOP_KEY_AUTOSTART_ENABLED "X-GNOME-Autostart-enabled"
+
+void      gsp_key_file_populate        (GKeyFile *keyfile);
+
+gboolean  gsp_key_file_to_file         (GKeyFile       *keyfile,
+                                        const gchar    *path,
+                                        GError        **error);
+
+gboolean gsp_key_file_get_boolean      (GKeyFile       *keyfile,
+                                        const gchar    *key,
+                                        gboolean        default_value);
+#define gsp_key_file_get_string(key_file, key) \
+         g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, key, NULL)
+#define gsp_key_file_get_locale_string(key_file, key) \
+         g_key_file_get_locale_string(key_file, G_KEY_FILE_DESKTOP_GROUP, key, NULL, NULL)
+
+#define gsp_key_file_set_boolean(key_file, key, value) \
+         g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, key, value)
+#define gsp_key_file_set_string(key_file, key, value) \
+         g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP, key, value)
+void    gsp_key_file_set_locale_string (GKeyFile    *keyfile,
+                                        const gchar *key,
+                                        const gchar *value);
+
+void gsp_key_file_ensure_C_key         (GKeyFile   *keyfile,
+                                        const char *key);
+
+G_END_DECLS
+
+#endif /* GSP_KEYFILE_H */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]