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                 if (targetsAtom2Clipboard == null) {
163                     targetsAtom2Clipboard = new HashMap<Long, XClipboard>(2);
164                 }
165                 mustSchedule = targetsAtom2Clipboard.isEmpty();
166                 targetsAtom2Clipboard.put(getTargetsPropertyAtom().getAtom(), this);
167                 if (mustSchedule) {
168                     XToolkit.addEventDispatcher(XWindow.getXAWTRootWindow().getWindow(),
169                                                 new SelectionNotifyHandler());
170                 }
171             }
172             if (mustSchedule) {
173                 XToolkit.schedule(new CheckChangeTimerTask(), XClipboard.getPollInterval());
174             }
175         } finally {
176             XToolkit.awtUnlock();
177         }
178     }
179 
180     private static class CheckChangeTimerTask implements Runnable {
run()181         public void run() {
182             for (XClipboard clpbrd : targetsAtom2Clipboard.values()) {
183                 clpbrd.getTargetsDelayed();
184             }
185             synchronized (XClipboard.classLock) {
186                 if (targetsAtom2Clipboard != null && !targetsAtom2Clipboard.isEmpty()) {
187                     // The viewer is still registered, schedule next poll.
188                     XToolkit.schedule(this, XClipboard.getPollInterval());
189                 }
190             }
191         }
192     }
193 
194     private static class SelectionNotifyHandler implements XEventDispatcher {
dispatchEvent(XEvent ev)195         public void dispatchEvent(XEvent ev) {
196             if (ev.get_type() == XConstants.SelectionNotify) {
197                 final XSelectionEvent xse = ev.get_xselection();
198                 XClipboard clipboard = null;
199                 synchronized (XClipboard.classLock) {
200                     if (targetsAtom2Clipboard != null && targetsAtom2Clipboard.isEmpty()) {
201                         // The viewer was unregistered, remove the dispatcher.
202                         XToolkit.removeEventDispatcher(XWindow.getXAWTRootWindow().getWindow(), this);
203                         return;
204                     }
205                     final long propertyAtom = xse.get_property();
206                     clipboard = targetsAtom2Clipboard.get(propertyAtom);
207                 }
208                 if (null != clipboard) {
209                     clipboard.checkChange(xse);
210                 }
211             }
212         }
213     }
214 
unregisterClipboardViewerChecked()215     protected void unregisterClipboardViewerChecked() {
216         isSelectionNotifyProcessed = false;
217         synchronized (XClipboard.classLock) {
218             targetsAtom2Clipboard.remove(getTargetsPropertyAtom().getAtom());
219         }
220     }
221 
222     // checkChange() will be called on SelectionNotify
getTargetsDelayed()223     private void getTargetsDelayed() {
224         XToolkit.awtLock();
225         try {
226             long curTime = System.currentTimeMillis();
227             if (isSelectionNotifyProcessed || curTime >= (convertSelectionTime + UNIXToolkit.getDatatransferTimeout()))
228             {
229                 convertSelectionTime = curTime;
230                 XlibWrapper.XConvertSelection(XToolkit.getDisplay(),
231                                               selection.getSelectionAtom().getAtom(),
232                                               XDataTransferer.TARGETS_ATOM.getAtom(),
233                                               getTargetsPropertyAtom().getAtom(),
234                                               XWindow.getXAWTRootWindow().getWindow(),
235                                               XConstants.CurrentTime);
236                 isSelectionNotifyProcessed = false;
237             }
238         } finally {
239             XToolkit.awtUnlock();
240         }
241     }
242 
243     /*
244      * Tracks changes of available formats.
245      * NOTE: This method may be called by privileged threads.
246      *       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
247      */
checkChange(XSelectionEvent xse)248     private void checkChange(XSelectionEvent xse) {
249         final long propertyAtom = xse.get_property();
250         if (propertyAtom != getTargetsPropertyAtom().getAtom()) {
251             // wrong atom
252             return;
253         }
254 
255         final XAtom selectionAtom = XAtom.get(xse.get_selection());
256         final XSelection changedSelection = XSelection.getSelection(selectionAtom);
257 
258         if (null == changedSelection || changedSelection != selection) {
259             // unknown selection - do nothing
260             return;
261         }
262 
263         isSelectionNotifyProcessed = true;
264 
265         if (selection.isOwner()) {
266             // selection is owner - do not need formats
267             return;
268         }
269 
270         long[] formats = null;
271 
272         if (propertyAtom == XConstants.None) {
273             // We treat None property atom as "empty selection".
274             formats = new long[0];
275         } else {
276             WindowPropertyGetter targetsGetter =
277                 new WindowPropertyGetter(XWindow.getXAWTRootWindow().getWindow(),
278                                          XAtom.get(propertyAtom), 0,
279                                          XSelection.MAX_LENGTH, true,
280                                          XConstants.AnyPropertyType);
281             try {
282                 targetsGetter.execute();
283                 formats = XSelection.getFormats(targetsGetter);
284             } finally {
285                 targetsGetter.dispose();
286             }
287         }
288 
289         XToolkit.awtUnlock();
290         try {
291             checkChange(formats);
292         } finally {
293             XToolkit.awtLock();
294         }
295     }
296 }
297