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
This commit is contained in:
Adel Gadllah 2011-01-05 15:47:27 +01:00
parent 62507c9759
commit ceedc7e32c
11 changed files with 433 additions and 16 deletions

View File

@ -56,4 +56,5 @@ nobase_dist_js_DATA = \
ui/windowManager.js \ ui/windowManager.js \
ui/workspace.js \ ui/workspace.js \
ui/workspacesView.js \ ui/workspacesView.js \
ui/workspaceSwitcherPopup.js ui/workspaceSwitcherPopup.js \
ui/xdndHandler.js

View File

@ -37,6 +37,7 @@ const ShellDBus = imports.ui.shellDBus;
const TelepathyClient = imports.ui.telepathyClient; const TelepathyClient = imports.ui.telepathyClient;
const WindowManager = imports.ui.windowManager; const WindowManager = imports.ui.windowManager;
const Magnifier = imports.ui.magnifier; const Magnifier = imports.ui.magnifier;
const XdndHandler = imports.ui.xdndHandler;
const StatusIconDispatcher = imports.ui.statusIconDispatcher; const StatusIconDispatcher = imports.ui.statusIconDispatcher;
const DEFAULT_BACKGROUND_COLOR = new Clutter.Color(); const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
@ -60,6 +61,7 @@ let modalCount = 0;
let modalActorFocusStack = []; let modalActorFocusStack = [];
let uiGroup = null; let uiGroup = null;
let magnifier = null; let magnifier = null;
let xdndHandler = null;
let statusIconDispatcher = null; let statusIconDispatcher = null;
let _errorLogStack = []; let _errorLogStack = [];
let _startDate; let _startDate;
@ -129,6 +131,7 @@ function start() {
global.stage.add_actor(uiGroup); global.stage.add_actor(uiGroup);
placesManager = new PlaceDisplay.PlacesManager(); placesManager = new PlaceDisplay.PlacesManager();
xdndHandler = new XdndHandler.XdndHandler();
overview = new Overview.Overview(); overview = new Overview.Overview();
chrome = new Chrome.Chrome(); chrome = new Chrome.Chrome();
magnifier = new Magnifier.Magnifier(); magnifier = new Magnifier.Magnifier();

View File

@ -9,9 +9,11 @@ const St = imports.gi.St;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const Gettext = imports.gettext.domain('gnome-shell'); const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext; const _ = Gettext.gettext;
const Gdk = imports.gi.Gdk;
const AppDisplay = imports.ui.appDisplay; const AppDisplay = imports.ui.appDisplay;
const Dash = imports.ui.dash; const Dash = imports.ui.dash;
const DND = imports.ui.dnd;
const DocDisplay = imports.ui.docDisplay; const DocDisplay = imports.ui.docDisplay;
const GenericDisplay = imports.ui.genericDisplay; const GenericDisplay = imports.ui.genericDisplay;
const Lightbox = imports.ui.lightbox; const Lightbox = imports.ui.lightbox;
@ -30,6 +32,8 @@ const ANIMATION_TIME = 0.25;
const DASH_SPLIT_FRACTION = 0.1; const DASH_SPLIT_FRACTION = 0.1;
const DND_WINDOW_SWITCH_TIMEOUT = 1250;
function Source() { function Source() {
this._init(); this._init();
} }
@ -178,9 +182,72 @@ Overview.prototype = {
this._coverPane.lower_bottom(); 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; 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() { _getDesktopClone: function() {
let windows = global.get_window_actors().filter(function(w) { let windows = global.get_window_actors().filter(function(w) {
return w.meta_window.get_window_type() == Meta.WindowType.DESKTOP; return w.meta_window.get_window_type() == Meta.WindowType.DESKTOP;
@ -531,6 +598,12 @@ Overview.prototype = {
this._animateVisible(); this._animateVisible();
this._syncInputMode(); this._syncInputMode();
// Fake a pointer event if requested
if (this._needsFakePointerEvent) {
this._fakePointerEvent();
this._needsFakePointerEvent = false;
}
} }
}; };
Signals.addSignalMethods(Overview.prototype); Signals.addSignalMethods(Overview.prototype);

View File

@ -26,6 +26,8 @@ const PANEL_ICON_SIZE = 24;
const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5; const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5;
const BUTTON_DND_ACTIVATION_TIMEOUT = 250;
const ANIMATED_ICON_UPDATE_TIMEOUT = 100; const ANIMATED_ICON_UPDATE_TIMEOUT = 100;
const SPINNER_UPDATE_TIMEOUT = 130; const SPINNER_UPDATE_TIMEOUT = 130;
const SPINNER_SPEED = 0.02; const SPINNER_SPEED = 0.02;
@ -728,7 +730,20 @@ Panel.prototype = {
reactive: true, reactive: true,
can_focus: true }); can_focus: true });
this.button.set_child(label); 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); this._leftBox.add(this.button);
// We use this flag to mark the case where the user has entered the // 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', this._hotCorner.connect('leave-event',
Lang.bind(this, this._onHotCornerLeft)); 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._hotCornerEnvirons);
this._boxContainer.add_actor(this._hotCorner); this._boxContainer.add_actor(this._hotCorner);
@ -821,6 +848,25 @@ Panel.prototype = {
Main.chrome.addActor(this.actor, { visibleInOverview: true }); 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() { startStatusArea: function() {
for (let i = 0; i < STANDARD_TRAY_ICON_ORDER.length; i++) { for (let i = 0; i < STANDARD_TRAY_ICON_ORDER.length; i++) {
let role = STANDARD_TRAY_ICON_ORDER[i]; let role = STANDARD_TRAY_ICON_ORDER[i];
@ -915,6 +961,17 @@ Panel.prototype = {
Main.uiGroup.add_actor(ripple); 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() { _onHotCornerEntered : function() {
if (this._menus.grabbed) if (this._menus.grabbed)
return false; return false;
@ -923,14 +980,7 @@ Panel.prototype = {
if (!Main.overview.animationInProgress) { if (!Main.overview.animationInProgress) {
this._hotCornerActivationTime = Date.now() / 1000; this._hotCornerActivationTime = Date.now() / 1000;
// Show three concentric ripples expanding outwards; the exact this.rippleAnimation();
// 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);
Main.overview.toggle(); Main.overview.toggle();
} }
} }

View File

@ -644,6 +644,9 @@ WorkspacesView.prototype = {
}, },
_onDragMotion: function(dragEvent) { _onDragMotion: function(dragEvent) {
if (Main.overview.animationInProgress)
return DND.DragMotionResult.CONTINUE;
let primary = global.get_primary_monitor(); let primary = global.get_primary_monitor();
let activeWorkspaceIndex = global.screen.get_active_workspace_index(); let activeWorkspaceIndex = global.screen.get_active_workspace_index();

130
js/ui/xdndHandler.js Normal file
View File

@ -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);

View File

@ -113,6 +113,8 @@ struct _GnomeShellPlugin
int glx_error_base; int glx_error_base;
int glx_event_base; int glx_event_base;
guint have_swap_event : 1; guint have_swap_event : 1;
ShellGlobal *global;
}; };
struct _GnomeShellPluginClass struct _GnomeShellPluginClass
@ -320,7 +322,6 @@ gnome_shell_plugin_start (MetaPlugin *plugin)
int status; int status;
const char *shell_js; const char *shell_js;
char **search_path; char **search_path;
ShellGlobal *global;
const char *glx_extensions; const char *glx_extensions;
bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
@ -379,10 +380,10 @@ gnome_shell_plugin_start (MetaPlugin *plugin)
gvc_muted_debug_log_handler, NULL); gvc_muted_debug_log_handler, NULL);
/* Initialize the global object here. */ /* 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_plugin (shell_plugin->global, META_PLUGIN(shell_plugin));
_shell_global_set_gjs_context (global, shell_plugin->gjs_context); _shell_global_set_gjs_context (shell_plugin->global, shell_plugin->gjs_context);
add_statistics (shell_plugin); add_statistics (shell_plugin);
@ -511,9 +512,9 @@ static gboolean
gnome_shell_plugin_xevent_filter (MetaPlugin *plugin, gnome_shell_plugin_xevent_filter (MetaPlugin *plugin,
XEvent *xev) 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 && if (shell_plugin->have_swap_event &&
xev->type == (shell_plugin->glx_event_base + GLX_BufferSwapComplete)) 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 ()))) && xev->xcrossing.window == clutter_x11_get_stage_window (CLUTTER_STAGE (clutter_stage_get_default ())))
return TRUE; 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; return clutter_x11_handle_event (xev) != CLUTTER_X11_FILTER_CONTINUE;
} }

View File

@ -11,4 +11,6 @@ void _shell_global_set_plugin (ShellGlobal *global,
void _shell_global_set_gjs_context (ShellGlobal *global, void _shell_global_set_gjs_context (ShellGlobal *global,
GjsContext *context); GjsContext *context);
gboolean _shell_global_check_xdnd_event (ShellGlobal *global,
XEvent *xev);
#endif /* __SHELL_GLOBAL_PRIVATE_H__ */ #endif /* __SHELL_GLOBAL_PRIVATE_H__ */

View File

@ -6,6 +6,7 @@
#include "shell-enum-types.h" #include "shell-enum-types.h"
#include "shell-perf-log.h" #include "shell-perf-log.h"
#include "shell-window-tracker.h" #include "shell-window-tracker.h"
#include "shell-marshal.h"
#include "shell-wm.h" #include "shell-wm.h"
#include "st.h" #include "st.h"
@ -70,6 +71,8 @@ struct _ShellGlobal {
/* For sound notifications */ /* For sound notifications */
ca_context *sound_context; ca_context *sound_context;
guint32 xdnd_timestamp;
}; };
enum { enum {
@ -92,8 +95,19 @@ enum {
PROP_FOCUS_MANAGER, PROP_FOCUS_MANAGER,
}; };
/* Signals */
enum
{
XDND_POSITION_CHANGED,
XDND_LEAVE,
XDND_ENTER,
LAST_SIGNAL
};
G_DEFINE_TYPE(ShellGlobal, shell_global, G_TYPE_OBJECT); G_DEFINE_TYPE(ShellGlobal, shell_global, G_TYPE_OBJECT);
static guint shell_global_signals [LAST_SIGNAL] = { 0 };
static void static void
shell_global_set_property(GObject *object, shell_global_set_property(GObject *object,
guint prop_id, guint prop_id,
@ -238,6 +252,36 @@ shell_global_class_init (ShellGlobalClass *klass)
gobject_class->get_property = shell_global_get_property; gobject_class->get_property = shell_global_get_property;
gobject_class->set_property = shell_global_set_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, g_object_class_install_property (gobject_class,
PROP_OVERLAY_GROUP, PROP_OVERLAY_GROUP,
g_param_spec_object ("overlay-group", g_param_spec_object ("overlay-group",
@ -1130,6 +1174,39 @@ grab_notify (GtkWidget *widget, gboolean was_grabbed, gpointer user_data)
shell_global_set_stage_input_mode (global, global->input_mode); shell_global_set_stage_input_mode (global, global->input_mode);
} }
/**
* 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: * shell_global_format_time_relative_pretty:
* @global: * @global:
@ -1422,6 +1499,10 @@ shell_global_get_current_time (ShellGlobal *global)
guint32 time; guint32 time;
MetaDisplay *display; 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 /* meta_display_get_current_time() will return the correct time
when handling an X or Gdk event, but will return CurrentTime when handling an X or Gdk event, but will return CurrentTime
from some Clutter event callbacks. 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); 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;
}

View File

@ -132,6 +132,8 @@ void shell_global_run_at_leisure (ShellGlobal *global,
void shell_global_play_theme_sound (ShellGlobal *global, void shell_global_play_theme_sound (ShellGlobal *global,
const char *name); const char *name);
void shell_global_init_xdnd (ShellGlobal *global);
G_END_DECLS G_END_DECLS
#endif /* __SHELL_GLOBAL_H__ */ #endif /* __SHELL_GLOBAL_H__ */

View File

@ -4,3 +4,4 @@ VOID:BOXED
VOID:BOXED,OBJECT VOID:BOXED,OBJECT
VOID:OBJECT,OBJECT VOID:OBJECT,OBJECT
VOID:STRING,OBJECT,BOOLEAN VOID:STRING,OBJECT,BOOLEAN
VOID:INT,INT