compositor: Add a clipboard manager

We use the selection signal to get a callback when somebody sets a
selection (including the X server proxy) and then copy the contents
of the first mime type.  If the selection is cleared (when the client
dies), we set a new selection with that contents.
diff --git a/src/Makefile.am b/src/Makefile.am
index 64932a1..51f7ad5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -18,6 +18,7 @@
 	screenshooter.c				\
 	screenshooter-protocol.c		\
 	screenshooter-server-protocol.h		\
+	clipboard.c				\
 	text-cursor-position.c			\
 	text-cursor-position-protocol.c		\
 	text-cursor-position-server-protocol.h	\
diff --git a/src/clipboard.c b/src/clipboard.c
new file mode 100644
index 0000000..f5caf41
--- /dev/null
+++ b/src/clipboard.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright © 2012 Intel Corporation
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <linux/input.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/uio.h>
+
+#include "compositor.h"
+
+struct clipboard_source {
+	struct wl_data_source base;
+	struct wl_array contents;
+	struct clipboard *clipboard;
+	struct wl_event_source *event_source;
+	uint32_t serial;
+	int refcount;
+};
+
+struct clipboard {
+	struct weston_seat *seat;
+	struct wl_listener selection_listener;
+	struct wl_listener destroy_listener;
+	struct clipboard_source *source;
+};
+
+static void clipboard_client_create(struct clipboard_source *source, int fd);
+
+static void
+clipboard_source_unref(struct clipboard_source *source)
+{
+	char **s;
+
+	source->refcount--;
+	if (source->refcount > 0)
+		return;
+
+	if (source->event_source)
+		wl_event_source_remove(source->event_source);
+	wl_signal_emit(&source->base.resource.destroy_signal,
+		       &source->base.resource);
+	s = source->base.mime_types.data;
+	free(*s);
+	wl_array_release(&source->base.mime_types);
+	wl_array_release(&source->contents);
+	free(source);
+}
+
+static int
+clipboard_source_data(int fd, uint32_t mask, void *data)
+{
+	struct clipboard_source *source = data;
+	struct clipboard *clipboard = source->clipboard;
+	char *p;
+	int len, size;
+
+	if (source->contents.alloc - source->contents.size < 1024) {
+		wl_array_add(&source->contents, 1024);
+		source->contents.size -= 1024;
+	}
+
+	p = source->contents.data + source->contents.size;
+	size = source->contents.alloc - source->contents.size;
+	len = read(fd, p, size);
+	if (len == 0) {
+		wl_event_source_remove(source->event_source);
+		source->event_source = NULL;
+	} else if (len < 0) {
+		clipboard_source_unref(source);
+		clipboard->source = NULL;
+	} else {
+		source->contents.size += len;
+	}
+
+	return 1;
+}
+
+static void
+clipboard_source_accept(struct wl_data_source *source,
+			uint32_t time, const char *mime_type)
+{
+}
+
+static void
+clipboard_source_send(struct wl_data_source *base,
+		      const char *mime_type, int32_t fd)
+{
+	struct clipboard_source *source =
+		container_of(base, struct clipboard_source, base);
+	char **s;
+
+	s = source->base.mime_types.data;
+	if (strcmp(mime_type, s[0]) == 0)
+		clipboard_client_create(source, fd);
+	else
+		close(fd);
+}
+
+static void
+clipboard_source_cancel(struct wl_data_source *source)
+{
+}
+
+static struct clipboard_source *
+clipboard_source_create(struct clipboard *clipboard,
+			const char *mime_type, uint32_t serial, int fd)
+{
+	struct wl_display *display = clipboard->seat->compositor->wl_display;
+	struct wl_event_loop *loop = wl_display_get_event_loop(display);
+	struct clipboard_source *source;
+	char **s;
+
+	source = malloc(sizeof *source);
+	wl_array_init(&source->contents);
+	wl_array_init(&source->base.mime_types);
+	source->base.accept = clipboard_source_accept;
+	source->base.send = clipboard_source_send;
+	source->base.cancel = clipboard_source_cancel;
+	source->base.resource.data = &source->base;
+	wl_signal_init(&source->base.resource.destroy_signal);
+	source->refcount = 1;
+	source->clipboard = clipboard;
+	source->serial = serial;
+
+	s = wl_array_add(&source->base.mime_types, sizeof *s);
+	*s = strdup(mime_type);
+
+	source->event_source =
+		wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE,
+				     clipboard_source_data, source);
+
+	return source;
+}
+
+struct clipboard_client {
+	struct wl_event_source *event_source;
+	size_t offset;
+	struct clipboard_source *source;
+};
+
+static int
+clipboard_client_data(int fd, uint32_t mask, void *data)
+{
+	struct clipboard_client *client = data;
+	char *p;
+	size_t size;
+	int len;
+
+	size = client->source->contents.size;
+	p = client->source->contents.data;
+	len = write(fd, p + client->offset, size - client->offset);
+	if (len > 0)
+		client->offset += len;
+
+	if (client->offset == size || len <= 0) {
+		close(fd);
+		wl_event_source_remove(client->event_source);
+		clipboard_source_unref(client->source);
+		free(client);
+	}
+
+	return 1;
+}
+
+static void
+clipboard_client_create(struct clipboard_source *source, int fd)
+{
+	struct weston_seat *seat = source->clipboard->seat;
+	struct clipboard_client *client;
+	struct wl_event_loop *loop =
+		wl_display_get_event_loop(seat->compositor->wl_display);
+
+	client = malloc(sizeof *client);
+
+	client->offset = 0;
+	client->source = source;
+	source->refcount++;
+	client->event_source =
+		wl_event_loop_add_fd(loop, fd, WL_EVENT_WRITABLE,
+				     clipboard_client_data, client);
+}
+
+static void
+clipboard_set_selection(struct wl_listener *listener, void *data)
+{
+	struct clipboard *clipboard =
+		container_of(listener, struct clipboard, selection_listener);
+	struct weston_seat *seat = data;
+	struct wl_data_source *source = seat->seat.selection_data_source;
+	const char **mime_types;
+	int p[2];
+
+	if (source == NULL) {
+		if (clipboard->source)
+			wl_seat_set_selection(&seat->seat,
+					      &clipboard->source->base,
+					      clipboard->source->serial);
+		return;
+	} else if (source->accept == clipboard_source_accept) {
+		/* Callback for our data source. */
+		return;
+	}
+
+	if (clipboard->source)
+		clipboard_source_unref(clipboard->source);
+
+	clipboard->source = NULL;
+
+	mime_types = source->mime_types.data;
+
+	if (pipe2(p, O_CLOEXEC) == -1)
+		return;
+
+	source->send(source, mime_types[0], p[1]);
+
+	clipboard->source =
+		clipboard_source_create(clipboard, mime_types[0],
+					seat->seat.selection_serial, p[0]);
+	if (clipboard->source == NULL)
+		return;
+}
+
+struct clipboard *
+clipboard_create(struct weston_seat *seat)
+{
+	struct clipboard *clipboard;
+
+	clipboard = malloc(sizeof *clipboard);
+	if (clipboard == NULL)
+		return NULL;
+
+	clipboard->seat = seat;
+	clipboard->selection_listener.notify = clipboard_set_selection;
+
+	wl_signal_add(&seat->seat.selection_signal,
+		      &clipboard->selection_listener);
+
+	return clipboard;
+}
diff --git a/src/compositor.c b/src/compositor.c
index b299e03..797ab99 100644
--- a/src/compositor.c
+++ b/src/compositor.c
@@ -2454,6 +2454,8 @@
 	seat->new_drag_icon_listener.notify = device_handle_new_drag_icon;
 	wl_signal_add(&seat->seat.drag_icon_signal,
 		      &seat->new_drag_icon_listener);
+
+	clipboard_create(seat);
 }
 
 WL_EXPORT void
diff --git a/src/compositor.h b/src/compositor.h
index d070471..836f10a 100644
--- a/src/compositor.h
+++ b/src/compositor.h
@@ -677,6 +677,9 @@
 void
 screenshooter_create(struct weston_compositor *ec);
 
+struct clipboard *
+clipboard_create(struct weston_seat *seat);
+
 void
 text_cursor_position_notifier_create(struct weston_compositor *ec);