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