|
|
244a5d |
From 63b277e801d8bce92abe4e52f7de4cf059f1f896 Mon Sep 17 00:00:00 2001
|
|
|
5d36d8 |
From: Ray Strode <rstrode@redhat.com>
|
|
|
5d36d8 |
Date: Fri, 20 Dec 2013 11:28:53 -0500
|
|
|
5d36d8 |
Subject: [PATCH 11/19] Tie session selector to properties dialog
|
|
|
5d36d8 |
|
|
|
5d36d8 |
---
|
|
|
5d36d8 |
capplet/gsm-properties-dialog.c | 30 +++++-
|
|
|
5d36d8 |
configure.ac | 3 +-
|
|
|
5d36d8 |
data/session-selector.ui | 2 +-
|
|
|
5d36d8 |
tools/Makefile.am | 1 +
|
|
|
5d36d8 |
tools/gnome-session-selector.c | 211 ++++++++++++++++++++++++++++++++--------
|
|
|
5d36d8 |
5 files changed, 200 insertions(+), 47 deletions(-)
|
|
|
5d36d8 |
|
|
|
5d36d8 |
diff --git a/capplet/gsm-properties-dialog.c b/capplet/gsm-properties-dialog.c
|
|
|
5d36d8 |
index d2be778b..51fa5106 100644
|
|
|
5d36d8 |
--- a/capplet/gsm-properties-dialog.c
|
|
|
5d36d8 |
+++ b/capplet/gsm-properties-dialog.c
|
|
|
244a5d |
@@ -471,87 +471,113 @@ session_saved_message (GsmPropertiesDialog *dialog,
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
GtkLabel *label;
|
|
|
5d36d8 |
gchar *markup;
|
|
|
5d36d8 |
label = GTK_LABEL (gtk_builder_get_object (dialog->priv->xml, CAPPLET_SESSION_SAVED_WIDGET_NAME));
|
|
|
5d36d8 |
if (is_error)
|
|
|
5d36d8 |
markup = g_markup_printf_escaped ("%s", msg);
|
|
|
5d36d8 |
else
|
|
|
5d36d8 |
markup = g_markup_escape_text (msg, -1);
|
|
|
5d36d8 |
gtk_label_set_markup (label, markup);
|
|
|
5d36d8 |
g_free (markup);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
session_saved_cb (DBusGProxy *proxy,
|
|
|
5d36d8 |
DBusGProxyCall *call_id,
|
|
|
5d36d8 |
void *user_data)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
gboolean res;
|
|
|
5d36d8 |
GsmPropertiesDialog *dialog = user_data;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
res = dbus_g_proxy_end_call (proxy, call_id, NULL, G_TYPE_INVALID);
|
|
|
5d36d8 |
if (res)
|
|
|
5d36d8 |
session_saved_message (dialog, _("Your session has been saved."), FALSE);
|
|
|
5d36d8 |
else
|
|
|
5d36d8 |
session_saved_message (dialog, _("Failed to save session"), TRUE);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
g_object_unref (proxy);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
-on_save_session_clicked (GtkWidget *widget,
|
|
|
5d36d8 |
- GsmPropertiesDialog *dialog)
|
|
|
5d36d8 |
+save_session_directly (GsmPropertiesDialog *dialog)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
DBusGConnection *conn;
|
|
|
5d36d8 |
DBusGProxy *proxy;
|
|
|
5d36d8 |
DBusGProxyCall *call;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
conn = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
|
|
|
5d36d8 |
if (conn == NULL) {
|
|
|
5d36d8 |
session_saved_message (dialog, _("Could not connect to the session bus"), TRUE);
|
|
|
5d36d8 |
return;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
proxy = dbus_g_proxy_new_for_name (conn, GSM_SERVICE_DBUS, GSM_PATH_DBUS, GSM_INTERFACE_DBUS);
|
|
|
5d36d8 |
if (proxy == NULL) {
|
|
|
5d36d8 |
session_saved_message (dialog, _("Could not connect to the session manager"), TRUE);
|
|
|
5d36d8 |
return;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
call = dbus_g_proxy_begin_call (proxy, "SaveSession", session_saved_cb, dialog, NULL, G_TYPE_INVALID);
|
|
|
5d36d8 |
if (call == NULL) {
|
|
|
5d36d8 |
session_saved_message (dialog, _("Failed to save session"), TRUE);
|
|
|
5d36d8 |
g_object_unref (proxy);
|
|
|
5d36d8 |
return;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
244a5d |
+static void
|
|
|
5d36d8 |
+save_session_from_selector (GsmPropertiesDialog *dialog,
|
|
|
5d36d8 |
+ const char *program_path)
|
|
|
5d36d8 |
+{
|
|
|
5d36d8 |
+ char *command_line = g_strdup_printf ("%s --action save", program_path);
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ g_spawn_command_line_sync (command_line, NULL, NULL, NULL, NULL);
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ g_free (command_line);
|
|
|
5d36d8 |
+}
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+static void
|
|
|
5d36d8 |
+on_save_session_clicked (GtkWidget *widget,
|
|
|
5d36d8 |
+ GsmPropertiesDialog *dialog)
|
|
|
5d36d8 |
+{
|
|
|
5d36d8 |
+ char *program_path;
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ program_path = g_find_program_in_path ("gnome-session-selector");
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ if (program_path != NULL) {
|
|
|
5d36d8 |
+ save_session_from_selector (dialog, program_path);
|
|
|
5d36d8 |
+ g_free (program_path);
|
|
|
5d36d8 |
+ } else {
|
|
|
5d36d8 |
+ save_session_directly (dialog);
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
+}
|
|
|
5d36d8 |
+
|
|
|
244a5d |
static void
|
|
|
5d36d8 |
setup_dialog (GsmPropertiesDialog *dialog)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
GtkTreeView *treeview;
|
|
|
5d36d8 |
GtkWidget *button;
|
|
|
5d36d8 |
GtkTreeModel *tree_filter;
|
|
|
5d36d8 |
GtkTreeViewColumn *column;
|
|
|
5d36d8 |
GtkCellRenderer *renderer;
|
|
|
5d36d8 |
GtkTreeSelection *selection;
|
|
|
5d36d8 |
GtkTargetList *targetlist;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_dialog_add_buttons (GTK_DIALOG (dialog),
|
|
|
5d36d8 |
GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
|
|
|
5d36d8 |
NULL);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
dialog->priv->list_store = gtk_list_store_new (NUMBER_OF_COLUMNS,
|
|
|
5d36d8 |
G_TYPE_BOOLEAN,
|
|
|
5d36d8 |
G_TYPE_BOOLEAN,
|
|
|
5d36d8 |
G_TYPE_ICON,
|
|
|
5d36d8 |
G_TYPE_STRING,
|
|
|
5d36d8 |
G_TYPE_OBJECT,
|
|
|
5d36d8 |
G_TYPE_STRING);
|
|
|
5d36d8 |
tree_filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (dialog->priv->list_store),
|
|
|
5d36d8 |
NULL);
|
|
|
5d36d8 |
g_object_unref (dialog->priv->list_store);
|
|
|
5d36d8 |
dialog->priv->tree_filter = tree_filter;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (tree_filter),
|
|
|
5d36d8 |
STORE_COL_VISIBLE);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
diff --git a/configure.ac b/configure.ac
|
|
|
244a5d |
index 73e69bc7..d0eeab8a 100644
|
|
|
5d36d8 |
--- a/configure.ac
|
|
|
5d36d8 |
+++ b/configure.ac
|
|
|
5d36d8 |
@@ -21,91 +21,90 @@ LT_PREREQ([2.2.6])
|
|
|
5d36d8 |
LT_INIT([dlopen disable-static])
|
|
|
5d36d8 |
|
|
|
5d36d8 |
GNOME_MAINTAINER_MODE_DEFINES
|
|
|
5d36d8 |
GNOME_COMPILE_WARNINGS([maximum])
|
|
|
5d36d8 |
|
|
|
5d36d8 |
AC_ARG_ENABLE(deprecation_flags,
|
|
|
5d36d8 |
[AS_HELP_STRING([--enable-deprecation-flags],
|
|
|
5d36d8 |
[use *_DISABLE_DEPRECATED flags @<:@default=no@:>@])],,
|
|
|
5d36d8 |
[enable_deprecation_flags=no])
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if test "x$enable_deprecation_flags" = "xyes"; then
|
|
|
5d36d8 |
DISABLE_DEPRECATED_CFLAGS=$DISABLE_DEPRECATED
|
|
|
5d36d8 |
AC_SUBST([DISABLE_DEPRECATED_CFLAGS])
|
|
|
5d36d8 |
fi
|
|
|
5d36d8 |
|
|
|
5d36d8 |
GLIB_REQUIRED=2.46.0
|
|
|
5d36d8 |
GTK3_REQUIRED=3.18.0
|
|
|
5d36d8 |
DBUS_GLIB_REQUIRED=0.76
|
|
|
5d36d8 |
UPOWER_REQUIRED=0.9.0
|
|
|
5d36d8 |
JSON_GLIB_REQUIRED=0.10
|
|
|
5d36d8 |
GNOME_DESKTOP_REQUIRED=3.18.0
|
|
|
5d36d8 |
|
|
|
5d36d8 |
AC_ARG_ENABLE(session-selector, AS_HELP_STRING([--enable-session-selector],
|
|
|
5d36d8 |
[enable building a custom session selector dialog]),
|
|
|
5d36d8 |
enable_session_selector=$enableval,enable_session_selector=no)
|
|
|
5d36d8 |
|
|
|
5d36d8 |
AM_CONDITIONAL(BUILD_SESSION_SELECTOR,
|
|
|
5d36d8 |
[test "$enable_session_selector" = yes])
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if test "$enable_session_selector" = yes; then
|
|
|
5d36d8 |
- PKG_CHECK_MODULES(SESSION_SELECTOR, gtk+-3.0 gio-2.0)
|
|
|
5d36d8 |
+ PKG_CHECK_MODULES(SESSION_SELECTOR, gtk+-3.0 gio-2.0 dbus-glib-1 >= $DBUS_GLIB_REQUIRED)
|
|
|
5d36d8 |
fi
|
|
|
5d36d8 |
|
|
|
5d36d8 |
dnl ====================================================================
|
|
|
5d36d8 |
dnl Dependency Checks
|
|
|
5d36d8 |
dnl ====================================================================
|
|
|
5d36d8 |
|
|
|
5d36d8 |
dnl Standard vertical stacks
|
|
|
5d36d8 |
PKG_CHECK_MODULES(GIO, gio-2.0)
|
|
|
5d36d8 |
PKG_CHECK_MODULES(GIOUNIX, gio-unix-2.0 >= $GLIB_REQUIRED)
|
|
|
5d36d8 |
PKG_CHECK_MODULES(GTK3, gtk+-3.0 >= $GTK3_REQUIRED)
|
|
|
5d36d8 |
|
|
|
5d36d8 |
PKG_CHECK_MODULES(GNOME_SESSION,
|
|
|
5d36d8 |
glib-2.0 >= $GLIB_REQUIRED
|
|
|
5d36d8 |
gio-2.0 >= $GLIB_REQUIRED
|
|
|
5d36d8 |
json-glib-1.0 >= $JSON_GLIB_REQUIRED
|
|
|
5d36d8 |
gnome-desktop-3.0 >= $GNOME_DESKTOP_REQUIRED
|
|
|
5d36d8 |
)
|
|
|
5d36d8 |
|
|
|
5d36d8 |
dnl We can only support old upower
|
|
|
5d36d8 |
dnl https://bugzilla.gnome.org/show_bug.cgi?id=710383
|
|
|
5d36d8 |
PKG_CHECK_MODULES(UPOWER, upower-glib < 0.99.0, have_old_upower=yes, have_old_upower=no)
|
|
|
5d36d8 |
AS_IF([test x$have_old_upower = xyes], [
|
|
|
5d36d8 |
AC_DEFINE([HAVE_OLD_UPOWER], [1], [Define if we have an older upower])
|
|
|
5d36d8 |
])
|
|
|
5d36d8 |
AM_CONDITIONAL(HAVE_OLD_UPOWER, test x$have_old_upower = xyes)
|
|
|
5d36d8 |
|
|
|
5d36d8 |
PKG_CHECK_MODULES(SESSION_PROPERTIES,
|
|
|
5d36d8 |
glib-2.0 >= $GLIB_REQUIRED
|
|
|
5d36d8 |
gtk+-3.0 >= $GTK3_REQUIRED
|
|
|
5d36d8 |
- dbus-glib-1 >= $DBUS_GLIB_REQUIRED
|
|
|
5d36d8 |
)
|
|
|
5d36d8 |
|
|
|
5d36d8 |
PKG_CHECK_MODULES(X11, x11)
|
|
|
5d36d8 |
PKG_CHECK_MODULES(SM, sm)
|
|
|
5d36d8 |
PKG_CHECK_MODULES(ICE, ice)
|
|
|
5d36d8 |
PKG_CHECK_MODULES(XEXT, xext xau)
|
|
|
5d36d8 |
|
|
|
5d36d8 |
PKG_CHECK_MODULES(GL_TEST, xcomposite gl glib-2.0 epoxy)
|
|
|
5d36d8 |
PKG_CHECK_MODULES(GLES_TEST, egl glesv2)
|
|
|
5d36d8 |
|
|
|
5d36d8 |
dnl ====================================================================
|
|
|
5d36d8 |
dnl Check for gconf
|
|
|
5d36d8 |
dnl ====================================================================
|
|
|
5d36d8 |
AC_ARG_ENABLE([gconf],
|
|
|
5d36d8 |
AS_HELP_STRING([--enable-gconf], [Support gconf-based autostart]),
|
|
|
5d36d8 |
[enable_gconf=$enableval],
|
|
|
5d36d8 |
[enable_gconf=auto])
|
|
|
5d36d8 |
|
|
|
244a5d |
if test x$enable_gconf != xno ; then
|
|
|
244a5d |
PKG_CHECK_MODULES(GCONF, gconf-2.0, [have_gconf=yes], [have_gconf=no])
|
|
|
5d36d8 |
|
|
|
244a5d |
if test x$enable_gconf = xyes -a x$have_gconf = xno ; then
|
|
|
244a5d |
AC_MSG_ERROR([GConf support explicitly required, but gconf not found])
|
|
|
244a5d |
fi
|
|
|
5d36d8 |
|
|
|
244a5d |
if test x$have_gconf = xyes ; then
|
|
|
244a5d |
AC_DEFINE([HAVE_GCONF], [1], [Define if we support gconf-based autostart])
|
|
|
244a5d |
fi
|
|
|
5d36d8 |
fi
|
|
|
5d36d8 |
|
|
|
5d36d8 |
diff --git a/data/session-selector.ui b/data/session-selector.ui
|
|
|
5d36d8 |
index 1c55712d..1534a746 100644
|
|
|
5d36d8 |
--- a/data/session-selector.ui
|
|
|
5d36d8 |
+++ b/data/session-selector.ui
|
|
|
5d36d8 |
@@ -20,61 +20,61 @@
|
|
|
5d36d8 |
<child>
|
|
|
5d36d8 |
<object class="GtkFrame" id="frame1">
|
|
|
5d36d8 |
<property name="visible">True</property>
|
|
|
5d36d8 |
<property name="label_xalign">0.5</property>
|
|
|
5d36d8 |
<property name="shadow_type">out</property>
|
|
|
5d36d8 |
<child>
|
|
|
5d36d8 |
<object class="GtkAlignment" id="alignment3">
|
|
|
5d36d8 |
<property name="visible">True</property>
|
|
|
5d36d8 |
<property name="border_width">12</property>
|
|
|
5d36d8 |
<child>
|
|
|
5d36d8 |
<object class="GtkVBox" id="vbox3">
|
|
|
5d36d8 |
<property name="visible">True</property>
|
|
|
5d36d8 |
<property name="orientation">vertical</property>
|
|
|
5d36d8 |
<property name="spacing">6</property>
|
|
|
5d36d8 |
|
|
|
5d36d8 |
<child>
|
|
|
5d36d8 |
<object class="GtkInfoBar" id="info-bar">
|
|
|
5d36d8 |
<property name="visible">True</property>
|
|
|
5d36d8 |
<property name="message-type">other</property>
|
|
|
5d36d8 |
|
|
|
5d36d8 |
<child internal-child="content_area">
|
|
|
5d36d8 |
<object class="GtkHBox" id="info-bar-content_area">
|
|
|
5d36d8 |
<property name="visible">True</property>
|
|
|
5d36d8 |
<property name="orientation">vertical</property>
|
|
|
5d36d8 |
<property name="spacing">0</property>
|
|
|
5d36d8 |
<child>
|
|
|
5d36d8 |
<object class="GtkLabel" id="info-label">
|
|
|
5d36d8 |
<property name="visible">True</property>
|
|
|
5d36d8 |
<property name="xalign">0.0</property>
|
|
|
5d36d8 |
<property name="yalign">0.5</property>
|
|
|
5d36d8 |
- <property name="label" translatable="yes">Please select a custom session to run</property>
|
|
|
5d36d8 |
+ <property name="label" translatable="yes">Please select a custom session to use</property>
|
|
|
5d36d8 |
</object>
|
|
|
5d36d8 |
<packing>
|
|
|
5d36d8 |
<property name="expand">True</property>
|
|
|
5d36d8 |
<property name="fill">True</property>
|
|
|
5d36d8 |
<property name="position">0</property>
|
|
|
5d36d8 |
</packing>
|
|
|
5d36d8 |
</child>
|
|
|
5d36d8 |
</object>
|
|
|
5d36d8 |
</child>
|
|
|
5d36d8 |
</object>
|
|
|
5d36d8 |
<packing>
|
|
|
5d36d8 |
<property name="expand">False</property>
|
|
|
5d36d8 |
<property name="fill">True</property>
|
|
|
5d36d8 |
<property name="position">0</property>
|
|
|
5d36d8 |
</packing>
|
|
|
5d36d8 |
</child>
|
|
|
5d36d8 |
<child>
|
|
|
5d36d8 |
<object class="GtkVBox" id="vbox4">
|
|
|
5d36d8 |
<property name="visible">True</property>
|
|
|
5d36d8 |
<property name="orientation">vertical</property>
|
|
|
5d36d8 |
<property name="spacing">12</property>
|
|
|
5d36d8 |
<child>
|
|
|
5d36d8 |
<object class="GtkHBox" id="hbox3">
|
|
|
5d36d8 |
<property name="visible">True</property>
|
|
|
5d36d8 |
<property name="spacing">12</property>
|
|
|
5d36d8 |
<child>
|
|
|
5d36d8 |
<object class="GtkScrolledWindow" id="scrolledwindow2">
|
|
|
5d36d8 |
<property name="visible">True</property>
|
|
|
5d36d8 |
<property name="can_focus">True</property>
|
|
|
5d36d8 |
<property name="hscrollbar_policy">never</property>
|
|
|
5d36d8 |
diff --git a/tools/Makefile.am b/tools/Makefile.am
|
|
|
5d36d8 |
index d3b8bf3f..d6784d1b 100644
|
|
|
5d36d8 |
--- a/tools/Makefile.am
|
|
|
5d36d8 |
+++ b/tools/Makefile.am
|
|
|
5d36d8 |
@@ -68,37 +68,38 @@ gnome_session_check_accelerated_gl_helper_CPPFLAGS = \
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gnome_session_check_accelerated_gl_helper_LDADD = \
|
|
|
5d36d8 |
$(GL_TEST_LIBS) \
|
|
|
5d36d8 |
$(X11_LIBS)
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gnome_session_check_accelerated_SOURCES = \
|
|
|
5d36d8 |
gnome-session-check-accelerated-common.h \
|
|
|
5d36d8 |
gnome-session-check-accelerated.c
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gnome_session_check_accelerated_CPPFLAGS = \
|
|
|
5d36d8 |
-DLIBEXECDIR=\""$(libexecdir)"\" \
|
|
|
5d36d8 |
$(AM_CPPFLAGS) \
|
|
|
5d36d8 |
$(GTK3_CFLAGS) \
|
|
|
5d36d8 |
$(GL_TEST_CFLAGS)
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gnome_session_check_accelerated_LDADD = \
|
|
|
5d36d8 |
$(GTK3_LIBS) \
|
|
|
5d36d8 |
$(X11_LIBS) \
|
|
|
5d36d8 |
$(GL_TEST_LIBS)
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if BUILD_SESSION_SELECTOR
|
|
|
5d36d8 |
gnome_session_selector_CPPFLAGS = \
|
|
|
5d36d8 |
$(AM_CPPFLAGS) \
|
|
|
5d36d8 |
$(GNOME_SESSION_CFLAGS) \
|
|
|
5d36d8 |
$(DBUS_GLIB_CFLAGS) \
|
|
|
5d36d8 |
-DGTKBUILDER_DIR=\""$(pkgdatadir)"\" \
|
|
|
5d36d8 |
-DLOCALE_DIR=\""$(datadir)/locale"\" \
|
|
|
5d36d8 |
$(DISABLE_DEPRECATED_CFLAGS)
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gnome_session_selector_LDADD = \
|
|
|
5d36d8 |
+ $(DBUS_GLIB_CFLAGS) \
|
|
|
5d36d8 |
$(SESSION_SELECTOR_LIBS)
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gnome_session_selector_SOURCES = \
|
|
|
5d36d8 |
gnome-session-selector.c
|
|
|
5d36d8 |
endif
|
|
|
5d36d8 |
|
|
|
5d36d8 |
-include $(top_srcdir)/git.mk
|
|
|
5d36d8 |
diff --git a/tools/gnome-session-selector.c b/tools/gnome-session-selector.c
|
|
|
244a5d |
index 71892c43..53822f6c 100644
|
|
|
5d36d8 |
--- a/tools/gnome-session-selector.c
|
|
|
5d36d8 |
+++ b/tools/gnome-session-selector.c
|
|
|
5d36d8 |
@@ -7,126 +7,133 @@
|
|
|
5d36d8 |
* the Free Software Foundation; either version 2 of the License, or
|
|
|
5d36d8 |
* (at your option) any later version.
|
|
|
5d36d8 |
*
|
|
|
5d36d8 |
* This program is distributed in the hope that it will be useful,
|
|
|
5d36d8 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
5d36d8 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
5d36d8 |
* GNU General Public License for more details.
|
|
|
5d36d8 |
*
|
|
|
5d36d8 |
* You should have received a copy of the GNU General Public License
|
|
|
5d36d8 |
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
5d36d8 |
*
|
|
|
5d36d8 |
* Written by: Matthias Clasen <mclasen@redhat.com>
|
|
|
5d36d8 |
*/
|
|
|
5d36d8 |
|
|
|
5d36d8 |
#include "config.h"
|
|
|
5d36d8 |
|
|
|
5d36d8 |
#include <fcntl.h>
|
|
|
5d36d8 |
#include <stdlib.h>
|
|
|
5d36d8 |
#include <string.h>
|
|
|
5d36d8 |
#include <sys/types.h>
|
|
|
5d36d8 |
#include <sys/stat.h>
|
|
|
5d36d8 |
#include <unistd.h>
|
|
|
5d36d8 |
|
|
|
5d36d8 |
#include <glib.h>
|
|
|
5d36d8 |
#include <gtk/gtk.h>
|
|
|
5d36d8 |
#include <gio/gio.h>
|
|
|
5d36d8 |
|
|
|
5d36d8 |
#include <glib/gi18n.h>
|
|
|
5d36d8 |
#include <glib/gstdio.h>
|
|
|
5d36d8 |
|
|
|
5d36d8 |
+#include <dbus/dbus-glib.h>
|
|
|
5d36d8 |
+#include <dbus/dbus-glib-lowlevel.h>
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+#define GSM_SERVICE_DBUS "org.gnome.SessionManager"
|
|
|
5d36d8 |
+#define GSM_PATH_DBUS "/org/gnome/SessionManager"
|
|
|
5d36d8 |
+#define GSM_INTERFACE_DBUS "org.gnome.SessionManager"
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
#define GSM_MANAGER_SCHEMA "org.gnome.SessionManager"
|
|
|
5d36d8 |
#define KEY_AUTOSAVE_ONE_SHOT "auto-save-session-one-shot"
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static GtkBuilder *builder;
|
|
|
5d36d8 |
static GtkWidget *session_list;
|
|
|
5d36d8 |
static GtkListStore *store;
|
|
|
5d36d8 |
static GtkTreeModelSort *sort_model;
|
|
|
5d36d8 |
+static char *info_text;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void select_session (const char *name);
|
|
|
5d36d8 |
+static gboolean make_session_current (const char *name);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static char *
|
|
|
5d36d8 |
get_session_path (const char *name)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
return g_build_filename (g_get_user_config_dir (), "gnome-session", name, NULL);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static char *
|
|
|
5d36d8 |
find_new_session_name (void)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
char *name;
|
|
|
5d36d8 |
char *path;
|
|
|
5d36d8 |
int i;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
for (i = 1; i < 20; i++) {
|
|
|
5d36d8 |
name = g_strdup_printf (_("Session %d"), i);
|
|
|
5d36d8 |
path = get_session_path (name);
|
|
|
5d36d8 |
if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
|
|
|
5d36d8 |
g_free (path);
|
|
|
5d36d8 |
return name;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
g_free (path);
|
|
|
5d36d8 |
g_free (name);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
return NULL;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static gboolean
|
|
|
5d36d8 |
is_valid_session_name (const char *name)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
GtkTreeIter iter;
|
|
|
5d36d8 |
char *n;
|
|
|
5d36d8 |
- const char *info_text;
|
|
|
5d36d8 |
char *warning_text;
|
|
|
5d36d8 |
gboolean user_tried_dot;
|
|
|
5d36d8 |
gboolean user_tried_slash;
|
|
|
5d36d8 |
GtkWidget *info_bar;
|
|
|
5d36d8 |
GtkWidget *label;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if (name[0] == 0) {
|
|
|
5d36d8 |
return FALSE;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if (name[0] == '.') {
|
|
|
5d36d8 |
user_tried_dot = TRUE;
|
|
|
5d36d8 |
} else {
|
|
|
5d36d8 |
user_tried_dot = FALSE;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if (strchr (name, '/') != NULL) {
|
|
|
5d36d8 |
user_tried_slash = TRUE;
|
|
|
5d36d8 |
} else {
|
|
|
5d36d8 |
user_tried_slash = FALSE;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
- info_text = _("Please select a custom session to run");
|
|
|
5d36d8 |
warning_text = NULL;
|
|
|
5d36d8 |
if (user_tried_dot && user_tried_slash) {
|
|
|
5d36d8 |
warning_text = g_strdup_printf ("%s\n<small>Note: %s</small>",
|
|
|
5d36d8 |
info_text,
|
|
|
244a5d |
_("Session names are not allowed to start with “.” or contain “/” characters"));
|
|
|
5d36d8 |
} else if (user_tried_dot) {
|
|
|
5d36d8 |
warning_text = g_strdup_printf ("%s\n<small>Note: %s</small>",
|
|
|
5d36d8 |
info_text,
|
|
|
244a5d |
_("Session names are not allowed to start with “.”"));
|
|
|
5d36d8 |
} else if (user_tried_slash) {
|
|
|
5d36d8 |
warning_text = g_strdup_printf ("%s\n<small>Note: %s</small>",
|
|
|
5d36d8 |
info_text,
|
|
|
244a5d |
_("Session names are not allowed to contain “/” characters"));
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
|
|
|
5d36d8 |
do {
|
|
|
5d36d8 |
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 0, &n, -1);
|
|
|
5d36d8 |
if (strcmp (n, name) == 0) {
|
|
|
5d36d8 |
char *message;
|
|
|
244a5d |
message = g_strdup_printf (_("A session named “%s” already exists"), name);
|
|
|
5d36d8 |
warning_text = g_strdup_printf ("%s\n<small>Note: %s</small>", info_text, message);
|
|
|
5d36d8 |
g_free (message);
|
|
|
5d36d8 |
g_free (n);
|
|
|
5d36d8 |
break;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
g_free (n);
|
|
|
5d36d8 |
} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
|
|
|
5d36d8 |
|
|
|
5d36d8 |
info_bar = (GtkWidget *) gtk_builder_get_object (builder, "info-bar");
|
|
|
244a5d |
@@ -181,518 +188,638 @@ populate_session_list (GtkWidget *session_list)
|
|
|
244a5d |
|
|
|
5d36d8 |
default_name = NULL;
|
|
|
5d36d8 |
if (readlink (saved_session, last_session, PATH_MAX - 1) > 0) {
|
|
|
5d36d8 |
default_name = g_path_get_basename (last_session);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
while ((name = g_dir_read_name (dir)) != NULL) {
|
|
|
5d36d8 |
if (strcmp (name, "saved-session") == 0)
|
|
|
5d36d8 |
continue;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_list_store_insert_with_values (store, &iter, 100, 0, name, -1);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if (g_strcmp0 (default_name, name) == 0) {
|
|
|
5d36d8 |
GtkTreeSelection *selection;
|
|
|
5d36d8 |
GtkTreeIter child_iter;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_tree_model_sort_convert_child_iter_to_iter (GTK_TREE_MODEL_SORT (sort_model), &child_iter, &iter);
|
|
|
5d36d8 |
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (session_list));
|
|
|
5d36d8 |
gtk_tree_selection_select_iter (selection, &child_iter);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
g_free (default_name);
|
|
|
5d36d8 |
g_dir_close (dir);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
out:
|
|
|
5d36d8 |
g_free (saved_session);
|
|
|
5d36d8 |
g_free (path);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
244a5d |
+static char *
|
|
|
5d36d8 |
+get_last_session (void)
|
|
|
5d36d8 |
+{
|
|
|
5d36d8 |
+ char *saved_session;
|
|
|
5d36d8 |
+ char last_session[PATH_MAX] = "";
|
|
|
5d36d8 |
+ char *name = NULL;
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ saved_session = get_session_path ("saved-session");
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ if (readlink (saved_session, last_session, PATH_MAX - 1) > 0) {
|
|
|
5d36d8 |
+ name = g_path_get_basename (last_session);
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ g_free (saved_session);
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ return name;
|
|
|
5d36d8 |
+}
|
|
|
5d36d8 |
+
|
|
|
244a5d |
static char *
|
|
|
5d36d8 |
get_selected_session (void)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
GtkTreeSelection *selection;
|
|
|
5d36d8 |
GtkTreeModel *model;
|
|
|
5d36d8 |
GtkTreeIter iter;
|
|
|
5d36d8 |
gchar *name;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (session_list));
|
|
|
5d36d8 |
if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
|
|
|
5d36d8 |
gtk_tree_model_get (model, &iter, 0, &name, -1);
|
|
|
5d36d8 |
return name;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
return NULL;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
remove_session (const char *name)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
char *path1, *path2;
|
|
|
5d36d8 |
char *n, *path;
|
|
|
5d36d8 |
const char *d;
|
|
|
5d36d8 |
GDir *dir;
|
|
|
5d36d8 |
GError *error;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
path1 = get_session_path ("saved-session");
|
|
|
5d36d8 |
path2 = get_session_path (name);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
error = NULL;
|
|
|
5d36d8 |
n = g_file_read_link (path1, &error);
|
|
|
5d36d8 |
if (n == NULL) {
|
|
|
5d36d8 |
g_warning ("Failed to read link: %s", error->message);
|
|
|
5d36d8 |
g_error_free (error);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
else if (strcmp (n, name) == 0) {
|
|
|
5d36d8 |
unlink (path1);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
g_free (n);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
dir = g_dir_open (path2, 0, NULL);
|
|
|
5d36d8 |
while ((d = g_dir_read_name (dir)) != NULL) {
|
|
|
5d36d8 |
path = g_build_filename (path2, d, NULL);
|
|
|
5d36d8 |
unlink (path);
|
|
|
5d36d8 |
g_free (path);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
g_dir_close (dir);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
remove (path2);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
g_free (path1);
|
|
|
5d36d8 |
g_free (path2);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
+static gboolean
|
|
|
5d36d8 |
+make_session_current (const char *name)
|
|
|
5d36d8 |
+{
|
|
|
5d36d8 |
+ char *path1;
|
|
|
5d36d8 |
+ gboolean ret = TRUE;
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ path1 = g_build_filename (g_get_user_config_dir (), "gnome-session", "saved-session", NULL);
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ unlink (path1);
|
|
|
5d36d8 |
+ if (symlink (name, path1) < 0) {
|
|
|
5d36d8 |
+ g_warning ("Failed to make session '%s' current", name);
|
|
|
5d36d8 |
+ ret = FALSE;
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ g_free (path1);
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ return ret;
|
|
|
5d36d8 |
+}
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
on_remove_session_clicked (GtkButton *button,
|
|
|
5d36d8 |
gpointer data)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
GtkTreeSelection *selection;
|
|
|
5d36d8 |
GtkTreeModel *model;
|
|
|
5d36d8 |
GtkTreeIter iter;
|
|
|
5d36d8 |
char *name;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (session_list));
|
|
|
5d36d8 |
if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
|
|
|
5d36d8 |
GtkTreeIter child_iter;
|
|
|
5d36d8 |
gtk_tree_model_get (model, &iter, 0, &name, -1);
|
|
|
5d36d8 |
remove_session (name);
|
|
|
5d36d8 |
g_free (name);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (model), &child_iter, &iter);
|
|
|
5d36d8 |
gtk_list_store_remove (GTK_LIST_STORE (store), &child_iter);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if (!gtk_tree_selection_get_selected (selection, NULL, NULL)) {
|
|
|
5d36d8 |
gtk_tree_model_get_iter_first (model, &iter);
|
|
|
5d36d8 |
gtk_tree_model_get (model, &iter, 0, &name, -1);
|
|
|
5d36d8 |
select_session (name);
|
|
|
5d36d8 |
+ make_session_current (name);
|
|
|
5d36d8 |
g_free (name);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
begin_rename (void)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
GtkTreePath *path;
|
|
|
5d36d8 |
GtkTreeViewColumn *column;
|
|
|
5d36d8 |
GList *cells;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_widget_grab_focus (session_list);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_tree_view_get_cursor (GTK_TREE_VIEW (session_list),
|
|
|
5d36d8 |
&path, &column);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if (cells != NULL) {
|
|
|
5d36d8 |
GtkCellRenderer *cell;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
cell = (GtkCellRenderer *) cells->data;
|
|
|
5d36d8 |
g_list_free (cells);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
g_object_set (cell, "editable", TRUE, NULL);
|
|
|
5d36d8 |
gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (session_list), path,
|
|
|
5d36d8 |
column, cell, TRUE);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
gtk_tree_path_free (path);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
on_rename_session_clicked (GtkButton *button,
|
|
|
5d36d8 |
gpointer data)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
begin_rename ();
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
on_continue_clicked (GtkButton *button,
|
|
|
5d36d8 |
gpointer data)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
char *name;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
name = get_selected_session ();
|
|
|
5d36d8 |
g_free (name);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_main_quit ();
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
create_session (const char *name)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
char *path;
|
|
|
5d36d8 |
GtkTreeIter iter;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
path = get_session_path (name);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
- if (mkdir (path, 0755) < 0) {
|
|
|
5d36d8 |
+ if (g_mkdir_with_parents (path, 0755) < 0) {
|
|
|
5d36d8 |
g_warning ("Failed to create directory %s", path);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
else {
|
|
|
5d36d8 |
char *marker;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_list_store_insert_with_values (store, &iter, 100, 0, name, -1);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
marker = g_build_filename (path, ".new-session", NULL);
|
|
|
5d36d8 |
creat (marker, 0600);
|
|
|
5d36d8 |
g_free (marker);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
g_free (path);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static gboolean
|
|
|
5d36d8 |
rename_session (const char *old_name,
|
|
|
5d36d8 |
const char *new_name)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
char *old_path, *new_path;
|
|
|
5d36d8 |
int result;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if (g_strcmp0 (old_name, new_name) == 0) {
|
|
|
5d36d8 |
return TRUE;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if (!is_valid_session_name (new_name)) {
|
|
|
5d36d8 |
return FALSE;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
old_path = get_session_path (old_name);
|
|
|
5d36d8 |
new_path = get_session_path (new_name);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
result = g_rename (old_path, new_path);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if (result < 0) {
|
|
|
5d36d8 |
g_warning ("Failed to rename session from '%s' to '%s': %m", old_name, new_name);
|
|
|
5d36d8 |
+ } else {
|
|
|
5d36d8 |
+ char *last_session;
|
|
|
5d36d8 |
+ last_session = get_last_session ();
|
|
|
5d36d8 |
+ if (g_strcmp0 (old_name, last_session) == 0) {
|
|
|
5d36d8 |
+ make_session_current (new_name);
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
+ g_free (last_session);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
g_free (old_path);
|
|
|
5d36d8 |
g_free (new_path);
|
|
|
5d36d8 |
-
|
|
|
5d36d8 |
return result == 0;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
244a5d |
-static gboolean
|
|
|
5d36d8 |
-make_session_current (const char *name)
|
|
|
5d36d8 |
-{
|
|
|
5d36d8 |
- char *path1;
|
|
|
5d36d8 |
- gboolean ret = TRUE;
|
|
|
5d36d8 |
-
|
|
|
5d36d8 |
- path1 = g_build_filename (g_get_user_config_dir (), "gnome-session", "saved-session", NULL);
|
|
|
5d36d8 |
-
|
|
|
5d36d8 |
- unlink (path1);
|
|
|
5d36d8 |
- if (symlink (name, path1) < 0) {
|
|
|
5d36d8 |
- g_warning ("Failed to make session '%s' current", name);
|
|
|
5d36d8 |
- ret = FALSE;
|
|
|
5d36d8 |
- }
|
|
|
5d36d8 |
-
|
|
|
5d36d8 |
- g_free (path1);
|
|
|
5d36d8 |
-
|
|
|
5d36d8 |
- return ret;
|
|
|
5d36d8 |
-}
|
|
|
5d36d8 |
-
|
|
|
244a5d |
static gboolean
|
|
|
5d36d8 |
create_and_select_session (const char *name)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
gchar *path;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if (name[0] == 0 || name[0] == '.' || strchr (name, '/')) {
|
|
|
5d36d8 |
g_warning ("Invalid session name");
|
|
|
5d36d8 |
return FALSE;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
path = g_build_filename (g_get_user_config_dir (), "gnome-session", name, NULL);
|
|
|
5d36d8 |
if (!g_file_test (path, G_FILE_TEST_IS_DIR)) {
|
|
|
5d36d8 |
- if (mkdir (path, 0755) < 0) {
|
|
|
5d36d8 |
+ if (g_mkdir_with_parents (path, 0755) < 0) {
|
|
|
5d36d8 |
g_warning ("Failed to create directory %s", path);
|
|
|
5d36d8 |
g_free (path);
|
|
|
5d36d8 |
return FALSE;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
g_free (path);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
return make_session_current (name);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
select_session (const char *name)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
GtkTreeIter iter;
|
|
|
5d36d8 |
char *n;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
- make_session_current (name);
|
|
|
5d36d8 |
-
|
|
|
5d36d8 |
/* now select it in the list */
|
|
|
5d36d8 |
gtk_tree_model_get_iter_first (GTK_TREE_MODEL (sort_model), &iter);
|
|
|
5d36d8 |
do {
|
|
|
5d36d8 |
gtk_tree_model_get (GTK_TREE_MODEL (sort_model), &iter, 0, &n, -1);
|
|
|
5d36d8 |
if (strcmp (n, name) == 0) {
|
|
|
5d36d8 |
GtkTreePath *path;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
path = gtk_tree_model_get_path (GTK_TREE_MODEL (sort_model), &iter);
|
|
|
5d36d8 |
gtk_tree_view_set_cursor (GTK_TREE_VIEW (session_list), path, NULL, FALSE);
|
|
|
5d36d8 |
gtk_tree_path_free (path);
|
|
|
5d36d8 |
g_free (n);
|
|
|
5d36d8 |
break;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
g_free (n);
|
|
|
5d36d8 |
} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (sort_model), &iter));
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
-on_new_session_clicked (GtkButton *button,
|
|
|
5d36d8 |
- gpointer data)
|
|
|
5d36d8 |
+create_session_and_begin_rename (void)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
gchar *name;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
name = find_new_session_name ();
|
|
|
5d36d8 |
create_session (name);
|
|
|
5d36d8 |
select_session (name);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
begin_rename ();
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
244a5d |
+static void
|
|
|
5d36d8 |
+on_new_session_clicked (GtkButton *button,
|
|
|
5d36d8 |
+ gpointer data)
|
|
|
5d36d8 |
+{
|
|
|
5d36d8 |
+ create_session_and_begin_rename ();
|
|
|
5d36d8 |
+}
|
|
|
5d36d8 |
+
|
|
|
244a5d |
static void
|
|
|
5d36d8 |
on_selection_changed (GtkTreeSelection *selection,
|
|
|
5d36d8 |
gpointer data)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
char *name;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
name = get_selected_session ();
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if (name == NULL) {
|
|
|
5d36d8 |
return;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
- make_session_current (name);
|
|
|
5d36d8 |
-
|
|
|
5d36d8 |
g_free (name);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
update_remove_button (void)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
GtkWidget *button;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
button = (GtkWidget *)gtk_builder_get_object (builder, "remove-session");
|
|
|
5d36d8 |
if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL) > 1) {
|
|
|
5d36d8 |
gtk_widget_set_sensitive (button, TRUE);
|
|
|
5d36d8 |
} else {
|
|
|
5d36d8 |
gtk_widget_set_sensitive (button, FALSE);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
on_row_edited (GtkCellRendererText *cell,
|
|
|
5d36d8 |
const char *path_string,
|
|
|
5d36d8 |
const char *new_name,
|
|
|
5d36d8 |
gpointer data)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
GtkTreePath *path;
|
|
|
5d36d8 |
GtkTreeIter sort_iter, items_iter;
|
|
|
5d36d8 |
char *old_name;
|
|
|
5d36d8 |
gboolean was_renamed;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
path = gtk_tree_path_new_from_string (path_string);
|
|
|
5d36d8 |
gtk_tree_model_get_iter (GTK_TREE_MODEL (sort_model), &sort_iter, path);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_tree_model_get (GTK_TREE_MODEL (sort_model), &sort_iter, 0, &old_name, -1);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
was_renamed = rename_session (old_name, new_name);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if (was_renamed) {
|
|
|
5d36d8 |
gtk_tree_model_sort_convert_iter_to_child_iter (sort_model, &items_iter, &sort_iter);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_list_store_set (store, &items_iter, 0, g_strdup (new_name), -1);
|
|
|
5d36d8 |
g_free (old_name);
|
|
|
5d36d8 |
- make_session_current (new_name);
|
|
|
5d36d8 |
} else {
|
|
|
5d36d8 |
begin_rename ();
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_tree_path_free (path);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
g_object_set (cell, "editable", FALSE, NULL);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
on_row_deleted (GtkTreeModel *model,
|
|
|
5d36d8 |
GtkTreePath *path,
|
|
|
5d36d8 |
gpointer data)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
update_remove_button ();
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
on_row_inserted (GtkTreeModel *model,
|
|
|
5d36d8 |
GtkTreePath *path,
|
|
|
5d36d8 |
GtkTreeIter *iter,
|
|
|
5d36d8 |
gpointer data)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
update_remove_button ();
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
on_row_activated (GtkTreeView *tree_view,
|
|
|
5d36d8 |
GtkTreePath *path,
|
|
|
5d36d8 |
GtkTreeViewColumn *column,
|
|
|
5d36d8 |
gpointer data)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
- char *name;
|
|
|
5d36d8 |
-
|
|
|
5d36d8 |
- name = get_selected_session ();
|
|
|
5d36d8 |
- g_free (name);
|
|
|
5d36d8 |
-
|
|
|
5d36d8 |
gtk_main_quit ();
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
auto_save_next_session (void)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
GSettings *settings;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
settings = g_settings_new (GSM_MANAGER_SCHEMA);
|
|
|
5d36d8 |
g_settings_set_boolean (settings, KEY_AUTOSAVE_ONE_SHOT, TRUE);
|
|
|
5d36d8 |
g_object_unref (settings);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
auto_save_next_session_if_needed (void)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
char *marker;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
marker = g_build_filename (g_get_user_config_dir (),
|
|
|
5d36d8 |
"gnome-session", "saved-session",
|
|
|
5d36d8 |
".new-session", NULL);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
if (g_file_test (marker, G_FILE_TEST_EXISTS)) {
|
|
|
5d36d8 |
auto_save_next_session ();
|
|
|
5d36d8 |
unlink (marker);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
g_free (marker);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
+static void
|
|
|
5d36d8 |
+save_session (void)
|
|
|
5d36d8 |
+{
|
|
|
5d36d8 |
+ DBusGConnection *conn;
|
|
|
5d36d8 |
+ DBusGProxy *proxy;
|
|
|
5d36d8 |
+ GError *error;
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ conn = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
|
|
|
5d36d8 |
+ if (conn == NULL) {
|
|
|
5d36d8 |
+ g_warning ("Could not connect to the session bus");
|
|
|
5d36d8 |
+ return;
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ proxy = dbus_g_proxy_new_for_name (conn, GSM_SERVICE_DBUS, GSM_PATH_DBUS, GSM_INTERFACE_DBUS);
|
|
|
5d36d8 |
+ if (proxy == NULL) {
|
|
|
5d36d8 |
+ g_warning ("Could not connect to the session manager");
|
|
|
5d36d8 |
+ return;
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ error = NULL;
|
|
|
5d36d8 |
+ if (!dbus_g_proxy_call (proxy, "SaveSession", &error, G_TYPE_INVALID, G_TYPE_INVALID)) {
|
|
|
5d36d8 |
+ g_warning ("Failed to save session: %s", error->message);
|
|
|
5d36d8 |
+ g_error_free (error);
|
|
|
5d36d8 |
+ return;
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ g_object_unref (proxy);
|
|
|
5d36d8 |
+}
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
static int
|
|
|
5d36d8 |
compare_sessions (GtkTreeModel *model,
|
|
|
5d36d8 |
GtkTreeIter *a,
|
|
|
5d36d8 |
GtkTreeIter *b,
|
|
|
5d36d8 |
gpointer data)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
char *name_a, *name_b;
|
|
|
5d36d8 |
int result;
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_tree_model_get (model, a, 0, &name_a, -1);
|
|
|
5d36d8 |
gtk_tree_model_get (model, b, 0, &name_b, -1);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
result = g_utf8_collate (name_a, name_b);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
g_free (name_a);
|
|
|
5d36d8 |
g_free (name_b);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
return result;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
static void
|
|
|
5d36d8 |
on_map (GtkWidget *widget,
|
|
|
5d36d8 |
gpointer data)
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
gdk_window_focus (gtk_widget_get_window (widget), GDK_CURRENT_TIME);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
int
|
|
|
5d36d8 |
main (int argc, char *argv[])
|
|
|
5d36d8 |
{
|
|
|
5d36d8 |
GtkWidget *window;
|
|
|
5d36d8 |
GtkWidget *widget;
|
|
|
5d36d8 |
+ GtkWidget *label;
|
|
|
5d36d8 |
GtkCellRenderer *cell;
|
|
|
5d36d8 |
GtkTreeViewColumn *column;
|
|
|
5d36d8 |
GtkTreeSelection *selection;
|
|
|
5d36d8 |
GError *error;
|
|
|
5d36d8 |
+ char *selected_session;
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ static char *action = NULL;
|
|
|
5d36d8 |
+ static char **remaining_args = NULL;
|
|
|
5d36d8 |
+ static GOptionEntry entries[] = {
|
|
|
5d36d8 |
+ {"action", '\0', 0, G_OPTION_ARG_STRING, &action, N_("What to do with session selection (save|load|print)"), NULL},
|
|
|
5d36d8 |
+{ G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &remaining_args, N_("[session-name]"), NULL}
|
|
|
5d36d8 |
+ };
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ if (action == NULL) {
|
|
|
5d36d8 |
+ if (getenv ("SESSION_MANAGER") != NULL)
|
|
|
5d36d8 |
+ action = "print";
|
|
|
5d36d8 |
+ else
|
|
|
5d36d8 |
+ action = "load";
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
|
|
|
5d36d8 |
- if (getenv ("SESSION_MANAGER") != NULL)
|
|
|
5d36d8 |
+ if (getenv ("SESSION_MANAGER") != NULL && strcmp (action, "load") == 0) {
|
|
|
5d36d8 |
+ g_warning ("Cannot load new session when session currently loaded");
|
|
|
5d36d8 |
return 1;
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ if (getenv ("SESSION_MANAGER") == NULL && strcmp (action, "save") == 0) {
|
|
|
5d36d8 |
+ g_warning ("Can only save session when session loaded");
|
|
|
5d36d8 |
+ return 1;
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ if (strcmp (action, "load") != 0 && strcmp (action, "save") != 0 && strcmp (action, "print") != 0) {
|
|
|
5d36d8 |
+ g_warning ("'%s' is not a supported action. Supported actions are load, save, and print.\n", action);
|
|
|
5d36d8 |
+ return 1;
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
|
|
|
5d36d8 |
- gtk_init (&argc, &argv);
|
|
|
5d36d8 |
- if (argc > 1) {
|
|
|
5d36d8 |
- g_print ("create and select session\n");
|
|
|
5d36d8 |
- if (!create_and_select_session (argv[1]))
|
|
|
5d36d8 |
+ error = NULL;
|
|
|
5d36d8 |
+ gtk_init_with_args (&argc, &argv,
|
|
|
5d36d8 |
+ NULL, entries, GETTEXT_PACKAGE, &error);
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ if (remaining_args != NULL) {
|
|
|
5d36d8 |
+ if (g_strv_length (remaining_args) > 1) {
|
|
|
5d36d8 |
+ g_warning ("gnome-session-selector takes at most one session argument");
|
|
|
5d36d8 |
+ return 1;
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ if (!create_and_select_session (remaining_args[0]))
|
|
|
5d36d8 |
return 1;
|
|
|
5d36d8 |
else
|
|
|
5d36d8 |
return 0;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
builder = gtk_builder_new ();
|
|
|
5d36d8 |
gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
error = NULL;
|
|
|
5d36d8 |
if (!gtk_builder_add_from_file (builder, GTKBUILDER_DIR "/" "session-selector.ui", &error)) {
|
|
|
5d36d8 |
g_warning ("Could not load file 'session-selector.ui': %s", error->message);
|
|
|
5d36d8 |
exit (1);
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
|
|
|
5d36d8 |
window = (GtkWidget *) gtk_builder_get_object (builder, "main-window");
|
|
|
5d36d8 |
|
|
|
5d36d8 |
store = (GtkListStore *) gtk_builder_get_object (builder, "session-store");
|
|
|
5d36d8 |
sort_model = (GtkTreeModelSort *) gtk_builder_get_object (builder, "sort-model");
|
|
|
5d36d8 |
|
|
|
5d36d8 |
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sort_model),
|
|
|
5d36d8 |
0, compare_sessions, NULL, NULL);
|
|
|
5d36d8 |
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort_model),
|
|
|
5d36d8 |
0, GTK_SORT_ASCENDING);
|
|
|
5d36d8 |
g_signal_connect (store, "row-deleted", G_CALLBACK (on_row_deleted), NULL);
|
|
|
5d36d8 |
g_signal_connect (store, "row-inserted", G_CALLBACK (on_row_inserted), NULL);
|
|
|
5d36d8 |
session_list = (GtkWidget *) gtk_builder_get_object (builder, "session-list");
|
|
|
5d36d8 |
|
|
|
5d36d8 |
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (session_list));
|
|
|
5d36d8 |
gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
populate_session_list (session_list);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
cell = gtk_cell_renderer_text_new ();
|
|
|
5d36d8 |
g_signal_connect (cell, "edited", G_CALLBACK (on_row_edited), NULL);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
column = gtk_tree_view_column_new_with_attributes ("", cell, "text", 0, NULL);
|
|
|
5d36d8 |
gtk_tree_view_append_column (GTK_TREE_VIEW (session_list), GTK_TREE_VIEW_COLUMN (column));
|
|
|
5d36d8 |
|
|
|
5d36d8 |
g_signal_connect (session_list, "row-activated", G_CALLBACK (on_row_activated), NULL);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
g_signal_connect (selection, "changed",
|
|
|
5d36d8 |
G_CALLBACK (on_selection_changed), NULL);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
widget = (GtkWidget *) gtk_builder_get_object (builder, "new-session");
|
|
|
5d36d8 |
g_signal_connect (widget, "clicked", G_CALLBACK (on_new_session_clicked), NULL);
|
|
|
5d36d8 |
widget = (GtkWidget *) gtk_builder_get_object (builder, "remove-session");
|
|
|
5d36d8 |
g_signal_connect (widget, "clicked", G_CALLBACK (on_remove_session_clicked), NULL);
|
|
|
5d36d8 |
widget = (GtkWidget *) gtk_builder_get_object (builder, "rename-session");
|
|
|
5d36d8 |
g_signal_connect (widget, "clicked", G_CALLBACK (on_rename_session_clicked), NULL);
|
|
|
5d36d8 |
widget = (GtkWidget *) gtk_builder_get_object (builder, "continue-button");
|
|
|
5d36d8 |
g_signal_connect (widget, "clicked", G_CALLBACK (on_continue_clicked), NULL);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
g_signal_connect (window, "map", G_CALLBACK (on_map), NULL);
|
|
|
5d36d8 |
gtk_widget_show (window);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
+ if (g_strcmp0 (action, "load") == 0) {
|
|
|
5d36d8 |
+ info_text = _("Please select a custom session to run");
|
|
|
5d36d8 |
+ } else if (g_strcmp0 (action, "print") == 0) {
|
|
|
5d36d8 |
+ info_text = _("Please select a session to use");
|
|
|
5d36d8 |
+ } else if (g_strcmp0 (action, "save") == 0) {
|
|
|
5d36d8 |
+ info_text = _("Please select a session to save to");
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ label = (GtkWidget*) gtk_builder_get_object (builder, "info-label");
|
|
|
5d36d8 |
+ gtk_label_set_markup (GTK_LABEL (label), info_text);
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ selected_session = get_selected_session ();
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ if (selected_session == NULL) {
|
|
|
5d36d8 |
+ create_session_and_begin_rename ();
|
|
|
5d36d8 |
+ } else {
|
|
|
5d36d8 |
+ g_free (selected_session);
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
gtk_main ();
|
|
|
5d36d8 |
|
|
|
5d36d8 |
- auto_save_next_session_if_needed ();
|
|
|
5d36d8 |
+ selected_session = get_selected_session ();
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ if (g_strcmp0 (action, "load") == 0) {
|
|
|
5d36d8 |
+ make_session_current (selected_session);
|
|
|
5d36d8 |
+ auto_save_next_session_if_needed ();
|
|
|
5d36d8 |
+ } else if (g_strcmp0 (action, "save") == 0) {
|
|
|
5d36d8 |
+ char *last_session;
|
|
|
5d36d8 |
+
|
|
|
5d36d8 |
+ last_session = get_last_session ();
|
|
|
5d36d8 |
+ make_session_current (selected_session);
|
|
|
5d36d8 |
+ save_session ();
|
|
|
5d36d8 |
+ if (last_session != NULL)
|
|
|
5d36d8 |
+ make_session_current (last_session);
|
|
|
5d36d8 |
+ } else if (g_strcmp0 (action, "print") == 0) {
|
|
|
5d36d8 |
+ g_print ("%s\n", selected_session);
|
|
|
5d36d8 |
+ }
|
|
|
5d36d8 |
+ g_free (selected_session);
|
|
|
5d36d8 |
|
|
|
5d36d8 |
return 0;
|
|
|
5d36d8 |
}
|
|
|
5d36d8 |
--
|
|
|
244a5d |
2.14.2
|
|
|
5d36d8 |
|