tests/src/test-runner
tests/src/test-script-hook
tests/src/test-session
+tests/src/unity-system-compositor
tests/src/vnc-client
tests/src/X
tests/src/Xvnc
# xdmcp-manager = XDMCP manager to connect to (implies xserver-allow-tcp=true)
# xdmcp-port = XDMCP UDP/IP port to communicate on
# xdmcp-key = Authentication key to use for XDM-AUTHENTICATION-1 (stored in keys.conf)
+# unity-compositor-command = Unity compositor command to run (can also contain arguments e.g. unity-system-compositor -special-option)
+# unity-compositor-timeout = Number of seconds to wait for compositor to start
# greeter-session = Session to load for greeter
# greeter-hide-users = True to hide the user list
# greeter-allow-guest = True if the greeter should show a guest login option
#xdmcp-manager=
#xdmcp-port=177
#xdmcp-key=
+#unity-compositor-command=unity-system-compositor
+#unity-compositor-timeout=60
#greeter-session=example-gtk-gnome
#greeter-hide-users=false
#greeter-allow-guest=true
process.h \
seat.c \
seat.h \
+ seat-unity.c \
+ seat-unity.h \
seat-xdmcp-session.c \
seat-xdmcp-session.h \
seat-xlocal.c \
#include "display.h"
#include "seat-xlocal.h"
#include "seat-xremote.h"
+#include "seat-unity.h"
#include "plymouth.h"
enum {
/* Load the seat modules */
seat_register_module ("xlocal", SEAT_XLOCAL_TYPE);
seat_register_module ("xremote", SEAT_XREMOTE_TYPE);
+ seat_register_module ("unity", SEAT_UNITY_TYPE);
}
static void
config_set_string (config_get_instance (), "SeatDefaults", "type", "xlocal");
if (!config_has_key (config_get_instance (), "SeatDefaults", "xserver-command"))
config_set_string (config_get_instance (), "SeatDefaults", "xserver-command", "X");
+ if (!config_has_key (config_get_instance (), "SeatDefaults", "unity-compositor-command"))
+ config_set_string (config_get_instance (), "SeatDefaults", "unity-compositor-command", "unity-system-compositor");
if (!config_has_key (config_get_instance (), "SeatDefaults", "start-session"))
config_set_boolean (config_get_instance (), "SeatDefaults", "start-session", TRUE);
if (!config_has_key (config_get_instance (), "SeatDefaults", "allow-guest"))
--- /dev/null
+/*
+ * Copyright (C) 2012-2013 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "seat-unity.h"
+#include "configuration.h"
+#include "xserver-local.h"
+#include "xsession.h"
+#include "vt.h"
+#include "plymouth.h"
+
+typedef enum
+{
+ USC_MESSAGE_PING = 0,
+ USC_MESSAGE_PONG = 1,
+ USC_MESSAGE_READY = 2,
+ USC_MESSAGE_SESSION_CONNECTED = 3,
+ USC_MESSAGE_SET_ACTIVE_SESSION = 4
+} USCMessageID;
+
+struct SeatUnityPrivate
+{
+ /* VT we are running on */
+ gint vt;
+
+ /* TRUE if waiting for X server to start before stopping Plymouth */
+ gboolean stopping_plymouth;
+
+ /* File to log to */
+ gchar *log_file;
+
+ /* Filename of Mir socket */
+ gchar *mir_socket_filename;
+
+ /* Pipes to communicate with compositor */
+ int to_compositor_pipe[2];
+ int from_compositor_pipe[2];
+
+ /* IO channel listening on for messages from the compositor */
+ GIOChannel *from_compositor_channel;
+
+ /* TRUE when the compositor indicates it is ready */
+ gboolean compositor_ready;
+
+ /* Buffer reading from channel */
+ guint8 *read_buffer;
+ gsize read_buffer_length;
+ gsize read_buffer_n_used;
+
+ /* Compositor process */
+ Process *compositor_process;
+
+ /* Timeout when waiting for compositor to start */
+ guint compositor_timeout;
+
+ /* Next Mir ID to use for a compositor client */
+ gint next_id;
+
+ /* TRUE if using VT switching fallback */
+ gboolean use_vt_switching;
+
+ /* The currently visible display */
+ Display *active_display;
+};
+
+G_DEFINE_TYPE (SeatUnity, seat_unity, SEAT_TYPE);
+
+static void
+seat_unity_setup (Seat *seat)
+{
+ seat_set_can_switch (seat, TRUE);
+ SEAT_CLASS (seat_unity_parent_class)->setup (seat);
+}
+
+static void
+compositor_stopped_cb (Process *process, SeatUnity *seat)
+{
+ if (seat->priv->compositor_timeout != 0)
+ g_source_remove (seat->priv->compositor_timeout);
+ seat->priv->compositor_timeout = 0;
+
+ if (seat_get_is_stopping (SEAT (seat)))
+ {
+ SEAT_CLASS (seat_unity_parent_class)->stop (SEAT (seat));
+ return;
+ }
+
+ /* If stopped before it was ready, then revert to VT mode */
+ if (!seat->priv->compositor_ready)
+ {
+ g_debug ("Compositor failed to start, switching to VT mode");
+ seat->priv->use_vt_switching = TRUE;
+ SEAT_CLASS (seat_unity_parent_class)->start (SEAT (seat));
+ return;
+ }
+
+ g_debug ("Stopping Unity seat, compositor terminated");
+
+ if (seat->priv->stopping_plymouth)
+ {
+ g_debug ("Stopping Plymouth, compositor failed to start");
+ plymouth_quit (FALSE);
+ seat->priv->stopping_plymouth = FALSE;
+ }
+
+ seat_stop (SEAT (seat));
+}
+
+static void
+compositor_run_cb (Process *process, SeatUnity *seat)
+{
+ int fd;
+
+ /* Make input non-blocking */
+ fd = open ("/dev/null", O_RDONLY);
+ dup2 (fd, STDIN_FILENO);
+ close (fd);
+
+ /* Redirect output to logfile */
+ if (seat->priv->log_file)
+ {
+ int fd;
+
+ fd = g_open (seat->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (fd < 0)
+ g_warning ("Failed to open log file %s: %s", seat->priv->log_file, g_strerror (errno));
+ else
+ {
+ dup2 (fd, STDOUT_FILENO);
+ dup2 (fd, STDERR_FILENO);
+ close (fd);
+ }
+ }
+}
+
+static void
+write_message (SeatUnity *seat, guint16 id, const guint8 *payload, guint16 payload_length)
+{
+ guint8 *data;
+ gsize data_length = 4 + payload_length;
+
+ data = g_malloc (data_length);
+ data[0] = id >> 8;
+ data[1] = id & 0xFF;
+ data[2] = payload_length >> 8;
+ data[3] = payload_length & 0xFF;
+ memcpy (data + 4, payload, payload_length);
+
+ errno = 0;
+ if (write (seat->priv->to_compositor_pipe[1], data, data_length) != data_length)
+ g_warning ("Failed to write to compositor: %s", strerror (errno));
+}
+
+static gboolean
+read_cb (GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ SeatUnity *seat = data;
+ gsize n_to_read = 0;
+ guint16 id, payload_length;
+ guint8 *payload;
+
+ if (condition == G_IO_HUP)
+ {
+ g_debug ("Compositor closed communication channel");
+ return FALSE;
+ }
+
+ /* Work out how much required for a message */
+ if (seat->priv->read_buffer_n_used < 4)
+ n_to_read = 4 - seat->priv->read_buffer_n_used;
+ else
+ {
+ payload_length = seat->priv->read_buffer[2] << 8 | seat->priv->read_buffer[3];
+ n_to_read = 4 + payload_length - seat->priv->read_buffer_n_used;
+ }
+
+ /* Read from compositor */
+ if (n_to_read > 0)
+ {
+ gsize n_total, n_read = 0;
+ GIOStatus status;
+ GError *error = NULL;
+
+ n_total = seat->priv->read_buffer_n_used + n_to_read;
+ if (seat->priv->read_buffer_length < n_total)
+ seat->priv->read_buffer = g_realloc (seat->priv->read_buffer, n_total);
+
+ status = g_io_channel_read_chars (source,
+ seat->priv->read_buffer + seat->priv->read_buffer_n_used,
+ n_to_read,
+ &n_read,
+ &error);
+ if (error)
+ g_warning ("Failed to read from compositor: %s", error->message);
+ if (status != G_IO_STATUS_NORMAL)
+ return TRUE;
+ g_clear_error (&error);
+ seat->priv->read_buffer_n_used += n_read;
+ }
+
+ /* Read header */
+ if (seat->priv->read_buffer_n_used < 4)
+ return TRUE;
+ id = seat->priv->read_buffer[0] << 8 | seat->priv->read_buffer[1];
+ payload_length = seat->priv->read_buffer[2] << 8 | seat->priv->read_buffer[3];
+
+ /* Read payload */
+ if (seat->priv->read_buffer_n_used < 4 + payload_length)
+ return TRUE;
+ payload = seat->priv->read_buffer + 4;
+
+ switch (id)
+ {
+ case USC_MESSAGE_PING:
+ g_debug ("PING!");
+ write_message (seat, USC_MESSAGE_PONG, NULL, 0);
+ break;
+ case USC_MESSAGE_PONG:
+ g_debug ("PONG!");
+ break;
+ case USC_MESSAGE_READY:
+ g_debug ("READY");
+ if (!seat->priv->compositor_ready)
+ {
+ seat->priv->compositor_ready = TRUE;
+ g_debug ("Compositor ready");
+ g_source_remove (seat->priv->compositor_timeout);
+ seat->priv->compositor_timeout = 0;
+ SEAT_CLASS (seat_unity_parent_class)->start (SEAT (seat));
+ }
+ break;
+ case USC_MESSAGE_SESSION_CONNECTED:
+ g_debug ("SESSION CONNECTED");
+ break;
+ default:
+ g_warning ("Ingoring unknown message %d with %d octets from system compositor", id, payload_length);
+ break;
+ }
+
+ /* Clear buffer */
+ seat->priv->read_buffer_n_used = 0;
+
+ return TRUE;
+}
+
+static gchar *
+get_absolute_command (const gchar *command)
+{
+ gchar **tokens;
+ gchar *absolute_binary, *absolute_command = NULL;
+
+ tokens = g_strsplit (command, " ", 2);
+
+ absolute_binary = g_find_program_in_path (tokens[0]);
+ if (absolute_binary)
+ {
+ if (tokens[1])
+ absolute_command = g_strjoin (" ", absolute_binary, tokens[1], NULL);
+ else
+ absolute_command = g_strdup (absolute_binary);
+ g_free (absolute_binary);
+ }
+ else
+ absolute_command = g_strdup (command);
+
+ g_strfreev (tokens);
+
+ return absolute_command;
+}
+
+static gboolean
+compositor_timeout_cb (gpointer data)
+{
+ SeatUnity *seat = data;
+
+ /* Stop the compositor - it is not working */
+ process_stop (seat->priv->compositor_process);
+
+ return TRUE;
+}
+
+static gboolean
+seat_unity_start (Seat *seat)
+{
+ const gchar *compositor_command;
+ gchar *command, *absolute_command, *dir;
+ gboolean result;
+ int timeout;
+
+ /* Replace Plymouth if it is running */
+ if (plymouth_get_is_active () && plymouth_has_active_vt ())
+ {
+ gint active_vt = vt_get_active ();
+ if (active_vt >= vt_get_min ())
+ {
+ g_debug ("Compositor will replace Plymouth");
+ SEAT_UNITY (seat)->priv->vt = active_vt;
+ plymouth_deactivate ();
+ }
+ else
+ g_debug ("Plymouth is running on VT %d, but this is less than the configured minimum of %d so not replacing it", active_vt, vt_get_min ());
+ }
+ if (SEAT_UNITY (seat)->priv->vt < 0)
+ SEAT_UNITY (seat)->priv->vt = vt_get_unused ();
+ if (SEAT_UNITY (seat)->priv->vt < 0)
+ {
+ g_debug ("Failed to get a VT to run on");
+ return FALSE;
+ }
+ vt_ref (SEAT_UNITY (seat)->priv->vt);
+
+ /* Create pipes to talk to compositor */
+ if (pipe (SEAT_UNITY (seat)->priv->to_compositor_pipe) < 0 || pipe (SEAT_UNITY (seat)->priv->from_compositor_pipe) < 0)
+ {
+ g_debug ("Failed to create compositor pipes: %s", g_strerror (errno));
+ return FALSE;
+ }
+
+ /* Don't allow the daemon end of the pipes to be accessed in the compositor */
+ fcntl (SEAT_UNITY (seat)->priv->to_compositor_pipe[1], F_SETFD, FD_CLOEXEC);
+ fcntl (SEAT_UNITY (seat)->priv->from_compositor_pipe[0], F_SETFD, FD_CLOEXEC);
+
+ /* Listen for messages from the compositor */
+ SEAT_UNITY (seat)->priv->from_compositor_channel = g_io_channel_unix_new (SEAT_UNITY (seat)->priv->from_compositor_pipe[0]);
+ g_io_add_watch (SEAT_UNITY (seat)->priv->from_compositor_channel, G_IO_IN | G_IO_HUP, read_cb, seat);
+
+ /* Setup logging */
+ dir = config_get_string (config_get_instance (), "LightDM", "log-directory");
+ SEAT_UNITY (seat)->priv->log_file = g_build_filename (dir, "unity-system-compositor.log", NULL);
+ g_debug ("Logging to %s", SEAT_UNITY (seat)->priv->log_file);
+ g_free (dir);
+
+ SEAT_UNITY (seat)->priv->mir_socket_filename = g_strdup ("/tmp/mir_socket"); // FIXME: Use this socket by default as XMir is hardcoded to this
+ timeout = seat_get_integer_property (seat, "unity-compositor-timeout");
+ compositor_command = seat_get_string_property (seat, "unity-compositor-command");
+ command = g_strdup_printf ("%s --from-dm-fd %d --to-dm-fd %d --vt %d", compositor_command, SEAT_UNITY (seat)->priv->to_compositor_pipe[0], SEAT_UNITY (seat)->priv->from_compositor_pipe[1], SEAT_UNITY (seat)->priv->vt);
+
+ absolute_command = get_absolute_command (command);
+ g_free (command);
+
+ /* Start the compositor */
+ process_set_command (SEAT_UNITY (seat)->priv->compositor_process, absolute_command);
+ g_free (absolute_command);
+ g_signal_connect (SEAT_UNITY (seat)->priv->compositor_process, "stopped", G_CALLBACK (compositor_stopped_cb), seat);
+ g_signal_connect (SEAT_UNITY (seat)->priv->compositor_process, "run", G_CALLBACK (compositor_run_cb), seat);
+ result = process_start (SEAT_UNITY (seat)->priv->compositor_process, FALSE);
+
+ /* Close compostor ends of the pipes */
+ close (SEAT_UNITY (seat)->priv->to_compositor_pipe[0]);
+ SEAT_UNITY (seat)->priv->to_compositor_pipe[0] = 0;
+ close (SEAT_UNITY (seat)->priv->from_compositor_pipe[1]);
+ SEAT_UNITY (seat)->priv->from_compositor_pipe[1] = 0;
+
+ if (!result)
+ return FALSE;
+
+ /* Connect to the compositor */
+ timeout = seat_get_integer_property (seat, "unity-compositor-timeout");
+ if (timeout <= 0)
+ timeout = 60;
+ g_debug ("Waiting for system compositor for %ds", timeout);
+ SEAT_UNITY (seat)->priv->compositor_timeout = g_timeout_add (timeout * 1000, compositor_timeout_cb, seat);
+
+ return TRUE;
+}
+
+static DisplayServer *
+seat_unity_create_display_server (Seat *seat)
+{
+ XServerLocal *xserver;
+ const gchar *command = NULL, *layout = NULL, *config_file = NULL, *xdmcp_manager = NULL, *key_name = NULL;
+ gboolean allow_tcp;
+ gint port = 0;
+ gchar *id;
+
+ g_debug ("Starting X server on Unity compositor");
+
+ xserver = xserver_local_new ();
+
+ if (!SEAT_UNITY (seat)->priv->use_vt_switching)
+ {
+ id = g_strdup_printf ("%d", SEAT_UNITY (seat)->priv->next_id);
+ SEAT_UNITY (seat)->priv->next_id++;
+ xserver_local_set_mir_id (xserver, id);
+ xserver_local_set_mir_socket (xserver, SEAT_UNITY (seat)->priv->mir_socket_filename);
+ g_free (id);
+ }
+
+ command = seat_get_string_property (seat, "xserver-command");
+ if (command)
+ xserver_local_set_command (xserver, command);
+
+ layout = seat_get_string_property (seat, "xserver-layout");
+ if (layout)
+ xserver_local_set_layout (xserver, layout);
+
+ config_file = seat_get_string_property (seat, "xserver-config");
+ if (config_file)
+ xserver_local_set_config (xserver, config_file);
+
+ allow_tcp = seat_get_boolean_property (seat, "xserver-allow-tcp");
+ xserver_local_set_allow_tcp (xserver, allow_tcp);
+
+ xdmcp_manager = seat_get_string_property (seat, "xdmcp-manager");
+ if (xdmcp_manager)
+ xserver_local_set_xdmcp_server (xserver, xdmcp_manager);
+
+ port = seat_get_integer_property (seat, "xdmcp-port");
+ if (port > 0)
+ xserver_local_set_xdmcp_port (xserver, port);
+
+ key_name = seat_get_string_property (seat, "xdmcp-key");
+ if (key_name)
+ {
+ gchar *dir, *path;
+ GKeyFile *keys;
+ gboolean result;
+ GError *error = NULL;
+
+ dir = config_get_string (config_get_instance (), "LightDM", "config-directory");
+ path = g_build_filename (dir, "keys.conf", NULL);
+ g_free (dir);
+
+ keys = g_key_file_new ();
+ result = g_key_file_load_from_file (keys, path, G_KEY_FILE_NONE, &error);
+ if (error)
+ g_debug ("Error getting key %s", error->message);
+ g_clear_error (&error);
+
+ if (result)
+ {
+ gchar *key = NULL;
+
+ if (g_key_file_has_key (keys, "keyring", key_name, NULL))
+ key = g_key_file_get_string (keys, "keyring", key_name, NULL);
+ else
+ g_debug ("Key %s not defined", key_name);
+
+ if (key)
+ xserver_local_set_xdmcp_key (xserver, key);
+ g_free (key);
+ }
+
+ g_free (path);
+ g_key_file_free (keys);
+ }
+
+ return DISPLAY_SERVER (xserver);
+}
+
+static Session *
+seat_unity_create_session (Seat *seat, Display *display)
+{
+ XServerLocal *xserver;
+ XSession *session;
+ int vt_number;
+ gchar *t;
+
+ xserver = XSERVER_LOCAL (display_get_display_server (display));
+
+ if (SEAT_UNITY (seat)->priv->use_vt_switching)
+ vt_number = xserver_local_get_vt (xserver);
+ else
+ vt_number = SEAT_UNITY (seat)->priv->vt;
+
+ session = xsession_new (XSERVER (xserver));
+ t = g_strdup_printf ("/dev/tty%d", vt_number);
+ session_set_tty (SESSION (session), t);
+ g_free (t);
+
+ /* Set variables for logind */
+ session_set_env (SESSION (session), "XDG_SEAT", "seat0");
+ t = g_strdup_printf ("%d", vt_number);
+ session_set_env (SESSION (session), "XDG_VTNR", t);
+ g_free (t);
+
+ return SESSION (session);
+}
+
+static void
+seat_unity_set_active_display (Seat *seat, Display *display)
+{
+ XServerLocal *xserver;
+ const gchar *id;
+
+ /* If no compositor, have to use VT switching */
+ if (SEAT_UNITY (seat)->priv->use_vt_switching)
+ {
+ gint vt = xserver_local_get_vt (XSERVER_LOCAL (display_get_display_server (display)));
+ if (vt >= 0)
+ vt_set_active (vt);
+
+ SEAT_CLASS (seat_unity_parent_class)->set_active_display (seat, display);
+ return;
+ }
+
+ if (display == SEAT_UNITY (seat)->priv->active_display)
+ return;
+ SEAT_UNITY (seat)->priv->active_display = display;
+
+ xserver = XSERVER_LOCAL (display_get_display_server (display));
+ id = xserver_local_get_mir_id (xserver);
+
+ g_debug ("Switching to Mir session %s", id);
+ write_message (SEAT_UNITY (seat), USC_MESSAGE_SET_ACTIVE_SESSION, id, strlen (id));
+
+ SEAT_CLASS (seat_unity_parent_class)->set_active_display (seat, display);
+}
+
+static Display *
+seat_unity_get_active_display (Seat *seat)
+{
+ if (SEAT_UNITY (seat)->priv->use_vt_switching)
+ {
+ gint vt;
+ GList *link;
+ vt = vt_get_active ();
+ if (vt < 0)
+ return NULL;
+
+ for (link = seat_get_displays (seat); link; link = link->next)
+ {
+ Display *display = link->data;
+ XServerLocal *xserver;
+
+ xserver = XSERVER_LOCAL (display_get_display_server (display));
+ if (xserver_local_get_vt (xserver) == vt)
+ return display;
+ }
+
+ return NULL;
+ }
+
+ return SEAT_UNITY (seat)->priv->active_display;
+}
+
+static void
+seat_unity_run_script (Seat *seat, Display *display, Process *script)
+{
+ const gchar *path;
+ XServerLocal *xserver;
+
+ xserver = XSERVER_LOCAL (display_get_display_server (display));
+ path = xserver_local_get_authority_file_path (xserver);
+ process_set_env (script, "DISPLAY", xserver_get_address (XSERVER (xserver)));
+ process_set_env (script, "XAUTHORITY", path);
+
+ SEAT_CLASS (seat_unity_parent_class)->run_script (seat, display, script);
+}
+
+static void
+seat_unity_stop (Seat *seat)
+{
+ /* Stop the compositor first */
+ if (process_get_is_running (SEAT_UNITY (seat)->priv->compositor_process))
+ {
+ process_stop (SEAT_UNITY (seat)->priv->compositor_process);
+ return;
+ }
+
+ SEAT_CLASS (seat_unity_parent_class)->stop (seat);
+}
+
+static void
+seat_unity_display_removed (Seat *seat, Display *display)
+{
+ if (seat_get_is_stopping (seat))
+ return;
+
+ /* If this is the only display and it failed to start then stop this seat */
+ if (g_list_length (seat_get_displays (seat)) == 0 && !display_get_is_ready (display))
+ {
+ g_debug ("Stopping Unity seat, failed to start a display");
+ seat_stop (seat);
+ return;
+ }
+
+ /* Show a new greeter */
+ if (display == seat_get_active_display (seat))
+ {
+ g_debug ("Active display stopped, switching to greeter");
+ seat_switch_to_greeter (seat);
+ }
+}
+
+static void
+seat_unity_init (SeatUnity *seat)
+{
+ seat->priv = G_TYPE_INSTANCE_GET_PRIVATE (seat, SEAT_UNITY_TYPE, SeatUnityPrivate);
+ seat->priv->vt = -1;
+ seat->priv->compositor_process = process_new ();
+}
+
+static void
+seat_unity_finalize (GObject *object)
+{
+ SeatUnity *seat = SEAT_UNITY (object);
+
+ if (seat->priv->vt >= 0)
+ vt_unref (seat->priv->vt);
+ g_free (seat->priv->log_file);
+ g_free (seat->priv->mir_socket_filename);
+ close (seat->priv->to_compositor_pipe[0]);
+ close (seat->priv->to_compositor_pipe[1]);
+ close (seat->priv->from_compositor_pipe[0]);
+ close (seat->priv->from_compositor_pipe[1]);
+ g_io_channel_unref (seat->priv->from_compositor_channel);
+ g_free (seat->priv->read_buffer);
+ g_object_unref (seat->priv->compositor_process);
+
+ G_OBJECT_CLASS (seat_unity_parent_class)->finalize (object);
+}
+
+static void
+seat_unity_class_init (SeatUnityClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ SeatClass *seat_class = SEAT_CLASS (klass);
+
+ object_class->finalize = seat_unity_finalize;
+ seat_class->setup = seat_unity_setup;
+ seat_class->start = seat_unity_start;
+ seat_class->create_display_server = seat_unity_create_display_server;
+ seat_class->create_session = seat_unity_create_session;
+ seat_class->set_active_display = seat_unity_set_active_display;
+ seat_class->get_active_display = seat_unity_get_active_display;
+ seat_class->run_script = seat_unity_run_script;
+ seat_class->stop = seat_unity_stop;
+ seat_class->display_removed = seat_unity_display_removed;
+
+ g_type_class_add_private (klass, sizeof (SeatUnityPrivate));
+}
--- /dev/null
+/*
+ * Copyright (C) 2012-2013 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+#ifndef _SEAT_UNITY_H_
+#define _SEAT_UNITY_H_
+
+#include <glib-object.h>
+#include "seat.h"
+
+G_BEGIN_DECLS
+
+#define SEAT_UNITY_TYPE (seat_unity_get_type())
+#define SEAT_UNITY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAT_UNITY_TYPE, SeatUnity))
+
+typedef struct SeatUnityPrivate SeatUnityPrivate;
+
+typedef struct
+{
+ Seat parent_instance;
+ SeatUnityPrivate *priv;
+} SeatUnity;
+
+typedef struct
+{
+ SeatClass parent_class;
+} SeatUnityClass;
+
+GType seat_unity_get_type (void);
+
+G_END_DECLS
+
+#endif /* _SEAT_UNITY_H_ */
/* XDMCP key to use */
gchar *xdmcp_key;
+ /* ID to report to Mir */
+ gchar *mir_id;
+
+ /* Filename of socket Mir is listening on */
+ gchar *mir_socket;
+
/* TRUE when received ready signal */
gboolean got_signal;
server->priv->xdmcp_key = g_strdup (key);
}
+void
+xserver_local_set_mir_id (XServerLocal *server, const gchar *id)
+{
+ g_return_if_fail (server != NULL);
+ g_free (server->priv->mir_id);
+ server->priv->mir_id = g_strdup (id);
+
+ if (server->priv->have_vt_ref)
+ {
+ vt_unref (server->priv->vt);
+ server->priv->have_vt_ref = FALSE;
+ }
+ server->priv->vt = -1;
+}
+
+const gchar *xserver_local_get_mir_id (XServerLocal *server)
+{
+ g_return_val_if_fail (server != NULL, NULL);
+ return server->priv->mir_id;
+}
+
+void
+xserver_local_set_mir_socket (XServerLocal *server, const gchar *socket)
+{
+ g_return_if_fail (server != NULL);
+ g_free (server->priv->mir_socket);
+ server->priv->mir_socket = g_strdup (socket);
+}
+
gint
xserver_local_get_vt (XServerLocal *server)
{
if (server->priv->authority_file)
g_string_append_printf (command, " -auth %s", server->priv->authority_file);
+ /* Setup for running inside Mir */
+ if (server->priv->mir_id)
+ g_string_append_printf (command, " -mir %s", server->priv->mir_id);
+
+ if (server->priv->mir_socket)
+ g_string_append_printf (command, " -mirSocket %s", server->priv->mir_socket);
+
/* Connect to a remote server using XDMCP */
if (server->priv->xdmcp_server != NULL)
{
g_free (self->priv->layout);
g_free (self->priv->xdmcp_server);
g_free (self->priv->xdmcp_key);
+ g_free (self->priv->mir_id);
+ g_free (self->priv->mir_socket);
g_free (self->priv->authority_file);
if (self->priv->have_vt_ref)
vt_unref (self->priv->vt);
void xserver_local_set_xdmcp_key (XServerLocal *server, const gchar *key);
+void xserver_local_set_mir_id (XServerLocal *server, const gchar *id);
+
+const gchar *xserver_local_get_mir_id (XServerLocal *server);
+
+void xserver_local_set_mir_socket (XServerLocal *server, const gchar *socket);
+
gint xserver_local_get_vt (XServerLocal *server);
const gchar *xserver_local_get_authority_file_path (XServerLocal *server);
test-python-power-no-login1 \
test-python-power-no-services \
test-open-file-descriptors \
- test-xdmcp-open-file-descriptors
+ test-xdmcp-open-file-descriptors \
+ test-unity-compositor-command \
+ test-unity-compositor-fail-start \
+ test-unity-compositor-fail-ready \
+ test-unity-autologin \
+ test-unity-login \
+ test-unity-switch
# test-session-exit-error
# test-greeter-no-exit
scripts/switch-to-user-logout.conf \
scripts/switch-to-user-no-password.conf \
scripts/system-xauthority.conf \
+ scripts/unity-autologin.conf \
+ scripts/unity-compositor-command.conf \
+ scripts/unity-compositor-fail-ready.conf \
+ scripts/unity-compositor-fail-start.conf \
+ scripts/unity-login.conf \
+ scripts/unity-switch.conf \
scripts/users.conf \
scripts/util-path.conf \
scripts/user-renamed.conf \
--- /dev/null
+#
+# Check can automatically login with Unity seat type
+#
+
+[SeatDefaults]
+type=unity
+autologin-user=have-password1
+user-session=default
+
+#?RUNNER DAEMON-START
+
+# System compositor starts
+#?UNITY-SYSTEM-COMPOSITOR START
+#?*UNITY-SYSTEM-COMPOSITOR READY
+
+# X server starts
+#?XSERVER-0 START MIR-ID=0
+#?XSERVER-0 INDICATE-READY
+
+# LightDM connects to X server
+#?XSERVER-0 ACCEPT-CONNECT
+
+# Session starts
+#?SESSION-X-0 START USER=have-password1
+#?XSERVER-0 ACCEPT-CONNECT
+#?SESSION-X-0 CONNECT-XSERVER
+
+# System compositor switches to session
+#?UNITY-SYSTEM-COMPOSITOR SET-ACTIVE-SESSION ID=0
+
+# Cleanup
+#?*STOP-DAEMON
+#?SESSION-X-0 TERMINATE SIGNAL=15
+#?XSERVER-0 TERMINATE SIGNAL=15
+#?UNITY-SYSTEM-COMPOSITOR TERMINATE SIGNAL=15
+#?RUNNER DAEMON-EXIT STATUS=0
--- /dev/null
+#
+# Check can set the unity compositor command
+#
+
+[SeatDefaults]
+type=unity
+user-session=default
+unity-compositor-command=unity-system-compositor --test
+
+#?RUNNER DAEMON-START
+
+# System compositor starts
+#?UNITY-SYSTEM-COMPOSITOR START TEST
+#?*UNITY-SYSTEM-COMPOSITOR READY
+
+# X server starts
+#?XSERVER-0 START MIR-ID=0
+#?XSERVER-0 INDICATE-READY
+
+# LightDM connects to X server
+#?XSERVER-0 ACCEPT-CONNECT
+
+# Greeter starts
+#?GREETER-X-0 START
+#?XSERVER-0 ACCEPT-CONNECT
+#?GREETER-X-0 CONNECT-XSERVER
+#?GREETER-X-0 CONNECT-TO-DAEMON
+#?GREETER-X-0 CONNECTED-TO-DAEMON
+
+# System compositor switches to greeter
+#?UNITY-SYSTEM-COMPOSITOR SET-ACTIVE-SESSION ID=0
+
+# Cleanup
+#?*STOP-DAEMON
+#?GREETER-X-0 TERMINATE SIGNAL=15
+#?XSERVER-0 TERMINATE SIGNAL=15
+#?UNITY-SYSTEM-COMPOSITOR TERMINATE SIGNAL=15
+#?RUNNER DAEMON-EXIT STATUS=0
--- /dev/null
+#
+# Check falls back to VT switching when the compositor fails to indicate it is ready
+#
+
+[SeatDefaults]
+type=unity
+unity-compositor-timeout=1
+
+#?RUNNER DAEMON-START
+
+# System compositor starts but doesn't indicate it is ready
+#?UNITY-SYSTEM-COMPOSITOR START
+
+# Timeout and compositor is stopped
+#?UNITY-SYSTEM-COMPOSITOR TERMINATE SIGNAL=15
+
+# X server starts in VT mode
+#?XSERVER-0 START VT=8
+#?XSERVER-0 INDICATE-READY
+
+# LightDM connects to X server
+#?XSERVER-0 ACCEPT-CONNECT
+
+# Greeter starts
+#?GREETER-X-0 START
+#?XSERVER-0 ACCEPT-CONNECT
+#?GREETER-X-0 CONNECT-XSERVER
+#?GREETER-X-0 CONNECT-TO-DAEMON
+#?GREETER-X-0 CONNECTED-TO-DAEMON
+
+# Cleanup
+#?*STOP-DAEMON
+#?GREETER-X-0 TERMINATE SIGNAL=15
+#?XSERVER-0 TERMINATE SIGNAL=15
+#?RUNNER DAEMON-EXIT STATUS=0
--- /dev/null
+#
+# Check falls back to VT switching when the compositor fails to start
+#
+
+[unity-system-compositor-config]
+return-value=1
+
+[SeatDefaults]
+type=unity
+
+#?RUNNER DAEMON-START
+
+# System compositor fails to start
+#?UNITY-SYSTEM-COMPOSITOR START
+#?UNITY-SYSTEM-COMPOSITOR EXIT CODE=1
+
+# X server starts in VT mode
+#?XSERVER-0 START VT=8
+#?XSERVER-0 INDICATE-READY
+
+# LightDM connects to X server
+#?XSERVER-0 ACCEPT-CONNECT
+
+# Greeter starts
+#?GREETER-X-0 START
+#?XSERVER-0 ACCEPT-CONNECT
+#?GREETER-X-0 CONNECT-XSERVER
+#?GREETER-X-0 CONNECT-TO-DAEMON
+#?GREETER-X-0 CONNECTED-TO-DAEMON
+
+# Cleanup
+#?*STOP-DAEMON
+#?GREETER-X-0 TERMINATE SIGNAL=15
+#?XSERVER-0 TERMINATE SIGNAL=15
+#?RUNNER DAEMON-EXIT STATUS=0
--- /dev/null
+#
+# Check can login with Unity seat type
+#
+
+[SeatDefaults]
+type=unity
+user-session=default
+
+#?RUNNER DAEMON-START
+
+# System compositor starts
+#?UNITY-SYSTEM-COMPOSITOR START
+#?*UNITY-SYSTEM-COMPOSITOR READY
+
+# X server starts
+#?XSERVER-0 START MIR-ID=0
+#?XSERVER-0 INDICATE-READY
+
+# LightDM connects to X server
+#?XSERVER-0 ACCEPT-CONNECT
+
+# Greeter starts
+#?GREETER-X-0 START
+#?XSERVER-0 ACCEPT-CONNECT
+#?GREETER-X-0 CONNECT-XSERVER
+#?GREETER-X-0 CONNECT-TO-DAEMON
+#?GREETER-X-0 CONNECTED-TO-DAEMON
+
+# System compositor switches to greeter
+#?UNITY-SYSTEM-COMPOSITOR SET-ACTIVE-SESSION ID=0
+
+# Log into account with a password
+#?*GREETER-X-0 AUTHENTICATE USERNAME=have-password1
+#?GREETER-X-0 SHOW-PROMPT TEXT="Password:"
+#?*GREETER-X-0 RESPOND TEXT="password"
+#?GREETER-X-0 AUTHENTICATION-COMPLETE USERNAME=have-password1 AUTHENTICATED=TRUE
+#?*GREETER-X-0 START-SESSION
+#?GREETER-X-0 TERMINATE SIGNAL=15
+
+# Session starts
+#?SESSION-X-0 START USER=have-password1
+#?XSERVER-0 ACCEPT-CONNECT
+#?SESSION-X-0 CONNECT-XSERVER
+
+# Cleanup
+#?*STOP-DAEMON
+#?SESSION-X-0 TERMINATE SIGNAL=15
+#?XSERVER-0 TERMINATE SIGNAL=15
+#?UNITY-SYSTEM-COMPOSITOR TERMINATE SIGNAL=15
+#?RUNNER DAEMON-EXIT STATUS=0
--- /dev/null
+#
+# Check system compositor correctly switches
+#
+
+[SeatDefaults]
+type=unity
+autologin-user=have-password1
+user-session=default
+
+#?RUNNER DAEMON-START
+
+# System compositor starts
+#?UNITY-SYSTEM-COMPOSITOR START
+#?*UNITY-SYSTEM-COMPOSITOR READY
+
+# X server starts
+#?XSERVER-0 START MIR-ID=0
+#?XSERVER-0 INDICATE-READY
+
+# LightDM connects to X server
+#?XSERVER-0 ACCEPT-CONNECT
+
+# Session starts
+#?SESSION-X-0 START USER=have-password1
+#?XSERVER-0 ACCEPT-CONNECT
+#?SESSION-X-0 CONNECT-XSERVER
+
+# System compositor switches to session
+#?UNITY-SYSTEM-COMPOSITOR SET-ACTIVE-SESSION ID=0
+
+# Show the greeter
+#?*SWITCH-TO-GREETER
+#?RUNNER SWITCH-TO-GREETER
+
+# New X server starts
+#?XSERVER-1 START MIR-ID=1
+#?XSERVER-1 INDICATE-READY
+
+# LightDM connects to X server
+#?XSERVER-1 ACCEPT-CONNECT
+
+# Greeter starts
+#?GREETER-X-1 START
+#?XSERVER-1 ACCEPT-CONNECT
+#?GREETER-X-1 CONNECT-XSERVER
+#?GREETER-X-1 CONNECT-TO-DAEMON
+#?GREETER-X-1 CONNECTED-TO-DAEMON
+
+# System compositor switches to greeter
+#?UNITY-SYSTEM-COMPOSITOR SET-ACTIVE-SESSION ID=1
+
+# Login
+#?*GREETER-X-1 AUTHENTICATE USERNAME=have-password2
+#?GREETER-X-1 SHOW-PROMPT TEXT="Password:"
+#?*GREETER-X-1 RESPOND TEXT="password"
+#?GREETER-X-1 AUTHENTICATION-COMPLETE USERNAME=have-password2 AUTHENTICATED=TRUE
+#?*GREETER-X-1 START-SESSION
+#?GREETER-X-1 TERMINATE SIGNAL=15
+
+# New session starts
+#?SESSION-X-1 START USER=have-password2
+#?XSERVER-1 ACCEPT-CONNECT
+#?SESSION-X-1 CONNECT-XSERVER
+
+# Logout of new session
+#?*SESSION-X-1 LOGOUT
+#?XSERVER-1 TERMINATE SIGNAL=15
+
+# X server starts
+#?XSERVER-1 START MIR-ID=2
+#?XSERVER-1 INDICATE-READY
+#?XSERVER-1 ACCEPT-CONNECT
+
+# Greeter starts
+#?GREETER-X-1 START
+#?XSERVER-1 ACCEPT-CONNECT
+#?GREETER-X-1 CONNECT-XSERVER
+#?GREETER-X-1 CONNECT-TO-DAEMON
+#?GREETER-X-1 CONNECTED-TO-DAEMON
+
+# System compositor switches to greeter
+#?UNITY-SYSTEM-COMPOSITOR SET-ACTIVE-SESSION ID=2
+
+# Cleanup
+#?*STOP-DAEMON
+#?SESSION-X-0 TERMINATE SIGNAL=15
+#?XSERVER-0 TERMINATE SIGNAL=15
+#?GREETER-X-1 TERMINATE SIGNAL=15
+#?XSERVER-1 TERMINATE SIGNAL=15
+#?UNITY-SYSTEM-COMPOSITOR TERMINATE SIGNAL=15
+#?RUNNER DAEMON-EXIT STATUS=0
-noinst_PROGRAMS = dbus-env test-runner X Xvnc test-greeter-wrapper test-gobject-greeter test-session test-script-hook guest-account initctl plymouth vnc-client
-dist_noinst_SCRIPTS = lightdm-session test-python-greeter
+noinst_PROGRAMS = dbus-env \
+ initctl \
+ plymouth \
+ test-gobject-greeter \
+ test-greeter-wrapper \
+ test-runner \
+ test-script-hook \
+ test-session \
+ guest-account \
+ unity-system-compositor \
+ vnc-client \
+ X \
+ Xvnc
+dist_noinst_SCRIPTS = lightdm-session \
+ test-python-greeter
noinst_LTLIBRARIES = libsystem.la
libsystem_la_SOURCES = libsystem.c
$(GLIB_LIBS) \
$(GIO_UNIX_LIBS)
+unity_system_compositor_SOURCES = unity-system-compositor.c status.c status.h
+unity_system_compositor_CFLAGS = \
+ $(WARN_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(GIO_CFLAGS) \
+ $(GIO_UNIX_CFLAGS)
+unity_system_compositor_LDADD = \
+ $(GLIB_LIBS) \
+ $(GIO_LIBS) \
+ $(GIO_UNIX_LIBS)
+
vnc_client_SOURCES = vnc-client.c status.c status.h
vnc_client_CFLAGS = \
$(WARN_CFLAGS) \
gboolean do_xdmcp = FALSE;
guint xdmcp_port = 0;
gchar *xdmcp_host = NULL;
+ gchar *mir_id = NULL;
gchar *lock_filename;
int lock_file;
GString *status_text;
{
vt_number = atoi (arg + 2);
}
- else if (g_str_has_prefix (arg, "-novtswitch"))
+ else if (strcmp (arg, "-novtswitch") == 0)
{
/* Ignore VT args */
}
+ else if (strcmp (arg, "-mir") == 0)
+ {
+ mir_id = argv[i+1];
+ i++;
+ }
+ else if (strcmp (arg, "-mirSocket") == 0)
+ {
+ /* FIXME */
+ i++;
+ }
else
{
g_printerr ("Unrecognized option: %s\n"
"-query host-name Contact named host for XDMCP\n"
"-broadcast Broadcast for XDMCP\n"
"-port port-num UDP port number to send messages to\n"
+ "-mir id Mir ID to use\n"
+ "-mirSocket name Mir socket to use\n"
"vtxx Use virtual terminal xx instead of the next available\n",
arg, argv[0]);
return EXIT_FAILURE;
g_string_printf (status_text, "XSERVER-%d START", display_number);
if (vt_number >= 0)
g_string_append_printf (status_text, " VT=%d", vt_number);
+ if (mir_id != NULL)
+ g_string_append_printf (status_text, " MIR-ID=%s", mir_id);
status_notify (status_text->str);
g_string_free (status_text, TRUE);
/* Forward to external processes */
else if (g_str_has_prefix (name, "SESSION-") ||
g_str_has_prefix (name, "GREETER-") ||
- g_str_has_prefix (name, "XSERVER-"))
+ g_str_has_prefix (name, "XSERVER-") ||
+ strcmp (name, "UNITY-SYSTEM-COMPOSITOR") == 0)
{
GList *link;
for (link = status_clients; link; link = link->next)
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "status.h"
+
+static GMainLoop *loop;
+static int exit_status = EXIT_SUCCESS;
+static int from_dm_fd = -1, to_dm_fd = -1;
+
+static GKeyFile *config;
+
+static void
+quit (int status)
+{
+ exit_status = status;
+ g_main_loop_quit (loop);
+}
+
+static void
+signal_cb (int signum)
+{
+ status_notify ("UNITY-SYSTEM-COMPOSITOR TERMINATE SIGNAL=%d", signum);
+ quit (EXIT_SUCCESS);
+}
+
+typedef enum
+{
+ USC_MESSAGE_PING = 0,
+ USC_MESSAGE_PONG = 1,
+ USC_MESSAGE_READY = 2,
+ USC_MESSAGE_SESSION_CONNECTED = 3,
+ USC_MESSAGE_SET_ACTIVE_SESSION = 4
+} USCMessageID;
+
+static void
+write_message (guint16 id, const guint8 *payload, guint16 payload_length)
+{
+ guint8 *data;
+ gsize data_length = 4 + payload_length;
+
+ data = g_malloc (data_length);
+ data[0] = id >> 8;
+ data[1] = id & 0xFF;
+ data[2] = payload_length >> 8;
+ data[3] = payload_length & 0xFF;
+ memcpy (data + 4, payload, payload_length);
+
+ if (write (to_dm_fd, data, data_length) < 0)
+ fprintf (stderr, "Failed to write to daemon: %s\n", strerror (errno));
+}
+
+static gboolean
+read_message_cb (GIOChannel *channel, GIOCondition condition, gpointer data)
+{
+ gchar header[4], *payload;
+ gsize n_read;
+ guint16 id;
+ guint16 payload_length;
+ GError *error = NULL;
+
+ if (g_io_channel_read_chars (channel, header, 4, &n_read, &error) != G_IO_STATUS_NORMAL)
+ {
+ g_printerr ("Failed to read header: %s\n", error->message);
+ return FALSE;
+ }
+ if (n_read != 4)
+ {
+ g_printerr ("Short read for header, %d instead of expected 4\n", n_read);
+ return FALSE;
+ }
+ id = header[0] << 8 | header[1];
+ payload_length = header[2] << 8 | header[3];
+ payload = g_malloc0 (payload_length + 1);
+ if (g_io_channel_read_chars (channel, payload, payload_length, &n_read, &error) != G_IO_STATUS_NORMAL)
+ {
+ g_printerr ("Failed to read payload: %s\n", error->message);
+ return FALSE;
+ }
+ if (n_read != payload_length)
+ {
+ g_printerr ("Short read for payload, %d instead of expected %d\n", n_read, payload_length);
+ return FALSE;
+ }
+
+ switch (id)
+ {
+ case USC_MESSAGE_PING:
+ status_notify ("UNITY-SYSTEM-COMPOSITOR PING");
+ break;
+ case USC_MESSAGE_SET_ACTIVE_SESSION:
+ status_notify ("UNITY-SYSTEM-COMPOSITOR SET-ACTIVE-SESSION ID=%s", (gchar *)payload);
+ break;
+ default:
+ g_printerr ("Ignoring message %d with %d octets\n", id, payload_length);
+ break;
+ }
+
+ free (payload);
+
+ return TRUE;
+}
+
+static void
+request_cb (const gchar *request)
+{
+ if (!request)
+ {
+ g_main_loop_quit (loop);
+ return;
+ }
+
+ if (strcmp (request, "UNITY-SYSTEM-COMPOSITOR PING") == 0)
+ write_message (USC_MESSAGE_PING, NULL, 0);
+ else if (strcmp (request, "UNITY-SYSTEM-COMPOSITOR PONG") == 0)
+ write_message (USC_MESSAGE_PONG, NULL, 0);
+ else if (strcmp (request, "UNITY-SYSTEM-COMPOSITOR READY") == 0)
+ write_message (USC_MESSAGE_READY, NULL, 0);
+}
+
+int
+main (int argc, char **argv)
+{
+ int i;
+ gboolean test = FALSE;
+
+ signal (SIGINT, signal_cb);
+ signal (SIGTERM, signal_cb);
+ signal (SIGHUP, signal_cb);
+
+#if !defined(GLIB_VERSION_2_36)
+ g_type_init ();
+#endif
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ status_connect (request_cb);
+
+ for (i = 1; i < argc; i++)
+ {
+ char *arg = argv[i];
+
+ if (strcmp (arg, "--from-dm-fd") == 0)
+ {
+ from_dm_fd = atoi (argv[i+1]);
+ i++;
+ }
+ else if (strcmp (arg, "--to-dm-fd") == 0)
+ {
+ to_dm_fd = atoi (argv[i+1]);
+ i++;
+ }
+ else if (strcmp (arg, "--vt") == 0)
+ {
+ //vt_number = atoi (argv[i+1]);
+ i++;
+ }
+ else if (strcmp (arg, "--test") == 0)
+ test = TRUE;
+ else
+ return EXIT_FAILURE;
+ }
+
+ g_io_add_watch (g_io_channel_unix_new (from_dm_fd), G_IO_IN, read_message_cb, NULL);
+
+ if (test)
+ status_notify ("UNITY-SYSTEM-COMPOSITOR START TEST");
+ else
+ status_notify ("UNITY-SYSTEM-COMPOSITOR START");
+
+ config = g_key_file_new ();
+ g_key_file_load_from_file (config, g_build_filename (g_getenv ("LIGHTDM_TEST_ROOT"), "script", NULL), G_KEY_FILE_NONE, NULL);
+
+ if (g_key_file_has_key (config, "unity-system-compositor-config", "return-value", NULL))
+ {
+ int return_value = g_key_file_get_integer (config, "unity-system-compositor-config", "return-value", NULL);
+ status_notify ("UNITY-SYSTEM-COMPOSITOR EXIT CODE=%d", return_value);
+ return return_value;
+ }
+
+ g_main_loop_run (loop);
+
+ return exit_status;
+}
--- /dev/null
+#!/bin/sh
+./src/dbus-env ./src/test-runner unity-autologin test-gobject-greeter
--- /dev/null
+#!/bin/sh
+./src/dbus-env ./src/test-runner unity-compositor-command test-gobject-greeter
--- /dev/null
+#!/bin/sh
+./src/dbus-env ./src/test-runner unity-compositor-fail-ready test-gobject-greeter
--- /dev/null
+#!/bin/sh
+./src/dbus-env ./src/test-runner unity-compositor-fail-start test-gobject-greeter
--- /dev/null
+#!/bin/sh
+./src/dbus-env ./src/test-runner unity-login test-gobject-greeter
--- /dev/null
+#!/bin/sh
+./src/dbus-env ./src/test-runner unity-switch test-gobject-greeter