diff -rupN a/js/ui/xdndHandler.js b/js/ui/xdndHandler.js
--- a/js/ui/xdndHandler.js	2013-03-14 20:02:39.000000000 +0100
+++ b/js/ui/xdndHandler.js	2013-03-21 19:19:52.320225042 +0100
@@ -7,6 +7,13 @@ const Shell = imports.gi.Shell;
 const Signals = imports.signals;
 const DND = imports.ui.dnd;
 
+const DRAG_STATUS_MAP = {
+    0: Shell.XdndDragStatus.NO_DROP,
+    1: Shell.XdndDragStatus.COPY_DROP,
+    2: Shell.XdndDragStatus.MOVE_DROP,
+    3: Shell.XdndDragStatus.NO_DROP
+};
+
 const XdndHandler = new Lang.Class({
     Name: 'XdndHandler',
 
@@ -21,11 +28,20 @@ const XdndHandler = new Lang.Class({
         Shell.util_set_hidden_from_pick(this._dummy, true);
         this._dummy.hide();
 
+        // Available mime types during XDnD.
+        this.mimeTypes = Array();
+
+        // Keeps information from xdnd-position-changed events for _onDrop and _onData.
+        this._pickedActor = null;
+        this._dragEvent = null;
+
         global.init_xdnd();
 
         global.connect('xdnd-enter', Lang.bind(this, this._onEnter));
         global.connect('xdnd-position-changed', Lang.bind(this, this._onPositionChanged));
         global.connect('xdnd-leave', Lang.bind(this, this._onLeave));
+        global.connect('xdnd-drop', Lang.bind(this, this._onDrop));
+        global.connect('xdnd-data', Lang.bind(this, this._onData));
 
         this._windowGroupVisibilityHandlerId = 0;
     },
@@ -42,9 +58,12 @@ const XdndHandler = new Lang.Class({
         }
 
         this.emit('drag-end');
+        this.mimeTypes = Array();
     },
 
-    _onEnter: function() {
+    _onEnter: function(obj, mimeTypes) {
+        this.mimeTypes = mimeTypes;
+
         this._windowGroupVisibilityHandlerId  =
                 global.window_group.connect('notify::visible',
                     Lang.bind(this, this._onWindowGroupVisibilityChanged));
@@ -84,6 +103,9 @@ const XdndHandler = new Lang.Class({
     _onPositionChanged: function(obj, x, y) {
         let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
 
+        this._pickedActor = null;
+        this._dragEvent = null;
+
         // Make sure that the cursor window is on top
         if (this._cursorWindowClone)
              this._cursorWindowClone.raise_top();
@@ -100,8 +122,10 @@ const XdndHandler = new Lang.Class({
             let motionFunc = DND.dragMonitors[i].dragMotion;
             if (motionFunc) {
                 let result = motionFunc(dragEvent);
-                if (result != DND.DragMotionResult.CONTINUE)
+                if (result != DND.DragMotionResult.CONTINUE) {
+                    global.set_xdnd_drag_status(DRAG_STATUS_MAP[result]);
                     return;
+                }
             }
         }
 
@@ -113,11 +137,60 @@ const XdndHandler = new Lang.Class({
                                                                       targX,
                                                                       targY,
                                                                       global.get_current_time());
-                    if (result != DND.DragMotionResult.CONTINUE)
+
+                    if (result != DND.DragMotionResult.CONTINUE) {
+                        this._dragEvent = dragEvent;
+                        this._pickedActor = pickedActor;
+                        global.set_xdnd_drag_status(DRAG_STATUS_MAP[result]);
                         return;
+                    }
                 }
                 pickedActor = pickedActor.get_parent();
         }
+
+        global.set_xdnd_drag_status(DRAG_STATUS_MAP[DND.DragMotionResult.NO_DROP]);
+    },
+
+    _onDrop: function(obj) {
+        let pickedActor = this._pickedActor;
+        let dragEvent = this._dragEvent;
+
+        if (pickedActor && dragEvent && pickedActor._delegate && pickedActor._delegate.acceptDrop) {
+            let [r, targX, targY] = pickedActor.transform_stage_point(dragEvent.x, dragEvent.y);
+            pickedActor._delegate.acceptDrop(this,
+                                             dragEvent.dragActor,
+                                             targX,
+                                             targY,
+                                             global.get_current_time());
+        }
+
+        this.emit('drag-end');
+        this.mimeTypes = Array();
+    },
+
+    _onData: function(obj, mimeType, data) {
+        let pickedActor = this._pickedActor;
+        let dragEvent = this._dragEvent;
+
+        if (pickedActor && dragEvent && pickedActor._delegate && pickedActor._delegate.acceptData) {
+            let [r, targX, targY] = pickedActor.transform_stage_point(dragEvent.x, dragEvent.y);
+            pickedActor._delegate.acceptData(this,
+                                             dragEvent.dragActor,
+                                             targX,
+                                             targY,
+                                             global.get_current_time(),
+                                             mimeType,
+                                             data);
+        }
+    },
+
+    requestData: function(delegate, mimeType) {
+        let pickedActor = this._pickedActor;
+
+        if (pickedActor && pickedActor._delegate == delegate && 0 <= this.mimeTypes.indexOf(mimeType))
+            global.request_xdnd_selection_data(mimeType);
+        else
+            throw new Error('Invalid XDnD data request');
     }
 });
 
diff -rupN a/src/shell-global.c b/src/shell-global.c
--- a/src/shell-global.c	2013-03-14 20:02:39.000000000 +0100
+++ b/src/shell-global.c	2013-03-21 19:19:20.168158382 +0100
@@ -90,6 +90,8 @@ struct _ShellGlobal {
   ca_context *sound_context;
 
   guint32 xdnd_timestamp;
+  Window xdnd_source;
+  Atom xdnd_action;
 
   gint64 last_gc_end_time;
 };
@@ -122,6 +124,8 @@ enum
  XDND_POSITION_CHANGED,
  XDND_LEAVE,
  XDND_ENTER,
+ XDND_DROP,
+ XDND_DATA,
  NOTIFY_ERROR,
  LAST_SIGNAL
 };
@@ -330,13 +334,31 @@ shell_global_class_init (ShellGlobalClas
                     G_TYPE_NONE, 0);
 
   /* Emitted from gnome-shell-plugin.c during event handling */
+  shell_global_signals[XDND_DROP] =
+      g_signal_new ("xdnd-drop",
+                    G_TYPE_FROM_CLASS (klass),
+                    G_SIGNAL_RUN_LAST,
+                    0,
+                    NULL, NULL, NULL,
+                    G_TYPE_NONE, 0);
+
+  /* Emitted from gnome-shell-plugin.c during event handling */
   shell_global_signals[XDND_ENTER] =
       g_signal_new ("xdnd-enter",
                     G_TYPE_FROM_CLASS (klass),
                     G_SIGNAL_RUN_LAST,
                     0,
                     NULL, NULL, NULL,
-                    G_TYPE_NONE, 0);
+                    G_TYPE_NONE, 1, G_TYPE_STRV);
+
+  /* Emitted from gnome-shell-plugin.c during event handling */
+  shell_global_signals[XDND_DATA] =
+      g_signal_new ("xdnd-data",
+                    G_TYPE_FROM_CLASS (klass),
+                    G_SIGNAL_RUN_LAST,
+                    0,
+                    NULL, NULL, NULL,
+                    G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
 
   shell_global_signals[NOTIFY_ERROR] =
       g_signal_new ("notify-error",
@@ -1231,6 +1253,10 @@ void shell_global_init_xdnd (ShellGlobal
   Window output_window = meta_get_overlay_window (global->meta_screen);
   long xdnd_version = 5;
 
+  global->xdnd_timestamp = 0;
+  global->xdnd_source = 0;
+  global->xdnd_action = None;
+
   XChangeProperty (global->xdisplay, global->stage_xwindow,
                    gdk_x11_get_xatom_by_name ("XdndAware"), XA_ATOM,
                    32, PropModeReplace, (const unsigned char *)&xdnd_version, 1);
@@ -1715,6 +1741,115 @@ shell_global_cancel_theme_sound (ShellGl
 }
 
 /*
+ * Retrieves XDnD mime types as a string array.
+ */
+gchar **
+_shell_global_get_xdnd_mime_types (ShellGlobal         *global,
+                                   XClientMessageEvent *xclient)
+{
+  GPtrArray *array = g_ptr_array_new ();
+  Atom atom, *typelist;
+  int i, format;
+  unsigned long count, remaining, offset = 0;
+  unsigned char *data = NULL;
+
+  if (xclient->message_type != gdk_x11_get_xatom_by_name ("XdndEnter"))
+    {
+      g_warning ("XDnD: Cannot get mime type from %s",
+                 gdk_x11_get_xatom_name (xclient->message_type));
+
+      g_ptr_array_add (array, NULL);
+      return (gchar **) g_ptr_array_free (array, FALSE);
+    }
+
+  /* For up to three types. */
+  if ((xclient->data.l[1] & 1) == 0)
+    for (i = 2; i < 5; i++)
+      {
+        atom = xclient->data.l[i];
+
+        if (atom != None)
+          g_ptr_array_add(array, (gpointer) gdk_x11_get_xatom_name (atom));
+      }
+
+  /* There are more than three types, we need to use XdndTypeList. */
+  else
+    do
+      {
+        XGetWindowProperty (global->xdisplay, xclient->data.l[0],
+                            gdk_x11_get_xatom_by_name ("XdndTypeList"),
+                            offset, 0x8000000L, False, XA_ATOM,
+                            &atom, &format, &count, &remaining, &data);
+
+        if (atom == XA_ATOM && format == 32 && count != 0 && data)
+          {
+            typelist = (Atom *) data;
+
+            for (i = 0; i < count; i++)
+              g_ptr_array_add (array, (gpointer) gdk_x11_get_xatom_name (typelist[i]));
+
+            XFree (data);
+            offset += count;
+          }
+        else
+          {
+            g_warning("XDnD: Unable to get mime types");
+
+            if (data)
+              XFree (data);
+
+            break;
+          }
+      }
+    while (remaining != 0);
+
+  g_ptr_array_add (array, NULL);
+  return (gchar **) g_ptr_array_free (array, FALSE);
+}
+
+/*
+ * Returns data from an XDnD drop.
+ */
+GString *
+_shell_global_get_xdnd_selection_data (ShellGlobal *global,
+                                       Atom target)
+{
+  GString *str = g_string_new (NULL);
+  Atom atom;
+  int format;
+  unsigned long count, remaining, offset = 0;
+  unsigned char *data = NULL;
+
+  do
+    {
+      XGetWindowProperty (global->xdisplay, global->stage_xwindow,
+                          gdk_x11_get_xatom_by_name ("GnomeShellXdndSelection"),
+                          offset, 0x8000000L, True, target,
+                          &atom, &format, &count, &remaining, &data);
+
+      if (atom == target && format == 8 && count != 0 && data)
+        {
+          str = g_string_append_len (str, (gchar *) data, count);
+
+          XFree (data);
+          offset += count;
+        }
+      else
+        {
+          g_warning("XDnD: Unable to get selection data");
+
+          if (data)
+            XFree (data);
+
+          break;
+        }
+    }
+  while (remaining != 0);
+
+  return str;
+}
+
+/*
  * Process Xdnd events
  *
  * We pass the position and leave events to JS via a signal
@@ -1730,41 +1865,83 @@ gboolean _shell_global_check_xdnd_event
   if (xev->xany.window != output_window && xev->xany.window != global->stage_xwindow)
     return FALSE;
 
-  if (xev->xany.type == ClientMessage && xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndPosition"))
+  if (xev->xany.type == ClientMessage &&
+      xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndPosition"))
+    {
+      /* Store the timestamp of the xdnd position event */
+      global->xdnd_timestamp = xev->xclient.data.l[3];
+
+      g_signal_emit_by_name (G_OBJECT (global), "xdnd-position-changed",
+                            (int)(xev->xclient.data.l[2] >> 16),
+                            (int)(xev->xclient.data.l[2] & 0xFFFF));
+
+      global->xdnd_timestamp = 0;
+
+      return TRUE;
+    }
+  else if (xev->xany.type == ClientMessage &&
+           xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndLeave"))
+    {
+      g_signal_emit_by_name (G_OBJECT (global), "xdnd-leave");
+      global->xdnd_source = 0;
+
+      return TRUE;
+    }
+  else if (xev->xany.type == ClientMessage &&
+           xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndDrop"))
     {
       XEvent xevent;
-      Window src = xev->xclient.data.l[0];
+
+      global->xdnd_timestamp = xev->xclient.data.l[2];
 
       memset (&xevent, 0, sizeof(xevent));
       xevent.xany.type = ClientMessage;
       xevent.xany.display = global->xdisplay;
-      xevent.xclient.window = src;
-      xevent.xclient.message_type = gdk_x11_get_xatom_by_name ("XdndStatus");
+      xevent.xclient.window = xev->xclient.data.l[0];
+      xevent.xclient.message_type = gdk_x11_get_xatom_by_name ("XdndFinished");
       xevent.xclient.format = 32;
       xevent.xclient.data.l[0] = output_window;
-      /* flags: bit 0: will we accept the drop? bit 1: do we want more position messages */
-      xevent.xclient.data.l[1] = 2;
-      xevent.xclient.data.l[4] = None;
+      xevent.xclient.data.l[2] = global->xdnd_action;
 
-      XSendEvent (global->xdisplay, src, False, 0, &xevent);
+      if (global->xdnd_action != None)
+          xevent.xclient.data.l[1] = 1;
+
+      g_signal_emit_by_name (G_OBJECT (global), "xdnd-drop");
+      XSendEvent (global->xdisplay, xev->xclient.data.l[0], False, 0, &xevent);
 
-      /* Store the timestamp of the xdnd position event */
-      global->xdnd_timestamp = xev->xclient.data.l[3];
-      g_signal_emit_by_name (G_OBJECT (global), "xdnd-position-changed",
-                            (int)(xev->xclient.data.l[2] >> 16), (int)(xev->xclient.data.l[2] & 0xFFFF));
       global->xdnd_timestamp = 0;
+      global->xdnd_source = 0;
 
       return TRUE;
     }
-   else if (xev->xany.type == ClientMessage && xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndLeave"))
+  else if (xev->xany.type == ClientMessage &&
+           xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndEnter"))
     {
-      g_signal_emit_by_name (G_OBJECT (global), "xdnd-leave");
+      gchar **mime_types;
+
+      global->xdnd_action = None;
+      global->xdnd_source = xev->xclient.data.l[0];
+
+      mime_types = _shell_global_get_xdnd_mime_types (global, &xev->xclient);
+      g_signal_emit_by_name (G_OBJECT (global), "xdnd-enter", mime_types);
+
+      /** Strings owned by GDK, free only the array. */
+      g_free(mime_types);
 
       return TRUE;
     }
-   else if (xev->xany.type == ClientMessage && xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndEnter"))
+  else if (xev->xany.type == SelectionNotify &&
+           xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndSelection") &&
+           xev->xselection.property == gdk_x11_get_xatom_by_name ("GnomeShellXdndSelection"))
     {
-      g_signal_emit_by_name (G_OBJECT (global), "xdnd-enter");
+      const gchar *mime_type = gdk_x11_get_xatom_name (xev->xselection.target);
+      GString *data = _shell_global_get_xdnd_selection_data (global, xev->xselection.target);
+
+      global->xdnd_timestamp = xev->xselection.time;
+      g_signal_emit_by_name (G_OBJECT (global), "xdnd-data", mime_type, data->str);
+      global->xdnd_timestamp = 0;
+
+      g_string_free (data, TRUE);
 
       return TRUE;
     }
@@ -1772,6 +1949,72 @@ gboolean _shell_global_check_xdnd_event
     return FALSE;
 }
 
+/**
+ * shell_global_set_xdnd_drag_status:
+ * @global: the #ShellGlobal
+ * @status: the drag status
+ *
+ * Informs the drag source about whether or not we accept the dragged items.
+ */
+void
+shell_global_set_xdnd_drag_status (ShellGlobal         *global,
+                                   ShellXdndDragStatus  status)
+{
+  Window output_window = meta_get_overlay_window (global->meta_screen);
+  XEvent xevent;
+
+  g_return_if_fail (global->xdnd_source != 0);
+  g_return_if_fail (global->xdnd_timestamp != 0);
+
+  memset (&xevent, 0, sizeof(xevent));
+  xevent.xany.type = ClientMessage;
+  xevent.xany.display = global->xdisplay;
+  xevent.xclient.window = global->xdnd_source;
+  xevent.xclient.message_type = gdk_x11_get_xatom_by_name ("XdndStatus");
+  xevent.xclient.format = 32;
+  xevent.xclient.data.l[0] = output_window;
+
+  switch (status)
+    {
+    case SHELL_XDND_DRAG_STATUS_COPY_DROP:
+      xevent.xclient.data.l[1] = 3;
+      global->xdnd_action = gdk_x11_get_xatom_by_name ("XdndActionCopy");
+      break;
+    case SHELL_XDND_DRAG_STATUS_MOVE_DROP:
+      xevent.xclient.data.l[1] = 3;
+      global->xdnd_action = gdk_x11_get_xatom_by_name ("XdndActionMove");
+      break;
+    default:
+      xevent.xclient.data.l[1] = 2;
+      global->xdnd_action = None;
+      break;
+    }
+
+  xevent.xclient.data.l[4] = global->xdnd_action;
+  XSendEvent (global->xdisplay, global->xdnd_source, False, 0, &xevent);
+}
+
+/**
+ * shell_global_request_xdnd_selection_data:
+ * @global: the #ShellGlobal
+ * @mime_type: the mime type of the data to request
+ *
+ * Requests information about a dragged item.
+ */
+void
+shell_global_request_xdnd_selection_data (ShellGlobal *global,
+                                          gchar       *mime_type)
+{
+  g_return_if_fail (global->xdnd_timestamp != 0);
+
+  XConvertSelection (global->xdisplay,
+                     gdk_x11_get_xatom_by_name ("XdndSelection"),
+                     gdk_x11_get_xatom_by_name (mime_type),
+                     gdk_x11_get_xatom_by_name ("GnomeShellXdndSelection"),
+                     global->stage_xwindow,
+                     global->xdnd_timestamp);
+}
+
 const char *
 shell_global_get_session_mode (ShellGlobal *global)
 {
diff -rupN a/src/shell-global.h b/src/shell-global.h
--- a/src/shell-global.h	2013-03-14 20:02:39.000000000 +0100
+++ b/src/shell-global.h	2013-03-21 19:19:17.706928457 +0100
@@ -105,6 +105,20 @@ void shell_global_run_at_leisure (ShellG
                                   gpointer              user_data,
                                   GDestroyNotify        notify);
 
+/* XDnD API */
+typedef enum {
+  SHELL_XDND_DRAG_STATUS_NO_DROP,
+  SHELL_XDND_DRAG_STATUS_COPY_DROP,
+  SHELL_XDND_DRAG_STATUS_MOVE_DROP,
+} ShellXdndDragStatus;
+
+void    shell_global_init_xdnd                   (ShellGlobal         *global);
+
+void    shell_global_set_xdnd_drag_status        (ShellGlobal         *global,
+                                                  ShellXdndDragStatus  status);
+
+void    shell_global_request_xdnd_selection_data (ShellGlobal         *global,
+                                                  gchar               *mime_type);
 
 /* Misc utilities / Shell API */
 void     shell_global_sync_pointer              (ShellGlobal  *global);
@@ -144,8 +158,6 @@ void     shell_global_notify_error
                                                  const char   *msg,
                                                  const char   *details);
 
-void     shell_global_init_xdnd                 (ShellGlobal  *global);
-
 void     shell_global_reexec_self               (ShellGlobal  *global);
 
 const char *     shell_global_get_session_mode  (ShellGlobal  *global);
diff -rupN a/src/shell-global-private.h b/src/shell-global-private.h
--- a/src/shell-global-private.h	2012-10-12 21:19:13.000000000 +0200
+++ b/src/shell-global-private.h	2013-03-21 19:19:11.235694765 +0100
@@ -16,4 +16,10 @@ GjsContext *_shell_global_get_gjs_contex
 gboolean _shell_global_check_xdnd_event (ShellGlobal  *global,
                                          XEvent       *xev);
 
+gchar **_shell_global_get_xdnd_mime_types (ShellGlobal         *global,
+                                           XClientMessageEvent *xclient);
+
+GString *_shell_global_get_xdnd_selection_data (ShellGlobal *global,
+                                                Atom target);
+
 #endif /* __SHELL_GLOBAL_PRIVATE_H__ */
