1 /*
   2  * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt.X11;
  27 
  28 import java.awt.datatransfer.Transferable;
  29 import java.awt.datatransfer.DataFlavor;
  30 
  31 import java.awt.dnd.DnDConstants;
  32 import java.awt.dnd.InvalidDnDOperationException;
  33 
  34 import java.util.Map;
  35 
  36 import sun.util.logging.PlatformLogger;
  37 
  38 import sun.misc.Unsafe;
  39 
  40 /**
  41  * XDragSourceProtocol implementation for XDnD protocol.
  42  *
  43  * @since 1.5
  44  */
  45 class XDnDDragSourceProtocol extends XDragSourceProtocol {
  46     private static final PlatformLogger logger =
  47         PlatformLogger.getLogger("sun.awt.X11.xembed.xdnd.XDnDDragSourceProtocol");
  48 
  49     private static final Unsafe unsafe = XlibWrapper.unsafe;
  50 
  51     protected XDnDDragSourceProtocol(XDragSourceProtocolListener listener) {
  52         super(listener);
  53     }
  54 
  55     /**
  56      * Creates an instance associated with the specified listener.
  57      *
  58      * @throws NullPointerException if listener is <code>null</code>.
  59      */
  60     static XDragSourceProtocol createInstance(XDragSourceProtocolListener listener) {
  61         return new XDnDDragSourceProtocol(listener);
  62     }
  63 
  64     public String getProtocolName() {
  65         return XDragAndDropProtocols.XDnD;
  66     }
  67 
  68     /**
  69      * Performs protocol-specific drag initialization.
  70      *
  71      * @return true if the initialized successfully.
  72      */
  73     protected void initializeDragImpl(int actions, Transferable contents,
  74                                       Map<Long, DataFlavor> formatMap, long[] formats)
  75       throws InvalidDnDOperationException,
  76         IllegalArgumentException, XException {
  77         assert XToolkit.isAWTLockHeldByCurrentThread();
  78 
  79         long window = XDragSourceProtocol.getDragSourceWindow();
  80 
  81         long data = Native.allocateLongArray(3);
  82         int action_count = 0;
  83         try {
  84             if ((actions & DnDConstants.ACTION_COPY) != 0) {
  85                 Native.putLong(data, action_count,
  86                                XDnDConstants.XA_XdndActionCopy.getAtom());
  87                 action_count++;
  88             }
  89             if ((actions & DnDConstants.ACTION_MOVE) != 0) {
  90                 Native.putLong(data, action_count,
  91                                XDnDConstants.XA_XdndActionMove.getAtom());
  92                 action_count++;
  93             }
  94             if ((actions & DnDConstants.ACTION_LINK) != 0) {
  95                 Native.putLong(data, action_count,
  96                                XDnDConstants.XA_XdndActionLink.getAtom());
  97                 action_count++;
  98             }
  99 
 100             XErrorHandlerUtil.WITH_XERROR_HANDLER(XErrorHandler.VerifyChangePropertyHandler.getInstance());
 101             XDnDConstants.XA_XdndActionList.setAtomData(window,
 102                                                         XAtom.XA_ATOM,
 103                                                         data, action_count);
 104             XErrorHandlerUtil.RESTORE_XERROR_HANDLER();
 105 
 106             if ((XErrorHandlerUtil.saved_error) != null &&
 107                 (XErrorHandlerUtil.saved_error.get_error_code() != XConstants.Success)) {
 108                 cleanup();
 109                 throw new XException("Cannot write XdndActionList property");
 110             }
 111         } finally {
 112             unsafe.freeMemory(data);
 113             data = 0;
 114         }
 115 
 116         data = Native.allocateLongArray(formats.length);
 117 
 118         try {
 119             Native.put(data, formats);
 120 
 121             XErrorHandlerUtil.WITH_XERROR_HANDLER(XErrorHandler.VerifyChangePropertyHandler.getInstance());
 122             XDnDConstants.XA_XdndTypeList.setAtomData(window,
 123                                                       XAtom.XA_ATOM,
 124                                                       data, formats.length);
 125             XErrorHandlerUtil.RESTORE_XERROR_HANDLER();
 126 
 127             if ((XErrorHandlerUtil.saved_error != null) &&
 128                 (XErrorHandlerUtil.saved_error.get_error_code() != XConstants.Success)) {
 129                 cleanup();
 130                 throw new XException("Cannot write XdndActionList property");
 131             }
 132         } finally {
 133             unsafe.freeMemory(data);
 134             data = 0;
 135         }
 136 
 137         if (!XDnDConstants.XDnDSelection.setOwner(contents, formatMap, formats,
 138                                                   XConstants.CurrentTime)) {
 139             cleanup();
 140             throw new InvalidDnDOperationException("Cannot acquire selection ownership");
 141         }
 142     }
 143 
 144     private boolean processXdndStatus(XClientMessageEvent xclient) {
 145         int action = DnDConstants.ACTION_NONE;
 146 
 147         /* Ignore XDnD messages from all other windows. */
 148         if (xclient.get_data(0) != getTargetWindow()) {
 149             return true;
 150         }
 151 
 152         if ((xclient.get_data(1) & XDnDConstants.XDND_ACCEPT_DROP_FLAG) != 0) {
 153             /* This feature is new in XDnD version 2, but we can use it as XDnD
 154                compliance only requires supporting version 3 and up. */
 155             action = XDnDConstants.getJavaActionForXDnDAction(xclient.get_data(4));
 156         }
 157 
 158         getProtocolListener().handleDragReply(action);
 159 
 160         return true;
 161     }
 162 
 163     private boolean processXdndFinished(XClientMessageEvent xclient) {
 164         /* Ignore XDnD messages from all other windows. */
 165         if (xclient.get_data(0) != getTargetWindow()) {
 166             return true;
 167         }
 168 
 169         if (getTargetProtocolVersion() >= 5) {
 170             boolean success = (xclient.get_data(1) & XDnDConstants.XDND_ACCEPT_DROP_FLAG) != 0;
 171             int action = XDnDConstants.getJavaActionForXDnDAction(xclient.get_data(2));
 172             getProtocolListener().handleDragFinished(success, action);
 173         } else {
 174             getProtocolListener().handleDragFinished();
 175         }
 176 
 177         finalizeDrop();
 178 
 179         return true;
 180     }
 181 
 182     public boolean processClientMessage(XClientMessageEvent xclient) {
 183         if (xclient.get_message_type() == XDnDConstants.XA_XdndStatus.getAtom()) {
 184             return processXdndStatus(xclient);
 185         } else if (xclient.get_message_type() == XDnDConstants.XA_XdndFinished.getAtom()) {
 186             return processXdndFinished(xclient);
 187         } else {
 188             return false;
 189         }
 190     }
 191 
 192     public TargetWindowInfo getTargetWindowInfo(long window) {
 193         assert XToolkit.isAWTLockHeldByCurrentThread();
 194 
 195         WindowPropertyGetter wpg1 =
 196             new WindowPropertyGetter(window, XDnDConstants.XA_XdndAware, 0, 1,
 197                                      false, XConstants.AnyPropertyType);
 198 
 199         int status = wpg1.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());
 200 
 201         if (status == XConstants.Success &&
 202             wpg1.getData() != 0 && wpg1.getActualType() == XAtom.XA_ATOM) {
 203 
 204             int targetVersion = (int)Native.getLong(wpg1.getData());
 205 
 206             wpg1.dispose();
 207 
 208             if (targetVersion >= XDnDConstants.XDND_MIN_PROTOCOL_VERSION) {
 209                 long proxy = 0;
 210                 int protocolVersion =
 211                     targetVersion < XDnDConstants.XDND_PROTOCOL_VERSION ?
 212                     targetVersion : XDnDConstants.XDND_PROTOCOL_VERSION;
 213 
 214                 WindowPropertyGetter wpg2 =
 215                     new WindowPropertyGetter(window, XDnDConstants.XA_XdndProxy,
 216                                              0, 1, false, XAtom.XA_WINDOW);
 217 
 218                 try {
 219                     status = wpg2.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());
 220 
 221                     if (status == XConstants.Success &&
 222                         wpg2.getData() != 0 &&
 223                         wpg2.getActualType() == XAtom.XA_WINDOW) {
 224 
 225                         proxy = Native.getLong(wpg2.getData());
 226                     }
 227                 } finally {
 228                     wpg2.dispose();
 229                 }
 230 
 231                 if (proxy != 0) {
 232                     WindowPropertyGetter wpg3 =
 233                         new WindowPropertyGetter(proxy, XDnDConstants.XA_XdndProxy,
 234                                                  0, 1, false, XAtom.XA_WINDOW);
 235 
 236                     try {
 237                         status = wpg3.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());
 238 
 239                         if (status != XConstants.Success ||
 240                             wpg3.getData() == 0 ||
 241                             wpg3.getActualType() != XAtom.XA_WINDOW ||
 242                             Native.getLong(wpg3.getData()) != proxy) {
 243 
 244                             proxy = 0;
 245                         } else {
 246                             WindowPropertyGetter wpg4 =
 247                                 new WindowPropertyGetter(proxy,
 248                                                          XDnDConstants.XA_XdndAware,
 249                                                          0, 1, false,
 250                                                          XConstants.AnyPropertyType);
 251 
 252                             try {
 253                                 status = wpg4.execute(XErrorHandler.IgnoreBadWindowHandler.getInstance());
 254 
 255                                 if (status != XConstants.Success ||
 256                                     wpg4.getData() == 0 ||
 257                                     wpg4.getActualType() != XAtom.XA_ATOM) {
 258 
 259                                     proxy = 0;
 260                                 }
 261                             } finally {
 262                                 wpg4.dispose();
 263                             }
 264                         }
 265                     } finally {
 266                         wpg3.dispose();
 267                     }
 268                 }
 269 
 270                 return new TargetWindowInfo(proxy, protocolVersion);
 271             }
 272         } else {
 273             wpg1.dispose();
 274         }
 275 
 276         return null;
 277     }
 278 
 279     public void sendEnterMessage(long[] formats,
 280                                  int sourceAction, int sourceActions, long time) {
 281         assert XToolkit.isAWTLockHeldByCurrentThread();
 282         assert getTargetWindow() != 0;
 283         assert formats != null;
 284 
 285         XClientMessageEvent msg = new XClientMessageEvent();
 286         try {
 287             msg.set_type(XConstants.ClientMessage);
 288             msg.set_window(getTargetWindow());
 289             msg.set_format(32);
 290             msg.set_message_type(XDnDConstants.XA_XdndEnter.getAtom());
 291             msg.set_data(0, XDragSourceProtocol.getDragSourceWindow());
 292             long data1 =
 293                 getTargetProtocolVersion() << XDnDConstants.XDND_PROTOCOL_SHIFT;
 294             data1 |= formats.length > 3 ? XDnDConstants.XDND_DATA_TYPES_BIT : 0;
 295             msg.set_data(1, data1);
 296             msg.set_data(2, formats.length > 0 ? formats[0] : 0);
 297             msg.set_data(3, formats.length > 1 ? formats[1] : 0);
 298             msg.set_data(4, formats.length > 2 ? formats[2] : 0);
 299             XlibWrapper.XSendEvent(XToolkit.getDisplay(),
 300                                    getTargetProxyWindow(),
 301                                    false, XConstants.NoEventMask,
 302                                    msg.pData);
 303         } finally {
 304             msg.dispose();
 305         }
 306     }
 307 
 308     public void sendMoveMessage(int xRoot, int yRoot,
 309                                 int sourceAction, int sourceActions, long time) {
 310         assert XToolkit.isAWTLockHeldByCurrentThread();
 311         assert getTargetWindow() != 0;
 312 
 313         XClientMessageEvent msg = new XClientMessageEvent();
 314         try {
 315             msg.set_type(XConstants.ClientMessage);
 316             msg.set_window(getTargetWindow());
 317             msg.set_format(32);
 318             msg.set_message_type(XDnDConstants.XA_XdndPosition.getAtom());
 319             msg.set_data(0, XDragSourceProtocol.getDragSourceWindow());
 320             msg.set_data(1, 0); /* flags */
 321             msg.set_data(2, xRoot << 16 | yRoot);
 322             msg.set_data(3, time);
 323             msg.set_data(4, XDnDConstants.getXDnDActionForJavaAction(sourceAction));
 324             XlibWrapper.XSendEvent(XToolkit.getDisplay(),
 325                                    getTargetProxyWindow(),
 326                                    false, XConstants.NoEventMask,
 327                                    msg.pData);
 328         } finally {
 329             msg.dispose();
 330         }
 331     }
 332 
 333     public void sendLeaveMessage(long time) {
 334         assert XToolkit.isAWTLockHeldByCurrentThread();
 335         assert getTargetWindow() != 0;
 336 
 337         XClientMessageEvent msg = new XClientMessageEvent();
 338         try {
 339             msg.set_type(XConstants.ClientMessage);
 340             msg.set_window(getTargetWindow());
 341             msg.set_format(32);
 342             msg.set_message_type(XDnDConstants.XA_XdndLeave.getAtom());
 343             msg.set_data(0, XDragSourceProtocol.getDragSourceWindow());
 344             msg.set_data(1, 0);
 345             msg.set_data(2, 0);
 346             msg.set_data(3, 0);
 347             msg.set_data(4, 0);
 348             XlibWrapper.XSendEvent(XToolkit.getDisplay(),
 349                                    getTargetProxyWindow(),
 350                                    false, XConstants.NoEventMask,
 351                                    msg.pData);
 352         } finally {
 353             msg.dispose();
 354         }
 355     }
 356 
 357     public void sendDropMessage(int xRoot, int yRoot,
 358                                 int sourceAction, int sourceActions,
 359                                 long time) {
 360         assert XToolkit.isAWTLockHeldByCurrentThread();
 361         assert getTargetWindow() != 0;
 362 
 363         XClientMessageEvent msg = new XClientMessageEvent();
 364         try {
 365             msg.set_type(XConstants.ClientMessage);
 366             msg.set_window(getTargetWindow());
 367             msg.set_format(32);
 368             msg.set_message_type(XDnDConstants.XA_XdndDrop.getAtom());
 369             msg.set_data(0, XDragSourceProtocol.getDragSourceWindow());
 370             msg.set_data(1, 0); /* flags */
 371             msg.set_data(2, time);
 372             msg.set_data(3, 0);
 373             msg.set_data(4, 0);
 374             XlibWrapper.XSendEvent(XToolkit.getDisplay(),
 375                                    getTargetProxyWindow(),
 376                                    false, XConstants.NoEventMask,
 377                                    msg.pData);
 378         } finally {
 379             msg.dispose();
 380         }
 381     }
 382 
 383     public boolean processProxyModeEvent(XClientMessageEvent xclient,
 384                                          long sourceWindow) {
 385         if (xclient.get_message_type() == XDnDConstants.XA_XdndStatus.getAtom() ||
 386             xclient.get_message_type() == XDnDConstants.XA_XdndFinished.getAtom()) {
 387 
 388             if (xclient.get_message_type() == XDnDConstants.XA_XdndFinished.getAtom()) {
 389                 XDragSourceContextPeer.setProxyModeSourceWindow(0);
 390             }
 391 
 392             // This can happen if the drag operation started in the XEmbed server.
 393             // In this case there is no need to forward it elsewhere, we should
 394             // process it here.
 395             if (xclient.get_window() == sourceWindow) {
 396                 return false;
 397             }
 398 
 399             if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
 400                 logger.finest("        sourceWindow=" + sourceWindow +
 401                               " get_window=" + xclient.get_window() +
 402                               " xclient=" + xclient);
 403             }
 404             xclient.set_data(0, xclient.get_window());
 405             xclient.set_window(sourceWindow);
 406 
 407             assert XToolkit.isAWTLockHeldByCurrentThread();
 408 
 409             XlibWrapper.XSendEvent(XToolkit.getDisplay(), sourceWindow,
 410                                    false, XConstants.NoEventMask,
 411                                    xclient.pData);
 412 
 413             return true;
 414         }
 415 
 416         return false;
 417     }
 418 
 419     // TODO: register this runnable with XDnDSelection.
 420     public void run() {
 421         // XdndSelection ownership lost.
 422         cleanup();
 423     }
 424 }