[PATCH wayland 09/10] Redo drag & drop interface

Daniel Stone daniel at fooishbar.org
Mon Jul 23 11:54:46 PDT 2012


Rename wl_data_device to wl_data_transfer to make its purpose more
explicit, and move its getter into wl_seat.
Rename data-device.c to data-transfer.c.
Split drag and selection negotiation out of wl_data_transfer into new
wl_drag_offer and wl_selection_offer interfaces, removing
wl_data_offer.
Add terminating NULL to wl_{drag,selection}_offer::offer events.
Add reject request and cancellation event to offers.
Rename wl_data_source cancel event to inactive.
Rename various requests and arguments to make their purpose clearer.
Add descriptions to all data transfer interfaces.

The implementation is left as pointer-only for now, until the wl_touch
changes have been landed.

Signed-off-by: Daniel Stone <daniel at fooishbar.org>
---
 doc/Wayland/en_US/Protocol.xml |   70 ++---
 protocol/wayland.xml           |  351 +++++++++++++++-------
 src/Makefile.am                |    2 +-
 src/data-device.c              |  579 ------------------------------------
 src/data-transfer.c            |  629 ++++++++++++++++++++++++++++++++++++++++
 src/wayland-private.h          |    6 +
 src/wayland-server.c           |   14 +-
 src/wayland-server.h           |   80 ++---
 8 files changed, 962 insertions(+), 769 deletions(-)
 delete mode 100644 src/data-device.c
 create mode 100644 src/data-transfer.c

diff --git a/doc/Wayland/en_US/Protocol.xml b/doc/Wayland/en_US/Protocol.xml
index 9a7db53..cad64a8 100644
--- a/doc/Wayland/en_US/Protocol.xml
+++ b/doc/Wayland/en_US/Protocol.xml
@@ -209,34 +209,34 @@
 	  </listitem>
 	</varlistentry>
 	<varlistentry>
-	  <term>wl_data_offer</term>
+	  <term>wl_drag_offer</term>
 	  <listitem>
 	    <para>
-	      for accepting and receiving specific mime types
+	      for receiving drag & drop data
 	    </para>
 	  </listitem>
 	</varlistentry>
 	<varlistentry>
-	  <term>wl_data_source</term>
+	  <term>wl_selection_offer</term>
 	  <listitem>
 	    <para>
-	      for offering specific mime types
+	      for receiving selection/clipboard data
 	    </para>
 	  </listitem>
 	</varlistentry>
 	<varlistentry>
-	  <term>wl_data_device</term>
+	  <term>wl_data_source</term>
 	  <listitem>
 	    <para>
-	      lets clients manage drag & drop, provides pointer enter/leave events and motion
+	      for offering data through selections and drag & drop
 	    </para>
 	  </listitem>
 	</varlistentry>
 	<varlistentry>
-	  <term>wl_data_device_manager</term>
+	  <term>wl_data_transfer</term>
 	  <listitem>
 	    <para>
-	      for managing data sources and devices
+	      lets clients manage drag & drop and selections
 	    </para>
 	  </listitem>
 	</varlistentry>
@@ -517,9 +517,8 @@
       expose mode, for example).
     </para>
     <para>
-      See <xref linkend="protocol-spec-interface-wl_data_offer"/>,
-      <xref linkend="protocol-spec-interface-wl_data_source"/> and
-      <xref linkend="protocol-spec-interface-wl_data_offer"/> for
+      See <xref linkend="protocol-spec-interface-wl_data_source"/> and
+      <xref linkend="protocol-spec-interface-wl_drag_offer"/> for
       protocol descriptions.
     </para>
     <para>
@@ -534,12 +533,6 @@
 	</listitem>
 	<listitem>
 	  <para>
-	    Should drag.send() destroy the object?  There's nothing to do
-	    after the data has been transferred.
-	  </para>
-	</listitem>
-	<listitem>
-	  <para>
 	    How do we marshal several mime-types?  We could make the drag
 	    setup a multi-step operation: dnd.create, drag.offer(mime-type1),
 	    drag.offer(mime-type2), drag.activate().  The drag object could send
@@ -554,12 +547,6 @@
 	</listitem>
 	<listitem>
 	  <para>
-	    Send a file descriptor over the protocol to let initiator and
-	    source exchange data out of band?
-	  </para>
-	</listitem>
-	<listitem>
-	  <para>
 	    Action? Specify action when creating the drag object? Ask
 	    action?
 	  </para>
@@ -581,18 +568,16 @@
 	<listitem>
 	  <para>
 	    The initiator creates a drag object by calling the
-	    <function>create_drag</function> method on the dnd global
-	    object.  As for any client created object, the client allocates
-	    the id.  The <function>create_drag</function> method also takes
+	    <function>start_drag</function> method on the wl_data_transfer
+	    object.  The <function>start_drag</function> method also takes
 	    the originating surface, the device that's dragging and the
-	    mime-types supported.  If the surface
-	    has indeed grabbed the device passed in, the server will create an
-	    active drag object for the device.  If the grab was released in the
-	    meantime, the drag object will be in-active, that is, the same state
-	    as when the grab is released.  In that case, the client will receive
-	    a button up event, which will let it know that the drag finished.
-	    To the client it will look like the drag was immediately cancelled
-	    by the grab ending.
+	    wl_data_source that will supply the data.  If the surface has indeed
+	    grabbed the device passed in, the server will create an active drag
+	    object for the device.  If the grab was released in the meantime,
+	    the drag object will be in-active, that is, the same state as when the
+	    grab is released.  In that case, the client will receive a drag leave
+	    event, which will let it know that the drag finished.  To the client
+	    it will look like the drag was immediately cancelled by the grab ending.
 	  </para>
 	  <para>
 	    The special mime-type application/x-root-target indicates that the
@@ -613,11 +598,11 @@
 	  <para>
 	    As long as the grab is active (or until the initiator cancels
 	    the drag by destroying the drag object), the drag object will send
-	    <function>offer</function> events to surfaces it moves across. As for motion
-	    events, these events contain the surface local coordinates of the
+	    <function>offer</function> events to surfaces it moves across. As for
+	    motion events, these events contain the surface local coordinates of the
 	    device as well as the list of mime-types offered.  When a device
-	    leaves a surface, it will send an <function>offer</function> event with an empty
-	    list of mime-types to indicate that the device left the surface.
+	    leaves a surface, the drag object will send a
+	    <function>leave</function> event.
 	  </para>
 	</listitem>
 	<listitem>
@@ -631,11 +616,10 @@
 	    feedback to the user that the drag has a valid target.  If the
 	    <function>offer</function> event moves to a different drop target (the surface
 	    decides the offer coordinates is outside the drop target) or leaves
-	    the surface (the offer event has an empty list of mime-types) it
-	    should revert the appearance of the drop target to the inactive
-	    state.  A surface can also decide to retract its drop target (if the
-	    drop target disappears or moves, for example), by calling the accept
-	    method with a NULL mime-type.
+	    the surface it should revert the appearance of the drop target to
+	    the inactive state.  A surface can also decide to retract its drop
+	    target (if the drop target disappears or moves, for example), by
+	    calling the reject method.
 	  </para>
 	</listitem>
 	<listitem>
diff --git a/protocol/wayland.xml b/protocol/wayland.xml
index 4b500e6..8c5e36c 100644
--- a/protocol/wayland.xml
+++ b/protocol/wayland.xml
@@ -241,99 +241,261 @@
     </event>
   </interface>
 
+  <interface name="wl_data_source" version="1">
+    <description summary="source for drag-and-drop and selections">
+      A wl_data_source represents a concrete offer from a client to
+      supply data to other clients through the wl_data_manager
+      drag-and-drop and/or selection interfaces.  This interface allows
+      clients to offer data and perform type negotiations with intended
+      targets.
+    </description>
 
-  <interface name="wl_data_offer" version="1">
-    <request name="accept">
-      <description summary="accept one of the offered mime-types">
-	Indicate that the client can accept the given mime-type, or
-	NULL for not accepted.  Use for feedback during drag and drop.
+    <request name="add_type">
+      <description summary="add an offered MIME type">
+	This request adds a MIME type to the set advertised to targets.
+	Can be called multiple times to offer different types; clients
+	should send NULL when they have added all the MIME types they
+	wish to offer.
       </description>
-
-      <arg name="serial" type="uint"/>
-      <arg name="type" type="string" allow-null="true"/>
+      <arg name="mime_type" type="string" allow-null="true"/>
     </request>
 
-    <request name="receive">
+    <event name="accepted">
+      <description summary="target has accepted offer">
+	Sent when a target has provisionally accepted a drag or
+	selection event, and indicates its preferred type.
+	Provided as a hint only; the data type sent as part of the
+	transfer event may differ, or the client may ultimately
+	decide to reject a transfer.
+      </description>
+      <arg name="mime_type" type="string"/>
+    </event>
+
+    <event name="transfer">
+      <description summary="send data to target">
+        Request for data from this offer client.  The data should be
+	written into the pipe provided by this event in the format
+	specified by mime_type, then the file descriptor closed.
+      </description>
       <arg name="mime_type" type="string"/>
       <arg name="fd" type="fd"/>
+    </event>
+
+    <!-- There's a race here, which can only be fixed by having a
+	 serial in wl_data_transfer::set_selection and
+	 wl_data_transfer::start_drag, and then passing the latest
+	 serial in here.  Otherwise the client can't know if the
+         server has taken its most recent request into account before
+	 sending this event. -->
+    <event name="inactive">
+      <description summary="source is not part of any selection">
+	Another selection became active; this data source is no longer
+	associated with any selections or drag-and-drop operations.
+      </description>
+    </event>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the data source">
+	Destroy the data source.  If the data source is currently
+	active as part of a selection or drag and drop operation,
+	these will be cancelled.
+      </description>
     </request>
+  </interface>
 
-    <request name="destroy" type="destructor"/>
+  <interface name="wl_drag_offer" version="1">
+    <description summary="single drag and drop sequence">
+      A wl_drag_offer object is created when a client requests a
+      drag-and-drop sequence begins with wl_data_transfer::start_drag, and
+      ends when the provoking button or touch event sequence ends (i.e.
+      when the mouse button is released, or the finger is removed from
+      the screen).
+    </description>
 
-    <event name="offer">
-      <description summary="advertise offered mime-type">
-	Sent immediately after creating the wl_data_offer object.  One
-	event per offered mime type.
+    <event name="enter">
+      <description summary="drop target now in surface">
+	The enter event is sent when the target for a drag-and-drop
+	sequence enters the client's surface, similar to the
+	wl_pointer::enter event.  A wl_keyboard::modifiers
+	event will be sent, if applicable, before this event.
       </description>
+      <arg name="surface" type="object" interface="wl_surface"/>
+      <arg name="x" type="fixed"/>
+      <arg name="y" type="fixed"/>
+    </event>
 
-      <arg name="type" type="string"/>
+    <event name="leave">
+      <description summary="drop target now out of surface">
+        The drop target has shifted to another surface or the drag
+	and drop sequence has been abnormally terminated, and this
+	object will not send any more events.  Another
+	wl_data_transfer::drag event will be sent if the target
+	re-enters this surface.
+      </description>
+      <arg name="serial" type="uint"/>
     </event>
-  </interface>
 
-  <interface name="wl_data_source" version="1">
-    <request name="offer">
-      <description summary="add an offered mime type">
-	This request adds a mime-type to the set of mime-types
-	advertised to targets.  Can be called several times to offer
-	multiple types.
+    <event name="motion">
+      <description summary="drop target moved">
+        Analogous to the wl_pointer::motion event, the drop target has
+	moved; this event provides the new location.
+      </description>
+      <arg name="time" type="uint"/>
+      <arg name="x" type="fixed"/>
+      <arg name="y" type="fixed"/>
+    </event>
+
+    <event name="drop">
+      <description summary="drag-and-drop sequence completed">
+        The drag and drop sequence has completed, with the target
+	co-ordinates being given by the last motion event, or the
+	last enter event if no motion events were sent since.  If
+	the client has successfully negotiated data types using
+	wl_data_offer, it should accept the offer if possible and
+	perform the appropriate action.
+      </description>
+    </event>
+
+    <event name="offer">
+      <description summary="new supported MIME type">
+        When a wl_drag_offer object is sent to a client as the
+	beginning of a drag and drop operation, one or more offer
+	events will be sent, detailing the MIME types supported by
+	the backing data source.  When there are no more MIME types
+	to be sent, the type argument will be NULL, and the receiving
+	client can decide which (if any) of the types it should accept.
+      </description>
+      <arg name="mime_type" type="string" allow-null="true"/>
+    </event>
+
+    <request name="accept">
+      <description summary="provisionally accept transfer">
+        Indicates that the client is able to accept the transfer;
+	mime_type is set to the client's preferred MIME type to
+	perform the transfer in, although this may differ from the
+	type which the client requests for the transfer.
       </description>
-      <arg name="type" type="string"/>
+      <arg name="mime_type" type="string"/>
     </request>
 
     <request name="destroy" type="destructor">
-      <description summary="destroy the data source">
-	Destroy the data source.
+      <description summary="reject transfer">
+        Reject the transfer and destroy the object.
       </description>
     </request>
 
-    <event name="target">
-      <description summary="a target accepts an offered mime-type">
-	Sent when a target accepts pointer_focus or motion events.  If
-	a target does not accept any of the offered types, type is NULL.
+    <request name="transfer" type="destructor">
+      <description summary="begin data transfer">
+        Requests the data source begin writing the data into the file
+	descriptor provided.  The drag object will be destroyed at this
+	point.
       </description>
+      <arg name="mime_type" type="string"/>
+      <arg name="fd" type="fd"/>
+    </request>
+  </interface>
 
+  <interface name="wl_selection_offer" version="1">
+    <description summary="copy and paste data transfer">
+      A wl_selection_offer represents an offer by a client currently owning
+      the selection (clipboard) data to transfer this data to another
+      client.  If requested (e.g. through a Paste menu item), the client
+      receiving this offer should use the transfer request to receive the
+      data from the source.
+    </description>
+
+    <event name="offer">
+      <description summary="new supported MIME type">
+        When a wl_selection_offer object is sent to a client, one or more
+	offer events will be sent, detailing the MIME types supported by
+	the backing data source.  When there are no more MIME types to be
+	sent, the type argument will be NULL, and the receiving client can
+	decide which (if any) of the types it should accept.
+      </description>
       <arg name="mime_type" type="string" allow-null="true"/>
     </event>
-
-    <event name="send">
-      <description summary="send the data">
-	Request for data from another client.  Send the data as the
-	specified mime-type over the passed fd, then close the fd.
+ 
+    <event name="cancel">
+      <description summary="offer withdrawn">
+        The selection owner has withdrawn its offer, and it is no longer
+	valid.  No more events will be sent from this object.
       </description>
+    </event>
 
+    <request name="accept">
+      <description summary="provisionally accept transfer">
+        Indicates that the client is able to accept the transfer;
+	mime_type is set to the client's preferred MIME type to
+	perform the transfer in, although this may differ from the
+	type which the client requests for the transfer.
+      </description>
       <arg name="mime_type" type="string"/>
-      <arg name="fd" type="fd"/>
-    </event>
+    </request>
 
-    <event name="cancelled">
-      <description summary="selection was cancelled">
-	Another selection became active.
+    <request name="reject" type="destructor">
+      <description summary="reject transfer">
+        Reject the transfer; no more events will be sent from this
+	object.
       </description>
-    </event>
+    </request>
 
+    <request name="transfer">
+      <description summary="begin data transfer">
+        Requests the data source begin writing the data into the file
+	descriptor provided.  
+      </description>
+      <arg name="mime_type" type="string"/>
+      <arg name="fd" type="fd"/>
+    </request>
   </interface>
 
-  <interface name="wl_data_device" version="1">
+  <interface name="wl_data_transfer" version="1">
+    <description summary="data transfer arbitration">
+      This interface is the arbitrator for all inter-client data
+      transfer operations, including drag and drop events and the
+      clipboard.  Clients with focus wishing to initiate transfers
+      should use the start_drag and set_selection requests;
+      conversely, drag and selection events will be sent to clients 
+      who are transfer targets.
+    </description>
+
+    <enum name="error">
+      <entry name="invalid_serial" value="256"
+             description="serial number is not valid for the operation"/>
+      <entry name="no_types" value="257"
+             description="attempted to offer data source with no types"/>
+    </enum>
+
+    <request name="create_data_source">
+      <description summary="create new data source">
+        This request creates a new wl_data_source object, to be used
+	when the client is capable of transmitting data to other
+	clients.  Clients may create as many data sources as they
+	wish.
+      </description>
+      <arg name="id" type="new_id" interface="wl_data_source"/>
+    </request>
+
     <request name="start_drag">
       <description summary="start drag and drop operation">
-	This request asks the compositor to start a drag and drop
+	This request asks the compositor to begin a drag and drop
 	operation on behalf of the client.
 
 	The source argument is the data source that provides the data
-	for the eventual data transfer. If source is NULL, enter, leave
-	and motion events are sent only to the client that initiated the
-	drag and the client is expected to handle the data passing
-	internally.
-
-	The origin surface is the surface where the drag originates and
-	the client must have an active implicit grab that matches the
-	serial.
+	for the eventual data transfer. If source is NULL, then the
+	wl_drag_operation will only be sent to the client which
+	initiated the drag; the client is expected to handle the data
+	passing internally.
 
+	The origin surface is the surface where the drag originated.
 	The icon surface is an optional (can be nil) surface that
 	provides an icon to be moved around with the cursor.  Initially,
 	the top-left corner of the icon surface is placed at the cursor
 	hotspot, but subsequent surface.attach request can move the
 	relative position.
+
+	The serial number provided must be from a wl_pointer::button
+	pressed event, or a wl_touch::begin.
       </description>
       <arg name="source" type="object" interface="wl_data_source" allow-null="true"/>
       <arg name="origin" type="object" interface="wl_surface"/>
@@ -341,70 +503,42 @@
       <arg name="serial" type="uint"/>
     </request>
 
-    <request name="set_selection">
-      <arg name="source" type="object" interface="wl_data_source" allow-null="true"/>
-      <arg name="serial" type="uint"/>
-    </request>
-
-    <event name="data_offer">
-      <description summary="introduce a new wl_data_offer">
-	The data_offer event introduces a new wl_data_offer object,
-	which will subsequently be used in either the
-	data_device.enter event (for drag and drop) or the
-	data_device.selection event (for selections).  Immediately
-	following the data_device_data_offer event, the new data_offer
-	object will send out data_offer.offer events to describe the
-	mime-types it offers.
+    <event name="drag">
+      <description summary="advertise new drag">
+        This event informs clients that a drag operation targeted at
+	one of its surfaces has begun; the wl_drag_operation interface
+	will send further events.
       </description>
-
-      <arg name="id" type="new_id" interface="wl_data_offer"/>
+      <arg name="id" type="new_id" interface="wl_drag_offer"/>
     </event>
 
-    <event name="enter">
+    <request name="set_selection">
+      <description summary="offer new selection">
+        When the client has the focus, offer a new wl_data_source as the
+	clipboard selection for this seat.  The serial given must match
+	a wl_pointer::button or wl_keyboard::key event; either pressed or
+	released is acceptable for both types of events.
+      </description>
+      <arg name="source" type="object" interface="wl_data_source" allow-null="true"/>
       <arg name="serial" type="uint"/>
-      <arg name="surface" type="object" interface="wl_surface"/>
-      <arg name="x" type="fixed"/>
-      <arg name="y" type="fixed"/>
-      <arg name="id" type="object" interface="wl_data_offer" allow-null="true"/>
-    </event>
-
-    <event name="leave"/>
-
-    <event name="motion">
-      <arg name="time" type="uint"/>
-      <arg name="x" type="fixed"/>
-      <arg name="y" type="fixed"/>
-    </event>
-
-    <event name="drop"/>
+    </request>
 
     <event name="selection">
       <description summary="advertise new selection">
 	The selection event is sent out to notify the client of a new
-	wl_data_offer for the selection for this device.  The
-	data_device.data_offer and the data_offer.offer events are
-	sent out immediately before this event to introduce the data
-	offer object.  The selection event is sent to a client
-	immediately before receiving keyboard focus and when a new
-	selection is set while the client has keyboard focus.  The
-	data_offer is valid until a new data_offer or NULL is received
-	or until the client loses keyboard focus.
-      </description>
-      <arg name="id" type="object" interface="wl_data_offer" allow-null="true"/>
+	wl_selection_offer for the selection for this seat.  If clients
+	wish to handle selection data, they should subscribe to events
+	from this object immediately upon receiving this event.
+	This event is sent to a client immediately before it receives
+	keybaord focus, as well as when the selection source changes in
+	any way while the client has keyboard focus.
+	The selection offer is valid until the next event this client
+	receives.
+      </description>
+      <arg name="id" type="new_id" interface="wl_selection_offer" allow-null="true"/>
     </event>
   </interface>
 
-  <interface name="wl_data_device_manager" version="1">
-    <request name="create_data_source">
-      <arg name="id" type="new_id" interface="wl_data_source"/>
-    </request>
-
-    <request name="get_data_device">
-      <arg name="id" type="new_id" interface="wl_data_device"/>
-      <arg name="seat" type="object" interface="wl_seat"/>
-    </request>
-  </interface>
-
   <interface name="wl_shell" version="1">
     <request name="get_shell_surface">
       <arg name="id" type="new_id" interface="wl_shell_surface"/>
@@ -413,7 +547,6 @@
   </interface>
 
   <interface name="wl_shell_surface" version="1">
-
     <description summary="desktop style meta data interface">
       An interface implemented by a wl_surface.  On server side the
       object is automatically destroyed when the related wl_surface is
@@ -735,9 +868,9 @@
       <entry name="pointer" value="1" summary="wl_pointer"/>
       <entry name="keyboard" value="2" summary="wl_keyboard"/>
       <entry name="touch" value="4" summary="wl_touch"/>
+      <entry name="data_transfer" value="8" summary="wl_data_transfer"/>
     </enum>
 
-
     <event name="capabilities">
       <description summary="seat capabilities changed">
         This is emitted whenever a seat gains or loses the pointer,
@@ -770,6 +903,14 @@
       </description>
       <arg name="id" type="new_id" interface="wl_touch"/>
     </request>
+
+    <request name="get_data_transfer">
+      <description summary="return data transfer object">
+        The ID provided will be initialized to the wl_data_transfer
+	interface for this seat.
+      </description>
+      <arg name="id" type="new_id" interface="wl_data_transfer"/>
+    </request>
   </interface>
 
   <interface name="wl_pointer" version="1">
diff --git a/src/Makefile.am b/src/Makefile.am
index f93954e..43e0dd9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -23,7 +23,7 @@ libwayland_server_la_SOURCES =			\
 	wayland-protocol.c			\
 	wayland-server.c			\
 	wayland-shm.c				\
-	data-device.c				\
+	data-transfer.c				\
 	event-loop.c
 
 libwayland_client_la_LIBADD = $(FFI_LIBS) libwayland-util.la -lrt -lm
diff --git a/src/data-device.c b/src/data-device.c
deleted file mode 100644
index 0249abb..0000000
--- a/src/data-device.c
+++ /dev/null
@@ -1,579 +0,0 @@
-/*
- * Copyright © 2011 Kristian Høgsberg
- *
- * 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 <string.h>
-#include <unistd.h>
-#include <stdio.h>
-
-#include "wayland-server.h"
-
-static void
-data_offer_accept(struct wl_client *client, struct wl_resource *resource,
-		  uint32_t serial, const char *mime_type)
-{
-	struct wl_data_offer *offer = resource->data;
-
-	/* FIXME: Check that client is currently focused by the input
-	 * device that is currently dragging this data source.  Should
-	 * this be a wl_data_device request? */
-
-	if (offer->source)
-		offer->source->accept(offer->source, serial, mime_type);
-}
-
-static void
-data_offer_receive(struct wl_client *client, struct wl_resource *resource,
-		   const char *mime_type, int32_t fd)
-{
-	struct wl_data_offer *offer = resource->data;
-
-	if (offer->source)
-		offer->source->send(offer->source, mime_type, fd);
-	else
-		close(fd);
-}
-
-static void
-data_offer_destroy(struct wl_client *client, struct wl_resource *resource)
-{
-	wl_resource_destroy(resource);
-}
-
-static const struct wl_data_offer_interface data_offer_interface = {
-	data_offer_accept,
-	data_offer_receive,
-	data_offer_destroy,
-};
-
-static void
-destroy_data_offer(struct wl_listener *listener, void *data)
-{
-	struct wl_data_offer *offer;
-
-	offer = container_of(listener, struct wl_data_offer,
-			     offer_destroy_listener);
-
-	if (offer->source)
-		wl_list_remove(&offer->source_destroy_listener.link);
-	free(offer);
-}
-
-static void
-destroy_offer_data_source(struct wl_listener *listener, void *data)
-{
-	struct wl_data_offer *offer;
-
-	offer = container_of(listener, struct wl_data_offer,
-			     source_destroy_listener);
-
-	offer->source = NULL;
-}
-
-static struct wl_resource *
-wl_data_source_send_offer(struct wl_data_source *source,
-			  struct wl_resource *target)
-{
-	struct wl_data_offer *offer;
-	char **p;
-
-	offer = malloc(sizeof *offer);
-	if (offer == NULL)
-		return NULL;
-
-	offer->resource =
-		wl_client_new_object(target->client, &wl_data_offer_interface,
-				     &data_offer_interface, offer);
-	if (offer->resource == NULL) {
-		free(offer);
-		return NULL;
-	}
-	offer->offer_destroy_listener.notify = destroy_data_offer;
-	wl_signal_add(&offer->resource->destroy_signal,
-		      &offer->offer_destroy_listener);
-
-	offer->source = source;
-	offer->source_destroy_listener.notify = destroy_offer_data_source;
-	wl_signal_add(&source->resource->destroy_signal,
-		      &offer->source_destroy_listener);
-
-	wl_data_device_send_data_offer(target, offer->resource);
-
-	wl_array_for_each(p, &source->mime_types)
-		wl_data_offer_send_offer(offer->resource, *p);
-
-	return offer->resource;
-}
-
-static void
-data_source_offer(struct wl_client *client,
-		  struct wl_resource *resource,
-		  const char *type)
-{
-	struct wl_data_source *source = resource->data;
-	char **p;
-
-	p = wl_array_add(&source->mime_types, sizeof *p);
-	if (p)
-		*p = strdup(type);
-	if (!p || !*p)
-		wl_resource_post_no_memory(resource);
-}
-
-static void
-data_source_destroy(struct wl_client *client, struct wl_resource *resource)
-{
-	wl_resource_destroy(resource);
-}
-
-static struct wl_data_source_interface data_source_interface = {
-	data_source_offer,
-	data_source_destroy
-};
-
-static struct wl_resource *
-find_resource(struct wl_list *list, struct wl_client *client)
-{
-	struct wl_resource *r;
-
-	wl_list_for_each(r, list, link) {
-		if (r->client == client)
-			return r;
-	}
-
-	return NULL;
-}
-
-static void
-destroy_drag_focus(struct wl_listener *listener, void *data)
-{
-	struct wl_seat *seat =
-		container_of(listener, struct wl_seat, drag_focus_listener);
-
-	seat->drag_focus_resource = NULL;
-}
-
-static void
-drag_grab_focus(struct wl_pointer_grab *grab,
-		struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y)
-{
-	struct wl_seat *seat = container_of(grab, struct wl_seat, drag_grab);
-	struct wl_resource *resource, *offer = NULL;
-	struct wl_display *display;
-	uint32_t serial;
-
-	if (seat->drag_focus_resource) {
-		wl_data_device_send_leave(seat->drag_focus_resource);
-		wl_list_remove(&seat->drag_focus_listener.link);
-		seat->drag_focus_resource = NULL;
-		seat->drag_focus = NULL;
-	}
-
-	if (!surface)
-		return;
-
-	if (!seat->drag_data_source &&
-	    surface->resource.client != seat->drag_client)
-		return;
-
-	resource = find_resource(&seat->drag_resource_list,
-				 surface->resource.client);
-	if (!resource)
-		return;
-
-	display = wl_client_get_display(resource->client);
-	serial = wl_display_next_serial(display);
-
-	if (seat->drag_data_source)
-		offer = wl_data_source_send_offer(seat->drag_data_source,
-						  resource);
-
-	wl_data_device_send_enter(resource, serial, &surface->resource,
-				  x, y, offer);
-
-	seat->drag_focus = surface;
-	seat->drag_focus_listener.notify = destroy_drag_focus;
-	wl_signal_add(&resource->destroy_signal,
-		      &seat->drag_focus_listener);
-	seat->drag_focus_resource = resource;
-	grab->focus = surface;
-
-	seat->pointer->cursor.icon = seat->drag_cursor.icon;
-	seat->pointer->cursor.x = seat->pointer->x;
-	seat->pointer->cursor.y = seat->pointer->y;
-	wl_signal_emit(&seat->pointer->cursor.update_signal,
-		       &seat->pointer->cursor);
-}
-
-static void
-drag_grab_motion(struct wl_pointer_grab *grab,
-		 uint32_t time, wl_fixed_t x, wl_fixed_t y)
-{
-	struct wl_seat *seat = container_of(grab, struct wl_seat, drag_grab);
-
-	if (seat->drag_focus_resource)
-		wl_data_device_send_motion(seat->drag_focus_resource,
-					   time, x, y);
-
-	seat->pointer->cursor.x = seat->pointer->x;
-	seat->pointer->cursor.y = seat->pointer->y;
-	wl_signal_emit(&seat->pointer->cursor.update_signal,
-		       &seat->pointer->cursor);
-}
-
-static void
-data_device_end_drag_grab(struct wl_seat *seat)
-{
-	struct wl_resource *surface_resource;
-	struct wl_surface_interface *implementation;
-
-	if (seat->drag_cursor.icon) {
-		surface_resource = &seat->drag_cursor.icon->resource;
-		implementation = (struct wl_surface_interface *)
-			surface_resource->object.implementation;
-
-		implementation->attach(surface_resource->client,
-				       surface_resource, NULL, 0, 0);
-		wl_list_remove(&seat->drag_icon_listener.link);
-		seat->drag_cursor.icon = NULL;
-	}
-
-	drag_grab_focus(&seat->drag_grab, NULL,
-	                wl_fixed_from_int(0), wl_fixed_from_int(0));
-
-	wl_pointer_end_grab(seat->pointer);
-
-	seat->drag_data_source = NULL;
-	seat->drag_cursor.icon = NULL;
-	seat->drag_client = NULL;
-}
-
-static void
-drag_grab_button(struct wl_pointer_grab *grab,
-		 uint32_t time, uint32_t button, uint32_t state_w)
-{
-	struct wl_seat *seat = container_of(grab, struct wl_seat, drag_grab);
-	enum wl_pointer_button_state state = state_w;
-
-	if (seat->drag_focus_resource &&
-	    seat->pointer->grab_button == button &&
-	    state == WL_POINTER_BUTTON_STATE_RELEASED)
-		wl_data_device_send_drop(seat->drag_focus_resource);
-
-	if (seat->pointer->button_count == 0 &&
-	    state == WL_POINTER_BUTTON_STATE_RELEASED) {
-		if (seat->drag_data_source)
-			wl_list_remove(&seat->drag_data_source_listener.link);
-		data_device_end_drag_grab(seat);
-	}
-}
-
-static const struct wl_pointer_grab_interface drag_grab_interface = {
-	drag_grab_focus,
-	drag_grab_motion,
-	drag_grab_button,
-};
-
-static void
-destroy_data_device_source(struct wl_listener *listener, void *data)
-{
-	struct wl_seat *seat = container_of(listener, struct wl_seat,
-					    drag_data_source_listener);
-
-	data_device_end_drag_grab(seat);
-}
-
-static void
-destroy_data_device_icon(struct wl_listener *listener, void *data)
-{
-	struct wl_seat *seat = container_of(listener, struct wl_seat,
-					    drag_icon_listener);
-
-	if (seat->pointer->cursor.icon == seat->drag_cursor.icon) {
-		seat->pointer->cursor.icon = NULL;
-		wl_signal_emit(&seat->pointer->cursor.update_signal,
-			       &seat->pointer->cursor);
-	}
-	seat->drag_cursor.icon = NULL;
-}
-
-static void
-data_device_start_drag(struct wl_client *client, struct wl_resource *resource,
-		       struct wl_resource *source_resource,
-		       struct wl_resource *origin_resource,
-		       struct wl_resource *icon_resource, uint32_t serial)
-{
-	struct wl_seat *seat = resource->data;
-
-	/* FIXME: Check that client has implicit grab on the origin
-	 * surface that matches the given time. */
-
-	/* FIXME: Check that the data source type array isn't empty. */
-
-	seat->drag_grab.interface = &drag_grab_interface;
-
-	seat->drag_client = client;
-	seat->drag_data_source = NULL;
-
-	if (source_resource) {
-		seat->drag_data_source = source_resource->data;
-		seat->drag_data_source_listener.notify =
-			destroy_data_device_source;
-		wl_signal_add(&source_resource->destroy_signal,
-			      &seat->drag_data_source_listener);
-	}
-
-	if (icon_resource) {
-		seat->drag_cursor.icon = icon_resource->data;
-		seat->drag_cursor.hotspot_x = 0;
-		seat->drag_cursor.hotspot_y = 0;
-		seat->drag_icon_listener.notify = destroy_data_device_icon;
-		wl_signal_add(&icon_resource->destroy_signal,
-			      &seat->drag_icon_listener);
-	}
-
-	wl_pointer_set_focus(seat->pointer, NULL,
-			     wl_fixed_from_int(0), wl_fixed_from_int(0));
-	wl_pointer_start_grab(seat->pointer, &seat->drag_grab);
-}
-
-static void
-destroy_selection_data_source(struct wl_listener *listener, void *data)
-{
-	struct wl_seat *seat = container_of(listener, struct wl_seat,
-					    selection_data_source_listener);
-	struct wl_resource *data_device;
-	struct wl_resource *focus = NULL;
-
-	seat->selection_data_source = NULL;
-
-	if (seat->keyboard)
-		focus = seat->keyboard->focus_resource;
-	if (focus) {
-		data_device = find_resource(&seat->drag_resource_list,
-					    focus->client);
-		if (data_device)
-			wl_data_device_send_selection(data_device, NULL);
-	}
-
-	wl_signal_emit(&seat->selection_signal, seat);
-}
-
-WL_EXPORT void
-wl_seat_set_selection(struct wl_seat *seat, struct wl_data_source *source,
-		      uint32_t serial)
-{
-	struct wl_resource *data_device, *offer;
-	struct wl_resource *focus = NULL;
-
-	if (seat->selection_data_source &&
-	    seat->selection_serial - serial < UINT32_MAX / 2)
-		return;
-
-	if (seat->selection_data_source) {
-		seat->selection_data_source->cancel(seat->selection_data_source);
-		wl_list_remove(&seat->selection_data_source_listener.link);
-		seat->selection_data_source = NULL;
-	}
-
-	seat->selection_data_source = source;
-	seat->selection_serial = serial;
-
-	if (seat->keyboard)
-		focus = seat->keyboard->focus_resource;
-	if (focus) {
-		data_device = find_resource(&seat->drag_resource_list,
-					    focus->client);
-		if (data_device && source) {
-			offer = wl_data_source_send_offer(seat->selection_data_source,
-							  data_device);
-			wl_data_device_send_selection(data_device, offer);
-		} else if (data_device) {
-			wl_data_device_send_selection(data_device, NULL);
-		}
-	}
-
-	wl_signal_emit(&seat->selection_signal, seat);
-
-	if (source) {
-		seat->selection_data_source_listener.notify =
-			destroy_selection_data_source;
-		wl_signal_add(&source->resource->destroy_signal,
-			      &seat->selection_data_source_listener);
-	}
-}
-
-static void
-data_device_set_selection(struct wl_client *client,
-			  struct wl_resource *resource,
-			  struct wl_resource *source_resource, uint32_t serial)
-{
-	if (!source_resource)
-		return;
-
-	/* FIXME: Store serial and check against incoming serial here. */
-	wl_seat_set_selection(resource->data, source_resource->data,
-			      serial);
-}
-
-static const struct wl_data_device_interface data_device_interface = {
-	data_device_start_drag,
-	data_device_set_selection,
-};
-
-static void
-destroy_data_source(struct wl_listener *listener, void *data)
-{
-	struct wl_data_source *source =
-		container_of(listener, struct wl_data_source, destroy_listener);
-	char **p;
-
-	wl_array_for_each(p, &source->mime_types)
-		free(*p);
-
-	wl_array_release(&source->mime_types);
-
-	free(source);
-}
-
-static void
-client_source_accept(struct wl_data_source *source,
-		     uint32_t time, const char *mime_type)
-{
-	wl_data_source_send_target(source->resource, mime_type);
-}
-
-static void
-client_source_send(struct wl_data_source *source,
-		   const char *mime_type, int32_t fd)
-{
-	wl_data_source_send_send(source->resource, mime_type, fd);
-	close(fd);
-}
-
-static void
-client_source_cancel(struct wl_data_source *source)
-{
-	wl_data_source_send_cancelled(source->resource);
-}
-
-static void
-create_data_source(struct wl_client *client,
-		   struct wl_resource *resource, uint32_t id)
-{
-	struct wl_data_source *source;
-
-	source = malloc(sizeof *source);
-	if (source == NULL) {
-		wl_resource_post_no_memory(resource);
-		return;
-	}
-
-	source->resource =
-		wl_client_add_object(client, &wl_data_source_interface,
-				     &data_source_interface, id, source);
-	if (source->resource == NULL) {
-		free(source);
-		wl_resource_post_no_memory(resource);
-		return;
-	}
-	source->destroy_listener.notify = destroy_data_source;
-	wl_signal_add(&source->resource->destroy_signal,
-		      &source->destroy_listener);
-
-	source->accept = client_source_accept;
-	source->send = client_source_send;
-	source->cancel = client_source_cancel;
-
-	wl_array_init(&source->mime_types);
-}
-
-static void unbind_data_device(struct wl_resource *resource)
-{
-	wl_list_remove(&resource->link);
-	free(resource);
-}
-
-static void
-get_data_device(struct wl_client *client,
-		struct wl_resource *manager_resource,
-		uint32_t id, struct wl_resource *seat_resource)
-{
-	struct wl_seat *seat = seat_resource->data;
-	struct wl_resource *resource;
-
-	resource = wl_client_add_object(client, &wl_data_device_interface,
-					&data_device_interface, id,
-					seat);
-
-	wl_list_insert(&seat->drag_resource_list, &resource->link);
-	resource->destroy = unbind_data_device;
-}
-
-static const struct wl_data_device_manager_interface manager_interface = {
-	create_data_source,
-	get_data_device
-};
-
-static void
-bind_manager(struct wl_client *client,
-	     void *data, uint32_t version, uint32_t id)
-{
-	wl_client_add_object(client, &wl_data_device_manager_interface,
-			     &manager_interface, id, NULL);
-}
-
-WL_EXPORT void
-wl_data_device_set_keyboard_focus(struct wl_seat *seat)
-{
-	struct wl_resource *data_device, *focus, *offer;
-	struct wl_data_source *source;
-
-	if (!seat->keyboard)
-		return;
-
-	focus = seat->keyboard->focus_resource;
-	if (!focus)
-		return;
-
-	data_device = find_resource(&seat->drag_resource_list,
-				    focus->client);
-	if (!data_device)
-		return;
-
-	source = seat->selection_data_source;
-	if (source) {
-		offer = wl_data_source_send_offer(source, data_device);
-		wl_data_device_send_selection(data_device, offer);
-	}
-}
-
-WL_EXPORT int
-wl_data_device_manager_init(struct wl_display *display)
-{
-	if (wl_display_add_global(display,
-				  &wl_data_device_manager_interface,
-				  NULL, bind_manager) == NULL)
-		return -1;
-
-	return 0;
-}
diff --git a/src/data-transfer.c b/src/data-transfer.c
new file mode 100644
index 0000000..9033d33
--- /dev/null
+++ b/src/data-transfer.c
@@ -0,0 +1,629 @@
+/*
+ * Copyright © 2011 Kristian Høgsberg
+ *
+ * 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 <string.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include "wayland-server.h"
+#include "wayland-private.h"
+
+static struct wl_resource *
+find_resource(struct wl_list *list, struct wl_client *client)
+{
+	struct wl_resource *r;
+
+	wl_list_for_each(r, list, link) {
+		if (r->client == client)
+			return r;
+	}
+
+	return NULL;
+}
+
+static int
+array_is_empty(struct wl_array *array)
+{
+	const char **p;
+
+	wl_array_for_each(p, array)
+		return 1;
+
+	return 0;
+}
+
+static void
+data_source_remove_offer(struct wl_data_offer *offer)
+{
+	struct wl_listener *tmp;
+
+	if (!offer->source)
+		return;
+
+	wl_list_remove(&offer->source_destroy_listener.link);
+
+	/* If no-one's listening for our destruction, then we're inactive. */
+	wl_list_for_each(tmp,
+			 &offer->source->resource->destroy_signal.listener_list,
+			 link) {
+		return;
+	}
+
+	wl_data_source_send_inactive(offer->source->resource);
+}
+
+static void
+destroy_drag_data_source(struct wl_listener *listener, void *data)
+{
+	struct wl_drag_offer *offer =
+		container_of(listener, struct wl_drag_offer,
+			     common.source_destroy_listener);
+
+	offer->common.source = NULL;
+
+	if (offer->resource) {
+		struct wl_client *client;
+		uint32_t serial;
+
+		client = offer->resource->client;
+		serial = wl_display_next_serial(wl_client_get_display(client));
+		wl_drag_offer_send_leave(offer->resource, serial);
+	}
+}
+
+static void
+destroy_selection_data_source(struct wl_listener *listener, void *data)
+{
+	struct wl_selection_offer *selection =
+		container_of(listener, struct wl_selection_offer,
+			     common.source_destroy_listener);
+	struct wl_data_source *source = selection->common.source;
+	struct wl_seat *seat = selection->common.seat;
+
+	selection->common.source = NULL;
+	if (selection->resource)
+		wl_selection_offer_send_cancel(selection->resource);
+
+	if (seat->selection_data_source == source) {
+		seat->selection_data_source = NULL;
+		wl_signal_emit(&seat->selection_signal, seat);
+	}
+}
+
+/**
+ * Called when the drag has ended and no more offers will be made.
+ */
+static void
+drag_free(struct wl_drag_offer *drag)
+{
+	data_source_remove_offer(&drag->common);
+	free(drag);
+}
+
+/**
+ * Called when the wl_resource associated with a drag offer to a specific
+ * client has been destroyed.
+ */
+static void
+destroy_drag_offer(struct wl_listener *listener, void *data)
+{
+	struct wl_drag_offer *drag =
+		container_of(listener, struct wl_drag_offer, destroy_listener);
+
+	if (drag->pending_drop && drag->resource == data)
+		drag_free(drag);
+	else
+		drag->resource = NULL;
+}
+
+static void
+drag_offer_accept(struct wl_client *client, struct wl_resource *resource,
+		  const char *mime_type)
+{
+	struct wl_data_offer *offer = resource->data;
+
+	if (offer->source)
+		offer->source->accept(offer->source, mime_type);
+}
+
+static void
+drag_offer_destroy(struct wl_client *client, struct wl_resource *resource)
+{
+	wl_resource_destroy(resource);
+}
+
+static void
+drag_offer_transfer(struct wl_client *client, struct wl_resource *resource,
+		    const char *mime_type, int32_t fd)
+{
+	struct wl_drag_offer *drag = resource->data;
+
+	drag->common.source->send(drag->common.source, mime_type, fd);
+}
+
+static const struct wl_drag_offer_interface drag_offer_interface = {
+	drag_offer_accept,
+	drag_offer_destroy,
+	drag_offer_transfer,
+};
+
+static void
+selection_offer_accept(struct wl_client *client, struct wl_resource *resource,
+		       const char *mime_type)
+{
+	struct wl_data_offer *offer = resource->data;
+
+	if (offer->source)
+		offer->source->accept(offer->source, mime_type);
+}
+
+static void
+selection_offer_reject(struct wl_client *client, struct wl_resource *resource)
+{
+	wl_resource_destroy(resource);
+}
+
+static void
+selection_offer_transfer(struct wl_client *client, struct wl_resource *resource,
+			 const char *mime_type, int32_t fd)
+{
+	struct wl_data_offer *offer = resource->data;
+
+	offer->source->send(offer->source, mime_type, fd);
+}
+
+static const struct wl_selection_offer_interface selection_offer_interface = {
+	selection_offer_accept,
+	selection_offer_reject,
+	selection_offer_transfer,
+};
+
+static void
+drag_grab_focus(struct wl_pointer_grab *grab,
+		struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y)
+{
+	struct wl_drag_offer *drag =
+		container_of(grab, struct wl_drag_offer, pointer_grab);
+	struct wl_pointer *pointer = grab->pointer;
+	struct wl_client *client;
+	struct wl_resource *resource;
+	uint32_t serial;
+	char **p;
+
+	if (drag->resource) {
+		client = drag->resource->client;
+		serial = wl_display_next_serial(wl_client_get_display(client));
+		wl_drag_offer_send_leave(drag->resource, serial);
+		wl_list_remove(&drag->destroy_listener.link);
+		drag->resource->data = NULL;
+	}
+
+	drag->resource = NULL;
+	drag->focus_resource = NULL;
+	drag->focus = NULL;
+	grab->focus = surface;
+
+	if (!surface)
+		return;
+
+	pointer->cursor.icon = drag->cursor.icon;
+	pointer->cursor.x = pointer->x;
+	pointer->cursor.y = pointer->y;
+	wl_signal_emit(&pointer->cursor.update_signal, &pointer->cursor);
+
+	if (!drag->common.source &&
+	    surface->resource.client != drag->original_client)
+		return;
+
+	resource = find_resource(&drag->common.seat->data_resource_list,
+				 surface->resource.client);
+	if (!resource)
+		return;
+
+	drag->resource =
+		wl_client_new_object(resource->client, &wl_drag_offer_interface,
+				     &drag_offer_interface, &drag->common);
+	if (drag->resource == NULL)
+		return;
+
+	drag->destroy_listener.notify = destroy_drag_offer;
+	wl_signal_add(&drag->resource->destroy_signal, &drag->destroy_listener);
+
+	wl_data_transfer_send_drag(resource, drag->resource);
+
+	if (drag->common.source) {
+		wl_array_for_each(p, &drag->common.source->mime_types)
+			wl_drag_offer_send_offer(drag->resource, *p);
+		wl_drag_offer_send_offer(drag->resource, NULL);
+	}
+
+	wl_drag_offer_send_enter(drag->resource, &surface->resource, x, y);
+
+	drag->focus = surface;
+	drag->focus_resource = resource;
+}
+
+static void
+drag_grab_motion(struct wl_pointer_grab *grab,
+		 uint32_t time, wl_fixed_t x, wl_fixed_t y)
+{
+	struct wl_drag_offer *drag =
+		container_of(grab, struct wl_drag_offer, pointer_grab);
+	struct wl_pointer *pointer = grab->pointer;
+
+	if (drag->resource)
+		wl_drag_offer_send_motion(drag->resource, time, x, y);
+
+	pointer->cursor.x = pointer->x;
+	pointer->cursor.y = pointer->y;
+	wl_signal_emit(&pointer->cursor.update_signal, &pointer->cursor);
+}
+
+static void
+drag_grab_button(struct wl_pointer_grab *grab,
+		 uint32_t time, uint32_t button, uint32_t state_w)
+{
+	struct wl_drag_offer *drag =
+		container_of(grab, struct wl_drag_offer, pointer_grab);
+	struct wl_pointer *pointer = grab->pointer;
+	struct wl_resource *surface_resource;
+	struct wl_surface_interface *implementation;
+	enum wl_pointer_button_state state = state_w;
+
+	if (drag->resource && pointer->grab_button == button &&
+	    state == WL_POINTER_BUTTON_STATE_RELEASED) {
+		drag->pending_drop = 1;
+		wl_drag_offer_send_drop(drag->resource);
+	}
+
+	if (pointer->button_count != 0 ||
+	    state != WL_POINTER_BUTTON_STATE_RELEASED)
+		return;
+
+	if (drag->cursor.icon) {
+		surface_resource = &drag->cursor.icon->resource;
+		implementation = (struct wl_surface_interface *)
+			surface_resource->object.implementation;
+
+		implementation->attach(surface_resource->client,
+				       surface_resource, NULL, 0, 0);
+		wl_list_remove(&drag->icon_listener.link);
+		drag->cursor.icon = NULL;
+	}
+
+	wl_pointer_set_focus(pointer, NULL,
+			     wl_fixed_from_int(0), wl_fixed_from_int(0));
+	wl_pointer_end_grab(pointer);
+
+	if (!drag->pending_drop)
+		drag_free(drag);
+}
+
+static const struct wl_pointer_grab_interface drag_grab_interface = {
+	drag_grab_focus,
+	drag_grab_motion,
+	drag_grab_button,
+};
+
+static void
+destroy_drag_icon(struct wl_listener *listener, void *data)
+{
+	struct wl_drag_offer *drag =
+		container_of(listener, struct wl_drag_offer, icon_listener);
+
+	drag->cursor.icon = NULL;
+}
+
+static void
+data_device_start_drag(struct wl_client *client, struct wl_resource *resource,
+		       struct wl_resource *source_resource,
+		       struct wl_resource *origin_resource,
+		       struct wl_resource *icon_resource, uint32_t serial)
+{
+	struct wl_seat *seat = resource->data;
+	struct wl_data_source *source = NULL;
+	struct wl_drag_offer *drag;
+
+	/* FIXME: Check that client has implicit grab on the origin
+	 * surface that matches the given serial. */
+
+	if (source_resource)
+		source = source_resource->data;
+	if (source && array_is_empty(&source->mime_types))
+		wl_resource_post_error(resource,
+				       WL_DATA_TRANSFER_ERROR_NO_TYPES,
+				       "no data types in wl_data_source");
+
+	drag = malloc(sizeof *drag);
+	if (!drag) {
+		wl_resource_post_no_memory(resource);
+		return;
+	}
+	drag->common.seat = seat;
+	drag->common.source = NULL;
+	drag->resource = NULL;
+	drag->pending_drop = 0;
+	drag->pointer_grab.interface = &drag_grab_interface;
+	drag->original_client = client;
+	drag->focus = NULL;
+	drag->focus_resource = NULL;
+	drag->cursor.icon = NULL;
+	drag->cursor.x = seat->pointer->x;
+	drag->cursor.y = seat->pointer->y;
+	drag->cursor.hotspot_x = 0;
+	drag->cursor.hotspot_y = 0;
+
+	if (source) {
+		drag->common.source = source;
+		drag->common.source_destroy_listener.notify =
+			destroy_drag_data_source;
+		wl_signal_add(&source_resource->destroy_signal,
+			      &drag->common.source_destroy_listener);
+	}
+
+	if (icon_resource) {
+		drag->cursor.icon = icon_resource->data;
+		drag->icon_listener.notify = destroy_drag_icon;
+		wl_signal_add(&icon_resource->destroy_signal,
+			      &drag->icon_listener);
+	}
+
+	wl_pointer_set_focus(seat->pointer, NULL,
+			     wl_fixed_from_int(0), wl_fixed_from_int(0));
+	wl_pointer_start_grab(seat->pointer, &drag->pointer_grab);
+}
+
+static void
+destroy_selection_offer(struct wl_listener *listener, void *data)
+{
+	struct wl_selection_offer *selection =
+		container_of(listener, struct wl_selection_offer,
+			     destroy_listener);
+	struct wl_seat *seat = selection->common.seat;
+
+	if (seat->selection == selection)
+		seat->selection = NULL;
+
+	wl_selection_offer_send_cancel(selection->resource);
+
+	data_source_remove_offer(&selection->common);
+
+	free(selection);
+}
+
+void
+wl_seat_update_selection(struct wl_seat *seat)
+{
+	struct wl_resource *kb_focus, *target;
+	struct wl_selection_offer *selection;
+	const char **p;
+
+	if (seat->selection)
+		wl_selection_offer_send_cancel(seat->selection->resource);
+	seat->selection = NULL;
+
+	if (!seat->keyboard || seat->selection_data_source)
+		return;
+
+	kb_focus = seat->keyboard->focus_resource;
+	if (!kb_focus)
+		return;
+
+	target = find_resource(&seat->data_resource_list, kb_focus->client);
+	if (!target)
+		return;
+
+	if (!seat->selection_data_source ||
+	    array_is_empty(&seat->selection_data_source->mime_types)) {
+		wl_data_transfer_send_selection(target, NULL);
+		seat->selection = NULL;
+		return;
+	}
+
+	selection = malloc(sizeof *selection);
+	if (selection == NULL)
+		return;
+
+	selection->common.seat = seat;
+
+	selection->resource =
+		wl_client_new_object(target->client,
+				     &wl_selection_offer_interface,
+				     &selection_offer_interface,
+				     &selection->common);
+	if (selection->resource == NULL) {
+		free(selection);
+		return;
+	}
+	selection->destroy_listener.notify = destroy_selection_offer;
+	wl_signal_add(&selection->resource->destroy_signal,
+		      &selection->destroy_listener);
+
+	selection->common.source = seat->selection_data_source;
+	selection->common.source_destroy_listener.notify =
+		destroy_selection_data_source;
+	wl_signal_add(&selection->common.source->resource->destroy_signal,
+		      &selection->common.source_destroy_listener);
+
+	wl_data_transfer_send_selection(target, selection->resource);
+
+	wl_array_for_each(p, &selection->common.source->mime_types)
+		wl_selection_offer_send_offer(selection->resource, *p);
+	wl_selection_offer_send_offer(selection->resource, NULL);
+
+	seat->selection = selection;
+}
+
+WL_EXPORT void
+wl_seat_set_selection(struct wl_seat *seat, struct wl_data_source *source,
+		      uint32_t serial)
+{
+
+	if (seat->selection_data_source &&
+	    seat->selection_serial - serial < UINT32_MAX / 2)
+		return;
+
+	if (seat->selection)
+		wl_selection_offer_send_cancel(seat->selection->resource);
+	seat->selection = NULL;
+
+	seat->selection_data_source = source;
+	seat->selection_serial = serial;
+
+	wl_seat_update_selection(seat);
+
+	wl_signal_emit(&seat->selection_signal, seat);
+}
+
+static void
+data_device_set_selection(struct wl_client *client,
+			  struct wl_resource *resource,
+			  struct wl_resource *source_resource, uint32_t serial)
+{
+	if (!source_resource)
+		return;
+
+	/* FIXME: Store serial and check against incoming serial here. */
+	wl_seat_set_selection(resource->data, source_resource->data,
+			      serial);
+}
+
+static void
+destroy_data_source(struct wl_listener *listener, void *data)
+{
+	struct wl_data_source *source =
+		container_of(listener, struct wl_data_source, destroy_listener);
+	char **p;
+
+	wl_array_for_each(p, &source->mime_types)
+		free(*p);
+
+	wl_array_release(&source->mime_types);
+
+	free(source);
+}
+
+static void
+data_source_add_type(struct wl_client *client, struct wl_resource *resource,
+		     const char *type)
+{
+	struct wl_data_source *source = resource->data;
+	char **p;
+
+	p = wl_array_add(&source->mime_types, sizeof *p);
+	if (p)
+		*p = strdup(type);
+	if (!p || !*p)
+		wl_resource_post_no_memory(resource);
+
+	/* XXX Should re-send a drag_offer or selection_offer notification
+	 *     to clients if the data source types change from underneath
+	 *     them? */
+}
+
+static void
+data_source_destroy(struct wl_client *client, struct wl_resource *resource)
+{
+	wl_resource_destroy(resource);
+}
+
+static struct wl_data_source_interface data_source_interface = {
+	data_source_add_type,
+	data_source_destroy
+};
+
+static void
+client_source_accept(struct wl_data_source *source, const char *mime_type)
+{
+	wl_data_source_send_accepted(source->resource, mime_type);
+}
+
+static void
+client_source_send(struct wl_data_source *source,
+		   const char *mime_type, int32_t fd)
+{
+	wl_data_source_send_transfer(source->resource, mime_type, fd);
+	close(fd);
+}
+
+static void
+client_source_cancel(struct wl_data_source *source)
+{
+	wl_data_source_send_inactive(source->resource);
+}
+
+static void
+create_data_source(struct wl_client *client,
+		   struct wl_resource *resource, uint32_t id)
+{
+	struct wl_data_source *source;
+
+	source = malloc(sizeof *source);
+	if (source == NULL) {
+		wl_resource_post_no_memory(resource);
+		return;
+	}
+
+	source->resource =
+		wl_client_add_object(client, &wl_data_source_interface,
+				     &data_source_interface, id, source);
+	if (source->resource == NULL) {
+		free(source);
+		wl_resource_post_no_memory(resource);
+		return;
+	}
+	source->destroy_listener.notify = destroy_data_source;
+	wl_signal_add(&source->resource->destroy_signal,
+		      &source->destroy_listener);
+
+	source->accept = client_source_accept;
+	source->send = client_source_send;
+	source->cancel = client_source_cancel;
+
+	wl_array_init(&source->mime_types);
+}
+
+static const struct wl_data_transfer_interface data_transfer_interface = {
+	create_data_source,
+	data_device_start_drag,
+	data_device_set_selection,
+};
+
+static void
+unbind_resource(struct wl_resource *resource)
+{
+	wl_list_remove(&resource->link);
+	free(resource);
+}
+
+WL_EXPORT void
+wl_seat_new_data_transfer(struct wl_seat *seat, struct wl_client *client,
+			  uint32_t id)
+{
+	struct wl_resource *cr;
+
+	cr = wl_client_add_object(client, &wl_data_transfer_interface,
+				  &data_transfer_interface, id, seat);
+	wl_list_insert(&seat->data_resource_list, &cr->link);
+	cr->destroy = unbind_resource;
+}
diff --git a/src/wayland-private.h b/src/wayland-private.h
index f9fcc96..6dd006d 100644
--- a/src/wayland-private.h
+++ b/src/wayland-private.h
@@ -53,6 +53,9 @@ void wl_map_for_each(struct wl_map *map, wl_iterator_func_t func, void *data);
 
 struct wl_connection;
 struct wl_closure;
+struct wl_seat;
+struct wl_resource;
+struct wl_client;
 
 #define WL_CONNECTION_READABLE 0x01
 #define WL_CONNECTION_WRITABLE 0x02
@@ -119,4 +122,7 @@ extern wl_log_func_t wl_log_handler;
 
 void wl_log(const char *fmt, ...);
 
+void
+wl_seat_update_selection(struct wl_seat *seat);
+
 #endif
diff --git a/src/wayland-server.c b/src/wayland-server.c
index f9d2522..fca8096 100644
--- a/src/wayland-server.c
+++ b/src/wayland-server.c
@@ -756,12 +756,11 @@ wl_seat_init(struct wl_seat *seat)
 	memset(seat, 0, sizeof *seat);
 
 	wl_signal_init(&seat->destroy_signal);
-
-	seat->selection_data_source = NULL;
-	wl_list_init(&seat->base_resource_list);
 	wl_signal_init(&seat->selection_signal);
-	wl_list_init(&seat->drag_resource_list);
 	wl_signal_init(&seat->cursor_signal);
+
+	wl_list_init(&seat->base_resource_list);
+	wl_list_init(&seat->data_resource_list);
 }
 
 WL_EXPORT void
@@ -781,7 +780,9 @@ static void
 seat_send_updated_caps(struct wl_seat *seat)
 {
 	struct wl_resource *r;
-	enum wl_seat_capability caps = 0;
+	enum wl_seat_capability caps;
+
+	caps = WL_SEAT_CAPABILITY_DATA_TRANSFER;
 
 	if (seat->pointer)
 		caps |= WL_SEAT_CAPABILITY_POINTER;
@@ -922,6 +923,9 @@ wl_keyboard_set_focus(struct wl_keyboard *keyboard, struct wl_surface *surface)
 
 	keyboard->focus_resource = resource;
 	keyboard->focus = surface;
+
+	wl_seat_update_selection(keyboard->seat);
+
 	wl_signal_emit(&keyboard->focus_signal, keyboard);
 }
 
diff --git a/src/wayland-server.h b/src/wayland-server.h
index 376a76f..ab48fbb 100644
--- a/src/wayland-server.h
+++ b/src/wayland-server.h
@@ -222,8 +222,22 @@ struct wl_keyboard_grab {
 	uint32_t key;
 };
 
+struct wl_cursor {
+	struct wl_signal update_signal;
+	struct wl_signal destroy_signal;
+
+	struct wl_surface *icon;
+
+	wl_fixed_t x;
+	wl_fixed_t y;
+
+	int32_t hotspot_x;
+	int32_t hotspot_y;
+};
+
+/* Shared betweeen wl_drag_offer and wl_selection_offer. */
 struct wl_data_offer {
-	struct wl_resource *resource;
+	struct wl_seat *seat;
 	struct wl_data_source *source;
 	struct wl_listener source_destroy_listener;
 	struct wl_listener offer_destroy_listener;
@@ -231,27 +245,35 @@ struct wl_data_offer {
 
 struct wl_data_source {
 	struct wl_resource *resource;
-	struct wl_listener destroy_listener;
 	struct wl_array mime_types;
+	struct wl_listener destroy_listener;
 
-	void (*accept)(struct wl_data_source *source,
-		       uint32_t serial, const char *mime_type);
-	void (*send)(struct wl_data_source *source,
-		     const char *mime_type, int32_t fd);
+	void (*accept)(struct wl_data_source *source, const char *mime_type);
+	void (*send)(struct wl_data_source *source, const char *mime_type,
+		     int32_t fd);
 	void (*cancel)(struct wl_data_source *source);
 };
 
-struct wl_cursor {
-	struct wl_signal update_signal;
-	struct wl_signal destroy_signal;
-
-	struct wl_surface *icon;
-
-	wl_fixed_t x;
-	wl_fixed_t y;
+struct wl_drag_offer {
+	struct wl_data_offer common;
+	struct wl_resource *resource;
+	struct wl_listener destroy_listener;
+	unsigned int pending_drop;
+	struct wl_pointer_grab pointer_grab;
+	struct wl_client *original_client;
+	struct wl_surface *focus;
+	struct wl_resource *focus_resource;
+	struct wl_listener focus_listener;
+	struct wl_cursor cursor;
+	struct wl_listener icon_listener;
+};
 
-	int32_t hotspot_x;
-	int32_t hotspot_y;
+struct wl_selection_offer {
+	struct wl_data_offer common;
+	struct wl_resource *resource;
+	struct wl_listener destroy_listener;
+	uint32_t serial;
+	struct wl_signal signal;
 };
 
 struct wl_pointer {
@@ -328,21 +350,11 @@ struct wl_seat {
 
 	struct wl_signal cursor_signal;
 
+	struct wl_list data_resource_list;
+	struct wl_selection_offer *selection;
 	uint32_t selection_serial;
 	struct wl_data_source *selection_data_source;
-	struct wl_listener selection_data_source_listener;
 	struct wl_signal selection_signal;
-
-	struct wl_list drag_resource_list;
-	struct wl_client *drag_client;
-	struct wl_data_source *drag_data_source;
-	struct wl_listener drag_data_source_listener;
-	struct wl_surface *drag_focus;
-	struct wl_resource *drag_focus_resource;
-	struct wl_listener drag_focus_listener;
-	struct wl_pointer_grab drag_grab;
-	struct wl_cursor drag_cursor;
-	struct wl_listener drag_icon_listener;
 };
 
 /*
@@ -433,16 +445,12 @@ void
 wl_touch_release(struct wl_touch *touch);
 
 void
-wl_data_device_set_keyboard_focus(struct wl_seat *seat);
-
-int
-wl_data_device_manager_init(struct wl_display *display);
-
-
-void
 wl_seat_set_selection(struct wl_seat *seat,
 		      struct wl_data_source *source, uint32_t serial);
-
+void
+wl_seat_new_data_transfer(struct wl_seat *seat,
+                          struct wl_client *client,
+                          uint32_t id);
 
 void *
 wl_shm_buffer_get_data(struct wl_buffer *buffer);
-- 
1.7.10.4



More information about the wayland-devel mailing list