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