1 /*
2  * Copyright (c) 2003, 2013, 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.io.IOException;
29 
30 import java.util.HashMap;
31 
32 import sun.util.logging.PlatformLogger;
33 
34 /**
35  * An abstract class for drop protocols on X11 systems.
36  * Contains protocol-independent drop target code.
37  *
38  * @since 1.5
39  */
40 abstract class XDropTargetProtocol {
41     private static final PlatformLogger logger =
42         PlatformLogger.getLogger("sun.awt.X11.xembed.xdnd.XDropTargetProtocol");
43 
44     private final XDropTargetProtocolListener listener;
45 
46     public static final int EMBEDDER_ALREADY_REGISTERED = 0;
47 
48     public static final int UNKNOWN_MESSAGE = 0;
49     public static final int ENTER_MESSAGE   = 1;
50     public static final int MOTION_MESSAGE  = 2;
51     public static final int LEAVE_MESSAGE   = 3;
52     public static final int DROP_MESSAGE    = 4;
53 
XDropTargetProtocol(XDropTargetProtocolListener listener)54     protected XDropTargetProtocol(XDropTargetProtocolListener listener) {
55         if (listener == null) {
56             throw new NullPointerException("Null XDropTargetProtocolListener");
57         }
58         this.listener = listener;
59     }
60 
getProtocolListener()61     protected final XDropTargetProtocolListener getProtocolListener() {
62         return listener;
63     }
64 
65     /**
66      * Returns the protocol name. The protocol name cannot be null.
67      */
getProtocolName()68     public abstract String getProtocolName();
69 
70     /* The caller must hold AWT_LOCK. */
registerDropTarget(long window)71     public abstract void registerDropTarget(long window);
72 
73     /* The caller must hold AWT_LOCK. */
unregisterDropTarget(long window)74     public abstract void unregisterDropTarget(long window);
75 
76     /* The caller must hold AWT_LOCK. */
registerEmbedderDropSite(long window)77     public abstract void registerEmbedderDropSite(long window);
78 
79     /* The caller must hold AWT_LOCK. */
unregisterEmbedderDropSite(long window)80     public abstract void unregisterEmbedderDropSite(long window);
81 
82     /* The caller must hold AWT_LOCK. */
registerEmbeddedDropSite(long embedded)83     public abstract void registerEmbeddedDropSite(long embedded);
84 
85     /* The caller must hold AWT_LOCK. */
unregisterEmbeddedDropSite(long embedded)86     public final void unregisterEmbeddedDropSite(long embedded) {
87         removeEmbedderRegistryEntry(embedded);
88     }
89 
90 
91     /* The caller must hold AWT_LOCK. */
isProtocolSupported(long window)92     public abstract boolean isProtocolSupported(long window);
93 
getMessageType(XClientMessageEvent xclient)94     public abstract int getMessageType(XClientMessageEvent xclient);
95 
96     /* The caller must hold AWT_LOCK. */
processClientMessage(XClientMessageEvent xclient)97     public final boolean processClientMessage(XClientMessageEvent xclient) {
98         int type = getMessageType(xclient);
99         boolean processed = processClientMessageImpl(xclient);
100 
101         postProcessClientMessage(xclient, processed, type);
102 
103         return processed;
104     }
105 
106     /* The caller must hold AWT_LOCK. */
processClientMessageImpl(XClientMessageEvent xclient)107     protected abstract boolean processClientMessageImpl(XClientMessageEvent xclient);
108 
109     /*
110      * Forwards a drag notification to the embedding toplevel modifying the event
111      * to match the protocol version supported by the toplevel.
112      * The caller must hold AWT_LOCK.
113      * Returns True if the event is sent, False otherwise.
114      */
forwardClientMessageToToplevel(long toplevel, XClientMessageEvent xclient)115     protected final boolean forwardClientMessageToToplevel(long toplevel,
116                                                            XClientMessageEvent xclient) {
117         EmbedderRegistryEntry entry = getEmbedderRegistryEntry(toplevel);
118 
119         if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
120             logger.finest("        entry={0}", entry);
121         }
122         // Window not registered as an embedder for this protocol.
123         if (entry == null) {
124             return false;
125         }
126 
127         if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
128             logger.finest("        entry.isOverriden()={0}", entry.isOverriden());
129         }
130         // Window didn't have an associated drop site, so there is no need
131         // to forward the message.
132         if (!entry.isOverriden()) {
133             return false;
134         }
135 
136         adjustEventForForwarding(xclient, entry);
137 
138         long proxy = entry.getProxy();
139 
140         if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
141             logger.finest("        proxy={0} toplevel={1}", proxy, toplevel);
142         }
143         if (proxy == 0) {
144             proxy = toplevel;
145         }
146 
147         xclient.set_window(toplevel);
148 
149         XToolkit.awtLock();
150         try {
151             XlibWrapper.XSendEvent(XToolkit.getDisplay(), proxy, false,
152                                    XConstants.NoEventMask, xclient.pData);
153         } finally {
154             XToolkit.awtUnlock();
155         }
156 
157         return true;
158     }
159 
160 
161     /* True iff the previous notification was MotionEvent and it was
162        forwarded to the browser. */
163     private boolean motionPassedAlong = false;
164 
sendEnterMessageToToplevel(long toplevel, XClientMessageEvent xclient)165     protected abstract void sendEnterMessageToToplevel(long toplevel,
166                                                        XClientMessageEvent xclient);
167 
sendLeaveMessageToToplevel(long toplevel, XClientMessageEvent xclient)168     protected abstract void sendLeaveMessageToToplevel(long toplevel,
169                                                        XClientMessageEvent xclient);
170 
postProcessClientMessage(XClientMessageEvent xclient, boolean processed, int type)171     private void postProcessClientMessage(XClientMessageEvent xclient,
172                                           boolean processed,
173                                           int type) {
174         long toplevel = xclient.get_window();
175 
176         if (getEmbedderRegistryEntry(toplevel) != null) {
177             /*
178              * This code forwards drag notifications to the browser according to the
179              * following rules:
180              *  - the messages that we failed to process are always forwarded to the
181              *    browser;
182              *  - MotionEvents and DropEvents are forwarded if and only if the drag
183              *    is not over a plugin window;
184              *  - XDnD: EnterEvents and LeaveEvents are never forwarded, instead, we
185              *    send synthesized EnterEvents or LeaveEvents when the drag
186              *    respectively exits or enters plugin windows;
187              *  - Motif DnD: EnterEvents and LeaveEvents are always forwarded.
188              * Synthetic EnterEvents and LeaveEvents are needed, because the XDnD drop
189              * site implemented Netscape 6.2 has a nice feature: when it receives
190              * the first XdndPosition it continuously sends XdndStatus messages to
191              * the source (every 100ms) until the drag terminates or leaves the drop
192              * site. When the mouse is dragged over plugin window embedded in the
193              * browser frame, these XdndStatus messages are mixed with the XdndStatus
194              * messages sent from the plugin.
195              * For Motif DnD, synthetic events cause Motif warnings being displayed,
196              * so these events are always forwarded. However, Motif DnD drop site in
197              * Netscape 6.2 is implemented in the same way, so there could be similar
198              * problems if the drag source choose Motif DnD for communication.
199              */
200             if (!processed) {
201                 forwardClientMessageToToplevel(toplevel, xclient);
202             } else {
203                 boolean motifProtocol =
204                     xclient.get_message_type() ==
205                     MotifDnDConstants.XA_MOTIF_DRAG_AND_DROP_MESSAGE.getAtom();
206 
207                 switch (type) {
208                 case XDropTargetProtocol.MOTION_MESSAGE:
209                     if (!isDragOverComponent()) {
210                         if (!motionPassedAlong && !motifProtocol) {
211                             sendEnterMessageToToplevel(toplevel, xclient);
212                         }
213                         forwardClientMessageToToplevel(toplevel, xclient);
214                         motionPassedAlong = true;
215                     } else {
216                         if (motionPassedAlong && !motifProtocol) {
217                             sendLeaveMessageToToplevel(toplevel, xclient);
218                         }
219                         motionPassedAlong = false;
220                     }
221                     break;
222                 case XDropTargetProtocol.DROP_MESSAGE:
223                     if (!isDragOverComponent()) {
224                         forwardClientMessageToToplevel(toplevel, xclient);
225                     }
226                     motionPassedAlong = false;
227                     break;
228                 case XDropTargetProtocol.ENTER_MESSAGE:
229                 case XDropTargetProtocol.LEAVE_MESSAGE:
230                     if (motifProtocol) {
231                         forwardClientMessageToToplevel(toplevel, xclient);
232                     }
233                     motionPassedAlong = false;
234                     break;
235                 }
236             }
237         }
238     }
239 
sendResponse(long ctxt, int eventID, int action)240     public abstract boolean sendResponse(long ctxt, int eventID, int action);
241 
242     /*
243      * Retrieves the data from the drag source in the specified format.
244      *
245      * @param ctxt a pointer to the XClientMessageEvent structure for this
246      *             protocol's drop message.
247      * @param format the format in which the data should be retrieved.
248      *
249      * @throws IllegalArgumentException if ctxt doesn't point to the
250      *         XClientMessageEvent structure for this protocol's drop message.
251      * @throws IOException if data retrieval failed.
252      */
getData(long ctxt, long format)253     public abstract Object getData(long ctxt, long format)
254       throws IllegalArgumentException, IOException;
255 
sendDropDone(long ctxt, boolean success, int dropAction)256     public abstract boolean sendDropDone(long ctxt, boolean success,
257                                          int dropAction);
258 
getSourceWindow()259     public abstract long getSourceWindow();
260 
cleanup()261     public abstract void cleanup();
262 
isDragOverComponent()263     public abstract boolean isDragOverComponent();
264 
adjustEventForForwarding(XClientMessageEvent xclient, EmbedderRegistryEntry entry)265     public void adjustEventForForwarding(XClientMessageEvent xclient,
266         EmbedderRegistryEntry entry) {}
267 
forwardEventToEmbedded(long embedded, long ctxt, int eventID)268     public abstract boolean forwardEventToEmbedded(long embedded, long ctxt,
269                                                    int eventID);
270 
271     /*
272      * Returns true if the XEmbed protocol prescribes that an XEmbed server must
273      * support this DnD protocol for drop sites associated with XEmbed clients.
274      */
isXEmbedSupported()275     public abstract boolean isXEmbedSupported();
276 
277     protected static final class EmbedderRegistryEntry {
278         private final boolean overriden;
279         private final int version;
280         private final long proxy;
EmbedderRegistryEntry(boolean overriden, int version, long proxy)281         EmbedderRegistryEntry(boolean overriden, int version, long proxy) {
282             this.overriden = overriden;
283             this.version = version;
284             this.proxy = proxy;
285         }
isOverriden()286         public boolean isOverriden() {
287             return overriden;
288         }
getVersion()289         public int getVersion() {
290             return version;
291         }
getProxy()292         public long getProxy() {
293             return proxy;
294         }
295     }
296 
297     /* Access to HashMap is synchronized on this XDropTargetProtocol instance. */
298     private final HashMap<Long, EmbedderRegistryEntry> embedderRegistry =
299         new HashMap<>();
300 
putEmbedderRegistryEntry(long embedder, boolean overriden, int version, long proxy)301     protected final void putEmbedderRegistryEntry(long embedder,
302                                                   boolean overriden,
303                                                   int version,
304                                                   long proxy) {
305         synchronized (this) {
306             embedderRegistry.put(Long.valueOf(embedder),
307                                  new EmbedderRegistryEntry(overriden, version,
308                                                            proxy));
309         }
310     }
311 
getEmbedderRegistryEntry(long embedder)312     protected final EmbedderRegistryEntry getEmbedderRegistryEntry(long embedder) {
313         synchronized (this) {
314             return embedderRegistry.get(Long.valueOf(embedder));
315         }
316     }
317 
removeEmbedderRegistryEntry(long embedder)318     protected final void removeEmbedderRegistryEntry(long embedder) {
319         synchronized (this) {
320             embedderRegistry.remove(Long.valueOf(embedder));
321         }
322     }
323 }
324