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