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 import java.util.SortedMap;
31 import java.io.IOException;
32 import java.security.AccessController;
33 import java.util.HashMap;
34 import java.util.Map;
35 import sun.awt.UNIXToolkit;
36 import sun.awt.datatransfer.DataTransferer;
37 import sun.awt.datatransfer.SunClipboard;
38 import sun.awt.datatransfer.ClipboardTransferable;
39 import sun.security.action.GetIntegerAction;
40 
41 /**
42  * A class which interfaces with the X11 selection service in order to support
43  * data transfer via Clipboard operations.
44  */
45 public final class XClipboard extends SunClipboard implements OwnershipListener
46 {
47     private final XSelection selection;
48     // Time of calling XConvertSelection().
49     private long convertSelectionTime;
50     // The flag used not to call XConvertSelection() if the previous SelectionNotify
51     // has not been processed by checkChange().
52     private volatile boolean isSelectionNotifyProcessed;
53     // The property in which the owner should place requested targets
54     // when tracking changes of available data flavors (practically targets).
55     private volatile XAtom targetsPropertyAtom;
56 
57     private static final Object classLock = new Object();
58 
59     private static final int defaultPollInterval = 200;
60 
61     private static int pollInterval;
62 
63     private static Map<Long, XClipboard> targetsAtom2Clipboard;
64 
65     /**
66      * Creates a system clipboard object.
67      */
XClipboard(String name, String selectionName)68     public XClipboard(String name, String selectionName) {
69         super(name);
70         selection = new XSelection(XAtom.get(selectionName));
71         selection.registerOwershipListener(this);
72     }
73 
74     /*
75      * NOTE: This method may be called by privileged threads.
76      *       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
77      */
ownershipChanged(final boolean isOwner)78     public void ownershipChanged(final boolean isOwner) {
79         if (isOwner) {
80             checkChangeHere(contents);
81         } else {
82             lostOwnershipImpl();
83         }
84     }
85 
setContentsNative(Transferable contents)86     protected synchronized void setContentsNative(Transferable contents) {
87         SortedMap<Long,DataFlavor> formatMap =
88             DataTransferer.getInstance().getFormatsForTransferable
89                 (contents, DataTransferer.adaptFlavorMap(getDefaultFlavorTable()));
90         long[] formats = DataTransferer.keysToLongArray(formatMap);
91 
92         if (!selection.setOwner(contents, formatMap, formats,
93                                 XToolkit.getCurrentServerTime())) {
94             this.owner = null;
95             this.contents = null;
96         }
97     }
98 
getID()99     public long getID() {
100         return selection.getSelectionAtom().getAtom();
101     }
102 
103     @Override
getContents(Object requestor)104     public synchronized Transferable getContents(Object requestor) {
105         if (contents != null) {
106             return contents;
107         }
108         return new ClipboardTransferable(this);
109     }
110 
111     /* Caller is synchronized on this. */
clearNativeContext()112     protected void clearNativeContext() {
113         selection.reset();
114     }
115 
116 
getClipboardFormats()117     protected long[] getClipboardFormats() {
118         return selection.getTargets(XToolkit.getCurrentServerTime());
119     }
120 
getClipboardData(long format)121     protected byte[] getClipboardData(long format) throws IOException {
122         return selection.getData(format, XToolkit.getCurrentServerTime());
123     }
124 
checkChangeHere(Transferable contents)125     private void checkChangeHere(Transferable contents) {
126         if (areFlavorListenersRegistered()) {
127             checkChange(DataTransferer.getInstance().
128                         getFormatsForTransferableAsArray(contents, getDefaultFlavorTable()));
129         }
130     }
131 
getPollInterval()132     private static int getPollInterval() {
133         synchronized (XClipboard.classLock) {
134             if (pollInterval <= 0) {
135                 pollInterval = AccessController.doPrivileged(
136                         new GetIntegerAction("awt.datatransfer.clipboard.poll.interval",
137                                              defaultPollInterval));
138                 if (pollInterval <= 0) {
139                     pollInterval = defaultPollInterval;
140                 }
141             }
142             return pollInterval;
143         }
144     }
145 
getTargetsPropertyAtom()146     private XAtom getTargetsPropertyAtom() {
147         if (null == targetsPropertyAtom) {
148             targetsPropertyAtom =
149                     XAtom.get("XAWT_TARGETS_OF_SELECTION:" + selection.getSelectionAtom().getName());
150         }
151         return targetsPropertyAtom;
152     }
153 
registerClipboardViewerChecked()154     protected void registerClipboardViewerChecked() {
155         // for XConvertSelection() to be called for the first time in getTargetsDelayed()
156         isSelectionNotifyProcessed = true;
157 
158         boolean mustSchedule = false;
159         XToolkit.awtLock();
160         try {
161             synchronized (XClipboard.classLock) {
162                 try {
163                     Thread.sleep(70);
164                 } catch (InterruptedException e) {
165                     e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
166                 }
167                 if (targetsAtom2Clipboard == null) {
168                     targetsAtom2Clipboard = new HashMap<Long, XClipboard>(2);
169                 }
170                 mustSchedule = targetsAtom2Clipboard.isEmpty();
171                 targetsAtom2Clipboard.put(getTargetsPropertyAtom().getAtom(), this);
172                 if (mustSchedule) {
173                     XToolkit.addEventDispatcher(XWindow.getXAWTRootWindow().getWindow(),
174                                                 new SelectionNotifyHandler());
175                 }
176             }
177             if (mustSchedule) {
178                 XToolkit.schedule(new CheckChangeTimerTask(), XClipboard.getPollInterval());
179             }
180         } finally {
181             XToolkit.awtUnlock();
182         }
183     }
184 
185     private static class CheckChangeTimerTask implements Runnable {
run()186         public void run() {
187             for (XClipboard clpbrd : targetsAtom2Clipboard.values()) {
188                 clpbrd.getTargetsDelayed();
189             }
190             synchronized (XClipboard.classLock) {
191                 if (targetsAtom2Clipboard != null && !targetsAtom2Clipboard.isEmpty()) {
192                     // The viewer is still registered, schedule next poll.
193                     XToolkit.schedule(this, XClipboard.getPollInterval());
194                 }
195             }
196         }
197     }
198 
199     private static class SelectionNotifyHandler implements XEventDispatcher {
dispatchEvent(XEvent ev)200         public void dispatchEvent(XEvent ev) {
201             if (ev.get_type() == XConstants.SelectionNotify) {
202                 final XSelectionEvent xse = ev.get_xselection();
203                 XClipboard clipboard = null;
204                 synchronized (XClipboard.classLock) {
205                     if (targetsAtom2Clipboard != null && targetsAtom2Clipboard.isEmpty()) {
206                         // The viewer was unregistered, remove the dispatcher.
207                         XToolkit.removeEventDispatcher(XWindow.getXAWTRootWindow().getWindow(), this);
208                         return;
209                     }
210                     final long propertyAtom = xse.get_property();
211                     clipboard = targetsAtom2Clipboard.get(propertyAtom);
212                 }
213                 if (null != clipboard) {
214                     clipboard.checkChange(xse);
215                 }
216             }
217         }
218     }
219 
unregisterClipboardViewerChecked()220     protected void unregisterClipboardViewerChecked() {
221         isSelectionNotifyProcessed = false;
222         synchronized (XClipboard.classLock) {
223             targetsAtom2Clipboard.remove(getTargetsPropertyAtom().getAtom());
224         }
225     }
226 
227     // checkChange() will be called on SelectionNotify
getTargetsDelayed()228     private void getTargetsDelayed() {
229         XToolkit.awtLock();
230         try {
231             long curTime = System.currentTimeMillis();
232             if (isSelectionNotifyProcessed || curTime >= (convertSelectionTime + UNIXToolkit.getDatatransferTimeout()))
233             {
234                 convertSelectionTime = curTime;
235                 XlibWrapper.XConvertSelection(XToolkit.getDisplay(),
236                                               selection.getSelectionAtom().getAtom(),
237                                               XDataTransferer.TARGETS_ATOM.getAtom(),
238                                               getTargetsPropertyAtom().getAtom(),
239                                               XWindow.getXAWTRootWindow().getWindow(),
240                                               XConstants.CurrentTime);
241                 isSelectionNotifyProcessed = false;
242             }
243         } finally {
244             XToolkit.awtUnlock();
245         }
246     }
247 
248     /*
249      * Tracks changes of available formats.
250      * NOTE: This method may be called by privileged threads.
251      *       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
252      */
checkChange(XSelectionEvent xse)253     private void checkChange(XSelectionEvent xse) {
254         final long propertyAtom = xse.get_property();
255         if (propertyAtom != getTargetsPropertyAtom().getAtom()) {
256             // wrong atom
257             return;
258         }
259 
260         final XAtom selectionAtom = XAtom.get(xse.get_selection());
261         final XSelection changedSelection = XSelection.getSelection(selectionAtom);
262 
263         if (null == changedSelection || changedSelection != selection) {
264             // unknown selection - do nothing
265             return;
266         }
267 
268         isSelectionNotifyProcessed = true;
269 
270         if (selection.isOwner()) {
271             // selection is owner - do not need formats
272             return;
273         }
274 
275         long[] formats = null;
276 
277         if (propertyAtom == XConstants.None) {
278             // We treat None property atom as "empty selection".
279             formats = new long[0];
280         } else {
281             WindowPropertyGetter targetsGetter =
282                 new WindowPropertyGetter(XWindow.getXAWTRootWindow().getWindow(),
283                                          XAtom.get(propertyAtom), 0,
284                                          XSelection.MAX_LENGTH, true,
285                                          XConstants.AnyPropertyType);
286             try {
287                 targetsGetter.execute();
288                 formats = XSelection.getFormats(targetsGetter);
289             } finally {
290                 targetsGetter.dispose();
291             }
292         }
293 
294         XToolkit.awtUnlock();
295         try {
296             checkChange(formats);
297         } finally {
298             XToolkit.awtLock();
299         }
300     }
301 }
302