You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4147 lines
137 KiB
4147 lines
137 KiB
From e18747f7c52f7d7d56f7d9490f934d59d84f8af9 Mon Sep 17 00:00:00 2001 |
|
From: Ray Strode <rstrode@redhat.com> |
|
Date: Fri, 8 May 2015 16:24:59 -0400 |
|
Subject: [PATCH 02/19] Revert "Remove gnome-session-properties" |
|
|
|
This reverts commit ea285af9962313ee2675fff27d3a852bb61e936a. |
|
--- |
|
Makefile.am | 1 + |
|
capplet/Makefile.am | 31 + |
|
capplet/gsm-app-dialog.c | 540 +++++++++++++ |
|
capplet/gsm-app-dialog.h | 66 ++ |
|
capplet/gsm-properties-dialog.c | 774 +++++++++++++++++++ |
|
capplet/gsm-properties-dialog.h | 57 ++ |
|
capplet/gsp-app-manager.c | 593 ++++++++++++++ |
|
capplet/gsp-app-manager.h | 81 ++ |
|
capplet/gsp-app.c | 1102 +++++++++++++++++++++++++++ |
|
capplet/gsp-app.h | 108 +++ |
|
capplet/gsp-keyfile.c | 201 +++++ |
|
capplet/gsp-keyfile.h | 65 ++ |
|
capplet/main.c | 108 +++ |
|
configure.ac | 2 + |
|
data/Makefile.am | 8 +- |
|
data/gnome-session-properties.desktop.in.in | 15 + |
|
doc/man/Makefile.am | 1 + |
|
doc/man/gnome-session-properties.1 | 24 + |
|
po/POTFILES.in | 5 + |
|
19 files changed, 3781 insertions(+), 1 deletion(-) |
|
create mode 100644 capplet/Makefile.am |
|
create mode 100644 capplet/gsm-app-dialog.c |
|
create mode 100644 capplet/gsm-app-dialog.h |
|
create mode 100644 capplet/gsm-properties-dialog.c |
|
create mode 100644 capplet/gsm-properties-dialog.h |
|
create mode 100644 capplet/gsp-app-manager.c |
|
create mode 100644 capplet/gsp-app-manager.h |
|
create mode 100644 capplet/gsp-app.c |
|
create mode 100644 capplet/gsp-app.h |
|
create mode 100644 capplet/gsp-keyfile.c |
|
create mode 100644 capplet/gsp-keyfile.h |
|
create mode 100644 capplet/main.c |
|
create mode 100644 data/gnome-session-properties.desktop.in.in |
|
create mode 100644 doc/man/gnome-session-properties.1 |
|
|
|
diff --git a/Makefile.am b/Makefile.am |
|
index 6560a4ec..6b6a47bf 100644 |
|
--- a/Makefile.am |
|
+++ b/Makefile.am |
|
@@ -1,32 +1,33 @@ |
|
SUBDIRS = \ |
|
gnome-session \ |
|
+ capplet \ |
|
tools \ |
|
data \ |
|
doc \ |
|
po |
|
|
|
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} |
|
|
|
EXTRA_DIST = \ |
|
HACKING \ |
|
MAINTAINERS |
|
|
|
MAINTAINERCLEANFILES = \ |
|
$(srcdir)/INSTALL \ |
|
$(srcdir)/aclocal.m4 \ |
|
$(srcdir)/config.guess \ |
|
$(srcdir)/config.h.in \ |
|
$(srcdir)/config.sub \ |
|
$(srcdir)/depcomp \ |
|
$(srcdir)/install-sh \ |
|
$(srcdir)/ltmain.sh \ |
|
$(srcdir)/missing \ |
|
$(srcdir)/mkinstalldirs \ |
|
$(srcdir)/configure \ |
|
$(srcdir)/m4/intltool.m4 \ |
|
`find "$(srcdir)" -type f -name Makefile.in -print` |
|
|
|
CHANGELOG_GIT_RANGE = GNOME_SESSION_2_26_1.. |
|
dist-hook: |
|
$(AM_V_GEN)if test -d "$(srcdir)/.git"; then \ |
|
( echo '# Generated by Makefile. Do not edit.'; echo; \ |
|
diff --git a/capplet/Makefile.am b/capplet/Makefile.am |
|
new file mode 100644 |
|
index 00000000..c2e563cf |
|
--- /dev/null |
|
+++ b/capplet/Makefile.am |
|
@@ -0,0 +1,31 @@ |
|
+bin_PROGRAMS = gnome-session-properties |
|
+ |
|
+AM_CPPFLAGS = \ |
|
+ $(SESSION_PROPERTIES_CFLAGS) \ |
|
+ $(GCONF_CFLAGS) \ |
|
+ -I$(top_srcdir)/gnome-session \ |
|
+ -DLOCALE_DIR=\""$(datadir)/locale"\" \ |
|
+ -DGTKBUILDER_DIR=\""$(pkgdatadir)"\" \ |
|
+ $(DISABLE_DEPRECATED_CFLAGS) |
|
+ |
|
+AM_CFLAGS = $(WARN_CFLAGS) |
|
+ |
|
+gnome_session_properties_SOURCES = \ |
|
+ main.c \ |
|
+ gsm-properties-dialog.h \ |
|
+ 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 |
|
+ |
|
+gnome_session_properties_LDADD = \ |
|
+ $(SESSION_PROPERTIES_LIBS) \ |
|
+ $(top_builddir)/gnome-session/libgsmutil.la \ |
|
+ $(GCONF_LIBS) |
|
+ |
|
+-include $(top_srcdir)/git.mk |
|
diff --git a/capplet/gsm-app-dialog.c b/capplet/gsm-app-dialog.c |
|
new file mode 100644 |
|
index 00000000..e7369dda |
|
--- /dev/null |
|
+++ b/capplet/gsm-app-dialog.c |
|
@@ -0,0 +1,540 @@ |
|
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- |
|
+ * |
|
+ * 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 "config.h" |
|
+ |
|
+#include <glib.h> |
|
+#include <glib/gi18n.h> |
|
+#include <gtk/gtk.h> |
|
+ |
|
+#include "gsm-util.h" |
|
+ |
|
+#include "gsm-app-dialog.h" |
|
+ |
|
+#define GTKBUILDER_FILE "session-properties.ui" |
|
+ |
|
+#define CAPPLET_NAME_ENTRY_WIDGET_NAME "session_properties_name_entry" |
|
+#define CAPPLET_COMMAND_ENTRY_WIDGET_NAME "session_properties_command_entry" |
|
+#define CAPPLET_COMMENT_ENTRY_WIDGET_NAME "session_properties_comment_entry" |
|
+#define CAPPLET_BROWSE_WIDGET_NAME "session_properties_browse_button" |
|
+ |
|
+ |
|
+#define GSM_APP_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_APP_DIALOG, GsmAppDialogPrivate)) |
|
+ |
|
+struct GsmAppDialogPrivate |
|
+{ |
|
+ GtkWidget *name_entry; |
|
+ GtkWidget *command_entry; |
|
+ GtkWidget *comment_entry; |
|
+ GtkWidget *browse_button; |
|
+ char *name; |
|
+ char *command; |
|
+ char *comment; |
|
+}; |
|
+ |
|
+static void gsm_app_dialog_class_init (GsmAppDialogClass *klass); |
|
+static void gsm_app_dialog_init (GsmAppDialog *app_dialog); |
|
+static void gsm_app_dialog_finalize (GObject *object); |
|
+ |
|
+enum { |
|
+ PROP_0, |
|
+ PROP_NAME, |
|
+ PROP_COMMAND, |
|
+ PROP_COMMENT |
|
+}; |
|
+ |
|
+G_DEFINE_TYPE (GsmAppDialog, gsm_app_dialog, GTK_TYPE_DIALOG) |
|
+ |
|
+static char * |
|
+make_exec_uri (const char *exec) |
|
+{ |
|
+ GString *str; |
|
+ const char *c; |
|
+ |
|
+ if (exec == NULL) { |
|
+ return g_strdup (""); |
|
+ } |
|
+ |
|
+ if (strchr (exec, ' ') == NULL) { |
|
+ return g_strdup (exec); |
|
+ } |
|
+ |
|
+ str = g_string_new_len (NULL, strlen (exec)); |
|
+ |
|
+ str = g_string_append_c (str, '"'); |
|
+ for (c = exec; *c != '\0'; c++) { |
|
+ /* FIXME: GKeyFile will add an additional backslach so we'll |
|
+ * end up with toto\\" instead of toto\" |
|
+ * We could use g_key_file_set_value(), but then we don't |
|
+ * benefit from the other escaping that glib is doing... |
|
+ */ |
|
+ if (*c == '"') { |
|
+ str = g_string_append (str, "\\\""); |
|
+ } else { |
|
+ str = g_string_append_c (str, *c); |
|
+ } |
|
+ } |
|
+ str = g_string_append_c (str, '"'); |
|
+ |
|
+ return g_string_free (str, FALSE); |
|
+} |
|
+ |
|
+static void |
|
+on_browse_button_clicked (GtkWidget *widget, |
|
+ GsmAppDialog *dialog) |
|
+{ |
|
+ GtkWidget *chooser; |
|
+ int response; |
|
+ |
|
+ chooser = gtk_file_chooser_dialog_new ("", |
|
+ GTK_WINDOW (dialog), |
|
+ GTK_FILE_CHOOSER_ACTION_OPEN, |
|
+ GTK_STOCK_CANCEL, |
|
+ GTK_RESPONSE_CANCEL, |
|
+ GTK_STOCK_OPEN, |
|
+ GTK_RESPONSE_ACCEPT, |
|
+ NULL); |
|
+ |
|
+ gtk_window_set_transient_for (GTK_WINDOW (chooser), |
|
+ GTK_WINDOW (dialog)); |
|
+ |
|
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (chooser), TRUE); |
|
+ |
|
+ gtk_window_set_title (GTK_WINDOW (chooser), _("Select Command")); |
|
+ |
|
+ gtk_widget_show (chooser); |
|
+ |
|
+ response = gtk_dialog_run (GTK_DIALOG (chooser)); |
|
+ |
|
+ if (response == GTK_RESPONSE_ACCEPT) { |
|
+ char *text; |
|
+ char *uri; |
|
+ |
|
+ text = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser)); |
|
+ |
|
+ uri = make_exec_uri (text); |
|
+ |
|
+ g_free (text); |
|
+ |
|
+ gtk_entry_set_text (GTK_ENTRY (dialog->priv->command_entry), uri); |
|
+ |
|
+ g_free (uri); |
|
+ } |
|
+ |
|
+ gtk_widget_destroy (chooser); |
|
+} |
|
+ |
|
+static void |
|
+on_entry_activate (GtkEntry *entry, |
|
+ GsmAppDialog *dialog) |
|
+{ |
|
+ gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); |
|
+} |
|
+ |
|
+static void |
|
+setup_dialog (GsmAppDialog *dialog) |
|
+{ |
|
+ GtkWidget *content_area; |
|
+ GtkWidget *widget; |
|
+ GtkBuilder *xml; |
|
+ GError *error; |
|
+ |
|
+ xml = gtk_builder_new (); |
|
+ gtk_builder_set_translation_domain (xml, GETTEXT_PACKAGE); |
|
+ |
|
+ error = NULL; |
|
+ if (!gtk_builder_add_from_file (xml, |
|
+ GTKBUILDER_DIR "/" GTKBUILDER_FILE, |
|
+ &error)) { |
|
+ if (error) { |
|
+ g_warning ("Could not load capplet UI file: %s", |
|
+ error->message); |
|
+ g_error_free (error); |
|
+ } else { |
|
+ g_warning ("Could not load capplet UI file."); |
|
+ } |
|
+ } |
|
+ |
|
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); |
|
+ widget = GTK_WIDGET (gtk_builder_get_object (xml, "main-table")); |
|
+ gtk_container_add (GTK_CONTAINER (content_area), widget); |
|
+ |
|
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 6); |
|
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "session-properties"); |
|
+ |
|
+ g_object_set (dialog, |
|
+ "allow-shrink", FALSE, |
|
+ "allow-grow", FALSE, |
|
+ NULL); |
|
+ |
|
+ gtk_dialog_add_button (GTK_DIALOG (dialog), |
|
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); |
|
+ |
|
+ if (dialog->priv->name == NULL |
|
+ && dialog->priv->command == NULL |
|
+ && dialog->priv->comment == NULL) { |
|
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Add Startup Program")); |
|
+ gtk_dialog_add_button (GTK_DIALOG (dialog), |
|
+ GTK_STOCK_ADD, GTK_RESPONSE_OK); |
|
+ } else { |
|
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Edit Startup Program")); |
|
+ gtk_dialog_add_button (GTK_DIALOG (dialog), |
|
+ GTK_STOCK_SAVE, GTK_RESPONSE_OK); |
|
+ } |
|
+ |
|
+ dialog->priv->name_entry = GTK_WIDGET (gtk_builder_get_object (xml, CAPPLET_NAME_ENTRY_WIDGET_NAME)); |
|
+ g_signal_connect (dialog->priv->name_entry, |
|
+ "activate", |
|
+ G_CALLBACK (on_entry_activate), |
|
+ dialog); |
|
+ if (dialog->priv->name != NULL) { |
|
+ gtk_entry_set_text (GTK_ENTRY (dialog->priv->name_entry), dialog->priv->name); |
|
+ } |
|
+ |
|
+ dialog->priv->browse_button = GTK_WIDGET (gtk_builder_get_object (xml, CAPPLET_BROWSE_WIDGET_NAME)); |
|
+ g_signal_connect (dialog->priv->browse_button, |
|
+ "clicked", |
|
+ G_CALLBACK (on_browse_button_clicked), |
|
+ dialog); |
|
+ |
|
+ dialog->priv->command_entry = GTK_WIDGET (gtk_builder_get_object (xml, CAPPLET_COMMAND_ENTRY_WIDGET_NAME)); |
|
+ g_signal_connect (dialog->priv->command_entry, |
|
+ "activate", |
|
+ G_CALLBACK (on_entry_activate), |
|
+ dialog); |
|
+ if (dialog->priv->command != NULL) { |
|
+ gtk_entry_set_text (GTK_ENTRY (dialog->priv->command_entry), dialog->priv->command); |
|
+ } |
|
+ |
|
+ dialog->priv->comment_entry = GTK_WIDGET (gtk_builder_get_object (xml, CAPPLET_COMMENT_ENTRY_WIDGET_NAME)); |
|
+ g_signal_connect (dialog->priv->comment_entry, |
|
+ "activate", |
|
+ G_CALLBACK (on_entry_activate), |
|
+ dialog); |
|
+ if (dialog->priv->comment != NULL) { |
|
+ gtk_entry_set_text (GTK_ENTRY (dialog->priv->comment_entry), dialog->priv->comment); |
|
+ } |
|
+ |
|
+ if (xml != NULL) { |
|
+ g_object_unref (xml); |
|
+ } |
|
+} |
|
+ |
|
+static GObject * |
|
+gsm_app_dialog_constructor (GType type, |
|
+ guint n_construct_app, |
|
+ GObjectConstructParam *construct_app) |
|
+{ |
|
+ GsmAppDialog *dialog; |
|
+ |
|
+ dialog = GSM_APP_DIALOG (G_OBJECT_CLASS (gsm_app_dialog_parent_class)->constructor (type, |
|
+ n_construct_app, |
|
+ construct_app)); |
|
+ |
|
+ setup_dialog (dialog); |
|
+ |
|
+ gtk_widget_show_all (GTK_WIDGET (dialog)); |
|
+ |
|
+ return G_OBJECT (dialog); |
|
+} |
|
+ |
|
+static void |
|
+gsm_app_dialog_dispose (GObject *object) |
|
+{ |
|
+ GsmAppDialog *dialog; |
|
+ |
|
+ g_return_if_fail (object != NULL); |
|
+ g_return_if_fail (GSM_IS_APP_DIALOG (object)); |
|
+ |
|
+ dialog = GSM_APP_DIALOG (object); |
|
+ |
|
+ g_free (dialog->priv->name); |
|
+ dialog->priv->name = NULL; |
|
+ g_free (dialog->priv->command); |
|
+ dialog->priv->command = NULL; |
|
+ g_free (dialog->priv->comment); |
|
+ dialog->priv->comment = NULL; |
|
+ |
|
+ G_OBJECT_CLASS (gsm_app_dialog_parent_class)->dispose (object); |
|
+} |
|
+ |
|
+static void |
|
+gsm_app_dialog_set_name (GsmAppDialog *dialog, |
|
+ const char *name) |
|
+{ |
|
+ g_return_if_fail (GSM_IS_APP_DIALOG (dialog)); |
|
+ |
|
+ g_free (dialog->priv->name); |
|
+ |
|
+ dialog->priv->name = g_strdup (name); |
|
+ g_object_notify (G_OBJECT (dialog), "name"); |
|
+} |
|
+ |
|
+static void |
|
+gsm_app_dialog_set_command (GsmAppDialog *dialog, |
|
+ const char *name) |
|
+{ |
|
+ g_return_if_fail (GSM_IS_APP_DIALOG (dialog)); |
|
+ |
|
+ g_free (dialog->priv->command); |
|
+ |
|
+ dialog->priv->command = g_strdup (name); |
|
+ g_object_notify (G_OBJECT (dialog), "command"); |
|
+} |
|
+ |
|
+static void |
|
+gsm_app_dialog_set_comment (GsmAppDialog *dialog, |
|
+ const char *name) |
|
+{ |
|
+ g_return_if_fail (GSM_IS_APP_DIALOG (dialog)); |
|
+ |
|
+ g_free (dialog->priv->comment); |
|
+ |
|
+ dialog->priv->comment = g_strdup (name); |
|
+ g_object_notify (G_OBJECT (dialog), "comment"); |
|
+} |
|
+ |
|
+const char * |
|
+gsm_app_dialog_get_name (GsmAppDialog *dialog) |
|
+{ |
|
+ g_return_val_if_fail (GSM_IS_APP_DIALOG (dialog), NULL); |
|
+ return gtk_entry_get_text (GTK_ENTRY (dialog->priv->name_entry)); |
|
+} |
|
+ |
|
+const char * |
|
+gsm_app_dialog_get_command (GsmAppDialog *dialog) |
|
+{ |
|
+ g_return_val_if_fail (GSM_IS_APP_DIALOG (dialog), NULL); |
|
+ return gtk_entry_get_text (GTK_ENTRY (dialog->priv->command_entry)); |
|
+} |
|
+ |
|
+const char * |
|
+gsm_app_dialog_get_comment (GsmAppDialog *dialog) |
|
+{ |
|
+ g_return_val_if_fail (GSM_IS_APP_DIALOG (dialog), NULL); |
|
+ return gtk_entry_get_text (GTK_ENTRY (dialog->priv->comment_entry)); |
|
+} |
|
+ |
|
+static void |
|
+gsm_app_dialog_set_property (GObject *object, |
|
+ guint prop_id, |
|
+ const GValue *value, |
|
+ GParamSpec *pspec) |
|
+{ |
|
+ GsmAppDialog *dialog = GSM_APP_DIALOG (object); |
|
+ |
|
+ switch (prop_id) { |
|
+ case PROP_NAME: |
|
+ gsm_app_dialog_set_name (dialog, g_value_get_string (value)); |
|
+ break; |
|
+ case PROP_COMMAND: |
|
+ gsm_app_dialog_set_command (dialog, g_value_get_string (value)); |
|
+ break; |
|
+ case PROP_COMMENT: |
|
+ gsm_app_dialog_set_comment (dialog, g_value_get_string (value)); |
|
+ break; |
|
+ default: |
|
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
+ break; |
|
+ } |
|
+} |
|
+ |
|
+static void |
|
+gsm_app_dialog_get_property (GObject *object, |
|
+ guint prop_id, |
|
+ GValue *value, |
|
+ GParamSpec *pspec) |
|
+{ |
|
+ GsmAppDialog *dialog = GSM_APP_DIALOG (object); |
|
+ |
|
+ switch (prop_id) { |
|
+ case PROP_NAME: |
|
+ g_value_set_string (value, dialog->priv->name); |
|
+ break; |
|
+ case PROP_COMMAND: |
|
+ g_value_set_string (value, dialog->priv->command); |
|
+ break; |
|
+ case PROP_COMMENT: |
|
+ g_value_set_string (value, dialog->priv->comment); |
|
+ break; |
|
+ default: |
|
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
+ break; |
|
+ } |
|
+} |
|
+ |
|
+static void |
|
+gsm_app_dialog_class_init (GsmAppDialogClass *klass) |
|
+{ |
|
+ GObjectClass *object_class = G_OBJECT_CLASS (klass); |
|
+ |
|
+ object_class->get_property = gsm_app_dialog_get_property; |
|
+ object_class->set_property = gsm_app_dialog_set_property; |
|
+ object_class->constructor = gsm_app_dialog_constructor; |
|
+ object_class->dispose = gsm_app_dialog_dispose; |
|
+ object_class->finalize = gsm_app_dialog_finalize; |
|
+ |
|
+ g_object_class_install_property (object_class, |
|
+ PROP_NAME, |
|
+ g_param_spec_string ("name", |
|
+ "name", |
|
+ "name", |
|
+ NULL, |
|
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); |
|
+ g_object_class_install_property (object_class, |
|
+ PROP_COMMAND, |
|
+ g_param_spec_string ("command", |
|
+ "command", |
|
+ "command", |
|
+ NULL, |
|
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); |
|
+ g_object_class_install_property (object_class, |
|
+ PROP_COMMENT, |
|
+ g_param_spec_string ("comment", |
|
+ "comment", |
|
+ "comment", |
|
+ NULL, |
|
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); |
|
+ |
|
+ g_type_class_add_private (klass, sizeof (GsmAppDialogPrivate)); |
|
+} |
|
+ |
|
+static void |
|
+gsm_app_dialog_init (GsmAppDialog *dialog) |
|
+{ |
|
+ |
|
+ dialog->priv = GSM_APP_DIALOG_GET_PRIVATE (dialog); |
|
+} |
|
+ |
|
+static void |
|
+gsm_app_dialog_finalize (GObject *object) |
|
+{ |
|
+ GsmAppDialog *dialog; |
|
+ |
|
+ g_return_if_fail (object != NULL); |
|
+ g_return_if_fail (GSM_IS_APP_DIALOG (object)); |
|
+ |
|
+ dialog = GSM_APP_DIALOG (object); |
|
+ |
|
+ g_return_if_fail (dialog->priv != NULL); |
|
+ |
|
+ G_OBJECT_CLASS (gsm_app_dialog_parent_class)->finalize (object); |
|
+} |
|
+ |
|
+GtkWidget * |
|
+gsm_app_dialog_new (const char *name, |
|
+ const char *command, |
|
+ const char *comment) |
|
+{ |
|
+ GObject *object; |
|
+ |
|
+ object = g_object_new (GSM_TYPE_APP_DIALOG, |
|
+ "name", name, |
|
+ "command", command, |
|
+ "comment", comment, |
|
+ NULL); |
|
+ |
|
+ 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; |
|
+ |
|
+ 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; |
|
+ } |
|
+ |
|
+ 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 |
|
new file mode 100644 |
|
index 00000000..ced0628c |
|
--- /dev/null |
|
+++ b/capplet/gsm-app-dialog.h |
|
@@ -0,0 +1,66 @@ |
|
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- |
|
+ * |
|
+ * 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 __GSM_APP_DIALOG_H |
|
+#define __GSM_APP_DIALOG_H |
|
+ |
|
+#include <glib-object.h> |
|
+#include <gtk/gtk.h> |
|
+ |
|
+G_BEGIN_DECLS |
|
+ |
|
+#define GSM_TYPE_APP_DIALOG (gsm_app_dialog_get_type ()) |
|
+#define GSM_APP_DIALOG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GSM_TYPE_APP_DIALOG, GsmAppDialog)) |
|
+#define GSM_APP_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GSM_TYPE_APP_DIALOG, GsmAppDialogClass)) |
|
+#define GSM_IS_APP_DIALOG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSM_TYPE_APP_DIALOG)) |
|
+#define GSM_IS_APP_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSM_TYPE_APP_DIALOG)) |
|
+#define GSM_APP_DIALOG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSM_TYPE_APP_DIALOG, GsmAppDialogClass)) |
|
+ |
|
+typedef struct GsmAppDialogPrivate GsmAppDialogPrivate; |
|
+ |
|
+typedef struct |
|
+{ |
|
+ GtkDialog parent; |
|
+ GsmAppDialogPrivate *priv; |
|
+} GsmAppDialog; |
|
+ |
|
+typedef struct |
|
+{ |
|
+ GtkDialogClass parent_class; |
|
+} GsmAppDialogClass; |
|
+ |
|
+GType gsm_app_dialog_get_type (void); |
|
+ |
|
+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); |
|
+ |
|
+G_END_DECLS |
|
+ |
|
+#endif /* __GSM_APP_DIALOG_H */ |
|
diff --git a/capplet/gsm-properties-dialog.c b/capplet/gsm-properties-dialog.c |
|
new file mode 100644 |
|
index 00000000..33812b8b |
|
--- /dev/null |
|
+++ b/capplet/gsm-properties-dialog.c |
|
@@ -0,0 +1,774 @@ |
|
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- |
|
+ * |
|
+ * Copyright (C) 1999 Free Software Foundation, Inc. |
|
+ * Copyright (C) 2007 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 "config.h" |
|
+ |
|
+#include <glib.h> |
|
+#include <glib/gi18n.h> |
|
+#include <gtk/gtk.h> |
|
+ |
|
+#include "gsm-properties-dialog.h" |
|
+#include "gsm-app-dialog.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)) |
|
+ |
|
+#define GTKBUILDER_FILE "session-properties.ui" |
|
+ |
|
+#define CAPPLET_TREEVIEW_WIDGET_NAME "session_properties_treeview" |
|
+#define CAPPLET_ADD_WIDGET_NAME "session_properties_add_button" |
|
+#define CAPPLET_DELETE_WIDGET_NAME "session_properties_delete_button" |
|
+#define CAPPLET_EDIT_WIDGET_NAME "session_properties_edit_button" |
|
+#define CAPPLET_SAVE_WIDGET_NAME "session_properties_save_button" |
|
+#define CAPPLET_REMEMBER_WIDGET_NAME "session_properties_remember_toggle" |
|
+ |
|
+#define STARTUP_APP_ICON "system-run" |
|
+ |
|
+#define SPC_SETTINGS_SCHEMA "org.gnome.SessionManager" |
|
+#define SPC_SETTINGS_AUTOSAVE_KEY "auto-save-session" |
|
+ |
|
+struct GsmPropertiesDialogPrivate |
|
+{ |
|
+ GtkBuilder *xml; |
|
+ GtkListStore *list_store; |
|
+ GtkTreeModel *tree_filter; |
|
+ |
|
+ GtkTreeView *treeview; |
|
+ GtkWidget *add_button; |
|
+ GtkWidget *delete_button; |
|
+ GtkWidget *edit_button; |
|
+ |
|
+ GSettings *settings; |
|
+ |
|
+ GspAppManager *manager; |
|
+}; |
|
+ |
|
+enum { |
|
+ STORE_COL_VISIBLE = 0, |
|
+ STORE_COL_ENABLED, |
|
+ STORE_COL_GICON, |
|
+ STORE_COL_DESCRIPTION, |
|
+ STORE_COL_APP, |
|
+ STORE_COL_SEARCH, |
|
+ NUMBER_OF_COLUMNS |
|
+}; |
|
+ |
|
+static void gsm_properties_dialog_class_init (GsmPropertiesDialogClass *klass); |
|
+static void gsm_properties_dialog_init (GsmPropertiesDialog *properties_dialog); |
|
+static void gsm_properties_dialog_finalize (GObject *object); |
|
+ |
|
+G_DEFINE_TYPE (GsmPropertiesDialog, gsm_properties_dialog, GTK_TYPE_DIALOG) |
|
+ |
|
+static gboolean |
|
+find_by_app (GtkTreeModel *model, |
|
+ GtkTreeIter *iter, |
|
+ GspApp *app) |
|
+{ |
|
+ GspApp *iter_app = NULL; |
|
+ |
|
+ if (!gtk_tree_model_get_iter_first (model, iter)) { |
|
+ return FALSE; |
|
+ } |
|
+ |
|
+ do { |
|
+ gtk_tree_model_get (model, iter, |
|
+ STORE_COL_APP, &iter_app, |
|
+ -1); |
|
+ |
|
+ if (iter_app == app) { |
|
+ g_object_unref (iter_app); |
|
+ return TRUE; |
|
+ } |
|
+ } while (gtk_tree_model_iter_next (model, iter)); |
|
+ |
|
+ return FALSE; |
|
+} |
|
+ |
|
+static void |
|
+_fill_iter_from_app (GtkListStore *list_store, |
|
+ GtkTreeIter *iter, |
|
+ GspApp *app) |
|
+{ |
|
+ gboolean hidden; |
|
+ gboolean display; |
|
+ gboolean enabled; |
|
+ gboolean shown; |
|
+ GIcon *icon; |
|
+ const char *description; |
|
+ const char *app_name; |
|
+ |
|
+ hidden = gsp_app_get_hidden (app); |
|
+ display = gsp_app_get_display (app); |
|
+ enabled = gsp_app_get_enabled (app); |
|
+ shown = gsp_app_get_shown (app); |
|
+ icon = gsp_app_get_icon (app); |
|
+ description = gsp_app_get_description (app); |
|
+ app_name = gsp_app_get_name (app); |
|
+ |
|
+ if (G_IS_THEMED_ICON (icon)) { |
|
+ GtkIconTheme *theme; |
|
+ const char * const *icon_names; |
|
+ |
|
+ theme = gtk_icon_theme_get_default (); |
|
+ icon_names = g_themed_icon_get_names (G_THEMED_ICON (icon)); |
|
+ if (icon_names[0] == NULL || |
|
+ !gtk_icon_theme_has_icon (theme, icon_names[0])) { |
|
+ g_object_unref (icon); |
|
+ icon = NULL; |
|
+ } |
|
+ } else if (G_IS_FILE_ICON (icon)) { |
|
+ GFile *iconfile; |
|
+ |
|
+ iconfile = g_file_icon_get_file (G_FILE_ICON (icon)); |
|
+ if (!g_file_query_exists (iconfile, NULL)) { |
|
+ g_object_unref (icon); |
|
+ icon = NULL; |
|
+ } |
|
+ } |
|
+ |
|
+ if (icon == NULL) { |
|
+ icon = g_themed_icon_new (STARTUP_APP_ICON); |
|
+ } |
|
+ |
|
+ gtk_list_store_set (list_store, iter, |
|
+ STORE_COL_VISIBLE, !hidden && shown && display, |
|
+ STORE_COL_ENABLED, enabled, |
|
+ STORE_COL_GICON, icon, |
|
+ STORE_COL_DESCRIPTION, description, |
|
+ STORE_COL_APP, app, |
|
+ STORE_COL_SEARCH, app_name, |
|
+ -1); |
|
+ g_object_unref (icon); |
|
+} |
|
+ |
|
+static void |
|
+_app_changed (GsmPropertiesDialog *dialog, |
|
+ GspApp *app) |
|
+{ |
|
+ GtkTreeIter iter; |
|
+ |
|
+ if (!find_by_app (GTK_TREE_MODEL (dialog->priv->list_store), |
|
+ &iter, app)) { |
|
+ return; |
|
+ } |
|
+ |
|
+ _fill_iter_from_app (dialog->priv->list_store, &iter, app); |
|
+} |
|
+ |
|
+static void |
|
+append_app (GsmPropertiesDialog *dialog, |
|
+ GspApp *app) |
|
+{ |
|
+ GtkTreeIter iter; |
|
+ |
|
+ gtk_list_store_append (dialog->priv->list_store, &iter); |
|
+ _fill_iter_from_app (dialog->priv->list_store, &iter, app); |
|
+ |
|
+ g_signal_connect_swapped (app, "changed", |
|
+ G_CALLBACK (_app_changed), dialog); |
|
+} |
|
+ |
|
+static void |
|
+_app_added (GsmPropertiesDialog *dialog, |
|
+ GspApp *app, |
|
+ GspAppManager *manager) |
|
+{ |
|
+ append_app (dialog, app); |
|
+} |
|
+ |
|
+static void |
|
+_app_removed (GsmPropertiesDialog *dialog, |
|
+ GspApp *app, |
|
+ GspAppManager *manager) |
|
+{ |
|
+ GtkTreeIter iter; |
|
+ |
|
+ if (!find_by_app (GTK_TREE_MODEL (dialog->priv->list_store), |
|
+ &iter, app)) { |
|
+ return; |
|
+ } |
|
+ |
|
+ g_signal_handlers_disconnect_by_func (app, |
|
+ _app_changed, |
|
+ dialog); |
|
+ gtk_list_store_remove (dialog->priv->list_store, &iter); |
|
+} |
|
+ |
|
+static void |
|
+populate_model (GsmPropertiesDialog *dialog) |
|
+{ |
|
+ GSList *apps; |
|
+ GSList *l; |
|
+ |
|
+ apps = gsp_app_manager_get_apps (dialog->priv->manager); |
|
+ for (l = apps; l != NULL; l = l->next) { |
|
+ append_app (dialog, GSP_APP (l->data)); |
|
+ } |
|
+ g_slist_free (apps); |
|
+} |
|
+ |
|
+static void |
|
+on_selection_changed (GtkTreeSelection *selection, |
|
+ GsmPropertiesDialog *dialog) |
|
+{ |
|
+ gboolean sel; |
|
+ |
|
+ sel = gtk_tree_selection_get_selected (selection, NULL, NULL); |
|
+ |
|
+ gtk_widget_set_sensitive (dialog->priv->edit_button, sel); |
|
+ gtk_widget_set_sensitive (dialog->priv->delete_button, sel); |
|
+} |
|
+ |
|
+static void |
|
+on_startup_enabled_toggled (GtkCellRendererToggle *cell_renderer, |
|
+ char *path, |
|
+ GsmPropertiesDialog *dialog) |
|
+{ |
|
+ GtkTreeIter iter; |
|
+ GspApp *app; |
|
+ gboolean active; |
|
+ |
|
+ if (!gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (dialog->priv->tree_filter), |
|
+ &iter, path)) { |
|
+ return; |
|
+ } |
|
+ |
|
+ app = NULL; |
|
+ gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->tree_filter), |
|
+ &iter, |
|
+ STORE_COL_APP, &app, |
|
+ -1); |
|
+ |
|
+ active = gtk_cell_renderer_toggle_get_active (cell_renderer); |
|
+ active = !active; |
|
+ |
|
+ if (app) { |
|
+ gsp_app_set_enabled (app, active); |
|
+ g_object_unref (app); |
|
+ } |
|
+} |
|
+ |
|
+static void |
|
+on_drag_data_received (GtkWidget *widget, |
|
+ GdkDragContext *drag_context, |
|
+ gint x, |
|
+ gint y, |
|
+ GtkSelectionData *data, |
|
+ guint info, |
|
+ guint time, |
|
+ GsmPropertiesDialog *dialog) |
|
+{ |
|
+ gboolean dnd_success; |
|
+ |
|
+ dnd_success = FALSE; |
|
+ |
|
+ if (data != NULL) { |
|
+ char **filenames; |
|
+ int i; |
|
+ |
|
+ filenames = gtk_selection_data_get_uris (data); |
|
+ |
|
+ for (i = 0; filenames[i] && filenames[i][0]; i++) { |
|
+ /* Return success if at least one file succeeded */ |
|
+ gboolean file_success; |
|
+ file_success = gsp_app_copy_desktop_file (filenames[i]); |
|
+ dnd_success = dnd_success || file_success; |
|
+ } |
|
+ |
|
+ g_strfreev (filenames); |
|
+ } |
|
+ |
|
+ gtk_drag_finish (drag_context, dnd_success, FALSE, time); |
|
+ g_signal_stop_emission_by_name (widget, "drag_data_received"); |
|
+} |
|
+ |
|
+static void |
|
+on_drag_begin (GtkWidget *widget, |
|
+ GdkDragContext *context, |
|
+ GsmPropertiesDialog *dialog) |
|
+{ |
|
+ GtkTreePath *path; |
|
+ GtkTreeIter iter; |
|
+ GspApp *app; |
|
+ |
|
+ gtk_tree_view_get_cursor (GTK_TREE_VIEW (widget), &path, NULL); |
|
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (dialog->priv->tree_filter), |
|
+ &iter, path); |
|
+ gtk_tree_path_free (path); |
|
+ |
|
+ gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->tree_filter), |
|
+ &iter, |
|
+ STORE_COL_APP, &app, |
|
+ -1); |
|
+ |
|
+ if (app) { |
|
+ g_object_set_data_full (G_OBJECT (context), "gsp-app", |
|
+ g_object_ref (app), g_object_unref); |
|
+ g_object_unref (app); |
|
+ } |
|
+ |
|
+} |
|
+ |
|
+static void |
|
+on_drag_data_get (GtkWidget *widget, |
|
+ GdkDragContext *context, |
|
+ GtkSelectionData *selection_data, |
|
+ guint info, |
|
+ guint time, |
|
+ GsmPropertiesDialog *dialog) |
|
+{ |
|
+ GspApp *app; |
|
+ |
|
+ app = g_object_get_data (G_OBJECT (context), "gsp-app"); |
|
+ if (app) { |
|
+ const char *uris[2]; |
|
+ char *uri; |
|
+ |
|
+ uri = g_filename_to_uri (gsp_app_get_path (app), NULL, NULL); |
|
+ |
|
+ uris[0] = uri; |
|
+ uris[1] = NULL; |
|
+ gtk_selection_data_set_uris (selection_data, (char **) uris); |
|
+ |
|
+ g_free (uri); |
|
+ } |
|
+} |
|
+ |
|
+static void |
|
+on_add_app_clicked (GtkWidget *widget, |
|
+ GsmPropertiesDialog *dialog) |
|
+{ |
|
+ GtkWidget *add_dialog; |
|
+ char *name; |
|
+ char *exec; |
|
+ char *comment; |
|
+ |
|
+ add_dialog = gsm_app_dialog_new (NULL, NULL, NULL); |
|
+ gtk_window_set_transient_for (GTK_WINDOW (add_dialog), |
|
+ GTK_WINDOW (dialog)); |
|
+ |
|
+ 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 |
|
+on_delete_app_clicked (GtkWidget *widget, |
|
+ GsmPropertiesDialog *dialog) |
|
+{ |
|
+ GtkTreeSelection *selection; |
|
+ GtkTreeIter iter; |
|
+ GspApp *app; |
|
+ |
|
+ selection = gtk_tree_view_get_selection (dialog->priv->treeview); |
|
+ |
|
+ if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) { |
|
+ return; |
|
+ } |
|
+ |
|
+ app = NULL; |
|
+ gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->tree_filter), |
|
+ &iter, |
|
+ STORE_COL_APP, &app, |
|
+ -1); |
|
+ |
|
+ if (app) { |
|
+ gsp_app_delete (app); |
|
+ g_object_unref (app); |
|
+ } |
|
+} |
|
+ |
|
+static void |
|
+on_edit_app_clicked (GtkWidget *widget, |
|
+ GsmPropertiesDialog *dialog) |
|
+{ |
|
+ GtkTreeSelection *selection; |
|
+ GtkTreeIter iter; |
|
+ GspApp *app; |
|
+ |
|
+ selection = gtk_tree_view_get_selection (dialog->priv->treeview); |
|
+ |
|
+ if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) { |
|
+ return; |
|
+ } |
|
+ |
|
+ app = NULL; |
|
+ gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->tree_filter), |
|
+ &iter, |
|
+ STORE_COL_APP, &app, |
|
+ -1); |
|
+ |
|
+ if (app) { |
|
+ GtkWidget *edit_dialog; |
|
+ char *name; |
|
+ char *exec; |
|
+ char *comment; |
|
+ |
|
+ edit_dialog = gsm_app_dialog_new (gsp_app_get_name (app), |
|
+ gsp_app_get_exec (app), |
|
+ gsp_app_get_comment (app)); |
|
+ gtk_window_set_transient_for (GTK_WINDOW (edit_dialog), |
|
+ GTK_WINDOW (dialog)); |
|
+ |
|
+ if (gsm_app_dialog_run (GSM_APP_DIALOG (edit_dialog), |
|
+ &name, &exec, &comment)) { |
|
+ gsp_app_update (app, name, comment, exec); |
|
+ g_free (name); |
|
+ g_free (exec); |
|
+ g_free (comment); |
|
+ } |
|
+ |
|
+ g_object_unref (app); |
|
+ } |
|
+} |
|
+ |
|
+static void |
|
+on_row_activated (GtkTreeView *tree_view, |
|
+ GtkTreePath *path, |
|
+ GtkTreeViewColumn *column, |
|
+ GsmPropertiesDialog *dialog) |
|
+{ |
|
+ on_edit_app_clicked (NULL, dialog); |
|
+} |
|
+ |
|
+static void |
|
+on_save_session_clicked (GtkWidget *widget, |
|
+ GsmPropertiesDialog *dialog) |
|
+{ |
|
+ g_debug ("Session saving is not implemented yet!"); |
|
+} |
|
+ |
|
+static void |
|
+setup_dialog (GsmPropertiesDialog *dialog) |
|
+{ |
|
+ GtkTreeView *treeview; |
|
+ GtkWidget *button; |
|
+ GtkTreeModel *tree_filter; |
|
+ GtkTreeViewColumn *column; |
|
+ GtkCellRenderer *renderer; |
|
+ GtkTreeSelection *selection; |
|
+ GtkTargetList *targetlist; |
|
+ |
|
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog), |
|
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, |
|
+ NULL); |
|
+ |
|
+ dialog->priv->list_store = gtk_list_store_new (NUMBER_OF_COLUMNS, |
|
+ G_TYPE_BOOLEAN, |
|
+ G_TYPE_BOOLEAN, |
|
+ G_TYPE_ICON, |
|
+ G_TYPE_STRING, |
|
+ G_TYPE_OBJECT, |
|
+ G_TYPE_STRING); |
|
+ 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 = GTK_TREE_VIEW (gtk_builder_get_object (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 (treeview); |
|
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); |
|
+ g_signal_connect (selection, |
|
+ "changed", |
|
+ G_CALLBACK (on_selection_changed), |
|
+ dialog); |
|
+ |
|
+ /* CHECKBOX COLUMN */ |
|
+ renderer = gtk_cell_renderer_toggle_new (); |
|
+ column = gtk_tree_view_column_new_with_attributes (_("Enabled"), |
|
+ renderer, |
|
+ "active", STORE_COL_ENABLED, |
|
+ NULL); |
|
+ gtk_tree_view_append_column (treeview, column); |
|
+ g_signal_connect (renderer, |
|
+ "toggled", |
|
+ G_CALLBACK (on_startup_enabled_toggled), |
|
+ dialog); |
|
+ |
|
+ /* ICON COLUMN */ |
|
+ renderer = gtk_cell_renderer_pixbuf_new (); |
|
+ column = gtk_tree_view_column_new_with_attributes (_("Icon"), |
|
+ renderer, |
|
+ "gicon", STORE_COL_GICON, |
|
+ "sensitive", STORE_COL_ENABLED, |
|
+ NULL); |
|
+ g_object_set (renderer, |
|
+ "stock-size", GSM_PROPERTIES_ICON_SIZE, |
|
+ NULL); |
|
+ gtk_tree_view_append_column (treeview, column); |
|
+ |
|
+ /* NAME COLUMN */ |
|
+ renderer = gtk_cell_renderer_text_new (); |
|
+ column = gtk_tree_view_column_new_with_attributes (_("Program"), |
|
+ renderer, |
|
+ "markup", STORE_COL_DESCRIPTION, |
|
+ "sensitive", STORE_COL_ENABLED, |
|
+ NULL); |
|
+ g_object_set (renderer, |
|
+ "ellipsize", PANGO_ELLIPSIZE_END, |
|
+ NULL); |
|
+ gtk_tree_view_append_column (treeview, column); |
|
+ |
|
+ |
|
+ gtk_tree_view_column_set_sort_column_id (column, STORE_COL_DESCRIPTION); |
|
+ gtk_tree_view_set_search_column (treeview, STORE_COL_SEARCH); |
|
+ gtk_tree_view_set_rules_hint (treeview, TRUE); |
|
+ |
|
+ gtk_tree_view_enable_model_drag_source (treeview, |
|
+ GDK_BUTTON1_MASK|GDK_BUTTON2_MASK, |
|
+ NULL, 0, |
|
+ GDK_ACTION_COPY); |
|
+ gtk_drag_source_add_uri_targets (GTK_WIDGET (treeview)); |
|
+ |
|
+ gtk_drag_dest_set (GTK_WIDGET (treeview), |
|
+ GTK_DEST_DEFAULT_ALL, |
|
+ NULL, 0, |
|
+ GDK_ACTION_COPY); |
|
+ |
|
+ gtk_drag_dest_add_uri_targets (GTK_WIDGET (treeview)); |
|
+ /* we don't want to accept drags coming from this widget */ |
|
+ targetlist = gtk_drag_dest_get_target_list (GTK_WIDGET (treeview)); |
|
+ if (targetlist != NULL) { |
|
+ GtkTargetEntry *targets; |
|
+ gint n_targets; |
|
+ gint i; |
|
+ |
|
+ targets = gtk_target_table_new_from_list (targetlist, &n_targets); |
|
+ for (i = 0; i < n_targets; i++) |
|
+ targets[i].flags = GTK_TARGET_OTHER_WIDGET; |
|
+ |
|
+ targetlist = gtk_target_list_new (targets, n_targets); |
|
+ gtk_drag_dest_set_target_list (GTK_WIDGET (treeview), targetlist); |
|
+ gtk_target_list_unref (targetlist); |
|
+ |
|
+ gtk_target_table_free (targets, n_targets); |
|
+ } |
|
+ |
|
+ g_signal_connect (treeview, "drag_begin", |
|
+ G_CALLBACK (on_drag_begin), |
|
+ dialog); |
|
+ g_signal_connect (treeview, "drag_data_get", |
|
+ G_CALLBACK (on_drag_data_get), |
|
+ dialog); |
|
+ g_signal_connect (treeview, "drag_data_received", |
|
+ G_CALLBACK (on_drag_data_received), |
|
+ dialog); |
|
+ |
|
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (dialog->priv->list_store), |
|
+ STORE_COL_DESCRIPTION, |
|
+ GTK_SORT_ASCENDING); |
|
+ |
|
+ |
|
+ button = GTK_WIDGET (gtk_builder_get_object (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 = GTK_WIDGET (gtk_builder_get_object (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 = GTK_WIDGET (gtk_builder_get_object (dialog->priv->xml, |
|
+ CAPPLET_EDIT_WIDGET_NAME)); |
|
+ dialog->priv->edit_button = button; |
|
+ g_signal_connect (button, |
|
+ "clicked", |
|
+ G_CALLBACK (on_edit_app_clicked), |
|
+ dialog); |
|
+ |
|
+ button = GTK_WIDGET (gtk_builder_get_object (dialog->priv->xml, |
|
+ CAPPLET_REMEMBER_WIDGET_NAME)); |
|
+ g_settings_bind (dialog->priv->settings, SPC_SETTINGS_AUTOSAVE_KEY, |
|
+ button, "active", G_SETTINGS_BIND_DEFAULT); |
|
+ |
|
+ button = GTK_WIDGET (gtk_builder_get_object (dialog->priv->xml, |
|
+ CAPPLET_SAVE_WIDGET_NAME)); |
|
+ g_signal_connect (button, |
|
+ "clicked", |
|
+ 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); |
|
+} |
|
+ |
|
+static GObject * |
|
+gsm_properties_dialog_constructor (GType type, |
|
+ guint n_construct_properties, |
|
+ GObjectConstructParam *construct_properties) |
|
+{ |
|
+ GsmPropertiesDialog *dialog; |
|
+ |
|
+ dialog = GSM_PROPERTIES_DIALOG (G_OBJECT_CLASS (gsm_properties_dialog_parent_class)->constructor (type, |
|
+ n_construct_properties, |
|
+ construct_properties)); |
|
+ |
|
+ setup_dialog (dialog); |
|
+ |
|
+ gtk_widget_show (GTK_WIDGET (dialog)); |
|
+ |
|
+ return G_OBJECT (dialog); |
|
+} |
|
+ |
|
+static void |
|
+gsm_properties_dialog_dispose (GObject *object) |
|
+{ |
|
+ GsmPropertiesDialog *dialog; |
|
+ |
|
+ g_return_if_fail (object != NULL); |
|
+ g_return_if_fail (GSM_IS_PROPERTIES_DIALOG (object)); |
|
+ |
|
+ dialog = GSM_PROPERTIES_DIALOG (object); |
|
+ |
|
+ if (dialog->priv->xml != NULL) { |
|
+ g_object_unref (dialog->priv->xml); |
|
+ dialog->priv->xml = NULL; |
|
+ } |
|
+ |
|
+ if (dialog->priv->settings != NULL) { |
|
+ g_object_unref (dialog->priv->settings); |
|
+ dialog->priv->settings = NULL; |
|
+ } |
|
+ |
|
+ G_OBJECT_CLASS (gsm_properties_dialog_parent_class)->dispose (object); |
|
+ |
|
+ /* it's important to do this after chaining to the parent dispose |
|
+ * method because we want to make sure the treeview has been disposed |
|
+ * and removed all its references to GspApp objects */ |
|
+ if (dialog->priv->manager != NULL) { |
|
+ g_object_unref (dialog->priv->manager); |
|
+ dialog->priv->manager = NULL; |
|
+ } |
|
+} |
|
+ |
|
+static void |
|
+gsm_properties_dialog_class_init (GsmPropertiesDialogClass *klass) |
|
+{ |
|
+ GObjectClass *object_class = G_OBJECT_CLASS (klass); |
|
+ |
|
+ object_class->constructor = gsm_properties_dialog_constructor; |
|
+ object_class->dispose = gsm_properties_dialog_dispose; |
|
+ object_class->finalize = gsm_properties_dialog_finalize; |
|
+ |
|
+ g_type_class_add_private (klass, sizeof (GsmPropertiesDialogPrivate)); |
|
+} |
|
+ |
|
+static void |
|
+gsm_properties_dialog_init (GsmPropertiesDialog *dialog) |
|
+{ |
|
+ GtkWidget *content_area; |
|
+ GtkWidget *widget; |
|
+ GError *error; |
|
+ |
|
+ dialog->priv = GSM_PROPERTIES_DIALOG_GET_PRIVATE (dialog); |
|
+ |
|
+ dialog->priv->settings = g_settings_new (SPC_SETTINGS_SCHEMA); |
|
+ |
|
+ dialog->priv->xml = gtk_builder_new (); |
|
+ gtk_builder_set_translation_domain (dialog->priv->xml, GETTEXT_PACKAGE); |
|
+ |
|
+ error = NULL; |
|
+ if (!gtk_builder_add_from_file (dialog->priv->xml, |
|
+ GTKBUILDER_DIR "/" GTKBUILDER_FILE, |
|
+ &error)) { |
|
+ if (error) { |
|
+ g_warning ("Could not load capplet UI file: %s", |
|
+ error->message); |
|
+ g_error_free (error); |
|
+ } else { |
|
+ g_warning ("Could not load capplet UI file."); |
|
+ } |
|
+ } |
|
+ |
|
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); |
|
+ widget = GTK_WIDGET (gtk_builder_get_object (dialog->priv->xml, |
|
+ "main-notebook")); |
|
+ gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0); |
|
+ |
|
+ gtk_window_set_default_size (GTK_WINDOW (dialog), 600, 450); |
|
+ gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE); |
|
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 6); |
|
+ gtk_box_set_spacing (GTK_BOX (content_area), 2); |
|
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "session-properties"); |
|
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Startup Applications Preferences")); |
|
+} |
|
+ |
|
+static void |
|
+gsm_properties_dialog_finalize (GObject *object) |
|
+{ |
|
+ GsmPropertiesDialog *dialog; |
|
+ |
|
+ g_return_if_fail (object != NULL); |
|
+ g_return_if_fail (GSM_IS_PROPERTIES_DIALOG (object)); |
|
+ |
|
+ dialog = GSM_PROPERTIES_DIALOG (object); |
|
+ |
|
+ g_return_if_fail (dialog->priv != NULL); |
|
+ |
|
+ G_OBJECT_CLASS (gsm_properties_dialog_parent_class)->finalize (object); |
|
+} |
|
+ |
|
+GtkWidget * |
|
+gsm_properties_dialog_new (void) |
|
+{ |
|
+ GObject *object; |
|
+ |
|
+ object = g_object_new (GSM_TYPE_PROPERTIES_DIALOG, |
|
+ NULL); |
|
+ |
|
+ return GTK_WIDGET (object); |
|
+} |
|
diff --git a/capplet/gsm-properties-dialog.h b/capplet/gsm-properties-dialog.h |
|
new file mode 100644 |
|
index 00000000..df4915ea |
|
--- /dev/null |
|
+++ b/capplet/gsm-properties-dialog.h |
|
@@ -0,0 +1,57 @@ |
|
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- |
|
+ * |
|
+ * 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 __GSM_PROPERTIES_DIALOG_H |
|
+#define __GSM_PROPERTIES_DIALOG_H |
|
+ |
|
+#include <glib-object.h> |
|
+#include <gtk/gtk.h> |
|
+ |
|
+G_BEGIN_DECLS |
|
+ |
|
+#define GSM_TYPE_PROPERTIES_DIALOG (gsm_properties_dialog_get_type ()) |
|
+#define GSM_PROPERTIES_DIALOG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GSM_TYPE_PROPERTIES_DIALOG, GsmPropertiesDialog)) |
|
+#define GSM_PROPERTIES_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GSM_TYPE_PROPERTIES_DIALOG, GsmPropertiesDialogClass)) |
|
+#define GSM_IS_PROPERTIES_DIALOG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSM_TYPE_PROPERTIES_DIALOG)) |
|
+#define GSM_IS_PROPERTIES_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSM_TYPE_PROPERTIES_DIALOG)) |
|
+#define GSM_PROPERTIES_DIALOG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSM_TYPE_PROPERTIES_DIALOG, GsmPropertiesDialogClass)) |
|
+ |
|
+typedef struct GsmPropertiesDialogPrivate GsmPropertiesDialogPrivate; |
|
+ |
|
+typedef struct |
|
+{ |
|
+ GtkDialog parent; |
|
+ GsmPropertiesDialogPrivate *priv; |
|
+} GsmPropertiesDialog; |
|
+ |
|
+typedef struct |
|
+{ |
|
+ GtkDialogClass parent_class; |
|
+} GsmPropertiesDialogClass; |
|
+ |
|
+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 00000000..bcd6d402 |
|
--- /dev/null |
|
+++ b/capplet/gsp-app-manager.c |
|
@@ -0,0 +1,593 @@ |
|
+/* -*- 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_dispose (GObject *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->dispose = gsp_app_manager_dispose; |
|
+ 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_dispose (GObject *object) |
|
+{ |
|
+ GspAppManager *manager; |
|
+ |
|
+ g_return_if_fail (object != NULL); |
|
+ g_return_if_fail (GSP_IS_APP_MANAGER (object)); |
|
+ |
|
+ manager = GSP_APP_MANAGER (object); |
|
+ |
|
+ /* we unref GspApp objects in dispose since they might need to |
|
+ * reference us during their dispose/finalize */ |
|
+ g_slist_foreach (manager->priv->apps, |
|
+ (GFunc) _gsp_app_manager_app_unref, manager); |
|
+ g_slist_free (manager->priv->apps); |
|
+ manager->priv->apps = NULL; |
|
+ |
|
+ G_OBJECT_CLASS (gsp_app_manager_parent_class)->dispose (object); |
|
+} |
|
+ |
|
+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->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 (index < position) { |
|
+ /* it got deleted, but in a position earlier than the current |
|
+ * one. This happens when the user file was changed and became |
|
+ * identical to the system file; in this case, the user file is |
|
+ * simply removed. */ |
|
+ g_assert (index == 0); |
|
+ 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 00000000..777f8d6c |
|
--- /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 00000000..c92b8dad |
|
--- /dev/null |
|
+++ b/capplet/gsp-app.c |
|
@@ -0,0 +1,1102 @@ |
|
+/* -*- 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. |
|
+ * |
|
+ */ |
|
+ |
|
+#ifdef HAVE_CONFIG_H |
|
+#include <config.h> |
|
+#endif |
|
+ |
|
+#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_NO_DISPLAY 0x0020 |
|
+#define GSP_ASP_SAVE_MASK_ALL 0xffff |
|
+ |
|
+struct _GspAppPrivate { |
|
+ char *basename; |
|
+ char *path; |
|
+ |
|
+ gboolean hidden; |
|
+ gboolean no_display; |
|
+ gboolean enabled; |
|
+ gboolean shown; |
|
+ |
|
+ char *name; |
|
+ char *exec; |
|
+ char *comment; |
|
+ char *icon; |
|
+ |
|
+ GIcon *gicon; |
|
+ 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_dispose (GObject *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->dispose = gsp_app_dispose; |
|
+ 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) { |
|
+ g_free (app->priv->icon); |
|
+ app->priv->icon = NULL; |
|
+ } |
|
+ |
|
+ if (app->priv->gicon) { |
|
+ g_object_unref (app->priv->gicon); |
|
+ app->priv->gicon = 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_dispose (GObject *object) |
|
+{ |
|
+ GspApp *app; |
|
+ |
|
+ g_return_if_fail (object != NULL); |
|
+ g_return_if_fail (GSP_IS_APP (object)); |
|
+ |
|
+ app = GSP_APP (object); |
|
+ |
|
+ /* we save in dispose since we might need to reference GspAppManager */ |
|
+ if (app->priv->save_timeout) { |
|
+ g_source_remove (app->priv->save_timeout); |
|
+ app->priv->save_timeout = 0; |
|
+ |
|
+ /* save now */ |
|
+ _gsp_app_save (app); |
|
+ } |
|
+ |
|
+ G_OBJECT_CLASS (gsp_app_parent_class)->dispose (object); |
|
+} |
|
+ |
|
+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->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 || |
|
+ gsp_key_file_get_shown (keyfile, |
|
+ gsm_util_get_current_desktop ()) != app->priv->shown) { |
|
+ g_free (path); |
|
+ g_key_file_free (keyfile); |
|
+ return FALSE; |
|
+ } |
|
+ |
|
+ if (gsp_key_file_get_boolean (keyfile, |
|
+ G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, |
|
+ FALSE) != app->priv->no_display) { |
|
+ 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; |
|
+ } |
|
+ g_free (str); |
|
+ |
|
+ 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; |
|
+ } |
|
+ g_free (str); |
|
+ |
|
+ 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_free (str); |
|
+ |
|
+ str = gsp_key_file_get_locale_string (keyfile, |
|
+ G_KEY_FILE_DESKTOP_KEY_ICON); |
|
+ if (!_gsp_str_equal (str, app->priv->icon)) { |
|
+ g_free (str); |
|
+ g_free (path); |
|
+ g_key_file_free (keyfile); |
|
+ return FALSE; |
|
+ } |
|
+ g_free (str); |
|
+ |
|
+ 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_NO_DISPLAY) { |
|
+ gsp_key_file_set_boolean (keyfile, |
|
+ G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, |
|
+ app->priv->no_display); |
|
+ } |
|
+ |
|
+ 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; |
|
+} |
|
+ |
|
+const char * |
|
+gsp_app_get_path (GspApp *app) |
|
+{ |
|
+ g_return_val_if_fail (GSP_IS_APP (app), NULL); |
|
+ |
|
+ return app->priv->path; |
|
+} |
|
+ |
|
+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_display (GspApp *app) |
|
+{ |
|
+ g_return_val_if_fail (GSP_IS_APP (app), FALSE); |
|
+ |
|
+ return !app->priv->no_display; |
|
+} |
|
+ |
|
+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); |
|
+} |
|
+ |
|
+gboolean |
|
+gsp_app_get_shown (GspApp *app) |
|
+{ |
|
+ g_return_val_if_fail (GSP_IS_APP (app), FALSE); |
|
+ |
|
+ return app->priv->shown; |
|
+} |
|
+ |
|
+const char * |
|
+gsp_app_get_name (GspApp *app) |
|
+{ |
|
+ g_return_val_if_fail (GSP_IS_APP (app), NULL); |
|
+ |
|
+ return app->priv->name; |
|
+} |
|
+ |
|
+const char * |
|
+gsp_app_get_exec (GspApp *app) |
|
+{ |
|
+ g_return_val_if_fail (GSP_IS_APP (app), NULL); |
|
+ |
|
+ return app->priv->exec; |
|
+} |
|
+ |
|
+const char * |
|
+gsp_app_get_comment (GspApp *app) |
|
+{ |
|
+ g_return_val_if_fail (GSP_IS_APP (app), NULL); |
|
+ |
|
+ return app->priv->comment; |
|
+} |
|
+ |
|
+GIcon * |
|
+gsp_app_get_icon (GspApp *app) |
|
+{ |
|
+ g_return_val_if_fail (GSP_IS_APP (app), NULL); |
|
+ |
|
+ if (app->priv->gicon) { |
|
+ return g_object_ref (app->priv->gicon); |
|
+ } else { |
|
+ return NULL; |
|
+ } |
|
+} |
|
+ |
|
+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_update (GspApp *app, |
|
+ const char *name, |
|
+ const char *comment, |
|
+ const char *exec) |
|
+{ |
|
+ gboolean changed; |
|
+ |
|
+ g_return_if_fail (GSP_IS_APP (app)); |
|
+ |
|
+ changed = FALSE; |
|
+ |
|
+ if (!_gsp_str_equal (name, app->priv->name)) { |
|
+ changed = TRUE; |
|
+ g_free (app->priv->name); |
|
+ app->priv->name = g_strdup (name); |
|
+ app->priv->save_mask |= GSP_ASP_SAVE_MASK_NAME; |
|
+ } |
|
+ |
|
+ if (!_gsp_str_equal (comment, app->priv->comment)) { |
|
+ changed = TRUE; |
|
+ g_free (app->priv->comment); |
|
+ app->priv->comment = g_strdup (comment); |
|
+ app->priv->save_mask |= GSP_ASP_SAVE_MASK_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 = g_strdup (exec); |
|
+ app->priv->save_mask |= GSP_ASP_SAVE_MASK_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); |
|
+} |
|
+ |
|
+GspApp * |
|
+gsp_app_new (const char *path, |
|
+ unsigned int xdg_position) |
|
+{ |
|
+ GspAppManager *manager; |
|
+ GspApp *app; |
|
+ GKeyFile *keyfile; |
|
+ char *basename; |
|
+ gboolean new; |
|
+ |
|
+ 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->no_display = gsp_key_file_get_boolean (keyfile, |
|
+ G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, |
|
+ FALSE); |
|
+ app->priv->enabled = gsp_key_file_get_boolean (keyfile, |
|
+ GSP_KEY_FILE_DESKTOP_KEY_AUTOSTART_ENABLED, |
|
+ TRUE); |
|
+ app->priv->shown = gsp_key_file_get_shown (keyfile, |
|
+ gsm_util_get_current_desktop ()); |
|
+ |
|
+ 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); |
|
+ } |
|
+ |
|
+ app->priv->icon = gsp_key_file_get_locale_string (keyfile, |
|
+ G_KEY_FILE_DESKTOP_KEY_ICON); |
|
+ |
|
+ if (app->priv->icon) { |
|
+ /* look at icon and see if it's a themed icon or not */ |
|
+ if (g_path_is_absolute (app->priv->icon)) { |
|
+ GFile *iconfile; |
|
+ |
|
+ iconfile = g_file_new_for_path (app->priv->icon); |
|
+ app->priv->gicon = g_file_icon_new (iconfile); |
|
+ g_object_unref (iconfile); |
|
+ } else { |
|
+ app->priv->gicon = g_themed_icon_new (app->priv->icon); |
|
+ } |
|
+ } else { |
|
+ app->priv->gicon = NULL; |
|
+ } |
|
+ |
|
+ 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->no_display = FALSE; |
|
+ app->priv->enabled = TRUE; |
|
+ app->priv->shown = 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 = NULL; |
|
+ |
|
+ app->priv->gicon = 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->no_display) { |
|
+ changed = TRUE; |
|
+ app->priv->no_display = FALSE; |
|
+ app->priv->save_mask |= GSP_ASP_SAVE_MASK_NO_DISPLAY; |
|
+ } |
|
+ |
|
+ 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 00000000..a1997955 |
|
--- /dev/null |
|
+++ b/capplet/gsp-app.h |
|
@@ -0,0 +1,108 @@ |
|
+/* -*- 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 <gio/gio.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); |
|
+ |
|
+void gsp_app_create (const char *name, |
|
+ const char *comment, |
|
+ const char *exec); |
|
+void gsp_app_update (GspApp *app, |
|
+ const char *name, |
|
+ const char *comment, |
|
+ const char *exec); |
|
+ |
|
+gboolean gsp_app_copy_desktop_file (const char *uri); |
|
+ |
|
+void gsp_app_delete (GspApp *app); |
|
+ |
|
+const char *gsp_app_get_basename (GspApp *app); |
|
+const char *gsp_app_get_path (GspApp *app); |
|
+ |
|
+gboolean gsp_app_get_hidden (GspApp *app); |
|
+gboolean gsp_app_get_display (GspApp *app); |
|
+ |
|
+gboolean gsp_app_get_enabled (GspApp *app); |
|
+void gsp_app_set_enabled (GspApp *app, |
|
+ gboolean enabled); |
|
+ |
|
+gboolean gsp_app_get_shown (GspApp *app); |
|
+ |
|
+const char *gsp_app_get_name (GspApp *app); |
|
+const char *gsp_app_get_exec (GspApp *app); |
|
+const char *gsp_app_get_comment (GspApp *app); |
|
+ |
|
+const char *gsp_app_get_description (GspApp *app); |
|
+GIcon *gsp_app_get_icon (GspApp *app); |
|
+ |
|
+/* private interface for GspAppManager only */ |
|
+ |
|
+GspApp *gsp_app_new (const char *path, |
|
+ unsigned int xdg_position); |
|
+ |
|
+void gsp_app_reload_at (GspApp *app, |
|
+ const char *path, |
|
+ unsigned int xdg_position); |
|
+ |
|
+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 00000000..de9fac0e |
|
--- /dev/null |
|
+++ b/capplet/gsp-keyfile.c |
|
@@ -0,0 +1,201 @@ |
|
+/* |
|
+ * 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; |
|
+} |
|
+ |
|
+gboolean |
|
+gsp_key_file_get_shown (GKeyFile *keyfile, |
|
+ const char *current_desktop) |
|
+{ |
|
+ char **only_show_in, **not_show_in; |
|
+ gboolean found; |
|
+ int i; |
|
+ |
|
+ if (!current_desktop) |
|
+ return TRUE; |
|
+ |
|
+ only_show_in = g_key_file_get_string_list (keyfile, G_KEY_FILE_DESKTOP_GROUP, |
|
+ G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, |
|
+ NULL, NULL); |
|
+ |
|
+ if (only_show_in) { |
|
+ found = FALSE; |
|
+ for (i = 0; only_show_in[i] != NULL; i++) { |
|
+ if (g_strcmp0 (current_desktop, only_show_in[i]) == 0) { |
|
+ found = TRUE; |
|
+ break; |
|
+ } |
|
+ } |
|
+ |
|
+ g_strfreev (only_show_in); |
|
+ |
|
+ if (!found) |
|
+ return FALSE; |
|
+ } |
|
+ |
|
+ not_show_in = g_key_file_get_string_list (keyfile, G_KEY_FILE_DESKTOP_GROUP, |
|
+ G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN, |
|
+ NULL, NULL); |
|
+ |
|
+ if (not_show_in) { |
|
+ found = FALSE; |
|
+ for (i = 0; not_show_in[i] != NULL; i++) { |
|
+ if (g_strcmp0 (current_desktop, not_show_in[i]) == 0) { |
|
+ found = TRUE; |
|
+ break; |
|
+ } |
|
+ } |
|
+ |
|
+ g_strfreev (not_show_in); |
|
+ |
|
+ if (found) |
|
+ return FALSE; |
|
+ } |
|
+ |
|
+ return TRUE; |
|
+} |
|
+ |
|
+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 00000000..d94f6672 |
|
--- /dev/null |
|
+++ b/capplet/gsp-keyfile.h |
|
@@ -0,0 +1,65 @@ |
|
+/* |
|
+ * 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); |
|
+gboolean gsp_key_file_get_shown (GKeyFile *keyfile, |
|
+ const char *current_desktop); |
|
+#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 */ |
|
diff --git a/capplet/main.c b/capplet/main.c |
|
new file mode 100644 |
|
index 00000000..3c7177bc |
|
--- /dev/null |
|
+++ b/capplet/main.c |
|
@@ -0,0 +1,108 @@ |
|
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- |
|
+ * main.c |
|
+ * Copyright (C) 1999 Free Software Foundation, Inc. |
|
+ * Copyright (C) 2008 Lucas Rocha. |
|
+ * |
|
+ * 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 |
|
+ * Lesser 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 <config.h> |
|
+ |
|
+#include <unistd.h> |
|
+#include <stdlib.h> |
|
+ |
|
+#include <glib/gi18n.h> |
|
+#include <gtk/gtk.h> |
|
+ |
|
+#include "gsm-properties-dialog.h" |
|
+ |
|
+static gboolean show_version = FALSE; |
|
+ |
|
+static GOptionEntry options[] = { |
|
+ { "version", 0, 0, G_OPTION_ARG_NONE, &show_version, N_("Version of this application"), NULL }, |
|
+ { NULL, 0, 0, 0, NULL, NULL, NULL } |
|
+}; |
|
+ |
|
+static void |
|
+dialog_response (GsmPropertiesDialog *dialog, |
|
+ guint response_id, |
|
+ gpointer data) |
|
+{ |
|
+ GdkScreen *screen; |
|
+ GError *error; |
|
+ |
|
+ if (response_id == GTK_RESPONSE_HELP) { |
|
+ screen = gtk_widget_get_screen (GTK_WIDGET (dialog)); |
|
+ |
|
+ error = NULL; |
|
+ gtk_show_uri (screen, "ghelp:user-guide?gosstartsession-2", |
|
+ gtk_get_current_event_time (), &error); |
|
+ |
|
+ if (error != NULL) { |
|
+ GtkWidget *d; |
|
+ d = gtk_message_dialog_new (GTK_WINDOW (dialog), |
|
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, |
|
+ GTK_MESSAGE_ERROR, |
|
+ GTK_BUTTONS_CLOSE, |
|
+ "%s", |
|
+ _("Could not display help document")); |
|
+ gtk_message_dialog_format_secondary_text ( |
|
+ GTK_MESSAGE_DIALOG (d), |
|
+ "%s", error->message); |
|
+ g_error_free (error); |
|
+ |
|
+ gtk_dialog_run (GTK_DIALOG (d)); |
|
+ gtk_widget_destroy (d); |
|
+ } |
|
+ } else { |
|
+ gtk_widget_destroy (GTK_WIDGET (dialog)); |
|
+ gtk_main_quit (); |
|
+ } |
|
+} |
|
+ |
|
+int |
|
+main (int argc, char *argv[]) |
|
+{ |
|
+ GError *error; |
|
+ GtkWidget *dialog; |
|
+ |
|
+ bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); |
|
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); |
|
+ textdomain (GETTEXT_PACKAGE); |
|
+ |
|
+ error = NULL; |
|
+ if (! gtk_init_with_args (&argc, &argv, " - GNOME Session Properties", options, GETTEXT_PACKAGE, &error)) { |
|
+ g_warning ("Unable to start: %s", error->message); |
|
+ g_error_free (error); |
|
+ return 1; |
|
+ } |
|
+ |
|
+ if (show_version) { |
|
+ g_print ("%s %s\n", argv [0], VERSION); |
|
+ return 0; |
|
+ } |
|
+ |
|
+ dialog = gsm_properties_dialog_new (); |
|
+ g_signal_connect (dialog, |
|
+ "response", |
|
+ G_CALLBACK (dialog_response), |
|
+ NULL); |
|
+ gtk_widget_show (dialog); |
|
+ |
|
+ gtk_main (); |
|
+ |
|
+ return 0; |
|
+} |
|
diff --git a/configure.ac b/configure.ac |
|
index f57dcf3d..b3f285ed 100644 |
|
--- a/configure.ac |
|
+++ b/configure.ac |
|
@@ -337,65 +337,67 @@ if test $enable_ipv6 = yes; then |
|
]])], |
|
[have_ipv6=yes], |
|
[have_ipv6=no] |
|
) |
|
AC_MSG_RESULT($have_ipv6) |
|
|
|
dnl ================================================================= |
|
dnl Now we would check for specific function like getaddrinfo. |
|
dnl ================================================================= |
|
have_getaddrinfo=no |
|
if test $have_ipv6=yes; then |
|
AC_CHECK_FUNC(getaddrinfo, have_getaddrinfo=yes) |
|
if test $have_getaddrinfo != yes; then |
|
# getaddrinfo is not in the default libraries. See if it's in some other. |
|
for lib in bsd socket inet; do |
|
AC_CHECK_LIB($lib, getaddrinfo, [LIBS="$LIBS -l$lib";have_getaddrinfo=yes; break]) |
|
done |
|
fi |
|
if test $have_getaddrinfo=yes; then |
|
AC_DEFINE(ENABLE_IPV6, 1, [Define if IPV6 is supported]) |
|
have_full_ipv6=yes |
|
fi |
|
fi |
|
fi |
|
dnl ============================================================================== |
|
dnl End of IPv6 checks |
|
dnl ============================================================================== |
|
|
|
AC_CONFIG_FILES([ |
|
Makefile |
|
+capplet/Makefile |
|
doc/Makefile |
|
doc/dbus/Makefile |
|
doc/dbus/gnome-session.xml |
|
doc/man/Makefile |
|
data/Makefile |
|
+data/gnome-session-properties.desktop.in |
|
data/org.gnome.SessionManager.gschema.xml |
|
data/icons/Makefile |
|
data/icons/16x16/Makefile |
|
data/icons/22x22/Makefile |
|
data/icons/24x24/Makefile |
|
data/icons/32x32/Makefile |
|
data/icons/48x48/Makefile |
|
data/icons/scalable/Makefile |
|
data/icons/symbolic/Makefile |
|
gnome-session/Makefile |
|
tools/Makefile |
|
po/Makefile.in |
|
]) |
|
AC_OUTPUT |
|
|
|
dnl --------------------------------------------------------------------------- |
|
dnl - Show summary |
|
dnl --------------------------------------------------------------------------- |
|
|
|
echo " |
|
gnome-session $VERSION |
|
`echo gnome-session $VERSION | sed "s/./=/g"` |
|
|
|
prefix: ${prefix} |
|
exec_prefix: ${exec_prefix} |
|
libdir: ${libdir} |
|
bindir: ${bindir} |
|
sbindir: ${sbindir} |
|
sysconfdir: ${sysconfdir} |
|
localstatedir: ${localstatedir} |
|
diff --git a/data/Makefile.am b/data/Makefile.am |
|
index 413279a2..b5c3f5ec 100644 |
|
--- a/data/Makefile.am |
|
+++ b/data/Makefile.am |
|
@@ -1,67 +1,73 @@ |
|
SUBDIRS = icons |
|
|
|
uidir = $(pkgdatadir) |
|
ui_DATA = \ |
|
session-properties.ui |
|
|
|
if BUILD_SESSION_SELECTOR |
|
ui_DATA += session-selector.ui |
|
endif |
|
|
|
hwcompatdir = $(pkgdatadir) |
|
hwcompat_DATA = hardware-compatibility |
|
|
|
xsessiondir = $(datadir)/xsessions |
|
xsession_in_files = gnome.desktop.in gnome-xorg.desktop.in |
|
|
|
if BUILD_SESSION_SELECTOR |
|
xsession_in_files += gnome-custom-session.desktop.in |
|
endif |
|
|
|
xsession_DATA = $(xsession_in_files:.desktop.in=.desktop) |
|
|
|
wayland_sessiondir = $(datadir)/wayland-sessions |
|
wayland_session_in_files = gnome.desktop.in |
|
wayland_session_DATA = $(wayland_session_in_files:.desktop.in=.desktop) |
|
|
|
+desktopdir = $(datadir)/applications |
|
+desktop_in_files = gnome-session-properties.desktop.in |
|
+desktop_DATA = $(desktop_in_files:.desktop.in=.desktop) |
|
+ |
|
sessiondir = $(datadir)/gnome-session/sessions |
|
session_in_in_files = gnome.session.desktop.in.in gnome-dummy.session.desktop.in.in |
|
session_in_files = $(session_in_in_files:.session.desktop.in.in=.session.desktop.in) |
|
session_DATA = $(session_in_files:.session.desktop.in=.session) |
|
|
|
%.session.desktop.in: %.session.desktop.in.in Makefile |
|
$(AM_V_GEN)sed \ |
|
-e "s|\@LIBEXECDIR\@|$(libexecdir)|" \ |
|
$< > $@ |
|
|
|
%.session: %.session.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@ |
|
|
|
@INTLTOOL_DESKTOP_RULE@ |
|
@INTLTOOL_XML_NOMERGE_RULE@ |
|
|
|
gsettings_SCHEMAS = org.gnome.SessionManager.gschema.xml |
|
@GSETTINGS_RULES@ |
|
|
|
migrationdir = $(datadir)/GConf/gsettings |
|
dist_migration_DATA = gnome-session.convert |
|
|
|
EXTRA_DIST = \ |
|
$(xsession_in_files) \ |
|
$(session_in_in_files) \ |
|
$(wayland_session_in_files) \ |
|
$(gsettings_SCHEMAS:.xml=.xml.in) \ |
|
session-selector.ui \ |
|
gnome-custom-session.desktop.in \ |
|
$(ui_DATA) \ |
|
$(hwcompat_DATA) |
|
|
|
CLEANFILES = \ |
|
$(gsettings_SCHEMAS) \ |
|
$(xsession_DATA) \ |
|
$(wayland_session_DATA) \ |
|
+ $(desktop_DATA) \ |
|
$(session_DATA) |
|
|
|
DISTCLEANFILES = \ |
|
- $(gsettings_SCHEMAS) |
|
+ $(gsettings_SCHEMAS) \ |
|
+ $(desktop_in_files) |
|
|
|
-include $(top_srcdir)/git.mk |
|
diff --git a/data/gnome-session-properties.desktop.in.in b/data/gnome-session-properties.desktop.in.in |
|
new file mode 100644 |
|
index 00000000..3dc7b033 |
|
--- /dev/null |
|
+++ b/data/gnome-session-properties.desktop.in.in |
|
@@ -0,0 +1,15 @@ |
|
+[Desktop Entry] |
|
+_Name=Startup Applications |
|
+_Comment=Choose what applications to start when you log in |
|
+Exec=gnome-session-properties |
|
+Icon=session-properties |
|
+Terminal=false |
|
+Type=Application |
|
+StartupNotify=true |
|
+Categories=GTK;GNOME;Settings;X-GNOME-PersonalSettings; |
|
+OnlyShowIn=GNOME;Unity; |
|
+NoDisplay=true |
|
+X-GNOME-Bugzilla-Bugzilla=GNOME |
|
+X-GNOME-Bugzilla-Product=gnome-session |
|
+X-GNOME-Bugzilla-Component=gnome-session-properties |
|
+X-GNOME-Bugzilla-Version=@VERSION@ |
|
diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am |
|
index 02e5c62a..28794233 100644 |
|
--- a/doc/man/Makefile.am |
|
+++ b/doc/man/Makefile.am |
|
@@ -1,29 +1,30 @@ |
|
XSLTPROC_FLAGS = \ |
|
--nonet \ |
|
--stringparam man.output.quietly 1 \ |
|
--stringparam funcsynopsis.style ansi \ |
|
--stringparam man.th.extra1.suppress 1 \ |
|
--stringparam man.authors.section.enabled 0 \ |
|
--stringparam man.copyright.section.enabled 0 |
|
|
|
.xml.1: |
|
$(AM_V_GEN) $(XSLTPROC) $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< |
|
|
|
man_MANS = \ |
|
gnome-session.1 \ |
|
+ gnome-session-properties.1 \ |
|
gnome-session-quit.1 \ |
|
gnome-session-inhibit.1 |
|
|
|
if BUILD_SESSION_SELECTOR |
|
man_MANS += gnome-session-selector.1 |
|
endif |
|
|
|
EXTRA_DIST = \ |
|
gnome-session-inhibit.xml \ |
|
gnome-session-selector.xml \ |
|
$(man_MANS) |
|
|
|
CLEANFILES = \ |
|
gnome-session-inhibit.1 |
|
|
|
-include $(top_srcdir)/git.mk |
|
diff --git a/doc/man/gnome-session-properties.1 b/doc/man/gnome-session-properties.1 |
|
new file mode 100644 |
|
index 00000000..c7ef1af3 |
|
--- /dev/null |
|
+++ b/doc/man/gnome-session-properties.1 |
|
@@ -0,0 +1,24 @@ |
|
+.\" |
|
+.\" gnome-session-properties manual page. |
|
+.\" (C) 2009-2010 Vincent Untz (vuntz@gnome.org) |
|
+.\" |
|
+.TH GNOME-SESSION-PROPERTIES 1 "GNOME" |
|
+.SH NAME |
|
+gnome-session-properties \- Configure applications to start on login |
|
+.SH SYNOPSIS |
|
+.B gnome-session-properties |
|
+.SH DESCRIPTION |
|
+.PP |
|
+The \fIgnome-session-properties\fP program enables the users to |
|
+configure what applications should be started on login, in addition to |
|
+the default startup applications configured on the system. |
|
+.PP |
|
+It also proposes an interface to save a snapshot of the currently |
|
+running applications so that they can automatically be restored to |
|
+their current state on your next GNOME session. |
|
+.SH BUGS |
|
+If you find bugs in the \fIgnome-session-properties\fP program, please report |
|
+these on https://bugzilla.gnome.org. |
|
+.SH SEE ALSO |
|
+.BR gnome-session(1) |
|
+.BR gnome-session-quit(1) |
|
diff --git a/po/POTFILES.in b/po/POTFILES.in |
|
index 76882645..5cb5123d 100644 |
|
--- a/po/POTFILES.in |
|
+++ b/po/POTFILES.in |
|
@@ -1,19 +1,24 @@ |
|
# List of source files containing translatable strings. |
|
# Please keep this file sorted alphabetically. |
|
+capplet/gsm-app-dialog.c |
|
+capplet/gsm-properties-dialog.c |
|
+capplet/gsp-app.c |
|
+capplet/main.c |
|
data/gnome-custom-session.desktop.in |
|
data/gnome.desktop.in |
|
data/gnome-xorg.desktop.in |
|
data/gnome-dummy.session.desktop.in.in |
|
data/gnome.session.desktop.in.in |
|
+data/gnome-session-properties.desktop.in.in |
|
[type: gettext/glade]data/session-selector.ui |
|
[type: gettext/glade]data/session-properties.ui |
|
gnome-session/gsm-fail-whale-dialog.c |
|
gnome-session/gsm-manager.c |
|
gnome-session/gsm-process-helper.c |
|
gnome-session/gsm-util.c |
|
gnome-session/gsm-xsmp-client.c |
|
gnome-session/gsm-xsmp-server.c |
|
gnome-session/main.c |
|
tools/gnome-session-inhibit.c |
|
tools/gnome-session-selector.c |
|
tools/gnome-session-quit.c |
|
-- |
|
2.14.2 |
|
|
|
|