[PATCH weston v4 15/15] Add a screen sharing plugin

Jason Ekstrand jason at jlekstrand.net
Tue Feb 25 17:26:47 PST 2014


This adds a plugin called screen-share.so.  If the screen-share.so module
is imported, it will add the CTRL+ALT+s keybinding to start a screen
sharing session.  If you press CTRL+ALT+S, weston will spawn another copy
of weston, this time with the RDP backend, and mirrors the current screen
to it and adds any seats from RDP as aditional seats.  The current screen
is defined as the one with the mouse pointer.  Currently the CTRL+ALT+s
keybinding is hardcoded as the only way to activate screen sharing.  If, at
some point, shells want more control over the screen sharing process, the
API's should be easy to update and export to make this possible.

For security, the command and path to weston is currently hard-coded.  It
would not take much aditional code to make this configurable or to allow a
shell to launch other screen-sharing programs.  However, handling those
security issues is outside the scope of this patch so it is hard-coded for
now.

Signed-off-by: Jason Ekstrand <jason at jlekstrand.net>
---
 Makefile.am        |   22 ++
 configure.ac       |   13 +
 src/screen-share.c | 1082 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1117 insertions(+)
 create mode 100644 src/screen-share.c

diff --git a/Makefile.am b/Makefile.am
index 838a051..f0fbec1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -707,6 +707,28 @@ nodist_fullscreen_shell_la_SOURCES =			\
 	protocol/fullscreen-shell-server-protocol.h
 endif
 
+if ENABLE_SCREEN_SHARING
+
+module_LTLIBRARIES += screen-share.la
+
+screen_share_la_CPPFLAGS = $(AM_CPPFLAGS) -DBINDIR='"$(bindir)"'
+screen_share_la_LDFLAGS = -module -avoid-version
+screen_share_la_LIBADD =			\
+	$(COMPOSITOR_LIBS)			\
+	$(SCREEN_SHARE_LIBS)			\
+	libshared-cairo.la
+screen_share_la_CFLAGS =			\
+	$(COMPOSITOR_CFLAGS)			\
+	$(SCREEN_SHARE_CFLAGS)			\
+	$(GCC_CFLAGS)
+screen_share_la_SOURCES =			\
+	src/screen-share.c
+nodist_screen_share_la_SOURCES =			\
+	protocol/fullscreen-shell-protocol.c		\
+	protocol/fullscreen-shell-client-protocol.h
+
+endif
+
 if ENABLE_XWAYLAND
 
 module_LTLIBRARIES += xwayland.la
diff --git a/configure.ac b/configure.ac
index 0809614..27fd536 100644
--- a/configure.ac
+++ b/configure.ac
@@ -212,6 +212,18 @@ if test x$enable_rdp_compositor = xyes; then
   CPPFLAGS="$SAVED_CPPFLAGS"
 fi
 
+AC_ARG_ENABLE([screen-sharing], [--enable-screen-sharing],,
+              enable_screen_sharing=no)
+AM_CONDITIONAL([ENABLE_SCREEN_SHARING],
+               [test x$enable_screen_sharing = xyes])
+if test x$enable_screen_sharing = xyes; then
+  PKG_CHECK_MODULES(SCREEN_SHARE, [wayland-client])
+
+  if test x$enable_rdp_compositor != xyes; then
+    AC_MSG_WARN([The screen-share.so module requires the RDP backend to work properly.])
+  fi
+fi
+
 AC_ARG_WITH(cairo,
 	    AS_HELP_STRING([--with-cairo=@<:@image|gl|glesv2@:>@]
 			   [Which Cairo renderer to use for the clients]),
@@ -513,6 +525,7 @@ AC_MSG_RESULT([
 	RPI Compositor			${enable_rpi_compositor}
 	FBDEV Compositor		${enable_fbdev_compositor}
 	RDP Compositor			${enable_rdp_compositor}
+	Screen Sharing			${enable_screen_sharing}
 
 	Raspberry Pi BCM headers	${have_bcm_host}
 
diff --git a/src/screen-share.c b/src/screen-share.c
new file mode 100644
index 0000000..6cc6bd5
--- /dev/null
+++ b/src/screen-share.c
@@ -0,0 +1,1082 @@
+/*
+ * Copyright © 2008-2011 Kristian Høgsberg
+ * Copyright © 2014 Jason Ekstrand
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission.  The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/mman.h>
+#include <signal.h>
+#include <linux/input.h>
+#include <errno.h>
+
+#include <wayland-client.h>
+
+#include "compositor.h"
+#include "../shared/os-compatibility.h"
+#include "fullscreen-shell-client-protocol.h"
+
+struct shared_output {
+	struct weston_output *output;
+	struct wl_listener output_destroyed;
+	struct wl_list seat_list;
+
+	struct {
+		struct wl_display *display;
+		struct wl_registry *registry;
+		struct wl_compositor *compositor;
+		struct wl_shm *shm;
+		uint32_t shm_formats;
+		struct wl_fullscreen_shell *fshell;
+		struct wl_output *output;
+		struct wl_surface *surface;
+		struct wl_callback *frame_cb;
+		struct wl_fullscreen_shell_mode_feedback *mode_feedback;
+	} parent;
+
+	struct wl_event_source *event_source;
+	struct wl_listener frame_listener;
+
+	struct {
+		int32_t width, height;
+
+		struct wl_list buffers;
+		struct wl_list free_buffers;
+	} shm;
+
+	int cache_dirty;
+	pixman_image_t *cache_image;
+	uint32_t *tmp_data;
+	size_t tmp_data_size;
+};
+
+struct ss_seat {
+	struct weston_seat base;
+	struct shared_output *output;
+	struct wl_list link;
+
+	struct {
+		struct wl_seat *seat;
+		struct wl_pointer *pointer;
+		struct wl_keyboard *keyboard;
+	} parent;
+
+	enum weston_key_state_update keyboard_state_update;
+	uint32_t key_serial;
+};
+
+struct ss_shm_buffer {
+	struct shared_output *output;
+	struct wl_list link;
+	struct wl_list free_link;
+
+	struct wl_buffer *buffer;
+	void *data;
+	size_t size;
+	pixman_region32_t damage;
+
+	pixman_image_t *pm_image;
+};
+
+static void
+ss_seat_handle_pointer_enter(void *data, struct wl_pointer *pointer,
+			     uint32_t serial, struct wl_surface *surface,
+			     wl_fixed_t x, wl_fixed_t y)
+{
+	struct ss_seat *seat = data;
+
+	/* We make the tacit assumption here that we are always recieving
+	 * input in output coordinates.
+	weston_output_transform_coordinate(&seat->output->base, x, y, &x, &y);
+	 */
+
+	notify_pointer_focus(&seat->base, NULL, 0, 0);
+}
+
+static void
+ss_seat_handle_pointer_leave(void *data, struct wl_pointer *pointer,
+			     uint32_t serial, struct wl_surface *surface)
+{
+	struct ss_seat *seat = data;
+
+	notify_pointer_focus(&seat->base, NULL, 0, 0);
+}
+
+static void
+ss_seat_handle_motion(void *data, struct wl_pointer *pointer,
+		      uint32_t time, wl_fixed_t x, wl_fixed_t y)
+{
+	struct ss_seat *seat = data;
+
+	/* We make the tacit assumption here that we are always recieving
+	 * input in output coordinates.
+	weston_output_transform_coordinate(&seat->output->base, x, y, &x, &y);
+	 */
+
+	notify_motion_absolute(&seat->base, time, x, y);
+}
+
+static void
+ss_seat_handle_button(void *data, struct wl_pointer *pointer,
+		      uint32_t serial, uint32_t time, uint32_t button,
+		      uint32_t state)
+{
+	struct ss_seat *seat = data;
+
+	notify_button(&seat->base, time, button, state);
+}
+
+static void
+ss_seat_handle_axis(void *data, struct wl_pointer *pointer,
+		    uint32_t time, uint32_t axis, wl_fixed_t value)
+{
+	struct ss_seat *seat = data;
+
+	notify_axis(&seat->base, time, axis, value);
+}
+
+static const struct wl_pointer_listener ss_seat_pointer_listener = {
+	ss_seat_handle_pointer_enter,
+	ss_seat_handle_pointer_leave,
+	ss_seat_handle_motion,
+	ss_seat_handle_button,
+	ss_seat_handle_axis,
+};
+
+static void
+ss_seat_handle_keymap(void *data, struct wl_keyboard *keyboard,
+		      uint32_t format, int fd, uint32_t size)
+{
+	struct ss_seat *seat = data;
+	struct xkb_keymap *keymap;
+	char *map_str;
+
+	if (!data)
+		goto error;
+
+	if (format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
+		map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+		if (map_str == MAP_FAILED) {
+			weston_log("mmap failed: %m\n");
+			goto error;
+		}
+
+		keymap = xkb_map_new_from_string(seat->base.compositor->xkb_context,
+						 map_str,
+						 XKB_KEYMAP_FORMAT_TEXT_V1,
+						 0);
+		munmap(map_str, size);
+
+		if (!keymap) {
+			weston_log("failed to compile keymap\n");
+			goto error;
+		}
+
+		seat->keyboard_state_update = STATE_UPDATE_NONE;
+	} else if (format == WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) {
+		weston_log("No keymap provided; falling back to defalt\n");
+		keymap = NULL;
+		seat->keyboard_state_update = STATE_UPDATE_AUTOMATIC;
+	} else {
+		weston_log("Invalid keymap\n");
+		goto error;
+	}
+
+	close(fd);
+
+	if (seat->base.keyboard)
+		weston_seat_update_keymap(&seat->base, keymap);
+	else
+		weston_seat_init_keyboard(&seat->base, keymap);
+
+	if (keymap)
+		xkb_map_unref(keymap);
+
+	return;
+
+error:
+	wl_keyboard_release(seat->parent.keyboard);
+	close(fd);
+}
+
+static void
+ss_seat_handle_keyboard_enter(void *data, struct wl_keyboard *keyboard,
+			      uint32_t serial, struct wl_surface *surface,
+			      struct wl_array *keys)
+{
+	struct ss_seat *seat = data;
+
+	/* XXX: If we get a modifier event immediately before the focus,
+	 *      we should try to keep the same serial. */
+	notify_keyboard_focus_in(&seat->base, keys,
+				 STATE_UPDATE_AUTOMATIC);
+}
+
+static void
+ss_seat_handle_keyboard_leave(void *data, struct wl_keyboard *keyboard,
+			      uint32_t serial, struct wl_surface *surface)
+{
+	struct ss_seat *seat = data;
+
+	notify_keyboard_focus_out(&seat->base);
+}
+
+static void
+ss_seat_handle_key(void *data, struct wl_keyboard *keyboard,
+		   uint32_t serial, uint32_t time,
+		   uint32_t key, uint32_t state)
+{
+	struct ss_seat *seat = data;
+
+	seat->key_serial = serial;
+	notify_key(&seat->base, time, key,
+		   state ? WL_KEYBOARD_KEY_STATE_PRESSED :
+			   WL_KEYBOARD_KEY_STATE_RELEASED,
+		   seat->keyboard_state_update);
+}
+
+static void
+ss_seat_handle_modifiers(void *data, struct wl_keyboard *keyboard,
+			 uint32_t serial_in, uint32_t mods_depressed,
+			 uint32_t mods_latched, uint32_t mods_locked,
+			 uint32_t group)
+{
+	struct ss_seat *seat = data;
+	struct weston_compositor *c = seat->output->output->compositor;
+	uint32_t serial_out;
+
+	/* If we get a key event followed by a modifier event with the
+	 * same serial number, then we try to preserve those semantics by
+	 * reusing the same serial number on the way out too. */
+	if (serial_in == seat->key_serial)
+		serial_out = wl_display_get_serial(c->wl_display);
+	else
+		serial_out = wl_display_next_serial(c->wl_display);
+
+	xkb_state_update_mask(seat->base.keyboard->xkb_state.state,
+			      mods_depressed, mods_latched,
+			      mods_locked, 0, 0, group);
+	notify_modifiers(&seat->base, serial_out);
+}
+
+static const struct wl_keyboard_listener ss_seat_keyboard_listener = {
+	ss_seat_handle_keymap,
+	ss_seat_handle_keyboard_enter,
+	ss_seat_handle_keyboard_leave,
+	ss_seat_handle_key,
+	ss_seat_handle_modifiers,
+};
+
+static void
+ss_seat_handle_capabilities(void *data, struct wl_seat *seat,
+			    enum wl_seat_capability caps)
+{
+	struct ss_seat *ss_seat = data;
+
+	if ((caps & WL_SEAT_CAPABILITY_POINTER) && !ss_seat->parent.pointer) {
+		ss_seat->parent.pointer = wl_seat_get_pointer(seat);
+		wl_pointer_set_user_data(ss_seat->parent.pointer, ss_seat);
+		wl_pointer_add_listener(ss_seat->parent.pointer,
+					&ss_seat_pointer_listener, ss_seat);
+		weston_seat_init_pointer(&ss_seat->base);
+	} else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && ss_seat->parent.pointer) {
+		wl_pointer_destroy(ss_seat->parent.pointer);
+		ss_seat->parent.pointer = NULL;
+	}
+
+	if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !ss_seat->parent.keyboard) {
+		ss_seat->parent.keyboard = wl_seat_get_keyboard(seat);
+		wl_keyboard_set_user_data(ss_seat->parent.keyboard, ss_seat);
+		wl_keyboard_add_listener(ss_seat->parent.keyboard,
+					 &ss_seat_keyboard_listener, ss_seat);
+	} else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && ss_seat->parent.keyboard) {
+		wl_keyboard_destroy(ss_seat->parent.keyboard);
+		ss_seat->parent.keyboard = NULL;
+	}
+}
+
+static const struct wl_seat_listener ss_seat_listener = {
+	ss_seat_handle_capabilities,
+};
+
+static struct ss_seat *
+ss_seat_create(struct shared_output *so, uint32_t id)
+{
+	struct ss_seat *seat;
+
+	seat = zalloc(sizeof *seat);
+	if (seat == NULL)
+		return NULL;
+
+	weston_seat_init(&seat->base, so->output->compositor, "default");
+	seat->output = so;
+	seat->parent.seat = wl_registry_bind(so->parent.registry, id,
+					     &wl_seat_interface, 1);
+	wl_list_insert(so->seat_list.prev, &seat->link);
+
+	wl_seat_add_listener(seat->parent.seat, &ss_seat_listener, seat);
+	wl_seat_set_user_data(seat->parent.seat, seat);
+
+	return seat;
+}
+
+static void
+ss_seat_destroy(struct ss_seat *seat)
+{
+	if (seat->parent.pointer)
+		wl_pointer_release(seat->parent.pointer);
+	if (seat->parent.keyboard)
+		wl_keyboard_release(seat->parent.keyboard);
+	wl_seat_destroy(seat->parent.seat);
+
+	wl_list_remove(&seat->link);
+
+	weston_seat_release(&seat->base);
+
+	free(seat);
+}
+
+static void
+ss_shm_buffer_destroy(struct ss_shm_buffer *buffer)
+{
+	pixman_image_unref(buffer->pm_image);
+
+	wl_buffer_destroy(buffer->buffer);
+	munmap(buffer->data, buffer->size);
+
+	pixman_region32_fini(&buffer->damage);
+
+	wl_list_remove(&buffer->link);
+	wl_list_remove(&buffer->free_link);
+	free(buffer);
+}
+
+static void
+buffer_release(void *data, struct wl_buffer *buffer)
+{
+	struct ss_shm_buffer *sb = data;
+
+	if (sb->output) {
+		wl_list_insert(&sb->output->shm.free_buffers, &sb->free_link);
+	} else {
+		ss_shm_buffer_destroy(sb);
+	}
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+	buffer_release
+};
+
+static struct ss_shm_buffer *
+shared_output_get_shm_buffer(struct shared_output *so)
+{
+	struct ss_shm_buffer *sb, *bnext;
+	struct wl_shm_pool *pool;
+	int width, height, stride;
+	int fd;
+	unsigned char *data;
+
+	width = so->output->width;
+	height = so->output->height;
+	stride = width * 4;
+
+	/* If the size of the output changed, we free the old buffers and
+	 * make new ones. */
+	if (so->shm.width != width ||
+	    so->shm.height != height) {
+
+	    	/* Destroy free buffers */
+		wl_list_for_each_safe(sb, bnext, &so->shm.free_buffers, link)
+			ss_shm_buffer_destroy(sb);
+
+	    	/* Orphan in-use buffers so they get destroyed */
+		wl_list_for_each(sb, &so->shm.buffers, link)
+			sb->output = NULL;
+
+		so->shm.width = width;
+		so->shm.height = height;
+	}
+
+	if (!wl_list_empty(&so->shm.free_buffers)) {
+		sb = container_of(so->shm.free_buffers.next,
+				  struct ss_shm_buffer, free_link);
+		wl_list_remove(&sb->free_link);
+		wl_list_init(&sb->free_link);
+
+		return sb;
+	}
+
+	fd = os_create_anonymous_file(height * stride);
+	if (fd < 0) {
+		perror("os_create_anonymous_file");
+		return NULL;
+	}
+
+	data = mmap(NULL, height * stride, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+	if (data == MAP_FAILED) {
+		perror("mmap");
+		close(fd);
+		return NULL;
+	}
+
+	sb = zalloc(sizeof *sb);
+
+	sb->output = so;
+	wl_list_init(&sb->free_link);
+	wl_list_insert(&so->shm.buffers, &sb->link);
+
+	pixman_region32_init_rect(&sb->damage, 0, 0, width, height);
+
+	sb->data = data;
+	sb->size = height * stride;
+
+	pool = wl_shm_create_pool(so->parent.shm, fd, sb->size);
+
+	sb->buffer = wl_shm_pool_create_buffer(pool, 0,
+					       width, height, stride,
+					       WL_SHM_FORMAT_ARGB8888);
+	wl_buffer_add_listener(sb->buffer, &buffer_listener, sb);
+	wl_shm_pool_destroy(pool);
+	close(fd);
+
+	memset(data, 0, sb->size);
+
+	sb->pm_image =
+		pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height,
+					 (uint32_t *)data, stride);
+
+	return sb;
+}
+
+static void
+output_compute_transform(struct weston_output *output,
+			 pixman_transform_t *transform)
+{
+	pixman_fixed_t fw, fh;
+
+	pixman_transform_init_identity(transform);
+
+	fw = pixman_int_to_fixed(output->width);
+	fh = pixman_int_to_fixed(output->height);
+
+	switch (output->transform) {
+	case WL_OUTPUT_TRANSFORM_FLIPPED:
+	case WL_OUTPUT_TRANSFORM_FLIPPED_90:
+	case WL_OUTPUT_TRANSFORM_FLIPPED_180:
+	case WL_OUTPUT_TRANSFORM_FLIPPED_270:
+		pixman_transform_scale(transform, NULL,
+				       pixman_int_to_fixed (-1),
+				       pixman_int_to_fixed (1));
+		pixman_transform_translate(transform, NULL, fw, 0);
+	}
+
+	switch (output->transform) {
+	default:
+	case WL_OUTPUT_TRANSFORM_NORMAL:
+	case WL_OUTPUT_TRANSFORM_FLIPPED:
+		break;
+	case WL_OUTPUT_TRANSFORM_90:
+	case WL_OUTPUT_TRANSFORM_FLIPPED_90:
+		pixman_transform_rotate(transform, NULL, 0, pixman_fixed_1);
+		pixman_transform_translate(transform, NULL, fh, 0);
+		break;
+	case WL_OUTPUT_TRANSFORM_180:
+	case WL_OUTPUT_TRANSFORM_FLIPPED_180:
+		pixman_transform_rotate(transform, NULL, -pixman_fixed_1, 0);
+		pixman_transform_translate(transform, NULL, fw, fh);
+		break;
+	case WL_OUTPUT_TRANSFORM_270:
+	case WL_OUTPUT_TRANSFORM_FLIPPED_270:
+		pixman_transform_rotate(transform, NULL, 0, -pixman_fixed_1);
+		pixman_transform_translate(transform, NULL, 0, fw);
+		break;
+	}
+
+	pixman_transform_scale(transform, NULL,
+			       pixman_fixed_1 * output->current_scale,
+			       pixman_fixed_1 * output->current_scale);
+}
+
+static void
+shared_output_destroy(struct shared_output *so);
+
+static int
+shared_output_ensure_tmp_data(struct shared_output *so,
+			      pixman_region32_t *region)
+{
+	pixman_box32_t *ext;
+	int32_t area;
+	size_t size;
+
+	if (pixman_region32_not_empty(region)) {
+		ext = pixman_region32_extents(region);
+		area = (ext->x2 - ext->x1) * (ext->y2 - ext->y1);
+	} else {
+		return 0;
+	}
+
+	/* Damage is in buffer coordinates */
+	area *= so->output->current_scale * so->output->current_scale;
+
+	size = area * 4;
+	if (so->tmp_data != NULL && size <= so->tmp_data_size)
+		return 0;
+	
+	free(so->tmp_data);
+	so->tmp_data = malloc(size);
+	if (so->tmp_data == NULL) {
+		so->tmp_data_size = 0;
+		errno = ENOMEM;
+		return -1;
+	}
+
+	so->tmp_data_size = size;
+
+	return 0;
+}
+
+static void
+shared_output_update(struct shared_output *so);
+
+static void
+shared_output_frame_callback(void *data, struct wl_callback *cb, uint32_t time)
+{
+	struct shared_output *so = data;
+
+	if (cb != so->parent.frame_cb)
+		return;
+
+	wl_callback_destroy(cb);
+	so->parent.frame_cb = NULL;
+
+	shared_output_update(so);
+}
+
+static const struct wl_callback_listener shared_output_frame_listener = {
+	shared_output_frame_callback
+};
+
+static void
+shared_output_update(struct shared_output *so)
+{
+	struct ss_shm_buffer *sb;
+	pixman_box32_t *r;
+	int i, nrects;
+	pixman_transform_t transform;
+
+	/* Only update if we need to */
+	if (!so->cache_dirty || so->parent.frame_cb)
+		return;
+
+	sb = shared_output_get_shm_buffer(so);
+	if (sb == NULL) {
+		shared_output_destroy(so);
+		return;
+	}
+
+	output_compute_transform(so->output, &transform);
+	pixman_image_set_transform(so->cache_image, &transform);
+
+	pixman_image_set_clip_region32(sb->pm_image, &sb->damage);
+
+	pixman_image_composite32(PIXMAN_OP_SRC,
+				 so->cache_image, /* src */
+				 NULL, /* mask */
+				 sb->pm_image, /* dest */
+				 0, 0, /* src_x, src_y */
+				 0, 0, /* mask_x, mask_y */
+				 0, 0, /* dest_x, dest_y */
+				 so->output->width, /* width */
+				 so->output->height /* height */);
+
+	pixman_image_set_transform(sb->pm_image, NULL);
+	pixman_image_set_clip_region32(sb->pm_image, NULL);
+
+	r = pixman_region32_rectangles(&sb->damage, &nrects);
+	for (i = 0; i < nrects; ++i)
+		wl_surface_damage(so->parent.surface, r[i].x1, r[i].y1,
+				  r[i].x2 - r[i].x1, r[i].y2 - r[i].y1);
+
+	wl_surface_attach(so->parent.surface, sb->buffer, 0, 0);
+
+	so->parent.frame_cb = wl_surface_frame(so->parent.surface);
+	wl_callback_add_listener(so->parent.frame_cb,
+				 &shared_output_frame_listener, so);
+
+	wl_surface_commit(so->parent.surface);
+	wl_callback_destroy(wl_display_sync(so->parent.display));
+	wl_display_flush(so->parent.display);
+
+	/* Clear the buffer damage */
+	pixman_region32_fini(&sb->damage);
+	pixman_region32_init(&sb->damage);
+}
+
+static void
+shm_handle_format(void *data, struct wl_shm *wl_shm, uint32_t format)
+{
+	struct shared_output *so = data;
+
+	so->parent.shm_formats |= (1 << format);
+}
+
+struct wl_shm_listener shm_listener = {
+	shm_handle_format
+};
+
+static void
+registry_handle_global(void *data, struct wl_registry *registry,
+		       uint32_t id, const char *interface, uint32_t version)
+{
+	struct shared_output *so = data;
+
+	if (strcmp(interface, "wl_compositor") == 0) {
+		so->parent.compositor =
+			wl_registry_bind(registry,
+					 id, &wl_compositor_interface, 1);
+	} else if (strcmp(interface, "wl_output") == 0 && !so->parent.output) {
+		so->parent.output =
+			wl_registry_bind(registry,
+					 id, &wl_output_interface, 1);
+	} else if (strcmp(interface, "wl_seat") == 0) {
+		ss_seat_create(so, id);
+	} else if (strcmp(interface, "wl_shm") == 0) {
+		so->parent.shm =
+			wl_registry_bind(registry,
+					 id, &wl_shm_interface, 1);
+		wl_shm_add_listener(so->parent.shm, &shm_listener, so);
+	} else if (strcmp(interface, "wl_fullscreen_shell") == 0) {
+		so->parent.fshell =
+			wl_registry_bind(registry,
+					 id, &wl_fullscreen_shell_interface, 1);
+	}
+}
+
+static void
+registry_handle_global_remove(void *data, struct wl_registry *registry,
+			      uint32_t name)
+{
+}
+
+static const struct wl_registry_listener registry_listener = {
+	registry_handle_global,
+	registry_handle_global_remove
+};
+
+static int
+shared_output_handle_event(int fd, uint32_t mask, void *data)
+{
+	struct shared_output *so = data;
+	int count = 0;
+
+	if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) {
+		shared_output_destroy(so);
+		return 0;
+	}
+
+	if (mask & WL_EVENT_READABLE)
+		count = wl_display_dispatch(so->parent.display);
+	if (mask & WL_EVENT_WRITABLE)
+		wl_display_flush(so->parent.display);
+
+	if (mask == 0) {
+		count = wl_display_dispatch_pending(so->parent.display);
+		wl_display_flush(so->parent.display);
+	}
+
+	return count;
+}
+
+static void
+output_destroyed(struct wl_listener *l, void *data)
+{
+	struct shared_output *so;
+
+	so = container_of(l, struct shared_output, output_destroyed);
+
+	shared_output_destroy(so);
+}
+
+static void
+mode_feedback_ok(void *data, struct wl_fullscreen_shell_mode_feedback *fb)
+{
+	struct shared_output *so = data;
+
+	wl_fullscreen_shell_mode_feedback_destroy(so->parent.mode_feedback);
+}
+
+static void
+mode_feedback_failed(void *data, struct wl_fullscreen_shell_mode_feedback *fb)
+{
+	struct shared_output *so = data;
+
+	wl_fullscreen_shell_mode_feedback_destroy(so->parent.mode_feedback);
+
+	weston_log("Screen share failed: present_surface_for_mode failed\n");
+	shared_output_destroy(so);
+}
+
+struct wl_fullscreen_shell_mode_feedback_listener mode_feedback_listener = {
+	mode_feedback_ok,
+	mode_feedback_failed,
+	mode_feedback_ok,
+};
+
+static void
+shared_output_repainted(struct wl_listener *listener, void *data)
+{
+	struct shared_output *so =
+		container_of(listener, struct shared_output, frame_listener);
+	pixman_region32_t damage;
+	struct ss_shm_buffer *sb;
+	int32_t x, y, width, height, stride;
+	int i, nrects, do_yflip;
+	pixman_box32_t *r;
+	uint32_t *cache_data;
+	
+	/* Damage in output coordinates */
+	pixman_region32_init(&damage);
+	pixman_region32_intersect(&damage, &so->output->region,
+				  &so->output->previous_damage);
+	pixman_region32_translate(&damage, -so->output->x, -so->output->y);
+
+	/* Apply damage to all buffers */
+	wl_list_for_each(sb, &so->shm.buffers, link)
+		pixman_region32_union(&sb->damage, &sb->damage, &damage);
+
+	/* Transform to buffer coordinates */
+	weston_transformed_region(so->output->width, so->output->height,
+				  so->output->transform,
+				  so->output->current_scale,
+				  &damage, &damage);
+
+	width = so->output->current_mode->width;
+	height = so->output->current_mode->height;
+	stride = width;
+
+	if (!so->cache_image ||
+	    pixman_image_get_width(so->cache_image) != width ||
+	    pixman_image_get_height(so->cache_image) != height) {
+		if (so->cache_image)
+			pixman_image_unref(so->cache_image);
+
+		so->cache_image =
+			pixman_image_create_bits(PIXMAN_a8r8g8b8,
+						 width, height, NULL,
+						 stride);
+		if (!so->cache_image) {
+			shared_output_destroy(so);
+			return;
+		}
+
+		pixman_region32_fini(&damage);
+		pixman_region32_init_rect(&damage, 0, 0, width, height);
+	}
+	
+	if (shared_output_ensure_tmp_data(so, &damage) < 0) {
+		shared_output_destroy(so);
+		return;
+	}
+
+	do_yflip = !!(so->output->compositor->capabilities & WESTON_CAP_CAPTURE_YFLIP);
+
+	cache_data = pixman_image_get_data(so->cache_image);
+	r = pixman_region32_rectangles(&damage, &nrects);
+	for (i = 0; i < nrects; ++i) {
+		x = r[i].x1;
+		y = r[i].y1;
+		width = r[i].x2 - r[i].x1;
+		height = r[i].y2 - r[i].y1;
+
+		if (do_yflip) {
+			so->output->compositor->renderer->read_pixels(
+				so->output, PIXMAN_a8r8g8b8, so->tmp_data,
+				x, so->output->current_mode->height - r[i].y2,
+				width, height);
+
+			pixman_blt(so->tmp_data, cache_data, -width, stride,
+				   32, 32, 0, 1 - height, x, y, width, height);
+		} else {
+			so->output->compositor->renderer->read_pixels(
+				so->output, PIXMAN_a8r8g8b8, so->tmp_data,
+				x, y, width, height);
+
+			pixman_blt(so->tmp_data, cache_data, width, stride,
+				   32, 32, 0, 0, x, y, width, height);
+		}
+	}
+
+	pixman_region32_fini(&damage);
+
+	so->cache_dirty = 1;
+
+	shared_output_update(so);
+}
+
+static struct shared_output *
+shared_output_create(struct weston_output *output, int parent_fd)
+{
+	struct shared_output *so;
+	struct wl_event_loop *loop;
+	struct ss_seat *seat;
+	int epoll_fd;
+
+	so = zalloc(sizeof *so);
+	if (so == NULL)
+		goto err_close;
+
+	wl_list_init(&so->seat_list);
+	
+	so->parent.display = wl_display_connect_to_fd(parent_fd);
+	if (!so->parent.display)
+		goto err_alloc;
+	
+	so->parent.registry = wl_display_get_registry(so->parent.display);
+	if (!so->parent.registry)
+		goto err_display;
+	wl_registry_add_listener(so->parent.registry,
+				 &registry_listener, so);
+	wl_display_roundtrip(so->parent.display);
+	if (so->parent.shm == NULL) {
+		weston_log("Screen share failed: No wl_shm found\n");
+		goto err_display;
+	}
+	if (so->parent.fshell == NULL) {
+		weston_log("Screen share failed: "
+			   "Parent does not support wl_fullscreen_shell\n");
+		goto err_display;
+	}
+	if (so->parent.compositor == NULL) {
+		weston_log("Screen share failed: No wl_compositor found\n");
+		goto err_display;
+	}
+
+	/* Get SHM formats */
+	wl_display_roundtrip(so->parent.display);
+	if (!(so->parent.shm_formats & (1 << WL_SHM_FORMAT_XRGB8888))) {
+		weston_log("Screen share failed: "
+			   "WL_SHM_FORMAT_XRGB8888 not available\n");
+		goto err_display;
+	}
+
+	so->parent.surface =
+		wl_compositor_create_surface(so->parent.compositor);
+	if (!so->parent.surface) {
+		weston_log("Screen share failed: %m");
+		goto err_display;
+	}
+
+	so->parent.mode_feedback =
+		wl_fullscreen_shell_present_surface_for_mode(so->parent.fshell,
+							     so->parent.surface,
+							     so->parent.output,
+							     output->current_mode->refresh);
+	if (!so->parent.mode_feedback) {
+		weston_log("Screen share failed: %m");
+		goto err_display;
+	}
+	wl_fullscreen_shell_mode_feedback_add_listener(so->parent.mode_feedback,
+						       &mode_feedback_listener,
+						       so);
+
+	loop = wl_display_get_event_loop(output->compositor->wl_display);
+
+	epoll_fd = wl_display_get_fd(so->parent.display);
+	so->event_source =
+		wl_event_loop_add_fd(loop, epoll_fd, WL_EVENT_READABLE,
+				     shared_output_handle_event, so);
+	if (!so->event_source) {
+		weston_log("Screen share failed: %m");
+		goto err_display;
+	}
+	
+	/* Ok, everything's created.  We should be good to go */
+	wl_list_init(&so->shm.buffers);
+	wl_list_init(&so->shm.free_buffers);
+
+	so->output = output;
+	so->output_destroyed.notify = output_destroyed;
+	wl_signal_add(&so->output->destroy_signal, &so->output_destroyed);
+
+	so->frame_listener.notify = shared_output_repainted;
+	wl_signal_add(&output->frame_signal, &so->frame_listener);
+	output->disable_planes++;
+	weston_output_damage(output);
+	
+	return so;
+
+err_display:
+	wl_list_for_each(seat, &so->seat_list, link)
+		ss_seat_destroy(seat);
+	wl_display_disconnect(so->parent.display);
+err_alloc:
+	free(so);
+err_close:
+	close(parent_fd);
+	return NULL;
+}
+
+static void
+shared_output_destroy(struct shared_output *so)
+{
+	struct ss_shm_buffer *buffer, *bnext;
+
+	wl_list_for_each_safe(buffer, bnext, &so->shm.buffers, link)
+		ss_shm_buffer_destroy(buffer);
+	wl_list_for_each_safe(buffer, bnext, &so->shm.free_buffers, link)
+		ss_shm_buffer_destroy(buffer);
+
+	wl_display_disconnect(so->parent.display);
+	wl_event_source_remove(so->event_source);
+
+	wl_list_remove(&so->output_destroyed.link);
+	wl_list_remove(&so->frame_listener.link);
+	
+	pixman_image_unref(so->cache_image);
+	free(so->tmp_data);
+	
+	free(so);
+}
+
+static struct shared_output *
+weston_output_share(struct weston_output *output,
+		    const char *path, char *const argv[])
+{
+	int sv[2];
+	char str[32];
+	pid_t pid;
+	sigset_t allsigs;
+
+	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) {
+		weston_log("weston_output_share: socketpair failed: %m\n");
+		return NULL;
+	}
+
+	pid = fork();
+
+	if (pid == -1) {
+		close(sv[0]);
+		close(sv[1]);
+		weston_log("weston_output_share: fork failed: %m\n");
+		return NULL;
+	}
+
+	if (pid == 0) {
+		/* We don't want anything circular */
+		unsetenv("WAYLAND_DISPLAY");
+		unsetenv("WAYLAND_SOCKET");
+
+		setenv("WAYLAND_DEBUG", "server", 1);
+
+		/* do not give our signal mask to the new process */
+		sigfillset(&allsigs);
+		sigprocmask(SIG_UNBLOCK, &allsigs, NULL);
+
+		/* Launch clients as the user. Do not lauch clients with
+		 * wrong euid. */
+		if (seteuid(getuid()) == -1) {
+			weston_log("weston_output_share: setuid failed: %m\n");
+			abort();
+		}
+
+		sv[1] = dup(sv[1]);
+		if (sv[1] == -1) {
+			weston_log("weston_output_share: dup failed: %m\n");
+			abort();
+		}
+
+		snprintf(str, sizeof str, "%d", sv[1]);
+		setenv("WAYLAND_SERVER_SOCKET", str, 1);
+
+		execv(path, argv);
+		weston_log("weston_output_share: exec failed: %m\n");
+		abort();
+	} else {
+		close(sv[1]);
+		return shared_output_create(output, sv[0]);
+	}
+
+	return NULL;
+}
+
+static struct weston_output *
+weston_output_find(struct weston_compositor *c, int32_t x, int32_t y)
+{
+	struct weston_output *output;
+
+	wl_list_for_each(output, &c->output_list, link) {
+		if (x >= output->x && y >= output->y &&
+		    x < output->x + output->width &&
+		    y < output->y + output->height)
+			return output;
+	}
+
+	return NULL;
+}
+
+static void
+share_output_binding(struct weston_seat *seat, uint32_t time, uint32_t key,
+		     void *data)
+{
+	struct weston_output *output;
+	const char *path = BINDIR "/weston";
+	char width_arg[32], height_arg[32];
+
+	if (!seat->pointer) {
+		weston_log("Cannot pick output: Seat does not have pointer\n");
+		return;
+	}
+	
+	output = weston_output_find(seat->compositor,
+				    wl_fixed_to_int(seat->pointer->x),
+				    wl_fixed_to_int(seat->pointer->y));
+	if (!output) {
+		weston_log("Cannot pick output: Pointer not on any output\n");
+		return;
+	}
+
+	snprintf(width_arg, sizeof width_arg, "--width=%d", output->width);
+	snprintf(height_arg, sizeof height_arg, "--height=%d", output->height);
+
+	char *const argv[] = {
+		"weston",
+		"--backend=rdp-backend.so",
+		"--shell=fullscreen-shell.so",
+		width_arg,
+		height_arg,
+		NULL
+	};
+
+	weston_output_share(output, path, argv);
+}
+
+WL_EXPORT int
+module_init(struct weston_compositor *compositor,
+	    int *argc, char *argv[])
+{
+	weston_compositor_add_key_binding(compositor, KEY_S,
+				          MODIFIER_CTRL | MODIFIER_ALT,
+					  share_output_binding, compositor);
+	return 0;
+}
-- 
1.8.5.3



More information about the wayland-devel mailing list