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 
  30 import java.io.ByteArrayOutputStream;
  31 import java.io.IOException;
  32 
  33 import java.util.Hashtable;
  34 import java.util.Map;
  35 
  36 import sun.awt.AppContext;
  37 import sun.awt.SunToolkit;
  38 import sun.awt.UNIXToolkit;
  39 
  40 import sun.awt.datatransfer.DataTransferer;
  41 
  42 /**
  43  * A class which interfaces with the X11 selection service.
  44  */
  45 public final class XSelection {
  46 
  47     /* Maps atoms to XSelection instances. */
  48     private static final Hashtable<XAtom, XSelection> table = new Hashtable<XAtom, XSelection>();
  49     /* Prevents from parallel selection data request processing. */
  50     private static final Object lock = new Object();
  51     /* The property in which the owner should place the requested data. */
  52     private static final XAtom selectionPropertyAtom = XAtom.get("XAWT_SELECTION");
  53     /* The maximal length of the property data. */
  54     public static final long MAX_LENGTH = 1000000;
  55     /*
  56      * The maximum data size for ChangeProperty request.
  57      * 100 is for the structure prepended to the request.
  58      */
  59     public static final int MAX_PROPERTY_SIZE;
  60     static {
  61         XToolkit.awtLock();
  62         try {
  63             MAX_PROPERTY_SIZE =
  64                 (int)(XlibWrapper.XMaxRequestSize(XToolkit.getDisplay()) * 4 - 100);
  65         } finally {
  66             XToolkit.awtUnlock();
  67         }
  68     }
  69 
  70     /* The PropertyNotify event handler for incremental data transfer. */
  71     private static final XEventDispatcher incrementalTransferHandler =
  72         new IncrementalTransferHandler();
  73     /* The context for the current request - protected with awtLock. */
  74     private static WindowPropertyGetter propertyGetter = null;
  75 
  76     // The orders of the lock acquisition:
  77     //   XClipboard -> XSelection -> awtLock.
  78     //   lock -> awtLock.
  79 
  80     /* The X atom for the underlying selection. */
  81     private final XAtom selectionAtom;
  82 
  83     /*
  84      * Owner-related variables - protected with synchronized (this).
  85      */
  86 
  87     /* The contents supplied by the current owner. */
  88     private Transferable contents = null;
  89     /* The format-to-flavor map for the current owner. */
  90     private Map formatMap = null;
  91     /* The formats supported by the current owner was set. */
  92     private long[] formats = null;
  93     /* The AppContext in which the current owner was set. */
  94     private AppContext appContext = null;
  95     // The X server time of the last XConvertSelection() call;
  96     // protected with 'lock' and awtLock.
  97     private static long lastRequestServerTime;
  98     /* The time at which the current owner was set. */
  99     private long ownershipTime = 0;
 100     // True if we are the owner of this selection.
 101     private boolean isOwner;
 102     private OwnershipListener ownershipListener = null;
 103     private final Object stateLock = new Object();
 104 
 105     static {
 106         XToolkit.addEventDispatcher(XWindow.getXAWTRootWindow().getWindow(),
 107                                     new SelectionEventHandler());
 108     }
 109 
 110     /*
 111      * Returns the XSelection object for the specified selection atom or
 112      * <code>null</code> if none exists.
 113      */
 114     static XSelection getSelection(XAtom atom) {
 115         return table.get(atom);
 116     }
 117 
 118     /**
 119      * Creates a selection object.
 120      *
 121      * @param atom   the selection atom.
 122      * @param clpbrd the corresponding clipoboard
 123      * @exception NullPointerException if atom is <code>null</code>.
 124      */
 125     public 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     public synchronized boolean setOwner(Transferable contents, Map formatMap,
 138                                          long[] formats, long time)
 139     {
 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     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 }