summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/gnome-session-check-accelerated-common.h29
-rw-r--r--tools/gnome-session-check-accelerated-gl-helper.c517
-rw-r--r--tools/gnome-session-check-accelerated-gles-helper.c201
-rw-r--r--tools/gnome-session-check-accelerated.c312
-rw-r--r--tools/gnome-session-ctl.c304
-rwxr-xr-xtools/gnome-session-custom-session4
-rw-r--r--tools/gnome-session-inhibit.c411
-rw-r--r--tools/gnome-session-quit.c216
-rw-r--r--tools/gnome-session-selector.c698
-rw-r--r--tools/meson.build71
10 files changed, 2763 insertions, 0 deletions
diff --git a/tools/gnome-session-check-accelerated-common.h b/tools/gnome-session-check-accelerated-common.h
new file mode 100644
index 0000000..59631d9
--- /dev/null
+++ b/tools/gnome-session-check-accelerated-common.h
@@ -0,0 +1,29 @@
+/* -*- mode:c; c-basic-offset: 8; indent-tabs-mode: nil; -*- */
+/* Tool to set the property _GNOME_SESSION_ACCELERATED on the root window */
+/*
+ * Copyright (C) 2013 SUSE LINUX Products GmbH, Nuernberg, Germany.
+ *
+ *
+ * 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
+ *
+ * Author:
+ * Frederic Crozat <fcrozat@suse.com>
+ */
+
+/* Exit value for helper */
+#define HELPER_ACCEL 0
+#define HELPER_NO_ACCEL 1
+#define HELPER_SOFTWARE_RENDERING 2
+
diff --git a/tools/gnome-session-check-accelerated-gl-helper.c b/tools/gnome-session-check-accelerated-gl-helper.c
new file mode 100644
index 0000000..4be9638
--- /dev/null
+++ b/tools/gnome-session-check-accelerated-gl-helper.c
@@ -0,0 +1,517 @@
+/* gcc -o gnome-session-accelerated `pkg-config --cflags --libs xcomposite gl` -Wall gnome-session-is-accelerated.c */
+
+/*
+ * Copyright (C) 2010 Novell, Inc.
+ * Copyright (C) 2006-2009 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 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
+ *
+ * Author:
+ * Vincent Untz <vuntz@gnome.org>
+ *
+ * Most of the code comes from desktop-effects [1], released under GPLv2+.
+ * desktop-effects was written by:
+ * Soren Sandmann <sandmann@redhat.com>
+ *
+ * [1] http://git.fedorahosted.org/git/?p=desktop-effects.git;a=blob_plain;f=desktop-effects.c;hb=HEAD
+ */
+
+/*
+ * Here's the rationale behind this helper, quoting Owen, in his mail to the
+ * release team:
+ * (http://mail.gnome.org/archives/release-team/2010-June/msg00079.html)
+ *
+ * """
+ * There are some limits to what we can do here automatically without
+ * knowing anything about the driver situation on the system. The basic
+ * problem is that there are all sorts of suck:
+ *
+ * * No GL at all. This typically only happens if a system is
+ * misconfigured.
+ *
+ * * Only software GL. This one is easy to detect. We have code in
+ * the Fedora desktop-effects tool, etc.
+ *
+ * * GL that isn't featureful enough. (Tiny texture size limits, no
+ * texture-from-pixmap, etc.) Possible to detect with more work, but
+ * largely a fringe case.
+ *
+ * * Buggy GL. This isn't possible to detect. Except for the case where
+ * all GL programs crash. For that reason, we probably don't want
+ * gnome-session to directly try and do any GL detection; better to
+ * use a helper binary.
+ *
+ * * Horribly slow hardware GL. We could theoretically develop some sort
+ * of benchmark, but it's a tricky area. And how slow is too slow?
+ * """
+ *
+ * Some other tools are doing similar checks:
+ * - desktop-effects (Fedora Config Tool) [1]
+ * - drak3d (Mandriva Config Tool) [2]
+ * - compiz-manager (Compiz wrapper) [3]
+ *
+ * [1] http://git.fedorahosted.org/git/?p=desktop-effects.git;a=blob_plain;f=desktop-effects.c;hb=HEAD
+ * [2] http://svn.mandriva.com/cgi-bin/viewvc.cgi/soft/drak3d/trunk/lib/Xconfig/glx.pm?view=markup
+ * [3] http://git.compiz.org/fusion/misc/compiz-manager/tree/compiz-manager
+ */
+
+/* for strcasestr */
+#define _GNU_SOURCE
+
+#include <ctype.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include <regex.h>
+
+#ifdef __FreeBSD__
+#include <kenv.h>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/Xcomposite.h>
+#include <GL/gl.h>
+#include <GL/glx.h>
+
+#include "gnome-session-check-accelerated-common.h"
+
+#define SIZE_UNSET 0
+#define SIZE_ERROR -1
+static int max_texture_size = SIZE_UNSET;
+static int max_renderbuffer_size = SIZE_UNSET;
+static gboolean has_llvmpipe = FALSE;
+
+static inline void
+_print_error (const char *str)
+{
+ fprintf (stderr, "gnome-session-is-accelerated: %s\n", str);
+}
+
+#define CMDLINE_UNSET -1
+#define CMDLINE_NON_FALLBACK_FORCED 0
+#define CMDLINE_FALLBACK_FORCED 1
+
+#if defined(__linux__)
+static int
+_parse_kcmdline (void)
+{
+ int ret = CMDLINE_UNSET;
+ GRegex *regex;
+ GMatchInfo *match;
+ char *contents;
+ char *word;
+ const char *arg;
+
+ if (!g_file_get_contents ("/proc/cmdline", &contents, NULL, NULL))
+ return ret;
+
+ regex = g_regex_new ("gnome.fallback=(\\S+)", 0, G_REGEX_MATCH_NOTEMPTY, NULL);
+ if (!g_regex_match (regex, contents, G_REGEX_MATCH_NOTEMPTY, &match))
+ goto out;
+
+ word = g_match_info_fetch (match, 0);
+ g_debug ("Found command-line match '%s'", word);
+ arg = word + strlen ("gnome.fallback=");
+ if (*arg != '0' && *arg != '1')
+ fprintf (stderr, "gnome-session-is-accelerated: Invalid value '%s' for gnome.fallback passed in kernel command line.\n", arg);
+ else
+ ret = atoi (arg);
+ g_free (word);
+
+out:
+ g_match_info_free (match);
+ g_regex_unref (regex);
+ g_free (contents);
+
+ g_debug ("Command-line parsed to %d", ret);
+
+ return ret;
+}
+#elif defined(__FreeBSD__)
+static int
+_parse_kcmdline (void)
+{
+ int ret = CMDLINE_UNSET;
+ char value[KENV_MVALLEN];
+
+ /* a compile time check to avoid unexpected stack overflow */
+ _Static_assert(KENV_MVALLEN < 1024 * 1024, "KENV_MVALLEN is too large");
+
+ if (kenv (KENV_GET, "gnome.fallback", value, KENV_MVALLEN) == -1)
+ return ret;
+
+ if (*value != '0' && *value != '1')
+ fprintf (stderr, "gnome-session-is-accelerated: Invalid value '%s' for gnome.fallback passed in kernel environment.\n", value);
+ else
+ ret = atoi (value);
+
+ g_debug ("Kernel environment parsed to %d", ret);
+
+ return ret;
+}
+#else
+static int
+_parse_kcmdline (void)
+{
+ return CMDLINE_UNSET;
+}
+#endif
+
+static gboolean
+_has_composite (Display *display)
+{
+ int dummy1, dummy2;
+
+ return XCompositeQueryExtension (display, &dummy1, &dummy2);
+}
+
+static gboolean
+_is_comment (const char *line)
+{
+ while (*line && isspace(*line))
+ line++;
+
+ if (*line == '#' || *line == '\0')
+ return TRUE;
+ return FALSE;
+}
+
+static gboolean
+_is_gl_renderer_blacklisted (const char *renderer)
+{
+ FILE *blacklist;
+ char *line = NULL;
+ size_t line_len = 0;
+ gboolean ret = TRUE;
+
+ blacklist = fopen(PKGDATADIR "/hardware-compatibility", "r");
+ if (blacklist == NULL)
+ goto out;
+
+ while (getline (&line, &line_len, blacklist) != -1) {
+ int whitelist = 0;
+ const char *re_str;
+ regex_t re;
+ int status;
+
+ if (line == NULL)
+ break;
+
+ /* Drop trailing \n */
+ line[strlen(line) - 1] = '\0';
+
+ if (_is_comment (line)) {
+ free (line);
+ line = NULL;
+ continue;
+ }
+
+ if (line[0] == '+')
+ whitelist = 1;
+ else if (line[0] == '-')
+ whitelist = 0;
+ else {
+ _print_error ("Invalid syntax in this line for hardware compatibility:");
+ _print_error (line);
+ free (line);
+ line = NULL;
+ continue;
+ }
+
+ re_str = line + 1;
+
+ if (regcomp (&re, re_str, REG_EXTENDED|REG_ICASE|REG_NOSUB) != 0) {
+ _print_error ("Cannot use this regular expression for hardware compatibility:");
+ _print_error (re_str);
+ } else {
+ status = regexec (&re, renderer, 0, NULL, 0);
+ regfree(&re);
+
+ if (status == 0) {
+ if (whitelist)
+ ret = FALSE;
+ goto out;
+ }
+ }
+
+ free (line);
+ line = NULL;
+ }
+
+ ret = FALSE;
+
+out:
+ if (line != NULL)
+ free (line);
+
+ if (blacklist != NULL)
+ fclose (blacklist);
+
+ return ret;
+}
+
+static char *
+_get_hardware_gl (Display *display)
+{
+ int screen;
+ Window root;
+ XVisualInfo *visual = NULL;
+ GLXContext context = NULL;
+ XSetWindowAttributes cwa = { 0 };
+ Window window = None;
+ char *renderer = NULL;
+
+ int attrlist[] = {
+ GLX_RGBA,
+ GLX_RED_SIZE, 1,
+ GLX_GREEN_SIZE, 1,
+ GLX_BLUE_SIZE, 1,
+ GLX_DOUBLEBUFFER,
+ None
+ };
+
+ screen = DefaultScreen (display);
+ root = RootWindow (display, screen);
+
+ visual = glXChooseVisual (display, screen, attrlist);
+ if (!visual)
+ goto out;
+
+ context = glXCreateContext (display, visual, NULL, True);
+ if (!context)
+ goto out;
+
+ cwa.colormap = XCreateColormap (display, root,
+ visual->visual, AllocNone);
+ cwa.background_pixel = 0;
+ cwa.border_pixel = 0;
+ window = XCreateWindow (display, root,
+ 0, 0, 1, 1, 0,
+ visual->depth, InputOutput, visual->visual,
+ CWColormap | CWBackPixel | CWBorderPixel,
+ &cwa);
+
+ if (!glXMakeCurrent (display, window, context))
+ goto out;
+
+ renderer = g_strdup ((const char *) glGetString (GL_RENDERER));
+ if (_is_gl_renderer_blacklisted (renderer)) {
+ g_clear_pointer (&renderer, g_free);
+ goto out;
+ }
+ if (renderer && strcasestr (renderer, "llvmpipe"))
+ has_llvmpipe = TRUE;
+
+ /* we need to get the max texture and renderbuffer sizes while we have
+ * a context, but we'll check their values later */
+
+ glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max_texture_size);
+ if (glGetError() != GL_NO_ERROR)
+ max_texture_size = SIZE_ERROR;
+
+ glGetIntegerv (GL_MAX_RENDERBUFFER_SIZE_EXT, &max_renderbuffer_size);
+ if (glGetError() != GL_NO_ERROR)
+ max_renderbuffer_size = SIZE_ERROR;
+
+out:
+ glXMakeCurrent (display, None, None);
+ if (context)
+ glXDestroyContext (display, context);
+ if (window)
+ XDestroyWindow (display, window);
+ if (cwa.colormap)
+ XFreeColormap (display, cwa.colormap);
+
+ return renderer;
+}
+
+static gboolean
+_has_extension (const char *extension_list,
+ const char *extension)
+{
+ char **extensions;
+ guint i;
+ gboolean ret;
+
+ g_return_val_if_fail (extension != NULL, TRUE);
+
+ /* Extension_list is one big string, containing extensions
+ * separated by spaces. */
+ if (extension_list == NULL)
+ return FALSE;
+
+ ret = FALSE;
+
+ extensions = g_strsplit (extension_list, " ", -1);
+ if (extensions == NULL)
+ return FALSE;
+
+ for (i = 0; extensions[i] != NULL; i++) {
+ if (g_str_equal (extensions[i], extension)) {
+ ret = TRUE;
+ break;
+ }
+ }
+
+ g_strfreev (extensions);
+
+ return ret;
+}
+
+static gboolean
+_has_texture_from_pixmap (Display *display)
+{
+ int screen;
+ const char *server_extensions;
+ const char *client_extensions;
+ gboolean ret = FALSE;
+
+ screen = DefaultScreen (display);
+
+ server_extensions = glXQueryServerString (display, screen,
+ GLX_EXTENSIONS);
+ if (!_has_extension (server_extensions,
+ "GLX_EXT_texture_from_pixmap"))
+ goto out;
+
+ client_extensions = glXGetClientString (display, GLX_EXTENSIONS);
+ if (!_has_extension (client_extensions,
+ "GLX_EXT_texture_from_pixmap"))
+ goto out;
+
+ ret = TRUE;
+
+out:
+ return ret;
+}
+
+static void
+_set_max_screen_size_property (Display *display, int screen, int size)
+{
+ Atom max_screen_size_atom;
+
+ max_screen_size_atom = XInternAtom (display, "_GNOME_MAX_SCREEN_SIZE",
+ False);
+
+ /* Will be read by gnome-settings-daemon and
+ * gnome-control-center to avoid display configurations where 3D
+ * is not available (and would break gnome-shell) */
+ XChangeProperty (display, RootWindow(display, screen),
+ max_screen_size_atom,
+ XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char *)&size, 1);
+
+ XSync(display, False);
+}
+
+static gboolean
+_is_max_texture_size_big_enough (Display *display)
+{
+ int screen, size;
+
+ screen = DefaultScreen (display);
+ size = MIN(max_renderbuffer_size, max_texture_size);
+ if (size < DisplayWidth (display, screen) ||
+ size < DisplayHeight (display, screen))
+ return FALSE;
+
+ _set_max_screen_size_property (display, screen, size);
+
+ return TRUE;
+}
+
+static gboolean print_renderer = FALSE;
+
+static const GOptionEntry entries[] = {
+ { "print-renderer", 'p', 0, G_OPTION_ARG_NONE, &print_renderer, "Print GL renderer name", NULL },
+ { NULL },
+};
+
+int
+main (int argc, char **argv)
+{
+ int kcmdline_parsed;
+ Display *display = NULL;
+ int ret = HELPER_NO_ACCEL;
+ GOptionContext *context;
+ GError *error = NULL;
+ char *renderer = NULL;
+
+ setlocale (LC_ALL, "");
+
+ context = g_option_context_new (NULL);
+ g_option_context_add_main_entries (context, entries, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, &error)) {
+ g_error ("Can't parse command line: %s\n", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ kcmdline_parsed = _parse_kcmdline ();
+ if (kcmdline_parsed > CMDLINE_UNSET) {
+ if (kcmdline_parsed == CMDLINE_NON_FALLBACK_FORCED) {
+ _print_error ("Non-fallback mode forced by kernel command line.");
+ ret = HELPER_ACCEL;
+ goto out;
+ } else if (kcmdline_parsed == CMDLINE_FALLBACK_FORCED) {
+ _print_error ("Fallback mode forced by kernel command line.");
+ goto out;
+ }
+ }
+
+ display = XOpenDisplay (NULL);
+ if (!display) {
+ _print_error ("No X display.");
+ goto out;
+ }
+
+ if (!_has_composite (display)) {
+ _print_error ("No composite extension.");
+ goto out;
+ }
+
+ renderer = _get_hardware_gl (display);
+ if (!renderer) {
+ _print_error ("No hardware 3D support.");
+ goto out;
+ }
+
+ if (!_has_texture_from_pixmap (display)) {
+ _print_error ("No GLX_EXT_texture_from_pixmap support.");
+ goto out;
+ }
+
+ if (!_is_max_texture_size_big_enough (display)) {
+ _print_error ("GL_MAX_{TEXTURE,RENDERBUFFER}_SIZE is too small.");
+ goto out;
+ }
+
+ ret = has_llvmpipe ? HELPER_SOFTWARE_RENDERING : HELPER_ACCEL;
+
+ if (print_renderer)
+ g_print ("%s", renderer);
+
+out:
+ if (display)
+ XCloseDisplay (display);
+ g_free (renderer);
+
+ return ret;
+}
diff --git a/tools/gnome-session-check-accelerated-gles-helper.c b/tools/gnome-session-check-accelerated-gles-helper.c
new file mode 100644
index 0000000..6a4463d
--- /dev/null
+++ b/tools/gnome-session-check-accelerated-gles-helper.c
@@ -0,0 +1,201 @@
+/* -*- mode:c; c-basic-offset: 8; indent-tabs-mode: nil; -*- */
+/*
+ *
+ * Copyright (C) 2016 Endless Mobile, 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 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.
+ *
+ * Author:
+ * Cosimo Cecchi <cosimo@endlessm.com>
+ */
+
+/* for strcasestr */
+#define _GNU_SOURCE
+
+#include <config.h>
+
+#include <gtk/gtk.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef GDK_WINDOWING_X11
+#define GL_GLEXT_PROTOTYPES
+
+#include <gdk/gdkx.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <EGL/egl.h>
+#endif
+
+#include "gnome-session-check-accelerated-common.h"
+
+#ifdef GDK_WINDOWING_X11
+static char *
+get_gles_renderer (void)
+{
+ /* Select GLESv2 config */
+ EGLint attribs[] = {
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 1,
+ EGL_GREEN_SIZE, 1,
+ EGL_BLUE_SIZE, 1,
+ EGL_NONE
+ };
+
+ EGLint ctx_attribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+
+ gboolean egl_inited = FALSE;
+ Display *display;
+ Window win = None;
+ EGLContext egl_ctx = NULL;
+ EGLDisplay egl_dpy = NULL;
+ EGLSurface egl_surf = NULL;
+ char *renderer = NULL;
+
+ gdk_error_trap_push ();
+
+ display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
+ egl_dpy = eglGetDisplay (display);
+ if (!egl_dpy) {
+ g_warning ("eglGetDisplay() failed");
+ goto out;
+ }
+
+ EGLint egl_major, egl_minor;
+ if (!eglInitialize (egl_dpy, &egl_major, &egl_minor)) {
+ g_warning ("eglInitialize() failed");
+ goto out;
+ }
+
+ egl_inited = TRUE;
+
+ EGLint num_configs;
+ EGLConfig config;
+ if (!eglChooseConfig (egl_dpy, attribs, &config, 1, &num_configs)) {
+ g_warning ("Failed to get EGL configuration");
+ goto out;
+ }
+
+ EGLint vid;
+ if (!eglGetConfigAttrib (egl_dpy, config, EGL_NATIVE_VISUAL_ID, &vid)) {
+ g_warning ("Failed to get EGL visual");
+ goto out;
+ }
+
+ /* The X window visual must match the EGL config */
+ XVisualInfo *vis_info, vis_template;
+ int num_visuals;
+ vis_template.visualid = vid;
+ vis_info = XGetVisualInfo (display, VisualIDMask, &vis_template, &num_visuals);
+ if (!vis_info) {
+ g_warning ("Failed to get X visual");
+ goto out;
+ }
+
+ XSetWindowAttributes attr;
+ attr.colormap = XCreateColormap (display, DefaultRootWindow (display),
+ vis_info->visual, AllocNone);
+ win = XCreateWindow (display, DefaultRootWindow (display),
+ 0, 0, /* x, y */
+ 1, 1, /* width, height */
+ 0, /* border_width */
+ vis_info->depth, InputOutput,
+ vis_info->visual, CWColormap, &attr);
+ XFree (vis_info);
+
+ eglBindAPI (EGL_OPENGL_ES_API);
+
+ egl_ctx = eglCreateContext (egl_dpy, config, EGL_NO_CONTEXT, ctx_attribs);
+ if (!egl_ctx) {
+ g_warning ("Failed to create EGL context");
+ goto out;
+ }
+
+ egl_surf = eglCreateWindowSurface (egl_dpy, config, win, NULL);
+ if (!egl_surf) {
+ g_warning ("Failed to create EGL surface");
+ goto out;
+ }
+
+ if (!eglMakeCurrent (egl_dpy, egl_surf, egl_surf, egl_ctx)) {
+ g_warning ("eglMakeCurrent() failed");
+ goto out;
+ }
+
+ renderer = g_strdup ((const char *) glGetString (GL_RENDERER));
+
+ out:
+ if (egl_ctx)
+ eglDestroyContext (egl_dpy, egl_ctx);
+ if (egl_surf)
+ eglDestroySurface (egl_dpy, egl_surf);
+ if (egl_inited)
+ eglTerminate (egl_dpy);
+ if (win != None)
+ XDestroyWindow (display, win);
+
+ gdk_error_trap_pop_ignored ();
+ return renderer;
+}
+#endif
+
+static gboolean print_renderer = FALSE;
+
+static const GOptionEntry entries[] = {
+ { "print-renderer", 'p', 0, G_OPTION_ARG_NONE, &print_renderer, "Print EGL renderer name", NULL },
+ { NULL },
+};
+
+int
+main (int argc,
+ char **argv)
+{
+ char *renderer = NULL;
+ GOptionContext *context;
+ int ret = HELPER_NO_ACCEL;
+ GError *error = NULL;
+
+ setlocale (LC_ALL, "");
+
+ context = g_option_context_new (NULL);
+ g_option_context_add_group (context, gtk_get_option_group (TRUE));
+ g_option_context_add_main_entries (context, entries, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, &error)) {
+ g_error ("Can't parse command line: %s\n", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+#ifdef GDK_WINDOWING_X11
+ renderer = get_gles_renderer ();
+#endif
+
+ if (renderer != NULL) {
+ if (print_renderer)
+ g_print ("%s", renderer);
+ if (strcasestr (renderer, "llvmpipe"))
+ ret = HELPER_SOFTWARE_RENDERING;
+ else
+ ret = HELPER_ACCEL;
+ }
+
+out:
+ return ret;
+}
diff --git a/tools/gnome-session-check-accelerated.c b/tools/gnome-session-check-accelerated.c
new file mode 100644
index 0000000..31f0a52
--- /dev/null
+++ b/tools/gnome-session-check-accelerated.c
@@ -0,0 +1,312 @@
+/* -*- mode:c; c-basic-offset: 8; indent-tabs-mode: nil; -*- */
+/* Tool to set the property _GNOME_SESSION_ACCELERATED on the root window */
+/*
+ * Copyright (C) 2011 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 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
+ *
+ * Author:
+ * Colin Walters <walters@verbum.org>
+ */
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <epoxy/gl.h>
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+#include <sys/wait.h>
+
+#include "gnome-session-check-accelerated-common.h"
+
+/* Wait up to this long for a running check to finish */
+#define PROPERTY_CHANGE_TIMEOUT 5000
+
+/* Values used for the _GNOME_SESSION_ACCELERATED root window property */
+#define NO_ACCEL 0
+#define HAVE_ACCEL 1
+#define ACCEL_CHECK_RUNNING 2
+
+static Atom is_accelerated_atom;
+static Atom is_software_rendering_atom;
+static Atom renderer_atom;
+static gboolean property_changed;
+
+static gboolean
+on_property_notify_timeout (gpointer data)
+{
+ gtk_main_quit ();
+ return FALSE;
+}
+
+static GdkFilterReturn
+property_notify_filter (GdkXEvent *xevent,
+ GdkEvent *event,
+ gpointer data)
+{
+ XPropertyEvent *ev = xevent;
+
+ if (ev->type == PropertyNotify && ev->atom == is_accelerated_atom) {
+ property_changed = TRUE;
+ gtk_main_quit ();
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+static gboolean
+wait_for_property_notify (void)
+{
+ GdkDisplay *display;
+ GdkScreen *screen;
+ GdkWindow *root;
+ Window rootwin;
+
+ property_changed = FALSE;
+
+ display = gdk_display_get_default ();
+ screen = gdk_display_get_default_screen (display);
+ root = gdk_screen_get_root_window (screen);
+ rootwin = gdk_x11_window_get_xid (root);
+
+ XSelectInput (GDK_DISPLAY_XDISPLAY (display), rootwin, PropertyChangeMask);
+ gdk_window_add_filter (root, property_notify_filter, NULL);
+ g_timeout_add (PROPERTY_CHANGE_TIMEOUT, on_property_notify_timeout, NULL);
+
+ gtk_main ();
+
+ return property_changed;
+}
+
+static char *
+get_gtk_gles_renderer (void)
+{
+ GtkWidget *win;
+ GdkGLContext *context;
+ char *renderer = NULL;
+
+ win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_realize (win);
+ context = gdk_window_create_gl_context (gtk_widget_get_window (win), NULL);
+ if (!context)
+ return NULL;
+ gdk_gl_context_make_current (context);
+ renderer = g_strdup ((char *) glGetString (GL_RENDERER));
+ gdk_gl_context_clear_current ();
+ g_object_unref (context);
+
+ return renderer;
+}
+
+static gboolean
+is_discrete_gpu_check (void)
+{
+ const char *dri_prime;
+
+ dri_prime = g_getenv ("DRI_PRIME");
+ if (!dri_prime)
+ return FALSE;
+ if (*dri_prime != '1')
+ return FALSE;
+ return TRUE;
+}
+
+int
+main (int argc, char **argv)
+{
+ GdkDisplay *display = NULL;
+ int estatus;
+ char *gl_helper_argv[] = { LIBEXECDIR "/gnome-session-check-accelerated-gl-helper", "--print-renderer", NULL };
+ char *gles_helper_argv[] = { LIBEXECDIR "/gnome-session-check-accelerated-gles-helper", "--print-renderer", NULL };
+ char *renderer_string = NULL;
+ char *gl_renderer_string = NULL, *gles_renderer_string = NULL;
+ gboolean gl_software_rendering = FALSE, gles_software_rendering = FALSE;
+ Window rootwin;
+ glong is_accelerated, is_software_rendering;
+ GError *gl_error = NULL, *gles_error = NULL;
+
+ gtk_init (NULL, NULL);
+
+ /* gnome-session-check-accelerated gets run before X is started in the wayland
+ * case, and it currently requires X. Until we have that working, just always
+ * assume wayland will work.
+ * Also make sure that we don't read cached information about the first GPU
+ * when requesting information about the second.
+ */
+ if (is_discrete_gpu_check () || g_strcmp0 (g_getenv ("XDG_SESSION_TYPE"), "x11") != 0) {
+ renderer_string = get_gtk_gles_renderer ();
+ if (renderer_string) {
+ g_print ("%s", renderer_string);
+ return 0;
+ }
+ return 1;
+ }
+
+ display = gdk_display_get_default ();
+ /* when running on X11 with a nested wayland GDK will default to wayland
+ * so looking for X11 atoms will not work (and crash).
+ */
+ if (!GDK_IS_X11_DISPLAY (display)) {
+ g_printerr ("gnome-session-check-accelerated: no X11 display found\n");
+ return 1;
+ }
+
+ rootwin = gdk_x11_get_default_root_xwindow ();
+
+ is_accelerated_atom = gdk_x11_get_xatom_by_name_for_display (display, "_GNOME_SESSION_ACCELERATED");
+ is_software_rendering_atom = gdk_x11_get_xatom_by_name_for_display (display, "_GNOME_IS_SOFTWARE_RENDERING");
+ renderer_atom = gdk_x11_get_xatom_by_name_for_display (display, "_GNOME_SESSION_RENDERER");
+
+ {
+ Atom type;
+ gint format;
+ gulong nitems;
+ gulong bytes_after;
+ guchar *data;
+
+ read:
+ gdk_x11_display_error_trap_push (display);
+ XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), rootwin,
+ is_accelerated_atom,
+ 0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
+ &bytes_after, &data);
+ gdk_x11_display_error_trap_pop_ignored (display);
+
+ if (type == XA_CARDINAL) {
+ glong *is_accelerated_ptr = (glong*) data;
+
+ if (*is_accelerated_ptr == ACCEL_CHECK_RUNNING) {
+ /* Test in progress, wait */
+ if (wait_for_property_notify ())
+ goto read;
+ /* else fall through and do the check ourselves */
+
+ } else {
+ gdk_x11_display_error_trap_push (display);
+ XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), rootwin,
+ renderer_atom,
+ 0, G_MAXLONG, False, XA_STRING, &type, &format, &nitems,
+ &bytes_after, &data);
+ gdk_x11_display_error_trap_pop_ignored (display);
+
+ if (type == XA_STRING) {
+ g_print ("%s", data);
+ }
+
+ return (*is_accelerated_ptr == 0 ? 1 : 0);
+ }
+ }
+ }
+
+ /* We don't have the property or it's the wrong type.
+ * Try to compute it now.
+ */
+
+ /* First indicate that a test is in progress */
+ is_accelerated = ACCEL_CHECK_RUNNING;
+ is_software_rendering = FALSE;
+ estatus = 1;
+
+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
+ rootwin,
+ is_accelerated_atom,
+ XA_CARDINAL, 32, PropModeReplace, (guchar *) &is_accelerated, 1);
+
+ gdk_display_sync (display);
+
+ /* First, try the GL helper */
+ if (g_spawn_sync (NULL, (char **) gl_helper_argv, NULL, 0,
+ NULL, NULL, &gl_renderer_string, NULL, &estatus, &gl_error)) {
+ is_accelerated = (WEXITSTATUS(estatus) == HELPER_ACCEL);
+ gl_software_rendering = (WEXITSTATUS(estatus) == HELPER_SOFTWARE_RENDERING);
+ if (is_accelerated) {
+ renderer_string = gl_renderer_string;
+ goto finish;
+ }
+
+ g_printerr ("gnome-session-check-accelerated: GL Helper exited with code %d\n", estatus);
+ }
+
+ /* Then, try the GLES helper */
+ if (g_spawn_sync (NULL, (char **) gles_helper_argv, NULL, 0,
+ NULL, NULL, &gles_renderer_string, NULL, &estatus, &gles_error)) {
+ is_accelerated = (WEXITSTATUS(estatus) == HELPER_ACCEL);
+ gles_software_rendering = (WEXITSTATUS(estatus) == HELPER_SOFTWARE_RENDERING);
+ if (is_accelerated) {
+ renderer_string = gles_renderer_string;
+ goto finish;
+ }
+
+ g_printerr ("gnome-session-check-accelerated: GLES Helper exited with code %d\n", estatus);
+ }
+
+ /* If we got here, GL software rendering is our best bet */
+ if (gl_software_rendering || gles_software_rendering) {
+ is_software_rendering = TRUE;
+ is_accelerated = TRUE;
+
+ if (gl_software_rendering)
+ renderer_string = gl_renderer_string;
+ else if (gles_software_rendering)
+ renderer_string = gles_renderer_string;
+
+ goto finish;
+ }
+
+ /* Both helpers failed; print their error messages */
+ if (gl_error != NULL) {
+ g_printerr ("gnome-session-check-accelerated: Failed to run GL helper: %s\n", gl_error->message);
+ g_clear_error (&gl_error);
+ }
+
+ if (gles_error != NULL) {
+ g_printerr ("gnome-session-check-accelerated: Failed to run GLES helper: %s\n", gles_error->message);
+ g_clear_error (&gles_error);
+ }
+
+ finish:
+ if (is_accelerated) {
+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
+ rootwin,
+ is_accelerated_atom,
+ XA_CARDINAL, 32, PropModeReplace, (guchar *) &is_accelerated, 1);
+ }
+
+ if (is_software_rendering) {
+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
+ rootwin,
+ is_software_rendering_atom,
+ XA_CARDINAL, 32, PropModeReplace, (guchar *) &is_software_rendering, 1);
+ }
+
+ if (renderer_string != NULL) {
+ XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
+ rootwin,
+ renderer_atom,
+ XA_STRING, 8, PropModeReplace, (guchar *) renderer_string, strlen (renderer_string));
+
+ /* Print the renderer */
+ g_print ("%s", renderer_string);
+ }
+
+ gdk_display_sync (display);
+
+ g_free (gl_renderer_string);
+ g_free (gles_renderer_string);
+
+ return is_accelerated ? 0 : 1;
+}
diff --git a/tools/gnome-session-ctl.c b/tools/gnome-session-ctl.c
new file mode 100644
index 0000000..8d94f84
--- /dev/null
+++ b/tools/gnome-session-ctl.c
@@ -0,0 +1,304 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ * gnome-session-tl.c - Small utility program to manage gnome systemd session.
+
+ Copyright (C) 1998 Tom Tromey
+ Copyright (C) 2008,2019 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, 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+
+#include <locale.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <systemd/sd-daemon.h>
+
+#include <glib.h>
+#include <glib-unix.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#define GSM_SERVICE_DBUS "org.gnome.SessionManager"
+#define GSM_PATH_DBUS "/org/gnome/SessionManager"
+#define GSM_INTERFACE_DBUS "org.gnome.SessionManager"
+
+#define SYSTEMD_DBUS "org.freedesktop.systemd1"
+#define SYSTEMD_PATH_DBUS "/org/freedesktop/systemd1"
+#define SYSTEMD_INTERFACE_DBUS "org.freedesktop.systemd1.Manager"
+
+static GDBusConnection *
+get_session_bus (void)
+{
+ g_autoptr(GError) error = NULL;
+ GDBusConnection *bus;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (bus == NULL)
+ g_warning ("Couldn't connect to session bus: %s", error->message);
+
+ return bus;
+}
+
+static void
+do_signal_init (void)
+{
+ g_autoptr(GDBusConnection) connection = NULL;
+ g_autoptr(GVariant) reply = NULL;
+ g_autoptr(GError) error = NULL;
+
+ connection = get_session_bus ();
+ if (connection == NULL)
+ return;
+
+ reply = g_dbus_connection_call_sync (connection,
+ GSM_SERVICE_DBUS,
+ GSM_PATH_DBUS,
+ GSM_INTERFACE_DBUS,
+ "Initialized",
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1, NULL, &error);
+
+ if (error != NULL)
+ g_warning ("Failed to call signal initialization: %s",
+ error->message);
+}
+
+static void
+do_start_unit (const gchar *unit, const char *mode)
+{
+ g_autoptr(GDBusConnection) connection = NULL;
+ g_autoptr(GVariant) reply = NULL;
+ g_autoptr(GError) error = NULL;
+
+ connection = get_session_bus ();
+ if (connection == NULL)
+ return;
+
+ reply = g_dbus_connection_call_sync (connection,
+ SYSTEMD_DBUS,
+ SYSTEMD_PATH_DBUS,
+ SYSTEMD_INTERFACE_DBUS,
+ "StartUnit",
+ g_variant_new ("(ss)",
+ unit,
+ mode),
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1, NULL, &error);
+
+ if (error != NULL)
+ g_warning ("Failed to start shutdown target: %s",
+ error->message);
+}
+
+static void
+do_restart_dbus (void)
+{
+ g_autoptr(GDBusConnection) connection = NULL;
+ g_autoptr(GVariant) reply = NULL;
+ g_autoptr(GError) error = NULL;
+
+ connection = get_session_bus ();
+ if (connection == NULL)
+ return;
+
+ reply = g_dbus_connection_call_sync (connection,
+ SYSTEMD_DBUS,
+ SYSTEMD_PATH_DBUS,
+ SYSTEMD_INTERFACE_DBUS,
+ "TryRestartUnit",
+ g_variant_new ("(ss)",
+ "dbus.service",
+ "replace"),
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1, NULL, &error);
+
+ if (error != NULL)
+ g_warning ("Failed to restart DBus service: %s",
+ error->message);
+}
+
+typedef struct {
+ GMainLoop *loop;
+ gint fifo_fd;
+} MonitorLeader;
+
+static gboolean
+leader_term_or_int_signal_cb (gpointer user_data)
+{
+ MonitorLeader *data = (MonitorLeader*) user_data;
+
+ g_main_quit (data->loop);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+leader_fifo_io_cb (gint fd,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ MonitorLeader *data = (MonitorLeader*) user_data;
+
+ sd_notify (0, "STOPPING=1");
+
+ if (condition | G_IO_IN) {
+ char buf[1];
+ read (data->fifo_fd, buf, 1);
+ }
+ if (condition | G_IO_HUP) {
+ g_main_loop_quit (data->loop);
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+/**
+ * do_monitor_leader:
+ *
+ * Function to monitor the leader to ensure clean session shutdown and
+ * propagation of this information to/from loginctl/GDM.
+ * See main.c systemd_leader_run() for more information.
+ */
+static void
+do_monitor_leader (void)
+{
+ MonitorLeader data;
+ g_autofree char *fifo_name = NULL;
+ int res;
+
+ data.loop = g_main_loop_new (NULL, TRUE);
+
+ fifo_name = g_strdup_printf ("%s/gnome-session-leader-fifo",
+ g_get_user_runtime_dir ());
+ res = mkfifo (fifo_name, 0666);
+ if (res < 0 && errno != EEXIST)
+ g_warning ("Error creating FIFO: %m");
+
+ data.fifo_fd = g_open (fifo_name, O_RDONLY | O_CLOEXEC, 0666);
+ if (data.fifo_fd >= 0) {
+ struct stat buf;
+
+ res = fstat (data.fifo_fd, &buf);
+ if (res < 0) {
+ g_warning ("Unable to monitor session leader: stat failed with error %m");
+ sd_notifyf (0, "STATUS=Unable to monitor session leader: FD is not a FIFO %m");
+ close (data.fifo_fd);
+ data.fifo_fd = -1;
+ } else if (!(buf.st_mode & S_IFIFO)) {
+ g_warning ("Unable to monitor session leader: FD is not a FIFO");
+ sd_notify (0, "STATUS=Unable to monitor session leader: FD is not a FIFO");
+ close (data.fifo_fd);
+ data.fifo_fd = -1;
+ } else {
+ sd_notify (0, "STATUS=Watching session leader");
+ g_unix_fd_add (data.fifo_fd, G_IO_HUP | G_IO_IN, leader_fifo_io_cb, &data);
+ }
+ } else {
+ g_warning ("Unable to monitor session leader: Opening FIFO failed with %m");
+ sd_notifyf (0, "STATUS=Unable to monitor session leader: Opening FIFO failed with %m");
+ }
+
+ g_unix_signal_add (SIGTERM, leader_term_or_int_signal_cb, &data);
+ g_unix_signal_add (SIGINT, leader_term_or_int_signal_cb, &data);
+
+ g_main_loop_run (data.loop);
+
+ g_main_loop_unref (data.loop);
+ /* FD is closed with the application. */
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_autoptr(GError) error = NULL;
+ static gboolean opt_shutdown;
+ static gboolean opt_monitor;
+ static gboolean opt_signal_init;
+ static gboolean opt_restart_dbus;
+ static gboolean opt_exec_stop_check;
+ int conflicting_options;
+ GOptionContext *ctx;
+ static const GOptionEntry options[] = {
+ { "shutdown", '\0', 0, G_OPTION_ARG_NONE, &opt_shutdown, N_("Start gnome-session-shutdown.target"), NULL },
+ { "monitor", '\0', 0, G_OPTION_ARG_NONE, &opt_monitor, N_("Start gnome-session-shutdown.target when receiving EOF or a single byte on stdin"), NULL },
+ { "signal-init", '\0', 0, G_OPTION_ARG_NONE, &opt_signal_init, N_("Signal initialization done to gnome-session"), NULL },
+ { "restart-dbus", '\0', 0, G_OPTION_ARG_NONE, &opt_restart_dbus, N_("Restart dbus.service if it is running"), NULL },
+ { "exec-stop-check", '\0', 0, G_OPTION_ARG_NONE, &opt_exec_stop_check, N_("Run from ExecStopPost to start gnome-session-failed.target on service failure"), NULL },
+ { NULL },
+ };
+
+ /* Initialize the i18n stuff */
+ setlocale (LC_ALL, "");
+ bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ ctx = g_option_context_new ("");
+ g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE);
+ if (! g_option_context_parse (ctx, &argc, &argv, &error)) {
+ g_warning ("Unable to start: %s", error->message);
+ exit (1);
+ }
+ g_option_context_free (ctx);
+
+ conflicting_options = 0;
+ if (opt_shutdown)
+ conflicting_options++;
+ if (opt_monitor)
+ conflicting_options++;
+ if (opt_signal_init)
+ conflicting_options++;
+ if (opt_restart_dbus)
+ conflicting_options++;
+ if (opt_exec_stop_check)
+ conflicting_options++;
+ if (conflicting_options != 1) {
+ g_printerr (_("Program needs exactly one parameter"));
+ exit (1);
+ }
+
+ sd_notify (0, "READY=1");
+
+ if (opt_signal_init) {
+ do_signal_init ();
+ } else if (opt_restart_dbus) {
+ do_restart_dbus ();
+ } else if (opt_shutdown) {
+ do_start_unit ("gnome-session-shutdown.target", "replace-irreversibly");
+ } else if (opt_monitor) {
+ do_monitor_leader ();
+ do_start_unit ("gnome-session-shutdown.target", "replace-irreversibly");
+ } else if (opt_exec_stop_check) {
+ /* Start failed target if the restart limit was hit */
+ if (g_strcmp0 ("start-limit-hit", g_getenv ("SERVICE_RESULT")) == 0) {
+ do_start_unit ("gnome-session-failed.target", "fail");
+ }
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return 0;
+}
diff --git a/tools/gnome-session-custom-session b/tools/gnome-session-custom-session
new file mode 100755
index 0000000..07fdb0c
--- /dev/null
+++ b/tools/gnome-session-custom-session
@@ -0,0 +1,4 @@
+#! /bin/sh
+
+gnome-session-selector
+exec gnome-session
diff --git a/tools/gnome-session-inhibit.c b/tools/gnome-session-inhibit.c
new file mode 100644
index 0000000..738a9b4
--- /dev/null
+++ b/tools/gnome-session-inhibit.c
@@ -0,0 +1,411 @@
+/*
+ * 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, 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "gnome-session/gsm-inhibitor-flag.h"
+
+static GsmInhibitorFlag parse_flags (const gchar *arg)
+{
+ GsmInhibitorFlag flags;
+ gchar **args;
+ gint i;
+
+ flags = 0;
+
+ args = g_strsplit (arg, ":", 0);
+ for (i = 0; args[i]; i++)
+ {
+ if (strcmp (args[i], "logout") == 0)
+ flags |= GSM_INHIBITOR_FLAG_LOGOUT;
+ else if (strcmp (args[i], "switch-user") == 0)
+ flags |= GSM_INHIBITOR_FLAG_SWITCH_USER;
+ else if (strcmp (args[i], "suspend") == 0)
+ flags |= GSM_INHIBITOR_FLAG_SUSPEND;
+ else if (strcmp (args[i], "idle") == 0)
+ flags |= GSM_INHIBITOR_FLAG_IDLE;
+ else if (strcmp (args[i], "automount") == 0)
+ flags |= GSM_INHIBITOR_FLAG_AUTOMOUNT;
+ else
+ g_print ("Ignoring inhibit argument: %s\n", args[i]);
+ }
+
+ g_strfreev (args);
+
+ return flags;
+}
+
+static gboolean inhibit (const gchar *app_id,
+ const gchar *reason,
+ GsmInhibitorFlag flags)
+{
+ GDBusConnection *bus;
+ GVariant *ret;
+ GError *error = NULL;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (bus == NULL)
+ {
+ g_warning ("Failed to connect to session bus: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ ret = g_dbus_connection_call_sync (bus,
+ "org.gnome.SessionManager",
+ "/org/gnome/SessionManager",
+ "org.gnome.SessionManager",
+ "Inhibit",
+ g_variant_new ("(susu)",
+ app_id, 0, reason, flags),
+ G_VARIANT_TYPE ("(u)"),
+ 0,
+ G_MAXINT,
+ NULL,
+ &error);
+
+ if (ret == NULL)
+ {
+ g_warning ("Failed to call Inhibit: %s\n", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_variant_unref (ret);
+
+ return TRUE;
+}
+
+static void usage (void)
+{
+ g_print (_("%s [OPTION…] COMMAND\n"
+ "\n"
+ "Execute COMMAND while inhibiting some session functionality.\n"
+ "\n"
+ " -h, --help Show this help\n"
+ " --version Show program version\n"
+ " --app-id ID The application id to use\n"
+ " when inhibiting (optional)\n"
+ " --reason REASON The reason for inhibiting (optional)\n"
+ " --inhibit ARG Things to inhibit, colon-separated list of:\n"
+ " logout, switch-user, suspend, idle, automount\n"
+ " --inhibit-only Do not launch COMMAND and wait forever instead\n"
+ " -l, --list List the existing inhibitions, and exit\n"
+ "\n"
+ "If no --inhibit option is specified, idle is assumed.\n"),
+ g_get_prgname ());
+}
+
+static void version (void)
+{
+ g_print ("%s %s\n", g_get_prgname (), PACKAGE_VERSION);
+}
+
+static GVariant *
+get_inhibitor_prop (GDBusProxy *inhibitor,
+ const char *method_name)
+{
+ g_autoptr(GVariant) variant = NULL;
+ g_autoptr(GError) error = NULL;
+
+ variant = g_dbus_proxy_call_sync (inhibitor,
+ method_name,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (variant == NULL) {
+ g_debug ("Failed to get property via '%s': %s",
+ method_name, error->message);
+ return NULL;
+ }
+
+ return g_variant_get_child_value (variant, 0);
+}
+
+static void
+string_append_comma (GString *s,
+ const char *value)
+{
+ if (s->len != 0)
+ g_string_append (s, ", ");
+ g_string_append (s, value);
+}
+
+static char *
+flags_to_str (guint32 flags)
+{
+ GString *s;
+
+ s = g_string_new (NULL);
+ if (flags & GSM_INHIBITOR_FLAG_LOGOUT)
+ string_append_comma (s, "logout");
+ if (flags & GSM_INHIBITOR_FLAG_SWITCH_USER)
+ string_append_comma (s, "switch-user");
+ if (flags & GSM_INHIBITOR_FLAG_SUSPEND)
+ string_append_comma (s, "suspend");
+ if (flags & GSM_INHIBITOR_FLAG_IDLE)
+ string_append_comma (s, "idle");
+ if (flags & GSM_INHIBITOR_FLAG_AUTOMOUNT)
+ string_append_comma (s, "automount");
+
+ return g_string_free (s, FALSE);
+}
+
+static void list (void)
+{
+ g_autoptr(GDBusConnection) bus = NULL;
+ g_autoptr(GVariant) ret = NULL;
+ g_autoptr(GVariant) array = NULL;
+ g_autoptr(GError) error = NULL;
+ guint num_children;
+ g_autoptr(GVariantIter) iter = NULL;
+ const char *inhibitor_path;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (bus == NULL)
+ {
+ g_warning ("Failed to connect to session bus: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ ret = g_dbus_connection_call_sync (bus,
+ "org.gnome.SessionManager",
+ "/org/gnome/SessionManager",
+ "org.gnome.SessionManager",
+ "GetInhibitors",
+ NULL,
+ NULL,
+ 0,
+ G_MAXINT,
+ NULL,
+ &error);
+
+ if (ret == NULL)
+ {
+ g_warning ("Failed to call GetInhibitors: %s\n", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ array = g_variant_get_child_value (ret, 0);
+
+ num_children = g_variant_n_children (array);
+ if (num_children == 0)
+ {
+ g_print ("No inhibitors\n");
+ return;
+ }
+
+ g_variant_get (array, "ao", &iter);
+ while (g_variant_iter_loop (iter, "&o", &inhibitor_path))
+ {
+ g_autoptr(GDBusProxy) inhibitor = NULL;
+ g_autoptr(GVariant) app_id = NULL;
+ g_autoptr(GVariant) reason = NULL;
+ g_autoptr(GVariant) flags = NULL;
+ g_autofree char *flags_str = NULL;
+
+ inhibitor = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.gnome.SessionManager",
+ inhibitor_path,
+ "org.gnome.SessionManager.Inhibitor",
+ NULL,
+ &error);
+
+ /* Skip inhibitors that might have disappeared */
+ if (!inhibitor)
+ {
+ g_debug ("Failed to get proxy for %s: %s",
+ inhibitor_path,
+ error->message);
+ continue;
+ }
+
+ app_id = get_inhibitor_prop (inhibitor, "GetAppId");
+ reason = get_inhibitor_prop (inhibitor, "GetReason");
+ flags = get_inhibitor_prop (inhibitor, "GetFlags");
+ if (!app_id || !reason || !flags)
+ continue;
+ flags_str = flags_to_str (g_variant_get_uint32 (flags));
+
+ g_print ("%s: %s (%s)\n",
+ g_variant_get_string (app_id, NULL),
+ g_variant_get_string (reason, NULL),
+ flags_str);
+ }
+}
+
+static void
+wait_for_child_app (char **argv)
+{
+ pid_t pid;
+ int status;
+
+ pid = fork ();
+ if (pid < 0)
+ {
+ g_print ("fork failed\n");
+ exit (1);
+ }
+
+ if (pid == 0)
+ {
+ execvp (*argv, argv);
+ g_print (_("Failed to execute %s\n"), *argv);
+ exit (1);
+ }
+
+ do
+ {
+ if (waitpid (pid, &status, 0) == -1)
+ {
+ g_print ("waitpid failed\n");
+ exit (1);
+ }
+
+ if (WIFEXITED (status))
+ exit (WEXITSTATUS (status));
+
+ } while (!WIFEXITED (status) && ! WIFSIGNALED(status));
+}
+
+static void
+wait_forever (void)
+{
+ g_print ("Inhibiting until Ctrl+C is pressed...\n");
+ while (1)
+ sleep (UINT32_MAX);
+}
+
+int main (int argc, char *argv[])
+{
+ gchar *prgname;
+ GsmInhibitorFlag inhibit_flags = 0;
+ gboolean show_help = FALSE;
+ gboolean show_version = FALSE;
+ gboolean show_list = FALSE;
+ gboolean no_launch = FALSE;
+ gint i;
+ const gchar *app_id = "unknown";
+ const gchar *reason = "not specified";
+
+ prgname = g_path_get_basename (argv[0]);
+ g_set_prgname (prgname);
+ g_free (prgname);
+
+ setlocale (LC_ALL, "");
+ bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ for (i = 1; i < argc; i++)
+ {
+ if (strcmp (argv[i], "--help") == 0 ||
+ strcmp (argv[i], "-h") == 0)
+ show_help = TRUE;
+ if (strcmp (argv[i], "--list") == 0 ||
+ strcmp (argv[i], "-l") == 0)
+ show_list = TRUE;
+ else if (strcmp (argv[i], "--version") == 0)
+ show_version = TRUE;
+ else if (strcmp (argv[i], "--inhibit-only") == 0)
+ no_launch = TRUE;
+ else if (strcmp (argv[i], "--app-id") == 0)
+ {
+ i++;
+ if (i == argc)
+ {
+ g_print (_("%s requires an argument\n"), argv[i]);
+ exit (1);
+ }
+ app_id = argv[i];
+ }
+ else if (strcmp (argv[i], "--reason") == 0)
+ {
+ i++;
+ if (i == argc)
+ {
+ g_print (_("%s requires an argument\n"), argv[i]);
+ exit (1);
+ }
+ reason = argv[i];
+ }
+ else if (strcmp (argv[i], "--inhibit") == 0)
+ {
+ i++;
+ if (i == argc)
+ {
+ g_print (_("%s requires an argument\n"), argv[i]);
+ exit (1);
+ }
+ inhibit_flags |= parse_flags (argv[i]);
+ }
+ else
+ break;
+ }
+
+ if (show_version)
+ {
+ version ();
+ return 0;
+ }
+
+ if (show_list)
+ {
+ list ();
+ return 0;
+ }
+
+ if (show_help || (i == argc && !no_launch))
+ {
+ usage ();
+ return 0;
+ }
+
+ if (inhibit_flags == 0)
+ inhibit_flags = GSM_INHIBITOR_FLAG_IDLE;
+
+ if (inhibit (app_id, reason, inhibit_flags) == FALSE)
+ return 1;
+
+ if (!no_launch)
+ wait_for_child_app (argv + i);
+ else
+ wait_forever ();
+
+ return 0;
+}
diff --git a/tools/gnome-session-quit.c b/tools/gnome-session-quit.c
new file mode 100644
index 0000000..4dddbfe
--- /dev/null
+++ b/tools/gnome-session-quit.c
@@ -0,0 +1,216 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ * save-session.c - Small program to talk to session manager.
+
+ Copyright (C) 1998 Tom Tromey
+ 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, 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+
+#include <locale.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#define GSM_SERVICE_DBUS "org.gnome.SessionManager"
+#define GSM_PATH_DBUS "/org/gnome/SessionManager"
+#define GSM_INTERFACE_DBUS "org.gnome.SessionManager"
+
+enum {
+ GSM_LOGOUT_MODE_NORMAL = 0,
+ GSM_LOGOUT_MODE_NO_CONFIRMATION,
+ GSM_LOGOUT_MODE_FORCE
+};
+
+static gboolean opt_logout = FALSE;
+static gboolean opt_power_off = FALSE;
+static gboolean opt_reboot = FALSE;
+static gboolean opt_no_prompt = FALSE;
+static gboolean opt_force = FALSE;
+
+static GOptionEntry options[] = {
+ {"logout", '\0', 0, G_OPTION_ARG_NONE, &opt_logout, N_("Log out"), NULL},
+ {"power-off", '\0', 0, G_OPTION_ARG_NONE, &opt_power_off, N_("Power off"), NULL},
+ {"reboot", '\0', 0, G_OPTION_ARG_NONE, &opt_reboot, N_("Reboot"), NULL},
+ {"force", '\0', 0, G_OPTION_ARG_NONE, &opt_force, N_("Ignoring any existing inhibitors"), NULL},
+ {"no-prompt", '\0', 0, G_OPTION_ARG_NONE, &opt_no_prompt, N_("Don’t prompt for user confirmation"), NULL},
+ {NULL}
+};
+
+static void
+display_error (const char *message)
+{
+ g_printerr ("%s\n", message);
+}
+
+static GDBusConnection *
+get_session_bus (void)
+{
+ GDBusConnection *bus;
+ GError *error = NULL;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+
+ if (bus == NULL) {
+ g_warning ("Couldn't connect to session bus: %s", error->message);
+ g_error_free (error);
+ }
+
+ return bus;
+}
+
+static GDBusProxy *
+get_sm_proxy (void)
+{
+ GDBusConnection *connection;
+ GDBusProxy *sm_proxy;
+
+ connection = get_session_bus ();
+ if (connection == NULL) {
+ display_error (_("Could not connect to the session manager"));
+ return NULL;
+ }
+
+ sm_proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ GSM_SERVICE_DBUS,
+ GSM_PATH_DBUS,
+ GSM_INTERFACE_DBUS,
+ NULL, NULL);
+ g_object_unref (connection);
+
+ if (sm_proxy == NULL) {
+ display_error (_("Could not connect to the session manager"));
+ return NULL;
+ }
+
+ return sm_proxy;
+}
+
+static void
+do_logout (unsigned int mode)
+{
+ GDBusProxy *sm_proxy;
+ GVariant *reply;
+ GError *error;
+
+ sm_proxy = get_sm_proxy ();
+ if (sm_proxy == NULL) {
+ return;
+ }
+
+ error = NULL;
+ reply = g_dbus_proxy_call_sync (sm_proxy,
+ "Logout",
+ g_variant_new ("(u)", mode),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, &error);
+
+ if (error != NULL) {
+ g_warning ("Failed to call logout: %s",
+ error->message);
+ g_error_free (error);
+ } else {
+ g_variant_unref (reply);
+ }
+ g_clear_object (&sm_proxy);
+}
+
+static void
+do_power_off (const char *action)
+{
+ GDBusProxy *sm_proxy;
+ GVariant *reply;
+ GError *error;
+
+ sm_proxy = get_sm_proxy ();
+ if (sm_proxy == NULL) {
+ return;
+ }
+
+ error = NULL;
+ reply = g_dbus_proxy_call_sync (sm_proxy,
+ action,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, &error);
+
+ if (error != NULL) {
+ g_warning ("Failed to call %s: %s",
+ action, error->message);
+ g_error_free (error);
+ } else {
+ g_variant_unref (reply);
+ }
+ g_clear_object (&sm_proxy);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GError *error;
+ int conflicting_options;
+ GOptionContext *ctx;
+
+ /* Initialize the i18n stuff */
+ setlocale (LC_ALL, "");
+ bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ error = NULL;
+ ctx = g_option_context_new ("");
+ g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE);
+ if (! g_option_context_parse (ctx, &argc, &argv, &error)) {
+ g_warning ("Unable to start: %s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+ g_option_context_free (ctx);
+
+ conflicting_options = 0;
+ if (opt_logout)
+ conflicting_options++;
+ if (opt_power_off)
+ conflicting_options++;
+ if (opt_reboot)
+ conflicting_options++;
+ if (conflicting_options > 1)
+ display_error (_("Program called with conflicting options"));
+
+ if (opt_power_off) {
+ do_power_off ("Shutdown");
+ } else if (opt_reboot) {
+ do_power_off ("Reboot");
+ } else {
+ /* default to logout */
+
+ if (opt_force)
+ do_logout (GSM_LOGOUT_MODE_FORCE);
+ else if (opt_no_prompt)
+ do_logout (GSM_LOGOUT_MODE_NO_CONFIRMATION);
+ else
+ do_logout (GSM_LOGOUT_MODE_NORMAL);
+ }
+
+ return 0;
+}
diff --git a/tools/gnome-session-selector.c b/tools/gnome-session-selector.c
new file mode 100644
index 0000000..71892c4
--- /dev/null
+++ b/tools/gnome-session-selector.c
@@ -0,0 +1,698 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2010, 2013 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 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 <http://www.gnu.org/licenses/>.
+ *
+ * Written by: Matthias Clasen <mclasen@redhat.com>
+ */
+
+#include "config.h"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#define GSM_MANAGER_SCHEMA "org.gnome.SessionManager"
+#define KEY_AUTOSAVE_ONE_SHOT "auto-save-session-one-shot"
+
+static GtkBuilder *builder;
+static GtkWidget *session_list;
+static GtkListStore *store;
+static GtkTreeModelSort *sort_model;
+
+static void select_session (const char *name);
+
+static char *
+get_session_path (const char *name)
+{
+ return g_build_filename (g_get_user_config_dir (), "gnome-session", name, NULL);
+}
+
+static char *
+find_new_session_name (void)
+{
+ char *name;
+ char *path;
+ int i;
+
+ for (i = 1; i < 20; i++) {
+ name = g_strdup_printf (_("Session %d"), i);
+ path = get_session_path (name);
+ if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
+ g_free (path);
+ return name;
+ }
+ g_free (path);
+ g_free (name);
+ }
+
+ return NULL;
+}
+
+static gboolean
+is_valid_session_name (const char *name)
+{
+ GtkTreeIter iter;
+ char *n;
+ const char *info_text;
+ char *warning_text;
+ gboolean user_tried_dot;
+ gboolean user_tried_slash;
+ GtkWidget *info_bar;
+ GtkWidget *label;
+
+ if (name[0] == 0) {
+ return FALSE;
+ }
+
+ if (name[0] == '.') {
+ user_tried_dot = TRUE;
+ } else {
+ user_tried_dot = FALSE;
+ }
+
+ if (strchr (name, '/') != NULL) {
+ user_tried_slash = TRUE;
+ } else {
+ user_tried_slash = FALSE;
+ }
+
+ info_text = _("Please select a custom session to run");
+ warning_text = NULL;
+ if (user_tried_dot && user_tried_slash) {
+ warning_text = g_strdup_printf ("%s\n<small><b>Note:</b> <i>%s</i></small>",
+ info_text,
+ _("Session names are not allowed to start with “.” or contain “/” characters"));
+ } else if (user_tried_dot) {
+ warning_text = g_strdup_printf ("%s\n<small><b>Note:</b> <i>%s</i></small>",
+ info_text,
+ _("Session names are not allowed to start with “.”"));
+ } else if (user_tried_slash) {
+ warning_text = g_strdup_printf ("%s\n<small><b>Note:</b> <i>%s</i></small>",
+ info_text,
+ _("Session names are not allowed to contain “/” characters"));
+ }
+
+ gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
+ do {
+ gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 0, &n, -1);
+ if (strcmp (n, name) == 0) {
+ char *message;
+ message = g_strdup_printf (_("A session named “%s” already exists"), name);
+ warning_text = g_strdup_printf ("%s\n<small><b>Note:</b> <i>%s</i></small>", info_text, message);
+ g_free (message);
+ g_free (n);
+ break;
+ }
+ g_free (n);
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
+
+ info_bar = (GtkWidget *) gtk_builder_get_object (builder, "info-bar");
+ label = (GtkWidget*) gtk_builder_get_object (builder, "info-label");
+
+ if (warning_text != NULL) {
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
+ gtk_label_set_markup (GTK_LABEL (label), warning_text);
+ g_free (warning_text);
+ return FALSE;
+ }
+
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_OTHER);
+ gtk_label_set_markup (GTK_LABEL (label), info_text);
+
+ return TRUE;
+}
+
+static void
+populate_session_list (GtkWidget *session_list)
+{
+ GtkTreeIter iter;
+ char *path;
+ const char *name;
+ GDir *dir;
+ GError *error;
+ char *saved_session;
+ char *default_session;
+ char *default_name;
+ char last_session[PATH_MAX] = "";
+
+ saved_session = get_session_path ("saved-session");
+
+ if (!g_file_test (saved_session, G_FILE_TEST_IS_SYMLINK)) {
+ default_name = find_new_session_name ();
+ default_session = get_session_path (default_name);
+ rename (saved_session, default_session);
+ if (symlink (default_name, saved_session) < 0)
+ g_warning ("Failed to convert saved-session to symlink");
+ g_free (default_name);
+ g_free (default_session);
+ }
+
+ path = g_build_filename (g_get_user_config_dir (), "gnome-session", NULL);
+ error = NULL;
+ dir = g_dir_open (path, 0, &error);
+ if (dir == NULL) {
+ g_warning ("Failed to open %s: %s", path, error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ default_name = NULL;
+ if (readlink (saved_session, last_session, PATH_MAX - 1) > 0) {
+ default_name = g_path_get_basename (last_session);
+ }
+
+ while ((name = g_dir_read_name (dir)) != NULL) {
+ if (strcmp (name, "saved-session") == 0)
+ continue;
+
+ gtk_list_store_insert_with_values (store, &iter, 100, 0, name, -1);
+
+ if (g_strcmp0 (default_name, name) == 0) {
+ GtkTreeSelection *selection;
+ GtkTreeIter child_iter;
+
+ gtk_tree_model_sort_convert_child_iter_to_iter (GTK_TREE_MODEL_SORT (sort_model), &child_iter, &iter);
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (session_list));
+ gtk_tree_selection_select_iter (selection, &child_iter);
+ }
+ }
+
+ g_free (default_name);
+ g_dir_close (dir);
+
+ out:
+ g_free (saved_session);
+ g_free (path);
+}
+
+static char *
+get_selected_session (void)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *name;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (session_list));
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gtk_tree_model_get (model, &iter, 0, &name, -1);
+ return name;
+ }
+
+ return NULL;
+}
+
+static void
+remove_session (const char *name)
+{
+ char *path1, *path2;
+ char *n, *path;
+ const char *d;
+ GDir *dir;
+ GError *error;
+
+ path1 = get_session_path ("saved-session");
+ path2 = get_session_path (name);
+
+ error = NULL;
+ n = g_file_read_link (path1, &error);
+ if (n == NULL) {
+ g_warning ("Failed to read link: %s", error->message);
+ g_error_free (error);
+ }
+ else if (strcmp (n, name) == 0) {
+ unlink (path1);
+ }
+ g_free (n);
+
+ dir = g_dir_open (path2, 0, NULL);
+ while ((d = g_dir_read_name (dir)) != NULL) {
+ path = g_build_filename (path2, d, NULL);
+ unlink (path);
+ g_free (path);
+ }
+ g_dir_close (dir);
+
+ remove (path2);
+
+ g_free (path1);
+ g_free (path2);
+}
+
+static void
+on_remove_session_clicked (GtkButton *button,
+ gpointer data)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ char *name;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (session_list));
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ GtkTreeIter child_iter;
+ gtk_tree_model_get (model, &iter, 0, &name, -1);
+ remove_session (name);
+ g_free (name);
+
+ gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (model), &child_iter, &iter);
+ gtk_list_store_remove (GTK_LIST_STORE (store), &child_iter);
+
+ if (!gtk_tree_selection_get_selected (selection, NULL, NULL)) {
+ gtk_tree_model_get_iter_first (model, &iter);
+ gtk_tree_model_get (model, &iter, 0, &name, -1);
+ select_session (name);
+ g_free (name);
+ }
+ }
+}
+
+static void
+begin_rename (void)
+{
+ GtkTreePath *path;
+ GtkTreeViewColumn *column;
+ GList *cells;
+
+ gtk_widget_grab_focus (session_list);
+
+ gtk_tree_view_get_cursor (GTK_TREE_VIEW (session_list),
+ &path, &column);
+
+ cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
+
+ if (cells != NULL) {
+ GtkCellRenderer *cell;
+
+ cell = (GtkCellRenderer *) cells->data;
+ g_list_free (cells);
+
+ g_object_set (cell, "editable", TRUE, NULL);
+ gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (session_list), path,
+ column, cell, TRUE);
+ }
+ gtk_tree_path_free (path);
+}
+
+static void
+on_rename_session_clicked (GtkButton *button,
+ gpointer data)
+{
+ begin_rename ();
+}
+
+static void
+on_continue_clicked (GtkButton *button,
+ gpointer data)
+{
+ char *name;
+
+ name = get_selected_session ();
+ g_free (name);
+
+ gtk_main_quit ();
+}
+
+static void
+create_session (const char *name)
+{
+ char *path;
+ GtkTreeIter iter;
+
+ path = get_session_path (name);
+
+ if (mkdir (path, 0755) < 0) {
+ g_warning ("Failed to create directory %s", path);
+ }
+ else {
+ char *marker;
+
+ gtk_list_store_insert_with_values (store, &iter, 100, 0, name, -1);
+
+ marker = g_build_filename (path, ".new-session", NULL);
+ creat (marker, 0600);
+ g_free (marker);
+ }
+
+ g_free (path);
+}
+
+static gboolean
+rename_session (const char *old_name,
+ const char *new_name)
+{
+ char *old_path, *new_path;
+ int result;
+
+ if (g_strcmp0 (old_name, new_name) == 0) {
+ return TRUE;
+ }
+
+ if (!is_valid_session_name (new_name)) {
+ return FALSE;
+ }
+
+ old_path = get_session_path (old_name);
+ new_path = get_session_path (new_name);
+
+ result = g_rename (old_path, new_path);
+
+ if (result < 0) {
+ g_warning ("Failed to rename session from '%s' to '%s': %m", old_name, new_name);
+ }
+
+ g_free (old_path);
+ g_free (new_path);
+
+ return result == 0;
+}
+
+static gboolean
+make_session_current (const char *name)
+{
+ char *path1;
+ gboolean ret = TRUE;
+
+ path1 = g_build_filename (g_get_user_config_dir (), "gnome-session", "saved-session", NULL);
+
+ unlink (path1);
+ if (symlink (name, path1) < 0) {
+ g_warning ("Failed to make session '%s' current", name);
+ ret = FALSE;
+ }
+
+ g_free (path1);
+
+ return ret;
+}
+
+static gboolean
+create_and_select_session (const char *name)
+{
+ gchar *path;
+
+ if (name[0] == 0 || name[0] == '.' || strchr (name, '/')) {
+ g_warning ("Invalid session name");
+ return FALSE;
+ }
+
+ path = g_build_filename (g_get_user_config_dir (), "gnome-session", name, NULL);
+ if (!g_file_test (path, G_FILE_TEST_IS_DIR)) {
+ if (mkdir (path, 0755) < 0) {
+ g_warning ("Failed to create directory %s", path);
+ g_free (path);
+ return FALSE;
+ }
+ }
+
+ g_free (path);
+
+ return make_session_current (name);
+}
+
+static void
+select_session (const char *name)
+{
+ GtkTreeIter iter;
+ char *n;
+
+ make_session_current (name);
+
+ /* now select it in the list */
+ gtk_tree_model_get_iter_first (GTK_TREE_MODEL (sort_model), &iter);
+ do {
+ gtk_tree_model_get (GTK_TREE_MODEL (sort_model), &iter, 0, &n, -1);
+ if (strcmp (n, name) == 0) {
+ GtkTreePath *path;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (sort_model), &iter);
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW (session_list), path, NULL, FALSE);
+ gtk_tree_path_free (path);
+ g_free (n);
+ break;
+ }
+ g_free (n);
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (sort_model), &iter));
+}
+
+static void
+on_new_session_clicked (GtkButton *button,
+ gpointer data)
+{
+ gchar *name;
+
+ name = find_new_session_name ();
+ create_session (name);
+ select_session (name);
+
+ begin_rename ();
+}
+
+static void
+on_selection_changed (GtkTreeSelection *selection,
+ gpointer data)
+{
+ char *name;
+
+ name = get_selected_session ();
+
+ if (name == NULL) {
+ return;
+ }
+
+ make_session_current (name);
+
+ g_free (name);
+}
+
+static void
+update_remove_button (void)
+{
+ GtkWidget *button;
+
+ button = (GtkWidget *)gtk_builder_get_object (builder, "remove-session");
+ if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL) > 1) {
+ gtk_widget_set_sensitive (button, TRUE);
+ } else {
+ gtk_widget_set_sensitive (button, FALSE);
+ }
+}
+
+static void
+on_row_edited (GtkCellRendererText *cell,
+ const char *path_string,
+ const char *new_name,
+ gpointer data)
+{
+ GtkTreePath *path;
+ GtkTreeIter sort_iter, items_iter;
+ char *old_name;
+ gboolean was_renamed;
+
+ path = gtk_tree_path_new_from_string (path_string);
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (sort_model), &sort_iter, path);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (sort_model), &sort_iter, 0, &old_name, -1);
+
+ was_renamed = rename_session (old_name, new_name);
+
+ if (was_renamed) {
+ gtk_tree_model_sort_convert_iter_to_child_iter (sort_model, &items_iter, &sort_iter);
+
+ gtk_list_store_set (store, &items_iter, 0, g_strdup (new_name), -1);
+ g_free (old_name);
+ make_session_current (new_name);
+ } else {
+ begin_rename ();
+ }
+
+ gtk_tree_path_free (path);
+
+ g_object_set (cell, "editable", FALSE, NULL);
+}
+
+static void
+on_row_deleted (GtkTreeModel *model,
+ GtkTreePath *path,
+ gpointer data)
+{
+ update_remove_button ();
+}
+
+static void
+on_row_inserted (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ update_remove_button ();
+}
+
+static void
+on_row_activated (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer data)
+{
+ char *name;
+
+ name = get_selected_session ();
+ g_free (name);
+
+ gtk_main_quit ();
+}
+
+static void
+auto_save_next_session (void)
+{
+ GSettings *settings;
+
+ settings = g_settings_new (GSM_MANAGER_SCHEMA);
+ g_settings_set_boolean (settings, KEY_AUTOSAVE_ONE_SHOT, TRUE);
+ g_object_unref (settings);
+}
+
+static void
+auto_save_next_session_if_needed (void)
+{
+ char *marker;
+
+ marker = g_build_filename (g_get_user_config_dir (),
+ "gnome-session", "saved-session",
+ ".new-session", NULL);
+
+ if (g_file_test (marker, G_FILE_TEST_EXISTS)) {
+ auto_save_next_session ();
+ unlink (marker);
+ }
+ g_free (marker);
+}
+
+static int
+compare_sessions (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer data)
+{
+ char *name_a, *name_b;
+ int result;
+
+ gtk_tree_model_get (model, a, 0, &name_a, -1);
+ gtk_tree_model_get (model, b, 0, &name_b, -1);
+
+ result = g_utf8_collate (name_a, name_b);
+
+ g_free (name_a);
+ g_free (name_b);
+
+ return result;
+}
+
+static void
+on_map (GtkWidget *widget,
+ gpointer data)
+{
+ gdk_window_focus (gtk_widget_get_window (widget), GDK_CURRENT_TIME);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *widget;
+ GtkCellRenderer *cell;
+ GtkTreeViewColumn *column;
+ GtkTreeSelection *selection;
+ GError *error;
+
+ if (getenv ("SESSION_MANAGER") != NULL)
+ return 1;
+
+ gtk_init (&argc, &argv);
+ if (argc > 1) {
+ g_print ("create and select session\n");
+ if (!create_and_select_session (argv[1]))
+ return 1;
+ else
+ return 0;
+ }
+
+ builder = gtk_builder_new ();
+ gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE);
+
+ error = NULL;
+ if (!gtk_builder_add_from_file (builder, GTKBUILDER_DIR "/" "session-selector.ui", &error)) {
+ g_warning ("Could not load file 'session-selector.ui': %s", error->message);
+ exit (1);
+ }
+
+ window = (GtkWidget *) gtk_builder_get_object (builder, "main-window");
+
+ store = (GtkListStore *) gtk_builder_get_object (builder, "session-store");
+ sort_model = (GtkTreeModelSort *) gtk_builder_get_object (builder, "sort-model");
+
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sort_model),
+ 0, compare_sessions, NULL, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort_model),
+ 0, GTK_SORT_ASCENDING);
+ g_signal_connect (store, "row-deleted", G_CALLBACK (on_row_deleted), NULL);
+ g_signal_connect (store, "row-inserted", G_CALLBACK (on_row_inserted), NULL);
+ session_list = (GtkWidget *) gtk_builder_get_object (builder, "session-list");
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (session_list));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+ populate_session_list (session_list);
+
+ cell = gtk_cell_renderer_text_new ();
+ g_signal_connect (cell, "edited", G_CALLBACK (on_row_edited), NULL);
+
+ column = gtk_tree_view_column_new_with_attributes ("", cell, "text", 0, NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (session_list), GTK_TREE_VIEW_COLUMN (column));
+
+ g_signal_connect (session_list, "row-activated", G_CALLBACK (on_row_activated), NULL);
+
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (on_selection_changed), NULL);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "new-session");
+ g_signal_connect (widget, "clicked", G_CALLBACK (on_new_session_clicked), NULL);
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "remove-session");
+ g_signal_connect (widget, "clicked", G_CALLBACK (on_remove_session_clicked), NULL);
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "rename-session");
+ g_signal_connect (widget, "clicked", G_CALLBACK (on_rename_session_clicked), NULL);
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "continue-button");
+ g_signal_connect (widget, "clicked", G_CALLBACK (on_continue_clicked), NULL);
+
+ g_signal_connect (window, "map", G_CALLBACK (on_map), NULL);
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ auto_save_next_session_if_needed ();
+
+ return 0;
+}
diff --git a/tools/meson.build b/tools/meson.build
new file mode 100644
index 0000000..68be8a4
--- /dev/null
+++ b/tools/meson.build
@@ -0,0 +1,71 @@
+install_data(
+ 'gnome-session-custom-session',
+ install_dir: session_bindir
+)
+
+deps = session_deps + [
+ sm_dep,
+ ice_dep
+]
+
+cflags = ['-DLOCALE_DIR="@0@"'.format(session_localedir)]
+
+programs = [
+ # name, deps, cflags, install_dir
+ ['gnome-session-quit', deps, cflags, session_bindir],
+ ['gnome-session-inhibit', session_deps, cflags, session_bindir]
+]
+
+if enable_systemd_session
+ programs += [['gnome-session-ctl', session_bin_deps, cflags, session_libexecdir]]
+endif
+
+if enable_session_selector
+ deps = [
+ glib_dep,
+ gtk_dep
+ ]
+
+ cflags += '-DGTKBUILDER_DIR="@0@"'.format(session_pkgdatadir)
+
+ programs += [['gnome-session-selector', deps, cflags, session_bindir]]
+endif
+
+deps = [
+ gtk_dep,
+ x11_dep,
+ dependency('egl'),
+ dependency('glesv2')
+]
+
+cflags = '-DPKGDATADIR="@0@"'.format(session_pkgdatadir)
+
+programs += [['gnome-session-check-accelerated-gles-helper', deps, cflags, session_libexecdir]]
+
+deps = [
+ glib_dep,
+ x11_dep,
+ dependency('gl'),
+ dependency('epoxy'),
+ dependency('xcomposite')
+]
+
+programs += [['gnome-session-check-accelerated-gl-helper', deps, cflags, session_libexecdir]]
+
+deps += [gtk_dep]
+
+cflags = '-DLIBEXECDIR="@0@"'.format(session_libexecdir)
+
+programs += [['gnome-session-check-accelerated', deps, cflags, session_libexecdir]]
+
+foreach program: programs
+ executable(
+ program[0],
+ program[0] + '.c',
+ include_directories: top_inc,
+ dependencies: program[1],
+ c_args: program[2],
+ install: true,
+ install_dir: program[3]
+ )
+endforeach