[clutter/fosdem-2012: 1/5] test-wayland-surface: Adds basic xwayland support
- From: Robert Bragg <rbragg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [clutter/fosdem-2012: 1/5] test-wayland-surface: Adds basic xwayland support
- Date: Wed, 4 Jan 2012 19:22:47 +0000 (UTC)
commit 7f837f9104ff0a87e0d62ecb8bf18d2f2f7b69d1
Author: Robert Bragg <robert linux intel com>
Date: Thu Dec 8 16:07:09 2011 +0000
test-wayland-surface: Adds basic xwayland support
This adds basic support for spawning a headless xwayland server when
running test-wayland-surface so that legacy X clients can be composited.
Since this depends on the xserver extension protocol which currently
lives in the wayland-demos repository this patch adds a
--with-wayland-protocols ./configure option so clutter can be told where
to find the xml for the xserver extension xml.
FIXME: This patch is currently hard coding the patch for xwayland to be
/home/bob/local/xserver-xwayland/bin/X
configure.ac | 17 +-
tests/interactive/Makefile.am | 36 +++-
tests/interactive/test-wayland-surface.c | 469 +++++++++++++++++++++++++++++-
3 files changed, 515 insertions(+), 7 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index aa10d43..30eff50 100644
--- a/configure.ac
+++ b/configure.ac
@@ -332,6 +332,22 @@ AC_ARG_ENABLE([wayland-compositor],
[enable_wayland_compositor=no])
])
+dnl The tests/interactive wayland compositor needs to refer to the xserver.xml
+dnl protocol extension that's currently part of the wayland demo compositor repo,
+dnl so we need this option to be told where that can be found.
+AC_ARG_WITH([wayland-protocols],
+ [AS_HELP_STRING([--with-wayland-protocols], [Location for wayland extension protocol specs])],
+ [
+ AC_PATH_PROG([WAYLAND_SCANNER],[wayland-scanner],[no])
+ AS_IF([test "x$WAYLAND_SCANNER" = "xno"],
+ AC_MSG_ERROR([Could not find wayland-scanner in your PATH, required for parsing wayland extension protocols]))
+ AC_SUBST([WAYLAND_SCANNER])
+ have_wayland_protocols_dir="yes"
+ AC_SUBST([WAYLAND_EXTENSION_PROTOCOLS_DIR],$withval)
+ ],
+ [])
+AM_CONDITIONAL(HAVE_WAYLAND_EXTENSION_PROTOCOLS_DIR, [test "x$have_wayland_protocols_dir" = "xyes"])
+
AS_IF([test "x$enable_wayland_compositor" = "xyes"],
[
PKG_CHECK_EXISTS([wayland-server],
@@ -963,7 +979,6 @@ CLUTTER_LIBS="$FLAVOUR_LIBS $CLUTTER_DEPS_LIBS $CLUTTER_PROFILE_LDFLAGS $GLIB_LI
AC_SUBST(CLUTTER_CFLAGS)
AC_SUBST(CLUTTER_LIBS)
-
dnl === GObject-Introspection check ===========================================
GOBJECT_INTROSPECTION_CHECK([gi_req_version])
diff --git a/tests/interactive/Makefile.am b/tests/interactive/Makefile.am
index 357b6bd..6474b06 100644
--- a/tests/interactive/Makefile.am
+++ b/tests/interactive/Makefile.am
@@ -65,8 +65,10 @@ UNIT_TESTS += test-pixmap.c
endif
if SUPPORT_WAYLAND_COMPOSITOR
+if HAVE_WAYLAND_EXTENSION_PROTOCOLS_DIR
UNIT_TESTS += test-wayland-surface.c
endif
+endif
if OS_WIN32
SHEXT =
@@ -153,7 +155,20 @@ common_ldadd = $(top_builddir)/clutter/libclutter- CLUTTER_API_VERSION@.la
noinst_PROGRAMS = test-interactive
-test_interactive_SOURCES = test-main.c test-unit-names.h $(UNIT_TESTS)
+test_interactive_SOURCES = \
+ test-main.c \
+ test-unit-names.h
+
+if SUPPORT_WAYLAND_COMPOSITOR
+if HAVE_WAYLAND_EXTENSION_PROTOCOLS_DIR
+test_interactive_SOURCES += \
+ xserver-protocol.c \
+ xserver-server-protocol.h
+endif
+endif
+
+test_interactive_SOURCES += $(UNIT_TESTS)
+
test_interactive_CFLAGS = $(CLUTTER_CFLAGS) $(MAINTAINER_CFLAGS)
test_interactive_CPPFLAGS = \
-DTESTS_DATADIR=\""$(abs_top_srcdir)/tests/data"\" \
@@ -169,7 +184,16 @@ EXTRA_DIST = \
DISTCLEANFILES = wrapper.sh .gitignore test-unit-names.h
-BUILT_SOURCES = test-unit-names.h wrappers
+BUILT_SOURCES = \
+ test-unit-names.h \
+ wrappers
+
+if SUPPORT_WAYLAND_COMPOSITOR
+if HAVE_WAYLAND_EXTENSION_PROTOCOLS_DIR
+BUILT_SOURCES += xserver-server-protocol.h xserver-protocol.c
+DISTCLEANFILES += xserver-server-protocol.h xserver-protocol.c
+endif
+endif
dist-hook: $(top_builddir)/build/win32/vs9/test-interactive-clutter.vcproj $(top_builddir)/build/win32/vs10/test-interactive-clutter.vcxproj $(top_builddir)/build/win32/vs10/test-interactive-clutter.vcxproj.filters
@@ -219,3 +243,11 @@ EXTRA_DIST += \
$(top_builddir)/build/win32/vs10/test-interactive-clutter.vcxproj.filters
clean-local: clean-wrappers
+
+%-protocol.c : @WAYLAND_EXTENSION_PROTOCOLS_DIR@/%.xml
+ $(AM_V_GEN)$(WAYLAND_SCANNER) code < $< > $@
+%-server-protocol.h : @WAYLAND_EXTENSION_PROTOCOLS_DIR@/%.xml
+ $(AM_V_GEN)$(WAYLAND_SCANNER) server-header < $< > $@
+%-client-protocol.h : @WAYLAND_EXTENSION_PROTOCOLS_DIR@/%.xml'
+ $(AM_V_GEN)$(WAYLAND_SCANNER) client-header < $< > $@
+
diff --git a/tests/interactive/test-wayland-surface.c b/tests/interactive/test-wayland-surface.c
index 2627412..69de77b 100644
--- a/tests/interactive/test-wayland-surface.c
+++ b/tests/interactive/test-wayland-surface.c
@@ -6,9 +6,19 @@
#include <glib.h>
#include <sys/time.h>
#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <sys/wait.h>
#include <wayland-server.h>
+#include "xserver-server-protocol.h"
+
typedef struct _TWSCompositor TWSCompositor;
typedef struct
@@ -69,8 +79,34 @@ struct _TWSCompositor
GSource *wayland_event_source;
GList *surfaces;
GArray *frame_callbacks;
+
+ int xwayland_display_index;
+ char *xwayland_lockfile;
+ int xwayland_abstract_fd;
+ int xwayland_unix_fd;
+ pid_t xwayland_pid;
+ struct wl_client *xwayland_client;
+ struct wl_resource *xserver_resource;
};
+static int signal_pipe[2];
+
+void
+report_signal (int signal)
+{
+ switch (signal)
+ {
+ case SIGINT:
+ write (signal_pipe[1], "I", 1);
+ break;
+ case SIGCHLD:
+ write (signal_pipe[1], "C", 1);
+ break;
+ default:
+ break;
+ }
+}
+
static guint32
get_time (void)
{
@@ -316,7 +352,8 @@ tws_surface_free (TWSSurface *surface)
compositor->surfaces = g_list_remove (compositor->surfaces, surface);
tws_surface_detach_buffer (surface);
- clutter_actor_destroy (surface->actor);
+ if (surface->actor)
+ clutter_actor_destroy (surface->actor);
g_slice_free (TWSSurface, surface);
}
@@ -508,14 +545,412 @@ bind_shell (struct wl_client *client,
&tws_shell_interface, id, data);
}
+static char *
+create_lockfile (int display, int *display_out)
+{
+ char *filename;
+ int size;
+ char pid[11];
+ int fd;
+
+ do
+ {
+ char *end;
+ pid_t other;
+
+ filename = g_strdup_printf ("/tmp/.X%d-lock", display);
+ fd = open (filename, O_WRONLY | O_CLOEXEC | O_CREAT | O_EXCL, 0444);
+
+ if (fd < 0 && errno == EEXIST)
+ {
+ fd = open (filename, O_CLOEXEC, O_RDONLY);
+ if (fd < 0 || read (fd, pid, 11) != 11)
+ {
+ const char *msg = strerror (errno);
+ g_warning ("can't read lock file %s: %s", filename, msg);
+ g_free (filename);
+
+ /* ignore error and try the next display number */
+ display++;
+ continue;
+ }
+
+ other = strtol (pid, &end, 0);
+ if (end != pid + 10)
+ {
+ g_warning ("can't parse lock file %s", filename);
+ g_free (filename);
+
+ /* ignore error and try the next display number */
+ display++;
+ continue;
+ }
+
+ if (kill (other, 0) < 0 && errno == ESRCH)
+ {
+ g_warning ("unlinking stale lock file %s", filename);
+ unlink (filename);
+ continue; /* try again */
+ }
+
+ g_free (filename);
+ display++;
+ continue;
+ }
+ else if (fd < 0)
+ {
+ const char *msg = strerror (errno);
+ g_warning ("failed to create lock file %s: %s", filename , msg);
+ g_free (filename);
+ return NULL;
+ }
+
+ break;
+ }
+ while (1);
+
+ /* Subtle detail: we use the pid of the wayland compositor, not the xserver
+ * in the lock file. */
+ size = snprintf (pid, 11, "%10d\n", getpid ());
+ if (size != 11 || write (fd, pid, 11) != 11)
+ {
+ unlink (filename);
+ close (fd);
+ g_warning ("failed to write pid to lock file %s", filename);
+ g_free (filename);
+ return NULL;
+ }
+
+ close (fd);
+
+ *display_out = display;
+ return filename;
+}
+
+static int
+bind_to_abstract_socket (int display)
+{
+ struct sockaddr_un addr;
+ socklen_t size, name_size;
+ int fd;
+
+ fd = socket (PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ return -1;
+
+ addr.sun_family = AF_LOCAL;
+ name_size = snprintf (addr.sun_path, sizeof addr.sun_path,
+ "%c/tmp/.X11-unix/X%d", 0, display);
+ size = offsetof (struct sockaddr_un, sun_path) + name_size;
+ if (bind (fd, (struct sockaddr *) &addr, size) < 0)
+ {
+ g_warning ("failed to bind to @%s: %s\n",
+ addr.sun_path + 1, strerror (errno));
+ close (fd);
+ return -1;
+ }
+
+ if (listen (fd, 1) < 0)
+ {
+ close (fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+static int
+bind_to_unix_socket (int display)
+{
+ struct sockaddr_un addr;
+ socklen_t size, name_size;
+ int fd;
+
+ fd = socket (PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (fd < 0)
+ return -1;
+
+ addr.sun_family = AF_LOCAL;
+ name_size = snprintf (addr.sun_path, sizeof addr.sun_path,
+ "/tmp/.X11-unix/X%d", display) + 1;
+ size = offsetof (struct sockaddr_un, sun_path) + name_size;
+ unlink (addr.sun_path);
+ if (bind (fd, (struct sockaddr *) &addr, size) < 0)
+ {
+ char *msg = strerror (errno);
+ g_warning ("failed to bind to %s (%s)\n", addr.sun_path, msg);
+ close (fd);
+ return -1;
+ }
+
+ if (listen (fd, 1) < 0) {
+ unlink (addr.sun_path);
+ close (fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+static gboolean
+start_xwayland (TWSCompositor *compositor)
+{
+ int display = 0;
+ char *lockfile = NULL;
+ int sp[2];
+ pid_t pid;
+
+ do
+ {
+ lockfile = create_lockfile (display, &display);
+ if (!lockfile)
+ {
+ g_warning ("Failed to create an X lock file");
+ return FALSE;
+ }
+
+ compositor->xwayland_abstract_fd = bind_to_abstract_socket (display);
+ if (compositor->xwayland_abstract_fd < 0 ||
+ compositor->xwayland_abstract_fd == EADDRINUSE)
+ {
+ unlink (lockfile);
+ display++;
+ continue;
+ }
+ compositor->xwayland_unix_fd = bind_to_unix_socket (display);
+ if (compositor->xwayland_abstract_fd < 0)
+ {
+ unlink (lockfile);
+ return FALSE;
+ }
+
+ break;
+ }
+ while (1);
+
+ compositor->xwayland_display_index = display;
+ compositor->xwayland_lockfile = lockfile;
+
+ /* We want xwayland to be a wayland client so we make a socketpair to setup a
+ * wayland protocol connection. */
+ if (socketpair (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sp) < 0)
+ {
+ g_warning ("socketpair failed\n");
+ unlink (lockfile);
+ return 1;
+ }
+
+ switch ((pid = fork()))
+ {
+ case 0:
+ {
+ char *fd_string;
+ char *display_name;
+ /* Make sure the client end of the socket pair doesn't get closed
+ * when we exec xwayland. */
+ int flags = fcntl (sp[1], F_GETFD);
+ if (flags != -1)
+ fcntl (sp[1], F_SETFD, flags & ~FD_CLOEXEC);
+
+ fd_string = g_strdup_printf ("%d", sp[1]);
+ setenv ("WAYLAND_SOCKET", fd_string, 1);
+ g_free (fd_string);
+
+ display_name = g_strdup_printf (":%d",
+ compositor->xwayland_display_index);
+
+ if (execl ("/home/bob/local/xserver-xwayland/bin/X",
+ "/home/bob/local/xserver-xwayland/bin/X",
+ display_name,
+ "-wayland",
+ "-rootless",
+ "-retro",
+ /* FIXME: does it make sense to log to the filesystem by
+ * default? */
+ "-logfile", "/tmp/xwayland.log",
+ "-nolisten", "all",
+ "-terminate",
+ NULL) < 0)
+ {
+ char *msg = strerror (errno);
+ g_warning ("xwayland exec failed: %s", msg);
+ }
+ exit (-1);
+ return FALSE;
+ }
+ default:
+ g_message ("forked X server, pid %d\n", pid);
+
+ close (sp[1]);
+ compositor->xwayland_client =
+ wl_client_create (compositor->wayland_display, sp[0]);
+
+ compositor->xwayland_pid = pid;
+ break;
+
+ case -1:
+ g_error ("Failed to fork for xwayland server");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+stop_xwayland (TWSCompositor *compositor)
+{
+ char path[256];
+
+ snprintf (path, sizeof path, "/tmp/.X%d-lock",
+ compositor->xwayland_display_index);
+ unlink (path);
+ snprintf (path, sizeof path, "/tmp/.X11-unix/X%d",
+ compositor->xwayland_display_index);
+ unlink (path);
+
+ unlink (compositor->xwayland_lockfile);
+}
+
+static void
+xserver_set_window_id (struct wl_client *client,
+ struct wl_resource *compositor_resource,
+ struct wl_resource *surface_resource,
+ guint32 id)
+{
+#if 0
+ TWSCompositor *compositor = compositor_resource->data;
+ struct wlsc_wm *wm = wxs->wm;
+ struct wl_surface *surface = surface_resource->data;
+ struct wlsc_wm_window *window;
+
+ if (client != wxs->client)
+ return;
+
+ window = wl_hash_table_lookup (wm->window_hash, id);
+ if (window == NULL)
+ {
+ g_warning ("set_window_id for unknown window %d", id);
+ return;
+ }
+
+ g_message ("set_window_id %d for surface %p", id, surface);
+
+ window->surface = (struct wlsc_surface *) surface;
+ window->surface_destroy_listener.func = surface_destroy;
+ wl_list_insert(surface->resource.destroy_listener_list.prev,
+ &window->surface_destroy_listener.link);
+#endif
+ g_message ("TODO: xserver_set_window_id");
+}
+
+static const struct xserver_interface xserver_implementation = {
+ xserver_set_window_id
+};
+
+static void
+bind_xserver (struct wl_client *client,
+ void *data,
+ guint32 version,
+ guint32 id)
+{
+ TWSCompositor *compositor = data;
+
+ /* If it's a different client than the xserver we launched,
+ * don't start the wm. */
+ if (client != compositor->xwayland_client)
+ return;
+
+ compositor->xserver_resource =
+ wl_client_add_object (client, &xserver_interface,
+ &xserver_implementation, id,
+ compositor);
+
+ /* TODO: Become the window manager for the xserver */
+
+ wl_resource_post_event (compositor->xserver_resource,
+ XSERVER_LISTEN_SOCKET,
+ compositor->xwayland_abstract_fd);
+
+ wl_resource_post_event (compositor->xserver_resource,
+ XSERVER_LISTEN_SOCKET,
+ compositor->xwayland_unix_fd);
+ g_warning ("bind_xserver");
+}
+
+static gboolean
+signal_handler (GIOChannel *source,
+ GIOCondition condition,
+ void *data)
+{
+ TWSCompositor *compositor = data;
+ char signal;
+ int count;
+
+ for (;;)
+ {
+ count = read (signal_pipe[0], &signal, 1);
+ if (count == EINTR)
+ continue;
+ if (count < 0)
+ {
+ const char *msg = strerror (errno);
+ g_warning ("Error handling signal: %s", msg);
+ }
+ if (count != 1)
+ {
+ g_warning ("Unexpectedly failed to read byte from signal pipe\n");
+ return TRUE;
+ }
+ break;
+ }
+ switch (signal)
+ {
+ case 'I': /* SIGINT */
+ /* TODO: cleanup gracefully */
+ abort ();
+ break;
+ case 'C': /* SIGCHLD */
+ {
+ int status;
+ pid_t pid = waitpid (-1, &status, WNOHANG);
+
+ /* The simplest measure to avoid infinitely re-spawning a crashing
+ * X server */
+ if (!WIFEXITED (status))
+ g_critical ("X Wayland crashed; aborting");
+
+ if (pid == compositor->xwayland_pid)
+ if (!start_xwayland (compositor))
+ g_critical ("Failed to re-start X Wayland server");
+ }
+ break;
+ default:
+ g_warning ("Spurious character '%c' read from signal pipe", signal);
+ }
+
+ return TRUE;
+}
+
G_MODULE_EXPORT int
test_wayland_surface_main (int argc, char **argv)
{
+ GIOChannel *signal_reciever;
+ struct sigaction signal_action;
TWSCompositor compositor;
GMainLoop *loop;
memset (&compositor, 0, sizeof (compositor));
+ pipe (signal_pipe);
+
+ signal_reciever = g_io_channel_unix_new (signal_pipe[0]);
+ g_io_add_watch (signal_reciever, G_IO_IN, signal_handler, &compositor);
+
+ signal_action.sa_handler = report_signal;
+ signal_action.sa_flags = 0;
+ sigaction (SIGINT, &signal_action, NULL);
+ sigaction (SIGCHLD, &signal_action, NULL);
+
compositor.wayland_display = wl_display_create ();
if (compositor.wayland_display == NULL)
g_error ("failed to create wayland display");
@@ -545,7 +980,7 @@ test_wayland_surface_main (int argc, char **argv)
if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
return 1;
- compositor.stage = clutter_stage_get_default ();
+ compositor.stage = clutter_stage_new ();
clutter_stage_set_user_resizable (CLUTTER_STAGE (compositor.stage), FALSE);
g_signal_connect_after (compositor.stage, "paint",
G_CALLBACK (paint_finished_cb), &compositor);
@@ -554,14 +989,40 @@ test_wayland_surface_main (int argc, char **argv)
if (wl_display_add_global (compositor.wayland_display, &wl_shell_interface,
&compositor, bind_shell) == NULL)
- g_error ("Failed to register a global shell object");
+ {
+ stop_xwayland (&compositor);
+ g_error ("Failed to register a global shell object");
+ }
clutter_actor_show (compositor.stage);
if (wl_display_add_socket (compositor.wayland_display, "wayland-0"))
- g_error ("Failed to create socket");
+ {
+ stop_xwayland (&compositor);
+ g_error ("Failed to create socket");
+ }
+
+ wl_display_add_global (compositor.wayland_display,
+ &xserver_interface,
+ &compositor,
+ bind_xserver);
+
+ /* XXX: It's important that we only try and start xwayland after we have
+ * initialized EGL because EGL implements the "wl_drm" interface which
+ * xwayland requires to determine what drm device name it should use.
+ *
+ * By waiting until we've shown the stage above we ensure that the underlying
+ * GL resources for the surface have also been allocated and so EGL must be
+ * initialized by this point.
+ */
+
+ if (!start_xwayland (&compositor))
+ return 1;
+
g_main_loop_run (loop);
+ stop_xwayland (&compositor);
+
return 0;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]