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:
parent
62507c9759
commit
ceedc7e32c
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
130
js/ui/xdndHandler.js
Normal 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);
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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__ */
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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__ */
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user