// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- const { Clutter, Meta } = imports.gi; const Signals = imports.signals; const DND = imports.ui.dnd; const Main = imports.ui.main; var XdndHandler = class { constructor() { // 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.Actor({ width: 1, height: 1, opacity: 0 }); Main.uiGroup.add_actor(this._dummy); this._dummy.hide(); if (!Meta.is_wayland_compositor()) global.init_xdnd(); var dnd = Meta.get_backend().get_dnd(); dnd.connect('dnd-enter', this._onEnter.bind(this)); dnd.connect('dnd-position-change', this._onPositionChanged.bind(this)); dnd.connect('dnd-leave', this._onLeave.bind(this)); this._windowGroupVisibilityHandlerId = 0; } // Called when the user cancels the drag (i.e release the button) _onLeave() { if (this._windowGroupVisibilityHandlerId != 0) { global.window_group.disconnect(this._windowGroupVisibilityHandlerId); this._windowGroupVisibilityHandlerId = 0; } if (this._cursorWindowClone) { this._cursorWindowClone.destroy(); this._cursorWindowClone = null; } this.emit('drag-end'); } _onEnter() { this._windowGroupVisibilityHandlerId = global.window_group.connect('notify::visible', this._onWindowGroupVisibilityChanged.bind(this)); this.emit('drag-begin', global.get_current_time()); } _onWindowGroupVisibilityChanged() { 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.get_meta_window().is_override_redirect()) return; let constraintPosition = new Clutter.BindConstraint({ coordinate: Clutter.BindCoordinate.POSITION, source: cursorWindow }); this._cursorWindowClone = new Clutter.Clone({ source: cursorWindow }); Main.uiGroup.add_actor(this._cursorWindowClone); // Make sure that the clone has the same position as the source this._cursorWindowClone.add_constraint(constraintPosition); } else { if (this._cursorWindowClone) { this._cursorWindowClone.destroy(); this._cursorWindowClone = null; } } } _onPositionChanged(obj, x, y) { let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, 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 [r, targX, targY] = pickedActor.transform_stage_point(x, y); let result = pickedActor._delegate.handleDragOver(this, dragEvent.dragActor, targX, targY, global.get_current_time()); if (result != DND.DragMotionResult.CONTINUE) return; } pickedActor = pickedActor.get_parent(); } } }; Signals.addSignalMethods(XdndHandler.prototype);