diff options
author | Robert Bragg <robert@linux.intel.com> | 2011-12-08 16:07:09 +0000 |
---|---|---|
committer | Robert Bragg <robert@linux.intel.com> | 2011-12-15 18:30:19 +0000 |
commit | 76d2a3306ae12f3145f0f545eecfe815bd91c1a5 (patch) | |
tree | 10861e8376e622d64308f1598d7be9dcbb7f2e25 | |
parent | c4590e59c72ac4253c4ede1badd4684bb616b30c (diff) | |
download | clutter-wip/wayland-compositor.tar.gz |
test-wayland-surface: Adds basic xwayland supportwip/wayland-compositor
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
-rw-r--r-- | configure.ac | 17 | ||||
-rw-r--r-- | tests/interactive/Makefile.am | 36 | ||||
-rw-r--r-- | tests/interactive/test-wayland-surface.c | 381 |
3 files changed, 428 insertions, 6 deletions
diff --git a/configure.ac b/configure.ac index 977fe4d8d..4d88412af 100644 --- a/configure.ac +++ b/configure.ac @@ -331,6 +331,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], @@ -961,7 +977,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 357b6bd56..6474b0690 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 2627412f1..5bcc89c40 100644 --- a/tests/interactive/test-wayland-surface.c +++ b/tests/interactive/test-wayland-surface.c @@ -6,9 +6,18 @@ #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 <wayland-server.h> +#include "xserver-server-protocol.h" + typedef struct _TWSCompositor TWSCompositor; typedef struct @@ -69,6 +78,14 @@ 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 guint32 @@ -508,6 +525,338 @@ 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]); + + /* TODO: install a SIGCHILD handler to catch if the server exists/crashes! */ + 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"); +} + G_MODULE_EXPORT int test_wayland_surface_main (int argc, char **argv) { @@ -545,7 +894,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 +903,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; } |