diff -Nru gnome-session-3.30.1/capplet/gsm-app-dialog.c gnome-session-3.30.1/capplet/gsm-app-dialog.c --- gnome-session-3.30.1/capplet/gsm-app-dialog.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/capplet/gsm-app-dialog.c 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,540 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * 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 +#include +#include + +#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 -Nru gnome-session-3.30.1/capplet/gsm-app-dialog.h gnome-session-3.30.1/capplet/gsm-app-dialog.h --- gnome-session-3.30.1/capplet/gsm-app-dialog.h 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/capplet/gsm-app-dialog.h 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * 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 +#include + +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 -Nru gnome-session-3.30.1/capplet/gsm-properties-dialog.c gnome-session-3.30.1/capplet/gsm-properties-dialog.c --- gnome-session-3.30.1/capplet/gsm-properties-dialog.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/capplet/gsm-properties-dialog.c 2019-02-13 19:09:46.000000000 +0000 @@ -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 + * + * 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 +#include +#include + +#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 -Nru gnome-session-3.30.1/capplet/gsm-properties-dialog.h gnome-session-3.30.1/capplet/gsm-properties-dialog.h --- gnome-session-3.30.1/capplet/gsm-properties-dialog.h 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/capplet/gsm-properties-dialog.h 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * 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 +#include + +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 -Nru gnome-session-3.30.1/capplet/gsp-app.c gnome-session-3.30.1/capplet/gsp-app.c --- gnome-session-3.30.1/capplet/gsp-app.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/capplet/gsp-app.c 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,1129 @@ +/* -*- 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 + * + * 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 +#endif + +#include +#include + +#include +#include + +#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 ("%s\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 char * +_gsp_get_current_desktop () +{ + static char *current_desktop = NULL; + + /* Support XDG_CURRENT_DESKTOP environment variable; this can be used + * to abuse gnome-session in non-GNOME desktops. */ + if (!current_desktop) { + const char *desktop; + + desktop = g_getenv ("XDG_CURRENT_DESKTOP"); + + /* Note: if XDG_CURRENT_DESKTOP is set but empty, do as if it + * was not set */ + if (!desktop || desktop[0] == '\0') + current_desktop = g_strdup ("GNOME"); + else + current_desktop = g_strdup (desktop); + } + + /* Using "*" means skipping desktop-related checks */ + if (g_strcmp0 (current_desktop, "*") == 0) + return NULL; + + return current_desktop; +} + +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, + _gsp_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, + _gsp_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 -Nru gnome-session-3.30.1/capplet/gsp-app.h gnome-session-3.30.1/capplet/gsp-app.h --- gnome-session-3.30.1/capplet/gsp-app.h 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/capplet/gsp-app.h 2019-02-13 19:09:46.000000000 +0000 @@ -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 + * + * 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 +#include + +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 -Nru gnome-session-3.30.1/capplet/gsp-app-manager.c gnome-session-3.30.1/capplet/gsp-app-manager.c --- gnome-session-3.30.1/capplet/gsp-app-manager.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/capplet/gsp-app-manager.c 2019-02-13 19:09:46.000000000 +0000 @@ -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 + * + * 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 + +#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 -Nru gnome-session-3.30.1/capplet/gsp-app-manager.h gnome-session-3.30.1/capplet/gsp-app-manager.h --- gnome-session-3.30.1/capplet/gsp-app-manager.h 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/capplet/gsp-app-manager.h 2019-02-13 19:09:46.000000000 +0000 @@ -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 + * + * 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 + +#include + +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 -Nru gnome-session-3.30.1/capplet/gsp-keyfile.c gnome-session-3.30.1/capplet/gsp-keyfile.c --- gnome-session-3.30.1/capplet/gsp-keyfile.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/capplet/gsp-keyfile.c 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,207 @@ +/* + * 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 + */ + +#include + +#include + +#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; + gchar **current_desktops = { NULL }; + 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 (current_desktop && current_desktop[0] != '\0') + current_desktops = g_strsplit (current_desktop, ":", 0); + + if (only_show_in) { + found = FALSE; + for (i = 0; only_show_in[i] != NULL; i++) { + if (g_strv_contains ((const gchar * const *) current_desktops, only_show_in[i])) { + 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_strv_contains ((const gchar * const *) current_desktops, not_show_in[i])) { + found = TRUE; + break; + } + } + + g_strfreev (not_show_in); + + if (found) + return FALSE; + } + + g_strfreev (current_desktops); + + 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 -Nru gnome-session-3.30.1/capplet/gsp-keyfile.h gnome-session-3.30.1/capplet/gsp-keyfile.h --- gnome-session-3.30.1/capplet/gsp-keyfile.h 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/capplet/gsp-keyfile.h 2019-02-13 19:09:46.000000000 +0000 @@ -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 + */ + +#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 -Nru gnome-session-3.30.1/capplet/main.c gnome-session-3.30.1/capplet/main.c --- gnome-session-3.30.1/capplet/main.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/capplet/main.c 2019-02-13 19:09:46.000000000 +0000 @@ -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 + +#include +#include + +#include +#include + +#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 -Nru gnome-session-3.30.1/capplet/meson.build gnome-session-3.30.1/capplet/meson.build --- gnome-session-3.30.1/capplet/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/capplet/meson.build 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,36 @@ + +cflags = [ + '-DLOCALE_DIR="@0@"'.format(session_localedir), + '-DGTKBUILDER_DIR="@0@"'.format(session_pkgdatadir) +] + +deps = [ + gio_dep, + glib_dep, + gtk_dep +] + +sources = files( + 'main.c', + 'gsm-properties-dialog.c', + 'gsm-app-dialog.c', + 'gsp-app.c', + 'gsp-app-manager.c', + 'gsp-keyfile.c', +) + +includes = [ + top_inc, + include_directories('../gnome-session') +] + +executable( + 'gnome-session-properties', + sources, + include_directories: includes, + dependencies: deps, + c_args: cflags, + link_with: libgsmutil, + install: true, + install_dir: session_bindir +) diff -Nru gnome-session-3.30.1/data/gnome.desktop.in.in gnome-session-3.30.1/data/gnome.desktop.in.in --- gnome-session-3.30.1/data/gnome.desktop.in.in 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/data/gnome.desktop.in.in 2019-02-13 19:09:46.000000000 +0000 @@ -1,7 +1,7 @@ [Desktop Entry] Name=GNOME Comment=This session logs you into GNOME -Exec=@bindir@/gnome-session +Exec=@bindir@/gnome-session --session=gnome TryExec=@bindir@/gnome-session Type=Application DesktopNames=GNOME diff -Nru gnome-session-3.30.1/data/gnome-session-properties.desktop.in gnome-session-3.30.1/data/gnome-session-properties.desktop.in --- gnome-session-3.30.1/data/gnome-session-properties.desktop.in 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/gnome-session-properties.desktop.in 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,14 @@ +[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; +X-GNOME-Bugzilla-Bugzilla=GNOME +X-GNOME-Bugzilla-Product=gnome-session +X-GNOME-Bugzilla-Component=gnome-session-properties +X-GNOME-Bugzilla-Version=@VERSION@ diff -Nru gnome-session-3.30.1/data/icons/16x16/session-properties.svg gnome-session-3.30.1/data/icons/16x16/session-properties.svg --- gnome-session-3.30.1/data/icons/16x16/session-properties.svg 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/icons/16x16/session-properties.svg 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,394 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Jakub Steiner + + + http://jimmac.musichall.cz + + Gnome Session Properties + + + + + + + + + + + + + + + + + + + + + diff -Nru gnome-session-3.30.1/data/icons/22x22/session-properties.svg gnome-session-3.30.1/data/icons/22x22/session-properties.svg --- gnome-session-3.30.1/data/icons/22x22/session-properties.svg 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/icons/22x22/session-properties.svg 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,440 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Jakub Steiner + + + http://jimmac.musichall.cz + + Gnome Session Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru gnome-session-3.30.1/data/icons/32x32/session-properties.svg gnome-session-3.30.1/data/icons/32x32/session-properties.svg --- gnome-session-3.30.1/data/icons/32x32/session-properties.svg 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/icons/32x32/session-properties.svg 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,490 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Jakub Steiner + + + http://jimmac.musichall.cz + + Gnome Session Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru gnome-session-3.30.1/data/icons/scalable/session-properties.svg gnome-session-3.30.1/data/icons/scalable/session-properties.svg --- gnome-session-3.30.1/data/icons/scalable/session-properties.svg 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/icons/scalable/session-properties.svg 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,515 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Jakub Steiner + + + http://jimmac.musichall.cz + + Gnome Session Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -Nru gnome-session-3.30.1/data/icons/symbolic/session-properties-symbolic.svg gnome-session-3.30.1/data/icons/symbolic/session-properties-symbolic.svg --- gnome-session-3.30.1/data/icons/symbolic/session-properties-symbolic.svg 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/icons/symbolic/session-properties-symbolic.svg 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,28 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + diff -Nru gnome-session-3.30.1/data/meson.build gnome-session-3.30.1/data/meson.build --- gnome-session-3.30.1/data/meson.build 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/data/meson.build 2019-02-13 19:09:46.000000000 +0000 @@ -1,8 +1,10 @@ desktop_plain = 'gnome' desktops = [ - desktop_plain, - 'gnome-xorg' + 'gnome-xorg', + 'ubuntu', + 'ubuntu-communitheme-snap', + 'unity', ] if enable_session_selector @@ -41,11 +43,46 @@ install: true, install_dir: install_dir ) + +endforeach + +wayland_desktops = [ + desktop_plain, + 'ubuntu-wayland', + 'ubuntu-communitheme-snap-wayland', +] + +foreach name: wayland_desktops + desktop_conf = configuration_data() + desktop_conf.set('bindir', session_bindir) + + desktop = name + '.desktop' + + desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf + ) + + install_dir = join_paths(session_datadir, 'wayland-sessions') + + desktop_target = i18n.merge_file( + desktop, + type: 'desktop', + input: desktop_in, + output: desktop, + po_dir: po_dir, + install: true, + install_dir: install_dir + ) + endforeach sessions = [ 'gnome.session', - 'gnome-dummy.session' + 'gnome-dummy.session', + 'ubuntu.session', + 'unity.session' ] foreach session: sessions @@ -94,7 +131,30 @@ data += files('session-selector.ui') endif +desktop = configure_file( + input: 'gnome-session-properties.desktop.in', + output: 'gnome-session-properties.desktop', + configuration: desktop_conf +) + +install_data( + desktop, + install_dir : join_paths(session_datadir, 'applications') +) + +data += files('session-properties.ui') + install_data( data, install_dir: session_pkgdatadir ) + +icondir = join_paths(get_option('datadir'), 'icons/hicolor') +foreach size: ['16x16', '22x22', '32x32', 'symbolic'] + install_subdir('icons/' + size, install_dir: icondir) +endforeach + +install_data( + 'icons/scalable/session-properties.svg', + install_dir: icondir + '/scalable/apps' +) diff -Nru gnome-session-3.30.1/data/session-properties.ui gnome-session-3.30.1/data/session-properties.ui --- gnome-session-3.30.1/data/session-properties.ui 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/session-properties.ui 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,303 @@ + + + + + + True + 6 + + + True + 12 + vertical + 3 + + + True + 0 + 3 + 3 + Additional startup _programs: + True + session_properties_treeview + + + False + 0 + + + + + True + 6 + + + True + True + never + automatic + etched-in + + + 210 + True + True + + + + + 0 + + + + + True + 6 + start + + + gtk-add + True + True + True + True + + + False + False + 0 + + + + + gtk-remove + True + False + True + True + True + + + False + False + 1 + + + + + gtk-edit + True + False + True + True + True + + + False + False + 2 + + + + + False + False + 1 + + + + + 1 + + + + + + + False + 12 + vertical + 6 + + + _Automatically remember running applications when logging out + True + True + False + True + True + + + False + False + 0 + + + + + True + + + True + True + + + True + 4 + + + True + gtk-save + + + False + False + 0 + + + + + True + _Remember Currently Running Applications + True + + + 1 + + + + + + + False + False + 0 + + + + + False + False + 1 + + + + + 1 + + + + + True + 6 + 3 + 2 + 12 + 6 + + + True + 12 + + + True + True + + + + 0 + + + + + Browse… + True + True + True + + + False + False + 1 + + + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + True + + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + True + True + + + + 1 + 2 + GTK_FILL + + + + + True + 0 + Comm_ent: + True + label2 + + + 2 + 3 + GTK_FILL + GTK_FILL + + + + + True + 0 + Co_mmand: + True + session_properties_command_entry + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + 0 + _Name: + True + session_properties_name_entry + + + GTK_FILL + GTK_FILL + + + + diff -Nru gnome-session-3.30.1/data/ubuntu-communitheme-snap.desktop.in.in gnome-session-3.30.1/data/ubuntu-communitheme-snap.desktop.in.in --- gnome-session-3.30.1/data/ubuntu-communitheme-snap.desktop.in.in 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/ubuntu-communitheme-snap.desktop.in.in 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=Ubuntu with communitheme snap +Comment=This session logs you into Ubuntu +Exec=env GNOME_SHELL_SESSION_MODE=ubuntu-communitheme /snap/communitheme/current/session +TryExec=/snap/communitheme/current/session +Type=Application +DesktopNames=communitheme:ubuntu:GNOME diff -Nru gnome-session-3.30.1/data/ubuntu-communitheme-snap-wayland.desktop.in.in gnome-session-3.30.1/data/ubuntu-communitheme-snap-wayland.desktop.in.in --- gnome-session-3.30.1/data/ubuntu-communitheme-snap-wayland.desktop.in.in 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/ubuntu-communitheme-snap-wayland.desktop.in.in 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=Ubuntu with communitheme snap on Wayland +Comment=This session logs you into Ubuntu +Exec=env GNOME_SHELL_SESSION_MODE=ubuntu-communitheme /snap/communitheme/current/session +TryExec=/snap/communitheme/current/session +Type=Application +DesktopNames=communitheme:ubuntu:GNOME diff -Nru gnome-session-3.30.1/data/ubuntu.desktop.in.in gnome-session-3.30.1/data/ubuntu.desktop.in.in --- gnome-session-3.30.1/data/ubuntu.desktop.in.in 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/ubuntu.desktop.in.in 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=Ubuntu +Comment=This session logs you into Ubuntu +Exec=env GNOME_SHELL_SESSION_MODE=ubuntu @bindir@/gnome-session --session=ubuntu +TryExec=@bindir@/gnome-shell +Type=Application +DesktopNames=ubuntu:GNOME diff -Nru gnome-session-3.30.1/data/ubuntu.session.desktop.in.in gnome-session-3.30.1/data/ubuntu.session.desktop.in.in --- gnome-session-3.30.1/data/ubuntu.session.desktop.in.in 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/ubuntu.session.desktop.in.in 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,3 @@ +[GNOME Session] +Name=Ubuntu +RequiredComponents=org.gnome.Shell;org.gnome.SettingsDaemon.A11ySettings;org.gnome.SettingsDaemon.Clipboard;org.gnome.SettingsDaemon.Color;org.gnome.SettingsDaemon.Datetime;org.gnome.SettingsDaemon.Housekeeping;org.gnome.SettingsDaemon.Keyboard;org.gnome.SettingsDaemon.MediaKeys;org.gnome.SettingsDaemon.Mouse;org.gnome.SettingsDaemon.Power;org.gnome.SettingsDaemon.PrintNotifications;org.gnome.SettingsDaemon.Rfkill;org.gnome.SettingsDaemon.ScreensaverProxy;org.gnome.SettingsDaemon.Sharing;org.gnome.SettingsDaemon.Smartcard;org.gnome.SettingsDaemon.Sound;org.gnome.SettingsDaemon.Wacom;org.gnome.SettingsDaemon.XSettings; diff -Nru gnome-session-3.30.1/data/ubuntu-wayland.desktop.in.in gnome-session-3.30.1/data/ubuntu-wayland.desktop.in.in --- gnome-session-3.30.1/data/ubuntu-wayland.desktop.in.in 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/ubuntu-wayland.desktop.in.in 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=Ubuntu on Wayland +Comment=This session logs you into Ubuntu +Exec=env GNOME_SHELL_SESSION_MODE=ubuntu @bindir@/gnome-session --session=ubuntu +TryExec=@bindir@/gnome-shell +Type=Application +DesktopNames=ubuntu:GNOME diff -Nru gnome-session-3.30.1/data/unity.desktop.in.in gnome-session-3.30.1/data/unity.desktop.in.in --- gnome-session-3.30.1/data/unity.desktop.in.in 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/unity.desktop.in.in 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=Unity +Comment=This session logs you into Unity +Exec=/usr/lib/gnome-session/run-systemd-session unity-session.target +TryExec=@bindir@/unity +Type=Application +DesktopNames=Unity:Unity7:ubuntu diff -Nru gnome-session-3.30.1/data/unity.session.desktop.in.in gnome-session-3.30.1/data/unity.session.desktop.in.in --- gnome-session-3.30.1/data/unity.session.desktop.in.in 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/data/unity.session.desktop.in.in 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,4 @@ +[GNOME Session] +Name=Unity +RequiredComponents=unity-settings-daemon; +DesktopName=Unity:Unity7:ubuntu diff -Nru gnome-session-3.30.1/debian/changelog gnome-session-3.30.1/debian/changelog --- gnome-session-3.30.1/debian/changelog 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/changelog 2019-02-13 19:09:46.000000000 +0000 @@ -1,3 +1,9 @@ +gnome-session (3.30.1-2ubuntu1+201902131909~ubuntu19.04.1) disco; urgency=low + + * Auto build. + + -- Khurshid Alam Wed, 13 Feb 2019 19:09:46 +0000 + gnome-session (3.30.1-2ubuntu1) disco; urgency=medium * Merge with Debian, remaining changes: diff -Nru gnome-session-3.30.1/debian/data/change-background-unity.nemo_action gnome-session-3.30.1/debian/data/change-background-unity.nemo_action --- gnome-session-3.30.1/debian/data/change-background-unity.nemo_action 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/debian/data/change-background-unity.nemo_action 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,82 @@ +[Nemo Action] + +Name=Change Desktop _Background + +Comment=Change the Unity desktop background + +Exec=unity-control-center appearance + +Selection=None + +Icon-Name=cs-backgrounds-symbolic + +Extensions=any; + + +Conditions=desktop;dbus com.canonical.Unity + +Name[jv]=Ganti Gambar _Buri Desktop +Name[pt_BR]=Alterar _plano de fundo +Name[ia]=Cambiar le fundo (_Background) del scriptorio +Name[ko]=바탕화면 배경 바꾸기(_B) +Name[kk]=Ф_он суретін ауыстыру +Name[af]=Verander Werkskerm Agtergrond +Name[el]=Αλλαγή _εικόνας επιφάνειας εργασίας +Name[fr_CA]=Modifier l’arrière-plan du _bureau +Name[ro]=Schim_bă imaginea de fundal +Name[da]=Skift skrive_bordsbaggrund +Name[gl]=Cambiar o _fondo do escritorio +Name[vi]=Đổi Ảnh _nền Màn hình +Name[ca]=Canvia el _fons de l'escriptori +Name[de]=_Hintergrund des Schreibtisches ändern +Name[sr@latin]=Iz_meni pozadinu +Name[he]=שנה את _רקע שולחן העבודה +Name[fr]=Modifier l'arrière-plan du _bureau +Name[ga]=Athraigh Cúlra_na Deisce +Name[bs]=Promijeni _pozadinu desktopa +Name[id]=Ubah Latar _Belakang Desktop +Name[sl]=Spremeni _ozadje namizja +Name[sv]=Byt skrivbordsbak_grund +Name[tg]=Тағйир додани пасзаминаи _мизи корӣ +Name[sq]=Ndrysho _sfondin e desktopit +Name[hu]=Asztal _hátterének módosítása +Name[fi]=Muuta työpöydän _taustaa +Name[ar]=تغيير خلفية _سطح المكتب +Name[en_AU]=Change Desktop _Background +Name[th]=_เปลี่ยนพื้นหลังของพื้นโต๊ะ +Name[ku]=Zemîna Sermaseyê Biguherîne +Name[ru]=Изменить _фон рабочего стола +Name[bg]=Промяна на _фона +Name[is]=Breyta _bakgrunni skjáborðs +Name[oc]=Modificar lo _rèire plan del burèu +Name[ur]=ڈیسک ٹاپ کا _پس منظر بدلیں +Name[gd]=Atharraich _cùlaibh an desktop +Name[sk]=Zmeniť _pozadie plochy +Name[zh_TW]=變更桌面背景(_B) +Name[zh_HK]=變更桌面背景(_B) +Name[be]=Змяніць _фон працоўнага стала +Name[en_GB]=Change Desktop _Background +Name[et]=Vaheta töölaua _taust +Name[hr]=Promijeni pozadinu _radne površine +Name[cy]=Newid Cefndir y _Bwrdd Gwaith +Name[ta]=(_B)பணிமேடை பின்னனியை மாற்று +Name[eu]=Aldatu _mahaigainaren atzeko planoa +Name[lt]=Keisti darbalaukio _foną +Name[uk]=Змінити ф_он стільниці +Name[ja]=背景を変更 (_B) +Name[ast]=Camudar el fon_du d'escritoriu +Name[ml]=ഡെസ്ക്ടോപ്പിന്റെ _പശ്ചാത്തലം മാറ്റുക +Name[bn]=ডেস্কটপের পটভূমি পরিবর্তন করুন (_B) +Name[sr]=Из_мени позадину +Name[zh_CN]=更改桌面背景(_B) +Name[it]=Ca_mbia sfondo desktop +Name[ms]=Tukar Latar _Belakang Desktop +Name[tr]=Masaüstü _Arkaplanını Değiştir +Name[pt]=Alterar _fundo da área de trabalho +Name[es]=Cambiar el _fondo del escritorio +Name[pl]=Zmień _tło pulpitu +Name[am]=የዴስክቶፕ _መደብ መቀየሪያ +Name[nb]=Endre skrivebords_bakgrunn +Name[cs]=Změnit _pozadí plochy +Name[nl]=Werk_bladachtergrond wijzigen +Name[nds]=Desktop_Hintergrund ändern diff -Nru gnome-session-3.30.1/debian/data/gnome-session.service gnome-session-3.30.1/debian/data/gnome-session.service --- gnome-session-3.30.1/debian/data/gnome-session.service 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/data/gnome-session.service 2019-02-13 19:09:46.000000000 +0000 @@ -8,4 +8,4 @@ [ "$DESKTOP_SESSION" = gnome-flashback-metacity ] && \ exec gnome-session --session=$DESKTOP_SESSION --disable-acceleration-check || \ exec gnome-session --session=$DESKTOP_SESSION' -ExecStopPost=-/bin/sh -xc '[ "${XDG_SESSION_PATH}" = "${GNOME_SESSION_XDG_SESSION_PATH}" ] && /usr/bin/dbus-send --type=method_call --address=${UPSTART_SESSION} /com/ubuntu/Upstart com.ubuntu.Upstart0_6.EndSession' +ExecStopPost=-/bin/sh -xc '[ "${XDG_SESSION_PATH}" = "${GNOME_SESSION_XDG_SESSION_PATH}" ] && [ "${UPSTART_SESSION}" ] && /usr/bin/dbus-send --type=method_call --address="${UPSTART_SESSION}" /com/ubuntu/Upstart com.ubuntu.Upstart0_6.EndSession' diff -Nru gnome-session-3.30.1/debian/data/nemo-unity-autostart.desktop gnome-session-3.30.1/debian/data/nemo-unity-autostart.desktop --- gnome-session-3.30.1/debian/data/nemo-unity-autostart.desktop 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/debian/data/nemo-unity-autostart.desktop 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Name=Nemo +Comment=Start Nemo desktop at log in +Exec=nemo-desktop +OnlyShowIn=Unity; +AutostartCondition=GSettings org.nemo.desktop show-desktop-icons +X-GNOME-AutoRestart=true +X-GNOME-Autostart-Delay=2 +NoDisplay=false diff -Nru gnome-session-3.30.1/debian/data/set-as-background-unity.nemo_action gnome-session-3.30.1/debian/data/set-as-background-unity.nemo_action --- gnome-session-3.30.1/debian/data/set-as-background-unity.nemo_action 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/debian/data/set-as-background-unity.nemo_action 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,68 @@ +[Nemo Action] + +Name=Set as Wallpaper... + +Comment=Set the selected image as your Unity desktop wallpaper + +Exec=gsettings set org.gnome.desktop.background picture-uri "%U" + +Selection=s + +Mimetypes=image/*; + +Conditions=dbus com.canonical.Unity; +Name[pt_BR]=Definir como plano de fundo... +Name[ia]=Fixar como tapete de papiro... +Name[ko]=배경화면으로 설정하기... +Name[el]=Ορισμός ως Ταπετσαρίας... +Name[fr_CA]=Définir en tant que fond d’écran... +Name[ro]=Setează ca imagine de fundal... +Name[da]=Anvend som baggrund … +Name[gl]=Estabelecer como fondo de pantalla +Name[vi]=Đặt làm Hình nền... +Name[ca]=Estableix com a fons de pantalla... +Name[de]=Als Hintergrund festlegen … +Name[sr@latin]=Postavi kao pozadinu... +Name[he]=הגדרה כרקע... +Name[fr]=Définir en tant que fond d'écran... +Name[ga]=Socraigh mar Cúlra na Deisce +Name[bs]=Postavi kao pozadinu... +Name[id]=Pasang sebagai Wallpaper... +Name[sl]=Nastavi kot sliko ozadja ... +Name[sv]=Ange som skrivbordsbakgrund... +Name[tg]=Танзим кардан ҳамчун тасвири экран... +Name[hu]=Beállítás háttérképként… +Name[fi]=Aseta taustakuvaksi... +Name[ar]=تعيين كخلفية... +Name[th]=ตั้งเป็นภาพพื้นหลัง... +Name[ru]=Сделать фоном... +Name[bg]=Задаване като тапет... +Name[is]=Gera að veggfóðri... +Name[ur]=بطور وال پیپر سیٹ کریں... +Name[gd]=Suidhich mar phàipear-balla... +Name[sk]=Nastaviť ako pozadie... +Name[zh_TW]=設為桌布... +Name[zh_HK]=設為桌布... +Name[be]=Ўсталяваць у якасці фонавага малюнка... +Name[en_GB]=Set as Wallpaper... +Name[et]=Määra taustapildiks... +Name[hr]=Postavi kao pozadinu... +Name[cy]=Gosod fel Papur Wal... +Name[eu]=Ezarri atzeko plano gisa... +Name[lt]=Nustatyti kaip darbalaukio foną +Name[uk]=Встановити як фон... +Name[ja]=壁紙に設定... +Name[ast]=Afitar como fondu pantalla... +Name[ml]=പശ്ചാത്തലചിത്രമായി സജ്ജീകരിക്കുക... +Name[sr]=Постави као позадину... +Name[zh_CN]=设置为壁纸... +Name[it]=Imposta come sfondo... +Name[ms]=Tetapkan sebagai Kertas Dinding... +Name[tr]=Duvarkağıdı olarak ayarla... +Name[pt]=Definir como fundo do Ambiente de Trabalho +Name[es]=Establecer como fondo... +Name[pl]=Ustaw jako tło pulpitu... +Name[am]=እንደ ግድግዳ ወረቀት ማሰናጃ... +Name[nb]=Sett som bakgrunn... +Name[cs]=Nastavit jako pozadí plochy… +Name[nl]=Gebruik als bureaubladachtergrond... diff -Nru gnome-session-3.30.1/debian/git-build-recipe.manifest gnome-session-3.30.1/debian/git-build-recipe.manifest --- gnome-session-3.30.1/debian/git-build-recipe.manifest 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/debian/git-build-recipe.manifest 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,2 @@ +# git-build-recipe format 0.4 deb-version {debversion}+201902131909 +lp:~khurshid-alam/ubuntu/+source/gnome-session git-commit:82a49aa69b295fa20766e3124b67d4d6450d9e11 diff -Nru gnome-session-3.30.1/debian/patches/debian/Revert-main-Remove-GNOME_DESKTOP_SESSION_ID-envvar.patch gnome-session-3.30.1/debian/patches/debian/Revert-main-Remove-GNOME_DESKTOP_SESSION_ID-envvar.patch --- gnome-session-3.30.1/debian/patches/debian/Revert-main-Remove-GNOME_DESKTOP_SESSION_ID-envvar.patch 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/patches/debian/Revert-main-Remove-GNOME_DESKTOP_SESSION_ID-envvar.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -From: =?utf-8?b?Ik1hcmNvIFRyZXZpc2FuIChUcmV2acOxbyki?= -Date: Wed, 15 Aug 2018 17:13:57 +0200 -Subject: Revert "main: Remove GNOME_DESKTOP_SESSION_ID envvar" - -This reverts commit 00e0e6226371d53f651cc881e74c0543192c94a8. - -Keep this for compatibility reasons, many apps still depends on this - -This is Debian-specific - upstream have chosen to remove this compatibility but -we'll keep it. - -Forwarded: not-needed ---- - gnome-session/main.c | 5 +++++ - 1 file changed, 5 insertions(+) - -diff --git a/gnome-session/main.c b/gnome-session/main.c -index 9a7f554..b1ac385 100644 ---- a/gnome-session/main.c -+++ b/gnome-session/main.c -@@ -408,6 +408,11 @@ main (int argc, char **argv) - g_free (ibus_path); - } - -+ /* Some third-party programs rely on GNOME_DESKTOP_SESSION_ID to -+ * detect if GNOME is running. We keep this for compatibility reasons. -+ */ -+ gsm_util_setenv ("GNOME_DESKTOP_SESSION_ID", "this-is-deprecated"); -+ - /* We want to use the GNOME menus which has the designed categories. - */ - gsm_util_setenv ("XDG_MENU_PREFIX", "gnome-"); diff -Nru gnome-session-3.30.1/debian/patches/revert_remove_gnome_session_properties.patch gnome-session-3.30.1/debian/patches/revert_remove_gnome_session_properties.patch --- gnome-session-3.30.1/debian/patches/revert_remove_gnome_session_properties.patch 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/patches/revert_remove_gnome_session_properties.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,6266 +0,0 @@ -From: Tim Lunn -Date: Sat, 3 Mar 2018 00:15:04 +0100 -Subject: [PATCH] Revert "Remove gnome-session-properties" - -This reverts commits ea285af9 and 0c6fe6ca and ports the build to meson. - -https://bugzilla.gnome.org/show_bug.cgi?id=708923 -https://bugzilla.gnome.org/show_bug.cgi?id=766836 - -Ubuntu note: .png icons were also removed but they aren't as important -as svg icons. .png icons don't fit in a quilt patch. ---- - 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 | 1129 ++++++++++++++++++++ - capplet/gsp-app.h | 108 ++ - capplet/gsp-keyfile.c | 207 ++++ - capplet/gsp-keyfile.h | 65 ++ - capplet/main.c | 108 ++ - capplet/meson.build | 36 + - data/gnome-session-properties.desktop.in | 14 + - data/icons/16x16/session-properties.svg | 394 +++++++ - data/icons/22x22/session-properties.svg | 440 ++++++++ - data/icons/32x32/session-properties.svg | 490 +++++++++ - data/icons/scalable/session-properties.svg | 515 +++++++++ - .../icons/symbolic/session-properties-symbolic.svg | 28 + - data/meson.build | 23 + - data/session-properties.ui | 303 ++++++ - doc/man/gnome-session-properties.1 | 24 + - doc/man/gnome-session.1 | 2 + - doc/man/meson.build | 3 +- - meson.build | 1 + - po/POTFILES.in | 6 + - po/POTFILES.skip | 1 + - 26 files changed, 6007 insertions(+), 1 deletion(-) - 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 capplet/meson.build - create mode 100644 data/gnome-session-properties.desktop.in - create mode 100644 data/icons/16x16/session-properties.svg - create mode 100644 data/icons/22x22/session-properties.svg - create mode 100644 data/icons/32x32/session-properties.svg - create mode 100644 data/icons/scalable/session-properties.svg - create mode 100644 data/icons/symbolic/session-properties-symbolic.svg - create mode 100644 data/session-properties.ui - create mode 100644 doc/man/gnome-session-properties.1 - -diff --git a/capplet/gsm-app-dialog.c b/capplet/gsm-app-dialog.c -new file mode 100644 -index 0000000..e7369dd ---- /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 -+ * -+ * 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 -+#include -+#include -+ -+#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 0000000..ced0628 ---- /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 -+ * -+ * 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 -+#include -+ -+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 0000000..33812b8 ---- /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 -+ * -+ * 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 -+#include -+#include -+ -+#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 0000000..df4915e ---- /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 -+ * -+ * 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 -+#include -+ -+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 0000000..bcd6d40 ---- /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 -+ * -+ * 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 -+ -+#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 0000000..777f8d6 ---- /dev/null -+++ b/capplet/gsp-app-manager.h -@@ -0,0 +1,81 @@ -+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- -+ * -+ * Copyright (C) 1999 Free Software Foundation, Inc. -+ * Copyright (C) 2007, 2009 Vincent Untz. -+ * Copyright (C) 2008 Lucas Rocha. -+ * Copyright (C) 2008 William Jon McCann -+ * -+ * 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 -+ -+#include -+ -+G_BEGIN_DECLS -+ -+#define GSP_TYPE_APP_MANAGER (gsp_app_manager_get_type ()) -+#define GSP_APP_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSP_TYPE_APP_MANAGER, GspAppManager)) -+#define GSP_APP_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSP_TYPE_APP_MANAGER, GspAppManagerClass)) -+#define GSP_IS_APP_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSP_TYPE_APP_MANAGER)) -+#define GSP_IS_APP_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSP_TYPE_APP_MANAGER)) -+#define GSP_APP_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSP_TYPE_APP_MANAGER, GspAppManagerClass)) -+ -+typedef struct _GspAppManager GspAppManager; -+typedef struct _GspAppManagerClass GspAppManagerClass; -+ -+typedef struct _GspAppManagerPrivate GspAppManagerPrivate; -+ -+struct _GspAppManagerClass -+{ -+ GObjectClass parent_class; -+ -+ void (* added) (GspAppManager *manager, -+ GspApp *app); -+ void (* removed) (GspAppManager *manager, -+ GspApp *app); -+}; -+ -+struct _GspAppManager -+{ -+ GObject parent_instance; -+ -+ GspAppManagerPrivate *priv; -+}; -+ -+GType gsp_app_manager_get_type (void); -+ -+GspAppManager *gsp_app_manager_get (void); -+ -+void gsp_app_manager_fill (GspAppManager *manager); -+ -+GSList *gsp_app_manager_get_apps (GspAppManager *manager); -+ -+GspApp *gsp_app_manager_find_app_with_basename (GspAppManager *manager, -+ const char *basename); -+ -+const char *gsp_app_manager_get_dir (GspAppManager *manager, -+ unsigned int index); -+ -+void gsp_app_manager_add (GspAppManager *manager, -+ GspApp *app); -+ -+G_END_DECLS -+ -+#endif /* __GSP_APP_MANAGER_H */ -diff --git a/capplet/gsp-app.c b/capplet/gsp-app.c -new file mode 100644 -index 0000000..1fa5496 ---- /dev/null -+++ b/capplet/gsp-app.c -@@ -0,0 +1,1129 @@ -+/* -*- 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 -+ * -+ * 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 -+#endif -+ -+#include -+#include -+ -+#include -+#include -+ -+#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 ("%s\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 char * -+_gsp_get_current_desktop () -+{ -+ static char *current_desktop = NULL; -+ -+ /* Support XDG_CURRENT_DESKTOP environment variable; this can be used -+ * to abuse gnome-session in non-GNOME desktops. */ -+ if (!current_desktop) { -+ const char *desktop; -+ -+ desktop = g_getenv ("XDG_CURRENT_DESKTOP"); -+ -+ /* Note: if XDG_CURRENT_DESKTOP is set but empty, do as if it -+ * was not set */ -+ if (!desktop || desktop[0] == '\0') -+ current_desktop = g_strdup ("GNOME"); -+ else -+ current_desktop = g_strdup (desktop); -+ } -+ -+ /* Using "*" means skipping desktop-related checks */ -+ if (g_strcmp0 (current_desktop, "*") == 0) -+ return NULL; -+ -+ return current_desktop; -+} -+ -+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, -+ _gsp_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, -+ _gsp_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 0000000..a199795 ---- /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 -+ * -+ * 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 -+#include -+ -+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 0000000..48c8e31 ---- /dev/null -+++ b/capplet/gsp-keyfile.c -@@ -0,0 +1,207 @@ -+/* -+ * 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 -+ */ -+ -+#include -+ -+#include -+ -+#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; -+ gchar **current_desktops = { NULL }; -+ 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 (current_desktop && current_desktop[0] != '\0') -+ current_desktops = g_strsplit (current_desktop, ":", 0); -+ -+ if (only_show_in) { -+ found = FALSE; -+ for (i = 0; only_show_in[i] != NULL; i++) { -+ if (g_strv_contains ((const gchar * const *) current_desktops, only_show_in[i])) { -+ 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_strv_contains ((const gchar * const *) current_desktops, not_show_in[i])) { -+ found = TRUE; -+ break; -+ } -+ } -+ -+ g_strfreev (not_show_in); -+ -+ if (found) -+ return FALSE; -+ } -+ -+ g_strfreev (current_desktops); -+ -+ 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 0000000..d94f667 ---- /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 -+ */ -+ -+#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 0000000..3c7177b ---- /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 -+ -+#include -+#include -+ -+#include -+#include -+ -+#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/capplet/meson.build b/capplet/meson.build -new file mode 100644 -index 0000000..8bc9f58 ---- /dev/null -+++ b/capplet/meson.build -@@ -0,0 +1,36 @@ -+ -+cflags = [ -+ '-DLOCALE_DIR="@0@"'.format(session_localedir), -+ '-DGTKBUILDER_DIR="@0@"'.format(session_pkgdatadir) -+] -+ -+deps = [ -+ gio_dep, -+ glib_dep, -+ gtk_dep -+] -+ -+sources = files( -+ 'main.c', -+ 'gsm-properties-dialog.c', -+ 'gsm-app-dialog.c', -+ 'gsp-app.c', -+ 'gsp-app-manager.c', -+ 'gsp-keyfile.c', -+) -+ -+includes = [ -+ top_inc, -+ include_directories('../gnome-session') -+] -+ -+executable( -+ 'gnome-session-properties', -+ sources, -+ include_directories: includes, -+ dependencies: deps, -+ c_args: cflags, -+ link_with: libgsmutil, -+ install: true, -+ install_dir: session_bindir -+) -diff --git a/data/gnome-session-properties.desktop.in b/data/gnome-session-properties.desktop.in -new file mode 100644 -index 0000000..a0b2c6b ---- /dev/null -+++ b/data/gnome-session-properties.desktop.in -@@ -0,0 +1,14 @@ -+[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; -+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/data/icons/16x16/session-properties.svg b/data/icons/16x16/session-properties.svg -new file mode 100644 -index 0000000..4f7c37f ---- /dev/null -+++ b/data/icons/16x16/session-properties.svg -@@ -0,0 +1,394 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ image/svg+xml -+ -+ -+ -+ Jakub Steiner -+ -+ -+ http://jimmac.musichall.cz -+ -+ Gnome Session Properties -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/data/icons/22x22/session-properties.svg b/data/icons/22x22/session-properties.svg -new file mode 100644 -index 0000000..1d0afda ---- /dev/null -+++ b/data/icons/22x22/session-properties.svg -@@ -0,0 +1,440 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ image/svg+xml -+ -+ -+ -+ Jakub Steiner -+ -+ -+ http://jimmac.musichall.cz -+ -+ Gnome Session Properties -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/data/icons/32x32/session-properties.svg b/data/icons/32x32/session-properties.svg -new file mode 100644 -index 0000000..5bb1455 ---- /dev/null -+++ b/data/icons/32x32/session-properties.svg -@@ -0,0 +1,490 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ image/svg+xml -+ -+ -+ -+ Jakub Steiner -+ -+ -+ http://jimmac.musichall.cz -+ -+ Gnome Session Properties -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/data/icons/scalable/session-properties.svg b/data/icons/scalable/session-properties.svg -new file mode 100644 -index 0000000..cec2c39 ---- /dev/null -+++ b/data/icons/scalable/session-properties.svg -@@ -0,0 +1,515 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ image/svg+xml -+ -+ -+ -+ Jakub Steiner -+ -+ -+ http://jimmac.musichall.cz -+ -+ Gnome Session Properties -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/data/icons/symbolic/session-properties-symbolic.svg b/data/icons/symbolic/session-properties-symbolic.svg -new file mode 100644 -index 0000000..497fb9d ---- /dev/null -+++ b/data/icons/symbolic/session-properties-symbolic.svg -@@ -0,0 +1,28 @@ -+ -+ -+ -+ -+ -+ -+ -+ image/svg+xml -+ -+ Gnome Symbolic Icon Theme -+ -+ -+ -+ -+ -+ -+ Gnome Symbolic Icon Theme -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/data/meson.build b/data/meson.build -index de68c44..5675828 100644 ---- a/data/meson.build -+++ b/data/meson.build -@@ -131,7 +131,30 @@ if enable_session_selector - data += files('session-selector.ui') - endif - -+desktop = configure_file( -+ input: 'gnome-session-properties.desktop.in', -+ output: 'gnome-session-properties.desktop', -+ configuration: desktop_conf -+) -+ -+install_data( -+ desktop, -+ install_dir : join_paths(session_datadir, 'applications') -+) -+ -+data += files('session-properties.ui') -+ - install_data( - data, - install_dir: session_pkgdatadir - ) -+ -+icondir = join_paths(get_option('datadir'), 'icons/hicolor') -+foreach size: ['16x16', '22x22', '32x32', 'symbolic'] -+ install_subdir('icons/' + size, install_dir: icondir) -+endforeach -+ -+install_data( -+ 'icons/scalable/session-properties.svg', -+ install_dir: icondir + '/scalable/apps' -+) -diff --git a/data/session-properties.ui b/data/session-properties.ui -new file mode 100644 -index 0000000..6526154 ---- /dev/null -+++ b/data/session-properties.ui -@@ -0,0 +1,303 @@ -+ -+ -+ -+ -+ -+ True -+ 6 -+ -+ -+ True -+ 12 -+ vertical -+ 3 -+ -+ -+ True -+ 0 -+ 3 -+ 3 -+ Additional startup _programs: -+ True -+ session_properties_treeview -+ -+ -+ False -+ 0 -+ -+ -+ -+ -+ True -+ 6 -+ -+ -+ True -+ True -+ never -+ automatic -+ etched-in -+ -+ -+ 210 -+ True -+ True -+ -+ -+ -+ -+ 0 -+ -+ -+ -+ -+ True -+ 6 -+ start -+ -+ -+ gtk-add -+ True -+ True -+ True -+ True -+ -+ -+ False -+ False -+ 0 -+ -+ -+ -+ -+ gtk-remove -+ True -+ False -+ True -+ True -+ True -+ -+ -+ False -+ False -+ 1 -+ -+ -+ -+ -+ gtk-edit -+ True -+ False -+ True -+ True -+ True -+ -+ -+ False -+ False -+ 2 -+ -+ -+ -+ -+ False -+ False -+ 1 -+ -+ -+ -+ -+ 1 -+ -+ -+ -+ -+ -+ -+ False -+ 12 -+ vertical -+ 6 -+ -+ -+ _Automatically remember running applications when logging out -+ True -+ True -+ False -+ True -+ True -+ -+ -+ False -+ False -+ 0 -+ -+ -+ -+ -+ True -+ -+ -+ True -+ True -+ -+ -+ True -+ 4 -+ -+ -+ True -+ gtk-save -+ -+ -+ False -+ False -+ 0 -+ -+ -+ -+ -+ True -+ _Remember Currently Running Applications -+ True -+ -+ -+ 1 -+ -+ -+ -+ -+ -+ -+ False -+ False -+ 0 -+ -+ -+ -+ -+ False -+ False -+ 1 -+ -+ -+ -+ -+ 1 -+ -+ -+ -+ -+ True -+ 6 -+ 3 -+ 2 -+ 12 -+ 6 -+ -+ -+ True -+ 12 -+ -+ -+ True -+ True -+ -+ -+ -+ 0 -+ -+ -+ -+ -+ Browse… -+ True -+ True -+ True -+ -+ -+ False -+ False -+ 1 -+ -+ -+ -+ -+ 1 -+ 2 -+ 1 -+ 2 -+ GTK_FILL -+ -+ -+ -+ -+ True -+ True -+ -+ -+ -+ 1 -+ 2 -+ 2 -+ 3 -+ GTK_FILL -+ -+ -+ -+ -+ True -+ True -+ -+ -+ -+ 1 -+ 2 -+ GTK_FILL -+ -+ -+ -+ -+ True -+ 0 -+ Comm_ent: -+ True -+ label2 -+ -+ -+ 2 -+ 3 -+ GTK_FILL -+ GTK_FILL -+ -+ -+ -+ -+ True -+ 0 -+ Co_mmand: -+ True -+ session_properties_command_entry -+ -+ -+ 1 -+ 2 -+ GTK_FILL -+ GTK_FILL -+ -+ -+ -+ -+ True -+ 0 -+ _Name: -+ True -+ session_properties_name_entry -+ -+ -+ GTK_FILL -+ GTK_FILL -+ -+ -+ -+ -diff --git a/doc/man/gnome-session-properties.1 b/doc/man/gnome-session-properties.1 -new file mode 100644 -index 0000000..c7ef1af ---- /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/doc/man/gnome-session.1 b/doc/man/gnome-session.1 -index bf9cf80..35c062c 100644 ---- a/doc/man/gnome-session.1 -+++ b/doc/man/gnome-session.1 -@@ -94,6 +94,7 @@ when gnome-session is invoked. - .B /usr/share/gnome/autostart - .IP - The applications defined in those directories will be started on login. -+\fIgnome-session-properties(1)\fP can be used to easily configure them. - .PP - .B $XDG_CONFIG_HOME/gnome-session/sessions - .B $XDG_CONFIG_DIRS/gnome-session/sessions -@@ -109,4 +110,5 @@ This directory contains the list of applications of the saved session. - If you find bugs in the \fIgnome-session\fP program, please report - these on https://bugzilla.gnome.org. - .SH SEE ALSO -+.BR gnome-session-properties(1) - .BR gnome-session-quit(1) -diff --git a/doc/man/meson.build b/doc/man/meson.build -index 4a389bc..9d9cc2d 100644 ---- a/doc/man/meson.build -+++ b/doc/man/meson.build -@@ -34,7 +34,8 @@ endforeach - - man_data = files( - 'gnome-session.1', -- 'gnome-session-quit.1' -+ 'gnome-session-quit.1', -+ 'gnome-session-properties.1' - ) - - install_data( -diff --git a/meson.build b/meson.build -index 510ef7e..cd67116 100644 ---- a/meson.build -+++ b/meson.build -@@ -181,6 +181,7 @@ top_inc = include_directories('.') - subdir('gnome-session') - subdir('tools') - subdir('data') -+subdir('capplet') - - enable_docbook = get_option('docbook') - enable_man = get_option('man') -diff --git a/po/POTFILES.in b/po/POTFILES.in -index 788155c..c90544f 100644 ---- a/po/POTFILES.in -+++ b/po/POTFILES.in -@@ -1,12 +1,18 @@ - # 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.in - data/gnome.desktop.in.in - data/gnome-dummy.session.desktop.in.in - data/gnome.session.desktop.in.in - data/gnome-xorg.desktop.in.in - data/org.gnome.SessionManager.gschema.xml.in -+data/gnome-session-properties.desktop.in - data/session-selector.ui -+data/session-properties.ui - data/ubuntu.desktop.in.in - data/ubuntu-wayland.desktop.in.in - data/ubuntu-communitheme-snap.desktop.in.in -diff --git a/po/POTFILES.skip b/po/POTFILES.skip -index d75a99c..4055aed 100644 ---- a/po/POTFILES.skip -+++ b/po/POTFILES.skip -@@ -6,6 +6,7 @@ data/gnome.desktop.in - data/gnome-dummy.session.desktop.in - data/gnome.session.desktop.in - data/gnome-xorg.desktop.in -+data/gnome-session-properties.desktop - data/ubuntu.desktop.in - data/ubuntu-wayland.desktop.in - data/ubuntu-communitheme-snap.desktop.in diff -Nru gnome-session-3.30.1/debian/patches/series gnome-session-3.30.1/debian/patches/series --- gnome-session-3.30.1/debian/patches/series 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -debian/Revert-main-Remove-GNOME_DESKTOP_SESSION_ID-envvar.patch -ubuntu/support_autostart_delay.patch -ubuntu/ubuntu-sessions.patch -ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch -ubuntu/remove_session_saving_from_gui.patch -ubuntu/add_sessionmigration.patch -ubuntu/dbus_request_shutdown.patch -ubuntu/kill_the_fail_whale.patch -ubuntu/ignore_gsettings_region.patch -revert_remove_gnome_session_properties.patch diff -Nru gnome-session-3.30.1/debian/patches/ubuntu/add_sessionmigration.patch gnome-session-3.30.1/debian/patches/ubuntu/add_sessionmigration.patch --- gnome-session-3.30.1/debian/patches/ubuntu/add_sessionmigration.patch 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/patches/ubuntu/add_sessionmigration.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -From: Ubuntu Developers -Date: Wed, 15 Aug 2018 17:13:26 +0200 -Subject: gsm-session-fill: Launch session migration - -=================================================================== ---- - gnome-session/gsm-session-fill.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/gnome-session/gsm-session-fill.c b/gnome-session/gsm-session-fill.c -index 9f18f14..725c459 100644 ---- a/gnome-session/gsm-session-fill.c -+++ b/gnome-session/gsm-session-fill.c -@@ -132,6 +132,21 @@ static void - load_standard_apps (GsmManager *manager, - GKeyFile *keyfile) - { -+ GError *error; -+ -+ if (!g_getenv("UPSTART_SESSION")) { -+ g_debug ("fill: *** Executing user migration"); -+ error = NULL; -+ if(!g_spawn_command_line_sync ("session-migration", NULL, NULL, NULL, &error)) { -+ g_warning ("Error while executing session-migration: %s", error->message); -+ g_error_free (error); -+ } -+ } else { -+ g_debug("fill: *** Not executing session-migration as we are " -+ "running in an Upstart user session which should execute " -+ "it."); -+ } -+ - g_debug ("fill: *** Adding required components"); - handle_required_components (keyfile, !gsm_manager_get_failsafe (manager), - append_required_components_helper, manager); diff -Nru gnome-session-3.30.1/debian/patches/ubuntu/dbus_request_shutdown.patch gnome-session-3.30.1/debian/patches/ubuntu/dbus_request_shutdown.patch --- gnome-session-3.30.1/debian/patches/ubuntu/dbus_request_shutdown.patch 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/patches/ubuntu/dbus_request_shutdown.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,107 +0,0 @@ -From: Ubuntu Developers -Date: Wed, 15 Aug 2018 17:13:26 +0200 -Subject: Add "RequestShutdown" and "RequestReboot" DBus methods to allow - -other applications to shutdown or reboot the machine via the session manager -Author: Chris Coulson ---- - gnome-session/gsm-manager.c | 41 ++++++++++++++++++++++++++++++ - gnome-session/gsm-manager.h | 4 +++ - gnome-session/org.gnome.SessionManager.xml | 17 +++++++++++++ - 3 files changed, 62 insertions(+) - -diff --git a/gnome-session/gsm-manager.c b/gnome-session/gsm-manager.c -index e11c9de..95d3639 100644 ---- a/gnome-session/gsm-manager.c -+++ b/gnome-session/gsm-manager.c -@@ -2487,6 +2487,47 @@ request_logout (GsmManager *manager, - end_phase (manager); - } - -+gboolean -+gsm_manager_request_shutdown (GsmManager *manager, -+ GError **error) -+{ -+ g_debug ("GsmManager: RequestShutdown called"); -+ -+ g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); -+ -+ if (manager->priv->phase != GSM_MANAGER_PHASE_RUNNING) { -+ g_set_error (error, -+ GSM_MANAGER_ERROR, -+ GSM_MANAGER_ERROR_NOT_IN_RUNNING, -+ "RequestShutdown interface is only available during the Running phase"); -+ return FALSE; -+ } -+ -+ request_shutdown (manager); -+ -+ return TRUE; -+} -+ -+gboolean -+gsm_manager_request_reboot (GsmManager *manager, -+ GError **error) -+{ -+ g_debug ("GsmManager: RequestReboot called"); -+ -+ g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); -+ -+ if (manager->priv->phase != GSM_MANAGER_PHASE_RUNNING) { -+ g_set_error (error, -+ GSM_MANAGER_ERROR, -+ GSM_MANAGER_ERROR_NOT_IN_RUNNING, -+ "RequestReboot interface is only available during the Running phase"); -+ return FALSE; -+ } -+ request_reboot (manager); -+ -+ return TRUE; -+} -+ - static gboolean - gsm_manager_shutdown (GsmExportedManager *skeleton, - GDBusMethodInvocation *invocation, -diff --git a/gnome-session/gsm-manager.h b/gnome-session/gsm-manager.h -index bc440cb..7da25a6 100644 ---- a/gnome-session/gsm-manager.h -+++ b/gnome-session/gsm-manager.h -@@ -126,6 +126,10 @@ void _gsm_manager_set_renderer (GsmManager * - gboolean gsm_manager_logout (GsmManager *manager, - guint logout_mode, - GError **error); -+gboolean gsm_manager_request_shutdown (GsmManager *manager, -+ GError **error); -+gboolean gsm_manager_request_reboot (GsmManager *manager, -+ GError **error); - - gboolean gsm_manager_set_phase (GsmManager *manager, - GsmManagerPhase phase); -diff --git a/gnome-session/org.gnome.SessionManager.xml b/gnome-session/org.gnome.SessionManager.xml -index 51eed3a..529cc8a 100644 ---- a/gnome-session/org.gnome.SessionManager.xml -+++ b/gnome-session/org.gnome.SessionManager.xml -@@ -369,6 +369,23 @@ - - - -+ -+ -+ -+ Request a shutdown with no dialog -+ -+ -+ -+ -+ -+ -+ -+ Request a reboot with no dialog -+ -+ -+ -+ -+ - - - diff -Nru gnome-session-3.30.1/debian/patches/ubuntu/ignore_gsettings_region.patch gnome-session-3.30.1/debian/patches/ubuntu/ignore_gsettings_region.patch --- gnome-session-3.30.1/debian/patches/ubuntu/ignore_gsettings_region.patch 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/patches/ubuntu/ignore_gsettings_region.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ -From: Gunnar Hjalmarsson -Date: Wed, 15 Aug 2018 17:13:26 +0200 -Subject: Ignore the "region" gsettings value - -We rely on ~/.pam_environment for the users' setting of LC_* variables. -Bug-Ubuntu: https://launchpad.net/bugs/1722002 -Forwarded: not-needed ---- - gnome-session/gnome-session.in | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/gnome-session/gnome-session.in b/gnome-session/gnome-session.in -index 7ded359..a2922ad 100755 ---- a/gnome-session/gnome-session.in -+++ b/gnome-session/gnome-session.in -@@ -13,9 +13,9 @@ if [ "x$XDG_SESSION_TYPE" = "xwayland" ] && - fi - fi - --SETTING=$(G_MESSAGES_DEBUG= gsettings get org.gnome.system.locale region) --REGION=${SETTING#\'} --REGION=${REGION%\'} -+#SETTING=$(G_MESSAGES_DEBUG= gsettings get org.gnome.system.locale region) -+#REGION=${SETTING#\'} -+#REGION=${REGION%\'} - - if [ -n "$REGION" ]; then - export LC_TIME=$REGION diff -Nru gnome-session-3.30.1/debian/patches/ubuntu/kill_the_fail_whale.patch gnome-session-3.30.1/debian/patches/ubuntu/kill_the_fail_whale.patch --- gnome-session-3.30.1/debian/patches/ubuntu/kill_the_fail_whale.patch 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/patches/ubuntu/kill_the_fail_whale.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,50 +0,0 @@ -From: Ubuntu Developers -Date: Wed, 15 Aug 2018 17:13:26 +0200 -Subject: kill the fail whale - -=================================================================== ---- - gnome-session/gsm-fail-whale.c | 25 ++++--------------------- - 1 file changed, 4 insertions(+), 21 deletions(-) - -diff --git a/gnome-session/gsm-fail-whale.c b/gnome-session/gsm-fail-whale.c -index 7b9a0a5..a62ceb0 100644 ---- a/gnome-session/gsm-fail-whale.c -+++ b/gnome-session/gsm-fail-whale.c -@@ -21,6 +21,8 @@ - #include - #include - -+#include -+ - #include - #include - -@@ -38,25 +40,6 @@ gsm_fail_whale_dialog_we_failed (gboolean debug_mode, - gboolean allow_logout, - GsmShellExtensions *extensions) - { -- gint i; -- gchar *argv[5]; -- GPid pid; -- -- i = 0; -- argv[i++] = LIBEXECDIR "/gnome-session-failed"; -- if (debug_mode) -- argv[i++] = "--debug"; -- if (allow_logout) -- argv[i++] = "--allow-logout"; -- if (extensions != NULL && gsm_shell_extensions_n_extensions (extensions) > 0) -- argv[i++] = "--extensions"; -- argv[i++] = NULL; -- -- if (!g_spawn_async (NULL, argv, (char **) gsm_util_listenv (), G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL)) { -- exit (1); -- } -- -- g_child_watch_add (pid, -- (GChildWatchFunc)on_fail_whale_failed, -- NULL); -+ g_critical ("We failed, but the fail whale is dead. Sorry...."); -+ exit (1); - } diff -Nru gnome-session-3.30.1/debian/patches/ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch gnome-session-3.30.1/debian/patches/ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch --- gnome-session-3.30.1/debian/patches/ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/patches/ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,45 +0,0 @@ -From: =?utf-8?b?Ik1hcmNvIFRyZXZpc2FuIChUcmV2acOxbyki?= -Date: Tue, 21 Aug 2018 17:32:13 +0200 -Subject: post_install: install gnome.desktop session in wayland-sessions only - -In ubuntu we use a symlink from gnome-xorg.desktop to gnome.desktop, so -no need to install this for X sessions. - -Forwarded: Not needed ---- - data/meson.build | 2 +- - meson_post_install.py | 4 ---- - 2 files changed, 1 insertion(+), 5 deletions(-) - -diff --git a/data/meson.build b/data/meson.build -index 33dcfe4..de68c44 100644 ---- a/data/meson.build -+++ b/data/meson.build -@@ -1,7 +1,6 @@ - desktop_plain = 'gnome' - - desktops = [ -- desktop_plain, - 'gnome-xorg', - 'ubuntu', - 'ubuntu-communitheme-snap', -@@ -48,6 +47,7 @@ foreach name: desktops - endforeach - - wayland_desktops = [ -+ desktop_plain, - 'ubuntu-wayland', - 'ubuntu-communitheme-snap-wayland', - ] -diff --git a/meson_post_install.py b/meson_post_install.py -index e2e352c..e88375d 100644 ---- a/meson_post_install.py -+++ b/meson_post_install.py -@@ -20,7 +20,3 @@ if not os.environ.get('DESTDIR'): - dst_dir = os.path.join(install_root, 'wayland-sessions') - if not os.path.exists(dst_dir): - os.makedirs(dst_dir) -- --src = os.path.join(install_root, 'xsessions', 'gnome.desktop') --dst = os.path.join(dst_dir, 'gnome.desktop') --shutil.copyfile(src, dst) diff -Nru gnome-session-3.30.1/debian/patches/ubuntu/remove_session_saving_from_gui.patch gnome-session-3.30.1/debian/patches/ubuntu/remove_session_saving_from_gui.patch --- gnome-session-3.30.1/debian/patches/ubuntu/remove_session_saving_from_gui.patch 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/patches/ubuntu/remove_session_saving_from_gui.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ -From: Ubuntu Developers -Date: Wed, 15 Aug 2018 17:13:26 +0200 -Subject: Remove session saving from gui - -=================================================================== ---- - gnome-session/gsm-session-fill.c | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/gnome-session/gsm-session-fill.c b/gnome-session/gsm-session-fill.c -index af203bb..9f18f14 100644 ---- a/gnome-session/gsm-session-fill.c -+++ b/gnome-session/gsm-session-fill.c -@@ -143,7 +143,8 @@ load_standard_apps (GsmManager *manager, - - autostart_dirs = gsm_util_get_autostart_dirs (); - -- maybe_load_saved_session_apps (manager); -+ if (g_getenv ("GNOME_SESSION_SAVE") != NULL) -+ maybe_load_saved_session_apps (manager); - - for (i = 0; autostart_dirs[i]; i++) { - gsm_manager_add_autostart_apps_from_dir (manager, diff -Nru gnome-session-3.30.1/debian/patches/ubuntu/support_autostart_delay.patch gnome-session-3.30.1/debian/patches/ubuntu/support_autostart_delay.patch --- gnome-session-3.30.1/debian/patches/ubuntu/support_autostart_delay.patch 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/patches/ubuntu/support_autostart_delay.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,242 +0,0 @@ -From: Chris Coulson -Date: Wed, 15 Aug 2018 17:13:26 +0200 -Subject: Add support for autostarting applications after a delay - -Make it possible to delay autostarting an application for a time -period defined by the "X-GNOME-Autostart-Delay" key in the applications -desktop file -Bug: https://bugzilla.gnome.org/show_bug.cgi?id=608402 ---- - gnome-session/gsm-app.c | 18 ++++++++++++++- - gnome-session/gsm-app.h | 2 ++ - gnome-session/gsm-autostart-app.c | 26 ++++++++++++++++++++++ - gnome-session/gsm-autostart-app.h | 1 + - gnome-session/gsm-manager.c | 46 ++++++++++++++++++++++++++++++++++++--- - 5 files changed, 89 insertions(+), 4 deletions(-) - -diff --git a/gnome-session/gsm-app.c b/gnome-session/gsm-app.c -index d1ef89a..3f6a8ec 100644 ---- a/gnome-session/gsm-app.c -+++ b/gnome-session/gsm-app.c -@@ -316,6 +316,7 @@ gsm_app_class_init (GsmAppClass *klass) - klass->impl_provides = NULL; - klass->impl_get_provides = NULL; - klass->impl_is_running = NULL; -+ klass->impl_peek_autostart_delay = NULL; - - g_object_class_install_property (object_class, - PROP_PHASE, -@@ -518,7 +519,22 @@ gboolean - gsm_app_stop (GsmApp *app, - GError **error) - { -- return GSM_APP_GET_CLASS (app)->impl_stop (app, error); -+ if (gsm_app_is_running (app)) -+ return GSM_APP_GET_CLASS (app)->impl_stop (app, error); -+ -+ return TRUE; -+} -+ -+int -+gsm_app_peek_autostart_delay (GsmApp *app) -+{ -+ g_return_val_if_fail (GSM_IS_APP (app), FALSE); -+ -+ if (GSM_APP_GET_CLASS (app)->impl_peek_autostart_delay) { -+ return GSM_APP_GET_CLASS (app)->impl_peek_autostart_delay (app); -+ } else { -+ return 0; -+ } - } - - void -diff --git a/gnome-session/gsm-app.h b/gnome-session/gsm-app.h -index f38b3be..85cb97e 100644 ---- a/gnome-session/gsm-app.h -+++ b/gnome-session/gsm-app.h -@@ -63,6 +63,7 @@ struct _GsmAppClass - GError **error); - gboolean (*impl_stop) (GsmApp *app, - GError **error); -+ int (*impl_peek_autostart_delay) (GsmApp *app); - gboolean (*impl_provides) (GsmApp *app, - const char *service); - char ** (*impl_get_provides) (GsmApp *app); -@@ -124,6 +125,7 @@ gboolean gsm_app_has_autostart_condition (GsmApp *app, - gboolean gsm_app_get_registered (GsmApp *app); - void gsm_app_set_registered (GsmApp *app, - gboolean registered); -+int gsm_app_peek_autostart_delay (GsmApp *app); - - gboolean gsm_app_save_to_keyfile (GsmApp *app, - GKeyFile *keyfile, -diff --git a/gnome-session/gsm-autostart-app.c b/gnome-session/gsm-autostart-app.c -index 2332b03..a7046cb 100644 ---- a/gnome-session/gsm-autostart-app.c -+++ b/gnome-session/gsm-autostart-app.c -@@ -20,6 +20,7 @@ - #include - - #include -+#include - #include - #include - #include -@@ -68,6 +69,7 @@ struct _GsmAutostartAppPrivate { - char *condition_string; - gboolean condition; - gboolean autorestart; -+ int autostart_delay; - - GFileMonitor *condition_monitor; - guint condition_notify_id; -@@ -105,6 +107,7 @@ gsm_autostart_app_init (GsmAutostartApp *app) - app->priv->pid = -1; - app->priv->condition_monitor = NULL; - app->priv->condition = FALSE; -+ app->priv->autostart_delay = -1; - } - - static gboolean -@@ -615,6 +618,20 @@ load_desktop_file (GsmAutostartApp *app) - "AutostartCondition"); - setup_condition_monitor (app); - -+ const char *delay; -+ delay = g_desktop_app_info_get_string (app->priv->app_info, -+ GSM_AUTOSTART_APP_DELAY_KEY); -+ -+ if (delay != NULL) { -+ app->priv->autostart_delay = strtol (delay, NULL, 10); -+ -+ if (app->priv->autostart_delay < 0) { -+ g_warning ("Invalid autostart delay of %d for %s", app->priv->autostart_delay, -+ gsm_app_peek_id (GSM_APP (app))); -+ app->priv->autostart_delay = -1; -+ } -+ } -+ - g_object_set (app, - "phase", phase, - "startup-id", startup_id, -@@ -1323,6 +1340,14 @@ gsm_autostart_app_get_app_id (GsmApp *app) - return g_app_info_get_id (G_APP_INFO (GSM_AUTOSTART_APP (app)->priv->app_info)); - } - -+static int -+gsm_autostart_app_peek_autostart_delay (GsmApp *app) -+{ -+ GsmAutostartApp *aapp = GSM_AUTOSTART_APP (app); -+ -+ return aapp->priv->autostart_delay; -+} -+ - static gboolean - gsm_autostart_app_initable_init (GInitable *initable, - GCancellable *cancellable, -@@ -1439,6 +1464,7 @@ gsm_autostart_app_class_init (GsmAutostartAppClass *klass) - app_class->impl_has_autostart_condition = gsm_autostart_app_has_autostart_condition; - app_class->impl_get_app_id = gsm_autostart_app_get_app_id; - app_class->impl_get_autorestart = gsm_autostart_app_get_autorestart; -+ app_class->impl_peek_autostart_delay = gsm_autostart_app_peek_autostart_delay; - app_class->impl_save_to_keyfile = gsm_autostart_app_save_to_keyfile; - - g_object_class_install_property (object_class, -diff --git a/gnome-session/gsm-autostart-app.h b/gnome-session/gsm-autostart-app.h -index a4cd9af..75ae659 100644 ---- a/gnome-session/gsm-autostart-app.h -+++ b/gnome-session/gsm-autostart-app.h -@@ -70,6 +70,7 @@ void gsm_autostart_app_add_provides (GsmAutostartApp *aapp, - #define GSM_AUTOSTART_APP_DBUS_PATH_KEY "X-GNOME-DBus-Path" - #define GSM_AUTOSTART_APP_DBUS_ARGS_KEY "X-GNOME-DBus-Start-Arguments" - #define GSM_AUTOSTART_APP_DISCARD_KEY "X-GNOME-Autostart-discard-exec" -+#define GSM_AUTOSTART_APP_DELAY_KEY "X-GNOME-Autostart-Delay" - - G_END_DECLS - -diff --git a/gnome-session/gsm-manager.c b/gnome-session/gsm-manager.c -index 6eeea4a..e11c9de 100644 ---- a/gnome-session/gsm-manager.c -+++ b/gnome-session/gsm-manager.c -@@ -399,7 +399,8 @@ app_condition_changed (GsmApp *app, - } else { - g_debug ("GsmManager: stopping app %s", gsm_app_peek_id (app)); - -- /* If we don't have a client then we should try to kill the app */ -+ /* If we don't have a client then we should try to kill the app , -+ * if it is running */ - error = NULL; - res = gsm_app_stop (app, &error); - if (! res) { -@@ -723,11 +724,37 @@ on_phase_timeout (GsmManager *manager) - return FALSE; - } - -+static gboolean -+_autostart_delay_timeout (GsmApp *app) -+{ -+ GError *error = NULL; -+ gboolean res; -+ -+ if (!gsm_app_peek_is_disabled (app) -+ && !gsm_app_peek_is_conditionally_disabled (app)) { -+ res = gsm_app_start (app, &error); -+ if (!res) { -+ if (error != NULL) { -+ g_warning ("Could not launch application '%s': %s", -+ gsm_app_peek_app_id (app), -+ error->message); -+ g_error_free (error); -+ } -+ } -+ } -+ -+ g_object_unref (app); -+ -+ return FALSE; -+} -+ - static gboolean - _start_app (const char *id, - GsmApp *app, - GsmManager *manager) - { -+ int delay; -+ - if (gsm_app_peek_phase (app) != manager->priv->phase) { - goto out; - } -@@ -745,6 +772,18 @@ _start_app (const char *id, - goto out; - } - -+ /* Only accept an autostart delay for the application phase */ -+ if (manager->priv->phase == GSM_MANAGER_PHASE_APPLICATION) { -+ delay = gsm_app_peek_autostart_delay (app); -+ if (delay > 0) { -+ g_timeout_add_seconds (delay, -+ (GSourceFunc)_autostart_delay_timeout, -+ g_object_ref (app)); -+ g_debug ("GsmManager: %s is scheduled to start in %d seconds", id, delay); -+ goto out; -+ } -+ } -+ - if (!start_app_or_warn (manager, app)) - goto out; - -@@ -1398,11 +1437,12 @@ _debug_app_for_phase (const char *id, - return FALSE; - } - -- g_debug ("GsmManager:\tID: %s\tapp-id:%s\tis-disabled:%d\tis-conditionally-disabled:%d", -+ g_debug ("GsmManager:\tID: %s\tapp-id:%s\tis-disabled:%d\tis-conditionally-disabled:%d\tis-delayed:%d", - gsm_app_peek_id (app), - gsm_app_peek_app_id (app), - gsm_app_peek_is_disabled (app), -- gsm_app_peek_is_conditionally_disabled (app)); -+ gsm_app_peek_is_conditionally_disabled (app), -+ (gsm_app_peek_autostart_delay (app) > 0)); - - return FALSE; - } diff -Nru gnome-session-3.30.1/debian/patches/ubuntu/ubuntu-sessions.patch gnome-session-3.30.1/debian/patches/ubuntu/ubuntu-sessions.patch --- gnome-session-3.30.1/debian/patches/ubuntu/ubuntu-sessions.patch 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/patches/ubuntu/ubuntu-sessions.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,226 +0,0 @@ -From: Ubuntu Developers -Date: Wed, 15 Aug 2018 17:13:26 +0200 -Subject: sessions: Tweak the system to make it for ubuntu. - -The session contains: - - ubuntu (gnome-shell with ubuntu tweaks) in ubuntu-session package - - unity in unity-session package - - gnome-shell in the main gnome-session package. - - ubuntu-communitheme-snap into ubuntu-session package - (TryExec enables to show them or not depends on the package installed) - -Forwarded: Not needed ---- - data/gnome.desktop.in.in | 2 +- - data/meson.build | 41 ++++++++++++++++++++-- - .../ubuntu-communitheme-snap-wayland.desktop.in.in | 7 ++++ - data/ubuntu-communitheme-snap.desktop.in.in | 7 ++++ - data/ubuntu-wayland.desktop.in.in | 7 ++++ - data/ubuntu.desktop.in.in | 7 ++++ - data/ubuntu.session.desktop.in.in | 3 ++ - data/unity.desktop.in.in | 7 ++++ - data/unity.session.desktop.in.in | 4 +++ - po/POTFILES.in | 7 ++++ - po/POTFILES.skip | 7 ++++ - 11 files changed, 96 insertions(+), 3 deletions(-) - create mode 100644 data/ubuntu-communitheme-snap-wayland.desktop.in.in - create mode 100644 data/ubuntu-communitheme-snap.desktop.in.in - create mode 100644 data/ubuntu-wayland.desktop.in.in - create mode 100644 data/ubuntu.desktop.in.in - create mode 100644 data/ubuntu.session.desktop.in.in - create mode 100644 data/unity.desktop.in.in - create mode 100644 data/unity.session.desktop.in.in - -diff --git a/data/gnome.desktop.in.in b/data/gnome.desktop.in.in -index 516c211..d2c9bee 100644 ---- a/data/gnome.desktop.in.in -+++ b/data/gnome.desktop.in.in -@@ -1,7 +1,7 @@ - [Desktop Entry] - Name=GNOME - Comment=This session logs you into GNOME --Exec=@bindir@/gnome-session -+Exec=@bindir@/gnome-session --session=gnome - TryExec=@bindir@/gnome-session - Type=Application - DesktopNames=GNOME -diff --git a/data/meson.build b/data/meson.build -index b976e73..33dcfe4 100644 ---- a/data/meson.build -+++ b/data/meson.build -@@ -2,7 +2,10 @@ desktop_plain = 'gnome' - - desktops = [ - desktop_plain, -- 'gnome-xorg' -+ 'gnome-xorg', -+ 'ubuntu', -+ 'ubuntu-communitheme-snap', -+ 'unity', - ] - - if enable_session_selector -@@ -41,11 +44,45 @@ foreach name: desktops - install: true, - install_dir: install_dir - ) -+ -+endforeach -+ -+wayland_desktops = [ -+ 'ubuntu-wayland', -+ 'ubuntu-communitheme-snap-wayland', -+] -+ -+foreach name: wayland_desktops -+ desktop_conf = configuration_data() -+ desktop_conf.set('bindir', session_bindir) -+ -+ desktop = name + '.desktop' -+ -+ desktop_in = configure_file( -+ input: desktop + '.in.in', -+ output: desktop + '.in', -+ configuration: desktop_conf -+ ) -+ -+ install_dir = join_paths(session_datadir, 'wayland-sessions') -+ -+ desktop_target = i18n.merge_file( -+ desktop, -+ type: 'desktop', -+ input: desktop_in, -+ output: desktop, -+ po_dir: po_dir, -+ install: true, -+ install_dir: install_dir -+ ) -+ - endforeach - - sessions = [ - 'gnome.session', -- 'gnome-dummy.session' -+ 'gnome-dummy.session', -+ 'ubuntu.session', -+ 'unity.session' - ] - - foreach session: sessions -diff --git a/data/ubuntu-communitheme-snap-wayland.desktop.in.in b/data/ubuntu-communitheme-snap-wayland.desktop.in.in -new file mode 100644 -index 0000000..e28ec7d ---- /dev/null -+++ b/data/ubuntu-communitheme-snap-wayland.desktop.in.in -@@ -0,0 +1,7 @@ -+[Desktop Entry] -+Name=Ubuntu with communitheme snap on Wayland -+Comment=This session logs you into Ubuntu -+Exec=env GNOME_SHELL_SESSION_MODE=ubuntu-communitheme /snap/communitheme/current/session -+TryExec=/snap/communitheme/current/session -+Type=Application -+DesktopNames=communitheme:ubuntu:GNOME -diff --git a/data/ubuntu-communitheme-snap.desktop.in.in b/data/ubuntu-communitheme-snap.desktop.in.in -new file mode 100644 -index 0000000..2860c66 ---- /dev/null -+++ b/data/ubuntu-communitheme-snap.desktop.in.in -@@ -0,0 +1,7 @@ -+[Desktop Entry] -+Name=Ubuntu with communitheme snap -+Comment=This session logs you into Ubuntu -+Exec=env GNOME_SHELL_SESSION_MODE=ubuntu-communitheme /snap/communitheme/current/session -+TryExec=/snap/communitheme/current/session -+Type=Application -+DesktopNames=communitheme:ubuntu:GNOME -diff --git a/data/ubuntu-wayland.desktop.in.in b/data/ubuntu-wayland.desktop.in.in -new file mode 100644 -index 0000000..420e227 ---- /dev/null -+++ b/data/ubuntu-wayland.desktop.in.in -@@ -0,0 +1,7 @@ -+[Desktop Entry] -+Name=Ubuntu on Wayland -+Comment=This session logs you into Ubuntu -+Exec=env GNOME_SHELL_SESSION_MODE=ubuntu @bindir@/gnome-session --session=ubuntu -+TryExec=@bindir@/gnome-shell -+Type=Application -+DesktopNames=ubuntu:GNOME -diff --git a/data/ubuntu.desktop.in.in b/data/ubuntu.desktop.in.in -new file mode 100644 -index 0000000..23db357 ---- /dev/null -+++ b/data/ubuntu.desktop.in.in -@@ -0,0 +1,7 @@ -+[Desktop Entry] -+Name=Ubuntu -+Comment=This session logs you into Ubuntu -+Exec=env GNOME_SHELL_SESSION_MODE=ubuntu @bindir@/gnome-session --session=ubuntu -+TryExec=@bindir@/gnome-shell -+Type=Application -+DesktopNames=ubuntu:GNOME -diff --git a/data/ubuntu.session.desktop.in.in b/data/ubuntu.session.desktop.in.in -new file mode 100644 -index 0000000..d8df078 ---- /dev/null -+++ b/data/ubuntu.session.desktop.in.in -@@ -0,0 +1,3 @@ -+[GNOME Session] -+Name=Ubuntu -+RequiredComponents=org.gnome.Shell;org.gnome.SettingsDaemon.A11ySettings;org.gnome.SettingsDaemon.Clipboard;org.gnome.SettingsDaemon.Color;org.gnome.SettingsDaemon.Datetime;org.gnome.SettingsDaemon.Housekeeping;org.gnome.SettingsDaemon.Keyboard;org.gnome.SettingsDaemon.MediaKeys;org.gnome.SettingsDaemon.Mouse;org.gnome.SettingsDaemon.Power;org.gnome.SettingsDaemon.PrintNotifications;org.gnome.SettingsDaemon.Rfkill;org.gnome.SettingsDaemon.ScreensaverProxy;org.gnome.SettingsDaemon.Sharing;org.gnome.SettingsDaemon.Smartcard;org.gnome.SettingsDaemon.Sound;org.gnome.SettingsDaemon.Wacom;org.gnome.SettingsDaemon.XSettings; -diff --git a/data/unity.desktop.in.in b/data/unity.desktop.in.in -new file mode 100644 -index 0000000..ea36804 ---- /dev/null -+++ b/data/unity.desktop.in.in -@@ -0,0 +1,7 @@ -+[Desktop Entry] -+Name=Unity -+Comment=This session logs you into Unity -+Exec=/usr/lib/gnome-session/run-systemd-session unity-session.target -+TryExec=@bindir@/unity -+Type=Application -+DesktopNames=Unity:Unity7:ubuntu -diff --git a/data/unity.session.desktop.in.in b/data/unity.session.desktop.in.in -new file mode 100644 -index 0000000..615b492 ---- /dev/null -+++ b/data/unity.session.desktop.in.in -@@ -0,0 +1,4 @@ -+[GNOME Session] -+Name=Unity -+RequiredComponents=unity-settings-daemon; -+DesktopName=Unity:Unity7:ubuntu -diff --git a/po/POTFILES.in b/po/POTFILES.in -index b1f8516..788155c 100644 ---- a/po/POTFILES.in -+++ b/po/POTFILES.in -@@ -7,6 +7,13 @@ data/gnome.session.desktop.in.in - data/gnome-xorg.desktop.in.in - data/org.gnome.SessionManager.gschema.xml.in - data/session-selector.ui -+data/ubuntu.desktop.in.in -+data/ubuntu-wayland.desktop.in.in -+data/ubuntu-communitheme-snap.desktop.in.in -+data/ubuntu-communitheme-snap-wayland.desktop.in.in -+data/ubuntu.session.desktop.in.in -+data/unity.desktop.in.in -+data/unity.session.desktop.in.in - gnome-session/gsm-fail-whale-dialog.c - gnome-session/gsm-manager.c - gnome-session/gsm-process-helper.c -diff --git a/po/POTFILES.skip b/po/POTFILES.skip -index 316da79..d75a99c 100644 ---- a/po/POTFILES.skip -+++ b/po/POTFILES.skip -@@ -6,3 +6,10 @@ data/gnome.desktop.in - data/gnome-dummy.session.desktop.in - data/gnome.session.desktop.in - data/gnome-xorg.desktop.in -+data/ubuntu.desktop.in -+data/ubuntu-wayland.desktop.in -+data/ubuntu-communitheme-snap.desktop.in -+data/ubuntu-communitheme-snap-wayland.desktop.in -+data/ubuntu.session.desktop.in -+data/unity.desktop.in -+data/unity.session.desktop.in diff -Nru gnome-session-3.30.1/debian/source/format gnome-session-3.30.1/debian/source/format --- gnome-session-3.30.1/debian/source/format 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/source/format 2019-02-13 19:09:46.000000000 +0000 @@ -1 +1 @@ -3.0 (quilt) +3.0 (native) diff -Nru gnome-session-3.30.1/debian/unity-session.install gnome-session-3.30.1/debian/unity-session.install --- gnome-session-3.30.1/debian/unity-session.install 2019-01-18 17:45:00.000000000 +0000 +++ gnome-session-3.30.1/debian/unity-session.install 2019-02-13 19:09:46.000000000 +0000 @@ -2,3 +2,5 @@ usr/share/gnome-session/sessions/unity.session debian/data/50-unity.conf /usr/share/lightdm/lightdm.conf.d debian/data/unity-session.target usr/lib/systemd/user/ +debian/data/nemo-unity-autostart.desktop /etc/xdg/autostart/ +debian/data/*.nemo_action /usr/share/nemo/actions/ diff -Nru gnome-session-3.30.1/doc/man/gnome-session.1 gnome-session-3.30.1/doc/man/gnome-session.1 --- gnome-session-3.30.1/doc/man/gnome-session.1 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/doc/man/gnome-session.1 2019-02-13 19:09:46.000000000 +0000 @@ -94,6 +94,7 @@ .B /usr/share/gnome/autostart .IP The applications defined in those directories will be started on login. +\fIgnome-session-properties(1)\fP can be used to easily configure them. .PP .B $XDG_CONFIG_HOME/gnome-session/sessions .B $XDG_CONFIG_DIRS/gnome-session/sessions @@ -109,4 +110,5 @@ If you find bugs in the \fIgnome-session\fP program, please report these on https://bugzilla.gnome.org. .SH SEE ALSO +.BR gnome-session-properties(1) .BR gnome-session-quit(1) diff -Nru gnome-session-3.30.1/doc/man/gnome-session-properties.1 gnome-session-3.30.1/doc/man/gnome-session-properties.1 --- gnome-session-3.30.1/doc/man/gnome-session-properties.1 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/doc/man/gnome-session-properties.1 2019-02-13 19:09:46.000000000 +0000 @@ -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 -Nru gnome-session-3.30.1/doc/man/meson.build gnome-session-3.30.1/doc/man/meson.build --- gnome-session-3.30.1/doc/man/meson.build 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/doc/man/meson.build 2019-02-13 19:09:46.000000000 +0000 @@ -34,7 +34,8 @@ man_data = files( 'gnome-session.1', - 'gnome-session-quit.1' + 'gnome-session-quit.1', + 'gnome-session-properties.1' ) install_data( diff -Nru gnome-session-3.30.1/gnome-session/gnome-session.in gnome-session-3.30.1/gnome-session/gnome-session.in --- gnome-session-3.30.1/gnome-session/gnome-session.in 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/gnome-session/gnome-session.in 2019-02-13 19:09:46.000000000 +0000 @@ -13,9 +13,9 @@ fi fi -SETTING=$(G_MESSAGES_DEBUG= gsettings get org.gnome.system.locale region) -REGION=${SETTING#\'} -REGION=${REGION%\'} +#SETTING=$(G_MESSAGES_DEBUG= gsettings get org.gnome.system.locale region) +#REGION=${SETTING#\'} +#REGION=${REGION%\'} if [ -n "$REGION" ]; then export LC_TIME=$REGION diff -Nru gnome-session-3.30.1/gnome-session/gsm-app.c gnome-session-3.30.1/gnome-session/gsm-app.c --- gnome-session-3.30.1/gnome-session/gsm-app.c 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/gnome-session/gsm-app.c 2019-02-13 19:09:46.000000000 +0000 @@ -316,6 +316,7 @@ klass->impl_provides = NULL; klass->impl_get_provides = NULL; klass->impl_is_running = NULL; + klass->impl_peek_autostart_delay = NULL; g_object_class_install_property (object_class, PROP_PHASE, @@ -518,7 +519,22 @@ gsm_app_stop (GsmApp *app, GError **error) { - return GSM_APP_GET_CLASS (app)->impl_stop (app, error); + if (gsm_app_is_running (app)) + return GSM_APP_GET_CLASS (app)->impl_stop (app, error); + + return TRUE; +} + +int +gsm_app_peek_autostart_delay (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + if (GSM_APP_GET_CLASS (app)->impl_peek_autostart_delay) { + return GSM_APP_GET_CLASS (app)->impl_peek_autostart_delay (app); + } else { + return 0; + } } void diff -Nru gnome-session-3.30.1/gnome-session/gsm-app.h gnome-session-3.30.1/gnome-session/gsm-app.h --- gnome-session-3.30.1/gnome-session/gsm-app.h 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/gnome-session/gsm-app.h 2019-02-13 19:09:46.000000000 +0000 @@ -63,6 +63,7 @@ GError **error); gboolean (*impl_stop) (GsmApp *app, GError **error); + int (*impl_peek_autostart_delay) (GsmApp *app); gboolean (*impl_provides) (GsmApp *app, const char *service); char ** (*impl_get_provides) (GsmApp *app); @@ -124,6 +125,7 @@ gboolean gsm_app_get_registered (GsmApp *app); void gsm_app_set_registered (GsmApp *app, gboolean registered); +int gsm_app_peek_autostart_delay (GsmApp *app); gboolean gsm_app_save_to_keyfile (GsmApp *app, GKeyFile *keyfile, diff -Nru gnome-session-3.30.1/gnome-session/gsm-autostart-app.c gnome-session-3.30.1/gnome-session/gsm-autostart-app.c --- gnome-session-3.30.1/gnome-session/gsm-autostart-app.c 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/gnome-session/gsm-autostart-app.c 2019-02-13 19:09:46.000000000 +0000 @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -68,6 +69,7 @@ char *condition_string; gboolean condition; gboolean autorestart; + int autostart_delay; GFileMonitor *condition_monitor; guint condition_notify_id; @@ -105,6 +107,7 @@ app->priv->pid = -1; app->priv->condition_monitor = NULL; app->priv->condition = FALSE; + app->priv->autostart_delay = -1; } static gboolean @@ -615,6 +618,20 @@ "AutostartCondition"); setup_condition_monitor (app); + const char *delay; + delay = g_desktop_app_info_get_string (app->priv->app_info, + GSM_AUTOSTART_APP_DELAY_KEY); + + if (delay != NULL) { + app->priv->autostart_delay = strtol (delay, NULL, 10); + + if (app->priv->autostart_delay < 0) { + g_warning ("Invalid autostart delay of %d for %s", app->priv->autostart_delay, + gsm_app_peek_id (GSM_APP (app))); + app->priv->autostart_delay = -1; + } + } + g_object_set (app, "phase", phase, "startup-id", startup_id, @@ -1323,6 +1340,14 @@ return g_app_info_get_id (G_APP_INFO (GSM_AUTOSTART_APP (app)->priv->app_info)); } +static int +gsm_autostart_app_peek_autostart_delay (GsmApp *app) +{ + GsmAutostartApp *aapp = GSM_AUTOSTART_APP (app); + + return aapp->priv->autostart_delay; +} + static gboolean gsm_autostart_app_initable_init (GInitable *initable, GCancellable *cancellable, @@ -1439,6 +1464,7 @@ app_class->impl_has_autostart_condition = gsm_autostart_app_has_autostart_condition; app_class->impl_get_app_id = gsm_autostart_app_get_app_id; app_class->impl_get_autorestart = gsm_autostart_app_get_autorestart; + app_class->impl_peek_autostart_delay = gsm_autostart_app_peek_autostart_delay; app_class->impl_save_to_keyfile = gsm_autostart_app_save_to_keyfile; g_object_class_install_property (object_class, diff -Nru gnome-session-3.30.1/gnome-session/gsm-autostart-app.h gnome-session-3.30.1/gnome-session/gsm-autostart-app.h --- gnome-session-3.30.1/gnome-session/gsm-autostart-app.h 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/gnome-session/gsm-autostart-app.h 2019-02-13 19:09:46.000000000 +0000 @@ -70,6 +70,7 @@ #define GSM_AUTOSTART_APP_DBUS_PATH_KEY "X-GNOME-DBus-Path" #define GSM_AUTOSTART_APP_DBUS_ARGS_KEY "X-GNOME-DBus-Start-Arguments" #define GSM_AUTOSTART_APP_DISCARD_KEY "X-GNOME-Autostart-discard-exec" +#define GSM_AUTOSTART_APP_DELAY_KEY "X-GNOME-Autostart-Delay" G_END_DECLS diff -Nru gnome-session-3.30.1/gnome-session/gsm-fail-whale.c gnome-session-3.30.1/gnome-session/gsm-fail-whale.c --- gnome-session-3.30.1/gnome-session/gsm-fail-whale.c 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/gnome-session/gsm-fail-whale.c 2019-02-13 19:09:46.000000000 +0000 @@ -21,6 +21,8 @@ #include #include +#include + #include #include @@ -38,25 +40,6 @@ gboolean allow_logout, GsmShellExtensions *extensions) { - gint i; - gchar *argv[5]; - GPid pid; - - i = 0; - argv[i++] = LIBEXECDIR "/gnome-session-failed"; - if (debug_mode) - argv[i++] = "--debug"; - if (allow_logout) - argv[i++] = "--allow-logout"; - if (extensions != NULL && gsm_shell_extensions_n_extensions (extensions) > 0) - argv[i++] = "--extensions"; - argv[i++] = NULL; - - if (!g_spawn_async (NULL, argv, (char **) gsm_util_listenv (), G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL)) { - exit (1); - } - - g_child_watch_add (pid, - (GChildWatchFunc)on_fail_whale_failed, - NULL); + g_critical ("We failed, but the fail whale is dead. Sorry...."); + exit (1); } diff -Nru gnome-session-3.30.1/gnome-session/gsm-manager.c gnome-session-3.30.1/gnome-session/gsm-manager.c --- gnome-session-3.30.1/gnome-session/gsm-manager.c 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/gnome-session/gsm-manager.c 2019-02-13 19:09:46.000000000 +0000 @@ -399,7 +399,8 @@ } else { g_debug ("GsmManager: stopping app %s", gsm_app_peek_id (app)); - /* If we don't have a client then we should try to kill the app */ + /* If we don't have a client then we should try to kill the app , + * if it is running */ error = NULL; res = gsm_app_stop (app, &error); if (! res) { @@ -724,10 +725,36 @@ } static gboolean +_autostart_delay_timeout (GsmApp *app) +{ + GError *error = NULL; + gboolean res; + + if (!gsm_app_peek_is_disabled (app) + && !gsm_app_peek_is_conditionally_disabled (app)) { + res = gsm_app_start (app, &error); + if (!res) { + if (error != NULL) { + g_warning ("Could not launch application '%s': %s", + gsm_app_peek_app_id (app), + error->message); + g_error_free (error); + } + } + } + + g_object_unref (app); + + return FALSE; +} + +static gboolean _start_app (const char *id, GsmApp *app, GsmManager *manager) { + int delay; + if (gsm_app_peek_phase (app) != manager->priv->phase) { goto out; } @@ -745,6 +772,18 @@ goto out; } + /* Only accept an autostart delay for the application phase */ + if (manager->priv->phase == GSM_MANAGER_PHASE_APPLICATION) { + delay = gsm_app_peek_autostart_delay (app); + if (delay > 0) { + g_timeout_add_seconds (delay, + (GSourceFunc)_autostart_delay_timeout, + g_object_ref (app)); + g_debug ("GsmManager: %s is scheduled to start in %d seconds", id, delay); + goto out; + } + } + if (!start_app_or_warn (manager, app)) goto out; @@ -1398,11 +1437,12 @@ return FALSE; } - g_debug ("GsmManager:\tID: %s\tapp-id:%s\tis-disabled:%d\tis-conditionally-disabled:%d", + g_debug ("GsmManager:\tID: %s\tapp-id:%s\tis-disabled:%d\tis-conditionally-disabled:%d\tis-delayed:%d", gsm_app_peek_id (app), gsm_app_peek_app_id (app), gsm_app_peek_is_disabled (app), - gsm_app_peek_is_conditionally_disabled (app)); + gsm_app_peek_is_conditionally_disabled (app), + (gsm_app_peek_autostart_delay (app) > 0)); return FALSE; } @@ -2447,6 +2487,47 @@ end_phase (manager); } +gboolean +gsm_manager_request_shutdown (GsmManager *manager, + GError **error) +{ + g_debug ("GsmManager: RequestShutdown called"); + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + + if (manager->priv->phase != GSM_MANAGER_PHASE_RUNNING) { + g_set_error (error, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "RequestShutdown interface is only available during the Running phase"); + return FALSE; + } + + request_shutdown (manager); + + return TRUE; +} + +gboolean +gsm_manager_request_reboot (GsmManager *manager, + GError **error) +{ + g_debug ("GsmManager: RequestReboot called"); + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + + if (manager->priv->phase != GSM_MANAGER_PHASE_RUNNING) { + g_set_error (error, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "RequestReboot interface is only available during the Running phase"); + return FALSE; + } + request_reboot (manager); + + return TRUE; +} + static gboolean gsm_manager_shutdown (GsmExportedManager *skeleton, GDBusMethodInvocation *invocation, diff -Nru gnome-session-3.30.1/gnome-session/gsm-manager.h gnome-session-3.30.1/gnome-session/gsm-manager.h --- gnome-session-3.30.1/gnome-session/gsm-manager.h 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/gnome-session/gsm-manager.h 2019-02-13 19:09:46.000000000 +0000 @@ -126,6 +126,10 @@ gboolean gsm_manager_logout (GsmManager *manager, guint logout_mode, GError **error); +gboolean gsm_manager_request_shutdown (GsmManager *manager, + GError **error); +gboolean gsm_manager_request_reboot (GsmManager *manager, + GError **error); gboolean gsm_manager_set_phase (GsmManager *manager, GsmManagerPhase phase); diff -Nru gnome-session-3.30.1/gnome-session/gsm-session-fill.c gnome-session-3.30.1/gnome-session/gsm-session-fill.c --- gnome-session-3.30.1/gnome-session/gsm-session-fill.c 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/gnome-session/gsm-session-fill.c 2019-02-13 19:09:46.000000000 +0000 @@ -132,6 +132,21 @@ load_standard_apps (GsmManager *manager, GKeyFile *keyfile) { + GError *error; + + if (!g_getenv("UPSTART_SESSION")) { + g_debug ("fill: *** Executing user migration"); + error = NULL; + if(!g_spawn_command_line_sync ("session-migration", NULL, NULL, NULL, &error)) { + g_warning ("Error while executing session-migration: %s", error->message); + g_error_free (error); + } + } else { + g_debug("fill: *** Not executing session-migration as we are " + "running in an Upstart user session which should execute " + "it."); + } + g_debug ("fill: *** Adding required components"); handle_required_components (keyfile, !gsm_manager_get_failsafe (manager), append_required_components_helper, manager); @@ -143,7 +158,8 @@ autostart_dirs = gsm_util_get_autostart_dirs (); - maybe_load_saved_session_apps (manager); + if (g_getenv ("GNOME_SESSION_SAVE") != NULL) + maybe_load_saved_session_apps (manager); for (i = 0; autostart_dirs[i]; i++) { gsm_manager_add_autostart_apps_from_dir (manager, diff -Nru gnome-session-3.30.1/gnome-session/main.c gnome-session-3.30.1/gnome-session/main.c --- gnome-session-3.30.1/gnome-session/main.c 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/gnome-session/main.c 2019-02-13 19:09:46.000000000 +0000 @@ -408,6 +408,11 @@ g_free (ibus_path); } + /* Some third-party programs rely on GNOME_DESKTOP_SESSION_ID to + * detect if GNOME is running. We keep this for compatibility reasons. + */ + gsm_util_setenv ("GNOME_DESKTOP_SESSION_ID", "this-is-deprecated"); + /* We want to use the GNOME menus which has the designed categories. */ gsm_util_setenv ("XDG_MENU_PREFIX", "gnome-"); diff -Nru gnome-session-3.30.1/gnome-session/org.gnome.SessionManager.xml gnome-session-3.30.1/gnome-session/org.gnome.SessionManager.xml --- gnome-session-3.30.1/gnome-session/org.gnome.SessionManager.xml 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/gnome-session/org.gnome.SessionManager.xml 2019-02-13 19:09:46.000000000 +0000 @@ -369,6 +369,23 @@ + + + + Request a shutdown with no dialog + + + + + + + + Request a reboot with no dialog + + + + + diff -Nru gnome-session-3.30.1/meson.build gnome-session-3.30.1/meson.build --- gnome-session-3.30.1/meson.build 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/meson.build 2019-02-13 19:09:46.000000000 +0000 @@ -181,6 +181,7 @@ subdir('gnome-session') subdir('tools') subdir('data') +subdir('capplet') enable_docbook = get_option('docbook') enable_man = get_option('man') diff -Nru gnome-session-3.30.1/meson_post_install.py gnome-session-3.30.1/meson_post_install.py --- gnome-session-3.30.1/meson_post_install.py 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/meson_post_install.py 2019-02-13 19:09:46.000000000 +0000 @@ -20,7 +20,3 @@ dst_dir = os.path.join(install_root, 'wayland-sessions') if not os.path.exists(dst_dir): os.makedirs(dst_dir) - -src = os.path.join(install_root, 'xsessions', 'gnome.desktop') -dst = os.path.join(dst_dir, 'gnome.desktop') -shutil.copyfile(src, dst) diff -Nru gnome-session-3.30.1/.pc/applied-patches gnome-session-3.30.1/.pc/applied-patches --- gnome-session-3.30.1/.pc/applied-patches 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/applied-patches 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,10 @@ +debian/Revert-main-Remove-GNOME_DESKTOP_SESSION_ID-envvar.patch +ubuntu/support_autostart_delay.patch +ubuntu/ubuntu-sessions.patch +ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch +ubuntu/remove_session_saving_from_gui.patch +ubuntu/add_sessionmigration.patch +ubuntu/dbus_request_shutdown.patch +ubuntu/kill_the_fail_whale.patch +ubuntu/ignore_gsettings_region.patch +revert_remove_gnome_session_properties.patch diff -Nru gnome-session-3.30.1/.pc/debian/Revert-main-Remove-GNOME_DESKTOP_SESSION_ID-envvar.patch/gnome-session/main.c gnome-session-3.30.1/.pc/debian/Revert-main-Remove-GNOME_DESKTOP_SESSION_ID-envvar.patch/gnome-session/main.c --- gnome-session-3.30.1/.pc/debian/Revert-main-Remove-GNOME_DESKTOP_SESSION_ID-envvar.patch/gnome-session/main.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/debian/Revert-main-Remove-GNOME_DESKTOP_SESSION_ID-envvar.patch/gnome-session/main.c 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,436 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * 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, see . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "gdm-log.h" + +#include "gsm-util.h" +#include "gsm-manager.h" +#include "gsm-session-fill.h" +#include "gsm-store.h" +#include "gsm-system.h" +#include "gsm-fail-whale.h" + +#ifdef ENABLE_SYSTEMD_JOURNAL +#include +#endif + +#define GSM_DBUS_NAME "org.gnome.SessionManager" + +static gboolean failsafe = FALSE; +static gboolean show_version = FALSE; +static gboolean debug = FALSE; +static gboolean please_fail = FALSE; +static gboolean disable_acceleration_check = FALSE; +static const char *session_name = NULL; +static GsmManager *manager = NULL; +static char *gl_renderer = NULL; + +static GMainLoop *loop; + +void +gsm_quit (void) +{ + g_main_loop_quit (loop); +} + +static void +gsm_main (void) +{ + if (loop == NULL) + loop = g_main_loop_new (NULL, TRUE); + + g_main_loop_run (loop); +} + +static void +on_name_lost (GDBusConnection *connection, + const char *name, + gpointer data) +{ + if (connection == NULL) { + g_warning ("Lost name on bus: %s", name); + gsm_fail_whale_dialog_we_failed (TRUE, TRUE, NULL); + } else { + g_debug ("Calling name lost callback function"); + + /* + * When the signal handler gets a shutdown signal, it calls + * this function to inform GsmManager to not restart + * applications in the off chance a handler is already queued + * to dispatch following the below call to gtk_main_quit. + */ + gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_EXIT); + + gsm_quit (); + } +} + +static gboolean +term_or_int_signal_cb (gpointer data) +{ + GsmManager *manager = (GsmManager *)data; + + /* let the fatal signals interrupt us */ + g_debug ("Caught SIGINT/SIGTERM, shutting down normally."); + + gsm_manager_logout (manager, GSM_MANAGER_LOGOUT_MODE_FORCE, NULL); + + return FALSE; +} + +static gboolean +sigusr2_cb (gpointer data) +{ + g_debug ("-------- MARK --------"); + return TRUE; +} + +static gboolean +sigusr1_cb (gpointer data) +{ + gdm_log_toggle_debug (); + return TRUE; +} + +static void +on_name_acquired (GDBusConnection *connection, + const char *name, + gpointer data) +{ + gsm_manager_start (manager); +} + +static void +create_manager (void) +{ + GsmStore *client_store; + + client_store = gsm_store_new (); + manager = gsm_manager_new (client_store, failsafe); + g_object_unref (client_store); + + g_unix_signal_add (SIGTERM, term_or_int_signal_cb, manager); + g_unix_signal_add (SIGINT, term_or_int_signal_cb, manager); + g_unix_signal_add (SIGUSR1, sigusr1_cb, manager); + g_unix_signal_add (SIGUSR2, sigusr2_cb, manager); + + if (IS_STRING_EMPTY (session_name)) { + session_name = _gsm_manager_get_default_session (manager); + } + + if (!gsm_session_fill (manager, session_name)) { + gsm_fail_whale_dialog_we_failed (FALSE, TRUE, NULL); + } + + _gsm_manager_set_renderer (manager, gl_renderer); +} + +static void +on_bus_acquired (GDBusConnection *connection, + const char *name, + gpointer data) +{ + create_manager (); +} + +static guint +acquire_name (void) +{ + return g_bus_own_name (G_BUS_TYPE_SESSION, + GSM_DBUS_NAME, + G_BUS_NAME_OWNER_FLAGS_NONE, + on_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, NULL); +} + +static gboolean +require_dbus_session (int argc, + char **argv, + GError **error) +{ + char **new_argv; + int i; + + if (g_getenv ("DBUS_SESSION_BUS_ADDRESS")) + return TRUE; + + /* Just a sanity check to prevent infinite recursion if + * dbus-launch fails to set DBUS_SESSION_BUS_ADDRESS + */ + g_return_val_if_fail (!g_str_has_prefix (argv[0], "dbus-launch"), + TRUE); + + /* +2 for our new arguments, +1 for NULL */ + new_argv = g_malloc ((argc + 3) * sizeof (*argv)); + + new_argv[0] = "dbus-launch"; + new_argv[1] = "--exit-with-session"; + for (i = 0; i < argc; i++) { + new_argv[i + 2] = argv[i]; + } + new_argv[i + 2] = NULL; + + if (!execvp ("dbus-launch", new_argv)) { + g_set_error (error, + G_SPAWN_ERROR, + G_SPAWN_ERROR_FAILED, + "No session bus and could not exec dbus-launch: %s", + g_strerror (errno)); + return FALSE; + } + + /* Should not be reached */ + return TRUE; +} + +static gboolean +check_gl (GError **error) +{ + int status; + char *argv[] = { LIBEXECDIR "/gnome-session-check-accelerated", NULL }; + + if (getenv ("DISPLAY") == NULL) { + /* Not connected to X11, someone else will take care of checking GL */ + return TRUE; + } + + if (!g_spawn_sync (NULL, (char **) argv, NULL, 0, NULL, NULL, &gl_renderer, NULL, + &status, error)) { + return FALSE; + } + + return g_spawn_check_exit_status (status, error); +} + +static void +initialize_gio (void) +{ + char *disable_fuse = NULL; + char *use_vfs = NULL; + + disable_fuse = g_strdup (g_getenv ("GVFS_DISABLE_FUSE")); + use_vfs = g_strdup (g_getenv ("GIO_USE_VFS")); + + g_setenv ("GVFS_DISABLE_FUSE", "1", TRUE); + g_setenv ("GIO_USE_VFS", "local", TRUE); + g_vfs_get_default (); + + if (use_vfs) { + g_setenv ("GIO_USE_VFS", use_vfs, TRUE); + g_free (use_vfs); + } else { + g_unsetenv ("GIO_USE_VFS"); + } + + if (disable_fuse) { + g_setenv ("GVFS_DISABLE_FUSE", disable_fuse, TRUE); + g_free (disable_fuse); + } else { + g_unsetenv ("GVFS_DISABLE_FUSE"); + } +} + +int +main (int argc, char **argv) +{ + GError *error = NULL; + static char **override_autostart_dirs = NULL; + static char *opt_session_name = NULL; + const char *debug_string = NULL; + gboolean gl_failed = FALSE; + guint name_owner_id; + GOptionContext *options; + static GOptionEntry entries[] = { + { "autostart", 'a', 0, G_OPTION_ARG_STRING_ARRAY, &override_autostart_dirs, N_("Override standard autostart directories"), N_("AUTOSTART_DIR") }, + { "session", 0, 0, G_OPTION_ARG_STRING, &opt_session_name, N_("Session to use"), N_("SESSION_NAME") }, + { "debug", 0, 0, G_OPTION_ARG_NONE, &debug, N_("Enable debugging code"), NULL }, + { "failsafe", 'f', 0, G_OPTION_ARG_NONE, &failsafe, N_("Do not load user-specified applications"), NULL }, + { "version", 0, 0, G_OPTION_ARG_NONE, &show_version, N_("Version of this application"), NULL }, + /* Translators: the 'fail whale' is the black dialog we show when something goes seriously wrong */ + { "whale", 0, 0, G_OPTION_ARG_NONE, &please_fail, N_("Show the fail whale dialog for testing"), NULL }, + { "disable-acceleration-check", 0, 0, G_OPTION_ARG_NONE, &disable_acceleration_check, N_("Disable hardware acceleration check"), NULL }, + { NULL, 0, 0, 0, NULL, NULL, NULL } + }; + + /* Make sure that we have a session bus */ + if (!require_dbus_session (argc, argv, &error)) { + gsm_util_init_error (TRUE, "%s", error->message); + } + + /* From 3.14 GDM sets XDG_CURRENT_DESKTOP. For compatibility with + * older versions of GDM, other display managers, and startx, + * set a fallback value if we don't find it set. + */ + if (g_getenv ("XDG_CURRENT_DESKTOP") == NULL) { + g_setenv("XDG_CURRENT_DESKTOP", "GNOME", TRUE); + gsm_util_setenv ("XDG_CURRENT_DESKTOP", "GNOME"); + } + + /* Make sure we initialize gio in a way that does not autostart any daemon */ + initialize_gio (); + + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + debug_string = g_getenv ("GNOME_SESSION_DEBUG"); + if (debug_string != NULL) { + debug = atoi (debug_string) == 1; + } + + error = NULL; + options = g_option_context_new (_(" — the GNOME session manager")); + g_option_context_add_main_entries (options, entries, GETTEXT_PACKAGE); + g_option_context_parse (options, &argc, &argv, &error); + if (error != NULL) { + g_warning ("%s", error->message); + exit (1); + } + + g_option_context_free (options); + + /* Rebind stdout/stderr to the journal explicitly, so that + * journald picks ups the nicer "gnome-session" as the program + * name instead of whatever shell script GDM happened to use. + */ +#ifdef ENABLE_SYSTEMD_JOURNAL + if (!debug) { + int journalfd; + + journalfd = sd_journal_stream_fd (PACKAGE, LOG_INFO, 0); + if (journalfd >= 0) { + dup2(journalfd, 1); + dup2(journalfd, 2); + } + } +#endif + + gdm_log_init (); + gdm_log_set_debug (debug); + + if (disable_acceleration_check) { + g_debug ("hardware acceleration check is disabled"); + } else { + /* Check GL, if it doesn't work out then force software fallback */ + if (!check_gl (&error)) { + gl_failed = TRUE; + + g_debug ("hardware acceleration check failed: %s", + error? error->message : ""); + g_clear_error (&error); + if (g_getenv ("LIBGL_ALWAYS_SOFTWARE") == NULL) { + g_setenv ("LIBGL_ALWAYS_SOFTWARE", "1", TRUE); + if (!check_gl (&error)) { + g_warning ("software acceleration check failed: %s", + error? error->message : ""); + g_clear_error (&error); + } else { + gl_failed = FALSE; + } + } + } + } + + if (show_version) { + g_print ("%s %s\n", argv [0], VERSION); + exit (0); + } + + if (gl_failed) { + gsm_fail_whale_dialog_we_failed (FALSE, TRUE, NULL); + gsm_main (); + exit (1); + } + + if (please_fail) { + gsm_fail_whale_dialog_we_failed (TRUE, TRUE, NULL); + gsm_main (); + exit (1); + } + + gsm_util_export_activation_environment (NULL); + +#ifdef HAVE_SYSTEMD + gsm_util_export_user_environment (NULL); +#endif + + { + gchar *ibus_path; + + ibus_path = g_find_program_in_path("ibus-daemon"); + + if (ibus_path) { + const gchar *p; + p = g_getenv ("QT_IM_MODULE"); + if (!p || !*p) + p = "ibus"; + gsm_util_setenv ("QT_IM_MODULE", p); + p = g_getenv ("XMODIFIERS"); + if (!p || !*p) + p = "@im=ibus"; + gsm_util_setenv ("XMODIFIERS", p); + } + + g_free (ibus_path); + } + + /* We want to use the GNOME menus which has the designed categories. + */ + gsm_util_setenv ("XDG_MENU_PREFIX", "gnome-"); + + gsm_util_set_autostart_dirs (override_autostart_dirs); + session_name = opt_session_name; + + /* Talk to logind before acquiring a name, since it does synchronous + * calls at initialization time that invoke a main loop and if we + * already owned a name, then we would service too early during + * that main loop. + */ + g_object_unref (gsm_get_system ()); + + name_owner_id = acquire_name (); + + gsm_main (); + + g_clear_object (&manager); + g_free (gl_renderer); + + g_bus_unown_name (name_owner_id); + gdm_log_shutdown (); + + return 0; +} diff -Nru gnome-session-3.30.1/.pc/.quilt_patches gnome-session-3.30.1/.pc/.quilt_patches --- gnome-session-3.30.1/.pc/.quilt_patches 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/.quilt_patches 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1 @@ +/home/buildd/build-RECIPEBRANCHBUILD-2039355/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches diff -Nru gnome-session-3.30.1/.pc/.quilt_series gnome-session-3.30.1/.pc/.quilt_series --- gnome-session-3.30.1/.pc/.quilt_series 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/.quilt_series 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1 @@ +/home/buildd/build-RECIPEBRANCHBUILD-2039355/chroot-autobuild/home/buildd/work/tree/recipe/debian/patches/series diff -Nru gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/data/meson.build gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/data/meson.build --- gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/data/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/data/meson.build 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,137 @@ +desktop_plain = 'gnome' + +desktops = [ + 'gnome-xorg', + 'ubuntu', + 'ubuntu-communitheme-snap', + 'unity', +] + +if enable_session_selector + desktops += 'gnome-custom-session' +endif + +foreach name: desktops + desktop_conf = configuration_data() + desktop_conf.set('bindir', session_bindir) + + desktop = name + '.desktop' + + desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf + ) + + install_dir = join_paths(session_datadir, 'xsessions') + # FIXME: The same target can not be copied into two directories. + # There is a workaround in meson_post_install.py until proper solution arises: + # https://groups.google.com/forum/#!topic/mesonbuild/3iIoYPrN4P0 + if name == desktop_plain + #install_dir: [ + # join_paths(session_datadir, 'xsessions'), + # join_paths(session_datadir, 'wayland-sessions') + #] + endif + + desktop_target = i18n.merge_file( + desktop, + type: 'desktop', + input: desktop_in, + output: desktop, + po_dir: po_dir, + install: true, + install_dir: install_dir + ) + +endforeach + +wayland_desktops = [ + desktop_plain, + 'ubuntu-wayland', + 'ubuntu-communitheme-snap-wayland', +] + +foreach name: wayland_desktops + desktop_conf = configuration_data() + desktop_conf.set('bindir', session_bindir) + + desktop = name + '.desktop' + + desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf + ) + + install_dir = join_paths(session_datadir, 'wayland-sessions') + + desktop_target = i18n.merge_file( + desktop, + type: 'desktop', + input: desktop_in, + output: desktop, + po_dir: po_dir, + install: true, + install_dir: install_dir + ) + +endforeach + +sessions = [ + 'gnome.session', + 'gnome-dummy.session', + 'ubuntu.session', + 'unity.session' +] + +foreach session: sessions + desktop_conf = configuration_data() + desktop_conf.set('libexecdir', session_libexecdir) + + desktop = session + '.desktop' + + desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf + ) + + i18n.merge_file( + session, + type: 'desktop', + input: desktop_in, + output: session, + po_dir: po_dir, + install: true, + install_dir: join_paths(session_pkgdatadir, 'sessions') + ) +endforeach + +schema_conf = configuration_data() + +schema = 'org.gnome.SessionManager.gschema.xml' + +configure_file( + input: schema + '.in', + output: schema, + install: true, + install_dir: join_paths(session_datadir, 'glib-2.0', 'schemas'), + configuration: schema_conf +) + +install_data( + 'gnome-session.convert', + install_dir: join_paths(session_datadir, 'GConf', 'gsettings') +) + +data = files('hardware-compatibility') + +if enable_session_selector + data += files('session-selector.ui') +endif + +install_data( + data, + install_dir: session_pkgdatadir +) diff -Nru gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/doc/man/gnome-session.1 gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/doc/man/gnome-session.1 --- gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/doc/man/gnome-session.1 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/doc/man/gnome-session.1 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,112 @@ +.\" +.\" gnome-session manual page. +.\" (C) 2000 Miguel de Icaza (miguel@helixcode.com) +.\" (C) 2009-2010 Vincent Untz (vuntz@gnome.org) +.\" +.TH GNOME-SESSION 1 "GNOME" +.SH NAME +gnome-session \- Start the GNOME desktop environment +.SH SYNOPSIS +.B gnome-session [\-a|\-\-autostart=DIR] [\-\-session=SESSION] [\-\-failsafe|\-f] [\-\-debug] [\-\-whale] +.SH DESCRIPTION +The \fIgnome-session\fP program starts up the GNOME desktop +environment. This command is typically executed by your login manager +(either gdm, xdm, or from your X startup scripts). It will load +either your saved session, or it will provide a default session for the +user as defined by the system administrator (or the default GNOME +installation on your system). +.PP +The default session is defined in \fBgnome.session\fP, a .desktop-like +file that is looked for in +\fB$XDG_CONFIG_HOME/gnome-session/sessions\fP, +\fB$XDG_CONFIG_DIRS/gnome-session/sessions\fP and +\fB$XDG_DATA_DIRS/gnome-session/sessions\fP. +.PP +When saving a session, \fIgnome-session\fP saves the currently running +applications in the \fB$XDG_CONFIG_HOME/gnome-session/saved-session\fP +directory. +.PP +\fIgnome-session\fP is an X11R6 session manager. It can manage GNOME +applications as well as any X11R6 SM compliant application. +.SH OPTIONS +The following options are supported: +.TP +.I "--autostart=DIR" +Start all applications defined in \fIDIR\fP, instead of starting the +applications defined in \fBgnome.session\fP, or via the \fI--session\fP +option. Multiple \fI--autostart\fP options can be passed. +.TP +.I "--session=SESSION" +Use the applications defined in \fBSESSION.session\fP. If not specified, +\fBgnome.session\fP will be used. +.TP +.I "--failsafe" +Run in fail-safe mode. User-specified applications will not be started. +.TP +.I "--debug" +Enable debugging code. +.TP +.I "--whale" +Show the fail whale in a dialog for debugging it. +.SH SESSION DEFINITION +Sessions are defined in \fB.session\fP files, that are using a .desktop-like +format, with the following keys in the \fBGNOME Session\fP group: +.TP +.I Name +Name of the session. This can be localized. +.TP +.I RequiredComponents +List of component identifiers (desktop files) that are required by the session. The required components will always run in the session. +.PP +Here is an example of a session definition: +.PP +.in +4n +.nf +[GNOME Session] +Name=GNOME +RequiredComponents=gnome-shell;gnome-settings-daemon; +.in +.fi +.PP +The \fB.session\fP files are looked for in +\fB$XDG_CONFIG_HOME/gnome-session/sessions\fP, +\fB$XDG_CONFIG_DIRS/gnome-session/sessions\fP and +\fB$XDG_DATA_DIRS/gnome-session/sessions\fP. +.SH ENVIRONMENT +\fIgnome-session\fP sets several environment variables for the use of +its child processes: +.PP +.B SESSION_MANAGER +.IP +This variable is used by session-manager aware clients to contact +gnome-session. +.PP +.B DISPLAY +.IP +This variable is set to the X display being used by +\fIgnome-session\fP. Note that if the \fI--display\fP option is used +this might be different from the setting of the environment variable +when gnome-session is invoked. +.SH FILES +.PP +.B $XDG_CONFIG_HOME/config/autostart +.B $XDG_CONFIG_DIRS/config/autostart +.B /usr/share/gnome/autostart +.IP +The applications defined in those directories will be started on login. +.PP +.B $XDG_CONFIG_HOME/gnome-session/sessions +.B $XDG_CONFIG_DIRS/gnome-session/sessions +.B $XDG_DATA_DIRS/gnome-session/sessions +.IP +These directories contain the \fB.session\fP files that can be used +with the \fI--session\fP option. +.PP +.B $XDG_CONFIG_HOME/gnome-session/saved-session +.IP +This directory contains the list of applications of the saved session. +.SH BUGS +If you find bugs in the \fIgnome-session\fP program, please report +these on https://bugzilla.gnome.org. +.SH SEE ALSO +.BR gnome-session-quit(1) diff -Nru gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/doc/man/meson.build gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/doc/man/meson.build --- gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/doc/man/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/doc/man/meson.build 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,43 @@ +man1_dir = join_paths(session_mandir, 'man1') + +xsltproc_cmd = [ + xsltproc, + '--output', '@OUTPUT@', + '--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', + 'http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl', + '@INPUT@' +] + +mans = ['gnome-session-inhibit'] + +if enable_session_selector + mans += 'gnome-session-selector' +endif + +foreach man: mans + output = man + '.1' + + custom_target( + output, + input: man + '.xml', + output: output, + command: xsltproc_cmd, + install: true, + install_dir: man1_dir + ) +endforeach + +man_data = files( + 'gnome-session.1', + 'gnome-session-quit.1' +) + +install_data( + man_data, + install_dir: man1_dir +) diff -Nru gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/meson.build gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/meson.build --- gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/meson.build 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,216 @@ +project( + 'gnome-session', 'c', + version: '3.30.1', + license: 'GPL2+', + default_options: 'buildtype=debugoptimized', + meson_version: '>= 0.43.0' +) + +session_version = meson.project_version() +version_array = session_version.split('.') +session_major_version = version_array[0].to_int() +session_minor_version = version_array[1].to_int() +session_micro_version = version_array[2].to_int() + +session_api_version = '@0@.0'.format(session_major_version) +session_api_name = '@0@-@1@'.format(meson.project_name(), session_api_version) + +session_prefix = get_option('prefix') +session_bindir = join_paths(session_prefix, get_option('bindir')) +session_datadir = join_paths(session_prefix, get_option('datadir')) +session_libdir = join_paths(session_prefix, get_option('libdir')) +session_libexecdir = join_paths(session_prefix, get_option('libexecdir')) +session_localedir = join_paths(session_prefix, get_option('localedir')) +session_localstatedir = join_paths(session_prefix, get_option('localstatedir')) +session_mandir = join_paths(session_prefix, get_option('mandir')) +session_sbindir = join_paths(session_prefix, get_option('sbindir')) +session_sysconfdir = join_paths(session_prefix, get_option('sysconfdir')) + +session_pkgdatadir = join_paths(session_datadir, meson.project_name()) + +session_debug = get_option('buildtype').contains('debug') + +cc = meson.get_compiler('c') + +config_h = configuration_data() + +# defines +set_defines = [ + # package + ['PACKAGE', meson.project_name()], + ['PACKAGE_VERSION', session_version], + ['VERSION', session_version], + # i18n + ['GETTEXT_PACKAGE', session_api_name] +] + +foreach define: set_defines + config_h.set_quoted(define[0], define[1]) +endforeach + +# compiler flags +common_flags = ['-DHAVE_CONFIG_H'] + +enable_deprecation_flags = get_option('deprecation_flags') +if enable_deprecation_flags + common_flags += [ + '-DBONOBO_DISABLE_DEPRECATED', + '-DBONOBO_DISABLE_SINGLE_INCLUDES', + '-DBONOBO_UI_DISABLE_DEPRECATED', + '-DBONOBO_UI_DISABLE_SINGLE_INCLUDES', + '-DGNOME_DISABLE_DEPRECATED', + '-DGNOME_DISABLE_SINGLE_INCLUDES', + '-DLIBGLADE_DISABLE_DEPRECATED', + '-DLIBGLADE_DISABLE_SINGLE_INCLUDES', + '-DGNOME_VFS_DISABLE_DEPRECATED', + '-DGNOME_VFS_DISABLE_SINGLE_INCLUDES', + '-DWNCK_DISABLE_DEPRECATED', + '-DWNCK_DISABLE_SINGLE_INCLUDES', + '-DLIBSOUP_DISABLE_DEPRECATED', + '-DLIBSOUP_DISABLE_SINGLE_INCLUDES' + ] +endif + +compiler_flags = [] +if session_debug + test_cflags = [ + '-Werror=format=2', + '-Werror=implicit-function-declaration', + '-Werror=init-self', + '-Werror=missing-include-dirs', + '-Werror=missing-prototypes', + '-Werror=pointer-arith', + '-Werror=return-type', + '-Wnested-externs', + '-Wstrict-prototypes' + ] + + compiler_flags += cc.get_supported_arguments(test_cflags) +endif + +add_project_arguments(common_flags + compiler_flags, language: 'c') + +glib_req_version = '>= 2.46.0' + +gio_dep = dependency('gio-2.0', version: glib_req_version) +glib_dep = dependency('glib-2.0', version: glib_req_version) +gtk_dep = dependency('gtk+-3.0', version: '>= 3.18.0') +xtrans_dep = dependency('xtrans') +ice_dep = dependency('ice') +sm_dep = dependency('sm') +x11_dep = dependency('x11') + +session_deps = [ + gio_dep, + glib_dep, + dependency('gnome-desktop-3.0', version: '>= 3.18.0'), + dependency('json-glib-1.0', version: '>= 0.10') +] + +session_bin_deps = session_deps + [ + xtrans_dep, + ice_dep, + sm_dep +] + +# Check for session selector GTK+ UI +enable_session_selector = get_option('session_selector') + +# Check for session tracking backend +session_tracking = 'null backend' + +enable_systemd = get_option('systemd') +enable_systemd_journal = get_option('systemd_journal') +enable_consolekit = get_option('consolekit') +if enable_systemd or enable_consolekit + session_bin_deps += dependency('gio-unix-2.0', version: glib_req_version) + + # Check for systemd + if enable_systemd + libsystemd_dep = dependency('libsystemd', version: '>= 209', required: false) + session_bin_deps += libsystemd_dep + + if not libsystemd_dep.found() + libsystemd_login_dep = dependency('libsystemd-login', version: '>= 183', required: false) + libsystemd_daemon_dep = dependency('libsystemd-daemon', required: false) + libsystemd_journal_dep = dependency('libsystemd-journal', required: false) + assert(libsystemd_login_dep.found() and libsystemd_daemon_dep.found() and libsystemd_journal_dep.found(), + 'Systemd support explicitly required, but systemd not found') + + session_bin_deps += [ + libsystemd_login_dep, + libsystemd_daemon_dep, + libsystemd_journal_dep + ] + endif + + session_tracking = 'systemd' + endif + + # Check for ConsoleKit + if enable_consolekit + dbus_glib_dep = dependency('dbus-glib-1', version: '>= 0.76') + assert(dbus_glib_dep.found(), 'ConsoleKit support explicitly required, but dbus-glib not found') + + session_bin_deps += dbus_glib_dep + + if enable_systemd + session_tracking += ' (with fallback to ConsoleKit)' + else + session_tracking = 'ConsoleKit' + endif + endif +endif +config_h.set('HAVE_SYSTEMD', enable_systemd) +config_h.set('ENABLE_SYSTEMD_JOURNAL', enable_systemd_journal) +config_h.set('HAVE_CONSOLEKIT', enable_consolekit) + +configure_file( + output: 'config.h', + configuration: config_h +) + +gnome = import('gnome') +i18n = import('i18n') +pkg = import('pkgconfig') + +po_dir = join_paths(meson.source_root(), 'po') + +top_inc = include_directories('.') + +subdir('gnome-session') +subdir('tools') +subdir('data') + +enable_docbook = get_option('docbook') +enable_man = get_option('man') +if enable_docbook or enable_man + subdir('doc') +endif + +subdir('po') + +meson.add_install_script( + 'meson_post_install.py', + session_datadir +) + +output = '\n gnome-session ' + session_version + '\n' +output += ' ====================\n\n' +output += ' prefix: ' + session_prefix + '\n' +output += ' exec_prefix: ' + session_libexecdir + '\n' +output += ' libdir: ' + session_libdir + '\n' +output += ' bindir: ' + session_bindir + '\n' +output += ' sbindir: ' + session_sbindir + '\n' +output += ' sysconfdir: ' + session_sysconfdir + '\n' +output += ' localstatedir: ' + session_localstatedir + '\n' +output += ' datadir: ' + session_datadir + '\n' +output += ' source code location: ' + meson.source_root() + '\n' +output += ' compiler: ' + cc.get_id() + '\n' +output += ' cflags: ' + ' '.join(compiler_flags) + '\n' +output += ' Debug mode: ' + session_debug.to_string() + '\n' +output += ' Use *_DISABLE_DEPRECATED: ' + enable_deprecation_flags.to_string() + '\n\n' +output += ' Session tracking: ' + session_tracking + ' \n' +output += ' Build Docbook: ' + enable_docbook.to_string() + '\n' +output += ' Build manpages: ' + enable_man.to_string() +message(output) diff -Nru gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/po/POTFILES.in gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/po/POTFILES.in --- gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/po/POTFILES.in 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/po/POTFILES.in 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,26 @@ +# List of source files containing translatable strings. +# Please keep this file sorted alphabetically. +data/gnome-custom-session.desktop.in.in +data/gnome.desktop.in.in +data/gnome-dummy.session.desktop.in.in +data/gnome.session.desktop.in.in +data/gnome-xorg.desktop.in.in +data/org.gnome.SessionManager.gschema.xml.in +data/session-selector.ui +data/ubuntu.desktop.in.in +data/ubuntu-wayland.desktop.in.in +data/ubuntu-communitheme-snap.desktop.in.in +data/ubuntu-communitheme-snap-wayland.desktop.in.in +data/ubuntu.session.desktop.in.in +data/unity.desktop.in.in +data/unity.session.desktop.in.in +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-quit.c +tools/gnome-session-selector.c diff -Nru gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/po/POTFILES.skip gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/po/POTFILES.skip --- gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/po/POTFILES.skip 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/revert_remove_gnome_session_properties.patch/po/POTFILES.skip 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,15 @@ +# List of source files containing translatable strings that should not be +# translated. +# Please keep this file sorted alphabetically. +data/gnome-custom-session.desktop.in +data/gnome.desktop.in +data/gnome-dummy.session.desktop.in +data/gnome.session.desktop.in +data/gnome-xorg.desktop.in +data/ubuntu.desktop.in +data/ubuntu-wayland.desktop.in +data/ubuntu-communitheme-snap.desktop.in +data/ubuntu-communitheme-snap-wayland.desktop.in +data/ubuntu.session.desktop.in +data/unity.desktop.in +data/unity.session.desktop.in diff -Nru gnome-session-3.30.1/.pc/ubuntu/add_sessionmigration.patch/gnome-session/gsm-session-fill.c gnome-session-3.30.1/.pc/ubuntu/add_sessionmigration.patch/gnome-session/gsm-session-fill.c --- gnome-session-3.30.1/.pc/ubuntu/add_sessionmigration.patch/gnome-session/gsm-session-fill.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/add_sessionmigration.patch/gnome-session/gsm-session-fill.c 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,335 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006, 2010 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * 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, see . + */ + +#include + +#include "gsm-session-fill.h" + +#include "gsm-system.h" +#include "gsm-manager.h" +#include "gsm-process-helper.h" +#include "gsm-util.h" + +#define GSM_KEYFILE_SESSION_GROUP "GNOME Session" +#define GSM_KEYFILE_RUNNABLE_KEY "IsRunnableHelper" +#define GSM_KEYFILE_FALLBACK_KEY "FallbackSession" +#define GSM_KEYFILE_REQUIRED_COMPONENTS_KEY "RequiredComponents" + +/* See https://bugzilla.gnome.org/show_bug.cgi?id=641992 for discussion */ +#define GSM_RUNNABLE_HELPER_TIMEOUT 3000 /* ms */ + +typedef void (*GsmFillHandleComponent) (const char *component, + const char *app_path, + gpointer user_data); + +static void +handle_required_components (GKeyFile *keyfile, + gboolean look_in_saved_session, + GsmFillHandleComponent callback, + gpointer user_data) +{ + char **required_components; + int i; + + g_assert (keyfile != NULL); + g_assert (callback != NULL); + + required_components = g_key_file_get_string_list (keyfile, + GSM_KEYFILE_SESSION_GROUP, + GSM_KEYFILE_REQUIRED_COMPONENTS_KEY, + NULL, NULL); + + if (!required_components) + return; + + for (i = 0; required_components[i] != NULL; i++) { + char *app_path; + + app_path = gsm_util_find_desktop_file_for_app_name (required_components[i], + look_in_saved_session, TRUE); + callback (required_components[i], app_path, user_data); + g_free (app_path); + } + + g_strfreev (required_components); +} + +static void +check_required_components_helper (const char *component, + const char *app_path, + gpointer user_data) +{ + gboolean *error = user_data; + + if (app_path == NULL) { + g_warning ("Unable to find required component '%s'", component); + *error = TRUE; + } +} + +static gboolean +check_required (GKeyFile *keyfile) +{ + gboolean error = FALSE; + + g_debug ("fill: *** Checking required components"); + + handle_required_components (keyfile, FALSE, + check_required_components_helper, &error); + + g_debug ("fill: *** Done checking required components"); + + return !error; +} + +static void +maybe_load_saved_session_apps (GsmManager *manager) +{ + GsmSystem *system; + gboolean is_login; + + system = gsm_get_system (); + is_login = gsm_system_is_login_session (system); + g_object_unref (system); + + if (is_login) + return; + + gsm_manager_add_autostart_apps_from_dir (manager, gsm_util_get_saved_session_dir ()); +} + +static void +append_required_components_helper (const char *component, + const char *app_path, + gpointer user_data) +{ + GsmManager *manager = user_data; + + if (app_path == NULL) + g_warning ("Unable to find required component '%s'", component); + else + gsm_manager_add_required_app (manager, app_path, NULL); +} + + +static void +load_standard_apps (GsmManager *manager, + GKeyFile *keyfile) +{ + g_debug ("fill: *** Adding required components"); + handle_required_components (keyfile, !gsm_manager_get_failsafe (manager), + append_required_components_helper, manager); + g_debug ("fill: *** Done adding required components"); + + if (!gsm_manager_get_failsafe (manager)) { + char **autostart_dirs; + int i; + + autostart_dirs = gsm_util_get_autostart_dirs (); + + if (g_getenv ("GNOME_SESSION_SAVE") != NULL) + maybe_load_saved_session_apps (manager); + + for (i = 0; autostart_dirs[i]; i++) { + gsm_manager_add_autostart_apps_from_dir (manager, + autostart_dirs[i]); + } + + g_strfreev (autostart_dirs); + } +} + +static GKeyFile * +get_session_keyfile_if_valid (const char *path) +{ + GKeyFile *keyfile; + gsize len; + char **list; + + g_debug ("fill: *** Looking if %s is a valid session file", path); + + keyfile = g_key_file_new (); + + if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL)) { + g_debug ("Cannot use session '%s': non-existing or invalid file.", path); + goto error; + } + + if (!g_key_file_has_group (keyfile, GSM_KEYFILE_SESSION_GROUP)) { + g_warning ("Cannot use session '%s': no '%s' group.", path, GSM_KEYFILE_SESSION_GROUP); + goto error; + } + + list = g_key_file_get_string_list (keyfile, + GSM_KEYFILE_SESSION_GROUP, + GSM_KEYFILE_REQUIRED_COMPONENTS_KEY, + &len, NULL); + if (list) + g_strfreev (list); + if (len == 0) + g_warning ("Session '%s': no component in the session.", path); + + return keyfile; + +error: + g_key_file_free (keyfile); + return NULL; +} + +/** + * find_valid_session_keyfile: + * @session: name of session + * + * We look for the session file in XDG_CONFIG_HOME, XDG_CONFIG_DIRS and + * XDG_DATA_DIRS. This enables users and sysadmins to override a specific + * session that is shipped in XDG_DATA_DIRS. + */ +static GKeyFile * +find_valid_session_keyfile (const char *session) +{ + GPtrArray *dirs; + const char * const *system_config_dirs; + const char * const *system_data_dirs; + int i; + GKeyFile *keyfile; + char *basename; + char *path; + + dirs = g_ptr_array_new (); + + g_ptr_array_add (dirs, (gpointer) g_get_user_config_dir ()); + + system_config_dirs = g_get_system_config_dirs (); + for (i = 0; system_config_dirs[i]; i++) + g_ptr_array_add (dirs, (gpointer) system_config_dirs[i]); + + system_data_dirs = g_get_system_data_dirs (); + for (i = 0; system_data_dirs[i]; i++) + g_ptr_array_add (dirs, (gpointer) system_data_dirs[i]); + + keyfile = NULL; + basename = g_strdup_printf ("%s.session", session); + path = NULL; + + for (i = 0; i < dirs->len; i++) { + path = g_build_filename (dirs->pdata[i], "gnome-session", "sessions", basename, NULL); + keyfile = get_session_keyfile_if_valid (path); + if (keyfile != NULL) + break; + } + + if (dirs) + g_ptr_array_free (dirs, TRUE); + if (basename) + g_free (basename); + if (path) + g_free (path); + + return keyfile; +} + +static GKeyFile * +get_session_keyfile (const char *session, + char **actual_session, + gboolean *is_fallback) +{ + GKeyFile *keyfile; + gboolean session_runnable; + char *value; + GError *error = NULL; + + *actual_session = NULL; + + g_debug ("fill: *** Getting session '%s'", session); + + keyfile = find_valid_session_keyfile (session); + + if (!keyfile) + return NULL; + + session_runnable = TRUE; + + value = g_key_file_get_string (keyfile, + GSM_KEYFILE_SESSION_GROUP, GSM_KEYFILE_RUNNABLE_KEY, + NULL); + if (!IS_STRING_EMPTY (value)) { + g_debug ("fill: *** Launching helper '%s' to know if session is runnable", value); + session_runnable = gsm_process_helper (value, GSM_RUNNABLE_HELPER_TIMEOUT, &error); + if (!session_runnable) { + g_warning ("Session '%s' runnable check failed: %s", session, + error->message); + g_clear_error (&error); + } + } + g_free (value); + + if (session_runnable) { + session_runnable = check_required (keyfile); + } + + if (session_runnable) { + *actual_session = g_strdup (session); + if (is_fallback) + *is_fallback = FALSE; + return keyfile; + } + + g_debug ("fill: *** Session is not runnable"); + + /* We can't run this session, so try to use the fallback */ + value = g_key_file_get_string (keyfile, + GSM_KEYFILE_SESSION_GROUP, GSM_KEYFILE_FALLBACK_KEY, + NULL); + + g_key_file_free (keyfile); + keyfile = NULL; + + if (!IS_STRING_EMPTY (value)) { + if (is_fallback) + *is_fallback = TRUE; + keyfile = get_session_keyfile (value, actual_session, NULL); + } + g_free (value); + + return keyfile; +} + +gboolean +gsm_session_fill (GsmManager *manager, + const char *session) +{ + GKeyFile *keyfile; + gboolean is_fallback; + char *actual_session; + + keyfile = get_session_keyfile (session, &actual_session, &is_fallback); + + if (!keyfile) + return FALSE; + + _gsm_manager_set_active_session (manager, actual_session, is_fallback); + + g_free (actual_session); + + load_standard_apps (manager, keyfile); + + g_key_file_free (keyfile); + + return TRUE; +} diff -Nru gnome-session-3.30.1/.pc/ubuntu/dbus_request_shutdown.patch/gnome-session/gsm-manager.c gnome-session-3.30.1/.pc/ubuntu/dbus_request_shutdown.patch/gnome-session/gsm-manager.c --- gnome-session-3.30.1/.pc/ubuntu/dbus_request_shutdown.patch/gnome-session/gsm-manager.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/dbus_request_shutdown.patch/gnome-session/gsm-manager.c 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,3755 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * Copyright (C) 2008 William Jon McCann + * + * 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, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gsm-manager.h" +#include "org.gnome.SessionManager.h" + +#ifdef ENABLE_SYSTEMD_JOURNAL +#include +#endif + +#include "gsm-store.h" +#include "gsm-inhibitor.h" +#include "gsm-presence.h" +#include "gsm-shell.h" + +#include "gsm-xsmp-server.h" +#include "gsm-xsmp-client.h" +#include "gsm-dbus-client.h" + +#include "gsm-autostart-app.h" + +#include "gsm-util.h" +#include "gsm-icon-names.h" +#include "gsm-system.h" +#include "gsm-session-save.h" +#include "gsm-shell-extensions.h" +#include "gsm-fail-whale.h" + +#define GSM_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_MANAGER, GsmManagerPrivate)) + +/* UUIDs for log messages */ +#define GSM_MANAGER_STARTUP_SUCCEEDED_MSGID "0ce153587afa4095832d233c17a88001" +#define GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID "10dd2dc188b54a5e98970f56499d1f73" + +#define GSM_MANAGER_DBUS_PATH "/org/gnome/SessionManager" +#define GSM_MANAGER_DBUS_NAME "org.gnome.SessionManager" +#define GSM_MANAGER_DBUS_IFACE "org.gnome.SessionManager" + +/* Probably about the longest amount of time someone could reasonably + * want to wait, at least for something happening more than once. + * We can get deployed on very slow media though like CDROM devices, + * often with complex stacking/compressing filesystems on top, which + * is not a recipie for speed. Particularly now that we throw up + * a fail whale if required components don't show up quickly enough, + * let's make this fairly long. + */ +#define GSM_MANAGER_PHASE_TIMEOUT 90 /* seconds */ + +#define GDM_FLEXISERVER_COMMAND "gdmflexiserver" +#define GDM_FLEXISERVER_ARGS "--startnew Standard" + +#define SESSION_SCHEMA "org.gnome.desktop.session" +#define KEY_IDLE_DELAY "idle-delay" +#define KEY_SESSION_NAME "session-name" + +#define GSM_MANAGER_SCHEMA "org.gnome.SessionManager" +#define KEY_AUTOSAVE "auto-save-session" +#define KEY_AUTOSAVE_ONE_SHOT "auto-save-session-one-shot" +#define KEY_LOGOUT_PROMPT "logout-prompt" +#define KEY_SHOW_FALLBACK_WARNING "show-fallback-warning" + +#define SCREENSAVER_SCHEMA "org.gnome.desktop.screensaver" +#define KEY_SLEEP_LOCK "lock-enabled" + +#define LOCKDOWN_SCHEMA "org.gnome.desktop.lockdown" +#define KEY_DISABLE_LOG_OUT "disable-log-out" +#define KEY_DISABLE_USER_SWITCHING "disable-user-switching" + +static void app_registered (GsmApp *app, GParamSpec *spec, GsmManager *manager); + +typedef enum +{ + GSM_MANAGER_LOGOUT_NONE, + GSM_MANAGER_LOGOUT_LOGOUT, + GSM_MANAGER_LOGOUT_REBOOT, + GSM_MANAGER_LOGOUT_REBOOT_INTERACT, + GSM_MANAGER_LOGOUT_SHUTDOWN, + GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT, +} GsmManagerLogoutType; + +struct GsmManagerPrivate +{ + gboolean failsafe; + GsmStore *clients; + GsmStore *inhibitors; + GsmInhibitorFlag inhibited_actions; + GsmStore *apps; + GsmPresence *presence; + GsmXsmpServer *xsmp_server; + + char *session_name; + gboolean is_fallback_session : 1; + + /* Current status */ + GsmManagerPhase phase; + guint phase_timeout_id; + GSList *required_apps; + GSList *pending_apps; + GsmManagerLogoutMode logout_mode; + GSList *query_clients; + guint query_timeout_id; + /* This is used for GSM_MANAGER_PHASE_END_SESSION only at the moment, + * since it uses a sublist of all running client that replied in a + * specific way */ + GSList *next_query_clients; + /* This is the action that will be done just before we exit */ + GsmManagerLogoutType logout_type; + + /* List of clients which were disconnected due to disabled condition + * and shouldn't be automatically restarted */ + GSList *condition_clients; + + GSList *pending_end_session_tasks; + GCancellable *end_session_cancellable; + + GSettings *settings; + GSettings *session_settings; + GSettings *screensaver_settings; + GSettings *lockdown_settings; + + GsmSystem *system; + GDBusConnection *connection; + GsmExportedManager *skeleton; + gboolean dbus_disconnected : 1; + + GsmShell *shell; + guint shell_end_session_dialog_canceled_id; + guint shell_end_session_dialog_open_failed_id; + guint shell_end_session_dialog_confirmed_logout_id; + guint shell_end_session_dialog_confirmed_shutdown_id; + guint shell_end_session_dialog_confirmed_reboot_id; +}; + +enum { + PROP_0, + PROP_CLIENT_STORE, + PROP_SESSION_NAME, + PROP_FALLBACK, + PROP_FAILSAFE +}; + +enum { + PHASE_CHANGED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0 }; + +static void gsm_manager_class_init (GsmManagerClass *klass); +static void gsm_manager_init (GsmManager *manager); + +static gboolean auto_save_is_enabled (GsmManager *manager); +static void maybe_save_session (GsmManager *manager); + +static gboolean _log_out_is_locked_down (GsmManager *manager); + +static void _handle_client_end_session_response (GsmManager *manager, + GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason); +static void show_shell_end_session_dialog (GsmManager *manager, + GsmShellEndSessionDialogType type); +static gpointer manager_object = NULL; + +G_DEFINE_TYPE (GsmManager, gsm_manager, G_TYPE_OBJECT) + +static const GDBusErrorEntry gsm_manager_error_entries[] = { + { GSM_MANAGER_ERROR_GENERAL, GSM_MANAGER_DBUS_IFACE ".GeneralError" }, + { GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, GSM_MANAGER_DBUS_IFACE ".NotInInitialization" }, + { GSM_MANAGER_ERROR_NOT_IN_RUNNING, GSM_MANAGER_DBUS_IFACE ".NotInRunning" }, + { GSM_MANAGER_ERROR_ALREADY_REGISTERED, GSM_MANAGER_DBUS_IFACE ".AlreadyRegistered" }, + { GSM_MANAGER_ERROR_NOT_REGISTERED, GSM_MANAGER_DBUS_IFACE ".NotRegistered" }, + { GSM_MANAGER_ERROR_INVALID_OPTION, GSM_MANAGER_DBUS_IFACE ".InvalidOption" }, + { GSM_MANAGER_ERROR_LOCKED_DOWN, GSM_MANAGER_DBUS_IFACE ".LockedDown" } +}; + +GQuark +gsm_manager_error_quark (void) +{ + static volatile gsize quark_volatile = 0; + + g_dbus_error_register_error_domain ("gsm_manager_error", + &quark_volatile, + gsm_manager_error_entries, + G_N_ELEMENTS (gsm_manager_error_entries)); + return quark_volatile; +} + +static gboolean +start_app_or_warn (GsmManager *manager, + GsmApp *app) +{ + gboolean res; + GError *error = NULL; + + g_debug ("GsmManager: starting app '%s'", gsm_app_peek_id (app)); + + res = gsm_app_start (app, &error); + if (error != NULL) { + g_warning ("Failed to start app: %s", error->message); + g_clear_error (&error); + } + return res; +} + +static gboolean +is_app_required (GsmManager *manager, + GsmApp *app) +{ + return g_slist_find (manager->priv->required_apps, app) != NULL; +} + +static void +on_required_app_failure (GsmManager *manager, + GsmApp *app) +{ + const gchar *app_id; + gboolean allow_logout; + GsmShellExtensions *extensions; + + app_id = gsm_app_peek_app_id (app); + + if (g_str_equal (app_id, "org.gnome.Shell")) { + extensions = g_object_new (GSM_TYPE_SHELL_EXTENSIONS, NULL); + gsm_shell_extensions_disable_all (extensions); + } else { + extensions = NULL; + } + + if (gsm_system_is_login_session (manager->priv->system)) { + allow_logout = FALSE; + } else { + allow_logout = !_log_out_is_locked_down (manager); + } + +#ifdef ENABLE_SYSTEMD_JOURNAL + sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID, + "PRIORITY=%d", 3, + "MESSAGE=Unrecoverable failure in required component %s", app_id, + NULL); +#endif + + gsm_fail_whale_dialog_we_failed (FALSE, + allow_logout, + extensions); +} + +static void +on_display_server_failure (GsmManager *manager, + GsmApp *app) +{ + const gchar *app_id; + GsmShellExtensions *extensions; + + app_id = gsm_app_peek_app_id (app); + + if (g_str_equal (app_id, "org.gnome.Shell")) { + extensions = g_object_new (GSM_TYPE_SHELL_EXTENSIONS, NULL); + gsm_shell_extensions_disable_all (extensions); + + g_object_unref (extensions); + } else { + extensions = NULL; + } + +#ifdef ENABLE_SYSTEMD_JOURNAL + sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID, + "PRIORITY=%d", 3, + "MESSAGE=Unrecoverable failure in required component %s", app_id, + NULL); +#endif + + gsm_quit (); +} + +static gboolean +_debug_client (const char *id, + GsmClient *client, + GsmManager *manager) +{ + g_debug ("GsmManager: Client %s", gsm_client_peek_id (client)); + return FALSE; +} + +static void +debug_clients (GsmManager *manager) +{ + gsm_store_foreach (manager->priv->clients, + (GsmStoreFunc)_debug_client, + manager); +} + +static gboolean +_find_by_cookie (const char *id, + GsmInhibitor *inhibitor, + guint *cookie_ap) +{ + guint cookie_b; + + cookie_b = gsm_inhibitor_peek_cookie (inhibitor); + + return (*cookie_ap == cookie_b); +} + +static gboolean +_client_has_startup_id (const char *id, + GsmClient *client, + const char *startup_id_a) +{ + const char *startup_id_b; + + startup_id_b = gsm_client_peek_startup_id (client); + if (IS_STRING_EMPTY (startup_id_b)) { + return FALSE; + } + + return (strcmp (startup_id_a, startup_id_b) == 0); +} + +static void +app_condition_changed (GsmApp *app, + gboolean condition, + GsmManager *manager) +{ + GsmClient *client; + + g_debug ("GsmManager: app:%s condition changed condition:%d", + gsm_app_peek_id (app), + condition); + + client = (GsmClient *)gsm_store_find (manager->priv->clients, + (GsmStoreFunc)_client_has_startup_id, + (char *)gsm_app_peek_startup_id (app)); + + if (condition) { + if (!gsm_app_is_running (app) && client == NULL) { + start_app_or_warn (manager, app); + } else { + g_debug ("GsmManager: not starting - app still running '%s'", gsm_app_peek_id (app)); + } + } else { + GError *error; + gboolean res; + + if (client != NULL) { + /* Kill client in case condition if false and make sure it won't + * be automatically restarted by adding the client to + * condition_clients */ + manager->priv->condition_clients = + g_slist_prepend (manager->priv->condition_clients, client); + + g_debug ("GsmManager: stopping client %s for app", gsm_client_peek_id (client)); + + error = NULL; + res = gsm_client_stop (client, &error); + if (! res) { + g_warning ("Not able to stop app client from its condition: %s", + error->message); + g_error_free (error); + } + } else { + g_debug ("GsmManager: stopping app %s", gsm_app_peek_id (app)); + + /* If we don't have a client then we should try to kill the app , + * if it is running */ + error = NULL; + res = gsm_app_stop (app, &error); + if (! res) { + g_warning ("Not able to stop app from its condition: %s", + error->message); + g_error_free (error); + } + } + } +} + +static const char * +phase_num_to_name (guint phase) +{ + const char *name; + + switch (phase) { + case GSM_MANAGER_PHASE_STARTUP: + name = "STARTUP"; + break; + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + name = "EARLY_INITIALIZATION"; + break; + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + name = "PRE_DISPLAY_SERVER"; + break; + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + name = "DISPLAY_SERVER"; + break; + case GSM_MANAGER_PHASE_INITIALIZATION: + name = "INITIALIZATION"; + break; + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + name = "WINDOW_MANAGER"; + break; + case GSM_MANAGER_PHASE_PANEL: + name = "PANEL"; + break; + case GSM_MANAGER_PHASE_DESKTOP: + name = "DESKTOP"; + break; + case GSM_MANAGER_PHASE_APPLICATION: + name = "APPLICATION"; + break; + case GSM_MANAGER_PHASE_RUNNING: + name = "RUNNING"; + break; + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + name = "QUERY_END_SESSION"; + break; + case GSM_MANAGER_PHASE_END_SESSION: + name = "END_SESSION"; + break; + case GSM_MANAGER_PHASE_EXIT: + name = "EXIT"; + break; + default: + g_assert_not_reached (); + break; + } + + return name; +} + +static void start_phase (GsmManager *manager); + +static void +gsm_manager_quit (GsmManager *manager) +{ + /* See the comment in request_reboot() for some more details about how + * this works. */ + + switch (manager->priv->logout_type) { + case GSM_MANAGER_LOGOUT_LOGOUT: + case GSM_MANAGER_LOGOUT_NONE: + gsm_quit (); + break; + case GSM_MANAGER_LOGOUT_REBOOT: + case GSM_MANAGER_LOGOUT_REBOOT_INTERACT: + gsm_system_complete_shutdown (manager->priv->system); + break; + case GSM_MANAGER_LOGOUT_SHUTDOWN: + case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT: + gsm_system_complete_shutdown (manager->priv->system); + break; + default: + g_assert_not_reached (); + break; + } +} + +static gboolean do_query_end_session_exit (GsmManager *manager); + +static void +end_phase (GsmManager *manager) +{ + gboolean start_next_phase = TRUE; + + g_debug ("GsmManager: ending phase %s", + phase_num_to_name (manager->priv->phase)); + + g_slist_free (manager->priv->pending_apps); + manager->priv->pending_apps = NULL; + + g_slist_free (manager->priv->query_clients); + manager->priv->query_clients = NULL; + + g_slist_free (manager->priv->next_query_clients); + manager->priv->next_query_clients = NULL; + + if (manager->priv->query_timeout_id > 0) { + g_source_remove (manager->priv->query_timeout_id); + manager->priv->query_timeout_id = 0; + } + if (manager->priv->phase_timeout_id > 0) { + g_source_remove (manager->priv->phase_timeout_id); + manager->priv->phase_timeout_id = 0; + } + + switch (manager->priv->phase) { + case GSM_MANAGER_PHASE_STARTUP: + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_INITIALIZATION: + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + case GSM_MANAGER_PHASE_PANEL: + case GSM_MANAGER_PHASE_DESKTOP: + case GSM_MANAGER_PHASE_APPLICATION: + break; + case GSM_MANAGER_PHASE_RUNNING: + if (_log_out_is_locked_down (manager)) { + g_warning ("Unable to logout: Logout has been locked down"); + start_next_phase = FALSE; + } + break; + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + if (!do_query_end_session_exit (manager)) + start_next_phase = FALSE; + break; + case GSM_MANAGER_PHASE_END_SESSION: + maybe_save_session (manager); + break; + case GSM_MANAGER_PHASE_EXIT: + start_next_phase = FALSE; + gsm_manager_quit (manager); + break; + default: + g_assert_not_reached (); + break; + } + + if (start_next_phase) { + manager->priv->phase++; + start_phase (manager); + } +} + +static void +app_event_during_startup (GsmManager *manager, + GsmApp *app) +{ + if (!(manager->priv->phase < GSM_MANAGER_PHASE_APPLICATION)) + return; + + manager->priv->pending_apps = g_slist_remove (manager->priv->pending_apps, app); + + if (manager->priv->pending_apps == NULL) { + if (manager->priv->phase_timeout_id > 0) { + g_source_remove (manager->priv->phase_timeout_id); + manager->priv->phase_timeout_id = 0; + } + + end_phase (manager); + } +} + +static gboolean +is_app_display_server (GsmManager *manager, + GsmApp *app) +{ + GsmManagerPhase phase; + + /* Apps can only really act as a display server if + * we're a wayland session. + */ + if (g_strcmp0 (g_getenv ("XDG_SESSION_TYPE"), "wayland") != 0) + return FALSE; + + phase = gsm_app_peek_phase (app); + + return (phase == GSM_MANAGER_PHASE_DISPLAY_SERVER && + is_app_required (manager, app)); +} + +static void +_restart_app (GsmManager *manager, + GsmApp *app) +{ + GError *error = NULL; + + if (is_app_display_server (manager, app)) { + on_display_server_failure (manager, app); + return; + } + + if (!gsm_app_restart (app, &error)) { + if (is_app_required (manager, app)) { + on_required_app_failure (manager, app); + } else { + g_warning ("Error on restarting session managed app: %s", error->message); + } + g_clear_error (&error); + + app_event_during_startup (manager, app); + } +} + +static void +app_died (GsmApp *app, + int signal, + GsmManager *manager) +{ + g_warning ("Application '%s' killed by signal %d", gsm_app_peek_app_id (app), signal); + + if (gsm_app_get_registered (app) && gsm_app_peek_autorestart (app)) { + g_debug ("Component '%s' is autorestart, ignoring died signal", + gsm_app_peek_app_id (app)); + return; + } + + _restart_app (manager, app); + + /* For now, we don't do anything with crashes from + * non-required apps after they hit the restart limit. + * + * Note that both required and not-required apps will be + * caught by ABRT/apport type infrastructure, and it'd be + * better to pick up the crash from there and do something + * un-intrusive about it generically. + */ +} + +static void +app_exited (GsmApp *app, + guchar exit_code, + GsmManager *manager) +{ + if (exit_code != 0) + g_warning ("App '%s' exited with code %d", gsm_app_peek_app_id (app), exit_code); + else + g_debug ("App %s exited successfully", gsm_app_peek_app_id (app)); + + /* Consider that non-success exit status means "crash" for required components */ + if (exit_code != 0 && is_app_required (manager, app)) { + if (gsm_app_get_registered (app) && gsm_app_peek_autorestart (app)) { + g_debug ("Component '%s' is autorestart, ignoring non-successful exit", + gsm_app_peek_app_id (app)); + return; + } + + _restart_app (manager, app); + } else { + app_event_during_startup (manager, app); + } +} + +static void +app_registered (GsmApp *app, + GParamSpec *spec, + GsmManager *manager) +{ + if (!gsm_app_get_registered (app)) { + return; + } + + g_debug ("App %s registered", gsm_app_peek_app_id (app)); + + app_event_during_startup (manager, app); +} + +static gboolean +on_phase_timeout (GsmManager *manager) +{ + GSList *a; + + manager->priv->phase_timeout_id = 0; + + switch (manager->priv->phase) { + case GSM_MANAGER_PHASE_STARTUP: + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_INITIALIZATION: + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + case GSM_MANAGER_PHASE_PANEL: + case GSM_MANAGER_PHASE_DESKTOP: + case GSM_MANAGER_PHASE_APPLICATION: + for (a = manager->priv->pending_apps; a; a = a->next) { + GsmApp *app = a->data; + g_warning ("Application '%s' failed to register before timeout", + gsm_app_peek_app_id (app)); + if (is_app_required (manager, app)) + on_required_app_failure (manager, app); + } + break; + case GSM_MANAGER_PHASE_RUNNING: + break; + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + case GSM_MANAGER_PHASE_END_SESSION: + break; + case GSM_MANAGER_PHASE_EXIT: + break; + default: + g_assert_not_reached (); + break; + } + + end_phase (manager); + + return FALSE; +} + +static gboolean +_autostart_delay_timeout (GsmApp *app) +{ + GError *error = NULL; + gboolean res; + + if (!gsm_app_peek_is_disabled (app) + && !gsm_app_peek_is_conditionally_disabled (app)) { + res = gsm_app_start (app, &error); + if (!res) { + if (error != NULL) { + g_warning ("Could not launch application '%s': %s", + gsm_app_peek_app_id (app), + error->message); + g_error_free (error); + } + } + } + + g_object_unref (app); + + return FALSE; +} + +static gboolean +_start_app (const char *id, + GsmApp *app, + GsmManager *manager) +{ + int delay; + + if (gsm_app_peek_phase (app) != manager->priv->phase) { + goto out; + } + + /* Keep track of app autostart condition in order to react + * accordingly in the future. */ + g_signal_connect (app, + "condition-changed", + G_CALLBACK (app_condition_changed), + manager); + + if (gsm_app_peek_is_disabled (app) + || gsm_app_peek_is_conditionally_disabled (app)) { + g_debug ("GsmManager: Skipping disabled app: %s", id); + goto out; + } + + /* Only accept an autostart delay for the application phase */ + if (manager->priv->phase == GSM_MANAGER_PHASE_APPLICATION) { + delay = gsm_app_peek_autostart_delay (app); + if (delay > 0) { + g_timeout_add_seconds (delay, + (GSourceFunc)_autostart_delay_timeout, + g_object_ref (app)); + g_debug ("GsmManager: %s is scheduled to start in %d seconds", id, delay); + goto out; + } + } + + if (!start_app_or_warn (manager, app)) + goto out; + + if (manager->priv->phase < GSM_MANAGER_PHASE_APPLICATION) { + /* Historical note - apparently, + * e.g. gnome-settings-daemon used to "daemonize", and + * so gnome-session assumes process exit means "ok + * we're done". Of course this is broken, we don't + * even distinguish between exit code 0 versus not-0, + * nor do we have any metadata which tells us a + * process is going to "daemonize" or not (and + * basically nothing should be anyways). + */ + g_signal_connect (app, + "exited", + G_CALLBACK (app_exited), + manager); + g_signal_connect (app, + "notify::registered", + G_CALLBACK (app_registered), + manager); + g_signal_connect (app, + "died", + G_CALLBACK (app_died), + manager); + manager->priv->pending_apps = g_slist_prepend (manager->priv->pending_apps, app); + } + out: + return FALSE; +} + +static void +do_phase_startup (GsmManager *manager) +{ + gsm_store_foreach (manager->priv->apps, + (GsmStoreFunc)_start_app, + manager); + + if (manager->priv->pending_apps != NULL) { + if (manager->priv->phase < GSM_MANAGER_PHASE_APPLICATION) { + manager->priv->phase_timeout_id = g_timeout_add_seconds (GSM_MANAGER_PHASE_TIMEOUT, + (GSourceFunc)on_phase_timeout, + manager); + } + } else { + end_phase (manager); + } +} + +typedef struct { + GsmManager *manager; + guint flags; +} ClientEndSessionData; + + +static gboolean +_client_end_session (GsmClient *client, + ClientEndSessionData *data) +{ + gboolean ret; + GError *error; + + error = NULL; + ret = gsm_client_end_session (client, data->flags, &error); + if (! ret) { + g_warning ("Unable to query client: %s", error->message); + g_error_free (error); + /* FIXME: what should we do if we can't communicate with client? */ + } else { + g_debug ("GsmManager: adding client to end-session clients: %s", gsm_client_peek_id (client)); + data->manager->priv->query_clients = g_slist_prepend (data->manager->priv->query_clients, + client); + } + + return FALSE; +} + +static gboolean +_client_end_session_helper (const char *id, + GsmClient *client, + ClientEndSessionData *data) +{ + return _client_end_session (client, data); +} + +static void +complete_end_session_tasks (GsmManager *manager) +{ + GSList *l; + + for (l = manager->priv->pending_end_session_tasks; + l != NULL; + l = l->next) { + GTask *task = G_TASK (l->data); + if (!g_task_return_error_if_cancelled (task)) + g_task_return_boolean (task, TRUE); + } + + g_slist_free_full (manager->priv->pending_end_session_tasks, + (GDestroyNotify) g_object_unref); + manager->priv->pending_end_session_tasks = NULL; +} + +static void +do_phase_end_session (GsmManager *manager) +{ + ClientEndSessionData data; + + complete_end_session_tasks (manager); + + data.manager = manager; + data.flags = 0; + + if (manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL; + } + if (auto_save_is_enabled (manager)) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_SAVE; + } + + if (manager->priv->phase_timeout_id > 0) { + g_source_remove (manager->priv->phase_timeout_id); + manager->priv->phase_timeout_id = 0; + } + + if (gsm_store_size (manager->priv->clients) > 0) { + manager->priv->phase_timeout_id = g_timeout_add_seconds (GSM_MANAGER_PHASE_TIMEOUT, + (GSourceFunc)on_phase_timeout, + manager); + + gsm_store_foreach (manager->priv->clients, + (GsmStoreFunc)_client_end_session_helper, + &data); + } else { + end_phase (manager); + } +} + +static void +do_phase_end_session_part_2 (GsmManager *manager) +{ + ClientEndSessionData data; + + data.manager = manager; + data.flags = 0; + + if (manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL; + } + if (auto_save_is_enabled (manager)) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_SAVE; + } + data.flags |= GSM_CLIENT_END_SESSION_FLAG_LAST; + + /* keep the timeout that was started at the beginning of the + * GSM_MANAGER_PHASE_END_SESSION phase */ + + if (g_slist_length (manager->priv->next_query_clients) > 0) { + g_slist_foreach (manager->priv->next_query_clients, + (GFunc)_client_end_session, + &data); + + g_slist_free (manager->priv->next_query_clients); + manager->priv->next_query_clients = NULL; + } else { + end_phase (manager); + } +} + +static gboolean +_client_stop (const char *id, + GsmClient *client, + gpointer user_data) +{ + gboolean ret; + GError *error; + + error = NULL; + ret = gsm_client_stop (client, &error); + if (! ret) { + g_warning ("Unable to stop client: %s", error->message); + g_error_free (error); + /* FIXME: what should we do if we can't communicate with client? */ + } else { + g_debug ("GsmManager: stopped client: %s", gsm_client_peek_id (client)); + } + + return FALSE; +} + +#ifdef HAVE_SYSTEMD +static void +maybe_restart_user_bus (GsmManager *manager) +{ + GsmSystem *system; + g_autoptr(GVariant) reply = NULL; + g_autoptr(GError) error = NULL; + + if (manager->priv->dbus_disconnected) + return; + + system = gsm_get_system (); + + if (!gsm_system_is_last_session_for_user (system)) + return; + + reply = g_dbus_connection_call_sync (manager->priv->connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "TryRestartUnit", + g_variant_new ("(ss)", "dbus.service", "replace"), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (error != NULL) { + g_debug ("GsmManager: reloading user bus failed: %s", error->message); + } +} +#endif + +static void +do_phase_exit (GsmManager *manager) +{ + if (gsm_store_size (manager->priv->clients) > 0) { + gsm_store_foreach (manager->priv->clients, + (GsmStoreFunc)_client_stop, + NULL); + } + +#ifdef HAVE_SYSTEMD + maybe_restart_user_bus (manager); +#endif + + end_phase (manager); +} + +static gboolean +_client_query_end_session (const char *id, + GsmClient *client, + ClientEndSessionData *data) +{ + gboolean ret; + GError *error; + + error = NULL; + ret = gsm_client_query_end_session (client, data->flags, &error); + if (! ret) { + g_warning ("Unable to query client: %s", error->message); + g_error_free (error); + /* FIXME: what should we do if we can't communicate with client? */ + } else { + g_debug ("GsmManager: adding client to query clients: %s", gsm_client_peek_id (client)); + data->manager->priv->query_clients = g_slist_prepend (data->manager->priv->query_clients, + client); + } + + return FALSE; +} + +static gboolean +inhibitor_has_flag (gpointer key, + GsmInhibitor *inhibitor, + gpointer data) +{ + guint flag; + guint flags; + + flag = GPOINTER_TO_UINT (data); + + flags = gsm_inhibitor_peek_flags (inhibitor); + + return (flags & flag); +} + +static gboolean +gsm_manager_is_logout_inhibited (GsmManager *manager) +{ + GsmInhibitor *inhibitor; + + if (manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + return FALSE; + } + + if (manager->priv->inhibitors == NULL) { + return FALSE; + } + + inhibitor = (GsmInhibitor *)gsm_store_find (manager->priv->inhibitors, + (GsmStoreFunc)inhibitor_has_flag, + GUINT_TO_POINTER (GSM_INHIBITOR_FLAG_LOGOUT)); + if (inhibitor == NULL) { + return FALSE; + } + return TRUE; +} + +static gboolean +gsm_manager_is_idle_inhibited (GsmManager *manager) +{ + GsmInhibitor *inhibitor; + + if (manager->priv->inhibitors == NULL) { + return FALSE; + } + + inhibitor = (GsmInhibitor *)gsm_store_find (manager->priv->inhibitors, + (GsmStoreFunc)inhibitor_has_flag, + GUINT_TO_POINTER (GSM_INHIBITOR_FLAG_IDLE)); + if (inhibitor == NULL) { + return FALSE; + } + return TRUE; +} + +static gboolean +_client_cancel_end_session (const char *id, + GsmClient *client, + GsmManager *manager) +{ + gboolean res; + GError *error; + + error = NULL; + res = gsm_client_cancel_end_session (client, &error); + if (! res) { + g_warning ("Unable to cancel end session: %s", error->message); + g_error_free (error); + } + + return FALSE; +} + +static gboolean +inhibitor_is_jit (gpointer key, + GsmInhibitor *inhibitor, + GsmManager *manager) +{ + gboolean matches; + const char *id; + + id = gsm_inhibitor_peek_client_id (inhibitor); + + matches = (id != NULL && id[0] != '\0'); + + return matches; +} + +static void +cancel_end_session (GsmManager *manager) +{ + /* just ignore if received outside of shutdown */ + if (manager->priv->phase < GSM_MANAGER_PHASE_QUERY_END_SESSION) { + return; + } + + /* switch back to running phase */ + g_debug ("GsmManager: Cancelling the end of session"); + + g_cancellable_cancel (manager->priv->end_session_cancellable); + + gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_RUNNING); + manager->priv->logout_mode = GSM_MANAGER_LOGOUT_MODE_NORMAL; + + manager->priv->logout_type = GSM_MANAGER_LOGOUT_NONE; + + /* clear all JIT inhibitors */ + gsm_store_foreach_remove (manager->priv->inhibitors, + (GsmStoreFunc)inhibitor_is_jit, + (gpointer)manager); + + gsm_store_foreach (manager->priv->clients, + (GsmStoreFunc)_client_cancel_end_session, + NULL); + + start_phase (manager); +} + +static void +end_session_or_show_shell_dialog (GsmManager *manager) +{ + gboolean logout_prompt; + GsmShellEndSessionDialogType type; + gboolean logout_inhibited; + + switch (manager->priv->logout_type) { + case GSM_MANAGER_LOGOUT_LOGOUT: + type = GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT; + break; + case GSM_MANAGER_LOGOUT_REBOOT: + case GSM_MANAGER_LOGOUT_REBOOT_INTERACT: + type = GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART; + break; + case GSM_MANAGER_LOGOUT_SHUTDOWN: + case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT: + type = GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN; + break; + default: + g_warning ("Unexpected logout type %d when creating end session dialog", + manager->priv->logout_type); + type = GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT; + break; + } + + logout_inhibited = gsm_manager_is_logout_inhibited (manager); + logout_prompt = g_settings_get_boolean (manager->priv->settings, + KEY_LOGOUT_PROMPT); + + switch (manager->priv->logout_mode) { + case GSM_MANAGER_LOGOUT_MODE_NORMAL: + if (logout_inhibited || logout_prompt) { + show_shell_end_session_dialog (manager, type); + } else { + end_phase (manager); + } + break; + + case GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION: + if (logout_inhibited) { + show_shell_end_session_dialog (manager, type); + } else { + end_phase (manager); + } + break; + + case GSM_MANAGER_LOGOUT_MODE_FORCE: + end_phase (manager); + break; + default: + g_assert_not_reached (); + break; + } + +} + +static void +query_end_session_complete (GsmManager *manager) +{ + + g_debug ("GsmManager: query end session complete"); + + /* Remove the timeout since this can be called from outside the timer + * and we don't want to have it called twice */ + if (manager->priv->query_timeout_id > 0) { + g_source_remove (manager->priv->query_timeout_id); + manager->priv->query_timeout_id = 0; + } + + end_session_or_show_shell_dialog (manager); +} + +static guint32 +generate_cookie (void) +{ + guint32 cookie; + + cookie = (guint32)g_random_int_range (1, G_MAXINT32); + + return cookie; +} + +static guint32 +_generate_unique_cookie (GsmManager *manager) +{ + guint32 cookie; + + do { + cookie = generate_cookie (); + } while (gsm_store_find (manager->priv->inhibitors, (GsmStoreFunc)_find_by_cookie, &cookie) != NULL); + + return cookie; +} + +static gboolean +_on_query_end_session_timeout (GsmManager *manager) +{ + GSList *l; + + manager->priv->query_timeout_id = 0; + + g_debug ("GsmManager: query end session timed out"); + + for (l = manager->priv->query_clients; l != NULL; l = l->next) { + guint cookie; + GsmInhibitor *inhibitor; + const char *bus_name; + char *app_id; + + g_warning ("Client '%s' failed to reply before timeout", + gsm_client_peek_id (l->data)); + + /* Don't add "not responding" inhibitors if logout is forced + */ + if (manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + continue; + } + + /* Add JIT inhibit for unresponsive client */ + if (GSM_IS_DBUS_CLIENT (l->data)) { + bus_name = gsm_dbus_client_get_bus_name (l->data); + } else { + bus_name = NULL; + } + + app_id = g_strdup (gsm_client_peek_app_id (l->data)); + if (IS_STRING_EMPTY (app_id)) { + /* XSMP clients don't give us an app id unless we start them */ + g_free (app_id); + app_id = gsm_client_get_app_name (l->data); + } + + cookie = _generate_unique_cookie (manager); + inhibitor = gsm_inhibitor_new_for_client (gsm_client_peek_id (l->data), + app_id, + GSM_INHIBITOR_FLAG_LOGOUT, + _("Not responding"), + bus_name, + cookie); + g_free (app_id); + gsm_store_add (manager->priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); + g_object_unref (inhibitor); + } + + g_slist_free (manager->priv->query_clients); + manager->priv->query_clients = NULL; + + query_end_session_complete (manager); + + return FALSE; +} + +static void +do_phase_query_end_session (GsmManager *manager) +{ + ClientEndSessionData data; + + data.manager = manager; + data.flags = 0; + + if (manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL; + } + /* We only query if an app is ready to log out, so we don't use + * GSM_CLIENT_END_SESSION_FLAG_SAVE here. + */ + + debug_clients (manager); + g_debug ("GsmManager: sending query-end-session to clients (logout mode: %s)", + manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_NORMAL? "normal" : + manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE? "forceful": + "no confirmation"); + gsm_store_foreach (manager->priv->clients, + (GsmStoreFunc)_client_query_end_session, + &data); + + /* This phase doesn't time out unless logout is forced. Typically, this + * separate timer is only used to show UI. */ + manager->priv->query_timeout_id = g_timeout_add_seconds (1, (GSourceFunc)_on_query_end_session_timeout, manager); +} + +static void +update_idle (GsmManager *manager) +{ + if (gsm_manager_is_idle_inhibited (manager)) { + gsm_presence_set_idle_enabled (manager->priv->presence, FALSE); + } else { + gsm_presence_set_idle_enabled (manager->priv->presence, TRUE); + } +} + +static void +start_phase (GsmManager *manager) +{ + + g_debug ("GsmManager: starting phase %s\n", + phase_num_to_name (manager->priv->phase)); + + /* reset state */ + g_slist_free (manager->priv->pending_apps); + manager->priv->pending_apps = NULL; + g_slist_free (manager->priv->query_clients); + manager->priv->query_clients = NULL; + g_slist_free (manager->priv->next_query_clients); + manager->priv->next_query_clients = NULL; + + if (manager->priv->query_timeout_id > 0) { + g_source_remove (manager->priv->query_timeout_id); + manager->priv->query_timeout_id = 0; + } + if (manager->priv->phase_timeout_id > 0) { + g_source_remove (manager->priv->phase_timeout_id); + manager->priv->phase_timeout_id = 0; + } + + switch (manager->priv->phase) { + case GSM_MANAGER_PHASE_STARTUP: + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_INITIALIZATION: + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + case GSM_MANAGER_PHASE_PANEL: + case GSM_MANAGER_PHASE_DESKTOP: + case GSM_MANAGER_PHASE_APPLICATION: + do_phase_startup (manager); + break; + case GSM_MANAGER_PHASE_RUNNING: +#ifdef ENABLE_SYSTEMD_JOURNAL + sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_STARTUP_SUCCEEDED_MSGID, + "PRIORITY=%d", 5, + "MESSAGE=Entering running state", + NULL); +#endif + gsm_xsmp_server_start_accepting_new_clients (manager->priv->xsmp_server); + if (manager->priv->pending_end_session_tasks != NULL) + complete_end_session_tasks (manager); + g_object_unref (manager->priv->end_session_cancellable); + manager->priv->end_session_cancellable = g_cancellable_new (); + gsm_exported_manager_emit_session_running (manager->priv->skeleton); + update_idle (manager); + break; + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + gsm_xsmp_server_stop_accepting_new_clients (manager->priv->xsmp_server); + do_phase_query_end_session (manager); + break; + case GSM_MANAGER_PHASE_END_SESSION: + do_phase_end_session (manager); + break; + case GSM_MANAGER_PHASE_EXIT: + do_phase_exit (manager); + break; + default: + g_assert_not_reached (); + break; + } +} + +static gboolean +_debug_app_for_phase (const char *id, + GsmApp *app, + gpointer data) +{ + guint phase; + + phase = GPOINTER_TO_UINT (data); + + if (gsm_app_peek_phase (app) != phase) { + return FALSE; + } + + g_debug ("GsmManager:\tID: %s\tapp-id:%s\tis-disabled:%d\tis-conditionally-disabled:%d\tis-delayed:%d", + gsm_app_peek_id (app), + gsm_app_peek_app_id (app), + gsm_app_peek_is_disabled (app), + gsm_app_peek_is_conditionally_disabled (app), + (gsm_app_peek_autostart_delay (app) > 0)); + + return FALSE; +} + +static void +debug_app_summary (GsmManager *manager) +{ + guint phase; + + g_debug ("GsmManager: App startup summary"); + for (phase = GSM_MANAGER_PHASE_EARLY_INITIALIZATION; phase < GSM_MANAGER_PHASE_RUNNING; phase++) { + g_debug ("GsmManager: Phase %s", phase_num_to_name (phase)); + gsm_store_foreach (manager->priv->apps, + (GsmStoreFunc)_debug_app_for_phase, + GUINT_TO_POINTER (phase)); + } +} + +void +gsm_manager_start (GsmManager *manager) +{ + g_debug ("GsmManager: GSM starting to manage"); + + g_return_if_fail (GSM_IS_MANAGER (manager)); + + gsm_xsmp_server_start (manager->priv->xsmp_server); + gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_EARLY_INITIALIZATION); + debug_app_summary (manager); + start_phase (manager); +} + +const char * +_gsm_manager_get_default_session (GsmManager *manager) +{ + return g_settings_get_string (manager->priv->session_settings, + KEY_SESSION_NAME); +} + +void +_gsm_manager_set_active_session (GsmManager *manager, + const char *session_name, + gboolean is_fallback) +{ + g_free (manager->priv->session_name); + manager->priv->session_name = g_strdup (session_name); + manager->priv->is_fallback_session = is_fallback; + + gsm_exported_manager_set_session_name (manager->priv->skeleton, session_name); +} + +void +_gsm_manager_set_renderer (GsmManager *manager, + const char *renderer) +{ + gsm_exported_manager_set_renderer (manager->priv->skeleton, renderer); +} + +static gboolean +_app_has_app_id (const char *id, + GsmApp *app, + const char *app_id_a) +{ + const char *app_id_b; + + app_id_b = gsm_app_peek_app_id (app); + return (app_id_b != NULL && strcmp (app_id_a, app_id_b) == 0); +} + +static GsmApp * +find_app_for_app_id (GsmManager *manager, + const char *app_id) +{ + GsmApp *app; + app = (GsmApp *)gsm_store_find (manager->priv->apps, + (GsmStoreFunc)_app_has_app_id, + (char *)app_id); + return app; +} + +static gboolean +inhibitor_has_client_id (gpointer key, + GsmInhibitor *inhibitor, + const char *client_id_a) +{ + gboolean matches; + const char *client_id_b; + + client_id_b = gsm_inhibitor_peek_client_id (inhibitor); + + matches = FALSE; + if (! IS_STRING_EMPTY (client_id_a) && ! IS_STRING_EMPTY (client_id_b)) { + matches = (strcmp (client_id_a, client_id_b) == 0); + if (matches) { + g_debug ("GsmManager: removing JIT inhibitor for %s for reason '%s'", + gsm_inhibitor_peek_client_id (inhibitor), + gsm_inhibitor_peek_reason (inhibitor)); + } + } + + return matches; +} + +static gboolean +_app_has_startup_id (const char *id, + GsmApp *app, + const char *startup_id_a) +{ + const char *startup_id_b; + + startup_id_b = gsm_app_peek_startup_id (app); + + if (IS_STRING_EMPTY (startup_id_b)) { + return FALSE; + } + + return (strcmp (startup_id_a, startup_id_b) == 0); +} + +static GsmApp * +find_app_for_startup_id (GsmManager *manager, + const char *startup_id) +{ + GsmApp *found_app; + GSList *a; + + found_app = NULL; + + /* If we're starting up the session, try to match the new client + * with one pending apps for the current phase. If not, try to match + * with any of the autostarted apps. */ + if (manager->priv->phase < GSM_MANAGER_PHASE_APPLICATION) { + for (a = manager->priv->pending_apps; a != NULL; a = a->next) { + GsmApp *app = GSM_APP (a->data); + + if (strcmp (startup_id, gsm_app_peek_startup_id (app)) == 0) { + found_app = app; + goto out; + } + } + } else { + GsmApp *app; + + app = (GsmApp *)gsm_store_find (manager->priv->apps, + (GsmStoreFunc)_app_has_startup_id, + (char *)startup_id); + if (app != NULL) { + found_app = app; + goto out; + } + } + out: + return found_app; +} + +static void +_disconnect_client (GsmManager *manager, + GsmClient *client) +{ + gboolean is_condition_client; + GsmApp *app; + const char *app_id; + const char *startup_id; + gboolean app_restart; + GsmClientRestartStyle client_restart_hint; + + g_debug ("GsmManager: disconnect client: %s", gsm_client_peek_id (client)); + + /* take a ref so it doesn't get finalized */ + g_object_ref (client); + + gsm_client_set_status (client, GSM_CLIENT_FINISHED); + + is_condition_client = FALSE; + if (g_slist_find (manager->priv->condition_clients, client)) { + manager->priv->condition_clients = g_slist_remove (manager->priv->condition_clients, client); + + is_condition_client = TRUE; + } + + /* remove any inhibitors for this client */ + gsm_store_foreach_remove (manager->priv->inhibitors, + (GsmStoreFunc)inhibitor_has_client_id, + (gpointer)gsm_client_peek_id (client)); + + app = NULL; + + /* first try to match on startup ID */ + startup_id = gsm_client_peek_startup_id (client); + if (! IS_STRING_EMPTY (startup_id)) { + app = find_app_for_startup_id (manager, startup_id); + + } + + /* then try to find matching app-id */ + if (app == NULL) { + app_id = gsm_client_peek_app_id (client); + if (! IS_STRING_EMPTY (app_id)) { + g_debug ("GsmManager: disconnect for app '%s'", app_id); + app = find_app_for_app_id (manager, app_id); + } + } + + if (manager->priv->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION) { + /* Instead of answering our end session query, the client just exited. + * Treat that as an "okay, end the session" answer. + * + * This call implicitly removes any inhibitors for the client, along + * with removing the client from the pending query list. + */ + _handle_client_end_session_response (manager, + client, + TRUE, + FALSE, + FALSE, + "Client exited in " + "query end session phase " + "instead of end session " + "phase"); + } + + if (manager->priv->dbus_disconnected && GSM_IS_DBUS_CLIENT (client)) { + g_debug ("GsmManager: dbus disconnected, not restarting application"); + goto out; + } + + if (app == NULL) { + g_debug ("GsmManager: unable to find application for client - not restarting"); + goto out; + } + + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + g_debug ("GsmManager: in shutdown, not restarting application"); + goto out; + } + + app_restart = gsm_app_peek_autorestart (app); + client_restart_hint = gsm_client_peek_restart_style_hint (client); + + /* allow legacy clients to override the app info */ + if (! app_restart + && client_restart_hint != GSM_CLIENT_RESTART_IMMEDIATELY) { + g_debug ("GsmManager: autorestart not set, not restarting application"); + goto out; + } + + if (is_condition_client) { + g_debug ("GsmManager: app conditionally disabled, not restarting application"); + goto out; + } + + g_debug ("GsmManager: restarting app"); + + _restart_app (manager, app); + + out: + g_object_unref (client); +} + +typedef struct { + const char *service_name; + GsmManager *manager; +} RemoveClientData; + +static gboolean +_disconnect_dbus_client (const char *id, + GsmClient *client, + RemoveClientData *data) +{ + const char *name; + + if (! GSM_IS_DBUS_CLIENT (client)) { + return FALSE; + } + + /* If no service name, then we simply disconnect all clients */ + if (!data->service_name) { + _disconnect_client (data->manager, client); + return TRUE; + } + + name = gsm_dbus_client_get_bus_name (GSM_DBUS_CLIENT (client)); + if (IS_STRING_EMPTY (name)) { + return FALSE; + } + + if (strcmp (data->service_name, name) == 0) { + _disconnect_client (data->manager, client); + return TRUE; + } + + return FALSE; +} + +/** + * remove_clients_for_connection: + * @manager: a #GsmManager + * @service_name: a service name + * + * Disconnects clients that own @service_name. + * + * If @service_name is NULL, then disconnects all clients for the connection. + */ +static void +remove_clients_for_connection (GsmManager *manager, + const char *service_name) +{ + RemoveClientData data; + + data.service_name = service_name; + data.manager = manager; + + /* disconnect dbus clients for name */ + gsm_store_foreach_remove (manager->priv->clients, + (GsmStoreFunc)_disconnect_dbus_client, + &data); + + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION + && gsm_store_size (manager->priv->clients) == 0) { + g_debug ("GsmManager: last client disconnected - exiting"); + end_phase (manager); + } +} + +static void +gsm_manager_set_failsafe (GsmManager *manager, + gboolean enabled) +{ + g_return_if_fail (GSM_IS_MANAGER (manager)); + + manager->priv->failsafe = enabled; +} + +gboolean +gsm_manager_get_failsafe (GsmManager *manager) +{ + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + + return manager->priv->failsafe; +} + +static void +on_client_disconnected (GsmClient *client, + GsmManager *manager) +{ + g_debug ("GsmManager: disconnect client"); + _disconnect_client (manager, client); + gsm_store_remove (manager->priv->clients, gsm_client_peek_id (client)); + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION + && gsm_store_size (manager->priv->clients) == 0) { + g_debug ("GsmManager: last client disconnected - exiting"); + end_phase (manager); + } +} + +static gboolean +on_xsmp_client_register_request (GsmXSMPClient *client, + char **id, + GsmManager *manager) +{ + gboolean handled; + char *new_id; + GsmApp *app; + + handled = TRUE; + new_id = NULL; + + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + goto out; + } + + if (IS_STRING_EMPTY (*id)) { + new_id = gsm_util_generate_startup_id (); + } else { + GsmClient *client; + + client = (GsmClient *)gsm_store_find (manager->priv->clients, + (GsmStoreFunc)_client_has_startup_id, + *id); + /* We can't have two clients with the same id. */ + if (client != NULL) { + goto out; + } + + new_id = g_strdup (*id); + } + + g_debug ("GsmManager: Adding new client %s to session", new_id); + + g_signal_connect (client, + "disconnected", + G_CALLBACK (on_client_disconnected), + manager); + + /* If it's a brand new client id, we just accept the client*/ + if (IS_STRING_EMPTY (*id)) { + goto out; + } + + app = find_app_for_startup_id (manager, new_id); + if (app != NULL) { + gsm_client_set_app_id (GSM_CLIENT (client), gsm_app_peek_app_id (app)); + goto out; + } + + /* app not found */ + g_free (new_id); + new_id = NULL; + + out: + g_free (*id); + *id = new_id; + + return handled; +} + +static void +on_xsmp_client_register_confirmed (GsmXSMPClient *client, + const gchar *id, + GsmManager *manager) +{ + GsmApp *app; + + app = find_app_for_startup_id (manager, id); + + if (app != NULL) { + gsm_app_set_registered (app, TRUE); + } +} + +static gboolean +auto_save_is_enabled (GsmManager *manager) +{ + return g_settings_get_boolean (manager->priv->settings, KEY_AUTOSAVE_ONE_SHOT) + || g_settings_get_boolean (manager->priv->settings, KEY_AUTOSAVE); +} + +static void +maybe_save_session (GsmManager *manager) +{ + GError *error; + + if (gsm_system_is_login_session (manager->priv->system)) + return; + + /* We only allow session saving when session is running or when + * logging out */ + if (manager->priv->phase != GSM_MANAGER_PHASE_RUNNING && + manager->priv->phase != GSM_MANAGER_PHASE_END_SESSION) { + return; + } + + if (!auto_save_is_enabled (manager)) { + gsm_session_save_clear (); + return; + } + + error = NULL; + gsm_session_save (manager->priv->clients, manager->priv->apps, &error); + + if (error) { + g_warning ("Error saving session: %s", error->message); + g_error_free (error); + } +} + +static void +_handle_client_end_session_response (GsmManager *manager, + GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason) +{ + /* just ignore if received outside of shutdown */ + if (manager->priv->phase < GSM_MANAGER_PHASE_QUERY_END_SESSION) { + return; + } + + g_debug ("GsmManager: Response from end session request: is-ok=%d do-last=%d cancel=%d reason=%s", is_ok, do_last, cancel, reason ? reason :""); + + if (cancel) { + cancel_end_session (manager); + return; + } + + manager->priv->query_clients = g_slist_remove (manager->priv->query_clients, client); + + if (! is_ok && manager->priv->logout_mode != GSM_MANAGER_LOGOUT_MODE_FORCE) { + guint cookie; + GsmInhibitor *inhibitor; + char *app_id; + const char *bus_name; + + /* FIXME: do we support updating the reason? */ + + /* Create JIT inhibit */ + if (GSM_IS_DBUS_CLIENT (client)) { + bus_name = gsm_dbus_client_get_bus_name (GSM_DBUS_CLIENT (client)); + } else { + bus_name = NULL; + } + + app_id = g_strdup (gsm_client_peek_app_id (client)); + if (IS_STRING_EMPTY (app_id)) { + /* XSMP clients don't give us an app id unless we start them */ + g_free (app_id); + app_id = gsm_client_get_app_name (client); + } + + cookie = _generate_unique_cookie (manager); + inhibitor = gsm_inhibitor_new_for_client (gsm_client_peek_id (client), + app_id, + GSM_INHIBITOR_FLAG_LOGOUT, + reason != NULL ? reason : _("Not responding"), + bus_name, + cookie); + g_free (app_id); + gsm_store_add (manager->priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); + g_object_unref (inhibitor); + } else { + gsm_store_foreach_remove (manager->priv->inhibitors, + (GsmStoreFunc)inhibitor_has_client_id, + (gpointer)gsm_client_peek_id (client)); + } + + if (manager->priv->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION) { + if (manager->priv->query_clients == NULL) { + query_end_session_complete (manager); + } + } else if (manager->priv->phase == GSM_MANAGER_PHASE_END_SESSION) { + if (do_last) { + /* This only makes sense if we're in part 1 of + * GSM_MANAGER_PHASE_END_SESSION. Doing this in part 2 + * can only happen because of a buggy client that loops + * wanting to be last again and again. The phase + * timeout will take care of this issue. */ + manager->priv->next_query_clients = g_slist_prepend (manager->priv->next_query_clients, + client); + } + + /* we can continue to the next step if all clients have replied + * and if there's no inhibitor */ + if (manager->priv->query_clients != NULL + || gsm_manager_is_logout_inhibited (manager)) { + return; + } + + if (manager->priv->next_query_clients != NULL) { + do_phase_end_session_part_2 (manager); + } else { + end_phase (manager); + } + } +} + +static void +on_client_end_session_response (GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason, + GsmManager *manager) +{ + _handle_client_end_session_response (manager, + client, + is_ok, + do_last, + cancel, + reason); +} + +static void +on_xsmp_client_logout_request (GsmXSMPClient *client, + gboolean show_dialog, + GsmManager *manager) +{ + GError *error; + int logout_mode; + + if (show_dialog) { + logout_mode = GSM_MANAGER_LOGOUT_MODE_NORMAL; + } else { + logout_mode = GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION; + } + + error = NULL; + gsm_manager_logout (manager, logout_mode, &error); + if (error != NULL) { + g_warning ("Unable to logout: %s", error->message); + g_error_free (error); + } +} + +static void +on_store_client_added (GsmStore *store, + const char *id, + GsmManager *manager) +{ + GsmClient *client; + + g_debug ("GsmManager: Client added: %s", id); + + client = (GsmClient *)gsm_store_lookup (store, id); + + /* a bit hacky */ + if (GSM_IS_XSMP_CLIENT (client)) { + g_signal_connect (client, + "register-request", + G_CALLBACK (on_xsmp_client_register_request), + manager); + g_signal_connect (client, + "register-confirmed", + G_CALLBACK (on_xsmp_client_register_confirmed), + manager); + g_signal_connect (client, + "logout-request", + G_CALLBACK (on_xsmp_client_logout_request), + manager); + } + + g_signal_connect (client, + "end-session-response", + G_CALLBACK (on_client_end_session_response), + manager); + + gsm_exported_manager_emit_client_added (manager->priv->skeleton, id); + /* FIXME: disconnect signal handler */ +} + +static void +on_store_client_removed (GsmStore *store, + const char *id, + GsmManager *manager) +{ + g_debug ("GsmManager: Client removed: %s", id); + + gsm_exported_manager_emit_client_removed (manager->priv->skeleton, id); +} + +static void +gsm_manager_set_client_store (GsmManager *manager, + GsmStore *store) +{ + g_return_if_fail (GSM_IS_MANAGER (manager)); + + if (store != NULL) { + g_object_ref (store); + } + + if (manager->priv->clients != NULL) { + g_signal_handlers_disconnect_by_func (manager->priv->clients, + on_store_client_added, + manager); + g_signal_handlers_disconnect_by_func (manager->priv->clients, + on_store_client_removed, + manager); + + g_object_unref (manager->priv->clients); + } + + + g_debug ("GsmManager: setting client store %p", store); + + manager->priv->clients = store; + + if (manager->priv->clients != NULL) { + if (manager->priv->xsmp_server) + g_object_unref (manager->priv->xsmp_server); + + manager->priv->xsmp_server = gsm_xsmp_server_new (store); + + g_signal_connect (manager->priv->clients, + "added", + G_CALLBACK (on_store_client_added), + manager); + g_signal_connect (manager->priv->clients, + "removed", + G_CALLBACK (on_store_client_removed), + manager); + } +} + +static void +gsm_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmManager *self; + + self = GSM_MANAGER (object); + + switch (prop_id) { + case PROP_FAILSAFE: + gsm_manager_set_failsafe (self, g_value_get_boolean (value)); + break; + case PROP_FALLBACK: + self->priv->is_fallback_session = g_value_get_boolean (value); + break; + case PROP_CLIENT_STORE: + gsm_manager_set_client_store (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmManager *self; + + self = GSM_MANAGER (object); + + switch (prop_id) { + case PROP_FAILSAFE: + g_value_set_boolean (value, self->priv->failsafe); + break; + case PROP_SESSION_NAME: + g_value_set_string (value, self->priv->session_name); + break; + case PROP_FALLBACK: + g_value_set_boolean (value, self->priv->is_fallback_session); + break; + case PROP_CLIENT_STORE: + g_value_set_object (value, self->priv->clients); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +_find_app_provides (const char *id, + GsmApp *app, + const char *service) +{ + return gsm_app_provides (app, service); +} + +static GObject * +gsm_manager_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GsmManager *manager; + + manager = GSM_MANAGER (G_OBJECT_CLASS (gsm_manager_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + return G_OBJECT (manager); +} + +static void +update_inhibited_actions (GsmManager *manager, + GsmInhibitorFlag new_inhibited_actions) +{ + if (manager->priv->inhibited_actions == new_inhibited_actions) + return; + + manager->priv->inhibited_actions = new_inhibited_actions; + gsm_exported_manager_set_inhibited_actions (manager->priv->skeleton, + manager->priv->inhibited_actions); +} + +static void +on_inhibitor_vanished (GsmInhibitor *inhibitor, + GsmManager *manager) +{ + gsm_store_remove (manager->priv->inhibitors, gsm_inhibitor_peek_id (inhibitor)); +} + +static void +on_store_inhibitor_added (GsmStore *store, + const char *id, + GsmManager *manager) +{ + GsmInhibitor *i; + GsmInhibitorFlag new_inhibited_actions; + + g_debug ("GsmManager: Inhibitor added: %s", id); + + i = GSM_INHIBITOR (gsm_store_lookup (store, id)); + gsm_system_add_inhibitor (manager->priv->system, id, + gsm_inhibitor_peek_flags (i)); + + new_inhibited_actions = manager->priv->inhibited_actions | gsm_inhibitor_peek_flags (i); + update_inhibited_actions (manager, new_inhibited_actions); + + g_signal_connect_object (i, "vanished", G_CALLBACK (on_inhibitor_vanished), manager, 0); + + gsm_exported_manager_emit_inhibitor_added (manager->priv->skeleton, id); + + update_idle (manager); +} + +static gboolean +collect_inhibition_flags (const char *id, + GObject *object, + gpointer user_data) +{ + GsmInhibitorFlag *new_inhibited_actions = user_data; + + *new_inhibited_actions |= gsm_inhibitor_peek_flags (GSM_INHIBITOR (object)); + + return FALSE; +} + +static void +on_store_inhibitor_removed (GsmStore *store, + const char *id, + GsmManager *manager) +{ + GsmInhibitorFlag new_inhibited_actions; + + g_debug ("GsmManager: Inhibitor removed: %s", id); + + gsm_system_remove_inhibitor (manager->priv->system, id); + + new_inhibited_actions = 0; + gsm_store_foreach (manager->priv->inhibitors, + collect_inhibition_flags, + &new_inhibited_actions); + update_inhibited_actions (manager, new_inhibited_actions); + + gsm_exported_manager_emit_inhibitor_removed (manager->priv->skeleton, id); + + update_idle (manager); + + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + end_session_or_show_shell_dialog (manager); + } +} + +static void +gsm_manager_dispose (GObject *object) +{ + GsmManager *manager = GSM_MANAGER (object); + + g_debug ("GsmManager: disposing manager"); + + g_clear_object (&manager->priv->end_session_cancellable); + g_clear_object (&manager->priv->xsmp_server); + + if (manager->priv->clients != NULL) { + g_signal_handlers_disconnect_by_func (manager->priv->clients, + on_store_client_added, + manager); + g_signal_handlers_disconnect_by_func (manager->priv->clients, + on_store_client_removed, + manager); + g_object_unref (manager->priv->clients); + manager->priv->clients = NULL; + } + + g_clear_object (&manager->priv->apps); + g_slist_free (manager->priv->required_apps); + manager->priv->required_apps = NULL; + + if (manager->priv->inhibitors != NULL) { + g_signal_handlers_disconnect_by_func (manager->priv->inhibitors, + on_store_inhibitor_added, + manager); + g_signal_handlers_disconnect_by_func (manager->priv->inhibitors, + on_store_inhibitor_removed, + manager); + + g_object_unref (manager->priv->inhibitors); + manager->priv->inhibitors = NULL; + } + + g_clear_object (&manager->priv->presence); + g_clear_object (&manager->priv->settings); + g_clear_object (&manager->priv->session_settings); + g_clear_object (&manager->priv->screensaver_settings); + g_clear_object (&manager->priv->lockdown_settings); + g_clear_object (&manager->priv->system); + g_clear_object (&manager->priv->shell); + + if (manager->priv->skeleton != NULL) { + g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (manager->priv->skeleton), + manager->priv->connection); + g_clear_object (&manager->priv->skeleton); + } + + g_clear_object (&manager->priv->connection); + + G_OBJECT_CLASS (gsm_manager_parent_class)->dispose (object); +} + +static void +gsm_manager_class_init (GsmManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gsm_manager_get_property; + object_class->set_property = gsm_manager_set_property; + object_class->constructor = gsm_manager_constructor; + object_class->dispose = gsm_manager_dispose; + + signals [PHASE_CHANGED] = + g_signal_new ("phase-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmManagerClass, phase_changed), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_STRING); + + g_object_class_install_property (object_class, + PROP_FAILSAFE, + g_param_spec_boolean ("failsafe", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + /** + * GsmManager::session-name + * + * Then name of the currently active session, typically "gnome" or "gnome-fallback". + * This may be the name of the configured default session, or the name of a fallback + * session in case we fell back. + */ + g_object_class_install_property (object_class, + PROP_SESSION_NAME, + g_param_spec_string ("session-name", + NULL, + NULL, + NULL, + G_PARAM_READABLE)); + + /** + * GsmManager::fallback + * + * If %TRUE, the current session is running in the "fallback" mode; + * this is distinct from whether or not it was configured as default. + */ + g_object_class_install_property (object_class, + PROP_FALLBACK, + g_param_spec_boolean ("fallback", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_CLIENT_STORE, + g_param_spec_object ("client-store", + NULL, + NULL, + GSM_TYPE_STORE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GsmManagerPrivate)); +} + +static void +on_presence_status_changed (GsmPresence *presence, + guint status, + GsmManager *manager) +{ + GsmSystem *system; + + system = gsm_get_system (); + gsm_system_set_session_idle (system, + (status == GSM_PRESENCE_STATUS_IDLE)); + g_object_unref (system); +} + +static void +on_gsm_system_active_changed (GsmSystem *system, + GParamSpec *pspec, + GsmManager *self) +{ + gboolean is_active; + + is_active = gsm_system_is_active (self->priv->system); + + g_debug ("emitting SessionIsActive"); + gsm_exported_manager_set_session_is_active (self->priv->skeleton, is_active); +} + +static gboolean +_log_out_is_locked_down (GsmManager *manager) +{ + return g_settings_get_boolean (manager->priv->lockdown_settings, + KEY_DISABLE_LOG_OUT); +} + +static void +complete_end_session_task (GsmManager *manager, + GAsyncResult *result, + GDBusMethodInvocation *invocation) +{ + GError *error = NULL; + + if (!g_task_propagate_boolean (G_TASK (result), &error)) + g_dbus_method_invocation_take_error (invocation, error); + else + g_dbus_method_invocation_return_value (invocation, NULL); +} + +static void +request_reboot (GsmManager *manager) +{ + g_debug ("GsmManager: requesting reboot"); + + /* FIXME: We need to support a more structured shutdown here, + * but that's blocking on an improved ConsoleKit api. + * + * See https://bugzilla.gnome.org/show_bug.cgi?id=585614 + */ + manager->priv->logout_type = GSM_MANAGER_LOGOUT_REBOOT_INTERACT; + end_phase (manager); +} + +static void +request_shutdown (GsmManager *manager) +{ + g_debug ("GsmManager: requesting shutdown"); + + /* See the comment in request_reboot() for some more details about + * what work needs to be done here. */ + manager->priv->logout_type = GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT; + end_phase (manager); +} + +static void +request_logout (GsmManager *manager, + GsmManagerLogoutMode mode) +{ + g_debug ("GsmManager: requesting logout"); + + manager->priv->logout_mode = mode; + manager->priv->logout_type = GSM_MANAGER_LOGOUT_LOGOUT; + + end_phase (manager); +} + +static gboolean +gsm_manager_shutdown (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GTask *task; + + g_debug ("GsmManager: Shutdown called"); + + if (manager->priv->phase < GSM_MANAGER_PHASE_RUNNING) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "Shutdown interface is only available after the Running phase starts"); + return TRUE; + } + + if (_log_out_is_locked_down (manager)) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_LOCKED_DOWN, + "Logout has been locked down"); + return TRUE; + } + + task = g_task_new (manager, manager->priv->end_session_cancellable, (GAsyncReadyCallback) complete_end_session_task, invocation); + + manager->priv->pending_end_session_tasks = g_slist_prepend (manager->priv->pending_end_session_tasks, + task); + + request_shutdown (manager); + + return TRUE; +} + +static gboolean +gsm_manager_reboot (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GTask *task; + + g_debug ("GsmManager: Reboot called"); + + if (manager->priv->phase < GSM_MANAGER_PHASE_RUNNING) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "Reboot interface is only available after the Running phase starts"); + return TRUE; + } + + if (_log_out_is_locked_down (manager)) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_LOCKED_DOWN, + "Logout has been locked down"); + return TRUE; + } + + task = g_task_new (manager, manager->priv->end_session_cancellable, (GAsyncReadyCallback) complete_end_session_task, invocation); + + manager->priv->pending_end_session_tasks = g_slist_prepend (manager->priv->pending_end_session_tasks, + task); + + request_reboot (manager); + + return TRUE; +} + +static gboolean +gsm_manager_can_shutdown (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + gboolean shutdown_available; + + g_debug ("GsmManager: CanShutdown called"); + + shutdown_available = !_log_out_is_locked_down (manager) && + (gsm_system_can_stop (manager->priv->system) + || gsm_system_can_restart (manager->priv->system) + || gsm_system_can_suspend (manager->priv->system) + || gsm_system_can_hibernate (manager->priv->system)); + + gsm_exported_manager_complete_can_shutdown (skeleton, invocation, shutdown_available); + + return TRUE; +} + +static gboolean +gsm_manager_can_reboot_to_firmware_setup (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + gboolean reboot_to_firmware_available; + + g_debug ("GsmManager: CanRebootToFirmwareSetup called"); + + reboot_to_firmware_available = !_log_out_is_locked_down (manager) && + gsm_system_can_restart_to_firmware_setup (manager->priv->system); + + gsm_exported_manager_complete_can_reboot_to_firmware_setup (skeleton, invocation, reboot_to_firmware_available); + + return TRUE; +} + +static gboolean +gsm_manager_set_reboot_to_firmware_setup (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + gboolean enable, + GsmManager *manager) +{ + g_debug ("GsmManager: SetRebootToFirmwareSetup called"); + + gsm_system_set_restart_to_firmware_setup (manager->priv->system, enable); + + gsm_exported_manager_complete_set_reboot_to_firmware_setup (skeleton, invocation); + + return TRUE; +} + +static gboolean +gsm_manager_setenv (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *variable, + const char *value, + GsmManager *manager) +{ + if (manager->priv->phase > GSM_MANAGER_PHASE_INITIALIZATION) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, + "Setenv interface is only available during the DisplayServer and Initialization phase"); + } else { + gsm_util_setenv (variable, value); + gsm_exported_manager_complete_setenv (skeleton, invocation); + } + + return TRUE; +} + +static gboolean +is_valid_category (int category) +{ + int categories[] = { + LC_CTYPE, + LC_NUMERIC, + LC_TIME, + LC_COLLATE, + LC_MONETARY, + LC_MESSAGES, +#if defined (LC_PAPER) + LC_PAPER, +#endif +#if defined (LC_NAME) + LC_NAME, +#endif +#if defined (LC_ADDRESS) + LC_ADDRESS, +#endif +#if defined (LC_TELEPHONE) + LC_TELEPHONE, +#endif +#if defined (LC_MEASUREMENT) + LC_MEASUREMENT, +#endif +#if defined (LC_IDENTIFICATION) + LC_IDENTIFICATION, +#endif + LC_ALL + }; + guint i; + + for (i = 0; i < G_N_ELEMENTS(categories); i++) + if (categories[i] == category) + return TRUE; + + return FALSE; +} + +static gboolean +gsm_manager_get_locale (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + int category, + GsmManager *manager) +{ + if (!is_valid_category (category)) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_INVALID_OPTION, + "GetLocale doesn't support locale category '%d'", category); + } else { + const char *value; + value = setlocale (category, NULL); + if (value == NULL) + value = ""; + + gsm_exported_manager_complete_get_locale (skeleton, invocation, value); + } + + return TRUE; +} + +static gboolean +gsm_manager_initialization_error (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *message, + gboolean fatal, + GsmManager *manager) +{ + if (manager->priv->phase != GSM_MANAGER_PHASE_INITIALIZATION) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, + "InitializationError interface is only available during the Initialization phase"); + return TRUE; + } + + gsm_util_init_error (fatal, "%s", message); + gsm_exported_manager_complete_initialization_error (skeleton, invocation); + + return TRUE; +} + +static void +user_logout (GsmManager *manager, + GsmManagerLogoutMode mode) +{ + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + manager->priv->logout_mode = mode; + end_session_or_show_shell_dialog (manager); + return; + } + + request_logout (manager, mode); +} + +gboolean +gsm_manager_logout (GsmManager *manager, + guint logout_mode, + GError **error) +{ + if (manager->priv->phase < GSM_MANAGER_PHASE_RUNNING) { + g_set_error (error, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "Logout interface is only available after the Running phase starts"); + return FALSE; + } + + if (_log_out_is_locked_down (manager)) { + g_set_error (error, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_LOCKED_DOWN, + "Logout has been locked down"); + return FALSE; + } + + switch (logout_mode) { + case GSM_MANAGER_LOGOUT_MODE_NORMAL: + case GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION: + case GSM_MANAGER_LOGOUT_MODE_FORCE: + user_logout (manager, logout_mode); + break; + + default: + g_debug ("Unknown logout mode option"); + + g_set_error (error, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_INVALID_OPTION, + "Unknown logout mode flag"); + return FALSE; + } + + return TRUE; +} + +static gboolean +gsm_manager_logout_dbus (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + guint logout_mode, + GsmManager *manager) +{ + GError *error = NULL; + + g_debug ("GsmManager: Logout called"); + + if (!gsm_manager_logout (manager, logout_mode, &error)) { + g_dbus_method_invocation_take_error (invocation, error); + } else { + gsm_exported_manager_complete_logout (skeleton, invocation); + } + + return TRUE; +} + +static gboolean +gsm_manager_register_client (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *app_id, + const char *startup_id, + GsmManager *manager) +{ + char *new_startup_id; + const char *sender; + GsmClient *client; + GsmApp *app; + + app = NULL; + client = NULL; + + g_debug ("GsmManager: RegisterClient %s", startup_id); + + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + g_debug ("Unable to register client: shutting down"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "Unable to register client"); + return TRUE; + } + + if (IS_STRING_EMPTY (startup_id)) { + new_startup_id = gsm_util_generate_startup_id (); + } else { + + client = (GsmClient *)gsm_store_find (manager->priv->clients, + (GsmStoreFunc)_client_has_startup_id, + (char *)startup_id); + /* We can't have two clients with the same startup id. */ + if (client != NULL) { + g_debug ("Unable to register client: already registered"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_ALREADY_REGISTERED, + "Unable to register client"); + return TRUE; + } + + new_startup_id = g_strdup (startup_id); + } + + g_debug ("GsmManager: Adding new client %s to session", new_startup_id); + + if (app == NULL && !IS_STRING_EMPTY (startup_id)) { + app = find_app_for_startup_id (manager, startup_id); + } + if (app == NULL && !IS_STRING_EMPTY (app_id)) { + /* try to associate this app id with a known app */ + app = find_app_for_app_id (manager, app_id); + } + + sender = g_dbus_method_invocation_get_sender (invocation); + client = gsm_dbus_client_new (new_startup_id, sender); + if (client == NULL) { + g_debug ("Unable to create client"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Unable to register client"); + return TRUE; + } + + gsm_store_add (manager->priv->clients, gsm_client_peek_id (client), G_OBJECT (client)); + /* the store will own the ref */ + g_object_unref (client); + + g_signal_connect (client, + "disconnected", + G_CALLBACK (on_client_disconnected), + manager); + + if (app != NULL) { + gsm_client_set_app_id (client, gsm_app_peek_app_id (app)); + gsm_app_set_registered (app, TRUE); + } else { + /* if an app id is specified store it in the client + so we can save it later */ + gsm_client_set_app_id (client, app_id); + } + + gsm_client_set_status (client, GSM_CLIENT_REGISTERED); + + g_assert (new_startup_id != NULL); + g_free (new_startup_id); + + gsm_exported_manager_complete_register_client (skeleton, invocation, gsm_client_peek_id (client)); + + return TRUE; +} + +static gboolean +gsm_manager_unregister_client (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *client_id, + GsmManager *manager) +{ + GsmClient *client; + + g_debug ("GsmManager: UnregisterClient %s", client_id); + + client = (GsmClient *)gsm_store_lookup (manager->priv->clients, client_id); + if (client == NULL) { + g_debug ("Unable to unregister client: not registered"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_REGISTERED, + "Unable to unregister client"); + return TRUE; + } + + /* don't disconnect client here, only change the status. + Wait until it leaves the bus before disconnecting it */ + gsm_client_set_status (client, GSM_CLIENT_UNREGISTERED); + + gsm_exported_manager_complete_unregister_client (skeleton, invocation); + + return TRUE; +} + +static gboolean +gsm_manager_inhibit (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *app_id, + guint toplevel_xid, + const char *reason, + guint flags, + GsmManager *manager) +{ + GsmInhibitor *inhibitor; + guint cookie; + + g_debug ("GsmManager: Inhibit xid=%u app_id=%s reason=%s flags=%u", + toplevel_xid, + app_id, + reason, + flags); + + if (manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Forced logout cannot be inhibited"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return TRUE; + } + + if (IS_STRING_EMPTY (app_id)) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Application ID not specified"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return TRUE; + } + + if (IS_STRING_EMPTY (reason)) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Reason not specified"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return FALSE; + } + + if (flags == 0) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Invalid inhibit flags"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return FALSE; + } + + cookie = _generate_unique_cookie (manager); + inhibitor = gsm_inhibitor_new (app_id, + toplevel_xid, + flags, + reason, + g_dbus_method_invocation_get_sender (invocation), + cookie); + gsm_store_add (manager->priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); + g_object_unref (inhibitor); + + gsm_exported_manager_complete_inhibit (skeleton, invocation, cookie); + + return TRUE; +} + +static gboolean +gsm_manager_uninhibit (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + guint cookie, + GsmManager *manager) +{ + GsmInhibitor *inhibitor; + + g_debug ("GsmManager: Uninhibit %u", cookie); + + inhibitor = (GsmInhibitor *)gsm_store_find (manager->priv->inhibitors, + (GsmStoreFunc)_find_by_cookie, + &cookie); + if (inhibitor == NULL) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Unable to uninhibit: Invalid cookie"); + g_debug ("Unable to uninhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return TRUE; + } + + g_debug ("GsmManager: removing inhibitor %s %u reason '%s' %u connection %s", + gsm_inhibitor_peek_app_id (inhibitor), + gsm_inhibitor_peek_toplevel_xid (inhibitor), + gsm_inhibitor_peek_reason (inhibitor), + gsm_inhibitor_peek_flags (inhibitor), + gsm_inhibitor_peek_bus_name (inhibitor)); + + gsm_store_remove (manager->priv->inhibitors, gsm_inhibitor_peek_id (inhibitor)); + + gsm_exported_manager_complete_uninhibit (skeleton, invocation); + + return TRUE; +} + +static gboolean +gsm_manager_is_inhibited (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + guint flags, + GsmManager *manager) +{ + GsmInhibitor *inhibitor; + gboolean is_inhibited; + + if (manager->priv->inhibitors == NULL + || gsm_store_size (manager->priv->inhibitors) == 0) { + is_inhibited = FALSE; + } else { + inhibitor = (GsmInhibitor *) gsm_store_find (manager->priv->inhibitors, + (GsmStoreFunc)inhibitor_has_flag, + GUINT_TO_POINTER (flags)); + if (inhibitor == NULL) { + is_inhibited = FALSE; + } else { + is_inhibited = TRUE; + } + } + + gsm_exported_manager_complete_is_inhibited (skeleton, invocation, is_inhibited); + + return TRUE; +} + +static gboolean +listify_store_ids (char *id, + GObject *object, + GPtrArray **array) +{ + g_ptr_array_add (*array, g_strdup (id)); + return FALSE; +} + +static gboolean +gsm_manager_get_clients (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GPtrArray *clients; + + clients = g_ptr_array_new_with_free_func (g_free); + gsm_store_foreach (manager->priv->clients, + (GsmStoreFunc) listify_store_ids, + &clients); + g_ptr_array_add (clients, NULL); + + gsm_exported_manager_complete_get_clients (skeleton, invocation, + (const gchar * const *) clients->pdata); + g_ptr_array_unref (clients); + + return TRUE; +} + +static gboolean +gsm_manager_get_inhibitors (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GPtrArray *inhibitors; + + inhibitors = g_ptr_array_new_with_free_func (g_free); + gsm_store_foreach (manager->priv->inhibitors, + (GsmStoreFunc) listify_store_ids, + &inhibitors); + g_ptr_array_add (inhibitors, NULL); + + gsm_exported_manager_complete_get_inhibitors (skeleton, invocation, + (const gchar * const *) inhibitors->pdata); + g_ptr_array_unref (inhibitors); + + return TRUE; +} + +static gboolean +_app_has_autostart_condition (const char *id, + GsmApp *app, + const char *condition) +{ + gboolean has; + gboolean disabled; + + has = gsm_app_has_autostart_condition (app, condition); + disabled = gsm_app_peek_is_disabled (app); + + return has && !disabled; +} + +static gboolean +gsm_manager_is_autostart_condition_handled (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *condition, + GsmManager *manager) +{ + GsmApp *app; + gboolean handled; + + app = (GsmApp *) gsm_store_find (manager->priv->apps,( + GsmStoreFunc) _app_has_autostart_condition, + (char *)condition); + + if (app != NULL) { + handled = TRUE; + } else { + handled = FALSE; + } + + gsm_exported_manager_complete_is_autostart_condition_handled (skeleton, invocation, handled); + + return TRUE; +} + +static gboolean +gsm_manager_is_session_running (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + gsm_exported_manager_complete_is_session_running (skeleton, invocation, + manager->priv->phase == GSM_MANAGER_PHASE_RUNNING); + return TRUE; +} + +static void +on_session_connection_closed (GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error, + gpointer user_data) +{ + GsmManager *manager; + + manager = GSM_MANAGER (user_data); + + g_debug ("GsmManager: dbus disconnected; disconnecting dbus clients..."); + manager->priv->dbus_disconnected = TRUE; + remove_clients_for_connection (manager, NULL); +} + +static gboolean +register_manager (GsmManager *manager) +{ + GDBusConnection *connection; + GsmExportedManager *skeleton; + GError *error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + + if (error != NULL) { + g_critical ("error getting session bus: %s", error->message); + g_error_free (error); + + exit (1); + } + + skeleton = gsm_exported_manager_skeleton_new (); + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), + connection, + GSM_MANAGER_DBUS_PATH, &error); + + if (error != NULL) { + g_critical ("error exporting manager on session bus: %s", error->message); + g_error_free (error); + + exit (1); + } + + g_signal_connect (skeleton, "handle-can-reboot-to-firmware-setup", + G_CALLBACK (gsm_manager_can_reboot_to_firmware_setup), manager); + g_signal_connect (skeleton, "handle-can-shutdown", + G_CALLBACK (gsm_manager_can_shutdown), manager); + g_signal_connect (skeleton, "handle-get-clients", + G_CALLBACK (gsm_manager_get_clients), manager); + g_signal_connect (skeleton, "handle-get-inhibitors", + G_CALLBACK (gsm_manager_get_inhibitors), manager); + g_signal_connect (skeleton, "handle-get-locale", + G_CALLBACK (gsm_manager_get_locale), manager); + g_signal_connect (skeleton, "handle-inhibit", + G_CALLBACK (gsm_manager_inhibit), manager); + g_signal_connect (skeleton, "handle-initialization-error", + G_CALLBACK (gsm_manager_initialization_error), manager); + g_signal_connect (skeleton, "handle-is-autostart-condition-handled", + G_CALLBACK (gsm_manager_is_autostart_condition_handled), manager); + g_signal_connect (skeleton, "handle-is-inhibited", + G_CALLBACK (gsm_manager_is_inhibited), manager); + g_signal_connect (skeleton, "handle-is-session-running", + G_CALLBACK (gsm_manager_is_session_running), manager); + g_signal_connect (skeleton, "handle-logout", + G_CALLBACK (gsm_manager_logout_dbus), manager); + g_signal_connect (skeleton, "handle-reboot", + G_CALLBACK (gsm_manager_reboot), manager); + g_signal_connect (skeleton, "handle-register-client", + G_CALLBACK (gsm_manager_register_client), manager); + g_signal_connect (skeleton, "handle-set-reboot-to-firmware-setup", + G_CALLBACK (gsm_manager_set_reboot_to_firmware_setup), manager); + g_signal_connect (skeleton, "handle-setenv", + G_CALLBACK (gsm_manager_setenv), manager); + g_signal_connect (skeleton, "handle-shutdown", + G_CALLBACK (gsm_manager_shutdown), manager); + g_signal_connect (skeleton, "handle-uninhibit", + G_CALLBACK (gsm_manager_uninhibit), manager); + g_signal_connect (skeleton, "handle-unregister-client", + G_CALLBACK (gsm_manager_unregister_client), manager); + + manager->priv->dbus_disconnected = FALSE; + g_signal_connect (connection, "closed", + G_CALLBACK (on_session_connection_closed), manager); + + manager->priv->connection = connection; + manager->priv->skeleton = skeleton; + + g_signal_connect (manager->priv->system, "notify::active", + G_CALLBACK (on_gsm_system_active_changed), manager); + + /* cold-plug SessionIsActive */ + on_gsm_system_active_changed (manager->priv->system, NULL, manager); + + return TRUE; +} + +static gboolean +idle_timeout_get_mapping (GValue *value, + GVariant *variant, + gpointer user_data) +{ + guint32 idle_timeout; + + idle_timeout = g_variant_get_uint32 (variant); + g_value_set_uint (value, idle_timeout * 1000); + + return TRUE; +} + +static void +gsm_manager_init (GsmManager *manager) +{ + + manager->priv = GSM_MANAGER_GET_PRIVATE (manager); + + manager->priv->settings = g_settings_new (GSM_MANAGER_SCHEMA); + manager->priv->session_settings = g_settings_new (SESSION_SCHEMA); + manager->priv->screensaver_settings = g_settings_new (SCREENSAVER_SCHEMA); + manager->priv->lockdown_settings = g_settings_new (LOCKDOWN_SCHEMA); + + manager->priv->inhibitors = gsm_store_new (); + g_signal_connect (manager->priv->inhibitors, + "added", + G_CALLBACK (on_store_inhibitor_added), + manager); + g_signal_connect (manager->priv->inhibitors, + "removed", + G_CALLBACK (on_store_inhibitor_removed), + manager); + + manager->priv->apps = gsm_store_new (); + + manager->priv->presence = gsm_presence_new (); + g_signal_connect (manager->priv->presence, + "status-changed", + G_CALLBACK (on_presence_status_changed), + manager); + + g_settings_bind_with_mapping (manager->priv->session_settings, + KEY_IDLE_DELAY, + manager->priv->presence, + "idle-timeout", + G_SETTINGS_BIND_GET, + idle_timeout_get_mapping, + NULL, + NULL, NULL); + + manager->priv->system = gsm_get_system (); + manager->priv->shell = gsm_get_shell (); + manager->priv->end_session_cancellable = g_cancellable_new (); +} + +GsmManager * +gsm_manager_get (void) +{ + return manager_object; +} + +GsmManager * +gsm_manager_new (GsmStore *client_store, + gboolean failsafe) +{ + if (manager_object != NULL) { + g_object_ref (manager_object); + } else { + gboolean res; + + manager_object = g_object_new (GSM_TYPE_MANAGER, + "client-store", client_store, + "failsafe", failsafe, + NULL); + + g_object_add_weak_pointer (manager_object, + (gpointer *) &manager_object); + res = register_manager (manager_object); + if (! res) { + g_object_unref (manager_object); + return NULL; + } + } + + return GSM_MANAGER (manager_object); +} + +static void +disconnect_shell_dialog_signals (GsmManager *manager) +{ + if (manager->priv->shell_end_session_dialog_canceled_id != 0) { + g_signal_handler_disconnect (manager->priv->shell, + manager->priv->shell_end_session_dialog_canceled_id); + manager->priv->shell_end_session_dialog_canceled_id = 0; + } + + if (manager->priv->shell_end_session_dialog_confirmed_logout_id != 0) { + g_signal_handler_disconnect (manager->priv->shell, + manager->priv->shell_end_session_dialog_confirmed_logout_id); + manager->priv->shell_end_session_dialog_confirmed_logout_id = 0; + } + + if (manager->priv->shell_end_session_dialog_confirmed_shutdown_id != 0) { + g_signal_handler_disconnect (manager->priv->shell, + manager->priv->shell_end_session_dialog_confirmed_shutdown_id); + manager->priv->shell_end_session_dialog_confirmed_shutdown_id = 0; + } + + if (manager->priv->shell_end_session_dialog_confirmed_reboot_id != 0) { + g_signal_handler_disconnect (manager->priv->shell, + manager->priv->shell_end_session_dialog_confirmed_reboot_id); + manager->priv->shell_end_session_dialog_confirmed_reboot_id = 0; + } + + if (manager->priv->shell_end_session_dialog_open_failed_id != 0) { + g_signal_handler_disconnect (manager->priv->shell, + manager->priv->shell_end_session_dialog_open_failed_id); + manager->priv->shell_end_session_dialog_open_failed_id = 0; + } +} + +static void +on_shell_end_session_dialog_canceled (GsmShell *shell, + GsmManager *manager) +{ + cancel_end_session (manager); + disconnect_shell_dialog_signals (manager); +} + +static void +_handle_end_session_dialog_response (GsmManager *manager, + GsmManagerLogoutType logout_type) +{ + /* Note we're checking for END_SESSION here and + * QUERY_END_SESSION in the fallback cases elsewhere. + * + * That's because they run at different times in the logout + * process. The shell combines the inhibit and + * confirmation dialogs, so it gets displayed after we've collected + * inhibitors. The fallback code has two distinct dialogs, once of + * which we can (and do show) before collecting the inhibitors. + */ + if (manager->priv->phase >= GSM_MANAGER_PHASE_END_SESSION) { + /* Already shutting down, nothing more to do */ + return; + } + + manager->priv->logout_mode = GSM_MANAGER_LOGOUT_MODE_FORCE; + manager->priv->logout_type = logout_type; + end_phase (manager); +} + +static void +on_shell_end_session_dialog_confirmed_logout (GsmShell *shell, + GsmManager *manager) +{ + _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_LOGOUT); + disconnect_shell_dialog_signals (manager); +} + +static void +on_shell_end_session_dialog_confirmed_shutdown (GsmShell *shell, + GsmManager *manager) +{ + _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_SHUTDOWN); + disconnect_shell_dialog_signals (manager); +} + +static void +on_shell_end_session_dialog_confirmed_reboot (GsmShell *shell, + GsmManager *manager) +{ + _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_REBOOT); + disconnect_shell_dialog_signals (manager); +} + +static void +connect_shell_dialog_signals (GsmManager *manager) +{ + if (manager->priv->shell_end_session_dialog_canceled_id != 0) + return; + + manager->priv->shell_end_session_dialog_canceled_id = + g_signal_connect (manager->priv->shell, + "end-session-dialog-canceled", + G_CALLBACK (on_shell_end_session_dialog_canceled), + manager); + + manager->priv->shell_end_session_dialog_open_failed_id = + g_signal_connect (manager->priv->shell, + "end-session-dialog-open-failed", + G_CALLBACK (on_shell_end_session_dialog_canceled), + manager); + + manager->priv->shell_end_session_dialog_confirmed_logout_id = + g_signal_connect (manager->priv->shell, + "end-session-dialog-confirmed-logout", + G_CALLBACK (on_shell_end_session_dialog_confirmed_logout), + manager); + + manager->priv->shell_end_session_dialog_confirmed_shutdown_id = + g_signal_connect (manager->priv->shell, + "end-session-dialog-confirmed-shutdown", + G_CALLBACK (on_shell_end_session_dialog_confirmed_shutdown), + manager); + + manager->priv->shell_end_session_dialog_confirmed_reboot_id = + g_signal_connect (manager->priv->shell, + "end-session-dialog-confirmed-reboot", + G_CALLBACK (on_shell_end_session_dialog_confirmed_reboot), + manager); +} + +static void +show_shell_end_session_dialog (GsmManager *manager, + GsmShellEndSessionDialogType type) +{ + if (!gsm_shell_is_running (manager->priv->shell)) + return; + + gsm_shell_open_end_session_dialog (manager->priv->shell, + type, + manager->priv->inhibitors); + connect_shell_dialog_signals (manager); +} + +/* + dbus-send --session --type=method_call --print-reply + --dest=org.gnome.SessionManager + /org/gnome/SessionManager + org.freedesktop.DBus.Introspectable.Introspect +*/ + +gboolean +gsm_manager_set_phase (GsmManager *manager, + GsmManagerPhase phase) +{ + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + manager->priv->phase = phase; + return (TRUE); +} + +static void +append_app (GsmManager *manager, + GsmApp *app, + const char *provides, + gboolean is_required) +{ + const char *id; + const char *app_id; + GsmApp *dup; + + id = gsm_app_peek_id (app); + if (IS_STRING_EMPTY (id)) { + g_debug ("GsmManager: not adding app: no id"); + return; + } + + dup = (GsmApp *)gsm_store_lookup (manager->priv->apps, id); + if (dup != NULL) { + g_debug ("GsmManager: not adding app: already added"); + return; + } + + app_id = gsm_app_peek_app_id (app); + if (IS_STRING_EMPTY (app_id)) { + g_debug ("GsmManager: not adding app: no app-id"); + return; + } + + dup = find_app_for_app_id (manager, app_id); + if (dup != NULL) { + g_debug ("GsmManager: not adding app: app-id '%s' already exists", app_id); + + if (provides && GSM_IS_AUTOSTART_APP (dup)) + gsm_autostart_app_add_provides (GSM_AUTOSTART_APP (dup), provides); + + if (is_required && + !g_slist_find (manager->priv->required_apps, dup)) { + g_debug ("GsmManager: making app '%s' required", gsm_app_peek_app_id (dup)); + manager->priv->required_apps = g_slist_prepend (manager->priv->required_apps, dup); + } + + return; + } + + gsm_store_add (manager->priv->apps, id, G_OBJECT (app)); + if (is_required) { + g_debug ("GsmManager: adding required app %s", gsm_app_peek_app_id (app)); + manager->priv->required_apps = g_slist_prepend (manager->priv->required_apps, app); + } +} + +static gboolean +add_autostart_app_internal (GsmManager *manager, + const char *path, + const char *provides, + gboolean is_required) +{ + GsmApp *app; + char **internal_provides; + GError *error = NULL; + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + g_return_val_if_fail (path != NULL, FALSE); + + /* Note: if we cannot add the app because its service is already + * provided, because its app-id is taken, or because of any other + * reason meaning there is already an app playing its role, then we + * should make sure that relevant properties (like + * provides/is_required) are set in the pre-existing app if needed. */ + + /* first check to see if service is already provided */ + if (provides != NULL) { + GsmApp *dup; + + dup = (GsmApp *)gsm_store_find (manager->priv->apps, + (GsmStoreFunc)_find_app_provides, + (char *)provides); + if (dup != NULL) { + g_debug ("GsmManager: service '%s' is already provided", provides); + + if (is_required && + !g_slist_find (manager->priv->required_apps, dup)) { + g_debug ("GsmManager: making app '%s' required", gsm_app_peek_app_id (dup)); + manager->priv->required_apps = g_slist_prepend (manager->priv->required_apps, dup); + } + + return FALSE; + } + } + + app = gsm_autostart_app_new (path, &error); + if (app == NULL) { + g_warning ("%s", error->message); + g_clear_error (&error); + return FALSE; + } + + internal_provides = gsm_app_get_provides (app); + if (internal_provides) { + int i; + gboolean provided = FALSE; + + for (i = 0; internal_provides[i] != NULL; i++) { + GsmApp *dup; + + dup = (GsmApp *)gsm_store_find (manager->priv->apps, + (GsmStoreFunc)_find_app_provides, + (char *)internal_provides[i]); + if (dup != NULL) { + g_debug ("GsmManager: service '%s' is already provided", internal_provides[i]); + + if (is_required && + !g_slist_find (manager->priv->required_apps, dup)) { + g_debug ("GsmManager: making app '%s' required", gsm_app_peek_app_id (dup)); + manager->priv->required_apps = g_slist_prepend (manager->priv->required_apps, dup); + } + + provided = TRUE; + break; + } + } + + g_strfreev (internal_provides); + + if (provided) { + g_object_unref (app); + return FALSE; + } + } + + if (provides) + gsm_autostart_app_add_provides (GSM_AUTOSTART_APP (app), provides); + + g_debug ("GsmManager: read %s", path); + append_app (manager, app, provides, is_required); + g_object_unref (app); + + return TRUE; +} + +gboolean +gsm_manager_add_autostart_app (GsmManager *manager, + const char *path, + const char *provides) +{ + return add_autostart_app_internal (manager, + path, + provides, + FALSE); +} + +/** + * gsm_manager_add_required_app: + * @manager: a #GsmManager + * @path: Path to desktop file + * @provides: What the component provides, as a space separated list + * + * Similar to gsm_manager_add_autostart_app(), except marks the + * component as being required; we then try harder to ensure + * it's running and inform the user if we can't. + * + */ +gboolean +gsm_manager_add_required_app (GsmManager *manager, + const char *path, + const char *provides) +{ + return add_autostart_app_internal (manager, + path, + provides, + TRUE); +} + + +gboolean +gsm_manager_add_autostart_apps_from_dir (GsmManager *manager, + const char *path) +{ + GDir *dir; + const char *name; + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + g_return_val_if_fail (path != NULL, FALSE); + + g_debug ("GsmManager: *** Adding autostart apps for %s", path); + + dir = g_dir_open (path, 0, NULL); + if (dir == NULL) { + return FALSE; + } + + while ((name = g_dir_read_name (dir))) { + char *desktop_file; + + if (!g_str_has_suffix (name, ".desktop")) { + continue; + } + + desktop_file = g_build_filename (path, name, NULL); + gsm_manager_add_autostart_app (manager, desktop_file, NULL); + g_free (desktop_file); + } + + g_dir_close (dir); + + return TRUE; +} + +static void +on_shutdown_prepared (GsmSystem *system, + gboolean success, + GsmManager *manager) +{ + g_debug ("GsmManager: on_shutdown_prepared, success: %d", success); + g_signal_handlers_disconnect_by_func (system, on_shutdown_prepared, manager); + + if (success) { + /* move to end-session phase */ + g_assert (manager->priv->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION); + manager->priv->phase++; + start_phase (manager); + } else { + disconnect_shell_dialog_signals (manager); + gsm_shell_close_end_session_dialog (manager->priv->shell); + /* back to running phase */ + cancel_end_session (manager); + } +} + +static gboolean +do_query_end_session_exit (GsmManager *manager) +{ + gboolean reboot = FALSE; + gboolean shutdown = FALSE; + + switch (manager->priv->logout_type) { + case GSM_MANAGER_LOGOUT_LOGOUT: + break; + case GSM_MANAGER_LOGOUT_REBOOT: + case GSM_MANAGER_LOGOUT_REBOOT_INTERACT: + reboot = TRUE; + break; + case GSM_MANAGER_LOGOUT_SHUTDOWN: + case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT: + shutdown = TRUE; + break; + default: + g_warning ("Unexpected logout type %d in do_query_end_session_exit()", + manager->priv->logout_type); + break; + } + + if (reboot || shutdown) { + g_signal_connect (manager->priv->system, "shutdown-prepared", + G_CALLBACK (on_shutdown_prepared), manager); + gsm_system_prepare_shutdown (manager->priv->system, reboot); + return FALSE; /* don't leave query end session yet */ + } + + return TRUE; /* go to end session phase */ +} diff -Nru gnome-session-3.30.1/.pc/ubuntu/dbus_request_shutdown.patch/gnome-session/gsm-manager.h gnome-session-3.30.1/.pc/ubuntu/dbus_request_shutdown.patch/gnome-session/gsm-manager.h --- gnome-session-3.30.1/.pc/ubuntu/dbus_request_shutdown.patch/gnome-session/gsm-manager.h 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/dbus_request_shutdown.patch/gnome-session/gsm-manager.h 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,135 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * 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, see . + * + */ + + +#ifndef __GSM_MANAGER_H +#define __GSM_MANAGER_H + +#include + +#include "gsm-store.h" +#include "gsm-manager-logout-mode.h" + +G_BEGIN_DECLS + +#define GSM_TYPE_MANAGER (gsm_manager_get_type ()) +#define GSM_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GSM_TYPE_MANAGER, GsmManager)) +#define GSM_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GSM_TYPE_MANAGER, GsmManagerClass)) +#define GSM_IS_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSM_TYPE_MANAGER)) +#define GSM_IS_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSM_TYPE_MANAGER)) +#define GSM_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSM_TYPE_MANAGER, GsmManagerClass)) + +typedef struct GsmManagerPrivate GsmManagerPrivate; + +typedef struct +{ + GObject parent; + GsmManagerPrivate *priv; +} GsmManager; + +typedef struct +{ + GObjectClass parent_class; + + void (* phase_changed) (GsmManager *manager, + const char *phase); +} GsmManagerClass; + +typedef enum { + /* gsm's own startup/initialization phase */ + GSM_MANAGER_PHASE_STARTUP = 0, + /* gnome-initial-setup */ + GSM_MANAGER_PHASE_EARLY_INITIALIZATION, + /* gnome-keyring-daemon */ + GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER, + /* wayland compositor and XWayland */ + GSM_MANAGER_PHASE_DISPLAY_SERVER, + /* xrandr setup, gnome-settings-daemon, etc */ + GSM_MANAGER_PHASE_INITIALIZATION, + /* window/compositing managers */ + GSM_MANAGER_PHASE_WINDOW_MANAGER, + /* apps that will create _NET_WM_WINDOW_TYPE_PANEL windows */ + GSM_MANAGER_PHASE_PANEL, + /* apps that will create _NET_WM_WINDOW_TYPE_DESKTOP windows */ + GSM_MANAGER_PHASE_DESKTOP, + /* everything else */ + GSM_MANAGER_PHASE_APPLICATION, + /* done launching */ + GSM_MANAGER_PHASE_RUNNING, + /* shutting down */ + GSM_MANAGER_PHASE_QUERY_END_SESSION, + GSM_MANAGER_PHASE_END_SESSION, + GSM_MANAGER_PHASE_EXIT +} GsmManagerPhase; + +typedef enum +{ + GSM_MANAGER_ERROR_GENERAL = 0, + GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + GSM_MANAGER_ERROR_ALREADY_REGISTERED, + GSM_MANAGER_ERROR_NOT_REGISTERED, + GSM_MANAGER_ERROR_INVALID_OPTION, + GSM_MANAGER_ERROR_LOCKED_DOWN, + GSM_MANAGER_NUM_ERRORS +} GsmManagerError; + +#define GSM_MANAGER_ERROR gsm_manager_error_quark () +GQuark gsm_manager_error_quark (void); + +GType gsm_manager_get_type (void); + +GsmManager * gsm_manager_new (GsmStore *client_store, + gboolean failsafe); +GsmManager * gsm_manager_get (void); + +gboolean gsm_manager_get_failsafe (GsmManager *manager); + +gboolean gsm_manager_add_autostart_app (GsmManager *manager, + const char *path, + const char *provides); +gboolean gsm_manager_add_required_app (GsmManager *manager, + const char *path, + const char *provides); +gboolean gsm_manager_add_autostart_apps_from_dir (GsmManager *manager, + const char *path); +gboolean gsm_manager_add_legacy_session_apps (GsmManager *manager, + const char *path); + +void gsm_manager_start (GsmManager *manager); + +const char * _gsm_manager_get_default_session (GsmManager *manager); + +void _gsm_manager_set_active_session (GsmManager *manager, + const char *session_name, + gboolean is_fallback); + +void _gsm_manager_set_renderer (GsmManager *manager, + const char *renderer); + +gboolean gsm_manager_logout (GsmManager *manager, + guint logout_mode, + GError **error); + +gboolean gsm_manager_set_phase (GsmManager *manager, + GsmManagerPhase phase); + +G_END_DECLS + +#endif /* __GSM_MANAGER_H */ diff -Nru gnome-session-3.30.1/.pc/ubuntu/dbus_request_shutdown.patch/gnome-session/org.gnome.SessionManager.xml gnome-session-3.30.1/.pc/ubuntu/dbus_request_shutdown.patch/gnome-session/org.gnome.SessionManager.xml --- gnome-session-3.30.1/.pc/ubuntu/dbus_request_shutdown.patch/gnome-session/org.gnome.SessionManager.xml 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/dbus_request_shutdown.patch/gnome-session/org.gnome.SessionManager.xml 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,484 @@ + + + + + + + + + + + The variable name + + + + + The value + + + + + Adds the variable name to the application launch environment with the specified value. May only be used during the Session Manager initialization phase. + + + + + + + + The locale category + + + + + The value + + + + + Reads the current state of the specific locale category. + + + + + + + + The error message + + + + + Whether the error should be treated as fatal + + + + + May be used by applications launched during the Session Manager initialization phase to indicate there was a problem. + + + + + + + + + + The application identifier + + + + + Client startup identifier + + + + + The object path of the newly registered client + + + + + Register the caller as a Session Management client. + + + + + + + + The object path of the client + + + + + Unregister the specified client from Session Management. + + + + + + + + The application identifier + + + + + The toplevel X window identifier + + + + + The reason for the inhibit + + + + + Flags that specify what should be inhibited + + + + + The cookie + + + + + Proactively indicates that the calling application is performing an action that should not be interrupted and sets a reason to be displayed to the user when an interruption is about to take placea. + + + Applications should invoke this method when they begin an operation that + should not be interrupted, such as creating a CD or DVD. The types of actions + that may be blocked are specified by the flags parameter. When the application + completes the operation it should call Uninhibit() + or disconnect from the session bus. + + + Applications should not expect that they will always be able to block the + action. In most cases, users will be given the option to force the action + to take place. + + + Reasons should be short and to the point. + + + The flags parameter must include at least one of the following: + + + 1 + Inhibit logging out + + + 2 + Inhibit user switching + + + 4 + Inhibit suspending the session or computer + + + 8 + Inhibit the session being marked as idle + + + 16 + Inhibit auto-mounting removable media for the session + + + Values for flags may be bitwise or'ed together. + + + The returned cookie is used to uniquely identify this request. It should be used + as an argument to Uninhibit() in + order to remove the request. + + + + + + + + + The cookie + + + + + Cancel a previous call to Inhibit() identified by the cookie. + + + + + + + + Flags that spefify what should be inhibited + + + + + Returns TRUE if any of the operations in the bitfield flags are inhibited + + + + + Determine if operation(s) specified by the flags + are currently inhibited. Flags are same as those accepted + by the + Inhibit() + method. + + + + + + + + an array of client IDs + + + + + This gets a list of all the Clients + that are currently known to the session manager. + Each Client ID is an D-Bus object path for the object that implements the + Client interface. + + org.gnome.SessionManager.Client + + + + + + + an array of inhibitor IDs + + + + + This gets a list of all the Inhibitors + that are currently known to the session manager. + Each Inhibitor ID is an D-Bus object path for the object that implements the + Inhibitor interface. + + org.gnome.SessionManager.Inhibitor + + + + + + + + The autostart condition string + + + + + True if condition is handled, false otherwise + + + + + Allows the caller to determine whether the session manager is + handling changes to the specified autostart condition. + + + + + + + + Request a shutdown dialog. + + + + + + + + Request a reboot dialog. + + + + + + + + True if shutdown is available to the user, false otherwise + + + + + Allows the caller to determine whether or not it's okay to show + a shutdown option in the UI + + + + + + + + Whether we should reboot into setup + + + + + Allows the caller to indicate to the system's firmware to boot into setup mode + + + + + + + + True if boot into setup mode is available to the user, false otherwise + + + + + Allows the caller to determine whether or not it's okay to show + a reboot to firmware option in the UI + + + + + + + + The type of logout that is being requested + + + + + Request a logout dialog + + Allowed values for the mode parameter are: + + + 0 + Normal. + + + 1 + No confirmation inferface should be shown. + + + 2 + Forcefully logout. No confirmation will be shown and any inhibitors will be ignored. + + + Values for flags may be bitwise or'ed together. + + + + + + + + + True if the session has entered the Running phase, false otherwise + + + + + Allows the caller to determine whether the session manager + has entered the Running phase, in case the client missed the + SessionRunning signal. + + + + + + + + + + The object path for the added client + + + + + Emitted when a client has been added to the session manager. + + + + + + + + The object path for the removed client + + + + + Emitted when a client has been removed from the session manager. + + + + + + + + + The object path for the added inhibitor + + + + + Emitted when an inhibitor has been added to the session manager. + + + + + + + + The object path for the removed inhibitor + + + + + Emitted when an inhibitor has been removed from the session manager. + + + + + + + + + Indicates the session has entered the Running phase. + + + + + + + + Indicates the session is about to end. + + + + + + + + + + The name of the session that has been loaded. + + + + + + + + The renderer for the session that has been loaded. + At the moment this supports GL and GLES, and is only used for the + X session. + + + + + + + + If true, the session is currently in the + foreground and available for user input. + + + + + + + + A bitmask of flags to indicate which actions + are inhibited. See the Inhibit() function's description + for a list of possible values. + + + + + + diff -Nru gnome-session-3.30.1/.pc/ubuntu/ignore_gsettings_region.patch/gnome-session/gnome-session.in gnome-session-3.30.1/.pc/ubuntu/ignore_gsettings_region.patch/gnome-session/gnome-session.in --- gnome-session-3.30.1/.pc/ubuntu/ignore_gsettings_region.patch/gnome-session/gnome-session.in 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/ignore_gsettings_region.patch/gnome-session/gnome-session.in 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,28 @@ +#!/bin/sh + +if [ "x$XDG_SESSION_TYPE" = "xwayland" ] && + [ "x$XDG_SESSION_CLASS" != "xgreeter" ] && + [ -n "$SHELL" ] && + grep -q "$SHELL" /etc/shells && + ! (echo "$SHELL" | grep -q "false") && + ! (echo "$SHELL" | grep -q "nologin"); then + if [ "$1" != '-l' ]; then + exec bash -c "exec -l '$SHELL' -c '$0 -l $*'" + else + shift + fi +fi + +SETTING=$(G_MESSAGES_DEBUG= gsettings get org.gnome.system.locale region) +REGION=${SETTING#\'} +REGION=${REGION%\'} + +if [ -n "$REGION" ]; then + export LC_TIME=$REGION + export LC_NUMERIC=$REGION + export LC_MONETARY=$REGION + export LC_MEASUREMENT=$REGION + export LC_PAPER=$REGION +fi + +exec @libexecdir@/gnome-session-binary "$@" diff -Nru gnome-session-3.30.1/.pc/ubuntu/kill_the_fail_whale.patch/gnome-session/gsm-fail-whale.c gnome-session-3.30.1/.pc/ubuntu/kill_the_fail_whale.patch/gnome-session/gsm-fail-whale.c --- gnome-session-3.30.1/.pc/ubuntu/kill_the_fail_whale.patch/gnome-session/gsm-fail-whale.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/kill_the_fail_whale.patch/gnome-session/gsm-fail-whale.c 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * gsm-fail-whale.c + * Copyright (C) 2012 Red Hat, Inc + * + * 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, see . + */ + +#include + +#include +#include + +#include +#include + +#include "gsm-fail-whale.h" +#include "gsm-util.h" + +static void +on_fail_whale_failed (void) +{ + raise (SIGTERM); +} + +void +gsm_fail_whale_dialog_we_failed (gboolean debug_mode, + gboolean allow_logout, + GsmShellExtensions *extensions) +{ + gint i; + gchar *argv[5]; + GPid pid; + + i = 0; + argv[i++] = LIBEXECDIR "/gnome-session-failed"; + if (debug_mode) + argv[i++] = "--debug"; + if (allow_logout) + argv[i++] = "--allow-logout"; + if (extensions != NULL && gsm_shell_extensions_n_extensions (extensions) > 0) + argv[i++] = "--extensions"; + argv[i++] = NULL; + + if (!g_spawn_async (NULL, argv, (char **) gsm_util_listenv (), G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL)) { + exit (1); + } + + g_child_watch_add (pid, + (GChildWatchFunc)on_fail_whale_failed, + NULL); +} diff -Nru gnome-session-3.30.1/.pc/ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch/data/meson.build gnome-session-3.30.1/.pc/ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch/data/meson.build --- gnome-session-3.30.1/.pc/ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch/data/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch/data/meson.build 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,137 @@ +desktop_plain = 'gnome' + +desktops = [ + desktop_plain, + 'gnome-xorg', + 'ubuntu', + 'ubuntu-communitheme-snap', + 'unity', +] + +if enable_session_selector + desktops += 'gnome-custom-session' +endif + +foreach name: desktops + desktop_conf = configuration_data() + desktop_conf.set('bindir', session_bindir) + + desktop = name + '.desktop' + + desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf + ) + + install_dir = join_paths(session_datadir, 'xsessions') + # FIXME: The same target can not be copied into two directories. + # There is a workaround in meson_post_install.py until proper solution arises: + # https://groups.google.com/forum/#!topic/mesonbuild/3iIoYPrN4P0 + if name == desktop_plain + #install_dir: [ + # join_paths(session_datadir, 'xsessions'), + # join_paths(session_datadir, 'wayland-sessions') + #] + endif + + desktop_target = i18n.merge_file( + desktop, + type: 'desktop', + input: desktop_in, + output: desktop, + po_dir: po_dir, + install: true, + install_dir: install_dir + ) + +endforeach + +wayland_desktops = [ + 'ubuntu-wayland', + 'ubuntu-communitheme-snap-wayland', +] + +foreach name: wayland_desktops + desktop_conf = configuration_data() + desktop_conf.set('bindir', session_bindir) + + desktop = name + '.desktop' + + desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf + ) + + install_dir = join_paths(session_datadir, 'wayland-sessions') + + desktop_target = i18n.merge_file( + desktop, + type: 'desktop', + input: desktop_in, + output: desktop, + po_dir: po_dir, + install: true, + install_dir: install_dir + ) + +endforeach + +sessions = [ + 'gnome.session', + 'gnome-dummy.session', + 'ubuntu.session', + 'unity.session' +] + +foreach session: sessions + desktop_conf = configuration_data() + desktop_conf.set('libexecdir', session_libexecdir) + + desktop = session + '.desktop' + + desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf + ) + + i18n.merge_file( + session, + type: 'desktop', + input: desktop_in, + output: session, + po_dir: po_dir, + install: true, + install_dir: join_paths(session_pkgdatadir, 'sessions') + ) +endforeach + +schema_conf = configuration_data() + +schema = 'org.gnome.SessionManager.gschema.xml' + +configure_file( + input: schema + '.in', + output: schema, + install: true, + install_dir: join_paths(session_datadir, 'glib-2.0', 'schemas'), + configuration: schema_conf +) + +install_data( + 'gnome-session.convert', + install_dir: join_paths(session_datadir, 'GConf', 'gsettings') +) + +data = files('hardware-compatibility') + +if enable_session_selector + data += files('session-selector.ui') +endif + +install_data( + data, + install_dir: session_pkgdatadir +) diff -Nru gnome-session-3.30.1/.pc/ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch/meson_post_install.py gnome-session-3.30.1/.pc/ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch/meson_post_install.py --- gnome-session-3.30.1/.pc/ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch/meson_post_install.py 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/post_install-install-gnome.desktop-session-in-wayland-ses.patch/meson_post_install.py 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +import os +import shutil +import subprocess +import sys + +if os.environ.get('DESTDIR'): + install_root = os.environ.get('DESTDIR') + os.path.abspath(sys.argv[1]) +else: + install_root = sys.argv[1] + +if not os.environ.get('DESTDIR'): + schemadir = os.path.join(install_root, 'glib-2.0', 'schemas') + print('Compile gsettings schemas...') + subprocess.call(['glib-compile-schemas', schemadir]) + +# FIXME: this is due to unable to copy a generated target file: +# https://groups.google.com/forum/#!topic/mesonbuild/3iIoYPrN4P0 +dst_dir = os.path.join(install_root, 'wayland-sessions') +if not os.path.exists(dst_dir): + os.makedirs(dst_dir) + +src = os.path.join(install_root, 'xsessions', 'gnome.desktop') +dst = os.path.join(dst_dir, 'gnome.desktop') +shutil.copyfile(src, dst) diff -Nru gnome-session-3.30.1/.pc/ubuntu/remove_session_saving_from_gui.patch/gnome-session/gsm-session-fill.c gnome-session-3.30.1/.pc/ubuntu/remove_session_saving_from_gui.patch/gnome-session/gsm-session-fill.c --- gnome-session-3.30.1/.pc/ubuntu/remove_session_saving_from_gui.patch/gnome-session/gsm-session-fill.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/remove_session_saving_from_gui.patch/gnome-session/gsm-session-fill.c 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,334 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006, 2010 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * 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, see . + */ + +#include + +#include "gsm-session-fill.h" + +#include "gsm-system.h" +#include "gsm-manager.h" +#include "gsm-process-helper.h" +#include "gsm-util.h" + +#define GSM_KEYFILE_SESSION_GROUP "GNOME Session" +#define GSM_KEYFILE_RUNNABLE_KEY "IsRunnableHelper" +#define GSM_KEYFILE_FALLBACK_KEY "FallbackSession" +#define GSM_KEYFILE_REQUIRED_COMPONENTS_KEY "RequiredComponents" + +/* See https://bugzilla.gnome.org/show_bug.cgi?id=641992 for discussion */ +#define GSM_RUNNABLE_HELPER_TIMEOUT 3000 /* ms */ + +typedef void (*GsmFillHandleComponent) (const char *component, + const char *app_path, + gpointer user_data); + +static void +handle_required_components (GKeyFile *keyfile, + gboolean look_in_saved_session, + GsmFillHandleComponent callback, + gpointer user_data) +{ + char **required_components; + int i; + + g_assert (keyfile != NULL); + g_assert (callback != NULL); + + required_components = g_key_file_get_string_list (keyfile, + GSM_KEYFILE_SESSION_GROUP, + GSM_KEYFILE_REQUIRED_COMPONENTS_KEY, + NULL, NULL); + + if (!required_components) + return; + + for (i = 0; required_components[i] != NULL; i++) { + char *app_path; + + app_path = gsm_util_find_desktop_file_for_app_name (required_components[i], + look_in_saved_session, TRUE); + callback (required_components[i], app_path, user_data); + g_free (app_path); + } + + g_strfreev (required_components); +} + +static void +check_required_components_helper (const char *component, + const char *app_path, + gpointer user_data) +{ + gboolean *error = user_data; + + if (app_path == NULL) { + g_warning ("Unable to find required component '%s'", component); + *error = TRUE; + } +} + +static gboolean +check_required (GKeyFile *keyfile) +{ + gboolean error = FALSE; + + g_debug ("fill: *** Checking required components"); + + handle_required_components (keyfile, FALSE, + check_required_components_helper, &error); + + g_debug ("fill: *** Done checking required components"); + + return !error; +} + +static void +maybe_load_saved_session_apps (GsmManager *manager) +{ + GsmSystem *system; + gboolean is_login; + + system = gsm_get_system (); + is_login = gsm_system_is_login_session (system); + g_object_unref (system); + + if (is_login) + return; + + gsm_manager_add_autostart_apps_from_dir (manager, gsm_util_get_saved_session_dir ()); +} + +static void +append_required_components_helper (const char *component, + const char *app_path, + gpointer user_data) +{ + GsmManager *manager = user_data; + + if (app_path == NULL) + g_warning ("Unable to find required component '%s'", component); + else + gsm_manager_add_required_app (manager, app_path, NULL); +} + + +static void +load_standard_apps (GsmManager *manager, + GKeyFile *keyfile) +{ + g_debug ("fill: *** Adding required components"); + handle_required_components (keyfile, !gsm_manager_get_failsafe (manager), + append_required_components_helper, manager); + g_debug ("fill: *** Done adding required components"); + + if (!gsm_manager_get_failsafe (manager)) { + char **autostart_dirs; + int i; + + autostart_dirs = gsm_util_get_autostart_dirs (); + + maybe_load_saved_session_apps (manager); + + for (i = 0; autostart_dirs[i]; i++) { + gsm_manager_add_autostart_apps_from_dir (manager, + autostart_dirs[i]); + } + + g_strfreev (autostart_dirs); + } +} + +static GKeyFile * +get_session_keyfile_if_valid (const char *path) +{ + GKeyFile *keyfile; + gsize len; + char **list; + + g_debug ("fill: *** Looking if %s is a valid session file", path); + + keyfile = g_key_file_new (); + + if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL)) { + g_debug ("Cannot use session '%s': non-existing or invalid file.", path); + goto error; + } + + if (!g_key_file_has_group (keyfile, GSM_KEYFILE_SESSION_GROUP)) { + g_warning ("Cannot use session '%s': no '%s' group.", path, GSM_KEYFILE_SESSION_GROUP); + goto error; + } + + list = g_key_file_get_string_list (keyfile, + GSM_KEYFILE_SESSION_GROUP, + GSM_KEYFILE_REQUIRED_COMPONENTS_KEY, + &len, NULL); + if (list) + g_strfreev (list); + if (len == 0) + g_warning ("Session '%s': no component in the session.", path); + + return keyfile; + +error: + g_key_file_free (keyfile); + return NULL; +} + +/** + * find_valid_session_keyfile: + * @session: name of session + * + * We look for the session file in XDG_CONFIG_HOME, XDG_CONFIG_DIRS and + * XDG_DATA_DIRS. This enables users and sysadmins to override a specific + * session that is shipped in XDG_DATA_DIRS. + */ +static GKeyFile * +find_valid_session_keyfile (const char *session) +{ + GPtrArray *dirs; + const char * const *system_config_dirs; + const char * const *system_data_dirs; + int i; + GKeyFile *keyfile; + char *basename; + char *path; + + dirs = g_ptr_array_new (); + + g_ptr_array_add (dirs, (gpointer) g_get_user_config_dir ()); + + system_config_dirs = g_get_system_config_dirs (); + for (i = 0; system_config_dirs[i]; i++) + g_ptr_array_add (dirs, (gpointer) system_config_dirs[i]); + + system_data_dirs = g_get_system_data_dirs (); + for (i = 0; system_data_dirs[i]; i++) + g_ptr_array_add (dirs, (gpointer) system_data_dirs[i]); + + keyfile = NULL; + basename = g_strdup_printf ("%s.session", session); + path = NULL; + + for (i = 0; i < dirs->len; i++) { + path = g_build_filename (dirs->pdata[i], "gnome-session", "sessions", basename, NULL); + keyfile = get_session_keyfile_if_valid (path); + if (keyfile != NULL) + break; + } + + if (dirs) + g_ptr_array_free (dirs, TRUE); + if (basename) + g_free (basename); + if (path) + g_free (path); + + return keyfile; +} + +static GKeyFile * +get_session_keyfile (const char *session, + char **actual_session, + gboolean *is_fallback) +{ + GKeyFile *keyfile; + gboolean session_runnable; + char *value; + GError *error = NULL; + + *actual_session = NULL; + + g_debug ("fill: *** Getting session '%s'", session); + + keyfile = find_valid_session_keyfile (session); + + if (!keyfile) + return NULL; + + session_runnable = TRUE; + + value = g_key_file_get_string (keyfile, + GSM_KEYFILE_SESSION_GROUP, GSM_KEYFILE_RUNNABLE_KEY, + NULL); + if (!IS_STRING_EMPTY (value)) { + g_debug ("fill: *** Launching helper '%s' to know if session is runnable", value); + session_runnable = gsm_process_helper (value, GSM_RUNNABLE_HELPER_TIMEOUT, &error); + if (!session_runnable) { + g_warning ("Session '%s' runnable check failed: %s", session, + error->message); + g_clear_error (&error); + } + } + g_free (value); + + if (session_runnable) { + session_runnable = check_required (keyfile); + } + + if (session_runnable) { + *actual_session = g_strdup (session); + if (is_fallback) + *is_fallback = FALSE; + return keyfile; + } + + g_debug ("fill: *** Session is not runnable"); + + /* We can't run this session, so try to use the fallback */ + value = g_key_file_get_string (keyfile, + GSM_KEYFILE_SESSION_GROUP, GSM_KEYFILE_FALLBACK_KEY, + NULL); + + g_key_file_free (keyfile); + keyfile = NULL; + + if (!IS_STRING_EMPTY (value)) { + if (is_fallback) + *is_fallback = TRUE; + keyfile = get_session_keyfile (value, actual_session, NULL); + } + g_free (value); + + return keyfile; +} + +gboolean +gsm_session_fill (GsmManager *manager, + const char *session) +{ + GKeyFile *keyfile; + gboolean is_fallback; + char *actual_session; + + keyfile = get_session_keyfile (session, &actual_session, &is_fallback); + + if (!keyfile) + return FALSE; + + _gsm_manager_set_active_session (manager, actual_session, is_fallback); + + g_free (actual_session); + + load_standard_apps (manager, keyfile); + + g_key_file_free (keyfile); + + return TRUE; +} diff -Nru gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-app.c gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-app.c --- gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-app.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-app.c 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,569 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * 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, see . + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "gsm-app.h" +#include "org.gnome.SessionManager.App.h" + +#define GSM_APP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_APP, GsmAppPrivate)) + +/* If a component crashes twice within a minute, we count that as a fatal error */ +#define _GSM_APP_RESPAWN_RATELIMIT_SECONDS 60 + +struct _GsmAppPrivate +{ + char *id; + char *app_id; + int phase; + char *startup_id; + gboolean registered; + GTimeVal last_restart_time; + GDBusConnection *connection; + GsmExportedApp *skeleton; +}; + + +enum { + EXITED, + DIED, + LAST_SIGNAL +}; + +static guint32 app_serial = 1; + +static guint signals[LAST_SIGNAL] = { 0 }; + +enum { + PROP_0, + PROP_ID, + PROP_STARTUP_ID, + PROP_PHASE, + PROP_REGISTERED, + LAST_PROP +}; + +G_DEFINE_TYPE (GsmApp, gsm_app, G_TYPE_OBJECT) + +GQuark +gsm_app_error_quark (void) +{ + static GQuark ret = 0; + if (ret == 0) { + ret = g_quark_from_static_string ("gsm_app_error"); + } + + return ret; + +} + +static gboolean +gsm_app_get_app_id (GsmExportedApp *skeleton, + GDBusMethodInvocation *invocation, + GsmApp *app) +{ + const gchar *id; + + id = GSM_APP_GET_CLASS (app)->impl_get_app_id (app); + gsm_exported_app_complete_get_app_id (skeleton, invocation, id); + + return TRUE; +} + +static gboolean +gsm_app_get_startup_id (GsmExportedApp *skeleton, + GDBusMethodInvocation *invocation, + GsmApp *app) +{ + const gchar *id; + + id = g_strdup (app->priv->startup_id); + gsm_exported_app_complete_get_startup_id (skeleton, invocation, id); + + return TRUE; +} + +static gboolean +gsm_app_get_phase (GsmExportedApp *skeleton, + GDBusMethodInvocation *invocation, + GsmApp *app) +{ + gsm_exported_app_complete_get_phase (skeleton, invocation, app->priv->phase); + return TRUE; +} + +static guint32 +get_next_app_serial (void) +{ + guint32 serial; + + serial = app_serial++; + + if ((gint32)app_serial < 0) { + app_serial = 1; + } + + return serial; +} + +static gboolean +register_app (GsmApp *app) +{ + GError *error; + GsmExportedApp *skeleton; + + error = NULL; + app->priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (error != NULL) { + g_critical ("error getting session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + skeleton = gsm_exported_app_skeleton_new (); + app->priv->skeleton = skeleton; + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), + app->priv->connection, app->priv->id, + &error); + + if (error != NULL) { + g_critical ("error registering app on session bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + g_signal_connect (skeleton, "handle-get-app-id", + G_CALLBACK (gsm_app_get_app_id), app); + g_signal_connect (skeleton, "handle-get-phase", + G_CALLBACK (gsm_app_get_phase), app); + g_signal_connect (skeleton, "handle-get-startup-id", + G_CALLBACK (gsm_app_get_startup_id), app); + + return TRUE; +} + +static GObject * +gsm_app_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GsmApp *app; + gboolean res; + + app = GSM_APP (G_OBJECT_CLASS (gsm_app_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + g_free (app->priv->id); + app->priv->id = g_strdup_printf ("/org/gnome/SessionManager/App%u", get_next_app_serial ()); + + res = register_app (app); + if (! res) { + g_warning ("Unable to register app with session bus"); + } + + return G_OBJECT (app); +} + +static void +gsm_app_init (GsmApp *app) +{ + app->priv = GSM_APP_GET_PRIVATE (app); +} + +static void +gsm_app_set_phase (GsmApp *app, + int phase) +{ + g_return_if_fail (GSM_IS_APP (app)); + + app->priv->phase = phase; +} + +static void +gsm_app_set_id (GsmApp *app, + const char *id) +{ + g_return_if_fail (GSM_IS_APP (app)); + + g_free (app->priv->id); + + app->priv->id = g_strdup (id); + g_object_notify (G_OBJECT (app), "id"); + +} +static void +gsm_app_set_startup_id (GsmApp *app, + const char *startup_id) +{ + g_return_if_fail (GSM_IS_APP (app)); + + g_free (app->priv->startup_id); + + app->priv->startup_id = g_strdup (startup_id); + g_object_notify (G_OBJECT (app), "startup-id"); + +} + +static void +gsm_app_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmApp *app = GSM_APP (object); + + switch (prop_id) { + case PROP_STARTUP_ID: + gsm_app_set_startup_id (app, g_value_get_string (value)); + break; + case PROP_ID: + gsm_app_set_id (app, g_value_get_string (value)); + break; + case PROP_PHASE: + gsm_app_set_phase (app, g_value_get_int (value)); + break; + case PROP_REGISTERED: + gsm_app_set_registered (app, g_value_get_boolean (value)); + break; + default: + break; + } +} + +static void +gsm_app_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmApp *app = GSM_APP (object); + + switch (prop_id) { + case PROP_STARTUP_ID: + g_value_set_string (value, app->priv->startup_id); + break; + case PROP_ID: + g_value_set_string (value, app->priv->id); + break; + case PROP_PHASE: + g_value_set_int (value, app->priv->phase); + break; + case PROP_REGISTERED: + g_value_set_boolean (value, app->priv->registered); + break; + default: + break; + } +} + +static void +gsm_app_dispose (GObject *object) +{ + GsmApp *app = GSM_APP (object); + + g_free (app->priv->startup_id); + app->priv->startup_id = NULL; + + g_free (app->priv->id); + app->priv->id = NULL; + + if (app->priv->skeleton != NULL) { + g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (app->priv->skeleton), + app->priv->connection); + g_clear_object (&app->priv->skeleton); + } + + g_clear_object (&app->priv->connection); + + G_OBJECT_CLASS (gsm_app_parent_class)->dispose (object); +} + +static void +gsm_app_class_init (GsmAppClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = gsm_app_set_property; + object_class->get_property = gsm_app_get_property; + object_class->dispose = gsm_app_dispose; + object_class->constructor = gsm_app_constructor; + + klass->impl_start = NULL; + klass->impl_get_app_id = NULL; + klass->impl_get_autorestart = NULL; + klass->impl_provides = NULL; + klass->impl_get_provides = NULL; + klass->impl_is_running = NULL; + + g_object_class_install_property (object_class, + PROP_PHASE, + g_param_spec_int ("phase", + "Phase", + "Phase", + -1, + G_MAXINT, + -1, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_string ("id", + "ID", + "ID", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_STARTUP_ID, + g_param_spec_string ("startup-id", + "startup ID", + "Session management startup ID", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_REGISTERED, + g_param_spec_boolean ("registered", + "Registered", + "Registered", + FALSE, + G_PARAM_READWRITE)); + + signals[EXITED] = + g_signal_new ("exited", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmAppClass, exited), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_UCHAR); + signals[DIED] = + g_signal_new ("died", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmAppClass, died), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_INT); + + g_type_class_add_private (klass, sizeof (GsmAppPrivate)); +} + +const char * +gsm_app_peek_id (GsmApp *app) +{ + return app->priv->id; +} + +const char * +gsm_app_peek_app_id (GsmApp *app) +{ + return GSM_APP_GET_CLASS (app)->impl_get_app_id (app); +} + +const char * +gsm_app_peek_startup_id (GsmApp *app) +{ + return app->priv->startup_id; +} + +/** + * gsm_app_peek_phase: + * @app: a %GsmApp + * + * Returns @app's startup phase. + * + * Return value: @app's startup phase + **/ +GsmManagerPhase +gsm_app_peek_phase (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), GSM_MANAGER_PHASE_APPLICATION); + + return app->priv->phase; +} + +gboolean +gsm_app_peek_is_disabled (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + if (GSM_APP_GET_CLASS (app)->impl_is_disabled) { + return GSM_APP_GET_CLASS (app)->impl_is_disabled (app); + } else { + return FALSE; + } +} + +gboolean +gsm_app_peek_is_conditionally_disabled (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + if (GSM_APP_GET_CLASS (app)->impl_is_conditionally_disabled) { + return GSM_APP_GET_CLASS (app)->impl_is_conditionally_disabled (app); + } else { + return FALSE; + } +} + +gboolean +gsm_app_is_running (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + if (GSM_APP_GET_CLASS (app)->impl_is_running) { + return GSM_APP_GET_CLASS (app)->impl_is_running (app); + } else { + return FALSE; + } +} + +gboolean +gsm_app_peek_autorestart (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + if (GSM_APP_GET_CLASS (app)->impl_get_autorestart) { + return GSM_APP_GET_CLASS (app)->impl_get_autorestart (app); + } else { + return FALSE; + } +} + +gboolean +gsm_app_provides (GsmApp *app, const char *service) +{ + if (GSM_APP_GET_CLASS (app)->impl_provides) { + return GSM_APP_GET_CLASS (app)->impl_provides (app, service); + } else { + return FALSE; + } +} + +char ** +gsm_app_get_provides (GsmApp *app) +{ + if (GSM_APP_GET_CLASS (app)->impl_get_provides) { + return GSM_APP_GET_CLASS (app)->impl_get_provides (app); + } else { + return NULL; + } +} + +gboolean +gsm_app_has_autostart_condition (GsmApp *app, + const char *condition) +{ + + if (GSM_APP_GET_CLASS (app)->impl_has_autostart_condition) { + return GSM_APP_GET_CLASS (app)->impl_has_autostart_condition (app, condition); + } else { + return FALSE; + } +} + +gboolean +gsm_app_start (GsmApp *app, + GError **error) +{ + g_debug ("Starting app: %s", app->priv->id); + return GSM_APP_GET_CLASS (app)->impl_start (app, error); +} + +gboolean +gsm_app_restart (GsmApp *app, + GError **error) +{ + GTimeVal current_time; + g_debug ("Re-starting app: %s", app->priv->id); + + g_get_current_time (¤t_time); + if (app->priv->last_restart_time.tv_sec > 0 + && (current_time.tv_sec - app->priv->last_restart_time.tv_sec) < _GSM_APP_RESPAWN_RATELIMIT_SECONDS) { + g_warning ("App '%s' respawning too quickly", gsm_app_peek_app_id (app)); + g_set_error (error, + GSM_APP_ERROR, + GSM_APP_ERROR_RESTART_LIMIT, + "Component '%s' crashing too quickly", + gsm_app_peek_app_id (app)); + return FALSE; + } + app->priv->last_restart_time = current_time; + + return GSM_APP_GET_CLASS (app)->impl_restart (app, error); +} + +gboolean +gsm_app_stop (GsmApp *app, + GError **error) +{ + return GSM_APP_GET_CLASS (app)->impl_stop (app, error); +} + +void +gsm_app_exited (GsmApp *app, + guchar exit_code) +{ + g_return_if_fail (GSM_IS_APP (app)); + + g_signal_emit (app, signals[EXITED], 0, exit_code); +} + +void +gsm_app_died (GsmApp *app, + int signal) +{ + g_return_if_fail (GSM_IS_APP (app)); + + g_signal_emit (app, signals[DIED], 0, signal); +} + +gboolean +gsm_app_get_registered (GsmApp *app) +{ + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + return app->priv->registered; +} + +void +gsm_app_set_registered (GsmApp *app, + gboolean registered) +{ + g_return_if_fail (GSM_IS_APP (app)); + + if (app->priv->registered != registered) { + app->priv->registered = registered; + g_object_notify (G_OBJECT (app), "registered"); + } +} + +gboolean +gsm_app_save_to_keyfile (GsmApp *app, + GKeyFile *keyfile, + GError **error) +{ + g_debug ("Saving app: %s", app->priv->id); + return GSM_APP_GET_CLASS (app)->impl_save_to_keyfile (app, keyfile, error); +} diff -Nru gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-app.h gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-app.h --- gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-app.h 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-app.h 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,134 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * 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, see . + */ + +#ifndef __GSM_APP_H__ +#define __GSM_APP_H__ + +#include +#include + + +#include "gsm-manager.h" +#include "gsm-client.h" + +G_BEGIN_DECLS + +#define GSM_TYPE_APP (gsm_app_get_type ()) +#define GSM_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_APP, GsmApp)) +#define GSM_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_APP, GsmAppClass)) +#define GSM_IS_APP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_APP)) +#define GSM_IS_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_APP)) +#define GSM_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_APP, GsmAppClass)) + +typedef struct _GsmApp GsmApp; +typedef struct _GsmAppClass GsmAppClass; +typedef struct _GsmAppPrivate GsmAppPrivate; + +struct _GsmApp +{ + GObject parent; + GsmAppPrivate *priv; +}; + +struct _GsmAppClass +{ + GObjectClass parent_class; + + /* signals */ + void (*exited) (GsmApp *app, + guchar exit_code); + void (*died) (GsmApp *app, + int signal); + + /* virtual methods */ + gboolean (*impl_start) (GsmApp *app, + GError **error); + gboolean (*impl_restart) (GsmApp *app, + GError **error); + gboolean (*impl_stop) (GsmApp *app, + GError **error); + gboolean (*impl_provides) (GsmApp *app, + const char *service); + char ** (*impl_get_provides) (GsmApp *app); + gboolean (*impl_has_autostart_condition) (GsmApp *app, + const char *service); + gboolean (*impl_is_running) (GsmApp *app); + + gboolean (*impl_get_autorestart) (GsmApp *app); + const char *(*impl_get_app_id) (GsmApp *app); + gboolean (*impl_is_disabled) (GsmApp *app); + gboolean (*impl_is_conditionally_disabled) (GsmApp *app); + + gboolean (*impl_save_to_keyfile) (GsmApp *app, + GKeyFile *keyfile, + GError **error); +}; + +typedef enum +{ + GSM_APP_ERROR_GENERAL = 0, + GSM_APP_ERROR_RESTART_LIMIT, + GSM_APP_ERROR_START, + GSM_APP_ERROR_STOP, + GSM_APP_NUM_ERRORS +} GsmAppError; + +#define GSM_APP_ERROR gsm_app_error_quark () + +GQuark gsm_app_error_quark (void); +GType gsm_app_get_type (void) G_GNUC_CONST; + +gboolean gsm_app_peek_autorestart (GsmApp *app); + +const char *gsm_app_peek_id (GsmApp *app); +const char *gsm_app_peek_app_id (GsmApp *app); +const char *gsm_app_peek_startup_id (GsmApp *app); +GsmManagerPhase gsm_app_peek_phase (GsmApp *app); +gboolean gsm_app_peek_is_disabled (GsmApp *app); +gboolean gsm_app_peek_is_conditionally_disabled (GsmApp *app); + +gboolean gsm_app_start (GsmApp *app, + GError **error); +gboolean gsm_app_restart (GsmApp *app, + GError **error); +gboolean gsm_app_stop (GsmApp *app, + GError **error); +gboolean gsm_app_is_running (GsmApp *app); + +void gsm_app_exited (GsmApp *app, + guchar exit_code); +void gsm_app_died (GsmApp *app, + int signal); + +gboolean gsm_app_provides (GsmApp *app, + const char *service); +char **gsm_app_get_provides (GsmApp *app); +gboolean gsm_app_has_autostart_condition (GsmApp *app, + const char *condition); +gboolean gsm_app_get_registered (GsmApp *app); +void gsm_app_set_registered (GsmApp *app, + gboolean registered); + +gboolean gsm_app_save_to_keyfile (GsmApp *app, + GKeyFile *keyfile, + GError **error); + +G_END_DECLS + +#endif /* __GSM_APP_H__ */ diff -Nru gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-autostart-app.c gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-autostart-app.c --- gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-autostart-app.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-autostart-app.c 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,1471 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * 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, see . + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#ifdef HAVE_SYSTEMD +#ifdef ENABLE_SYSTEMD_JOURNAL +#include +#endif +#include +#endif + +#include "gsm-autostart-app.h" +#include "gsm-util.h" + +enum { + AUTOSTART_LAUNCH_SPAWN = 0, + AUTOSTART_LAUNCH_ACTIVATE +}; + +enum { + GSM_CONDITION_NONE = 0, + GSM_CONDITION_IF_EXISTS = 1, + GSM_CONDITION_UNLESS_EXISTS = 2, + GSM_CONDITION_GSETTINGS = 3, + GSM_CONDITION_IF_SESSION = 4, + GSM_CONDITION_UNLESS_SESSION = 5, + GSM_CONDITION_UNKNOWN = 6 +}; + +#define GSM_SESSION_CLIENT_DBUS_INTERFACE "org.gnome.SessionClient" + +struct _GsmAutostartAppPrivate { + char *desktop_filename; + char *desktop_id; + char *startup_id; + + GDesktopAppInfo *app_info; + /* provides defined in session definition */ + GSList *session_provides; + + /* desktop file state */ + char *condition_string; + gboolean condition; + gboolean autorestart; + + GFileMonitor *condition_monitor; + guint condition_notify_id; + GSettings *condition_settings; + + int launch_type; + GPid pid; + guint child_watch_id; +}; + +enum { + CONDITION_CHANGED, + LAST_SIGNAL +}; + +enum { + PROP_0, + PROP_DESKTOP_FILENAME +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +#define GSM_AUTOSTART_APP_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), GSM_TYPE_AUTOSTART_APP, GsmAutostartAppPrivate)) + +static void gsm_autostart_app_initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GsmAutostartApp, gsm_autostart_app, GSM_TYPE_APP, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, gsm_autostart_app_initable_iface_init)) + +static void +gsm_autostart_app_init (GsmAutostartApp *app) +{ + app->priv = GSM_AUTOSTART_APP_GET_PRIVATE (app); + + app->priv->pid = -1; + app->priv->condition_monitor = NULL; + app->priv->condition = FALSE; +} + +static gboolean +is_disabled (GsmApp *app) +{ + GsmAutostartAppPrivate *priv; + + priv = GSM_AUTOSTART_APP (app)->priv; + + /* GSM_AUTOSTART_APP_ENABLED_KEY key, used by old gnome-session */ + if (g_desktop_app_info_has_key (priv->app_info, + GSM_AUTOSTART_APP_ENABLED_KEY) && + !g_desktop_app_info_get_boolean (priv->app_info, + GSM_AUTOSTART_APP_ENABLED_KEY)) { + g_debug ("app %s is disabled by " GSM_AUTOSTART_APP_ENABLED_KEY, + gsm_app_peek_id (app)); + return TRUE; + } + + /* Hidden key, used by autostart spec */ + if (g_desktop_app_info_get_is_hidden (priv->app_info)) { + g_debug ("app %s is disabled by Hidden", + gsm_app_peek_id (app)); + return TRUE; + } + + /* Check OnlyShowIn/NotShowIn/TryExec */ + if (!g_desktop_app_info_get_show_in (priv->app_info, NULL)) { + g_debug ("app %s is not for the current desktop", + gsm_app_peek_id (app)); + return TRUE; + } + + /* Do not check AutostartCondition - this method is only to determine + if the app is unconditionally disabled */ + + return FALSE; +} + +static gboolean +parse_condition_string (const char *condition_string, + guint *condition_kindp, + char **keyp) +{ + const char *space; + const char *key; + int len; + guint kind; + + space = condition_string + strcspn (condition_string, " "); + len = space - condition_string; + key = space; + while (isspace ((unsigned char)*key)) { + key++; + } + + kind = GSM_CONDITION_UNKNOWN; + + if (!g_ascii_strncasecmp (condition_string, "if-exists", len) && key) { + kind = GSM_CONDITION_IF_EXISTS; + } else if (!g_ascii_strncasecmp (condition_string, "unless-exists", len) && key) { + kind = GSM_CONDITION_UNLESS_EXISTS; + } else if (!g_ascii_strncasecmp (condition_string, "GSettings", len)) { + kind = GSM_CONDITION_GSETTINGS; + } else if (!g_ascii_strncasecmp (condition_string, "GNOME3", len)) { + condition_string = key; + space = condition_string + strcspn (condition_string, " "); + len = space - condition_string; + key = space; + while (isspace ((unsigned char)*key)) { + key++; + } + if (!g_ascii_strncasecmp (condition_string, "if-session", len) && key) { + kind = GSM_CONDITION_IF_SESSION; + } else if (!g_ascii_strncasecmp (condition_string, "unless-session", len) && key) { + kind = GSM_CONDITION_UNLESS_SESSION; + } + } + + if (kind == GSM_CONDITION_UNKNOWN) { + key = NULL; + } + + if (keyp != NULL) { + *keyp = g_strdup (key); + } + + if (condition_kindp != NULL) { + *condition_kindp = kind; + } + + return (kind != GSM_CONDITION_UNKNOWN); +} + +static void +if_exists_condition_cb (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + GsmApp *app) +{ + GsmAutostartAppPrivate *priv; + gboolean condition = FALSE; + + priv = GSM_AUTOSTART_APP (app)->priv; + + switch (event) { + case G_FILE_MONITOR_EVENT_CREATED: + condition = TRUE; + break; + case G_FILE_MONITOR_EVENT_DELETED: + condition = FALSE; + break; + default: + /* Ignore any other monitor event */ + return; + } + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static void +unless_exists_condition_cb (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + GsmApp *app) +{ + GsmAutostartAppPrivate *priv; + gboolean condition = FALSE; + + priv = GSM_AUTOSTART_APP (app)->priv; + + switch (event) { + case G_FILE_MONITOR_EVENT_CREATED: + condition = FALSE; + break; + case G_FILE_MONITOR_EVENT_DELETED: + condition = TRUE; + break; + default: + /* Ignore any other monitor event */ + return; + } + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static void +gsettings_condition_cb (GSettings *settings, + const char *key, + gpointer user_data) +{ + GsmApp *app; + GsmAutostartAppPrivate *priv; + gboolean condition; + + g_return_if_fail (GSM_IS_APP (user_data)); + + app = GSM_APP (user_data); + + priv = GSM_AUTOSTART_APP (app)->priv; + + condition = g_settings_get_boolean (settings, key); + + g_debug ("GsmAutostartApp: app:%s condition changed condition:%d", + gsm_app_peek_id (app), + condition); + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static gboolean +setup_gsettings_condition_monitor (GsmAutostartApp *app, + const char *key) +{ + GSettingsSchemaSource *source; + GSettingsSchema *schema; + GSettings *settings; + GSettingsSchemaKey *schema_key; + const GVariantType *key_type; + char **elems; + gboolean retval = FALSE; + char *signal; + + retval = FALSE; + + schema = NULL; + + elems = g_strsplit (key, " ", 2); + + if (elems == NULL) + goto out; + + if (elems[0] == NULL || elems[1] == NULL) + goto out; + + source = g_settings_schema_source_get_default (); + + schema = g_settings_schema_source_lookup (source, elems[0], TRUE); + + if (schema == NULL) + goto out; + + if (!g_settings_schema_has_key (schema, elems[1])) + goto out; + + schema_key = g_settings_schema_get_key (schema, elems[1]); + + g_assert (schema_key != NULL); + + key_type = g_settings_schema_key_get_value_type (schema_key); + + g_settings_schema_key_unref (schema_key); + + g_assert (key_type != NULL); + + if (!g_variant_type_equal (key_type, G_VARIANT_TYPE_BOOLEAN)) + goto out; + + settings = g_settings_new_full (schema, NULL, NULL); + retval = g_settings_get_boolean (settings, elems[1]); + + signal = g_strdup_printf ("changed::%s", elems[1]); + g_signal_connect (G_OBJECT (settings), signal, + G_CALLBACK (gsettings_condition_cb), app); + g_free (signal); + + app->priv->condition_settings = settings; + +out: + if (schema) + g_settings_schema_unref (schema); + g_strfreev (elems); + + return retval; +} + +static void +if_session_condition_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GsmApp *app; + GsmAutostartAppPrivate *priv; + char *session_name; + char *key; + gboolean condition; + + g_return_if_fail (GSM_IS_APP (user_data)); + + app = GSM_APP (user_data); + + priv = GSM_AUTOSTART_APP (app)->priv; + + parse_condition_string (priv->condition_string, NULL, &key); + + g_object_get (object, "session-name", &session_name, NULL); + condition = strcmp (session_name, key) == 0; + g_free (session_name); + + g_free (key); + + g_debug ("GsmAutostartApp: app:%s condition changed condition:%d", + gsm_app_peek_id (app), + condition); + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static void +unless_session_condition_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GsmApp *app; + GsmAutostartAppPrivate *priv; + char *session_name; + char *key; + gboolean condition; + + g_return_if_fail (GSM_IS_APP (user_data)); + + app = GSM_APP (user_data); + + priv = GSM_AUTOSTART_APP (app)->priv; + + parse_condition_string (priv->condition_string, NULL, &key); + + g_object_get (object, "session-name", &session_name, NULL); + condition = strcmp (session_name, key) != 0; + g_free (session_name); + + g_free (key); + + g_debug ("GsmAutostartApp: app:%s condition changed condition:%d", + gsm_app_peek_id (app), + condition); + + /* Emit only if the condition actually changed */ + if (condition != priv->condition) { + priv->condition = condition; + g_signal_emit (app, signals[CONDITION_CHANGED], 0, condition); + } +} + +static char * +resolve_conditional_file_path (const char *file) +{ + if (g_path_is_absolute (file)) + return g_strdup (file); + return g_build_filename (g_get_user_config_dir (), file, NULL); +} + +static void +setup_condition_monitor (GsmAutostartApp *app) +{ + guint kind; + char *key; + gboolean res; + gboolean disabled; + + if (app->priv->condition_monitor != NULL) { + g_file_monitor_cancel (app->priv->condition_monitor); + } + + if (app->priv->condition_string == NULL) { + return; + } + + /* if it is disabled outright there is no point in monitoring */ + if (is_disabled (GSM_APP (app))) { + return; + } + + key = NULL; + res = parse_condition_string (app->priv->condition_string, &kind, &key); + if (! res) { + g_free (key); + return; + } + + if (key == NULL) { + return; + } + + if (kind == GSM_CONDITION_IF_EXISTS) { + char *file_path; + GFile *file; + + file_path = resolve_conditional_file_path (key); + disabled = !g_file_test (file_path, G_FILE_TEST_EXISTS); + + file = g_file_new_for_path (file_path); + app->priv->condition_monitor = g_file_monitor_file (file, 0, NULL, NULL); + + g_signal_connect (app->priv->condition_monitor, "changed", + G_CALLBACK (if_exists_condition_cb), + app); + + g_object_unref (file); + g_free (file_path); + } else if (kind == GSM_CONDITION_UNLESS_EXISTS) { + char *file_path; + GFile *file; + + file_path = resolve_conditional_file_path (key); + disabled = g_file_test (file_path, G_FILE_TEST_EXISTS); + + file = g_file_new_for_path (file_path); + app->priv->condition_monitor = g_file_monitor_file (file, 0, NULL, NULL); + + g_signal_connect (app->priv->condition_monitor, "changed", + G_CALLBACK (unless_exists_condition_cb), + app); + + g_object_unref (file); + g_free (file_path); + } else if (kind == GSM_CONDITION_GSETTINGS) { + disabled = !setup_gsettings_condition_monitor (app, key); + } else if (kind == GSM_CONDITION_IF_SESSION) { + GsmManager *manager; + char *session_name; + + /* get the singleton */ + manager = gsm_manager_get (); + + g_object_get (manager, "session-name", &session_name, NULL); + disabled = strcmp (session_name, key) != 0; + + g_signal_connect (manager, "notify::session-name", + G_CALLBACK (if_session_condition_cb), app); + g_free (session_name); + } else if (kind == GSM_CONDITION_UNLESS_SESSION) { + GsmManager *manager; + char *session_name; + + /* get the singleton */ + manager = gsm_manager_get (); + + g_object_get (manager, "session-name", &session_name, NULL); + disabled = strcmp (session_name, key) == 0; + + g_signal_connect (manager, "notify::session-name", + G_CALLBACK (unless_session_condition_cb), app); + g_free (session_name); + } else { + disabled = TRUE; + } + + g_free (key); + + if (disabled) { + /* FIXME: cache the disabled value? */ + } +} + +static void +load_desktop_file (GsmAutostartApp *app) +{ + char *dbus_name; + char *startup_id; + char *phase_str; + int phase; + gboolean res; + + g_assert (app->priv->app_info != NULL); + + phase_str = g_desktop_app_info_get_string (app->priv->app_info, + GSM_AUTOSTART_APP_PHASE_KEY); + if (phase_str != NULL) { + if (strcmp (phase_str, "EarlyInitialization") == 0) { + phase = GSM_MANAGER_PHASE_EARLY_INITIALIZATION; + } else if (strcmp (phase_str, "PreDisplayServer") == 0) { + phase = GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER; + } else if (strcmp (phase_str, "DisplayServer") == 0) { + phase = GSM_MANAGER_PHASE_DISPLAY_SERVER; + } else if (strcmp (phase_str, "Initialization") == 0) { + phase = GSM_MANAGER_PHASE_INITIALIZATION; + } else if (strcmp (phase_str, "WindowManager") == 0) { + phase = GSM_MANAGER_PHASE_WINDOW_MANAGER; + } else if (strcmp (phase_str, "Panel") == 0) { + phase = GSM_MANAGER_PHASE_PANEL; + } else if (strcmp (phase_str, "Desktop") == 0) { + phase = GSM_MANAGER_PHASE_DESKTOP; + } else { + phase = GSM_MANAGER_PHASE_APPLICATION; + } + + g_free (phase_str); + } else { + phase = GSM_MANAGER_PHASE_APPLICATION; + } + + dbus_name = g_desktop_app_info_get_string (app->priv->app_info, + GSM_AUTOSTART_APP_DBUS_NAME_KEY); + if (dbus_name != NULL) { + app->priv->launch_type = AUTOSTART_LAUNCH_ACTIVATE; + } else { + app->priv->launch_type = AUTOSTART_LAUNCH_SPAWN; + } + + /* this must only be done on first load */ + switch (app->priv->launch_type) { + case AUTOSTART_LAUNCH_SPAWN: + startup_id = + g_desktop_app_info_get_string (app->priv->app_info, + GSM_AUTOSTART_APP_STARTUP_ID_KEY); + + if (startup_id == NULL) { + startup_id = gsm_util_generate_startup_id (); + } + break; + case AUTOSTART_LAUNCH_ACTIVATE: + startup_id = g_strdup (dbus_name); + break; + default: + g_assert_not_reached (); + } + + res = g_desktop_app_info_has_key (app->priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + if (res) { + app->priv->autorestart = g_desktop_app_info_get_boolean (app->priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + } else { + app->priv->autorestart = FALSE; + } + + g_free (app->priv->condition_string); + app->priv->condition_string = g_desktop_app_info_get_string (app->priv->app_info, + "AutostartCondition"); + setup_condition_monitor (app); + + g_object_set (app, + "phase", phase, + "startup-id", startup_id, + NULL); + + g_free (startup_id); + g_free (dbus_name); +} + +static void +gsm_autostart_app_set_desktop_filename (GsmAutostartApp *app, + const char *desktop_filename) +{ + if (app->priv->app_info != NULL) { + g_clear_object (&app->priv->app_info); + g_clear_pointer (&app->priv->desktop_filename, g_free); + g_clear_pointer (&app->priv->desktop_id, g_free); + } + + if (desktop_filename == NULL) { + return; + } + + app->priv->desktop_filename = g_strdup (desktop_filename); + app->priv->desktop_id = g_path_get_basename (desktop_filename); +} + +static void +gsm_autostart_app_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmAutostartApp *self; + + self = GSM_AUTOSTART_APP (object); + + switch (prop_id) { + case PROP_DESKTOP_FILENAME: + gsm_autostart_app_set_desktop_filename (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_autostart_app_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmAutostartApp *self; + + self = GSM_AUTOSTART_APP (object); + + switch (prop_id) { + case PROP_DESKTOP_FILENAME: + if (self->priv->app_info != NULL) { + g_value_set_string (value, g_desktop_app_info_get_filename (self->priv->app_info)); + } else { + g_value_set_string (value, NULL); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_autostart_app_dispose (GObject *object) +{ + GsmAutostartAppPrivate *priv; + + priv = GSM_AUTOSTART_APP (object)->priv; + + g_clear_pointer (&priv->startup_id, g_free); + + if (priv->session_provides) { + g_slist_free_full (priv->session_provides, g_free); + priv->session_provides = NULL; + } + + g_clear_pointer (&priv->condition_string, g_free); + g_clear_object (&priv->condition_settings); + g_clear_object (&priv->app_info); + g_clear_pointer (&priv->desktop_filename, g_free); + g_clear_pointer (&priv->desktop_id, g_free); + + if (priv->child_watch_id > 0) { + g_source_remove (priv->child_watch_id); + priv->child_watch_id = 0; + } + + if (priv->condition_monitor) { + g_file_monitor_cancel (priv->condition_monitor); + } + + G_OBJECT_CLASS (gsm_autostart_app_parent_class)->dispose (object); +} + +static gboolean +is_running (GsmApp *app) +{ + GsmAutostartAppPrivate *priv; + gboolean is; + + priv = GSM_AUTOSTART_APP (app)->priv; + + /* is running if pid is still valid or + * or a client is connected + */ + /* FIXME: check client */ + is = (priv->pid != -1); + + return is; +} + +static gboolean +is_conditionally_disabled (GsmApp *app) +{ + GsmAutostartAppPrivate *priv; + gboolean res; + gboolean disabled; + char *key; + guint kind; + + priv = GSM_AUTOSTART_APP (app)->priv; + + /* Check AutostartCondition */ + if (priv->condition_string == NULL) { + return FALSE; + } + + key = NULL; + res = parse_condition_string (priv->condition_string, &kind, &key); + if (! res) { + g_free (key); + return TRUE; + } + + if (key == NULL) { + return TRUE; + } + + if (kind == GSM_CONDITION_IF_EXISTS) { + char *file_path; + + file_path = resolve_conditional_file_path (key); + disabled = !g_file_test (file_path, G_FILE_TEST_EXISTS); + g_free (file_path); + } else if (kind == GSM_CONDITION_UNLESS_EXISTS) { + char *file_path; + + file_path = resolve_conditional_file_path (key); + disabled = g_file_test (file_path, G_FILE_TEST_EXISTS); + g_free (file_path); + } else if (kind == GSM_CONDITION_GSETTINGS && + priv->condition_settings != NULL) { + char **elems; + elems = g_strsplit (key, " ", 2); + disabled = !g_settings_get_boolean (priv->condition_settings, elems[1]); + g_strfreev (elems); + } else if (kind == GSM_CONDITION_IF_SESSION) { + GsmManager *manager; + char *session_name; + + /* get the singleton */ + manager = gsm_manager_get (); + + g_object_get (manager, "session-name", &session_name, NULL); + disabled = strcmp (session_name, key) != 0; + g_free (session_name); + } else if (kind == GSM_CONDITION_UNLESS_SESSION) { + GsmManager *manager; + char *session_name; + + /* get the singleton */ + manager = gsm_manager_get (); + + g_object_get (manager, "session-name", &session_name, NULL); + disabled = strcmp (session_name, key) == 0; + g_free (session_name); + } else { + disabled = TRUE; + } + + /* Set initial condition */ + priv->condition = !disabled; + + g_free (key); + + return disabled; +} + +static void +app_exited (GPid pid, + int status, + GsmAutostartApp *app) +{ + g_debug ("GsmAutostartApp: (pid:%d) done (%s:%d)", + (int) pid, + WIFEXITED (status) ? "status" + : WIFSIGNALED (status) ? "signal" + : "unknown", + WIFEXITED (status) ? WEXITSTATUS (status) + : WIFSIGNALED (status) ? WTERMSIG (status) + : -1); + + g_spawn_close_pid (app->priv->pid); + app->priv->pid = -1; + app->priv->child_watch_id = 0; + + if (WIFEXITED (status)) { + gsm_app_exited (GSM_APP (app), WEXITSTATUS (status)); + } else if (WIFSIGNALED (status)) { + gsm_app_died (GSM_APP (app), WTERMSIG (status)); + } +} + +static int +_signal_pid (int pid, + int signal) +{ + int status; + + /* perhaps block sigchld */ + g_debug ("GsmAutostartApp: sending signal %d to process %d", signal, pid); + errno = 0; + status = kill (pid, signal); + + if (status < 0) { + if (errno == ESRCH) { + g_warning ("Child process %d was already dead.", + (int)pid); + } else { + g_warning ("Couldn't kill child process %d: %s", + pid, + g_strerror (errno)); + } + } + + /* perhaps unblock sigchld */ + + return status; +} + +static gboolean +autostart_app_stop_spawn (GsmAutostartApp *app, + GError **error) +{ + int res; + + if (app->priv->pid < 1) { + g_set_error (error, + GSM_APP_ERROR, + GSM_APP_ERROR_STOP, + "Not running"); + return FALSE; + } + + res = _signal_pid (app->priv->pid, SIGTERM); + if (res != 0) { + g_set_error (error, + GSM_APP_ERROR, + GSM_APP_ERROR_STOP, + "Unable to stop: %s", + g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +static gboolean +autostart_app_stop_activate (GsmAutostartApp *app, + GError **error) +{ + return TRUE; +} + +static gboolean +gsm_autostart_app_stop (GsmApp *app, + GError **error) +{ + GsmAutostartApp *aapp; + gboolean ret; + + aapp = GSM_AUTOSTART_APP (app); + + g_return_val_if_fail (aapp->priv->app_info != NULL, FALSE); + + switch (aapp->priv->launch_type) { + case AUTOSTART_LAUNCH_SPAWN: + ret = autostart_app_stop_spawn (aapp, error); + break; + case AUTOSTART_LAUNCH_ACTIVATE: + ret = autostart_app_stop_activate (aapp, error); + break; + default: + g_assert_not_reached (); + break; + } + + return ret; +} + +static void +app_launched (GAppLaunchContext *ctx, + GAppInfo *appinfo, + GVariant *platform_data, + gpointer data) +{ + GsmAutostartApp *app = data; + gint pid; + gchar *sn_id; + + pid = 0; + sn_id = NULL; + + g_variant_lookup (platform_data, "pid", "i", &pid); + g_variant_lookup (platform_data, "startup-notification-id", "s", &sn_id); + app->priv->pid = pid; + app->priv->startup_id = sn_id; +} + +#ifdef ENABLE_SYSTEMD_JOURNAL +static void +on_child_setup (GsmAutostartApp *app) +{ + int standard_output, standard_error; + + /* The FALSE means programs aren't expected to prefix each + * line with prefix to specify priority. + */ + standard_output = sd_journal_stream_fd (app->priv->desktop_id, + LOG_INFO, + FALSE); + standard_error = sd_journal_stream_fd (app->priv->desktop_id, + LOG_WARNING, + FALSE); + + if (standard_output >= 0) { + dup2 (standard_output, STDOUT_FILENO); + close (standard_output); + } + + if (standard_error >= 0) { + dup2 (standard_error, STDERR_FILENO); + close (standard_error); + } +} +#endif + +static gboolean +autostart_app_start_spawn (GsmAutostartApp *app, + GError **error) +{ + gboolean success; + GError *local_error; + const char *startup_id; + const char * const *child_environment; + int i; + GAppLaunchContext *ctx; + GSpawnChildSetupFunc child_setup_func = NULL; + gpointer child_setup_data = NULL; + guint handler; + + startup_id = gsm_app_peek_startup_id (GSM_APP (app)); + g_assert (startup_id != NULL); + + g_debug ("GsmAutostartApp: starting %s: command=%s startup-id=%s", app->priv->desktop_id, g_app_info_get_commandline (G_APP_INFO (app->priv->app_info)), startup_id); + + g_free (app->priv->startup_id); + local_error = NULL; + ctx = g_app_launch_context_new (); + + child_environment = gsm_util_listenv (); + for (i = 0; child_environment[i] != NULL; i++) { + char **environment_tuple; + const char *key; + const char *value; + + environment_tuple = g_strsplit (child_environment[i], "=", 2); + key = environment_tuple[0]; + value = environment_tuple[1]; + + if (value != NULL) + g_app_launch_context_setenv (ctx, key, value); + + g_strfreev (environment_tuple); + } + + if (startup_id != NULL) { + g_app_launch_context_setenv (ctx, "DESKTOP_AUTOSTART_ID", startup_id); + } + +#ifdef ENABLE_SYSTEMD_JOURNAL + if (sd_booted () > 0) { + child_setup_func = (GSpawnChildSetupFunc) on_child_setup; + child_setup_data = app; + } +#endif + + handler = g_signal_connect (ctx, "launched", G_CALLBACK (app_launched), app); + success = g_desktop_app_info_launch_uris_as_manager (app->priv->app_info, + NULL, + ctx, + G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, + child_setup_func, child_setup_data, + NULL, NULL, + &local_error); + g_signal_handler_disconnect (ctx, handler); + + if (success) { + g_debug ("GsmAutostartApp: started pid:%d", app->priv->pid); + app->priv->child_watch_id = g_child_watch_add (app->priv->pid, + (GChildWatchFunc)app_exited, + app); + } else { + g_set_error (error, + GSM_APP_ERROR, + GSM_APP_ERROR_START, + "Unable to start application: %s", local_error->message); + g_error_free (local_error); + } + + return success; +} + +static void +start_notify (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GError *error; + GsmAutostartApp *app; + + app = user_data; + error = NULL; + + g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error); + + if (error != NULL) { + g_warning ("GsmAutostartApp: Error starting application: %s", error->message); + g_error_free (error); + } else { + g_debug ("GsmAutostartApp: Started application %s", app->priv->desktop_id); + } +} + +static gboolean +autostart_app_start_activate (GsmAutostartApp *app, + GError **error) +{ + const char *name; + char *path; + char *arguments; + GDBusConnection *bus; + GError *local_error; + + local_error = NULL; + bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error); + if (local_error != NULL) { + g_warning ("error getting session bus: %s", local_error->message); + g_propagate_error (error, local_error); + return FALSE; + } + + name = gsm_app_peek_startup_id (GSM_APP (app)); + g_assert (name != NULL); + + path = g_desktop_app_info_get_string (app->priv->app_info, + GSM_AUTOSTART_APP_DBUS_PATH_KEY); + if (path == NULL) { + /* just pick one? */ + path = g_strdup ("/"); + } + + arguments = g_desktop_app_info_get_string (app->priv->app_info, + GSM_AUTOSTART_APP_DBUS_ARGS_KEY); + + g_dbus_connection_call (bus, + name, + path, + GSM_SESSION_CLIENT_DBUS_INTERFACE, + "Start", + g_variant_new ("(s)", arguments), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, + start_notify, app); + g_object_unref (bus); + + return TRUE; +} + +static gboolean +gsm_autostart_app_start (GsmApp *app, + GError **error) +{ + GsmAutostartApp *aapp; + gboolean ret; + + aapp = GSM_AUTOSTART_APP (app); + + g_return_val_if_fail (aapp->priv->app_info != NULL, FALSE); + + switch (aapp->priv->launch_type) { + case AUTOSTART_LAUNCH_SPAWN: + ret = autostart_app_start_spawn (aapp, error); + break; + case AUTOSTART_LAUNCH_ACTIVATE: + ret = autostart_app_start_activate (aapp, error); + break; + default: + g_assert_not_reached (); + break; + } + + return ret; +} + +static gboolean +gsm_autostart_app_restart (GsmApp *app, + GError **error) +{ + GError *local_error; + gboolean res; + + /* ignore stop errors - it is fine if it is already stopped */ + local_error = NULL; + res = gsm_app_stop (app, &local_error); + if (! res) { + g_debug ("GsmAutostartApp: Couldn't stop app: %s", local_error->message); + g_error_free (local_error); + } + + res = gsm_app_start (app, &local_error); + if (! res) { + g_propagate_error (error, local_error); + return FALSE; + } + + return TRUE; +} + +static gboolean +gsm_autostart_app_provides (GsmApp *app, + const char *service) +{ + gchar *provides_str; + char **provides; + gsize i; + GSList *l; + GsmAutostartApp *aapp; + + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + + aapp = GSM_AUTOSTART_APP (app); + + if (aapp->priv->app_info == NULL) { + return FALSE; + } + + for (l = aapp->priv->session_provides; l != NULL; l = l->next) { + if (!strcmp (l->data, service)) + return TRUE; + } + + provides_str = g_desktop_app_info_get_string (aapp->priv->app_info, + GSM_AUTOSTART_APP_PROVIDES_KEY); + if (!provides_str) { + return FALSE; + } + provides = g_strsplit (provides_str, ";", -1); + g_free (provides_str); + + for (i = 0; provides[i]; i++) { + if (!strcmp (provides[i], service)) { + g_strfreev (provides); + return TRUE; + } + } + + g_strfreev (provides); + + return FALSE; +} + +static char ** +gsm_autostart_app_get_provides (GsmApp *app) +{ + GsmAutostartApp *aapp; + gchar *provides_str; + char **provides; + gsize provides_len; + char **result; + gsize result_len; + int i; + GSList *l; + + g_return_val_if_fail (GSM_IS_APP (app), NULL); + + aapp = GSM_AUTOSTART_APP (app); + + if (aapp->priv->app_info == NULL) { + return NULL; + } + + provides_str = g_desktop_app_info_get_string (aapp->priv->app_info, + GSM_AUTOSTART_APP_PROVIDES_KEY); + + if (provides_str == NULL) { + return NULL; + } + + provides = g_strsplit (provides_str, ";", -1); + provides_len = g_strv_length (provides); + g_free (provides_str); + + if (!aapp->priv->session_provides) { + return provides; + } + + result_len = provides_len + g_slist_length (aapp->priv->session_provides); + result = g_new (char *, result_len + 1); /* including last NULL */ + + for (i = 0; provides[i] != NULL; i++) + result[i] = provides[i]; + g_free (provides); + + for (l = aapp->priv->session_provides; l != NULL; l = l->next, i++) + result[i] = g_strdup (l->data); + + result[i] = NULL; + + g_assert (i == result_len); + + return result; +} + +void +gsm_autostart_app_add_provides (GsmAutostartApp *aapp, + const char *provides) +{ + g_return_if_fail (GSM_IS_AUTOSTART_APP (aapp)); + + aapp->priv->session_provides = g_slist_prepend (aapp->priv->session_provides, + g_strdup (provides)); +} + +static gboolean +gsm_autostart_app_has_autostart_condition (GsmApp *app, + const char *condition) +{ + GsmAutostartApp *aapp; + + g_return_val_if_fail (GSM_IS_APP (app), FALSE); + g_return_val_if_fail (condition != NULL, FALSE); + + aapp = GSM_AUTOSTART_APP (app); + + if (aapp->priv->condition_string == NULL) { + return FALSE; + } + + if (strcmp (aapp->priv->condition_string, condition) == 0) { + return TRUE; + } + + return FALSE; +} + +static gboolean +gsm_autostart_app_get_autorestart (GsmApp *app) +{ + gboolean res; + gboolean autorestart; + + if (GSM_AUTOSTART_APP (app)->priv->app_info == NULL) { + return FALSE; + } + + autorestart = FALSE; + + res = g_desktop_app_info_has_key (GSM_AUTOSTART_APP (app)->priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + if (res) { + autorestart = g_desktop_app_info_get_boolean (GSM_AUTOSTART_APP (app)->priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + } + + return autorestart; +} + +static const char * +gsm_autostart_app_get_app_id (GsmApp *app) +{ + if (GSM_AUTOSTART_APP (app)->priv->app_info == NULL) { + return NULL; + } + + return g_app_info_get_id (G_APP_INFO (GSM_AUTOSTART_APP (app)->priv->app_info)); +} + +static gboolean +gsm_autostart_app_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + GsmAutostartApp *app = GSM_AUTOSTART_APP (initable); + + g_assert (app->priv->desktop_filename != NULL); + app->priv->app_info = g_desktop_app_info_new_from_filename (app->priv->desktop_filename); + if (app->priv->app_info == NULL) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not parse desktop file %s or it references a not found TryExec binary", app->priv->desktop_id); + return FALSE; + } + + load_desktop_file (app); + + return TRUE; +} + +static gboolean +gsm_autostart_app_save_to_keyfile (GsmApp *base_app, + GKeyFile *keyfile, + GError **error) +{ + GsmAutostartApp *app = GSM_AUTOSTART_APP (base_app); + char **provides = NULL; + char *dbus_name; + char *phase; + gboolean res; + + provides = gsm_app_get_provides (base_app); + if (provides != NULL) { + g_key_file_set_string_list (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_PROVIDES_KEY, + (const char * const *) + provides, + g_strv_length (provides)); + g_strfreev (provides); + } + + phase = g_desktop_app_info_get_string (app->priv->app_info, + GSM_AUTOSTART_APP_PHASE_KEY); + if (phase != NULL) { + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_PHASE_KEY, + phase); + g_free (phase); + } + + dbus_name = g_desktop_app_info_get_string (app->priv->app_info, + GSM_AUTOSTART_APP_DBUS_NAME_KEY); + if (dbus_name != NULL) { + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_DBUS_NAME_KEY, + dbus_name); + g_free (dbus_name); + } + + res = g_desktop_app_info_has_key (app->priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + if (res) { + g_key_file_set_boolean (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + GSM_AUTOSTART_APP_AUTORESTART_KEY, + g_desktop_app_info_get_boolean (app->priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY)); + } + + res = g_desktop_app_info_has_key (app->priv->app_info, + GSM_AUTOSTART_APP_AUTORESTART_KEY); + if (res) { + char *autostart_condition; + + autostart_condition = g_desktop_app_info_get_string (app->priv->app_info, "AutostartCondition"); + + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + "AutostartCondition", + autostart_condition); + g_free (autostart_condition); + } + + return TRUE; +} + +static void +gsm_autostart_app_initable_iface_init (GInitableIface *iface) +{ + iface->init = gsm_autostart_app_initable_init; +} + +static void +gsm_autostart_app_class_init (GsmAutostartAppClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GsmAppClass *app_class = GSM_APP_CLASS (klass); + + object_class->set_property = gsm_autostart_app_set_property; + object_class->get_property = gsm_autostart_app_get_property; + object_class->dispose = gsm_autostart_app_dispose; + + app_class->impl_is_disabled = is_disabled; + app_class->impl_is_conditionally_disabled = is_conditionally_disabled; + app_class->impl_is_running = is_running; + app_class->impl_start = gsm_autostart_app_start; + app_class->impl_restart = gsm_autostart_app_restart; + app_class->impl_stop = gsm_autostart_app_stop; + app_class->impl_provides = gsm_autostart_app_provides; + app_class->impl_get_provides = gsm_autostart_app_get_provides; + app_class->impl_has_autostart_condition = gsm_autostart_app_has_autostart_condition; + app_class->impl_get_app_id = gsm_autostart_app_get_app_id; + app_class->impl_get_autorestart = gsm_autostart_app_get_autorestart; + app_class->impl_save_to_keyfile = gsm_autostart_app_save_to_keyfile; + + g_object_class_install_property (object_class, + PROP_DESKTOP_FILENAME, + g_param_spec_string ("desktop-filename", + "Desktop filename", + "Freedesktop .desktop file", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + signals[CONDITION_CHANGED] = + g_signal_new ("condition-changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmAutostartAppClass, condition_changed), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + G_TYPE_BOOLEAN); + + g_type_class_add_private (object_class, sizeof (GsmAutostartAppPrivate)); +} + +GsmApp * +gsm_autostart_app_new (const char *desktop_file, + GError **error) +{ + return (GsmApp*) g_initable_new (GSM_TYPE_AUTOSTART_APP, NULL, error, + "desktop-filename", desktop_file, + NULL); +} diff -Nru gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-autostart-app.h gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-autostart-app.h --- gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-autostart-app.h 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-autostart-app.h 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,76 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * 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, see . + */ + +#ifndef __GSM_AUTOSTART_APP_H__ +#define __GSM_AUTOSTART_APP_H__ + +#include "gsm-app.h" + +#include + +G_BEGIN_DECLS + +#define GSM_TYPE_AUTOSTART_APP (gsm_autostart_app_get_type ()) +#define GSM_AUTOSTART_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_AUTOSTART_APP, GsmAutostartApp)) +#define GSM_AUTOSTART_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_AUTOSTART_APP, GsmAutostartAppClass)) +#define GSM_IS_AUTOSTART_APP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_AUTOSTART_APP)) +#define GSM_IS_AUTOSTART_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_AUTOSTART_APP)) +#define GSM_AUTOSTART_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_AUTOSTART_APP, GsmAutostartAppClass)) + +typedef struct _GsmAutostartApp GsmAutostartApp; +typedef struct _GsmAutostartAppClass GsmAutostartAppClass; +typedef struct _GsmAutostartAppPrivate GsmAutostartAppPrivate; + +struct _GsmAutostartApp +{ + GsmApp parent; + + GsmAutostartAppPrivate *priv; +}; + +struct _GsmAutostartAppClass +{ + GsmAppClass parent_class; + + /* signals */ + void (*condition_changed) (GsmApp *app, + gboolean condition); +}; + +GType gsm_autostart_app_get_type (void) G_GNUC_CONST; + +GsmApp *gsm_autostart_app_new (const char *desktop_file, + GError **error); + +void gsm_autostart_app_add_provides (GsmAutostartApp *aapp, + const char *provides); + +#define GSM_AUTOSTART_APP_ENABLED_KEY "X-GNOME-Autostart-enabled" +#define GSM_AUTOSTART_APP_PHASE_KEY "X-GNOME-Autostart-Phase" +#define GSM_AUTOSTART_APP_PROVIDES_KEY "X-GNOME-Provides" +#define GSM_AUTOSTART_APP_STARTUP_ID_KEY "X-GNOME-Autostart-startup-id" +#define GSM_AUTOSTART_APP_AUTORESTART_KEY "X-GNOME-AutoRestart" +#define GSM_AUTOSTART_APP_DBUS_NAME_KEY "X-GNOME-DBus-Name" +#define GSM_AUTOSTART_APP_DBUS_PATH_KEY "X-GNOME-DBus-Path" +#define GSM_AUTOSTART_APP_DBUS_ARGS_KEY "X-GNOME-DBus-Start-Arguments" +#define GSM_AUTOSTART_APP_DISCARD_KEY "X-GNOME-Autostart-discard-exec" + +G_END_DECLS + +#endif /* __GSM_AUTOSTART_APP_H__ */ diff -Nru gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-manager.c gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-manager.c --- gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-manager.c 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/support_autostart_delay.patch/gnome-session/gsm-manager.c 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,3715 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * Copyright (C) 2008 William Jon McCann + * + * 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, see . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gsm-manager.h" +#include "org.gnome.SessionManager.h" + +#ifdef ENABLE_SYSTEMD_JOURNAL +#include +#endif + +#include "gsm-store.h" +#include "gsm-inhibitor.h" +#include "gsm-presence.h" +#include "gsm-shell.h" + +#include "gsm-xsmp-server.h" +#include "gsm-xsmp-client.h" +#include "gsm-dbus-client.h" + +#include "gsm-autostart-app.h" + +#include "gsm-util.h" +#include "gsm-icon-names.h" +#include "gsm-system.h" +#include "gsm-session-save.h" +#include "gsm-shell-extensions.h" +#include "gsm-fail-whale.h" + +#define GSM_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_MANAGER, GsmManagerPrivate)) + +/* UUIDs for log messages */ +#define GSM_MANAGER_STARTUP_SUCCEEDED_MSGID "0ce153587afa4095832d233c17a88001" +#define GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID "10dd2dc188b54a5e98970f56499d1f73" + +#define GSM_MANAGER_DBUS_PATH "/org/gnome/SessionManager" +#define GSM_MANAGER_DBUS_NAME "org.gnome.SessionManager" +#define GSM_MANAGER_DBUS_IFACE "org.gnome.SessionManager" + +/* Probably about the longest amount of time someone could reasonably + * want to wait, at least for something happening more than once. + * We can get deployed on very slow media though like CDROM devices, + * often with complex stacking/compressing filesystems on top, which + * is not a recipie for speed. Particularly now that we throw up + * a fail whale if required components don't show up quickly enough, + * let's make this fairly long. + */ +#define GSM_MANAGER_PHASE_TIMEOUT 90 /* seconds */ + +#define GDM_FLEXISERVER_COMMAND "gdmflexiserver" +#define GDM_FLEXISERVER_ARGS "--startnew Standard" + +#define SESSION_SCHEMA "org.gnome.desktop.session" +#define KEY_IDLE_DELAY "idle-delay" +#define KEY_SESSION_NAME "session-name" + +#define GSM_MANAGER_SCHEMA "org.gnome.SessionManager" +#define KEY_AUTOSAVE "auto-save-session" +#define KEY_AUTOSAVE_ONE_SHOT "auto-save-session-one-shot" +#define KEY_LOGOUT_PROMPT "logout-prompt" +#define KEY_SHOW_FALLBACK_WARNING "show-fallback-warning" + +#define SCREENSAVER_SCHEMA "org.gnome.desktop.screensaver" +#define KEY_SLEEP_LOCK "lock-enabled" + +#define LOCKDOWN_SCHEMA "org.gnome.desktop.lockdown" +#define KEY_DISABLE_LOG_OUT "disable-log-out" +#define KEY_DISABLE_USER_SWITCHING "disable-user-switching" + +static void app_registered (GsmApp *app, GParamSpec *spec, GsmManager *manager); + +typedef enum +{ + GSM_MANAGER_LOGOUT_NONE, + GSM_MANAGER_LOGOUT_LOGOUT, + GSM_MANAGER_LOGOUT_REBOOT, + GSM_MANAGER_LOGOUT_REBOOT_INTERACT, + GSM_MANAGER_LOGOUT_SHUTDOWN, + GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT, +} GsmManagerLogoutType; + +struct GsmManagerPrivate +{ + gboolean failsafe; + GsmStore *clients; + GsmStore *inhibitors; + GsmInhibitorFlag inhibited_actions; + GsmStore *apps; + GsmPresence *presence; + GsmXsmpServer *xsmp_server; + + char *session_name; + gboolean is_fallback_session : 1; + + /* Current status */ + GsmManagerPhase phase; + guint phase_timeout_id; + GSList *required_apps; + GSList *pending_apps; + GsmManagerLogoutMode logout_mode; + GSList *query_clients; + guint query_timeout_id; + /* This is used for GSM_MANAGER_PHASE_END_SESSION only at the moment, + * since it uses a sublist of all running client that replied in a + * specific way */ + GSList *next_query_clients; + /* This is the action that will be done just before we exit */ + GsmManagerLogoutType logout_type; + + /* List of clients which were disconnected due to disabled condition + * and shouldn't be automatically restarted */ + GSList *condition_clients; + + GSList *pending_end_session_tasks; + GCancellable *end_session_cancellable; + + GSettings *settings; + GSettings *session_settings; + GSettings *screensaver_settings; + GSettings *lockdown_settings; + + GsmSystem *system; + GDBusConnection *connection; + GsmExportedManager *skeleton; + gboolean dbus_disconnected : 1; + + GsmShell *shell; + guint shell_end_session_dialog_canceled_id; + guint shell_end_session_dialog_open_failed_id; + guint shell_end_session_dialog_confirmed_logout_id; + guint shell_end_session_dialog_confirmed_shutdown_id; + guint shell_end_session_dialog_confirmed_reboot_id; +}; + +enum { + PROP_0, + PROP_CLIENT_STORE, + PROP_SESSION_NAME, + PROP_FALLBACK, + PROP_FAILSAFE +}; + +enum { + PHASE_CHANGED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0 }; + +static void gsm_manager_class_init (GsmManagerClass *klass); +static void gsm_manager_init (GsmManager *manager); + +static gboolean auto_save_is_enabled (GsmManager *manager); +static void maybe_save_session (GsmManager *manager); + +static gboolean _log_out_is_locked_down (GsmManager *manager); + +static void _handle_client_end_session_response (GsmManager *manager, + GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason); +static void show_shell_end_session_dialog (GsmManager *manager, + GsmShellEndSessionDialogType type); +static gpointer manager_object = NULL; + +G_DEFINE_TYPE (GsmManager, gsm_manager, G_TYPE_OBJECT) + +static const GDBusErrorEntry gsm_manager_error_entries[] = { + { GSM_MANAGER_ERROR_GENERAL, GSM_MANAGER_DBUS_IFACE ".GeneralError" }, + { GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, GSM_MANAGER_DBUS_IFACE ".NotInInitialization" }, + { GSM_MANAGER_ERROR_NOT_IN_RUNNING, GSM_MANAGER_DBUS_IFACE ".NotInRunning" }, + { GSM_MANAGER_ERROR_ALREADY_REGISTERED, GSM_MANAGER_DBUS_IFACE ".AlreadyRegistered" }, + { GSM_MANAGER_ERROR_NOT_REGISTERED, GSM_MANAGER_DBUS_IFACE ".NotRegistered" }, + { GSM_MANAGER_ERROR_INVALID_OPTION, GSM_MANAGER_DBUS_IFACE ".InvalidOption" }, + { GSM_MANAGER_ERROR_LOCKED_DOWN, GSM_MANAGER_DBUS_IFACE ".LockedDown" } +}; + +GQuark +gsm_manager_error_quark (void) +{ + static volatile gsize quark_volatile = 0; + + g_dbus_error_register_error_domain ("gsm_manager_error", + &quark_volatile, + gsm_manager_error_entries, + G_N_ELEMENTS (gsm_manager_error_entries)); + return quark_volatile; +} + +static gboolean +start_app_or_warn (GsmManager *manager, + GsmApp *app) +{ + gboolean res; + GError *error = NULL; + + g_debug ("GsmManager: starting app '%s'", gsm_app_peek_id (app)); + + res = gsm_app_start (app, &error); + if (error != NULL) { + g_warning ("Failed to start app: %s", error->message); + g_clear_error (&error); + } + return res; +} + +static gboolean +is_app_required (GsmManager *manager, + GsmApp *app) +{ + return g_slist_find (manager->priv->required_apps, app) != NULL; +} + +static void +on_required_app_failure (GsmManager *manager, + GsmApp *app) +{ + const gchar *app_id; + gboolean allow_logout; + GsmShellExtensions *extensions; + + app_id = gsm_app_peek_app_id (app); + + if (g_str_equal (app_id, "org.gnome.Shell")) { + extensions = g_object_new (GSM_TYPE_SHELL_EXTENSIONS, NULL); + gsm_shell_extensions_disable_all (extensions); + } else { + extensions = NULL; + } + + if (gsm_system_is_login_session (manager->priv->system)) { + allow_logout = FALSE; + } else { + allow_logout = !_log_out_is_locked_down (manager); + } + +#ifdef ENABLE_SYSTEMD_JOURNAL + sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID, + "PRIORITY=%d", 3, + "MESSAGE=Unrecoverable failure in required component %s", app_id, + NULL); +#endif + + gsm_fail_whale_dialog_we_failed (FALSE, + allow_logout, + extensions); +} + +static void +on_display_server_failure (GsmManager *manager, + GsmApp *app) +{ + const gchar *app_id; + GsmShellExtensions *extensions; + + app_id = gsm_app_peek_app_id (app); + + if (g_str_equal (app_id, "org.gnome.Shell")) { + extensions = g_object_new (GSM_TYPE_SHELL_EXTENSIONS, NULL); + gsm_shell_extensions_disable_all (extensions); + + g_object_unref (extensions); + } else { + extensions = NULL; + } + +#ifdef ENABLE_SYSTEMD_JOURNAL + sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_UNRECOVERABLE_FAILURE_MSGID, + "PRIORITY=%d", 3, + "MESSAGE=Unrecoverable failure in required component %s", app_id, + NULL); +#endif + + gsm_quit (); +} + +static gboolean +_debug_client (const char *id, + GsmClient *client, + GsmManager *manager) +{ + g_debug ("GsmManager: Client %s", gsm_client_peek_id (client)); + return FALSE; +} + +static void +debug_clients (GsmManager *manager) +{ + gsm_store_foreach (manager->priv->clients, + (GsmStoreFunc)_debug_client, + manager); +} + +static gboolean +_find_by_cookie (const char *id, + GsmInhibitor *inhibitor, + guint *cookie_ap) +{ + guint cookie_b; + + cookie_b = gsm_inhibitor_peek_cookie (inhibitor); + + return (*cookie_ap == cookie_b); +} + +static gboolean +_client_has_startup_id (const char *id, + GsmClient *client, + const char *startup_id_a) +{ + const char *startup_id_b; + + startup_id_b = gsm_client_peek_startup_id (client); + if (IS_STRING_EMPTY (startup_id_b)) { + return FALSE; + } + + return (strcmp (startup_id_a, startup_id_b) == 0); +} + +static void +app_condition_changed (GsmApp *app, + gboolean condition, + GsmManager *manager) +{ + GsmClient *client; + + g_debug ("GsmManager: app:%s condition changed condition:%d", + gsm_app_peek_id (app), + condition); + + client = (GsmClient *)gsm_store_find (manager->priv->clients, + (GsmStoreFunc)_client_has_startup_id, + (char *)gsm_app_peek_startup_id (app)); + + if (condition) { + if (!gsm_app_is_running (app) && client == NULL) { + start_app_or_warn (manager, app); + } else { + g_debug ("GsmManager: not starting - app still running '%s'", gsm_app_peek_id (app)); + } + } else { + GError *error; + gboolean res; + + if (client != NULL) { + /* Kill client in case condition if false and make sure it won't + * be automatically restarted by adding the client to + * condition_clients */ + manager->priv->condition_clients = + g_slist_prepend (manager->priv->condition_clients, client); + + g_debug ("GsmManager: stopping client %s for app", gsm_client_peek_id (client)); + + error = NULL; + res = gsm_client_stop (client, &error); + if (! res) { + g_warning ("Not able to stop app client from its condition: %s", + error->message); + g_error_free (error); + } + } else { + g_debug ("GsmManager: stopping app %s", gsm_app_peek_id (app)); + + /* If we don't have a client then we should try to kill the app */ + error = NULL; + res = gsm_app_stop (app, &error); + if (! res) { + g_warning ("Not able to stop app from its condition: %s", + error->message); + g_error_free (error); + } + } + } +} + +static const char * +phase_num_to_name (guint phase) +{ + const char *name; + + switch (phase) { + case GSM_MANAGER_PHASE_STARTUP: + name = "STARTUP"; + break; + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + name = "EARLY_INITIALIZATION"; + break; + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + name = "PRE_DISPLAY_SERVER"; + break; + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + name = "DISPLAY_SERVER"; + break; + case GSM_MANAGER_PHASE_INITIALIZATION: + name = "INITIALIZATION"; + break; + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + name = "WINDOW_MANAGER"; + break; + case GSM_MANAGER_PHASE_PANEL: + name = "PANEL"; + break; + case GSM_MANAGER_PHASE_DESKTOP: + name = "DESKTOP"; + break; + case GSM_MANAGER_PHASE_APPLICATION: + name = "APPLICATION"; + break; + case GSM_MANAGER_PHASE_RUNNING: + name = "RUNNING"; + break; + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + name = "QUERY_END_SESSION"; + break; + case GSM_MANAGER_PHASE_END_SESSION: + name = "END_SESSION"; + break; + case GSM_MANAGER_PHASE_EXIT: + name = "EXIT"; + break; + default: + g_assert_not_reached (); + break; + } + + return name; +} + +static void start_phase (GsmManager *manager); + +static void +gsm_manager_quit (GsmManager *manager) +{ + /* See the comment in request_reboot() for some more details about how + * this works. */ + + switch (manager->priv->logout_type) { + case GSM_MANAGER_LOGOUT_LOGOUT: + case GSM_MANAGER_LOGOUT_NONE: + gsm_quit (); + break; + case GSM_MANAGER_LOGOUT_REBOOT: + case GSM_MANAGER_LOGOUT_REBOOT_INTERACT: + gsm_system_complete_shutdown (manager->priv->system); + break; + case GSM_MANAGER_LOGOUT_SHUTDOWN: + case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT: + gsm_system_complete_shutdown (manager->priv->system); + break; + default: + g_assert_not_reached (); + break; + } +} + +static gboolean do_query_end_session_exit (GsmManager *manager); + +static void +end_phase (GsmManager *manager) +{ + gboolean start_next_phase = TRUE; + + g_debug ("GsmManager: ending phase %s", + phase_num_to_name (manager->priv->phase)); + + g_slist_free (manager->priv->pending_apps); + manager->priv->pending_apps = NULL; + + g_slist_free (manager->priv->query_clients); + manager->priv->query_clients = NULL; + + g_slist_free (manager->priv->next_query_clients); + manager->priv->next_query_clients = NULL; + + if (manager->priv->query_timeout_id > 0) { + g_source_remove (manager->priv->query_timeout_id); + manager->priv->query_timeout_id = 0; + } + if (manager->priv->phase_timeout_id > 0) { + g_source_remove (manager->priv->phase_timeout_id); + manager->priv->phase_timeout_id = 0; + } + + switch (manager->priv->phase) { + case GSM_MANAGER_PHASE_STARTUP: + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_INITIALIZATION: + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + case GSM_MANAGER_PHASE_PANEL: + case GSM_MANAGER_PHASE_DESKTOP: + case GSM_MANAGER_PHASE_APPLICATION: + break; + case GSM_MANAGER_PHASE_RUNNING: + if (_log_out_is_locked_down (manager)) { + g_warning ("Unable to logout: Logout has been locked down"); + start_next_phase = FALSE; + } + break; + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + if (!do_query_end_session_exit (manager)) + start_next_phase = FALSE; + break; + case GSM_MANAGER_PHASE_END_SESSION: + maybe_save_session (manager); + break; + case GSM_MANAGER_PHASE_EXIT: + start_next_phase = FALSE; + gsm_manager_quit (manager); + break; + default: + g_assert_not_reached (); + break; + } + + if (start_next_phase) { + manager->priv->phase++; + start_phase (manager); + } +} + +static void +app_event_during_startup (GsmManager *manager, + GsmApp *app) +{ + if (!(manager->priv->phase < GSM_MANAGER_PHASE_APPLICATION)) + return; + + manager->priv->pending_apps = g_slist_remove (manager->priv->pending_apps, app); + + if (manager->priv->pending_apps == NULL) { + if (manager->priv->phase_timeout_id > 0) { + g_source_remove (manager->priv->phase_timeout_id); + manager->priv->phase_timeout_id = 0; + } + + end_phase (manager); + } +} + +static gboolean +is_app_display_server (GsmManager *manager, + GsmApp *app) +{ + GsmManagerPhase phase; + + /* Apps can only really act as a display server if + * we're a wayland session. + */ + if (g_strcmp0 (g_getenv ("XDG_SESSION_TYPE"), "wayland") != 0) + return FALSE; + + phase = gsm_app_peek_phase (app); + + return (phase == GSM_MANAGER_PHASE_DISPLAY_SERVER && + is_app_required (manager, app)); +} + +static void +_restart_app (GsmManager *manager, + GsmApp *app) +{ + GError *error = NULL; + + if (is_app_display_server (manager, app)) { + on_display_server_failure (manager, app); + return; + } + + if (!gsm_app_restart (app, &error)) { + if (is_app_required (manager, app)) { + on_required_app_failure (manager, app); + } else { + g_warning ("Error on restarting session managed app: %s", error->message); + } + g_clear_error (&error); + + app_event_during_startup (manager, app); + } +} + +static void +app_died (GsmApp *app, + int signal, + GsmManager *manager) +{ + g_warning ("Application '%s' killed by signal %d", gsm_app_peek_app_id (app), signal); + + if (gsm_app_get_registered (app) && gsm_app_peek_autorestart (app)) { + g_debug ("Component '%s' is autorestart, ignoring died signal", + gsm_app_peek_app_id (app)); + return; + } + + _restart_app (manager, app); + + /* For now, we don't do anything with crashes from + * non-required apps after they hit the restart limit. + * + * Note that both required and not-required apps will be + * caught by ABRT/apport type infrastructure, and it'd be + * better to pick up the crash from there and do something + * un-intrusive about it generically. + */ +} + +static void +app_exited (GsmApp *app, + guchar exit_code, + GsmManager *manager) +{ + if (exit_code != 0) + g_warning ("App '%s' exited with code %d", gsm_app_peek_app_id (app), exit_code); + else + g_debug ("App %s exited successfully", gsm_app_peek_app_id (app)); + + /* Consider that non-success exit status means "crash" for required components */ + if (exit_code != 0 && is_app_required (manager, app)) { + if (gsm_app_get_registered (app) && gsm_app_peek_autorestart (app)) { + g_debug ("Component '%s' is autorestart, ignoring non-successful exit", + gsm_app_peek_app_id (app)); + return; + } + + _restart_app (manager, app); + } else { + app_event_during_startup (manager, app); + } +} + +static void +app_registered (GsmApp *app, + GParamSpec *spec, + GsmManager *manager) +{ + if (!gsm_app_get_registered (app)) { + return; + } + + g_debug ("App %s registered", gsm_app_peek_app_id (app)); + + app_event_during_startup (manager, app); +} + +static gboolean +on_phase_timeout (GsmManager *manager) +{ + GSList *a; + + manager->priv->phase_timeout_id = 0; + + switch (manager->priv->phase) { + case GSM_MANAGER_PHASE_STARTUP: + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_INITIALIZATION: + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + case GSM_MANAGER_PHASE_PANEL: + case GSM_MANAGER_PHASE_DESKTOP: + case GSM_MANAGER_PHASE_APPLICATION: + for (a = manager->priv->pending_apps; a; a = a->next) { + GsmApp *app = a->data; + g_warning ("Application '%s' failed to register before timeout", + gsm_app_peek_app_id (app)); + if (is_app_required (manager, app)) + on_required_app_failure (manager, app); + } + break; + case GSM_MANAGER_PHASE_RUNNING: + break; + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + case GSM_MANAGER_PHASE_END_SESSION: + break; + case GSM_MANAGER_PHASE_EXIT: + break; + default: + g_assert_not_reached (); + break; + } + + end_phase (manager); + + return FALSE; +} + +static gboolean +_start_app (const char *id, + GsmApp *app, + GsmManager *manager) +{ + if (gsm_app_peek_phase (app) != manager->priv->phase) { + goto out; + } + + /* Keep track of app autostart condition in order to react + * accordingly in the future. */ + g_signal_connect (app, + "condition-changed", + G_CALLBACK (app_condition_changed), + manager); + + if (gsm_app_peek_is_disabled (app) + || gsm_app_peek_is_conditionally_disabled (app)) { + g_debug ("GsmManager: Skipping disabled app: %s", id); + goto out; + } + + if (!start_app_or_warn (manager, app)) + goto out; + + if (manager->priv->phase < GSM_MANAGER_PHASE_APPLICATION) { + /* Historical note - apparently, + * e.g. gnome-settings-daemon used to "daemonize", and + * so gnome-session assumes process exit means "ok + * we're done". Of course this is broken, we don't + * even distinguish between exit code 0 versus not-0, + * nor do we have any metadata which tells us a + * process is going to "daemonize" or not (and + * basically nothing should be anyways). + */ + g_signal_connect (app, + "exited", + G_CALLBACK (app_exited), + manager); + g_signal_connect (app, + "notify::registered", + G_CALLBACK (app_registered), + manager); + g_signal_connect (app, + "died", + G_CALLBACK (app_died), + manager); + manager->priv->pending_apps = g_slist_prepend (manager->priv->pending_apps, app); + } + out: + return FALSE; +} + +static void +do_phase_startup (GsmManager *manager) +{ + gsm_store_foreach (manager->priv->apps, + (GsmStoreFunc)_start_app, + manager); + + if (manager->priv->pending_apps != NULL) { + if (manager->priv->phase < GSM_MANAGER_PHASE_APPLICATION) { + manager->priv->phase_timeout_id = g_timeout_add_seconds (GSM_MANAGER_PHASE_TIMEOUT, + (GSourceFunc)on_phase_timeout, + manager); + } + } else { + end_phase (manager); + } +} + +typedef struct { + GsmManager *manager; + guint flags; +} ClientEndSessionData; + + +static gboolean +_client_end_session (GsmClient *client, + ClientEndSessionData *data) +{ + gboolean ret; + GError *error; + + error = NULL; + ret = gsm_client_end_session (client, data->flags, &error); + if (! ret) { + g_warning ("Unable to query client: %s", error->message); + g_error_free (error); + /* FIXME: what should we do if we can't communicate with client? */ + } else { + g_debug ("GsmManager: adding client to end-session clients: %s", gsm_client_peek_id (client)); + data->manager->priv->query_clients = g_slist_prepend (data->manager->priv->query_clients, + client); + } + + return FALSE; +} + +static gboolean +_client_end_session_helper (const char *id, + GsmClient *client, + ClientEndSessionData *data) +{ + return _client_end_session (client, data); +} + +static void +complete_end_session_tasks (GsmManager *manager) +{ + GSList *l; + + for (l = manager->priv->pending_end_session_tasks; + l != NULL; + l = l->next) { + GTask *task = G_TASK (l->data); + if (!g_task_return_error_if_cancelled (task)) + g_task_return_boolean (task, TRUE); + } + + g_slist_free_full (manager->priv->pending_end_session_tasks, + (GDestroyNotify) g_object_unref); + manager->priv->pending_end_session_tasks = NULL; +} + +static void +do_phase_end_session (GsmManager *manager) +{ + ClientEndSessionData data; + + complete_end_session_tasks (manager); + + data.manager = manager; + data.flags = 0; + + if (manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL; + } + if (auto_save_is_enabled (manager)) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_SAVE; + } + + if (manager->priv->phase_timeout_id > 0) { + g_source_remove (manager->priv->phase_timeout_id); + manager->priv->phase_timeout_id = 0; + } + + if (gsm_store_size (manager->priv->clients) > 0) { + manager->priv->phase_timeout_id = g_timeout_add_seconds (GSM_MANAGER_PHASE_TIMEOUT, + (GSourceFunc)on_phase_timeout, + manager); + + gsm_store_foreach (manager->priv->clients, + (GsmStoreFunc)_client_end_session_helper, + &data); + } else { + end_phase (manager); + } +} + +static void +do_phase_end_session_part_2 (GsmManager *manager) +{ + ClientEndSessionData data; + + data.manager = manager; + data.flags = 0; + + if (manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL; + } + if (auto_save_is_enabled (manager)) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_SAVE; + } + data.flags |= GSM_CLIENT_END_SESSION_FLAG_LAST; + + /* keep the timeout that was started at the beginning of the + * GSM_MANAGER_PHASE_END_SESSION phase */ + + if (g_slist_length (manager->priv->next_query_clients) > 0) { + g_slist_foreach (manager->priv->next_query_clients, + (GFunc)_client_end_session, + &data); + + g_slist_free (manager->priv->next_query_clients); + manager->priv->next_query_clients = NULL; + } else { + end_phase (manager); + } +} + +static gboolean +_client_stop (const char *id, + GsmClient *client, + gpointer user_data) +{ + gboolean ret; + GError *error; + + error = NULL; + ret = gsm_client_stop (client, &error); + if (! ret) { + g_warning ("Unable to stop client: %s", error->message); + g_error_free (error); + /* FIXME: what should we do if we can't communicate with client? */ + } else { + g_debug ("GsmManager: stopped client: %s", gsm_client_peek_id (client)); + } + + return FALSE; +} + +#ifdef HAVE_SYSTEMD +static void +maybe_restart_user_bus (GsmManager *manager) +{ + GsmSystem *system; + g_autoptr(GVariant) reply = NULL; + g_autoptr(GError) error = NULL; + + if (manager->priv->dbus_disconnected) + return; + + system = gsm_get_system (); + + if (!gsm_system_is_last_session_for_user (system)) + return; + + reply = g_dbus_connection_call_sync (manager->priv->connection, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "TryRestartUnit", + g_variant_new ("(ss)", "dbus.service", "replace"), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (error != NULL) { + g_debug ("GsmManager: reloading user bus failed: %s", error->message); + } +} +#endif + +static void +do_phase_exit (GsmManager *manager) +{ + if (gsm_store_size (manager->priv->clients) > 0) { + gsm_store_foreach (manager->priv->clients, + (GsmStoreFunc)_client_stop, + NULL); + } + +#ifdef HAVE_SYSTEMD + maybe_restart_user_bus (manager); +#endif + + end_phase (manager); +} + +static gboolean +_client_query_end_session (const char *id, + GsmClient *client, + ClientEndSessionData *data) +{ + gboolean ret; + GError *error; + + error = NULL; + ret = gsm_client_query_end_session (client, data->flags, &error); + if (! ret) { + g_warning ("Unable to query client: %s", error->message); + g_error_free (error); + /* FIXME: what should we do if we can't communicate with client? */ + } else { + g_debug ("GsmManager: adding client to query clients: %s", gsm_client_peek_id (client)); + data->manager->priv->query_clients = g_slist_prepend (data->manager->priv->query_clients, + client); + } + + return FALSE; +} + +static gboolean +inhibitor_has_flag (gpointer key, + GsmInhibitor *inhibitor, + gpointer data) +{ + guint flag; + guint flags; + + flag = GPOINTER_TO_UINT (data); + + flags = gsm_inhibitor_peek_flags (inhibitor); + + return (flags & flag); +} + +static gboolean +gsm_manager_is_logout_inhibited (GsmManager *manager) +{ + GsmInhibitor *inhibitor; + + if (manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + return FALSE; + } + + if (manager->priv->inhibitors == NULL) { + return FALSE; + } + + inhibitor = (GsmInhibitor *)gsm_store_find (manager->priv->inhibitors, + (GsmStoreFunc)inhibitor_has_flag, + GUINT_TO_POINTER (GSM_INHIBITOR_FLAG_LOGOUT)); + if (inhibitor == NULL) { + return FALSE; + } + return TRUE; +} + +static gboolean +gsm_manager_is_idle_inhibited (GsmManager *manager) +{ + GsmInhibitor *inhibitor; + + if (manager->priv->inhibitors == NULL) { + return FALSE; + } + + inhibitor = (GsmInhibitor *)gsm_store_find (manager->priv->inhibitors, + (GsmStoreFunc)inhibitor_has_flag, + GUINT_TO_POINTER (GSM_INHIBITOR_FLAG_IDLE)); + if (inhibitor == NULL) { + return FALSE; + } + return TRUE; +} + +static gboolean +_client_cancel_end_session (const char *id, + GsmClient *client, + GsmManager *manager) +{ + gboolean res; + GError *error; + + error = NULL; + res = gsm_client_cancel_end_session (client, &error); + if (! res) { + g_warning ("Unable to cancel end session: %s", error->message); + g_error_free (error); + } + + return FALSE; +} + +static gboolean +inhibitor_is_jit (gpointer key, + GsmInhibitor *inhibitor, + GsmManager *manager) +{ + gboolean matches; + const char *id; + + id = gsm_inhibitor_peek_client_id (inhibitor); + + matches = (id != NULL && id[0] != '\0'); + + return matches; +} + +static void +cancel_end_session (GsmManager *manager) +{ + /* just ignore if received outside of shutdown */ + if (manager->priv->phase < GSM_MANAGER_PHASE_QUERY_END_SESSION) { + return; + } + + /* switch back to running phase */ + g_debug ("GsmManager: Cancelling the end of session"); + + g_cancellable_cancel (manager->priv->end_session_cancellable); + + gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_RUNNING); + manager->priv->logout_mode = GSM_MANAGER_LOGOUT_MODE_NORMAL; + + manager->priv->logout_type = GSM_MANAGER_LOGOUT_NONE; + + /* clear all JIT inhibitors */ + gsm_store_foreach_remove (manager->priv->inhibitors, + (GsmStoreFunc)inhibitor_is_jit, + (gpointer)manager); + + gsm_store_foreach (manager->priv->clients, + (GsmStoreFunc)_client_cancel_end_session, + NULL); + + start_phase (manager); +} + +static void +end_session_or_show_shell_dialog (GsmManager *manager) +{ + gboolean logout_prompt; + GsmShellEndSessionDialogType type; + gboolean logout_inhibited; + + switch (manager->priv->logout_type) { + case GSM_MANAGER_LOGOUT_LOGOUT: + type = GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT; + break; + case GSM_MANAGER_LOGOUT_REBOOT: + case GSM_MANAGER_LOGOUT_REBOOT_INTERACT: + type = GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART; + break; + case GSM_MANAGER_LOGOUT_SHUTDOWN: + case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT: + type = GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN; + break; + default: + g_warning ("Unexpected logout type %d when creating end session dialog", + manager->priv->logout_type); + type = GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT; + break; + } + + logout_inhibited = gsm_manager_is_logout_inhibited (manager); + logout_prompt = g_settings_get_boolean (manager->priv->settings, + KEY_LOGOUT_PROMPT); + + switch (manager->priv->logout_mode) { + case GSM_MANAGER_LOGOUT_MODE_NORMAL: + if (logout_inhibited || logout_prompt) { + show_shell_end_session_dialog (manager, type); + } else { + end_phase (manager); + } + break; + + case GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION: + if (logout_inhibited) { + show_shell_end_session_dialog (manager, type); + } else { + end_phase (manager); + } + break; + + case GSM_MANAGER_LOGOUT_MODE_FORCE: + end_phase (manager); + break; + default: + g_assert_not_reached (); + break; + } + +} + +static void +query_end_session_complete (GsmManager *manager) +{ + + g_debug ("GsmManager: query end session complete"); + + /* Remove the timeout since this can be called from outside the timer + * and we don't want to have it called twice */ + if (manager->priv->query_timeout_id > 0) { + g_source_remove (manager->priv->query_timeout_id); + manager->priv->query_timeout_id = 0; + } + + end_session_or_show_shell_dialog (manager); +} + +static guint32 +generate_cookie (void) +{ + guint32 cookie; + + cookie = (guint32)g_random_int_range (1, G_MAXINT32); + + return cookie; +} + +static guint32 +_generate_unique_cookie (GsmManager *manager) +{ + guint32 cookie; + + do { + cookie = generate_cookie (); + } while (gsm_store_find (manager->priv->inhibitors, (GsmStoreFunc)_find_by_cookie, &cookie) != NULL); + + return cookie; +} + +static gboolean +_on_query_end_session_timeout (GsmManager *manager) +{ + GSList *l; + + manager->priv->query_timeout_id = 0; + + g_debug ("GsmManager: query end session timed out"); + + for (l = manager->priv->query_clients; l != NULL; l = l->next) { + guint cookie; + GsmInhibitor *inhibitor; + const char *bus_name; + char *app_id; + + g_warning ("Client '%s' failed to reply before timeout", + gsm_client_peek_id (l->data)); + + /* Don't add "not responding" inhibitors if logout is forced + */ + if (manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + continue; + } + + /* Add JIT inhibit for unresponsive client */ + if (GSM_IS_DBUS_CLIENT (l->data)) { + bus_name = gsm_dbus_client_get_bus_name (l->data); + } else { + bus_name = NULL; + } + + app_id = g_strdup (gsm_client_peek_app_id (l->data)); + if (IS_STRING_EMPTY (app_id)) { + /* XSMP clients don't give us an app id unless we start them */ + g_free (app_id); + app_id = gsm_client_get_app_name (l->data); + } + + cookie = _generate_unique_cookie (manager); + inhibitor = gsm_inhibitor_new_for_client (gsm_client_peek_id (l->data), + app_id, + GSM_INHIBITOR_FLAG_LOGOUT, + _("Not responding"), + bus_name, + cookie); + g_free (app_id); + gsm_store_add (manager->priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); + g_object_unref (inhibitor); + } + + g_slist_free (manager->priv->query_clients); + manager->priv->query_clients = NULL; + + query_end_session_complete (manager); + + return FALSE; +} + +static void +do_phase_query_end_session (GsmManager *manager) +{ + ClientEndSessionData data; + + data.manager = manager; + data.flags = 0; + + if (manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + data.flags |= GSM_CLIENT_END_SESSION_FLAG_FORCEFUL; + } + /* We only query if an app is ready to log out, so we don't use + * GSM_CLIENT_END_SESSION_FLAG_SAVE here. + */ + + debug_clients (manager); + g_debug ("GsmManager: sending query-end-session to clients (logout mode: %s)", + manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_NORMAL? "normal" : + manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE? "forceful": + "no confirmation"); + gsm_store_foreach (manager->priv->clients, + (GsmStoreFunc)_client_query_end_session, + &data); + + /* This phase doesn't time out unless logout is forced. Typically, this + * separate timer is only used to show UI. */ + manager->priv->query_timeout_id = g_timeout_add_seconds (1, (GSourceFunc)_on_query_end_session_timeout, manager); +} + +static void +update_idle (GsmManager *manager) +{ + if (gsm_manager_is_idle_inhibited (manager)) { + gsm_presence_set_idle_enabled (manager->priv->presence, FALSE); + } else { + gsm_presence_set_idle_enabled (manager->priv->presence, TRUE); + } +} + +static void +start_phase (GsmManager *manager) +{ + + g_debug ("GsmManager: starting phase %s\n", + phase_num_to_name (manager->priv->phase)); + + /* reset state */ + g_slist_free (manager->priv->pending_apps); + manager->priv->pending_apps = NULL; + g_slist_free (manager->priv->query_clients); + manager->priv->query_clients = NULL; + g_slist_free (manager->priv->next_query_clients); + manager->priv->next_query_clients = NULL; + + if (manager->priv->query_timeout_id > 0) { + g_source_remove (manager->priv->query_timeout_id); + manager->priv->query_timeout_id = 0; + } + if (manager->priv->phase_timeout_id > 0) { + g_source_remove (manager->priv->phase_timeout_id); + manager->priv->phase_timeout_id = 0; + } + + switch (manager->priv->phase) { + case GSM_MANAGER_PHASE_STARTUP: + case GSM_MANAGER_PHASE_EARLY_INITIALIZATION: + case GSM_MANAGER_PHASE_PRE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_DISPLAY_SERVER: + case GSM_MANAGER_PHASE_INITIALIZATION: + case GSM_MANAGER_PHASE_WINDOW_MANAGER: + case GSM_MANAGER_PHASE_PANEL: + case GSM_MANAGER_PHASE_DESKTOP: + case GSM_MANAGER_PHASE_APPLICATION: + do_phase_startup (manager); + break; + case GSM_MANAGER_PHASE_RUNNING: +#ifdef ENABLE_SYSTEMD_JOURNAL + sd_journal_send ("MESSAGE_ID=%s", GSM_MANAGER_STARTUP_SUCCEEDED_MSGID, + "PRIORITY=%d", 5, + "MESSAGE=Entering running state", + NULL); +#endif + gsm_xsmp_server_start_accepting_new_clients (manager->priv->xsmp_server); + if (manager->priv->pending_end_session_tasks != NULL) + complete_end_session_tasks (manager); + g_object_unref (manager->priv->end_session_cancellable); + manager->priv->end_session_cancellable = g_cancellable_new (); + gsm_exported_manager_emit_session_running (manager->priv->skeleton); + update_idle (manager); + break; + case GSM_MANAGER_PHASE_QUERY_END_SESSION: + gsm_xsmp_server_stop_accepting_new_clients (manager->priv->xsmp_server); + do_phase_query_end_session (manager); + break; + case GSM_MANAGER_PHASE_END_SESSION: + do_phase_end_session (manager); + break; + case GSM_MANAGER_PHASE_EXIT: + do_phase_exit (manager); + break; + default: + g_assert_not_reached (); + break; + } +} + +static gboolean +_debug_app_for_phase (const char *id, + GsmApp *app, + gpointer data) +{ + guint phase; + + phase = GPOINTER_TO_UINT (data); + + if (gsm_app_peek_phase (app) != phase) { + return FALSE; + } + + g_debug ("GsmManager:\tID: %s\tapp-id:%s\tis-disabled:%d\tis-conditionally-disabled:%d", + gsm_app_peek_id (app), + gsm_app_peek_app_id (app), + gsm_app_peek_is_disabled (app), + gsm_app_peek_is_conditionally_disabled (app)); + + return FALSE; +} + +static void +debug_app_summary (GsmManager *manager) +{ + guint phase; + + g_debug ("GsmManager: App startup summary"); + for (phase = GSM_MANAGER_PHASE_EARLY_INITIALIZATION; phase < GSM_MANAGER_PHASE_RUNNING; phase++) { + g_debug ("GsmManager: Phase %s", phase_num_to_name (phase)); + gsm_store_foreach (manager->priv->apps, + (GsmStoreFunc)_debug_app_for_phase, + GUINT_TO_POINTER (phase)); + } +} + +void +gsm_manager_start (GsmManager *manager) +{ + g_debug ("GsmManager: GSM starting to manage"); + + g_return_if_fail (GSM_IS_MANAGER (manager)); + + gsm_xsmp_server_start (manager->priv->xsmp_server); + gsm_manager_set_phase (manager, GSM_MANAGER_PHASE_EARLY_INITIALIZATION); + debug_app_summary (manager); + start_phase (manager); +} + +const char * +_gsm_manager_get_default_session (GsmManager *manager) +{ + return g_settings_get_string (manager->priv->session_settings, + KEY_SESSION_NAME); +} + +void +_gsm_manager_set_active_session (GsmManager *manager, + const char *session_name, + gboolean is_fallback) +{ + g_free (manager->priv->session_name); + manager->priv->session_name = g_strdup (session_name); + manager->priv->is_fallback_session = is_fallback; + + gsm_exported_manager_set_session_name (manager->priv->skeleton, session_name); +} + +void +_gsm_manager_set_renderer (GsmManager *manager, + const char *renderer) +{ + gsm_exported_manager_set_renderer (manager->priv->skeleton, renderer); +} + +static gboolean +_app_has_app_id (const char *id, + GsmApp *app, + const char *app_id_a) +{ + const char *app_id_b; + + app_id_b = gsm_app_peek_app_id (app); + return (app_id_b != NULL && strcmp (app_id_a, app_id_b) == 0); +} + +static GsmApp * +find_app_for_app_id (GsmManager *manager, + const char *app_id) +{ + GsmApp *app; + app = (GsmApp *)gsm_store_find (manager->priv->apps, + (GsmStoreFunc)_app_has_app_id, + (char *)app_id); + return app; +} + +static gboolean +inhibitor_has_client_id (gpointer key, + GsmInhibitor *inhibitor, + const char *client_id_a) +{ + gboolean matches; + const char *client_id_b; + + client_id_b = gsm_inhibitor_peek_client_id (inhibitor); + + matches = FALSE; + if (! IS_STRING_EMPTY (client_id_a) && ! IS_STRING_EMPTY (client_id_b)) { + matches = (strcmp (client_id_a, client_id_b) == 0); + if (matches) { + g_debug ("GsmManager: removing JIT inhibitor for %s for reason '%s'", + gsm_inhibitor_peek_client_id (inhibitor), + gsm_inhibitor_peek_reason (inhibitor)); + } + } + + return matches; +} + +static gboolean +_app_has_startup_id (const char *id, + GsmApp *app, + const char *startup_id_a) +{ + const char *startup_id_b; + + startup_id_b = gsm_app_peek_startup_id (app); + + if (IS_STRING_EMPTY (startup_id_b)) { + return FALSE; + } + + return (strcmp (startup_id_a, startup_id_b) == 0); +} + +static GsmApp * +find_app_for_startup_id (GsmManager *manager, + const char *startup_id) +{ + GsmApp *found_app; + GSList *a; + + found_app = NULL; + + /* If we're starting up the session, try to match the new client + * with one pending apps for the current phase. If not, try to match + * with any of the autostarted apps. */ + if (manager->priv->phase < GSM_MANAGER_PHASE_APPLICATION) { + for (a = manager->priv->pending_apps; a != NULL; a = a->next) { + GsmApp *app = GSM_APP (a->data); + + if (strcmp (startup_id, gsm_app_peek_startup_id (app)) == 0) { + found_app = app; + goto out; + } + } + } else { + GsmApp *app; + + app = (GsmApp *)gsm_store_find (manager->priv->apps, + (GsmStoreFunc)_app_has_startup_id, + (char *)startup_id); + if (app != NULL) { + found_app = app; + goto out; + } + } + out: + return found_app; +} + +static void +_disconnect_client (GsmManager *manager, + GsmClient *client) +{ + gboolean is_condition_client; + GsmApp *app; + const char *app_id; + const char *startup_id; + gboolean app_restart; + GsmClientRestartStyle client_restart_hint; + + g_debug ("GsmManager: disconnect client: %s", gsm_client_peek_id (client)); + + /* take a ref so it doesn't get finalized */ + g_object_ref (client); + + gsm_client_set_status (client, GSM_CLIENT_FINISHED); + + is_condition_client = FALSE; + if (g_slist_find (manager->priv->condition_clients, client)) { + manager->priv->condition_clients = g_slist_remove (manager->priv->condition_clients, client); + + is_condition_client = TRUE; + } + + /* remove any inhibitors for this client */ + gsm_store_foreach_remove (manager->priv->inhibitors, + (GsmStoreFunc)inhibitor_has_client_id, + (gpointer)gsm_client_peek_id (client)); + + app = NULL; + + /* first try to match on startup ID */ + startup_id = gsm_client_peek_startup_id (client); + if (! IS_STRING_EMPTY (startup_id)) { + app = find_app_for_startup_id (manager, startup_id); + + } + + /* then try to find matching app-id */ + if (app == NULL) { + app_id = gsm_client_peek_app_id (client); + if (! IS_STRING_EMPTY (app_id)) { + g_debug ("GsmManager: disconnect for app '%s'", app_id); + app = find_app_for_app_id (manager, app_id); + } + } + + if (manager->priv->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION) { + /* Instead of answering our end session query, the client just exited. + * Treat that as an "okay, end the session" answer. + * + * This call implicitly removes any inhibitors for the client, along + * with removing the client from the pending query list. + */ + _handle_client_end_session_response (manager, + client, + TRUE, + FALSE, + FALSE, + "Client exited in " + "query end session phase " + "instead of end session " + "phase"); + } + + if (manager->priv->dbus_disconnected && GSM_IS_DBUS_CLIENT (client)) { + g_debug ("GsmManager: dbus disconnected, not restarting application"); + goto out; + } + + if (app == NULL) { + g_debug ("GsmManager: unable to find application for client - not restarting"); + goto out; + } + + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + g_debug ("GsmManager: in shutdown, not restarting application"); + goto out; + } + + app_restart = gsm_app_peek_autorestart (app); + client_restart_hint = gsm_client_peek_restart_style_hint (client); + + /* allow legacy clients to override the app info */ + if (! app_restart + && client_restart_hint != GSM_CLIENT_RESTART_IMMEDIATELY) { + g_debug ("GsmManager: autorestart not set, not restarting application"); + goto out; + } + + if (is_condition_client) { + g_debug ("GsmManager: app conditionally disabled, not restarting application"); + goto out; + } + + g_debug ("GsmManager: restarting app"); + + _restart_app (manager, app); + + out: + g_object_unref (client); +} + +typedef struct { + const char *service_name; + GsmManager *manager; +} RemoveClientData; + +static gboolean +_disconnect_dbus_client (const char *id, + GsmClient *client, + RemoveClientData *data) +{ + const char *name; + + if (! GSM_IS_DBUS_CLIENT (client)) { + return FALSE; + } + + /* If no service name, then we simply disconnect all clients */ + if (!data->service_name) { + _disconnect_client (data->manager, client); + return TRUE; + } + + name = gsm_dbus_client_get_bus_name (GSM_DBUS_CLIENT (client)); + if (IS_STRING_EMPTY (name)) { + return FALSE; + } + + if (strcmp (data->service_name, name) == 0) { + _disconnect_client (data->manager, client); + return TRUE; + } + + return FALSE; +} + +/** + * remove_clients_for_connection: + * @manager: a #GsmManager + * @service_name: a service name + * + * Disconnects clients that own @service_name. + * + * If @service_name is NULL, then disconnects all clients for the connection. + */ +static void +remove_clients_for_connection (GsmManager *manager, + const char *service_name) +{ + RemoveClientData data; + + data.service_name = service_name; + data.manager = manager; + + /* disconnect dbus clients for name */ + gsm_store_foreach_remove (manager->priv->clients, + (GsmStoreFunc)_disconnect_dbus_client, + &data); + + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION + && gsm_store_size (manager->priv->clients) == 0) { + g_debug ("GsmManager: last client disconnected - exiting"); + end_phase (manager); + } +} + +static void +gsm_manager_set_failsafe (GsmManager *manager, + gboolean enabled) +{ + g_return_if_fail (GSM_IS_MANAGER (manager)); + + manager->priv->failsafe = enabled; +} + +gboolean +gsm_manager_get_failsafe (GsmManager *manager) +{ + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + + return manager->priv->failsafe; +} + +static void +on_client_disconnected (GsmClient *client, + GsmManager *manager) +{ + g_debug ("GsmManager: disconnect client"); + _disconnect_client (manager, client); + gsm_store_remove (manager->priv->clients, gsm_client_peek_id (client)); + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION + && gsm_store_size (manager->priv->clients) == 0) { + g_debug ("GsmManager: last client disconnected - exiting"); + end_phase (manager); + } +} + +static gboolean +on_xsmp_client_register_request (GsmXSMPClient *client, + char **id, + GsmManager *manager) +{ + gboolean handled; + char *new_id; + GsmApp *app; + + handled = TRUE; + new_id = NULL; + + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + goto out; + } + + if (IS_STRING_EMPTY (*id)) { + new_id = gsm_util_generate_startup_id (); + } else { + GsmClient *client; + + client = (GsmClient *)gsm_store_find (manager->priv->clients, + (GsmStoreFunc)_client_has_startup_id, + *id); + /* We can't have two clients with the same id. */ + if (client != NULL) { + goto out; + } + + new_id = g_strdup (*id); + } + + g_debug ("GsmManager: Adding new client %s to session", new_id); + + g_signal_connect (client, + "disconnected", + G_CALLBACK (on_client_disconnected), + manager); + + /* If it's a brand new client id, we just accept the client*/ + if (IS_STRING_EMPTY (*id)) { + goto out; + } + + app = find_app_for_startup_id (manager, new_id); + if (app != NULL) { + gsm_client_set_app_id (GSM_CLIENT (client), gsm_app_peek_app_id (app)); + goto out; + } + + /* app not found */ + g_free (new_id); + new_id = NULL; + + out: + g_free (*id); + *id = new_id; + + return handled; +} + +static void +on_xsmp_client_register_confirmed (GsmXSMPClient *client, + const gchar *id, + GsmManager *manager) +{ + GsmApp *app; + + app = find_app_for_startup_id (manager, id); + + if (app != NULL) { + gsm_app_set_registered (app, TRUE); + } +} + +static gboolean +auto_save_is_enabled (GsmManager *manager) +{ + return g_settings_get_boolean (manager->priv->settings, KEY_AUTOSAVE_ONE_SHOT) + || g_settings_get_boolean (manager->priv->settings, KEY_AUTOSAVE); +} + +static void +maybe_save_session (GsmManager *manager) +{ + GError *error; + + if (gsm_system_is_login_session (manager->priv->system)) + return; + + /* We only allow session saving when session is running or when + * logging out */ + if (manager->priv->phase != GSM_MANAGER_PHASE_RUNNING && + manager->priv->phase != GSM_MANAGER_PHASE_END_SESSION) { + return; + } + + if (!auto_save_is_enabled (manager)) { + gsm_session_save_clear (); + return; + } + + error = NULL; + gsm_session_save (manager->priv->clients, manager->priv->apps, &error); + + if (error) { + g_warning ("Error saving session: %s", error->message); + g_error_free (error); + } +} + +static void +_handle_client_end_session_response (GsmManager *manager, + GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason) +{ + /* just ignore if received outside of shutdown */ + if (manager->priv->phase < GSM_MANAGER_PHASE_QUERY_END_SESSION) { + return; + } + + g_debug ("GsmManager: Response from end session request: is-ok=%d do-last=%d cancel=%d reason=%s", is_ok, do_last, cancel, reason ? reason :""); + + if (cancel) { + cancel_end_session (manager); + return; + } + + manager->priv->query_clients = g_slist_remove (manager->priv->query_clients, client); + + if (! is_ok && manager->priv->logout_mode != GSM_MANAGER_LOGOUT_MODE_FORCE) { + guint cookie; + GsmInhibitor *inhibitor; + char *app_id; + const char *bus_name; + + /* FIXME: do we support updating the reason? */ + + /* Create JIT inhibit */ + if (GSM_IS_DBUS_CLIENT (client)) { + bus_name = gsm_dbus_client_get_bus_name (GSM_DBUS_CLIENT (client)); + } else { + bus_name = NULL; + } + + app_id = g_strdup (gsm_client_peek_app_id (client)); + if (IS_STRING_EMPTY (app_id)) { + /* XSMP clients don't give us an app id unless we start them */ + g_free (app_id); + app_id = gsm_client_get_app_name (client); + } + + cookie = _generate_unique_cookie (manager); + inhibitor = gsm_inhibitor_new_for_client (gsm_client_peek_id (client), + app_id, + GSM_INHIBITOR_FLAG_LOGOUT, + reason != NULL ? reason : _("Not responding"), + bus_name, + cookie); + g_free (app_id); + gsm_store_add (manager->priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); + g_object_unref (inhibitor); + } else { + gsm_store_foreach_remove (manager->priv->inhibitors, + (GsmStoreFunc)inhibitor_has_client_id, + (gpointer)gsm_client_peek_id (client)); + } + + if (manager->priv->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION) { + if (manager->priv->query_clients == NULL) { + query_end_session_complete (manager); + } + } else if (manager->priv->phase == GSM_MANAGER_PHASE_END_SESSION) { + if (do_last) { + /* This only makes sense if we're in part 1 of + * GSM_MANAGER_PHASE_END_SESSION. Doing this in part 2 + * can only happen because of a buggy client that loops + * wanting to be last again and again. The phase + * timeout will take care of this issue. */ + manager->priv->next_query_clients = g_slist_prepend (manager->priv->next_query_clients, + client); + } + + /* we can continue to the next step if all clients have replied + * and if there's no inhibitor */ + if (manager->priv->query_clients != NULL + || gsm_manager_is_logout_inhibited (manager)) { + return; + } + + if (manager->priv->next_query_clients != NULL) { + do_phase_end_session_part_2 (manager); + } else { + end_phase (manager); + } + } +} + +static void +on_client_end_session_response (GsmClient *client, + gboolean is_ok, + gboolean do_last, + gboolean cancel, + const char *reason, + GsmManager *manager) +{ + _handle_client_end_session_response (manager, + client, + is_ok, + do_last, + cancel, + reason); +} + +static void +on_xsmp_client_logout_request (GsmXSMPClient *client, + gboolean show_dialog, + GsmManager *manager) +{ + GError *error; + int logout_mode; + + if (show_dialog) { + logout_mode = GSM_MANAGER_LOGOUT_MODE_NORMAL; + } else { + logout_mode = GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION; + } + + error = NULL; + gsm_manager_logout (manager, logout_mode, &error); + if (error != NULL) { + g_warning ("Unable to logout: %s", error->message); + g_error_free (error); + } +} + +static void +on_store_client_added (GsmStore *store, + const char *id, + GsmManager *manager) +{ + GsmClient *client; + + g_debug ("GsmManager: Client added: %s", id); + + client = (GsmClient *)gsm_store_lookup (store, id); + + /* a bit hacky */ + if (GSM_IS_XSMP_CLIENT (client)) { + g_signal_connect (client, + "register-request", + G_CALLBACK (on_xsmp_client_register_request), + manager); + g_signal_connect (client, + "register-confirmed", + G_CALLBACK (on_xsmp_client_register_confirmed), + manager); + g_signal_connect (client, + "logout-request", + G_CALLBACK (on_xsmp_client_logout_request), + manager); + } + + g_signal_connect (client, + "end-session-response", + G_CALLBACK (on_client_end_session_response), + manager); + + gsm_exported_manager_emit_client_added (manager->priv->skeleton, id); + /* FIXME: disconnect signal handler */ +} + +static void +on_store_client_removed (GsmStore *store, + const char *id, + GsmManager *manager) +{ + g_debug ("GsmManager: Client removed: %s", id); + + gsm_exported_manager_emit_client_removed (manager->priv->skeleton, id); +} + +static void +gsm_manager_set_client_store (GsmManager *manager, + GsmStore *store) +{ + g_return_if_fail (GSM_IS_MANAGER (manager)); + + if (store != NULL) { + g_object_ref (store); + } + + if (manager->priv->clients != NULL) { + g_signal_handlers_disconnect_by_func (manager->priv->clients, + on_store_client_added, + manager); + g_signal_handlers_disconnect_by_func (manager->priv->clients, + on_store_client_removed, + manager); + + g_object_unref (manager->priv->clients); + } + + + g_debug ("GsmManager: setting client store %p", store); + + manager->priv->clients = store; + + if (manager->priv->clients != NULL) { + if (manager->priv->xsmp_server) + g_object_unref (manager->priv->xsmp_server); + + manager->priv->xsmp_server = gsm_xsmp_server_new (store); + + g_signal_connect (manager->priv->clients, + "added", + G_CALLBACK (on_store_client_added), + manager); + g_signal_connect (manager->priv->clients, + "removed", + G_CALLBACK (on_store_client_removed), + manager); + } +} + +static void +gsm_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsmManager *self; + + self = GSM_MANAGER (object); + + switch (prop_id) { + case PROP_FAILSAFE: + gsm_manager_set_failsafe (self, g_value_get_boolean (value)); + break; + case PROP_FALLBACK: + self->priv->is_fallback_session = g_value_get_boolean (value); + break; + case PROP_CLIENT_STORE: + gsm_manager_set_client_store (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gsm_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsmManager *self; + + self = GSM_MANAGER (object); + + switch (prop_id) { + case PROP_FAILSAFE: + g_value_set_boolean (value, self->priv->failsafe); + break; + case PROP_SESSION_NAME: + g_value_set_string (value, self->priv->session_name); + break; + case PROP_FALLBACK: + g_value_set_boolean (value, self->priv->is_fallback_session); + break; + case PROP_CLIENT_STORE: + g_value_set_object (value, self->priv->clients); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +_find_app_provides (const char *id, + GsmApp *app, + const char *service) +{ + return gsm_app_provides (app, service); +} + +static GObject * +gsm_manager_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GsmManager *manager; + + manager = GSM_MANAGER (G_OBJECT_CLASS (gsm_manager_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + return G_OBJECT (manager); +} + +static void +update_inhibited_actions (GsmManager *manager, + GsmInhibitorFlag new_inhibited_actions) +{ + if (manager->priv->inhibited_actions == new_inhibited_actions) + return; + + manager->priv->inhibited_actions = new_inhibited_actions; + gsm_exported_manager_set_inhibited_actions (manager->priv->skeleton, + manager->priv->inhibited_actions); +} + +static void +on_inhibitor_vanished (GsmInhibitor *inhibitor, + GsmManager *manager) +{ + gsm_store_remove (manager->priv->inhibitors, gsm_inhibitor_peek_id (inhibitor)); +} + +static void +on_store_inhibitor_added (GsmStore *store, + const char *id, + GsmManager *manager) +{ + GsmInhibitor *i; + GsmInhibitorFlag new_inhibited_actions; + + g_debug ("GsmManager: Inhibitor added: %s", id); + + i = GSM_INHIBITOR (gsm_store_lookup (store, id)); + gsm_system_add_inhibitor (manager->priv->system, id, + gsm_inhibitor_peek_flags (i)); + + new_inhibited_actions = manager->priv->inhibited_actions | gsm_inhibitor_peek_flags (i); + update_inhibited_actions (manager, new_inhibited_actions); + + g_signal_connect_object (i, "vanished", G_CALLBACK (on_inhibitor_vanished), manager, 0); + + gsm_exported_manager_emit_inhibitor_added (manager->priv->skeleton, id); + + update_idle (manager); +} + +static gboolean +collect_inhibition_flags (const char *id, + GObject *object, + gpointer user_data) +{ + GsmInhibitorFlag *new_inhibited_actions = user_data; + + *new_inhibited_actions |= gsm_inhibitor_peek_flags (GSM_INHIBITOR (object)); + + return FALSE; +} + +static void +on_store_inhibitor_removed (GsmStore *store, + const char *id, + GsmManager *manager) +{ + GsmInhibitorFlag new_inhibited_actions; + + g_debug ("GsmManager: Inhibitor removed: %s", id); + + gsm_system_remove_inhibitor (manager->priv->system, id); + + new_inhibited_actions = 0; + gsm_store_foreach (manager->priv->inhibitors, + collect_inhibition_flags, + &new_inhibited_actions); + update_inhibited_actions (manager, new_inhibited_actions); + + gsm_exported_manager_emit_inhibitor_removed (manager->priv->skeleton, id); + + update_idle (manager); + + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + end_session_or_show_shell_dialog (manager); + } +} + +static void +gsm_manager_dispose (GObject *object) +{ + GsmManager *manager = GSM_MANAGER (object); + + g_debug ("GsmManager: disposing manager"); + + g_clear_object (&manager->priv->end_session_cancellable); + g_clear_object (&manager->priv->xsmp_server); + + if (manager->priv->clients != NULL) { + g_signal_handlers_disconnect_by_func (manager->priv->clients, + on_store_client_added, + manager); + g_signal_handlers_disconnect_by_func (manager->priv->clients, + on_store_client_removed, + manager); + g_object_unref (manager->priv->clients); + manager->priv->clients = NULL; + } + + g_clear_object (&manager->priv->apps); + g_slist_free (manager->priv->required_apps); + manager->priv->required_apps = NULL; + + if (manager->priv->inhibitors != NULL) { + g_signal_handlers_disconnect_by_func (manager->priv->inhibitors, + on_store_inhibitor_added, + manager); + g_signal_handlers_disconnect_by_func (manager->priv->inhibitors, + on_store_inhibitor_removed, + manager); + + g_object_unref (manager->priv->inhibitors); + manager->priv->inhibitors = NULL; + } + + g_clear_object (&manager->priv->presence); + g_clear_object (&manager->priv->settings); + g_clear_object (&manager->priv->session_settings); + g_clear_object (&manager->priv->screensaver_settings); + g_clear_object (&manager->priv->lockdown_settings); + g_clear_object (&manager->priv->system); + g_clear_object (&manager->priv->shell); + + if (manager->priv->skeleton != NULL) { + g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (manager->priv->skeleton), + manager->priv->connection); + g_clear_object (&manager->priv->skeleton); + } + + g_clear_object (&manager->priv->connection); + + G_OBJECT_CLASS (gsm_manager_parent_class)->dispose (object); +} + +static void +gsm_manager_class_init (GsmManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gsm_manager_get_property; + object_class->set_property = gsm_manager_set_property; + object_class->constructor = gsm_manager_constructor; + object_class->dispose = gsm_manager_dispose; + + signals [PHASE_CHANGED] = + g_signal_new ("phase-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsmManagerClass, phase_changed), + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_STRING); + + g_object_class_install_property (object_class, + PROP_FAILSAFE, + g_param_spec_boolean ("failsafe", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + /** + * GsmManager::session-name + * + * Then name of the currently active session, typically "gnome" or "gnome-fallback". + * This may be the name of the configured default session, or the name of a fallback + * session in case we fell back. + */ + g_object_class_install_property (object_class, + PROP_SESSION_NAME, + g_param_spec_string ("session-name", + NULL, + NULL, + NULL, + G_PARAM_READABLE)); + + /** + * GsmManager::fallback + * + * If %TRUE, the current session is running in the "fallback" mode; + * this is distinct from whether or not it was configured as default. + */ + g_object_class_install_property (object_class, + PROP_FALLBACK, + g_param_spec_boolean ("fallback", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_CLIENT_STORE, + g_param_spec_object ("client-store", + NULL, + NULL, + GSM_TYPE_STORE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GsmManagerPrivate)); +} + +static void +on_presence_status_changed (GsmPresence *presence, + guint status, + GsmManager *manager) +{ + GsmSystem *system; + + system = gsm_get_system (); + gsm_system_set_session_idle (system, + (status == GSM_PRESENCE_STATUS_IDLE)); + g_object_unref (system); +} + +static void +on_gsm_system_active_changed (GsmSystem *system, + GParamSpec *pspec, + GsmManager *self) +{ + gboolean is_active; + + is_active = gsm_system_is_active (self->priv->system); + + g_debug ("emitting SessionIsActive"); + gsm_exported_manager_set_session_is_active (self->priv->skeleton, is_active); +} + +static gboolean +_log_out_is_locked_down (GsmManager *manager) +{ + return g_settings_get_boolean (manager->priv->lockdown_settings, + KEY_DISABLE_LOG_OUT); +} + +static void +complete_end_session_task (GsmManager *manager, + GAsyncResult *result, + GDBusMethodInvocation *invocation) +{ + GError *error = NULL; + + if (!g_task_propagate_boolean (G_TASK (result), &error)) + g_dbus_method_invocation_take_error (invocation, error); + else + g_dbus_method_invocation_return_value (invocation, NULL); +} + +static void +request_reboot (GsmManager *manager) +{ + g_debug ("GsmManager: requesting reboot"); + + /* FIXME: We need to support a more structured shutdown here, + * but that's blocking on an improved ConsoleKit api. + * + * See https://bugzilla.gnome.org/show_bug.cgi?id=585614 + */ + manager->priv->logout_type = GSM_MANAGER_LOGOUT_REBOOT_INTERACT; + end_phase (manager); +} + +static void +request_shutdown (GsmManager *manager) +{ + g_debug ("GsmManager: requesting shutdown"); + + /* See the comment in request_reboot() for some more details about + * what work needs to be done here. */ + manager->priv->logout_type = GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT; + end_phase (manager); +} + +static void +request_logout (GsmManager *manager, + GsmManagerLogoutMode mode) +{ + g_debug ("GsmManager: requesting logout"); + + manager->priv->logout_mode = mode; + manager->priv->logout_type = GSM_MANAGER_LOGOUT_LOGOUT; + + end_phase (manager); +} + +static gboolean +gsm_manager_shutdown (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GTask *task; + + g_debug ("GsmManager: Shutdown called"); + + if (manager->priv->phase < GSM_MANAGER_PHASE_RUNNING) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "Shutdown interface is only available after the Running phase starts"); + return TRUE; + } + + if (_log_out_is_locked_down (manager)) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_LOCKED_DOWN, + "Logout has been locked down"); + return TRUE; + } + + task = g_task_new (manager, manager->priv->end_session_cancellable, (GAsyncReadyCallback) complete_end_session_task, invocation); + + manager->priv->pending_end_session_tasks = g_slist_prepend (manager->priv->pending_end_session_tasks, + task); + + request_shutdown (manager); + + return TRUE; +} + +static gboolean +gsm_manager_reboot (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GTask *task; + + g_debug ("GsmManager: Reboot called"); + + if (manager->priv->phase < GSM_MANAGER_PHASE_RUNNING) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "Reboot interface is only available after the Running phase starts"); + return TRUE; + } + + if (_log_out_is_locked_down (manager)) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_LOCKED_DOWN, + "Logout has been locked down"); + return TRUE; + } + + task = g_task_new (manager, manager->priv->end_session_cancellable, (GAsyncReadyCallback) complete_end_session_task, invocation); + + manager->priv->pending_end_session_tasks = g_slist_prepend (manager->priv->pending_end_session_tasks, + task); + + request_reboot (manager); + + return TRUE; +} + +static gboolean +gsm_manager_can_shutdown (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + gboolean shutdown_available; + + g_debug ("GsmManager: CanShutdown called"); + + shutdown_available = !_log_out_is_locked_down (manager) && + (gsm_system_can_stop (manager->priv->system) + || gsm_system_can_restart (manager->priv->system) + || gsm_system_can_suspend (manager->priv->system) + || gsm_system_can_hibernate (manager->priv->system)); + + gsm_exported_manager_complete_can_shutdown (skeleton, invocation, shutdown_available); + + return TRUE; +} + +static gboolean +gsm_manager_can_reboot_to_firmware_setup (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + gboolean reboot_to_firmware_available; + + g_debug ("GsmManager: CanRebootToFirmwareSetup called"); + + reboot_to_firmware_available = !_log_out_is_locked_down (manager) && + gsm_system_can_restart_to_firmware_setup (manager->priv->system); + + gsm_exported_manager_complete_can_reboot_to_firmware_setup (skeleton, invocation, reboot_to_firmware_available); + + return TRUE; +} + +static gboolean +gsm_manager_set_reboot_to_firmware_setup (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + gboolean enable, + GsmManager *manager) +{ + g_debug ("GsmManager: SetRebootToFirmwareSetup called"); + + gsm_system_set_restart_to_firmware_setup (manager->priv->system, enable); + + gsm_exported_manager_complete_set_reboot_to_firmware_setup (skeleton, invocation); + + return TRUE; +} + +static gboolean +gsm_manager_setenv (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *variable, + const char *value, + GsmManager *manager) +{ + if (manager->priv->phase > GSM_MANAGER_PHASE_INITIALIZATION) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, + "Setenv interface is only available during the DisplayServer and Initialization phase"); + } else { + gsm_util_setenv (variable, value); + gsm_exported_manager_complete_setenv (skeleton, invocation); + } + + return TRUE; +} + +static gboolean +is_valid_category (int category) +{ + int categories[] = { + LC_CTYPE, + LC_NUMERIC, + LC_TIME, + LC_COLLATE, + LC_MONETARY, + LC_MESSAGES, +#if defined (LC_PAPER) + LC_PAPER, +#endif +#if defined (LC_NAME) + LC_NAME, +#endif +#if defined (LC_ADDRESS) + LC_ADDRESS, +#endif +#if defined (LC_TELEPHONE) + LC_TELEPHONE, +#endif +#if defined (LC_MEASUREMENT) + LC_MEASUREMENT, +#endif +#if defined (LC_IDENTIFICATION) + LC_IDENTIFICATION, +#endif + LC_ALL + }; + guint i; + + for (i = 0; i < G_N_ELEMENTS(categories); i++) + if (categories[i] == category) + return TRUE; + + return FALSE; +} + +static gboolean +gsm_manager_get_locale (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + int category, + GsmManager *manager) +{ + if (!is_valid_category (category)) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_INVALID_OPTION, + "GetLocale doesn't support locale category '%d'", category); + } else { + const char *value; + value = setlocale (category, NULL); + if (value == NULL) + value = ""; + + gsm_exported_manager_complete_get_locale (skeleton, invocation, value); + } + + return TRUE; +} + +static gboolean +gsm_manager_initialization_error (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *message, + gboolean fatal, + GsmManager *manager) +{ + if (manager->priv->phase != GSM_MANAGER_PHASE_INITIALIZATION) { + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_INITIALIZATION, + "InitializationError interface is only available during the Initialization phase"); + return TRUE; + } + + gsm_util_init_error (fatal, "%s", message); + gsm_exported_manager_complete_initialization_error (skeleton, invocation); + + return TRUE; +} + +static void +user_logout (GsmManager *manager, + GsmManagerLogoutMode mode) +{ + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + manager->priv->logout_mode = mode; + end_session_or_show_shell_dialog (manager); + return; + } + + request_logout (manager, mode); +} + +gboolean +gsm_manager_logout (GsmManager *manager, + guint logout_mode, + GError **error) +{ + if (manager->priv->phase < GSM_MANAGER_PHASE_RUNNING) { + g_set_error (error, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "Logout interface is only available after the Running phase starts"); + return FALSE; + } + + if (_log_out_is_locked_down (manager)) { + g_set_error (error, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_LOCKED_DOWN, + "Logout has been locked down"); + return FALSE; + } + + switch (logout_mode) { + case GSM_MANAGER_LOGOUT_MODE_NORMAL: + case GSM_MANAGER_LOGOUT_MODE_NO_CONFIRMATION: + case GSM_MANAGER_LOGOUT_MODE_FORCE: + user_logout (manager, logout_mode); + break; + + default: + g_debug ("Unknown logout mode option"); + + g_set_error (error, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_INVALID_OPTION, + "Unknown logout mode flag"); + return FALSE; + } + + return TRUE; +} + +static gboolean +gsm_manager_logout_dbus (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + guint logout_mode, + GsmManager *manager) +{ + GError *error = NULL; + + g_debug ("GsmManager: Logout called"); + + if (!gsm_manager_logout (manager, logout_mode, &error)) { + g_dbus_method_invocation_take_error (invocation, error); + } else { + gsm_exported_manager_complete_logout (skeleton, invocation); + } + + return TRUE; +} + +static gboolean +gsm_manager_register_client (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *app_id, + const char *startup_id, + GsmManager *manager) +{ + char *new_startup_id; + const char *sender; + GsmClient *client; + GsmApp *app; + + app = NULL; + client = NULL; + + g_debug ("GsmManager: RegisterClient %s", startup_id); + + if (manager->priv->phase >= GSM_MANAGER_PHASE_QUERY_END_SESSION) { + g_debug ("Unable to register client: shutting down"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_IN_RUNNING, + "Unable to register client"); + return TRUE; + } + + if (IS_STRING_EMPTY (startup_id)) { + new_startup_id = gsm_util_generate_startup_id (); + } else { + + client = (GsmClient *)gsm_store_find (manager->priv->clients, + (GsmStoreFunc)_client_has_startup_id, + (char *)startup_id); + /* We can't have two clients with the same startup id. */ + if (client != NULL) { + g_debug ("Unable to register client: already registered"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_ALREADY_REGISTERED, + "Unable to register client"); + return TRUE; + } + + new_startup_id = g_strdup (startup_id); + } + + g_debug ("GsmManager: Adding new client %s to session", new_startup_id); + + if (app == NULL && !IS_STRING_EMPTY (startup_id)) { + app = find_app_for_startup_id (manager, startup_id); + } + if (app == NULL && !IS_STRING_EMPTY (app_id)) { + /* try to associate this app id with a known app */ + app = find_app_for_app_id (manager, app_id); + } + + sender = g_dbus_method_invocation_get_sender (invocation); + client = gsm_dbus_client_new (new_startup_id, sender); + if (client == NULL) { + g_debug ("Unable to create client"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Unable to register client"); + return TRUE; + } + + gsm_store_add (manager->priv->clients, gsm_client_peek_id (client), G_OBJECT (client)); + /* the store will own the ref */ + g_object_unref (client); + + g_signal_connect (client, + "disconnected", + G_CALLBACK (on_client_disconnected), + manager); + + if (app != NULL) { + gsm_client_set_app_id (client, gsm_app_peek_app_id (app)); + gsm_app_set_registered (app, TRUE); + } else { + /* if an app id is specified store it in the client + so we can save it later */ + gsm_client_set_app_id (client, app_id); + } + + gsm_client_set_status (client, GSM_CLIENT_REGISTERED); + + g_assert (new_startup_id != NULL); + g_free (new_startup_id); + + gsm_exported_manager_complete_register_client (skeleton, invocation, gsm_client_peek_id (client)); + + return TRUE; +} + +static gboolean +gsm_manager_unregister_client (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *client_id, + GsmManager *manager) +{ + GsmClient *client; + + g_debug ("GsmManager: UnregisterClient %s", client_id); + + client = (GsmClient *)gsm_store_lookup (manager->priv->clients, client_id); + if (client == NULL) { + g_debug ("Unable to unregister client: not registered"); + + g_dbus_method_invocation_return_error (invocation, + GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_NOT_REGISTERED, + "Unable to unregister client"); + return TRUE; + } + + /* don't disconnect client here, only change the status. + Wait until it leaves the bus before disconnecting it */ + gsm_client_set_status (client, GSM_CLIENT_UNREGISTERED); + + gsm_exported_manager_complete_unregister_client (skeleton, invocation); + + return TRUE; +} + +static gboolean +gsm_manager_inhibit (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *app_id, + guint toplevel_xid, + const char *reason, + guint flags, + GsmManager *manager) +{ + GsmInhibitor *inhibitor; + guint cookie; + + g_debug ("GsmManager: Inhibit xid=%u app_id=%s reason=%s flags=%u", + toplevel_xid, + app_id, + reason, + flags); + + if (manager->priv->logout_mode == GSM_MANAGER_LOGOUT_MODE_FORCE) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Forced logout cannot be inhibited"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return TRUE; + } + + if (IS_STRING_EMPTY (app_id)) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Application ID not specified"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return TRUE; + } + + if (IS_STRING_EMPTY (reason)) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Reason not specified"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return FALSE; + } + + if (flags == 0) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Invalid inhibit flags"); + g_debug ("GsmManager: Unable to inhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return FALSE; + } + + cookie = _generate_unique_cookie (manager); + inhibitor = gsm_inhibitor_new (app_id, + toplevel_xid, + flags, + reason, + g_dbus_method_invocation_get_sender (invocation), + cookie); + gsm_store_add (manager->priv->inhibitors, gsm_inhibitor_peek_id (inhibitor), G_OBJECT (inhibitor)); + g_object_unref (inhibitor); + + gsm_exported_manager_complete_inhibit (skeleton, invocation, cookie); + + return TRUE; +} + +static gboolean +gsm_manager_uninhibit (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + guint cookie, + GsmManager *manager) +{ + GsmInhibitor *inhibitor; + + g_debug ("GsmManager: Uninhibit %u", cookie); + + inhibitor = (GsmInhibitor *)gsm_store_find (manager->priv->inhibitors, + (GsmStoreFunc)_find_by_cookie, + &cookie); + if (inhibitor == NULL) { + GError *new_error; + + new_error = g_error_new (GSM_MANAGER_ERROR, + GSM_MANAGER_ERROR_GENERAL, + "Unable to uninhibit: Invalid cookie"); + g_debug ("Unable to uninhibit: %s", new_error->message); + g_dbus_method_invocation_take_error (invocation, new_error); + return TRUE; + } + + g_debug ("GsmManager: removing inhibitor %s %u reason '%s' %u connection %s", + gsm_inhibitor_peek_app_id (inhibitor), + gsm_inhibitor_peek_toplevel_xid (inhibitor), + gsm_inhibitor_peek_reason (inhibitor), + gsm_inhibitor_peek_flags (inhibitor), + gsm_inhibitor_peek_bus_name (inhibitor)); + + gsm_store_remove (manager->priv->inhibitors, gsm_inhibitor_peek_id (inhibitor)); + + gsm_exported_manager_complete_uninhibit (skeleton, invocation); + + return TRUE; +} + +static gboolean +gsm_manager_is_inhibited (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + guint flags, + GsmManager *manager) +{ + GsmInhibitor *inhibitor; + gboolean is_inhibited; + + if (manager->priv->inhibitors == NULL + || gsm_store_size (manager->priv->inhibitors) == 0) { + is_inhibited = FALSE; + } else { + inhibitor = (GsmInhibitor *) gsm_store_find (manager->priv->inhibitors, + (GsmStoreFunc)inhibitor_has_flag, + GUINT_TO_POINTER (flags)); + if (inhibitor == NULL) { + is_inhibited = FALSE; + } else { + is_inhibited = TRUE; + } + } + + gsm_exported_manager_complete_is_inhibited (skeleton, invocation, is_inhibited); + + return TRUE; +} + +static gboolean +listify_store_ids (char *id, + GObject *object, + GPtrArray **array) +{ + g_ptr_array_add (*array, g_strdup (id)); + return FALSE; +} + +static gboolean +gsm_manager_get_clients (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GPtrArray *clients; + + clients = g_ptr_array_new_with_free_func (g_free); + gsm_store_foreach (manager->priv->clients, + (GsmStoreFunc) listify_store_ids, + &clients); + g_ptr_array_add (clients, NULL); + + gsm_exported_manager_complete_get_clients (skeleton, invocation, + (const gchar * const *) clients->pdata); + g_ptr_array_unref (clients); + + return TRUE; +} + +static gboolean +gsm_manager_get_inhibitors (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + GPtrArray *inhibitors; + + inhibitors = g_ptr_array_new_with_free_func (g_free); + gsm_store_foreach (manager->priv->inhibitors, + (GsmStoreFunc) listify_store_ids, + &inhibitors); + g_ptr_array_add (inhibitors, NULL); + + gsm_exported_manager_complete_get_inhibitors (skeleton, invocation, + (const gchar * const *) inhibitors->pdata); + g_ptr_array_unref (inhibitors); + + return TRUE; +} + +static gboolean +_app_has_autostart_condition (const char *id, + GsmApp *app, + const char *condition) +{ + gboolean has; + gboolean disabled; + + has = gsm_app_has_autostart_condition (app, condition); + disabled = gsm_app_peek_is_disabled (app); + + return has && !disabled; +} + +static gboolean +gsm_manager_is_autostart_condition_handled (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + const char *condition, + GsmManager *manager) +{ + GsmApp *app; + gboolean handled; + + app = (GsmApp *) gsm_store_find (manager->priv->apps,( + GsmStoreFunc) _app_has_autostart_condition, + (char *)condition); + + if (app != NULL) { + handled = TRUE; + } else { + handled = FALSE; + } + + gsm_exported_manager_complete_is_autostart_condition_handled (skeleton, invocation, handled); + + return TRUE; +} + +static gboolean +gsm_manager_is_session_running (GsmExportedManager *skeleton, + GDBusMethodInvocation *invocation, + GsmManager *manager) +{ + gsm_exported_manager_complete_is_session_running (skeleton, invocation, + manager->priv->phase == GSM_MANAGER_PHASE_RUNNING); + return TRUE; +} + +static void +on_session_connection_closed (GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error, + gpointer user_data) +{ + GsmManager *manager; + + manager = GSM_MANAGER (user_data); + + g_debug ("GsmManager: dbus disconnected; disconnecting dbus clients..."); + manager->priv->dbus_disconnected = TRUE; + remove_clients_for_connection (manager, NULL); +} + +static gboolean +register_manager (GsmManager *manager) +{ + GDBusConnection *connection; + GsmExportedManager *skeleton; + GError *error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + + if (error != NULL) { + g_critical ("error getting session bus: %s", error->message); + g_error_free (error); + + exit (1); + } + + skeleton = gsm_exported_manager_skeleton_new (); + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (skeleton), + connection, + GSM_MANAGER_DBUS_PATH, &error); + + if (error != NULL) { + g_critical ("error exporting manager on session bus: %s", error->message); + g_error_free (error); + + exit (1); + } + + g_signal_connect (skeleton, "handle-can-reboot-to-firmware-setup", + G_CALLBACK (gsm_manager_can_reboot_to_firmware_setup), manager); + g_signal_connect (skeleton, "handle-can-shutdown", + G_CALLBACK (gsm_manager_can_shutdown), manager); + g_signal_connect (skeleton, "handle-get-clients", + G_CALLBACK (gsm_manager_get_clients), manager); + g_signal_connect (skeleton, "handle-get-inhibitors", + G_CALLBACK (gsm_manager_get_inhibitors), manager); + g_signal_connect (skeleton, "handle-get-locale", + G_CALLBACK (gsm_manager_get_locale), manager); + g_signal_connect (skeleton, "handle-inhibit", + G_CALLBACK (gsm_manager_inhibit), manager); + g_signal_connect (skeleton, "handle-initialization-error", + G_CALLBACK (gsm_manager_initialization_error), manager); + g_signal_connect (skeleton, "handle-is-autostart-condition-handled", + G_CALLBACK (gsm_manager_is_autostart_condition_handled), manager); + g_signal_connect (skeleton, "handle-is-inhibited", + G_CALLBACK (gsm_manager_is_inhibited), manager); + g_signal_connect (skeleton, "handle-is-session-running", + G_CALLBACK (gsm_manager_is_session_running), manager); + g_signal_connect (skeleton, "handle-logout", + G_CALLBACK (gsm_manager_logout_dbus), manager); + g_signal_connect (skeleton, "handle-reboot", + G_CALLBACK (gsm_manager_reboot), manager); + g_signal_connect (skeleton, "handle-register-client", + G_CALLBACK (gsm_manager_register_client), manager); + g_signal_connect (skeleton, "handle-set-reboot-to-firmware-setup", + G_CALLBACK (gsm_manager_set_reboot_to_firmware_setup), manager); + g_signal_connect (skeleton, "handle-setenv", + G_CALLBACK (gsm_manager_setenv), manager); + g_signal_connect (skeleton, "handle-shutdown", + G_CALLBACK (gsm_manager_shutdown), manager); + g_signal_connect (skeleton, "handle-uninhibit", + G_CALLBACK (gsm_manager_uninhibit), manager); + g_signal_connect (skeleton, "handle-unregister-client", + G_CALLBACK (gsm_manager_unregister_client), manager); + + manager->priv->dbus_disconnected = FALSE; + g_signal_connect (connection, "closed", + G_CALLBACK (on_session_connection_closed), manager); + + manager->priv->connection = connection; + manager->priv->skeleton = skeleton; + + g_signal_connect (manager->priv->system, "notify::active", + G_CALLBACK (on_gsm_system_active_changed), manager); + + /* cold-plug SessionIsActive */ + on_gsm_system_active_changed (manager->priv->system, NULL, manager); + + return TRUE; +} + +static gboolean +idle_timeout_get_mapping (GValue *value, + GVariant *variant, + gpointer user_data) +{ + guint32 idle_timeout; + + idle_timeout = g_variant_get_uint32 (variant); + g_value_set_uint (value, idle_timeout * 1000); + + return TRUE; +} + +static void +gsm_manager_init (GsmManager *manager) +{ + + manager->priv = GSM_MANAGER_GET_PRIVATE (manager); + + manager->priv->settings = g_settings_new (GSM_MANAGER_SCHEMA); + manager->priv->session_settings = g_settings_new (SESSION_SCHEMA); + manager->priv->screensaver_settings = g_settings_new (SCREENSAVER_SCHEMA); + manager->priv->lockdown_settings = g_settings_new (LOCKDOWN_SCHEMA); + + manager->priv->inhibitors = gsm_store_new (); + g_signal_connect (manager->priv->inhibitors, + "added", + G_CALLBACK (on_store_inhibitor_added), + manager); + g_signal_connect (manager->priv->inhibitors, + "removed", + G_CALLBACK (on_store_inhibitor_removed), + manager); + + manager->priv->apps = gsm_store_new (); + + manager->priv->presence = gsm_presence_new (); + g_signal_connect (manager->priv->presence, + "status-changed", + G_CALLBACK (on_presence_status_changed), + manager); + + g_settings_bind_with_mapping (manager->priv->session_settings, + KEY_IDLE_DELAY, + manager->priv->presence, + "idle-timeout", + G_SETTINGS_BIND_GET, + idle_timeout_get_mapping, + NULL, + NULL, NULL); + + manager->priv->system = gsm_get_system (); + manager->priv->shell = gsm_get_shell (); + manager->priv->end_session_cancellable = g_cancellable_new (); +} + +GsmManager * +gsm_manager_get (void) +{ + return manager_object; +} + +GsmManager * +gsm_manager_new (GsmStore *client_store, + gboolean failsafe) +{ + if (manager_object != NULL) { + g_object_ref (manager_object); + } else { + gboolean res; + + manager_object = g_object_new (GSM_TYPE_MANAGER, + "client-store", client_store, + "failsafe", failsafe, + NULL); + + g_object_add_weak_pointer (manager_object, + (gpointer *) &manager_object); + res = register_manager (manager_object); + if (! res) { + g_object_unref (manager_object); + return NULL; + } + } + + return GSM_MANAGER (manager_object); +} + +static void +disconnect_shell_dialog_signals (GsmManager *manager) +{ + if (manager->priv->shell_end_session_dialog_canceled_id != 0) { + g_signal_handler_disconnect (manager->priv->shell, + manager->priv->shell_end_session_dialog_canceled_id); + manager->priv->shell_end_session_dialog_canceled_id = 0; + } + + if (manager->priv->shell_end_session_dialog_confirmed_logout_id != 0) { + g_signal_handler_disconnect (manager->priv->shell, + manager->priv->shell_end_session_dialog_confirmed_logout_id); + manager->priv->shell_end_session_dialog_confirmed_logout_id = 0; + } + + if (manager->priv->shell_end_session_dialog_confirmed_shutdown_id != 0) { + g_signal_handler_disconnect (manager->priv->shell, + manager->priv->shell_end_session_dialog_confirmed_shutdown_id); + manager->priv->shell_end_session_dialog_confirmed_shutdown_id = 0; + } + + if (manager->priv->shell_end_session_dialog_confirmed_reboot_id != 0) { + g_signal_handler_disconnect (manager->priv->shell, + manager->priv->shell_end_session_dialog_confirmed_reboot_id); + manager->priv->shell_end_session_dialog_confirmed_reboot_id = 0; + } + + if (manager->priv->shell_end_session_dialog_open_failed_id != 0) { + g_signal_handler_disconnect (manager->priv->shell, + manager->priv->shell_end_session_dialog_open_failed_id); + manager->priv->shell_end_session_dialog_open_failed_id = 0; + } +} + +static void +on_shell_end_session_dialog_canceled (GsmShell *shell, + GsmManager *manager) +{ + cancel_end_session (manager); + disconnect_shell_dialog_signals (manager); +} + +static void +_handle_end_session_dialog_response (GsmManager *manager, + GsmManagerLogoutType logout_type) +{ + /* Note we're checking for END_SESSION here and + * QUERY_END_SESSION in the fallback cases elsewhere. + * + * That's because they run at different times in the logout + * process. The shell combines the inhibit and + * confirmation dialogs, so it gets displayed after we've collected + * inhibitors. The fallback code has two distinct dialogs, once of + * which we can (and do show) before collecting the inhibitors. + */ + if (manager->priv->phase >= GSM_MANAGER_PHASE_END_SESSION) { + /* Already shutting down, nothing more to do */ + return; + } + + manager->priv->logout_mode = GSM_MANAGER_LOGOUT_MODE_FORCE; + manager->priv->logout_type = logout_type; + end_phase (manager); +} + +static void +on_shell_end_session_dialog_confirmed_logout (GsmShell *shell, + GsmManager *manager) +{ + _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_LOGOUT); + disconnect_shell_dialog_signals (manager); +} + +static void +on_shell_end_session_dialog_confirmed_shutdown (GsmShell *shell, + GsmManager *manager) +{ + _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_SHUTDOWN); + disconnect_shell_dialog_signals (manager); +} + +static void +on_shell_end_session_dialog_confirmed_reboot (GsmShell *shell, + GsmManager *manager) +{ + _handle_end_session_dialog_response (manager, GSM_MANAGER_LOGOUT_REBOOT); + disconnect_shell_dialog_signals (manager); +} + +static void +connect_shell_dialog_signals (GsmManager *manager) +{ + if (manager->priv->shell_end_session_dialog_canceled_id != 0) + return; + + manager->priv->shell_end_session_dialog_canceled_id = + g_signal_connect (manager->priv->shell, + "end-session-dialog-canceled", + G_CALLBACK (on_shell_end_session_dialog_canceled), + manager); + + manager->priv->shell_end_session_dialog_open_failed_id = + g_signal_connect (manager->priv->shell, + "end-session-dialog-open-failed", + G_CALLBACK (on_shell_end_session_dialog_canceled), + manager); + + manager->priv->shell_end_session_dialog_confirmed_logout_id = + g_signal_connect (manager->priv->shell, + "end-session-dialog-confirmed-logout", + G_CALLBACK (on_shell_end_session_dialog_confirmed_logout), + manager); + + manager->priv->shell_end_session_dialog_confirmed_shutdown_id = + g_signal_connect (manager->priv->shell, + "end-session-dialog-confirmed-shutdown", + G_CALLBACK (on_shell_end_session_dialog_confirmed_shutdown), + manager); + + manager->priv->shell_end_session_dialog_confirmed_reboot_id = + g_signal_connect (manager->priv->shell, + "end-session-dialog-confirmed-reboot", + G_CALLBACK (on_shell_end_session_dialog_confirmed_reboot), + manager); +} + +static void +show_shell_end_session_dialog (GsmManager *manager, + GsmShellEndSessionDialogType type) +{ + if (!gsm_shell_is_running (manager->priv->shell)) + return; + + gsm_shell_open_end_session_dialog (manager->priv->shell, + type, + manager->priv->inhibitors); + connect_shell_dialog_signals (manager); +} + +/* + dbus-send --session --type=method_call --print-reply + --dest=org.gnome.SessionManager + /org/gnome/SessionManager + org.freedesktop.DBus.Introspectable.Introspect +*/ + +gboolean +gsm_manager_set_phase (GsmManager *manager, + GsmManagerPhase phase) +{ + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + manager->priv->phase = phase; + return (TRUE); +} + +static void +append_app (GsmManager *manager, + GsmApp *app, + const char *provides, + gboolean is_required) +{ + const char *id; + const char *app_id; + GsmApp *dup; + + id = gsm_app_peek_id (app); + if (IS_STRING_EMPTY (id)) { + g_debug ("GsmManager: not adding app: no id"); + return; + } + + dup = (GsmApp *)gsm_store_lookup (manager->priv->apps, id); + if (dup != NULL) { + g_debug ("GsmManager: not adding app: already added"); + return; + } + + app_id = gsm_app_peek_app_id (app); + if (IS_STRING_EMPTY (app_id)) { + g_debug ("GsmManager: not adding app: no app-id"); + return; + } + + dup = find_app_for_app_id (manager, app_id); + if (dup != NULL) { + g_debug ("GsmManager: not adding app: app-id '%s' already exists", app_id); + + if (provides && GSM_IS_AUTOSTART_APP (dup)) + gsm_autostart_app_add_provides (GSM_AUTOSTART_APP (dup), provides); + + if (is_required && + !g_slist_find (manager->priv->required_apps, dup)) { + g_debug ("GsmManager: making app '%s' required", gsm_app_peek_app_id (dup)); + manager->priv->required_apps = g_slist_prepend (manager->priv->required_apps, dup); + } + + return; + } + + gsm_store_add (manager->priv->apps, id, G_OBJECT (app)); + if (is_required) { + g_debug ("GsmManager: adding required app %s", gsm_app_peek_app_id (app)); + manager->priv->required_apps = g_slist_prepend (manager->priv->required_apps, app); + } +} + +static gboolean +add_autostart_app_internal (GsmManager *manager, + const char *path, + const char *provides, + gboolean is_required) +{ + GsmApp *app; + char **internal_provides; + GError *error = NULL; + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + g_return_val_if_fail (path != NULL, FALSE); + + /* Note: if we cannot add the app because its service is already + * provided, because its app-id is taken, or because of any other + * reason meaning there is already an app playing its role, then we + * should make sure that relevant properties (like + * provides/is_required) are set in the pre-existing app if needed. */ + + /* first check to see if service is already provided */ + if (provides != NULL) { + GsmApp *dup; + + dup = (GsmApp *)gsm_store_find (manager->priv->apps, + (GsmStoreFunc)_find_app_provides, + (char *)provides); + if (dup != NULL) { + g_debug ("GsmManager: service '%s' is already provided", provides); + + if (is_required && + !g_slist_find (manager->priv->required_apps, dup)) { + g_debug ("GsmManager: making app '%s' required", gsm_app_peek_app_id (dup)); + manager->priv->required_apps = g_slist_prepend (manager->priv->required_apps, dup); + } + + return FALSE; + } + } + + app = gsm_autostart_app_new (path, &error); + if (app == NULL) { + g_warning ("%s", error->message); + g_clear_error (&error); + return FALSE; + } + + internal_provides = gsm_app_get_provides (app); + if (internal_provides) { + int i; + gboolean provided = FALSE; + + for (i = 0; internal_provides[i] != NULL; i++) { + GsmApp *dup; + + dup = (GsmApp *)gsm_store_find (manager->priv->apps, + (GsmStoreFunc)_find_app_provides, + (char *)internal_provides[i]); + if (dup != NULL) { + g_debug ("GsmManager: service '%s' is already provided", internal_provides[i]); + + if (is_required && + !g_slist_find (manager->priv->required_apps, dup)) { + g_debug ("GsmManager: making app '%s' required", gsm_app_peek_app_id (dup)); + manager->priv->required_apps = g_slist_prepend (manager->priv->required_apps, dup); + } + + provided = TRUE; + break; + } + } + + g_strfreev (internal_provides); + + if (provided) { + g_object_unref (app); + return FALSE; + } + } + + if (provides) + gsm_autostart_app_add_provides (GSM_AUTOSTART_APP (app), provides); + + g_debug ("GsmManager: read %s", path); + append_app (manager, app, provides, is_required); + g_object_unref (app); + + return TRUE; +} + +gboolean +gsm_manager_add_autostart_app (GsmManager *manager, + const char *path, + const char *provides) +{ + return add_autostart_app_internal (manager, + path, + provides, + FALSE); +} + +/** + * gsm_manager_add_required_app: + * @manager: a #GsmManager + * @path: Path to desktop file + * @provides: What the component provides, as a space separated list + * + * Similar to gsm_manager_add_autostart_app(), except marks the + * component as being required; we then try harder to ensure + * it's running and inform the user if we can't. + * + */ +gboolean +gsm_manager_add_required_app (GsmManager *manager, + const char *path, + const char *provides) +{ + return add_autostart_app_internal (manager, + path, + provides, + TRUE); +} + + +gboolean +gsm_manager_add_autostart_apps_from_dir (GsmManager *manager, + const char *path) +{ + GDir *dir; + const char *name; + + g_return_val_if_fail (GSM_IS_MANAGER (manager), FALSE); + g_return_val_if_fail (path != NULL, FALSE); + + g_debug ("GsmManager: *** Adding autostart apps for %s", path); + + dir = g_dir_open (path, 0, NULL); + if (dir == NULL) { + return FALSE; + } + + while ((name = g_dir_read_name (dir))) { + char *desktop_file; + + if (!g_str_has_suffix (name, ".desktop")) { + continue; + } + + desktop_file = g_build_filename (path, name, NULL); + gsm_manager_add_autostart_app (manager, desktop_file, NULL); + g_free (desktop_file); + } + + g_dir_close (dir); + + return TRUE; +} + +static void +on_shutdown_prepared (GsmSystem *system, + gboolean success, + GsmManager *manager) +{ + g_debug ("GsmManager: on_shutdown_prepared, success: %d", success); + g_signal_handlers_disconnect_by_func (system, on_shutdown_prepared, manager); + + if (success) { + /* move to end-session phase */ + g_assert (manager->priv->phase == GSM_MANAGER_PHASE_QUERY_END_SESSION); + manager->priv->phase++; + start_phase (manager); + } else { + disconnect_shell_dialog_signals (manager); + gsm_shell_close_end_session_dialog (manager->priv->shell); + /* back to running phase */ + cancel_end_session (manager); + } +} + +static gboolean +do_query_end_session_exit (GsmManager *manager) +{ + gboolean reboot = FALSE; + gboolean shutdown = FALSE; + + switch (manager->priv->logout_type) { + case GSM_MANAGER_LOGOUT_LOGOUT: + break; + case GSM_MANAGER_LOGOUT_REBOOT: + case GSM_MANAGER_LOGOUT_REBOOT_INTERACT: + reboot = TRUE; + break; + case GSM_MANAGER_LOGOUT_SHUTDOWN: + case GSM_MANAGER_LOGOUT_SHUTDOWN_INTERACT: + shutdown = TRUE; + break; + default: + g_warning ("Unexpected logout type %d in do_query_end_session_exit()", + manager->priv->logout_type); + break; + } + + if (reboot || shutdown) { + g_signal_connect (manager->priv->system, "shutdown-prepared", + G_CALLBACK (on_shutdown_prepared), manager); + gsm_system_prepare_shutdown (manager->priv->system, reboot); + return FALSE; /* don't leave query end session yet */ + } + + return TRUE; /* go to end session phase */ +} diff -Nru gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/data/gnome.desktop.in.in gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/data/gnome.desktop.in.in --- gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/data/gnome.desktop.in.in 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/data/gnome.desktop.in.in 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=GNOME +Comment=This session logs you into GNOME +Exec=@bindir@/gnome-session +TryExec=@bindir@/gnome-session +Type=Application +DesktopNames=GNOME diff -Nru gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/data/meson.build gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/data/meson.build --- gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/data/meson.build 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/data/meson.build 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,100 @@ +desktop_plain = 'gnome' + +desktops = [ + desktop_plain, + 'gnome-xorg' +] + +if enable_session_selector + desktops += 'gnome-custom-session' +endif + +foreach name: desktops + desktop_conf = configuration_data() + desktop_conf.set('bindir', session_bindir) + + desktop = name + '.desktop' + + desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf + ) + + install_dir = join_paths(session_datadir, 'xsessions') + # FIXME: The same target can not be copied into two directories. + # There is a workaround in meson_post_install.py until proper solution arises: + # https://groups.google.com/forum/#!topic/mesonbuild/3iIoYPrN4P0 + if name == desktop_plain + #install_dir: [ + # join_paths(session_datadir, 'xsessions'), + # join_paths(session_datadir, 'wayland-sessions') + #] + endif + + desktop_target = i18n.merge_file( + desktop, + type: 'desktop', + input: desktop_in, + output: desktop, + po_dir: po_dir, + install: true, + install_dir: install_dir + ) +endforeach + +sessions = [ + 'gnome.session', + 'gnome-dummy.session' +] + +foreach session: sessions + desktop_conf = configuration_data() + desktop_conf.set('libexecdir', session_libexecdir) + + desktop = session + '.desktop' + + desktop_in = configure_file( + input: desktop + '.in.in', + output: desktop + '.in', + configuration: desktop_conf + ) + + i18n.merge_file( + session, + type: 'desktop', + input: desktop_in, + output: session, + po_dir: po_dir, + install: true, + install_dir: join_paths(session_pkgdatadir, 'sessions') + ) +endforeach + +schema_conf = configuration_data() + +schema = 'org.gnome.SessionManager.gschema.xml' + +configure_file( + input: schema + '.in', + output: schema, + install: true, + install_dir: join_paths(session_datadir, 'glib-2.0', 'schemas'), + configuration: schema_conf +) + +install_data( + 'gnome-session.convert', + install_dir: join_paths(session_datadir, 'GConf', 'gsettings') +) + +data = files('hardware-compatibility') + +if enable_session_selector + data += files('session-selector.ui') +endif + +install_data( + data, + install_dir: session_pkgdatadir +) diff -Nru gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/po/POTFILES.in gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/po/POTFILES.in --- gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/po/POTFILES.in 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/po/POTFILES.in 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,19 @@ +# List of source files containing translatable strings. +# Please keep this file sorted alphabetically. +data/gnome-custom-session.desktop.in.in +data/gnome.desktop.in.in +data/gnome-dummy.session.desktop.in.in +data/gnome.session.desktop.in.in +data/gnome-xorg.desktop.in.in +data/org.gnome.SessionManager.gschema.xml.in +data/session-selector.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-quit.c +tools/gnome-session-selector.c diff -Nru gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/po/POTFILES.skip gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/po/POTFILES.skip --- gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/po/POTFILES.skip 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/ubuntu/ubuntu-sessions.patch/po/POTFILES.skip 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1,8 @@ +# List of source files containing translatable strings that should not be +# translated. +# Please keep this file sorted alphabetically. +data/gnome-custom-session.desktop.in +data/gnome.desktop.in +data/gnome-dummy.session.desktop.in +data/gnome.session.desktop.in +data/gnome-xorg.desktop.in diff -Nru gnome-session-3.30.1/.pc/.version gnome-session-3.30.1/.pc/.version --- gnome-session-3.30.1/.pc/.version 1970-01-01 00:00:00.000000000 +0000 +++ gnome-session-3.30.1/.pc/.version 2019-02-13 19:09:46.000000000 +0000 @@ -0,0 +1 @@ +2 diff -Nru gnome-session-3.30.1/po/POTFILES.in gnome-session-3.30.1/po/POTFILES.in --- gnome-session-3.30.1/po/POTFILES.in 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/po/POTFILES.in 2019-02-13 19:09:46.000000000 +0000 @@ -1,12 +1,25 @@ # 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.in data/gnome.desktop.in.in data/gnome-dummy.session.desktop.in.in data/gnome.session.desktop.in.in data/gnome-xorg.desktop.in.in data/org.gnome.SessionManager.gschema.xml.in +data/gnome-session-properties.desktop.in data/session-selector.ui +data/session-properties.ui +data/ubuntu.desktop.in.in +data/ubuntu-wayland.desktop.in.in +data/ubuntu-communitheme-snap.desktop.in.in +data/ubuntu-communitheme-snap-wayland.desktop.in.in +data/ubuntu.session.desktop.in.in +data/unity.desktop.in.in +data/unity.session.desktop.in.in gnome-session/gsm-fail-whale-dialog.c gnome-session/gsm-manager.c gnome-session/gsm-process-helper.c diff -Nru gnome-session-3.30.1/po/POTFILES.skip gnome-session-3.30.1/po/POTFILES.skip --- gnome-session-3.30.1/po/POTFILES.skip 2018-09-25 20:50:34.000000000 +0000 +++ gnome-session-3.30.1/po/POTFILES.skip 2019-02-13 19:09:46.000000000 +0000 @@ -6,3 +6,11 @@ data/gnome-dummy.session.desktop.in data/gnome.session.desktop.in data/gnome-xorg.desktop.in +data/gnome-session-properties.desktop +data/ubuntu.desktop.in +data/ubuntu-wayland.desktop.in +data/ubuntu-communitheme-snap.desktop.in +data/ubuntu-communitheme-snap-wayland.desktop.in +data/ubuntu.session.desktop.in +data/unity.desktop.in +data/unity.session.desktop.in