[gnome-shell] Implement cross overview drag & drop



commit ceedc7e32c4f993574378ddc532465ef2729337a
Author: Adel Gadllah <adel gadllah gmail com>
Date:   Wed Jan 5 15:47:27 2011 +0100

    Implement cross overview drag & drop
    
    The gnome-panel allows the user to hover over a tasklist entry
    while draging to activate a minimized or obscured window and drop onto it.
    
    Implement a similar behaviour by allowing draging to the activities button or
    the hotcorner (and thus opening the overview), which allows the user to
    activate any window (even on different workspaces) as a drop target.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=601731

 js/Makefile.am             |    3 +-
 js/ui/main.js              |    3 +
 js/ui/overview.js          |   73 ++++++++++++++++++++++
 js/ui/panel.js             |   68 ++++++++++++++++++---
 js/ui/workspacesView.js    |    3 +
 js/ui/xdndHandler.js       |  130 +++++++++++++++++++++++++++++++++++++++
 src/gnome-shell-plugin.c   |   19 ++++--
 src/shell-global-private.h |    2 +
 src/shell-global.c         |  145 ++++++++++++++++++++++++++++++++++++++++++++
 src/shell-global.h         |    2 +
 src/shell-marshal.list     |    1 +
 11 files changed, 433 insertions(+), 16 deletions(-)
---
diff --git a/js/Makefile.am b/js/Makefile.am
index 555be7b..49de2ff 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -56,4 +56,5 @@ nobase_dist_js_DATA = 	\
 	ui/windowManager.js	\
 	ui/workspace.js		\
 	ui/workspacesView.js	\
-	ui/workspaceSwitcherPopup.js
+	ui/workspaceSwitcherPopup.js    \
+	ui/xdndHandler.js
diff --git a/js/ui/main.js b/js/ui/main.js
index 4a8f8ef..e08781b 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -37,6 +37,7 @@ const ShellDBus = imports.ui.shellDBus;
 const TelepathyClient = imports.ui.telepathyClient;
 const WindowManager = imports.ui.windowManager;
 const Magnifier = imports.ui.magnifier;
+const XdndHandler = imports.ui.xdndHandler;
 const StatusIconDispatcher = imports.ui.statusIconDispatcher;
 
 const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
@@ -60,6 +61,7 @@ let modalCount = 0;
 let modalActorFocusStack = [];
 let uiGroup = null;
 let magnifier = null;
+let xdndHandler = null;
 let statusIconDispatcher = null;
 let _errorLogStack = [];
 let _startDate;
@@ -129,6 +131,7 @@ function start() {
     global.stage.add_actor(uiGroup);
 
     placesManager = new PlaceDisplay.PlacesManager();
+    xdndHandler = new XdndHandler.XdndHandler();
     overview = new Overview.Overview();
     chrome = new Chrome.Chrome();
     magnifier = new Magnifier.Magnifier();
diff --git a/js/ui/overview.js b/js/ui/overview.js
index 56da40a..bfabcb3 100644
--- a/js/ui/overview.js
+++ b/js/ui/overview.js
@@ -9,9 +9,11 @@ const St = imports.gi.St;
 const Shell = imports.gi.Shell;
 const Gettext = imports.gettext.domain('gnome-shell');
 const _ = Gettext.gettext;
+const Gdk = imports.gi.Gdk;
 
 const AppDisplay = imports.ui.appDisplay;
 const Dash = imports.ui.dash;
+const DND = imports.ui.dnd;
 const DocDisplay = imports.ui.docDisplay;
 const GenericDisplay = imports.ui.genericDisplay;
 const Lightbox = imports.ui.lightbox;
@@ -30,6 +32,8 @@ const ANIMATION_TIME = 0.25;
 const DASH_SPLIT_FRACTION = 0.1;
 
 
+const DND_WINDOW_SWITCH_TIMEOUT = 1250;
+
 function Source() {
     this._init();
 }
@@ -178,9 +182,72 @@ Overview.prototype = {
 
         this._coverPane.lower_bottom();
 
+        // XDND
+        this._dragMonitor = {
+            dragMotion: Lang.bind(this, this._onDragMotion)
+        };
+
+        Main.xdndHandler.connect('drag-begin', Lang.bind(this, this._onDragBegin));
+        Main.xdndHandler.connect('drag-end', Lang.bind(this, this._onDragEnd));
+
+        this._windowSwitchTimeoutId = 0;
+        this._windowSwitchTimestamp = 0;
+        this._lastActiveWorkspaceIndex = -1;
+        this._needsFakePointerEvent = false;
+
         this.workspaces = null;
     },
 
+    _onDragBegin: function() {
+        DND.addDragMonitor(this._dragMonitor);
+        // Remember the workspace we started from
+        this._lastActiveWorkspaceIndex = global.screen.get_active_workspace_index();
+    },
+
+    _onDragEnd: function(time) {
+        // In case the drag was canceled while in the overview
+        // we have to go back to where we started and hide
+        // the overview
+        if (this._shownTemporarily)  {
+            global.screen.get_workspace_by_index(this._lastActiveWorkspaceIndex).activate(time);
+            this.hideTemporarily();
+        }
+
+        DND.removeMonitor(this._dragMonitor);
+    },
+
+    _fakePointerEvent: function() {
+        let display = Gdk.Display.get_default();
+        let deviceManager = display.get_device_manager();
+        let pointer = deviceManager.get_client_pointer();
+        let [screen, pointerX, pointerY] = display.get_device_state(pointer);
+
+        display.warp_device(pointer, screen, pointerX, pointerY);
+    },
+
+    _onDragMotion: function(dragEvent) {
+        if (this._windowSwitchTimeoutId != 0) {
+            Mainloop.source_remove(this._windowSwitchTimeoutId);
+            this._windowSwitchTimeoutId = 0;
+            this._needsFakePointerEvent = false;
+        }
+
+        if (dragEvent.targetActor &&
+            dragEvent.targetActor._delegate &&
+            dragEvent.targetActor._delegate.metaWindow) {
+            this._windowSwitchTimestamp = global.get_current_time();
+            this._windowSwitchTimeoutId = Mainloop.timeout_add(DND_WINDOW_SWITCH_TIMEOUT,
+                                            Lang.bind(this, function() {
+                                                this._needsFakePointerEvent = true;
+                                                Main.activateWindow(dragEvent.targetActor._delegate.metaWindow,
+                                                                    this._windowSwitchTimestamp);
+                                                this.hideTemporarily();
+                                            }));
+        }
+
+        return DND.DragMotionResult.CONTINUE;
+    },
+
     _getDesktopClone: function() {
         let windows = global.get_window_actors().filter(function(w) {
             return w.meta_window.get_window_type() == Meta.WindowType.DESKTOP;
@@ -531,6 +598,12 @@ Overview.prototype = {
             this._animateVisible();
 
         this._syncInputMode();
+
+        // Fake a pointer event if requested
+        if (this._needsFakePointerEvent) {
+            this._fakePointerEvent();
+            this._needsFakePointerEvent = false;
+        }
     }
 };
 Signals.addSignalMethods(Overview.prototype);
diff --git a/js/ui/panel.js b/js/ui/panel.js
index 4439d3d..4fe60a9 100644
--- a/js/ui/panel.js
+++ b/js/ui/panel.js
@@ -26,6 +26,8 @@ const PANEL_ICON_SIZE = 24;
 
 const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5;
 
+const BUTTON_DND_ACTIVATION_TIMEOUT = 250;
+
 const ANIMATED_ICON_UPDATE_TIMEOUT = 100;
 const SPINNER_UPDATE_TIMEOUT = 130;
 const SPINNER_SPEED = 0.02;
@@ -728,7 +730,20 @@ Panel.prototype = {
                                          reactive: true,
                                          can_focus: true });
         this.button.set_child(label);
-
+        this.button._delegate = this.button;
+        this.button._xdndTimeOut = 0;
+        this.button.handleDragOver = Lang.bind(this,
+            function(source, actor, x, y, time) {
+                 if (source == Main.xdndHandler) {
+                    if (this.button._xdndTimeOut != 0)
+                        Mainloop.source_remove(this.button._xdndTimeOut);
+                    this.button._xdndTimeOut = Mainloop.timeout_add(BUTTON_DND_ACTIVATION_TIMEOUT,
+                                                                    Lang.bind(this,
+                                                                                function() {
+                                                                                    this._xdndShowOverview(actor);
+                                                                                }));
+                 }
+            });
         this._leftBox.add(this.button);
 
         // We use this flag to mark the case where the user has entered the
@@ -766,6 +781,18 @@ Panel.prototype = {
         this._hotCorner.connect('leave-event',
                                 Lang.bind(this, this._onHotCornerLeft));
 
+        this._hotCorner._delegate = this._hotCorner;
+        this._hotCorner.handleDragOver = Lang.bind(this,
+            function(source, actor, x, y, time) {
+                 if (source == Main.xdndHandler) {
+                    if(!Main.overview.visible && !Main.overview.animationInProgress) {
+                        this.rippleAnimation();
+                        Main.overview.showTemporarily();
+                        Main.overview.beginItemDrag(actor);
+                    }
+                 }
+            });
+
         this._boxContainer.add_actor(this._hotCornerEnvirons);
         this._boxContainer.add_actor(this._hotCorner);
 
@@ -821,6 +848,25 @@ Panel.prototype = {
         Main.chrome.addActor(this.actor, { visibleInOverview: true });
     },
 
+    _xdndShowOverview: function (actor) {
+        let [x, y, mask] = global.get_pointer();
+        let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
+
+        if (pickedActor != this.button) {
+            Mainloop.source_remove(this.button._xdndTimeOut);
+            this.button._xdndTimeOut = 0;
+            return;
+        }
+
+        if(!Main.overview.visible && !Main.overview.animationInProgress) {
+            Main.overview.showTemporarily();
+            Main.overview.beginItemDrag(actor);
+        }
+
+        Mainloop.source_remove(this.button._xdndTimeOut);
+        this.button._xdndTimeOut = 0;
+    },
+
     startStatusArea: function() {
         for (let i = 0; i < STANDARD_TRAY_ICON_ORDER.length; i++) {
             let role = STANDARD_TRAY_ICON_ORDER[i];
@@ -915,6 +961,17 @@ Panel.prototype = {
         Main.uiGroup.add_actor(ripple);
     },
 
+    rippleAnimation: function() {
+        // Show three concentric ripples expanding outwards; the exact
+        // parameters were found by trial and error, so don't look
+        // for them to make perfect sense mathematically
+
+        //              delay  time  scale opacity => scale opacity
+        this._addRipple(0.0,   0.83,  0.25,  1.0,    1.5,  0.0);
+        this._addRipple(0.05,  1.0,   0.0,   0.7,    1.25, 0.0);
+        this._addRipple(0.35,  1.0,   0.0,   0.3,    1,    0.0);
+    },
+
     _onHotCornerEntered : function() {
         if (this._menus.grabbed)
             return false;
@@ -923,14 +980,7 @@ Panel.prototype = {
             if (!Main.overview.animationInProgress) {
                 this._hotCornerActivationTime = Date.now() / 1000;
 
-                // Show three concentric ripples expanding outwards; the exact
-                // parameters were found by trial and error, so don't look
-                // for them to make perfect sense mathematically
-
-                //              delay  time  scale opacity => scale opacity
-                this._addRipple(0.0,   0.83,  0.25,  1.0,    1.5,  0.0);
-                this._addRipple(0.05,  1.0,   0.0,   0.7,    1.25, 0.0);
-                this._addRipple(0.35,  1.0,   0.0,   0.3,    1,    0.0);
+                this.rippleAnimation();
                 Main.overview.toggle();
             }
         }
diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js
index 678a9e0..7478003 100644
--- a/js/ui/workspacesView.js
+++ b/js/ui/workspacesView.js
@@ -644,6 +644,9 @@ WorkspacesView.prototype = {
     },
 
     _onDragMotion: function(dragEvent) {
+        if (Main.overview.animationInProgress)
+             return DND.DragMotionResult.CONTINUE;
+
         let primary = global.get_primary_monitor();
 
         let activeWorkspaceIndex = global.screen.get_active_workspace_index();
diff --git a/js/ui/xdndHandler.js b/js/ui/xdndHandler.js
new file mode 100644
index 0000000..0275559
--- /dev/null
+++ b/js/ui/xdndHandler.js
@@ -0,0 +1,130 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Clutter = imports.gi.Clutter;
+const Lang = imports.lang;
+const Shell = imports.gi.Shell;
+const Signals = imports.signals;
+const Mainloop = imports.mainloop;
+const DND = imports.ui.dnd;
+
+function XdndHandler() {
+    this._init();
+}
+
+XdndHandler.prototype = {
+    _init: function() {
+        // Used to display a clone of the cursor window when the
+        // window group is hidden (like it happens in the overview)
+        this._cursorWindowClone = null;
+
+        // Used as a drag actor in case we don't have a cursor window clone
+        this._dummy = new Clutter.Rectangle({ width: 1, height: 1, opacity: 0 });
+        global.stage.add_actor(this._dummy);
+        this._dummy.hide();
+
+        // Mutter delays the creation of the output window as long
+        // as possible to avoid flicker. In case a plugin wants to
+        // access it directly it has to connect to the stage's show
+        // signal. (see comment in compositor.c:meta_compositor_manage_screen)
+        global.stage.connect('show', function () {
+                                        global.init_xdnd();
+                                        return false;
+                                      });
+
+        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));
+
+        this._windowGroupVisibilityHandlerId = 0;
+    },
+
+    // Called when the user cancels the drag (i.e release the button)
+    _onLeave: function() {
+        if (this._windowGroupVisibilityHandlerId != 0) {
+            Mainloop.source_remove(this._windowGroupVisibilityHandlerId);
+            this._windowGroupVisibilityHandlerId = 0;
+        }
+        this.emit('drag-end');
+    },
+
+    _onEnter: function() {
+        this._windowGroupVisibilityHandlerId  =
+                global.window_group.connect('notify::visible',
+                    Lang.bind(this, this._onWindowGroupVisibilityChanged));
+
+        this.emit('drag-begin', global.get_current_time());
+    },
+
+    _onWindowGroupVisibilityChanged: function() {
+        if (!global.window_group.visible) {
+            if (this._cursorWindowClone)
+                return;
+
+            let windows = global.get_window_actors();
+            let cursorWindow = windows[windows.length - 1];
+
+            // FIXME: more reliable way?
+            if (!cursorWindow.is_override_redirect())
+                return;
+
+            let constraint_x = new Clutter.BindConstraint({ coordinate : Clutter.BindCoordinate.X,
+                                                             source: cursorWindow});
+            let constraint_y = new Clutter.BindConstraint({ coordinate : Clutter.BindCoordinate.Y,
+                                                             source: cursorWindow});
+
+            this._cursorWindowClone = new Clutter.Clone({ source: cursorWindow });
+            global.overlay_group.add_actor(this._cursorWindowClone);
+            Shell.util_set_hidden_from_pick(this._cursorWindowClone, true);
+
+            // Make sure that the clone has the same position as the source
+            this._cursorWindowClone.add_constraint(constraint_x);
+            this._cursorWindowClone.add_constraint(constraint_y);
+        } else {
+            if (this._cursorWindowClone)
+            {
+                this._cursorWindowClone.destroy();
+                this._cursorWindowClone = null;
+            }
+        }
+    },
+
+    _onPositionChanged: function(obj, x, y) {
+        let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
+
+        // Make sure that the cursor window is on top
+        if (this._cursorWindowClone)
+             this._cursorWindowClone.raise_top();
+
+        let dragEvent = {
+            x: x,
+            y: y,
+            dragActor: this._cursorWindowClone ? this._cursorWindowClone : this._dummy,
+            source: this,
+            targetActor: pickedActor
+        };
+
+        for (let i = 0; i < DND.dragMonitors.length; i++) {
+            let motionFunc = DND.dragMonitors[i].dragMotion;
+            if (motionFunc) {
+                let result = motionFunc(dragEvent);
+                if (result != DND.DragMotionResult.CONTINUE)
+                    return;
+            }
+        }
+
+        while (pickedActor) {
+                if (pickedActor._delegate && pickedActor._delegate.handleDragOver) {
+                    let result = pickedActor._delegate.handleDragOver(this,
+                                                                      dragEvent.dragActor,
+                                                                      x,
+                                                                      y,
+                                                                      global.get_current_time());
+                    if (result != DND.DragMotionResult.CONTINUE)
+                        return;
+                }
+                pickedActor = pickedActor.get_parent();
+        }
+    }
+}
+
+Signals.addSignalMethods(XdndHandler.prototype);
diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c
index ec23bf3..50e2c5c 100644
--- a/src/gnome-shell-plugin.c
+++ b/src/gnome-shell-plugin.c
@@ -113,6 +113,8 @@ struct _GnomeShellPlugin
   int glx_error_base;
   int glx_event_base;
   guint have_swap_event : 1;
+
+  ShellGlobal *global;
 };
 
 struct _GnomeShellPluginClass
@@ -320,7 +322,6 @@ gnome_shell_plugin_start (MetaPlugin *plugin)
   int status;
   const char *shell_js;
   char **search_path;
-  ShellGlobal *global;
   const char *glx_extensions;
 
   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
@@ -379,10 +380,10 @@ gnome_shell_plugin_start (MetaPlugin *plugin)
                      gvc_muted_debug_log_handler, NULL);
 
   /* Initialize the global object here. */
-  global = shell_global_get ();
+  shell_plugin->global = shell_global_get ();
 
-  _shell_global_set_plugin (global, META_PLUGIN(shell_plugin));
-  _shell_global_set_gjs_context (global, shell_plugin->gjs_context);
+  _shell_global_set_plugin (shell_plugin->global, META_PLUGIN(shell_plugin));
+  _shell_global_set_gjs_context (shell_plugin->global, shell_plugin->gjs_context);
 
   add_statistics (shell_plugin);
 
@@ -511,9 +512,9 @@ static gboolean
 gnome_shell_plugin_xevent_filter (MetaPlugin *plugin,
                                   XEvent     *xev)
 {
-#ifdef GLX_INTEL_swap_event
-  GnomeShellPlugin *shell_plugin = GNOME_SHELL_PLUGIN (plugin);
 
+  GnomeShellPlugin *shell_plugin = GNOME_SHELL_PLUGIN (plugin);
+#ifdef GLX_INTEL_swap_event
   if (shell_plugin->have_swap_event &&
       xev->type == (shell_plugin->glx_event_base + GLX_BufferSwapComplete))
     {
@@ -545,6 +546,12 @@ gnome_shell_plugin_xevent_filter (MetaPlugin *plugin,
       && xev->xcrossing.window == clutter_x11_get_stage_window (CLUTTER_STAGE (clutter_stage_get_default ())))
     return TRUE;
 
+  /*
+   * Pass the event to shell-global
+   */
+  if (_shell_global_check_xdnd_event (shell_plugin->global, xev))
+    return TRUE;
+
   return clutter_x11_handle_event (xev) != CLUTTER_X11_FILTER_CONTINUE;
 }
 
diff --git a/src/shell-global-private.h b/src/shell-global-private.h
index 201ace8..e175145 100644
--- a/src/shell-global-private.h
+++ b/src/shell-global-private.h
@@ -11,4 +11,6 @@ void _shell_global_set_plugin      (ShellGlobal  *global,
 void _shell_global_set_gjs_context (ShellGlobal  *global,
                                     GjsContext   *context);
 
+gboolean _shell_global_check_xdnd_event (ShellGlobal  *global,
+                                         XEvent       *xev);
 #endif /* __SHELL_GLOBAL_PRIVATE_H__ */
diff --git a/src/shell-global.c b/src/shell-global.c
index 2cf6ec6..4c3246f 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -6,6 +6,7 @@
 #include "shell-enum-types.h"
 #include "shell-perf-log.h"
 #include "shell-window-tracker.h"
+#include "shell-marshal.h"
 #include "shell-wm.h"
 #include "st.h"
 
@@ -70,6 +71,8 @@ struct _ShellGlobal {
 
   /* For sound notifications */
   ca_context *sound_context;
+
+  guint32 xdnd_timestamp;
 };
 
 enum {
@@ -92,8 +95,19 @@ enum {
   PROP_FOCUS_MANAGER,
 };
 
+/* Signals */
+enum
+{
+ XDND_POSITION_CHANGED,
+ XDND_LEAVE,
+ XDND_ENTER,
+ LAST_SIGNAL
+};
+
 G_DEFINE_TYPE(ShellGlobal, shell_global, G_TYPE_OBJECT);
 
+static guint shell_global_signals [LAST_SIGNAL] = { 0 };
+
 static void
 shell_global_set_property(GObject         *object,
                           guint            prop_id,
@@ -238,6 +252,36 @@ shell_global_class_init (ShellGlobalClass *klass)
   gobject_class->get_property = shell_global_get_property;
   gobject_class->set_property = shell_global_set_property;
 
+  /* Emitted from gnome-shell-plugin.c during event handling */
+  shell_global_signals[XDND_POSITION_CHANGED] =
+      g_signal_new ("xdnd-position-changed",
+                    G_TYPE_FROM_CLASS (klass),
+                    G_SIGNAL_RUN_LAST,
+                    0,
+                    NULL, NULL,
+                    _shell_marshal_VOID__INT_INT,
+                    G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
+
+  /* Emitted from gnome-shell-plugin.c during event handling */
+  shell_global_signals[XDND_LEAVE] =
+      g_signal_new ("xdnd-leave",
+                    G_TYPE_FROM_CLASS (klass),
+                    G_SIGNAL_RUN_LAST,
+                    0,
+                    NULL, NULL,
+                    g_cclosure_marshal_VOID__VOID,
+                    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,
+                    g_cclosure_marshal_VOID__VOID,
+                    G_TYPE_NONE, 0);
+
   g_object_class_install_property (gobject_class,
                                    PROP_OVERLAY_GROUP,
                                    g_param_spec_object ("overlay-group",
@@ -1131,6 +1175,39 @@ grab_notify (GtkWidget *widget, gboolean was_grabbed, gpointer user_data)
 }
 
 /**
+ * shell_global_init_xdnd:
+ * @global: the #ShellGlobal
+ *
+ * Enables tracking of Xdnd events
+ */
+void shell_global_init_xdnd (ShellGlobal *global)
+{
+  long xdnd_version = 5;
+
+  MetaScreen *screen = shell_global_get_screen (global);
+  Window output_window = meta_get_overlay_window (screen);
+
+  MetaDisplay *display = meta_screen_get_display (screen);
+  Display *xdisplay = meta_display_get_xdisplay (display);
+
+  ClutterStage *stage = CLUTTER_STAGE(meta_plugin_get_stage (global->plugin));
+  Window stage_win = clutter_x11_get_stage_window (stage);
+
+  XChangeProperty (xdisplay, stage_win, gdk_x11_get_xatom_by_name ("XdndAware"), XA_ATOM,
+                  32, PropModeReplace, (const unsigned char *)&xdnd_version, 1);
+
+  XChangeProperty (xdisplay, output_window, gdk_x11_get_xatom_by_name ("XdndProxy"), XA_WINDOW,
+                  32, PropModeReplace, (const unsigned char *)&stage_win, 1);
+
+  /*
+   * XdndProxy is additionally set on the proxy window as verification that the
+   * XdndProxy property on the target window isn't a left-over
+   */
+  XChangeProperty (xdisplay, stage_win, gdk_x11_get_xatom_by_name ("XdndProxy"), XA_WINDOW,
+                  32, PropModeReplace, (const unsigned char *)&stage_win, 1);
+}
+
+/**
  * shell_global_format_time_relative_pretty:
  * @global:
  * @delta: Time in seconds since the current time
@@ -1422,6 +1499,10 @@ shell_global_get_current_time (ShellGlobal *global)
   guint32 time;
   MetaDisplay *display;
 
+  /* In case we have a xdnd timestamp use it */
+  if (global->xdnd_timestamp != 0)
+    return global->xdnd_timestamp;
+
   /* meta_display_get_current_time() will return the correct time
      when handling an X or Gdk event, but will return CurrentTime
      from some Clutter event callbacks.
@@ -1689,3 +1770,67 @@ shell_global_play_theme_sound (ShellGlobal *global,
 {
   ca_context_play (global->sound_context, 0, CA_PROP_EVENT_ID, name, NULL);
 }
+
+/*
+ * Process Xdnd events
+ *
+ * We pass the position and leave events to JS via a signal
+ * where the actual drag & drop handling happens.
+ *
+ * http://www.freedesktop.org/wiki/Specifications/XDND
+ */
+gboolean _shell_global_check_xdnd_event (ShellGlobal  *global,
+                                         XEvent       *xev)
+{
+  MetaScreen *screen = meta_plugin_get_screen (global->plugin);
+  Window output_window = meta_get_overlay_window (screen);
+  MetaDisplay *display = meta_screen_get_display (screen);
+  Display *xdisplay = meta_display_get_xdisplay (display);
+
+  ClutterStage *stage = CLUTTER_STAGE (meta_plugin_get_stage (global->plugin));
+  Window stage_win = clutter_x11_get_stage_window (stage);
+
+  if (xev->xany.window != output_window && xev->xany.window != stage_win)
+    return FALSE;
+
+  if (xev->xany.type == ClientMessage && xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndPosition"))
+    {
+      XEvent xevent;
+      Window src = xev->xclient.data.l[0];
+
+      memset (&xevent, 0, sizeof(xevent));
+      xevent.xany.type = ClientMessage;
+      xevent.xany.display = xdisplay;
+      xevent.xclient.window = src;
+      xevent.xclient.message_type = gdk_x11_get_xatom_by_name ("XdndStatus");
+      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;
+
+      XSendEvent (xdisplay, src, 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;
+
+      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");
+
+      return TRUE;
+    }
+   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-enter");
+
+      return TRUE;
+    }
+
+    return FALSE;
+}
diff --git a/src/shell-global.h b/src/shell-global.h
index 91a82c4..4a40bcd 100644
--- a/src/shell-global.h
+++ b/src/shell-global.h
@@ -132,6 +132,8 @@ void shell_global_run_at_leisure (ShellGlobal         *global,
 void shell_global_play_theme_sound (ShellGlobal       *global,
                                     const char        *name);
 
+void shell_global_init_xdnd (ShellGlobal *global);
+
 G_END_DECLS
 
 #endif /* __SHELL_GLOBAL_H__ */
diff --git a/src/shell-marshal.list b/src/shell-marshal.list
index 588693d..34d1078 100644
--- a/src/shell-marshal.list
+++ b/src/shell-marshal.list
@@ -4,3 +4,4 @@ VOID:BOXED
 VOID:BOXED,OBJECT
 VOID:OBJECT,OBJECT
 VOID:STRING,OBJECT,BOOLEAN
+VOID:INT,INT



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]