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 */ 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 */ 78 public void ownershipChanged(final boolean isOwner) { 79 if (isOwner) { 80 checkChangeHere(contents); 81 } else { 82 lostOwnershipImpl(); 83 } 84 } 85 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 99 public long getID() { 100 return selection.getSelectionAtom().getAtom(); 101 } 102 103 @Override 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. */ 112 protected void clearNativeContext() { 113 selection.reset(); 114 } 115 116 117 protected long[] getClipboardFormats() { 118 return selection.getTargets(XToolkit.getCurrentServerTime()); 119 } 120 121 protected byte[] getClipboardData(long format) throws IOException { 122 return selection.getData(format, XToolkit.getCurrentServerTime()); 123 } 124 125 private void checkChangeHere(Transferable contents) { 126 if (areFlavorListenersRegistered()) { 127 checkChange(DataTransferer.getInstance(). 128 getFormatsForTransferableAsArray(contents, getDefaultFlavorTable())); 129 } 130 } 131 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 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 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 { 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 { 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 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 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 */ 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 }