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
26package sun.awt.X11;
27
28import java.awt.datatransfer.Transferable;
29import java.awt.datatransfer.DataFlavor;
30
31import java.awt.dnd.DnDConstants;
32import java.awt.dnd.InvalidDnDOperationException;
33
34import java.util.Map;
35
36import sun.util.logging.PlatformLogger;
37
38import jdk.internal.misc.Unsafe;
39
40/**
41 * XDragSourceProtocol implementation for XDnD protocol.
42 *
43 * @since 1.5
44 */
45class 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}.
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}
425