Home | History | Annotate | Download | only in x
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "ui/base/x/selection_owner.h"
      6 
      7 #include <algorithm>
      8 #include <X11/Xlib.h>
      9 #include <X11/Xatom.h>
     10 
     11 #include "base/logging.h"
     12 #include "ui/base/x/selection_utils.h"
     13 #include "ui/base/x/x11_foreign_window_manager.h"
     14 #include "ui/base/x/x11_util.h"
     15 
     16 namespace ui {
     17 
     18 namespace {
     19 
     20 const char kAtomPair[] = "ATOM_PAIR";
     21 const char kIncr[] = "INCR";
     22 const char kMultiple[] = "MULTIPLE";
     23 const char kSaveTargets[] = "SAVE_TARGETS";
     24 const char kTargets[] = "TARGETS";
     25 
     26 const char* kAtomsToCache[] = {
     27   kAtomPair,
     28   kIncr,
     29   kMultiple,
     30   kSaveTargets,
     31   kTargets,
     32   NULL
     33 };
     34 
     35 // The period of |incremental_transfer_abort_timer_|. Arbitrary but must be <=
     36 // than kIncrementalTransferTimeoutMs.
     37 const int kTimerPeriodMs = 1000;
     38 
     39 // The amount of time to wait for the selection requestor to process the data
     40 // sent by the selection owner before aborting an incremental data transfer.
     41 const int kIncrementalTransferTimeoutMs = 10000;
     42 
     43 COMPILE_ASSERT(kTimerPeriodMs <= kIncrementalTransferTimeoutMs,
     44                timer_period_must_be_less_or_equal_to_transfer_timeout);
     45 
     46 // Returns a conservative max size of the data we can pass into
     47 // XChangeProperty(). Copied from GTK.
     48 size_t GetMaxRequestSize(XDisplay* display) {
     49   long extended_max_size = XExtendedMaxRequestSize(display);
     50   long max_size =
     51       (extended_max_size ? extended_max_size : XMaxRequestSize(display)) - 100;
     52   return std::min(static_cast<long>(0x40000),
     53                   std::max(static_cast<long>(0), max_size));
     54 }
     55 
     56 // Gets the value of an atom pair array property. On success, true is returned
     57 // and the value is stored in |value|.
     58 bool GetAtomPairArrayProperty(XID window,
     59                               XAtom property,
     60                               std::vector<std::pair<XAtom,XAtom> >* value) {
     61   XAtom type = None;
     62   int format = 0;  // size in bits of each item in 'property'
     63   unsigned long num_items = 0;
     64   unsigned char* properties = NULL;
     65   unsigned long remaining_bytes = 0;
     66 
     67   int result = XGetWindowProperty(gfx::GetXDisplay(),
     68                                   window,
     69                                   property,
     70                                   0,          // offset into property data to
     71                                               // read
     72                                   (~0L),      // entire array
     73                                   False,      // deleted
     74                                   AnyPropertyType,
     75                                   &type,
     76                                   &format,
     77                                   &num_items,
     78                                   &remaining_bytes,
     79                                   &properties);
     80 
     81   if (result != Success)
     82     return false;
     83 
     84   // GTK does not require |type| to be kAtomPair.
     85   if (format != 32 || num_items % 2 != 0) {
     86     XFree(properties);
     87     return false;
     88   }
     89 
     90   XAtom* atom_properties = reinterpret_cast<XAtom*>(properties);
     91   value->clear();
     92   for (size_t i = 0; i < num_items; i+=2)
     93     value->push_back(std::make_pair(atom_properties[i], atom_properties[i+1]));
     94   XFree(properties);
     95   return true;
     96 }
     97 
     98 }  // namespace
     99 
    100 SelectionOwner::SelectionOwner(XDisplay* x_display,
    101                                XID x_window,
    102                                XAtom selection_name)
    103     : x_display_(x_display),
    104       x_window_(x_window),
    105       selection_name_(selection_name),
    106       max_request_size_(GetMaxRequestSize(x_display)),
    107       atom_cache_(x_display_, kAtomsToCache) {
    108 }
    109 
    110 SelectionOwner::~SelectionOwner() {
    111   // If we are the selection owner, we need to release the selection so we
    112   // don't receive further events. However, we don't call ClearSelectionOwner()
    113   // because we don't want to do this indiscriminately.
    114   if (XGetSelectionOwner(x_display_, selection_name_) == x_window_)
    115     XSetSelectionOwner(x_display_, selection_name_, None, CurrentTime);
    116 }
    117 
    118 void SelectionOwner::RetrieveTargets(std::vector<XAtom>* targets) {
    119   for (SelectionFormatMap::const_iterator it = format_map_.begin();
    120        it != format_map_.end(); ++it) {
    121     targets->push_back(it->first);
    122   }
    123 }
    124 
    125 void SelectionOwner::TakeOwnershipOfSelection(
    126     const SelectionFormatMap& data) {
    127   XSetSelectionOwner(x_display_, selection_name_, x_window_, CurrentTime);
    128 
    129   if (XGetSelectionOwner(x_display_, selection_name_) == x_window_) {
    130     // The X server agrees that we are the selection owner. Commit our data.
    131     format_map_ = data;
    132   }
    133 }
    134 
    135 void SelectionOwner::ClearSelectionOwner() {
    136   XSetSelectionOwner(x_display_, selection_name_, None, CurrentTime);
    137   format_map_ = SelectionFormatMap();
    138 }
    139 
    140 void SelectionOwner::OnSelectionRequest(const XEvent& event) {
    141   XID requestor = event.xselectionrequest.requestor;
    142   XAtom requested_target = event.xselectionrequest.target;
    143   XAtom requested_property = event.xselectionrequest.property;
    144 
    145   // Incrementally build our selection. By default this is a refusal, and we'll
    146   // override the parts indicating success in the different cases.
    147   XEvent reply;
    148   reply.xselection.type = SelectionNotify;
    149   reply.xselection.requestor = requestor;
    150   reply.xselection.selection = event.xselectionrequest.selection;
    151   reply.xselection.target = requested_target;
    152   reply.xselection.property = None;  // Indicates failure
    153   reply.xselection.time = event.xselectionrequest.time;
    154 
    155   if (requested_target == atom_cache_.GetAtom(kMultiple)) {
    156     // The contents of |requested_property| should be a list of
    157     // <target,property> pairs.
    158     std::vector<std::pair<XAtom,XAtom> > conversions;
    159     if (GetAtomPairArrayProperty(requestor,
    160                                  requested_property,
    161                                  &conversions)) {
    162       std::vector<XAtom> conversion_results;
    163       for (size_t i = 0; i < conversions.size(); ++i) {
    164         bool conversion_successful = ProcessTarget(conversions[i].first,
    165                                                    requestor,
    166                                                    conversions[i].second);
    167         conversion_results.push_back(conversions[i].first);
    168         conversion_results.push_back(
    169             conversion_successful ? conversions[i].second : None);
    170       }
    171 
    172       // Set the property to indicate which conversions succeeded. This matches
    173       // what GTK does.
    174       XChangeProperty(
    175           x_display_,
    176           requestor,
    177           requested_property,
    178           atom_cache_.GetAtom(kAtomPair),
    179           32,
    180           PropModeReplace,
    181           reinterpret_cast<const unsigned char*>(&conversion_results.front()),
    182           conversion_results.size());
    183 
    184       reply.xselection.property = requested_property;
    185     }
    186   } else {
    187     if (ProcessTarget(requested_target, requestor, requested_property))
    188       reply.xselection.property = requested_property;
    189   }
    190 
    191   // Send off the reply.
    192   XSendEvent(x_display_, requestor, False, 0, &reply);
    193 }
    194 
    195 void SelectionOwner::OnSelectionClear(const XEvent& event) {
    196   DLOG(ERROR) << "SelectionClear";
    197 
    198   // TODO(erg): If we receive a SelectionClear event while we're handling data,
    199   // we need to delay clearing.
    200 }
    201 
    202 bool SelectionOwner::CanDispatchPropertyEvent(const XEvent& event) {
    203   return event.xproperty.state == PropertyDelete &&
    204          FindIncrementalTransferForEvent(event) != incremental_transfers_.end();
    205 }
    206 
    207 void SelectionOwner::OnPropertyEvent(const XEvent& event) {
    208   std::vector<IncrementalTransfer>::iterator it =
    209       FindIncrementalTransferForEvent(event);
    210   if (it == incremental_transfers_.end())
    211     return;
    212 
    213   ProcessIncrementalTransfer(&(*it));
    214   if (!it->data.get())
    215     CompleteIncrementalTransfer(it);
    216 }
    217 
    218 bool SelectionOwner::ProcessTarget(XAtom target,
    219                                    XID requestor,
    220                                    XAtom property) {
    221   XAtom multiple_atom = atom_cache_.GetAtom(kMultiple);
    222   XAtom save_targets_atom = atom_cache_.GetAtom(kSaveTargets);
    223   XAtom targets_atom = atom_cache_.GetAtom(kTargets);
    224 
    225   if (target == multiple_atom || target == save_targets_atom)
    226     return false;
    227 
    228   if (target == targets_atom) {
    229     // We have been asked for TARGETS. Send an atom array back with the data
    230     // types we support.
    231     std::vector<XAtom> targets;
    232     targets.push_back(targets_atom);
    233     targets.push_back(save_targets_atom);
    234     targets.push_back(multiple_atom);
    235     RetrieveTargets(&targets);
    236 
    237     XChangeProperty(x_display_, requestor, property, XA_ATOM, 32,
    238                     PropModeReplace,
    239                     reinterpret_cast<unsigned char*>(&targets.front()),
    240                     targets.size());
    241     return true;
    242   } else {
    243     // Try to find the data type in map.
    244     SelectionFormatMap::const_iterator it = format_map_.find(target);
    245     if (it != format_map_.end()) {
    246       if (it->second->size() > max_request_size_) {
    247         // We must send the data back in several chunks due to a limitation in
    248         // the size of X requests. Notify the selection requestor that the data
    249         // will be sent incrementally by returning data of type "INCR".
    250         int length = it->second->size();
    251         XChangeProperty(x_display_,
    252                         requestor,
    253                         property,
    254                         atom_cache_.GetAtom(kIncr),
    255                         32,
    256                         PropModeReplace,
    257                         reinterpret_cast<unsigned char*>(&length),
    258                         1);
    259 
    260         // Wait for the selection requestor to indicate that it has processed
    261         // the selection result before sending the first chunk of data. The
    262         // selection requestor indicates this by deleting |property|.
    263         base::TimeTicks timeout =
    264             base::TimeTicks::Now() +
    265             base::TimeDelta::FromMilliseconds(kIncrementalTransferTimeoutMs);
    266         int foreign_window_manager_id =
    267             ui::XForeignWindowManager::GetInstance()->RequestEvents(
    268                 requestor, PropertyChangeMask);
    269         incremental_transfers_.push_back(
    270             IncrementalTransfer(requestor,
    271                                 target,
    272                                 property,
    273                                 it->second,
    274                                 0,
    275                                 timeout,
    276                                 foreign_window_manager_id));
    277 
    278         // Start a timer to abort the data transfer in case that the selection
    279         // requestor does not support the INCR property or gets destroyed during
    280         // the data transfer.
    281         if (!incremental_transfer_abort_timer_.IsRunning()) {
    282           incremental_transfer_abort_timer_.Start(
    283               FROM_HERE,
    284               base::TimeDelta::FromMilliseconds(kTimerPeriodMs),
    285               this,
    286               &SelectionOwner::AbortStaleIncrementalTransfers);
    287         }
    288       } else {
    289         XChangeProperty(
    290             x_display_,
    291             requestor,
    292             property,
    293             target,
    294             8,
    295             PropModeReplace,
    296             const_cast<unsigned char*>(it->second->front()),
    297             it->second->size());
    298       }
    299       return true;
    300     }
    301     // I would put error logging here, but GTK ignores TARGETS and spams us
    302     // looking for its own internal types.
    303   }
    304   return false;
    305 }
    306 
    307 void SelectionOwner::ProcessIncrementalTransfer(IncrementalTransfer* transfer) {
    308   size_t remaining = transfer->data->size() - transfer->offset;
    309   size_t chunk_length = std::min(remaining, max_request_size_);
    310   XChangeProperty(
    311       x_display_,
    312       transfer->window,
    313       transfer->property,
    314       transfer->target,
    315       8,
    316       PropModeReplace,
    317       const_cast<unsigned char*>(transfer->data->front() + transfer->offset),
    318       chunk_length);
    319   transfer->offset += chunk_length;
    320   transfer->timeout = base::TimeTicks::Now() +
    321       base::TimeDelta::FromMilliseconds(kIncrementalTransferTimeoutMs);
    322 
    323   // When offset == data->size(), we still need to transfer a zero-sized chunk
    324   // to notify the selection requestor that the transfer is complete. Clear
    325   // transfer->data once the zero-sized chunk is sent to indicate that state
    326   // related to this data transfer can be cleared.
    327   if (chunk_length == 0)
    328     transfer->data = NULL;
    329 }
    330 
    331 void SelectionOwner::AbortStaleIncrementalTransfers() {
    332   base::TimeTicks now = base::TimeTicks::Now();
    333   for (int i = static_cast<int>(incremental_transfers_.size()) - 1;
    334        i >= 0; --i) {
    335     if (incremental_transfers_[i].timeout <= now)
    336       CompleteIncrementalTransfer(incremental_transfers_.begin() + i);
    337   }
    338 }
    339 
    340 void SelectionOwner::CompleteIncrementalTransfer(
    341     std::vector<IncrementalTransfer>::iterator it) {
    342   ui::XForeignWindowManager::GetInstance()->CancelRequest(
    343       it->foreign_window_manager_id);
    344   incremental_transfers_.erase(it);
    345 
    346   if (incremental_transfers_.empty())
    347     incremental_transfer_abort_timer_.Stop();
    348 }
    349 
    350 std::vector<SelectionOwner::IncrementalTransfer>::iterator
    351     SelectionOwner::FindIncrementalTransferForEvent(const XEvent& event) {
    352   for (std::vector<IncrementalTransfer>::iterator it =
    353            incremental_transfers_.begin();
    354        it != incremental_transfers_.end();
    355        ++it) {
    356     if (it->window == event.xproperty.window &&
    357         it->property == event.xproperty.atom) {
    358       return it;
    359     }
    360   }
    361   return incremental_transfers_.end();
    362 }
    363 
    364 SelectionOwner::IncrementalTransfer::IncrementalTransfer(
    365     XID window,
    366     XAtom target,
    367     XAtom property,
    368     const scoped_refptr<base::RefCountedMemory>& data,
    369     int offset,
    370     base::TimeTicks timeout,
    371     int foreign_window_manager_id)
    372     : window(window),
    373       target(target),
    374       property(property),
    375       data(data),
    376       offset(offset),
    377       timeout(timeout),
    378       foreign_window_manager_id(foreign_window_manager_id) {
    379 }
    380 
    381 SelectionOwner::IncrementalTransfer::~IncrementalTransfer() {
    382 }
    383 
    384 }  // namespace ui
    385