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.DataFlavor;
29import java.awt.datatransfer.Transferable;
30
31import java.io.ByteArrayOutputStream;
32import java.io.IOException;
33
34import java.util.Hashtable;
35import java.util.Map;
36
37import sun.awt.AppContext;
38import sun.awt.SunToolkit;
39import sun.awt.UNIXToolkit;
40
41import sun.awt.datatransfer.DataTransferer;
42
43/**
44 * A class which interfaces with the X11 selection service.
45 */
46final class XSelection {
47
48    /* Maps atoms to XSelection instances. */
49    private static final Hashtable<XAtom, XSelection> table = new Hashtable<XAtom, XSelection>();
50    /* Prevents from parallel selection data request processing. */
51    private static final Object lock = new Object();
52    /* The property in which the owner should place the requested data. */
53    private static final XAtom selectionPropertyAtom = XAtom.get("XAWT_SELECTION");
54    /* The maximal length of the property data. */
55    public static final long MAX_LENGTH = 1000000;
56    /*
57     * The maximum data size for ChangeProperty request.
58     * 100 is for the structure prepended to the request.
59     */
60    public static final int MAX_PROPERTY_SIZE;
61    static {
62        XToolkit.awtLock();
63        try {
64            MAX_PROPERTY_SIZE =
65                (int)(XlibWrapper.XMaxRequestSize(XToolkit.getDisplay()) * 4 - 100);
66        } finally {
67            XToolkit.awtUnlock();
68        }
69    }
70
71    /* The PropertyNotify event handler for incremental data transfer. */
72    private static final XEventDispatcher incrementalTransferHandler =
73        new IncrementalTransferHandler();
74    /* The context for the current request - protected with awtLock. */
75    private static WindowPropertyGetter propertyGetter = null;
76
77    // The orders of the lock acquisition:
78    //   XClipboard -> XSelection -> awtLock.
79    //   lock -> awtLock.
80
81    /* The X atom for the underlying selection. */
82    private final XAtom selectionAtom;
83
84    /*
85     * Owner-related variables - protected with synchronized (this).
86     */
87
88    /* The contents supplied by the current owner. */
89    private Transferable contents = null;
90    /* The format-to-flavor map for the current owner. */
91    private Map<Long, DataFlavor> formatMap = null;
92    /* The formats supported by the current owner was set. */
93    private long[] formats = null;
94    /* The AppContext in which the current owner was set. */
95    private AppContext appContext = null;
96    // The X server time of the last XConvertSelection() call;
97    // protected with 'lock' and awtLock.
98    private static long lastRequestServerTime;
99    /* The time at which the current owner was set. */
100    private long ownershipTime = 0;
101    // True if we are the owner of this selection.
102    private boolean isOwner;
103    private OwnershipListener ownershipListener = null;
104    private final Object stateLock = new Object();
105
106    static {
107        XToolkit.addEventDispatcher(XWindow.getXAWTRootWindow().getWindow(),
108                                    new SelectionEventHandler());
109    }
110
111    /*
112     * Returns the XSelection object for the specified selection atom or
113     * {@code null} if none exists.
114     */
115    static XSelection getSelection(XAtom atom) {
116        return table.get(atom);
117    }
118
119    /**
120     * Creates a selection object.
121     *
122     * @param  atom the selection atom
123     * @throws NullPointerException if atom is {@code null}
124     */
125    XSelection(XAtom atom) {
126        if (atom == null) {
127            throw new NullPointerException("Null atom");
128        }
129        selectionAtom = atom;
130        table.put(selectionAtom, this);
131    }
132
133    public XAtom getSelectionAtom() {
134        return selectionAtom;
135    }
136
137    synchronized boolean setOwner(Transferable contents,
138                                  Map<Long, DataFlavor> formatMap,
139                                  long[] formats, long time) {
140        long owner = XWindow.getXAWTRootWindow().getWindow();
141        long selection = selectionAtom.getAtom();
142
143        // ICCCM prescribes that CurrentTime should not be used for SetSelectionOwner.
144        if (time == XConstants.CurrentTime) {
145            time = XToolkit.getCurrentServerTime();
146        }
147
148        this.contents = contents;
149        this.formatMap = formatMap;
150        this.formats = formats;
151        this.appContext = AppContext.getAppContext();
152        this.ownershipTime = time;
153
154        XToolkit.awtLock();
155        try {
156            XlibWrapper.XSetSelectionOwner(XToolkit.getDisplay(),
157                                           selection, owner, time);
158            if (XlibWrapper.XGetSelectionOwner(XToolkit.getDisplay(),
159                                               selection) != owner)
160            {
161                reset();
162                return false;
163            }
164            setOwnerProp(true);
165            return true;
166        } finally {
167            XToolkit.awtUnlock();
168        }
169    }
170
171    /**
172     * Blocks the current thread till SelectionNotify or PropertyNotify (in case of INCR transfer) arrives.
173     */
174    private static void waitForSelectionNotify(WindowPropertyGetter dataGetter) throws InterruptedException {
175        long startTime = System.currentTimeMillis();
176        XToolkit.awtLock();
177        try {
178            do {
179                DataTransferer.getInstance().processDataConversionRequests();
180                XToolkit.awtLockWait(250);
181            } while (propertyGetter == dataGetter && System.currentTimeMillis() < startTime + UNIXToolkit.getDatatransferTimeout());
182        } finally {
183            XToolkit.awtUnlock();
184        }
185    }
186
187    /*
188     * Returns the list of atoms that represent the targets for which an attempt
189     * to convert the current selection will succeed.
190     */
191    public long[] getTargets(long time) {
192        if (XToolkit.isToolkitThread()) {
193            throw new Error("UNIMPLEMENTED");
194        }
195
196        long[] targets = null;
197
198        synchronized (lock) {
199            WindowPropertyGetter targetsGetter =
200                new WindowPropertyGetter(XWindow.getXAWTRootWindow().getWindow(),
201                                         selectionPropertyAtom, 0, MAX_LENGTH,
202                                         true, XConstants.AnyPropertyType);
203
204            try {
205                XToolkit.awtLock();
206                try {
207                    propertyGetter = targetsGetter;
208                    lastRequestServerTime = time;
209
210                    XlibWrapper.XConvertSelection(XToolkit.getDisplay(),
211                                                  getSelectionAtom().getAtom(),
212                                                  XDataTransferer.TARGETS_ATOM.getAtom(),
213                                                  selectionPropertyAtom.getAtom(),
214                                                  XWindow.getXAWTRootWindow().getWindow(),
215                                                  time);
216
217                    // If the owner doesn't respond within the
218                    // SELECTION_TIMEOUT, we report conversion failure.
219                    try {
220                        waitForSelectionNotify(targetsGetter);
221                    } catch (InterruptedException ie) {
222                        return new long[0];
223                    } finally {
224                        propertyGetter = null;
225                    }
226                } finally {
227                    XToolkit.awtUnlock();
228                }
229                targets = getFormats(targetsGetter);
230            } finally {
231                targetsGetter.dispose();
232            }
233        }
234        return targets;
235    }
236
237    static long[] getFormats(WindowPropertyGetter targetsGetter) {
238        long[] formats = null;
239
240        if (targetsGetter.isExecuted() && !targetsGetter.isDisposed() &&
241                (targetsGetter.getActualType() == XAtom.XA_ATOM ||
242                 targetsGetter.getActualType() == XDataTransferer.TARGETS_ATOM.getAtom()) &&
243                targetsGetter.getActualFormat() == 32)
244        {
245            // we accept property with TARGETS type to be compatible with old jdks
246            // see 6607163
247            int count = targetsGetter.getNumberOfItems();
248            if (count > 0) {
249                long atoms = targetsGetter.getData();
250                formats = new long[count];
251                for (int index = 0; index < count; index++) {
252                    formats[index] =
253                            Native.getLong(atoms+index*XAtom.getAtomSize());
254                }
255            }
256        }
257
258        return formats != null ? formats : new long[0];
259    }
260
261    /*
262     * Requests the selection data in the specified format and returns
263     * the data provided by the owner.
264     */
265    public byte[] getData(long format, long time) throws IOException {
266        if (XToolkit.isToolkitThread()) {
267            throw new Error("UNIMPLEMENTED");
268        }
269
270        byte[] data = null;
271
272        synchronized (lock) {
273            WindowPropertyGetter dataGetter =
274                new WindowPropertyGetter(XWindow.getXAWTRootWindow().getWindow(),
275                                         selectionPropertyAtom, 0, MAX_LENGTH,
276                                         false, // don't delete to handle INCR properly.
277                                         XConstants.AnyPropertyType);
278
279            try {
280                XToolkit.awtLock();
281                try {
282                    propertyGetter = dataGetter;
283                    lastRequestServerTime = time;
284
285                    XlibWrapper.XConvertSelection(XToolkit.getDisplay(),
286                                                  getSelectionAtom().getAtom(),
287                                                  format,
288                                                  selectionPropertyAtom.getAtom(),
289                                                  XWindow.getXAWTRootWindow().getWindow(),
290                                                  time);
291
292                    // If the owner doesn't respond within the
293                    // SELECTION_TIMEOUT, we report conversion failure.
294                    try {
295                        waitForSelectionNotify(dataGetter);
296                    } catch (InterruptedException ie) {
297                        return new byte[0];
298                    } finally {
299                        propertyGetter = null;
300                    }
301                } finally {
302                    XToolkit.awtUnlock();
303                }
304
305                validateDataGetter(dataGetter);
306
307                // Handle incremental transfer.
308                if (dataGetter.getActualType() ==
309                    XDataTransferer.INCR_ATOM.getAtom()) {
310
311                    if (dataGetter.getActualFormat() != 32) {
312                        throw new IOException("Unsupported INCR format: " +
313                                              dataGetter.getActualFormat());
314                    }
315
316                    int count = dataGetter.getNumberOfItems();
317
318                    if (count <= 0) {
319                        throw new IOException("INCR data is missed.");
320                    }
321
322                    long ptr = dataGetter.getData();
323
324                    int len = 0;
325
326                    {
327                        // Following Xt sources use the last element.
328                        long longLength = Native.getLong(ptr, count-1);
329
330                        if (longLength <= 0) {
331                            return new byte[0];
332                        }
333
334                        if (longLength > Integer.MAX_VALUE) {
335                            throw new IOException("Can't handle large data block: "
336                                                  + longLength + " bytes");
337                        }
338
339                        len = (int)longLength;
340                    }
341
342                    dataGetter.dispose();
343
344                    ByteArrayOutputStream dataStream = new ByteArrayOutputStream(len);
345
346                    while (true) {
347                        WindowPropertyGetter incrDataGetter =
348                            new WindowPropertyGetter(XWindow.getXAWTRootWindow().getWindow(),
349                                                     selectionPropertyAtom,
350                                                     0, MAX_LENGTH, false,
351                                                     XConstants.AnyPropertyType);
352
353                        try {
354                            XToolkit.awtLock();
355                            XToolkit.addEventDispatcher(XWindow.getXAWTRootWindow().getWindow(),
356                                                        incrementalTransferHandler);
357
358                            propertyGetter = incrDataGetter;
359
360                            try {
361                                XlibWrapper.XDeleteProperty(XToolkit.getDisplay(),
362                                                            XWindow.getXAWTRootWindow().getWindow(),
363                                                            selectionPropertyAtom.getAtom());
364
365                                // If the owner doesn't respond within the
366                                // SELECTION_TIMEOUT, we terminate incremental
367                                // transfer.
368                                waitForSelectionNotify(incrDataGetter);
369                            } catch (InterruptedException ie) {
370                                break;
371                            } finally {
372                                propertyGetter = null;
373                                XToolkit.removeEventDispatcher(XWindow.getXAWTRootWindow().getWindow(),
374                                                               incrementalTransferHandler);
375                                XToolkit.awtUnlock();
376                            }
377
378                            validateDataGetter(incrDataGetter);
379
380                            if (incrDataGetter.getActualFormat() != 8) {
381                                throw new IOException("Unsupported data format: " +
382                                                      incrDataGetter.getActualFormat());
383                            }
384
385                            count = incrDataGetter.getNumberOfItems();
386
387                            if (count == 0) {
388                                break;
389                            }
390
391                            if (count > 0) {
392                                ptr = incrDataGetter.getData();
393                                for (int index = 0; index < count; index++) {
394                                    dataStream.write(Native.getByte(ptr + index));
395                                }
396                            }
397
398                            data = dataStream.toByteArray();
399
400                        } finally {
401                            incrDataGetter.dispose();
402                        }
403                    }
404                } else {
405                    XToolkit.awtLock();
406                    try {
407                        XlibWrapper.XDeleteProperty(XToolkit.getDisplay(),
408                                                    XWindow.getXAWTRootWindow().getWindow(),
409                                                    selectionPropertyAtom.getAtom());
410                    } finally {
411                        XToolkit.awtUnlock();
412                    }
413
414                    if (dataGetter.getActualFormat() != 8) {
415                        throw new IOException("Unsupported data format: " +
416                                              dataGetter.getActualFormat());
417                    }
418
419                    int count = dataGetter.getNumberOfItems();
420                    if (count > 0) {
421                        data = new byte[count];
422                        long ptr = dataGetter.getData();
423                        for (int index = 0; index < count; index++) {
424                            data[index] = Native.getByte(ptr + index);
425                        }
426                    }
427                }
428            } finally {
429                dataGetter.dispose();
430            }
431        }
432
433        return data != null ? data : new byte[0];
434    }
435
436    private void validateDataGetter(WindowPropertyGetter propertyGetter)
437            throws IOException
438    {
439        // The order of checks is important because a property getter
440        // has not been executed in case of timeout as well as in case of
441        // changed selection owner.
442
443        if (propertyGetter.isDisposed()) {
444            throw new IOException("Owner failed to convert data");
445        }
446
447        // The owner didn't respond - terminate the transfer.
448        if (!propertyGetter.isExecuted()) {
449            throw new IOException("Owner timed out");
450        }
451    }
452
453    // To be MT-safe this method should be called under awtLock.
454    boolean isOwner() {
455        return isOwner;
456    }
457
458    // To be MT-safe this method should be called under awtLock.
459    private void setOwnerProp(boolean f) {
460        isOwner = f;
461        fireOwnershipChanges(isOwner);
462    }
463
464    private void lostOwnership() {
465        setOwnerProp(false);
466    }
467
468    public synchronized void reset() {
469        contents = null;
470        formatMap = null;
471        formats = null;
472        appContext = null;
473        ownershipTime = 0;
474    }
475
476    // Converts the data to the 'format' and if the conversion succeeded stores
477    // the data in the 'property' on the 'requestor' window.
478    // Returns true if the conversion succeeded.
479    private boolean convertAndStore(long requestor, long format, long property) {
480        int dataFormat = 8; /* Can choose between 8,16,32. */
481        byte[] byteData = null;
482        long nativeDataPtr = 0;
483        int count = 0;
484
485        try {
486            SunToolkit.insertTargetMapping(this, appContext);
487
488            byteData = DataTransferer.getInstance().convertData(this,
489                                                                contents,
490                                                                format,
491                                                                formatMap,
492                                                                XToolkit.isToolkitThread());
493        } catch (IOException ioe) {
494            return false;
495        }
496
497        if (byteData == null) {
498            return false;
499        }
500
501        count = byteData.length;
502
503        try {
504            if (count > 0) {
505                if (count <= MAX_PROPERTY_SIZE) {
506                    nativeDataPtr = Native.toData(byteData);
507                } else {
508                    // Initiate incremental data transfer.
509                    new IncrementalDataProvider(requestor, property, format, 8,
510                                                byteData);
511
512                    nativeDataPtr =
513                        XlibWrapper.unsafe.allocateMemory(XAtom.getAtomSize());
514
515                    Native.putLong(nativeDataPtr, (long)count);
516
517                    format = XDataTransferer.INCR_ATOM.getAtom();
518                    dataFormat = 32;
519                    count = 1;
520                }
521
522            }
523
524            XToolkit.awtLock();
525            try {
526                XlibWrapper.XChangeProperty(XToolkit.getDisplay(), requestor, property,
527                                            format, dataFormat,
528                                            XConstants.PropModeReplace,
529                                            nativeDataPtr, count);
530            } finally {
531                XToolkit.awtUnlock();
532            }
533        } finally {
534            if (nativeDataPtr != 0) {
535                XlibWrapper.unsafe.freeMemory(nativeDataPtr);
536                nativeDataPtr = 0;
537            }
538        }
539
540        return true;
541    }
542
543    private void handleSelectionRequest(XSelectionRequestEvent xsre) {
544        long property = xsre.get_property();
545        final long requestor = xsre.get_requestor();
546        final long requestTime = xsre.get_time();
547        final long format = xsre.get_target();
548        boolean conversionSucceeded = false;
549
550        if (ownershipTime != 0 &&
551            (requestTime == XConstants.CurrentTime || requestTime >= ownershipTime))
552        {
553            // Handle MULTIPLE requests as per ICCCM.
554            if (format == XDataTransferer.MULTIPLE_ATOM.getAtom()) {
555                conversionSucceeded = handleMultipleRequest(requestor, property);
556            } else {
557                // Support for obsolete clients as per ICCCM.
558                if (property == XConstants.None) {
559                    property = format;
560                }
561
562                if (format == XDataTransferer.TARGETS_ATOM.getAtom()) {
563                    conversionSucceeded = handleTargetsRequest(property, requestor);
564                } else {
565                    conversionSucceeded = convertAndStore(requestor, format, property);
566                }
567            }
568        }
569
570        if (!conversionSucceeded) {
571            // None property indicates conversion failure.
572            property = XConstants.None;
573        }
574
575        XSelectionEvent xse = new XSelectionEvent();
576        try {
577            xse.set_type(XConstants.SelectionNotify);
578            xse.set_send_event(true);
579            xse.set_requestor(requestor);
580            xse.set_selection(selectionAtom.getAtom());
581            xse.set_target(format);
582            xse.set_property(property);
583            xse.set_time(requestTime);
584
585            XToolkit.awtLock();
586            try {
587                XlibWrapper.XSendEvent(XToolkit.getDisplay(), requestor, false,
588                                       XConstants.NoEventMask, xse.pData);
589            } finally {
590                XToolkit.awtUnlock();
591            }
592        } finally {
593            xse.dispose();
594        }
595    }
596
597    private boolean handleMultipleRequest(final long requestor, long property) {
598        if (XConstants.None == property) {
599            // The property cannot be None for a MULTIPLE request.
600            return false;
601        }
602
603        boolean conversionSucceeded = false;
604
605        // First retrieve the list of requested targets.
606        WindowPropertyGetter wpg =
607                new WindowPropertyGetter(requestor, XAtom.get(property),
608                                         0, MAX_LENGTH, false,
609                                         XConstants.AnyPropertyType);
610        try {
611            wpg.execute();
612
613            if (wpg.getActualFormat() == 32 && (wpg.getNumberOfItems() % 2) == 0) {
614                final long count = wpg.getNumberOfItems() / 2;
615                final long pairsPtr = wpg.getData();
616                boolean writeBack = false;
617
618                for (int i = 0; i < count; i++) {
619                    long target = Native.getLong(pairsPtr, 2 * i);
620                    long prop = Native.getLong(pairsPtr, 2 * i + 1);
621
622                    if (!convertAndStore(requestor, target, prop)) {
623                        // To report failure, we should replace the
624                        // target atom with 0 in the MULTIPLE property.
625                        Native.putLong(pairsPtr, 2 * i, 0);
626                        writeBack = true;
627                    }
628                }
629                if (writeBack) {
630                    XToolkit.awtLock();
631                    try {
632                        XlibWrapper.XChangeProperty(XToolkit.getDisplay(),
633                                                    requestor,
634                                                    property,
635                                                    wpg.getActualType(),
636                                                    wpg.getActualFormat(),
637                                                                XConstants.PropModeReplace,
638                                                    wpg.getData(),
639                                                    wpg.getNumberOfItems());
640                    } finally {
641                        XToolkit.awtUnlock();
642                    }
643                }
644                conversionSucceeded = true;
645            }
646        } finally {
647            wpg.dispose();
648        }
649
650        return conversionSucceeded;
651    }
652
653    private boolean handleTargetsRequest(long property, long requestor)
654            throws IllegalStateException
655    {
656        boolean conversionSucceeded = false;
657        // Use a local copy to avoid synchronization.
658        long[] formatsLocal = formats;
659
660        if (formatsLocal == null) {
661            throw new IllegalStateException("Not an owner.");
662        }
663
664        long nativeDataPtr = 0;
665
666        try {
667            final int count = formatsLocal.length;
668            final int dataFormat = 32;
669
670            if (count > 0) {
671                nativeDataPtr = Native.allocateLongArray(count);
672                Native.put(nativeDataPtr, formatsLocal);
673            }
674
675            conversionSucceeded = true;
676
677            XToolkit.awtLock();
678            try {
679                XlibWrapper.XChangeProperty(XToolkit.getDisplay(), requestor,
680                                            property, XAtom.XA_ATOM, dataFormat,
681                                            XConstants.PropModeReplace,
682                                            nativeDataPtr, count);
683            } finally {
684                XToolkit.awtUnlock();
685            }
686        } finally {
687            if (nativeDataPtr != 0) {
688                XlibWrapper.unsafe.freeMemory(nativeDataPtr);
689                nativeDataPtr = 0;
690            }
691        }
692        return conversionSucceeded;
693    }
694
695    private void fireOwnershipChanges(final boolean isOwner) {
696        OwnershipListener l = null;
697        synchronized (stateLock) {
698            l = ownershipListener;
699        }
700        if (null != l) {
701            l.ownershipChanged(isOwner);
702        }
703    }
704
705    void registerOwershipListener(OwnershipListener l) {
706        synchronized (stateLock) {
707            ownershipListener = l;
708        }
709    }
710
711    void unregisterOwnershipListener() {
712        synchronized (stateLock) {
713            ownershipListener = null;
714        }
715    }
716
717    private static class SelectionEventHandler implements XEventDispatcher {
718        public void dispatchEvent(XEvent ev) {
719            switch (ev.get_type()) {
720            case XConstants.SelectionNotify: {
721                XToolkit.awtLock();
722                try {
723                    XSelectionEvent xse = ev.get_xselection();
724                    // Ignore the SelectionNotify event if it is not the response to our last request.
725                    if (propertyGetter != null && xse.get_time() == lastRequestServerTime) {
726                        // The property will be None in case of convertion failure.
727                        if (xse.get_property() == selectionPropertyAtom.getAtom()) {
728                            propertyGetter.execute();
729                            propertyGetter = null;
730                        } else if (xse.get_property() == 0) {
731                            propertyGetter.dispose();
732                            propertyGetter = null;
733                        }
734                    }
735                    XToolkit.awtLockNotifyAll();
736                } finally {
737                    XToolkit.awtUnlock();
738                }
739                break;
740            }
741            case XConstants.SelectionRequest: {
742                XSelectionRequestEvent xsre = ev.get_xselectionrequest();
743                long atom = xsre.get_selection();
744                XSelection selection = XSelection.getSelection(XAtom.get(atom));
745
746                if (selection != null) {
747                    selection.handleSelectionRequest(xsre);
748                }
749                break;
750            }
751            case XConstants.SelectionClear: {
752                XSelectionClearEvent xsce = ev.get_xselectionclear();
753                long atom = xsce.get_selection();
754                XSelection selection = XSelection.getSelection(XAtom.get(atom));
755
756                if (selection != null) {
757                    selection.lostOwnership();
758                }
759
760                XToolkit.awtLock();
761                try {
762                    XToolkit.awtLockNotifyAll();
763                } finally {
764                    XToolkit.awtUnlock();
765                }
766                break;
767            }
768            }
769        }
770    };
771
772    private static class IncrementalDataProvider implements XEventDispatcher {
773        private final long requestor;
774        private final long property;
775        private final long target;
776        private final int format;
777        private final byte[] data;
778        private int offset = 0;
779
780        // NOTE: formats other than 8 are not supported.
781        public IncrementalDataProvider(long requestor, long property,
782                                       long target, int format, byte[] data) {
783            if (format != 8) {
784                throw new IllegalArgumentException("Unsupported format: " + format);
785            }
786
787            this.requestor = requestor;
788            this.property = property;
789            this.target = target;
790            this.format = format;
791            this.data = data;
792
793            XWindowAttributes wattr = new XWindowAttributes();
794            try {
795                XToolkit.awtLock();
796                try {
797                    XlibWrapper.XGetWindowAttributes(XToolkit.getDisplay(), requestor,
798                                                     wattr.pData);
799                    XlibWrapper.XSelectInput(XToolkit.getDisplay(), requestor,
800                                             wattr.get_your_event_mask() |
801                                             XConstants.PropertyChangeMask);
802                } finally {
803                    XToolkit.awtUnlock();
804                }
805            } finally {
806                wattr.dispose();
807            }
808            XToolkit.addEventDispatcher(requestor, this);
809        }
810
811        public void dispatchEvent(XEvent ev) {
812            switch (ev.get_type()) {
813            case XConstants.PropertyNotify:
814                XPropertyEvent xpe = ev.get_xproperty();
815                if (xpe.get_window() == requestor &&
816                    xpe.get_state() == XConstants.PropertyDelete &&
817                    xpe.get_atom() == property) {
818
819                    int count = data.length - offset;
820                    long nativeDataPtr = 0;
821                    if (count > MAX_PROPERTY_SIZE) {
822                        count = MAX_PROPERTY_SIZE;
823                    }
824
825                    if (count > 0) {
826                        nativeDataPtr = XlibWrapper.unsafe.allocateMemory(count);
827                        for (int i = 0; i < count; i++) {
828                            Native.putByte(nativeDataPtr+i, data[offset + i]);
829                        }
830                    } else {
831                        assert (count == 0);
832                        // All data has been transferred.
833                        // This zero-length data indicates end of transfer.
834                        XToolkit.removeEventDispatcher(requestor, this);
835                    }
836
837                    XToolkit.awtLock();
838                    try {
839                        XlibWrapper.XChangeProperty(XToolkit.getDisplay(),
840                                                    requestor, property,
841                                                    target, format,
842                                                    XConstants.PropModeReplace,
843                                                    nativeDataPtr, count);
844                    } finally {
845                        XToolkit.awtUnlock();
846                    }
847                    if (nativeDataPtr != 0) {
848                        XlibWrapper.unsafe.freeMemory(nativeDataPtr);
849                        nativeDataPtr = 0;
850                    }
851
852                    offset += count;
853                }
854            }
855        }
856    }
857
858    private static class IncrementalTransferHandler implements XEventDispatcher {
859        public void dispatchEvent(XEvent ev) {
860            switch (ev.get_type()) {
861            case XConstants.PropertyNotify:
862                XPropertyEvent xpe = ev.get_xproperty();
863                if (xpe.get_state() == XConstants.PropertyNewValue &&
864                    xpe.get_atom() == selectionPropertyAtom.getAtom()) {
865                    XToolkit.awtLock();
866                    try {
867                        if (propertyGetter != null) {
868                            propertyGetter.execute();
869                            propertyGetter = null;
870                        }
871                        XToolkit.awtLockNotifyAll();
872                    } finally {
873                        XToolkit.awtUnlock();
874                    }
875                }
876                break;
877            }
878        }
879    };
880}
881