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