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
26package sun.awt.X11;
27
28import java.awt.datatransfer.Transferable;
29import java.awt.datatransfer.DataFlavor;
30import java.util.SortedMap;
31import java.io.IOException;
32import java.security.AccessController;
33import java.util.HashMap;
34import java.util.Map;
35import sun.awt.UNIXToolkit;
36import sun.awt.datatransfer.DataTransferer;
37import sun.awt.datatransfer.SunClipboard;
38import sun.awt.datatransfer.ClipboardTransferable;
39import 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 */
45public 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}
297