Path: blob/master/src/java.desktop/unix/classes/sun/awt/X11/XDragSourceContextPeer.java
41159 views
/*1* Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.awt.X11;2627import java.awt.Component;28import java.awt.Cursor;29import java.awt.Window;3031import java.awt.datatransfer.DataFlavor;32import java.awt.datatransfer.Transferable;3334import java.awt.dnd.DnDConstants;35import java.awt.dnd.DragGestureEvent;36import java.awt.dnd.InvalidDnDOperationException;3738import java.util.*;3940import sun.java2d.pipe.Region;41import sun.util.logging.PlatformLogger;4243import sun.awt.dnd.SunDragSourceContextPeer;44import sun.awt.dnd.SunDropTargetContextPeer;45import sun.awt.SunToolkit;46import sun.awt.AWTAccessor;4748/**49* The XDragSourceContextPeer class is the class responsible for handling50* the interaction between the XDnD/Motif DnD subsystem and Java drag sources.51*52* @since 1.553*/54public final class XDragSourceContextPeer55extends SunDragSourceContextPeer implements XDragSourceProtocolListener {56private static final PlatformLogger logger =57PlatformLogger.getLogger("sun.awt.X11.xembed.xdnd.XDragSourceContextPeer");5859/* The events selected on the root window when the drag begins. */60private static final int ROOT_EVENT_MASK = (int)XConstants.ButtonMotionMask |61(int)XConstants.KeyPressMask | (int)XConstants.KeyReleaseMask;62/* The events to be delivered during grab. */63private static final int GRAB_EVENT_MASK = (int)XConstants.ButtonPressMask |64(int)XConstants.ButtonMotionMask | (int)XConstants.ButtonReleaseMask;6566/* The event mask of the root window before the drag operation starts. */67private long rootEventMask = 0;68private boolean dndInProgress = false;69private boolean dragInProgress = false;70private long dragRootWindow = 0;7172/* The protocol chosen for the communication with the current drop target. */73private XDragSourceProtocol dragProtocol = null;74/* The drop action chosen by the current drop target. */75private int targetAction = DnDConstants.ACTION_NONE;76/* The set of drop actions supported by the drag source. */77private int sourceActions = DnDConstants.ACTION_NONE;78/* The drop action selected by the drag source based on the modifiers state79and the action selected by the current drop target. */80private int sourceAction = DnDConstants.ACTION_NONE;81/* The data formats supported by the drag source for the current drag82operation. */83private long[] sourceFormats = null;84/* The XID of the root subwindow that contains the current target. */85private long targetRootSubwindow = 0;86/* window scale factor */87int windowScale = 1;88/* The pointer location. */89private int xRoot = 0;90private int yRoot = 0;91/* Keyboard modifiers state. */92private int eventState = 0;9394/* XEmbed DnD support. We act as a proxy between source and target. */95private long proxyModeSourceWindow = 0;9697/* The singleton instance. */98private static final XDragSourceContextPeer theInstance =99new XDragSourceContextPeer(null);100101private XDragSourceContextPeer(DragGestureEvent dge) {102super(dge);103}104105static XDragSourceProtocolListener getXDragSourceProtocolListener() {106return theInstance;107}108109static XDragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge)110throws InvalidDnDOperationException {111theInstance.setTrigger(dge);112return theInstance;113}114115protected void startDrag(Transferable transferable,116long[] formats, Map<Long, DataFlavor> formatMap) {117Component component = getTrigger().getComponent();118Component c = null;119XWindowPeer wpeer = null;120121for (c = component; c != null && !(c instanceof Window);122c = AWTAccessor.getComponentAccessor().getParent(c));123124if (c instanceof Window) {125wpeer = AWTAccessor.getComponentAccessor().getPeer(c);126}127128if (wpeer == null) {129throw new InvalidDnDOperationException(130"Cannot find top-level for the drag source component");131}132133long xcursor = 0;134long rootWindow = 0;135long timeStamp = 0;136windowScale = wpeer.getScale();137138/* Retrieve the X cursor for the drag operation. */139{140Cursor cursor = getCursor();141if (cursor != null) {142xcursor = XGlobalCursorManager.getCursor(cursor);143}144}145146XToolkit.awtLock();147try {148if (proxyModeSourceWindow != 0) {149throw new InvalidDnDOperationException("Proxy drag in progress");150}151if (dndInProgress) {152throw new InvalidDnDOperationException("Drag in progress");153}154155/* Determine the root window for the drag operation. */156{157long screen = XlibWrapper.XScreenNumberOfScreen(wpeer.getScreen());158rootWindow = XlibWrapper.RootWindow(XToolkit.getDisplay(), screen);159}160161timeStamp = XToolkit.getCurrentServerTime();162163int dropActions = getDragSourceContext().getSourceActions();164165Iterator<XDragSourceProtocol> dragProtocols =166XDragAndDropProtocols.getDragSourceProtocols();167while (dragProtocols.hasNext()) {168XDragSourceProtocol dragProtocol = dragProtocols.next();169try {170dragProtocol.initializeDrag(dropActions, transferable,171formatMap, formats);172} catch (XException xe) {173throw (InvalidDnDOperationException)174new InvalidDnDOperationException().initCause(xe);175}176}177178/* Install X grabs. */179{180int status;181XWindowAttributes wattr = new XWindowAttributes();182try {183status = XlibWrapper.XGetWindowAttributes(XToolkit.getDisplay(),184rootWindow, wattr.pData);185186if (status == 0) {187throw new InvalidDnDOperationException("XGetWindowAttributes failed");188}189190rootEventMask = wattr.get_your_event_mask();191192XlibWrapper.XSelectInput(XToolkit.getDisplay(), rootWindow,193rootEventMask | ROOT_EVENT_MASK);194} finally {195wattr.dispose();196}197198XBaseWindow.ungrabInput();199200status = XlibWrapper.XGrabPointer(XToolkit.getDisplay(), rootWindow,2010, GRAB_EVENT_MASK,202XConstants.GrabModeAsync,203XConstants.GrabModeAsync,204XConstants.None, xcursor, timeStamp);205206if (status != XConstants.GrabSuccess) {207cleanup(timeStamp);208throwGrabFailureException("Cannot grab pointer", status);209return;210}211212status = XlibWrapper.XGrabKeyboard(XToolkit.getDisplay(), rootWindow,2130,214XConstants.GrabModeAsync,215XConstants.GrabModeAsync,216timeStamp);217218if (status != XConstants.GrabSuccess) {219cleanup(timeStamp);220throwGrabFailureException("Cannot grab keyboard", status);221return;222}223}224225/* Update the global state. */226dndInProgress = true;227dragInProgress = true;228dragRootWindow = rootWindow;229sourceActions = dropActions;230sourceFormats = formats;231} finally {232XToolkit.awtUnlock();233}234235/* This implementation doesn't use native context */236setNativeContext(0);237238SunDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(transferable);239}240241public long getProxyModeSourceWindow() {242return proxyModeSourceWindow;243}244245private void setProxyModeSourceWindowImpl(long window) {246proxyModeSourceWindow = window;247}248249public static void setProxyModeSourceWindow(long window) {250theInstance.setProxyModeSourceWindowImpl(window);251}252253/**254* set cursor255*/256257public void setCursor(Cursor c) throws InvalidDnDOperationException {258XToolkit.awtLock();259try {260super.setCursor(c);261} finally {262XToolkit.awtUnlock();263}264}265266protected void setNativeCursor(long nativeCtxt, Cursor c, int cType) {267assert XToolkit.isAWTLockHeldByCurrentThread();268269if (c == null) {270return;271}272273long xcursor = XGlobalCursorManager.getCursor(c);274275if (xcursor == 0) {276return;277}278279XlibWrapper.XChangeActivePointerGrab(XToolkit.getDisplay(),280GRAB_EVENT_MASK,281xcursor,282XConstants.CurrentTime);283}284285protected boolean needsBogusExitBeforeDrop() {286return false;287}288289private void throwGrabFailureException(String msg, int grabStatus)290throws InvalidDnDOperationException {291String msgCause = "";292switch (grabStatus) {293case XConstants.GrabNotViewable: msgCause = "not viewable"; break;294case XConstants.AlreadyGrabbed: msgCause = "already grabbed"; break;295case XConstants.GrabInvalidTime: msgCause = "invalid time"; break;296case XConstants.GrabFrozen: msgCause = "grab frozen"; break;297default: msgCause = "unknown failure"; break;298}299throw new InvalidDnDOperationException(msg + ": " + msgCause);300}301302/**303* The caller must own awtLock.304*/305public void cleanup(long time) {306if (dndInProgress) {307if (dragProtocol != null) {308dragProtocol.sendLeaveMessage(time);309}310311if (targetAction != DnDConstants.ACTION_NONE) {312dragExit(xRoot, yRoot);313}314315dragDropFinished(false, DnDConstants.ACTION_NONE, xRoot, yRoot);316}317318Iterator<XDragSourceProtocol> dragProtocols =319XDragAndDropProtocols.getDragSourceProtocols();320while (dragProtocols.hasNext()) {321XDragSourceProtocol dragProtocol = dragProtocols.next();322try {323dragProtocol.cleanup();324} catch (XException xe) {325// Ignore the exception.326}327}328329dndInProgress = false;330dragInProgress = false;331dragRootWindow = 0;332sourceFormats = null;333sourceActions = DnDConstants.ACTION_NONE;334sourceAction = DnDConstants.ACTION_NONE;335eventState = 0;336xRoot = 0;337yRoot = 0;338339cleanupTargetInfo();340341removeDnDGrab(time);342}343344/**345* The caller must own awtLock.346*/347private void cleanupTargetInfo() {348targetAction = DnDConstants.ACTION_NONE;349dragProtocol = null;350targetRootSubwindow = 0;351}352353private void removeDnDGrab(long time) {354assert XToolkit.isAWTLockHeldByCurrentThread();355356XlibWrapper.XUngrabPointer(XToolkit.getDisplay(), time);357XlibWrapper.XUngrabKeyboard(XToolkit.getDisplay(), time);358359/* Restore the root event mask if it was changed. */360if ((rootEventMask | ROOT_EVENT_MASK) != rootEventMask &&361dragRootWindow != 0) {362363XlibWrapper.XSelectInput(XToolkit.getDisplay(),364dragRootWindow,365rootEventMask);366}367368rootEventMask = 0;369dragRootWindow = 0;370}371372private boolean processClientMessage(XClientMessageEvent xclient) {373if (dragProtocol != null) {374return dragProtocol.processClientMessage(xclient);375}376return false;377}378379/**380* Updates the source action according to the specified state.381*382* @return true if the source383*/384private boolean updateSourceAction(int state) {385int action = SunDragSourceContextPeer.convertModifiersToDropAction(XWindow.getModifiers(state, 0, 0),386sourceActions);387if (sourceAction == action) {388return false;389}390sourceAction = action;391return true;392}393394/**395* Returns the client window under the specified root subwindow.396*/397private static long findClientWindow(long window) {398if (XlibUtil.isTrueToplevelWindow(window)) {399return window;400}401402Set<Long> children = XlibUtil.getChildWindows(window);403for (Long child : children) {404long win = findClientWindow(child);405if (win != 0) {406return win;407}408}409410return 0;411}412413private void doUpdateTargetWindow(long subwindow, long time) {414long clientWindow = 0;415long proxyWindow = 0;416XDragSourceProtocol protocol = null;417boolean isReceiver = false;418419if (subwindow != 0) {420clientWindow = findClientWindow(subwindow);421}422423if (clientWindow != 0) {424Iterator<XDragSourceProtocol> dragProtocols =425XDragAndDropProtocols.getDragSourceProtocols();426while (dragProtocols.hasNext()) {427XDragSourceProtocol dragProtocol = dragProtocols.next();428if (dragProtocol.attachTargetWindow(clientWindow, time)) {429protocol = dragProtocol;430break;431}432}433}434435/* Update the global state. */436dragProtocol = protocol;437targetAction = DnDConstants.ACTION_NONE;438targetRootSubwindow = subwindow;439}440441private void updateTargetWindow(XMotionEvent xmotion) {442assert XToolkit.isAWTLockHeldByCurrentThread();443444int x = scaleDown(xmotion.get_x_root());445int y = scaleDown(xmotion.get_y_root());446long time = xmotion.get_time();447long subwindow = xmotion.get_subwindow();448449/*450* If this event had occurred before the pointer was grabbed,451* query the server for the current root subwindow.452*/453if (xmotion.get_window() != xmotion.get_root()) {454XlibWrapper.XQueryPointer(XToolkit.getDisplay(),455xmotion.get_root(),456XlibWrapper.larg1, // root457XlibWrapper.larg2, // subwindow458XlibWrapper.larg3, // x_root459XlibWrapper.larg4, // y_root460XlibWrapper.larg5, // x461XlibWrapper.larg6, // y462XlibWrapper.larg7); // modifiers463subwindow = Native.getLong(XlibWrapper.larg2);464}465466if (targetRootSubwindow != subwindow) {467if (dragProtocol != null) {468dragProtocol.sendLeaveMessage(time);469470/*471* Neither Motif DnD nor XDnD provide a mean for the target472* to notify the source that the pointer exits the drop site473* that occupies the whole top level.474* We detect this situation and post dragExit.475*/476if (targetAction != DnDConstants.ACTION_NONE) {477dragExit(x, y);478}479}480481/* Update the global state. */482doUpdateTargetWindow(subwindow, time);483484if (dragProtocol != null) {485dragProtocol.sendEnterMessage(sourceFormats,486sourceAction,487sourceActions,488time);489}490}491}492493/*494* DO NOT USE is_hint field of xmotion since it could not be set when we495* convert XKeyEvent or XButtonRelease to XMotionEvent.496*/497private void processMouseMove(XMotionEvent xmotion) {498if (!dragInProgress) {499return;500}501502int motionXRoot = scaleDown(xmotion.get_x_root());503int motionYRoot = scaleDown(xmotion.get_y_root());504505if (xRoot != motionXRoot || yRoot != motionYRoot) {506xRoot = motionXRoot;507yRoot = motionYRoot;508509postDragSourceDragEvent(targetAction,510XWindow.getModifiers(xmotion.get_state(),0,0),511xRoot, yRoot, DISPATCH_MOUSE_MOVED);512}513514if (eventState != xmotion.get_state()) {515if (updateSourceAction(xmotion.get_state()) && dragProtocol != null) {516postDragSourceDragEvent(targetAction,517XWindow.getModifiers(xmotion.get_state(),0,0),518xRoot, yRoot, DISPATCH_CHANGED);519}520eventState = xmotion.get_state();521}522523updateTargetWindow(xmotion);524525if (dragProtocol != null) {526dragProtocol.sendMoveMessage(xmotion.get_x_root(),527xmotion.get_y_root(),528sourceAction, sourceActions,529xmotion.get_time());530}531}532533private void processDrop(XButtonEvent xbutton) {534try {535dragProtocol.initiateDrop(xbutton.get_x_root(),536xbutton.get_y_root(),537sourceAction, sourceActions,538xbutton.get_time());539} catch (XException e) {540cleanup(xbutton.get_time());541}542}543544private boolean processProxyModeEvent(XEvent ev) {545if (getProxyModeSourceWindow() == 0) {546return false;547}548549if (ev.get_type() != XConstants.ClientMessage) {550return false;551}552553if (logger.isLoggable(PlatformLogger.Level.FINEST)) {554logger.finest(" proxyModeSourceWindow=" +555getProxyModeSourceWindow() +556" ev=" + ev);557}558559XClientMessageEvent xclient = ev.get_xclient();560561Iterator<XDragSourceProtocol> dragProtocols =562XDragAndDropProtocols.getDragSourceProtocols();563while (dragProtocols.hasNext()) {564XDragSourceProtocol dragProtocol = dragProtocols.next();565if (dragProtocol.processProxyModeEvent(xclient,566getProxyModeSourceWindow())) {567return true;568}569}570571return false;572}573574/**575* The caller must own awtLock.576*577* @return true if the event was processed and shouldn't be passed along.578*/579private boolean doProcessEvent(XEvent ev) {580assert XToolkit.isAWTLockHeldByCurrentThread();581582if (processProxyModeEvent(ev)) {583return true;584}585586if (!dndInProgress) {587return false;588}589590switch (ev.get_type()) {591case XConstants.ClientMessage: {592XClientMessageEvent xclient = ev.get_xclient();593return processClientMessage(xclient);594}595case XConstants.DestroyNotify: {596XDestroyWindowEvent xde = ev.get_xdestroywindow();597598/* Target crashed during drop processing - cleanup. */599if (!dragInProgress &&600dragProtocol != null &&601xde.get_window() == dragProtocol.getTargetWindow()) {602cleanup(XConstants.CurrentTime);603return true;604}605/* Pass along */606return false;607}608}609610if (!dragInProgress) {611return false;612}613614/* Process drag-only messages. */615switch (ev.get_type()) {616case XConstants.KeyRelease:617case XConstants.KeyPress: {618XKeyEvent xkey = ev.get_xkey();619long keysym = XlibWrapper.XKeycodeToKeysym(XToolkit.getDisplay(),620xkey.get_keycode(), 0);621switch ((int)keysym) {622case (int)XKeySymConstants.XK_Escape: {623if (ev.get_type() == XConstants.KeyRelease) {624cleanup(xkey.get_time());625}626break;627}628case (int)XKeySymConstants.XK_Control_R:629case (int)XKeySymConstants.XK_Control_L:630case (int)XKeySymConstants.XK_Shift_R:631case (int)XKeySymConstants.XK_Shift_L: {632XlibWrapper.XQueryPointer(XToolkit.getDisplay(),633xkey.get_root(),634XlibWrapper.larg1, // root635XlibWrapper.larg2, // subwindow636XlibWrapper.larg3, // x_root637XlibWrapper.larg4, // y_root638XlibWrapper.larg5, // x639XlibWrapper.larg6, // y640XlibWrapper.larg7); // modifiers641XMotionEvent xmotion = new XMotionEvent();642try {643xmotion.set_type(XConstants.MotionNotify);644xmotion.set_serial(xkey.get_serial());645xmotion.set_send_event(xkey.get_send_event());646xmotion.set_display(xkey.get_display());647xmotion.set_window(xkey.get_window());648xmotion.set_root(xkey.get_root());649xmotion.set_subwindow(xkey.get_subwindow());650xmotion.set_time(xkey.get_time());651xmotion.set_x(xkey.get_x());652xmotion.set_y(xkey.get_y());653xmotion.set_x_root(xkey.get_x_root());654xmotion.set_y_root(xkey.get_y_root());655xmotion.set_state(Native.getInt(XlibWrapper.larg7));656// we do not use this field, so it's unset for now657// xmotion.set_is_hint(???);658xmotion.set_same_screen(xkey.get_same_screen());659660//It's safe to use key event as motion event since we use only their common fields.661processMouseMove(xmotion);662} finally {663xmotion.dispose();664}665break;666}667}668return true;669}670case XConstants.ButtonPress:671return true;672case XConstants.MotionNotify:673processMouseMove(ev.get_xmotion());674return true;675case XConstants.ButtonRelease: {676XButtonEvent xbutton = ev.get_xbutton();677/*678* Ignore the buttons above 20 due to the bit limit for679* InputEvent.BUTTON_DOWN_MASK.680* One more bit is reserved for FIRST_HIGH_BIT.681*/682if (xbutton.get_button() > SunToolkit.MAX_BUTTONS_SUPPORTED) {683return true;684}685686/*687* On some X servers it could happen that ButtonRelease coordinates688* differ from the latest MotionNotify coordinates, so we need to689* process it as a mouse motion.690*/691XMotionEvent xmotion = new XMotionEvent();692try {693xmotion.set_type(XConstants.MotionNotify);694xmotion.set_serial(xbutton.get_serial());695xmotion.set_send_event(xbutton.get_send_event());696xmotion.set_display(xbutton.get_display());697xmotion.set_window(xbutton.get_window());698xmotion.set_root(xbutton.get_root());699xmotion.set_subwindow(xbutton.get_subwindow());700xmotion.set_time(xbutton.get_time());701xmotion.set_x(xbutton.get_x());702xmotion.set_y(xbutton.get_y());703xmotion.set_x_root(xbutton.get_x_root());704xmotion.set_y_root(xbutton.get_y_root());705xmotion.set_state(xbutton.get_state());706// we do not use this field, so it's unset for now707// xmotion.set_is_hint(???);708xmotion.set_same_screen(xbutton.get_same_screen());709710//It's safe to use key event as motion event since we use only their common fields.711processMouseMove(xmotion);712} finally {713xmotion.dispose();714}715if (xbutton.get_button() == XConstants.buttons[0]716|| xbutton.get_button() == XConstants.buttons[1]) {717// drag is initiated with Button1 or Button2 pressed and718// ended on release of either of these buttons (as the same719// behavior was with our old Motif DnD-based implementation)720removeDnDGrab(xbutton.get_time());721dragInProgress = false;722if (dragProtocol != null && targetAction != DnDConstants.ACTION_NONE) {723/*724* ACTION_NONE indicates that either the drop target rejects the725* drop or it haven't responded yet. The latter could happen in726* case of fast drag, slow target-server connection or slow727* drag notifications processing on the target side.728*/729processDrop(xbutton);730} else {731cleanup(xbutton.get_time());732}733}734return true;735}736}737738return false;739}740741static boolean processEvent(XEvent ev) {742XToolkit.awtLock();743try {744try {745return theInstance.doProcessEvent(ev);746} catch (XException e) {747e.printStackTrace();748return false;749}750} finally {751XToolkit.awtUnlock();752}753}754755/* XDragSourceProtocolListener implementation */756757public void handleDragReply(int action) {758// NOTE: we have to use the current pointer location, since759// the target didn't specify the coordinates for the reply.760handleDragReply(action, xRoot, yRoot);761}762763public void handleDragReply(int action, int x, int y) {764// NOTE: we have to use the current modifiers state, since765// the target didn't specify the modifiers state for the reply.766handleDragReply(action, xRoot, yRoot, XWindow.getModifiers(eventState,0,0));767}768769public void handleDragReply(int action, int x, int y, int modifiers) {770if (action == DnDConstants.ACTION_NONE &&771targetAction != DnDConstants.ACTION_NONE) {772dragExit(x, y);773} else if (action != DnDConstants.ACTION_NONE) {774int type = 0;775776if (targetAction == DnDConstants.ACTION_NONE) {777type = SunDragSourceContextPeer.DISPATCH_ENTER;778} else {779type = SunDragSourceContextPeer.DISPATCH_MOTION;780}781782// Note that we use the modifiers state a783postDragSourceDragEvent(action, modifiers, x, y, type);784}785786targetAction = action;787}788789public void handleDragFinished() {790/* Assume that the drop was successful. */791handleDragFinished(true);792}793794public void handleDragFinished(boolean success) {795/* Assume that the performed drop action is the latest drop action796accepted by the drop target. */797handleDragFinished(true, targetAction);798}799800public void handleDragFinished(boolean success, int action) {801// NOTE: we have to use the current pointer location, since802// the target didn't specify the coordinates for the reply.803handleDragFinished(success, action, xRoot, yRoot);804}805806public void handleDragFinished(boolean success, int action, int x, int y) {807dragDropFinished(success, action, x, y);808809dndInProgress = false;810cleanup(XConstants.CurrentTime);811}812813public int scaleUp(int x) {814return Region.clipRound(x * (double)windowScale);815}816817public int scaleDown(int x) {818return Region.clipRound(x / (double)windowScale);819}820}821822823