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