clipboard_aurax11.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 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/clipboard/clipboard.h" 6 7#include <X11/extensions/Xfixes.h> 8#include <X11/Xatom.h> 9#include <list> 10#include <set> 11 12#include "base/basictypes.h" 13#include "base/files/file_path.h" 14#include "base/i18n/icu_string_conversions.h" 15#include "base/logging.h" 16#include "base/memory/scoped_ptr.h" 17#include "base/memory/singleton.h" 18#include "base/message_pump_aurax11.h" 19#include "base/message_pump_observer.h" 20#include "base/run_loop.h" 21#include "base/stl_util.h" 22#include "base/utf_string_conversions.h" 23#include "third_party/skia/include/core/SkBitmap.h" 24#include "ui/base/clipboard/custom_data_helper.h" 25#include "ui/base/x/x11_atom_cache.h" 26#include "ui/base/x/x11_util.h" 27 28#include "ui/gfx/size.h" 29 30namespace ui { 31 32namespace { 33 34const char kChromeSelection[] = "CHROME_SELECTION"; 35const char kClipboard[] = "CLIPBOARD"; 36const char kMimeTypeBitmap[] = "image/bmp"; 37const char kMimeTypeFilename[] = "chromium/filename"; 38const char kMimeTypeMozillaURL[] = "text/x-moz-url"; 39const char kMimeTypePepperCustomData[] = "chromium/x-pepper-custom-data"; 40const char kMimeTypeWebkitSmartPaste[] = "chromium/x-webkit-paste"; 41const char kMultiple[] = "MULTIPLE"; 42const char kSourceTagType[] = "org.chromium.source-tag"; 43const char kString[] = "STRING"; 44const char kTargets[] = "TARGETS"; 45const char kText[] = "TEXT"; 46const char kUtf8String[] = "UTF8_STRING"; 47 48const char* kAtomsToCache[] = { 49 kChromeSelection, 50 kClipboard, 51 kMimeTypeBitmap, 52 kMimeTypeFilename, 53 kMimeTypeMozillaURL, 54 kMimeTypeWebkitSmartPaste, 55 kMultiple, 56 kSourceTagType, 57 kString, 58 kTargets, 59 kText, 60 kUtf8String, 61 NULL 62}; 63 64/////////////////////////////////////////////////////////////////////////////// 65 66// Returns a list of all text atoms that we handle. 67std::vector< ::Atom> GetTextAtomsFrom(const X11AtomCache* atom_cache) { 68 std::vector< ::Atom> atoms; 69 atoms.push_back(atom_cache->GetAtom(kUtf8String)); 70 atoms.push_back(atom_cache->GetAtom(kString)); 71 atoms.push_back(atom_cache->GetAtom(kText)); 72 return atoms; 73} 74 75/////////////////////////////////////////////////////////////////////////////// 76 77// Uses the XFixes API to provide sequence numbers for GetSequenceNumber(). 78class SelectionChangeObserver : public base::MessagePumpObserver { 79 public: 80 static SelectionChangeObserver* GetInstance(); 81 82 uint64 clipboard_sequence_number() const { 83 return clipboard_sequence_number_; 84 } 85 uint64 primary_sequence_number() const { return primary_sequence_number_; } 86 87 private: 88 friend struct DefaultSingletonTraits<SelectionChangeObserver>; 89 90 SelectionChangeObserver(); 91 ~SelectionChangeObserver(); 92 93 // Overridden from base::MessagePumpObserver: 94 virtual base::EventStatus WillProcessEvent( 95 const base::NativeEvent& event) OVERRIDE; 96 virtual void DidProcessEvent( 97 const base::NativeEvent& event) OVERRIDE {} 98 99 int event_base_; 100 Atom clipboard_atom_; 101 uint64 clipboard_sequence_number_; 102 uint64 primary_sequence_number_; 103 104 DISALLOW_COPY_AND_ASSIGN(SelectionChangeObserver); 105}; 106 107SelectionChangeObserver::SelectionChangeObserver() 108 : event_base_(-1), 109 clipboard_atom_(None), 110 clipboard_sequence_number_(0), 111 primary_sequence_number_(0) { 112 int ignored; 113 if (XFixesQueryExtension(GetXDisplay(), &event_base_, &ignored)) { 114 clipboard_atom_ = XInternAtom(GetXDisplay(), kClipboard, false); 115 XFixesSelectSelectionInput(GetXDisplay(), GetX11RootWindow(), 116 clipboard_atom_, 117 XFixesSetSelectionOwnerNotifyMask | 118 XFixesSelectionWindowDestroyNotifyMask | 119 XFixesSelectionClientCloseNotifyMask); 120 // This seems to be semi-optional. For some reason, registering for any 121 // selection notify events seems to subscribe us to events for both the 122 // primary and the clipboard buffers. Register anyway just to be safe. 123 XFixesSelectSelectionInput(GetXDisplay(), GetX11RootWindow(), 124 XA_PRIMARY, 125 XFixesSetSelectionOwnerNotifyMask | 126 XFixesSelectionWindowDestroyNotifyMask | 127 XFixesSelectionClientCloseNotifyMask); 128 129 base::MessagePumpAuraX11::Current()->AddObserver(this); 130 } 131} 132 133SelectionChangeObserver::~SelectionChangeObserver() { 134 // We are a singleton; we will outlive our message pump. 135} 136 137SelectionChangeObserver* SelectionChangeObserver::GetInstance() { 138 return Singleton<SelectionChangeObserver>::get(); 139} 140 141base::EventStatus SelectionChangeObserver::WillProcessEvent( 142 const base::NativeEvent& event) { 143 if (event->type == event_base_ + XFixesSelectionNotify) { 144 XFixesSelectionNotifyEvent* ev = 145 reinterpret_cast<XFixesSelectionNotifyEvent*>(event); 146 if (ev->selection == clipboard_atom_) { 147 clipboard_sequence_number_++; 148 } else if (ev->selection == XA_PRIMARY) { 149 primary_sequence_number_++; 150 } else { 151 DLOG(ERROR) << "Unexpected selection atom: " << ev->selection; 152 } 153 } 154 return base::EVENT_CONTINUE; 155} 156 157/////////////////////////////////////////////////////////////////////////////// 158 159// Represents the selection in different data formats. Binary data passed in is 160// assumed to be allocated with new char[], and is owned by FormatMap. 161class FormatMap { 162 public: 163 // Our internal data store, which we only expose through iterators. 164 typedef std::map< ::Atom, std::pair<char*, size_t> > InternalMap; 165 typedef std::map< ::Atom, std::pair<char*, size_t> >::const_iterator 166 const_iterator; 167 168 FormatMap(); 169 ~FormatMap(); 170 171 // Adds the selection in the format |atom|. Ownership of |data| is passed to 172 // us. 173 void Insert(::Atom atom, char* data, size_t size); 174 175 // Pass through to STL map. Only allow non-mutation access. 176 const_iterator begin() const { return data_.begin(); } 177 const_iterator end() const { return data_.end(); } 178 const_iterator find(::Atom atom) const { return data_.find(atom); } 179 size_t size() const { return data_.size(); } 180 181 private: 182 InternalMap data_; 183 184 DISALLOW_COPY_AND_ASSIGN(FormatMap); 185}; 186 187FormatMap::FormatMap() {} 188 189FormatMap::~FormatMap() { 190 // WriteText() inserts the same pointer multiple times for different 191 // representations; we need to dedupe it. 192 std::set<char*> to_delete; 193 for (InternalMap::iterator it = data_.begin(); it != data_.end(); ++it) 194 to_delete.insert(it->second.first); 195 196 for (std::set<char*>::iterator it = to_delete.begin(); it != to_delete.end(); 197 ++it) { 198 delete [] *it; 199 } 200} 201 202void FormatMap::Insert(::Atom atom, char* data, size_t size) { 203 DCHECK(data_.find(atom) == data_.end()); 204 data_.insert(std::make_pair(atom, std::make_pair(data, size))); 205} 206 207/////////////////////////////////////////////////////////////////////////////// 208 209// Represents a list of possible return types. Copy constructable. 210class TargetList { 211 public: 212 typedef std::vector< ::Atom> AtomVector; 213 214 TargetList(const AtomVector& target_list, X11AtomCache* atom_cache); 215 216 bool ContainsText() const; 217 bool ContainsFormat(const Clipboard::FormatType& format_type) const; 218 bool ContainsAtom(::Atom atom) const; 219 220 private: 221 AtomVector target_list_; 222 X11AtomCache* atom_cache_; 223}; 224 225TargetList::TargetList(const AtomVector& target_list, 226 X11AtomCache* atom_cache) 227 : target_list_(target_list), 228 atom_cache_(atom_cache) { 229} 230 231bool TargetList::ContainsText() const { 232 std::vector< ::Atom> atoms = GetTextAtomsFrom(atom_cache_); 233 for (std::vector< ::Atom>::const_iterator it = atoms.begin(); 234 it != atoms.end(); ++it) { 235 if (ContainsAtom(*it)) 236 return true; 237 } 238 239 return false; 240} 241 242bool TargetList::ContainsFormat( 243 const Clipboard::FormatType& format_type) const { 244 ::Atom atom = atom_cache_->GetAtom(format_type.ToString().c_str()); 245 return ContainsAtom(atom); 246} 247 248bool TargetList::ContainsAtom(::Atom atom) const { 249 return find(target_list_.begin(), target_list_.end(), atom) 250 != target_list_.end(); 251} 252 253/////////////////////////////////////////////////////////////////////////////// 254 255// A holder for data with optional X11 deletion semantics. 256class SelectionData { 257 public: 258 // |atom_cache| is still owned by caller. 259 explicit SelectionData(X11AtomCache* atom_cache); 260 ~SelectionData(); 261 262 ::Atom type() const { return type_; } 263 char* data() const { return data_; } 264 size_t size() const { return size_; } 265 266 void Set(::Atom type, char* data, size_t size, bool owned); 267 268 // If |type_| is a string type, convert the data to UTF8 and return it. 269 std::string GetText() const; 270 271 // Assigns the raw data to the string. 272 void AssignTo(std::string* result) const; 273 274 private: 275 ::Atom type_; 276 char* data_; 277 size_t size_; 278 bool owned_; 279 280 X11AtomCache* atom_cache_; 281}; 282 283SelectionData::SelectionData(X11AtomCache* atom_cache) 284 : type_(None), 285 data_(NULL), 286 size_(0), 287 owned_(false), 288 atom_cache_(atom_cache) { 289} 290 291SelectionData::~SelectionData() { 292 if (owned_) 293 XFree(data_); 294} 295 296void SelectionData::Set(::Atom type, char* data, size_t size, bool owned) { 297 if (owned_) 298 XFree(data_); 299 300 type_ = type; 301 data_ = data; 302 size_ = size; 303 owned_ = owned; 304} 305 306std::string SelectionData::GetText() const { 307 if (type_ == atom_cache_->GetAtom(kUtf8String) || 308 type_ == atom_cache_->GetAtom(kText)) { 309 return std::string(data_, size_); 310 } else if (type_ == atom_cache_->GetAtom(kString)) { 311 std::string result; 312 base::ConvertToUtf8AndNormalize(std::string(data_, size_), 313 base::kCodepageLatin1, 314 &result); 315 return result; 316 } else { 317 // BTW, I looked at COMPOUND_TEXT, and there's no way we're going to 318 // support that. Yuck. 319 NOTREACHED(); 320 return std::string(); 321 } 322} 323 324void SelectionData::AssignTo(std::string* result) const { 325 result->assign(data_, size_); 326} 327 328} // namespace 329 330/////////////////////////////////////////////////////////////////////////////// 331 332// I would love for the FormatType to really be a wrapper around an X11 ::Atom, 333// but there are a few problems. Chromeos unit tests spawn a new X11 server for 334// each test, so Atom numeric values don't persist across tests. We could still 335// maybe deal with that if we didn't have static accessor methods everywhere. 336 337Clipboard::FormatType::FormatType() { 338} 339 340Clipboard::FormatType::FormatType(const std::string& native_format) 341 : data_(native_format) { 342} 343 344Clipboard::FormatType::~FormatType() { 345} 346 347std::string Clipboard::FormatType::Serialize() const { 348 return data_; 349} 350 351// static 352Clipboard::FormatType Clipboard::FormatType::Deserialize( 353 const std::string& serialization) { 354 return FormatType(serialization); 355} 356 357bool Clipboard::FormatType::Equals(const FormatType& other) const { 358 return data_ == other.data_; 359} 360 361/////////////////////////////////////////////////////////////////////////////// 362// Clipboard::AuraX11Details 363 364// Private implementation of our X11 integration. Keeps X11 headers out of the 365// majority of chrome, which break badly. 366class Clipboard::AuraX11Details : public base::MessagePumpDispatcher { 367 public: 368 AuraX11Details(); 369 ~AuraX11Details(); 370 371 X11AtomCache* atom_cache() { return &atom_cache_; } 372 373 // Returns the X11 type that we pass to various XSelection functions for the 374 // given buffer. 375 ::Atom LookupSelectionForBuffer(Buffer buffer) const; 376 377 // Finds the FormatMap for the incoming selection atom. 378 FormatMap* LookupStorageForAtom(::Atom atom); 379 380 // As we need to collect all the data types before we tell X11 that we own a 381 // particular selection, we create a temporary clipboard mapping that 382 // InsertMapping writes to. Then we commit it in TakeOwnershipOfSelection, 383 // where we save it in one of the clipboard data slots. 384 void CreateNewClipboardData(); 385 386 // Inserts a mapping into clipboard_data_. 387 void InsertMapping(const std::string& key, char* data, size_t data_len); 388 389 // Moves the temporary |clipboard_data_| to the long term data storage for 390 // |buffer|. 391 void TakeOwnershipOfSelection(Buffer buffer); 392 393 // Returns the first of |types| offered by the current selection holder in 394 // |data_out|, or returns NULL if none of those types are available. 395 // 396 // If the selection holder is us, this call is synchronous and we pull 397 // the data out of |clipboard_selection_| or |primary_selection_|. If the 398 // selection holder is some other window, we spin up a nested message loop 399 // and do the asynchronous dance with whatever application is holding the 400 // selection. 401 scoped_ptr<SelectionData> RequestAndWaitForTypes( 402 Buffer buffer, 403 const std::vector< ::Atom>& types); 404 405 // Retrieves the list of possible data types the current clipboard owner has. 406 // 407 // If the selection holder is us, this is synchronous, otherwise this runs a 408 // blocking message loop. 409 TargetList WaitAndGetTargetsList(Buffer buffer); 410 411 // Does the work of requesting |target| from |selection_name|, spinning up 412 // the nested message loop, and reading the resulting data back. |out_data| 413 // is allocated with the X allocator and must be freed with 414 // XFree(). |out_data_bytes| is the length in machine chars, while 415 // |out_data_items| is the length in |out_type| items. 416 bool PerformBlockingConvertSelection(::Atom selection_name, 417 ::Atom target, 418 unsigned char** out_data, 419 size_t* out_data_bytes, 420 size_t* out_data_items, 421 ::Atom* out_type); 422 423 // Returns a list of all text atoms that we handle. 424 std::vector< ::Atom> GetTextAtoms() const; 425 426 // Returns a vector with a |format| converted to an X11 atom. 427 std::vector< ::Atom> GetAtomsForFormat(const Clipboard::FormatType& format); 428 429 // Clears a certain data buffer. 430 void Clear(Buffer buffer); 431 432 private: 433 // Called by Dispatch to handle specific types of events. 434 void HandleSelectionRequest(const XSelectionRequestEvent& event); 435 void HandleSelectionNotify(const XSelectionEvent& event); 436 void HandleSelectionClear(const XSelectionClearEvent& event); 437 void HandlePropertyNotify(const XPropertyEvent& event); 438 439 // Overridden from base::MessagePumpDispatcher: 440 virtual bool Dispatch(const base::NativeEvent& event) OVERRIDE; 441 442 // Temporary target map that we write to during DispatchObects. 443 scoped_ptr<FormatMap> clipboard_data_; 444 445 // The current value of our clipboard and primary selections. These should be 446 // non-NULL when we own the selection. 447 scoped_ptr<FormatMap> clipboard_selection_; 448 scoped_ptr<FormatMap> primary_selection_; 449 450 // Our X11 state. 451 Display* x_display_; 452 ::Window x_root_window_; 453 454 // Input-only window used as a selection owner. 455 ::Window x_window_; 456 457 // True if we're currently running a nested message loop, waiting for data to 458 // come back from the X server. 459 bool in_nested_loop_; 460 461 // Data to the current XConvertSelection request. Used for error detection; 462 // we verify it on the return message. 463 ::Atom current_selection_; 464 ::Atom current_target_; 465 466 // The property in the returning SelectNotify message is used to signal 467 // success. If None, our request failed somehow. If equal to the property 468 // atom that we sent in the XConvertSelection call, we can read that property 469 // on |x_window_| for the requested data. 470 ::Atom returned_property_; 471 472 // Called to terminate the nested message loop. 473 base::Closure quit_closure_; 474 475 X11AtomCache atom_cache_; 476 477 DISALLOW_COPY_AND_ASSIGN(AuraX11Details); 478}; 479 480Clipboard::AuraX11Details::AuraX11Details() 481 : x_display_(GetXDisplay()), 482 x_root_window_(DefaultRootWindow(x_display_)), 483 in_nested_loop_(false), 484 atom_cache_(x_display_, kAtomsToCache) { 485 // We don't know all possible MIME types at compile time. 486 atom_cache_.allow_uncached_atoms(); 487 488 x_window_ = XCreateWindow( 489 x_display_, x_root_window_, 490 -100, -100, 10, 10, // x, y, width, height 491 0, // border width 492 CopyFromParent, // depth 493 InputOnly, 494 CopyFromParent, // visual 495 0, 496 NULL); 497 XStoreName(x_display_, x_window_, "Chromium clipboard"); 498 XSelectInput(x_display_, x_window_, PropertyChangeMask); 499 500 base::MessagePumpAuraX11::Current()->AddDispatcherForWindow(this, x_window_); 501} 502 503Clipboard::AuraX11Details::~AuraX11Details() { 504 base::MessagePumpAuraX11::Current()->RemoveDispatcherForWindow(x_window_); 505 506 XDestroyWindow(x_display_, x_window_); 507} 508 509::Atom Clipboard::AuraX11Details::LookupSelectionForBuffer( 510 Buffer buffer) const { 511 if (buffer == BUFFER_STANDARD) 512 return atom_cache_.GetAtom(kClipboard); 513 else 514 return XA_PRIMARY; 515} 516 517FormatMap* Clipboard::AuraX11Details::LookupStorageForAtom(::Atom atom) { 518 if (atom == XA_PRIMARY) 519 return primary_selection_.get(); 520 else if (atom == atom_cache_.GetAtom(kClipboard)) 521 return clipboard_selection_.get(); 522 else 523 return NULL; 524} 525 526void Clipboard::AuraX11Details::CreateNewClipboardData() { 527 clipboard_data_.reset(new FormatMap); 528} 529 530void Clipboard::AuraX11Details::InsertMapping(const std::string& key, 531 char* data, 532 size_t data_len) { 533 ::Atom atom_key = atom_cache_.GetAtom(key.c_str()); 534 clipboard_data_->Insert(atom_key, data, data_len); 535} 536 537void Clipboard::AuraX11Details::TakeOwnershipOfSelection(Buffer buffer) { 538 // Tell the X server that we are now the selection owner. 539 ::Atom xselection = LookupSelectionForBuffer(buffer); 540 XSetSelectionOwner(x_display_, xselection, x_window_, CurrentTime); 541 542 if (XGetSelectionOwner(x_display_, xselection) == x_window_) { 543 // The X server agrees that we are the selection owner. Commit our data. 544 if (buffer == BUFFER_STANDARD) 545 clipboard_selection_ = clipboard_data_.Pass(); 546 else 547 primary_selection_ = clipboard_data_.Pass(); 548 } 549} 550 551scoped_ptr<SelectionData> Clipboard::AuraX11Details::RequestAndWaitForTypes( 552 Buffer buffer, 553 const std::vector< ::Atom>& types) { 554 ::Atom selection_name = LookupSelectionForBuffer(buffer); 555 if (XGetSelectionOwner(x_display_, selection_name) == x_window_) { 556 // We can local fastpath instead of playing the nested message loop game 557 // with the X server. 558 FormatMap* format_map = LookupStorageForAtom(selection_name); 559 DCHECK(format_map); 560 561 for (std::vector< ::Atom>::const_iterator it = types.begin(); 562 it != types.end(); ++it) { 563 FormatMap::const_iterator format_map_it = format_map->find(*it); 564 if (format_map_it != format_map->end()) { 565 scoped_ptr<SelectionData> data_out(new SelectionData(&atom_cache_)); 566 data_out->Set(format_map_it->first, format_map_it->second.first, 567 format_map_it->second.second, false); 568 return data_out.Pass(); 569 } 570 } 571 } else { 572 TargetList targets = WaitAndGetTargetsList(buffer); 573 574 for (std::vector< ::Atom>::const_iterator it = types.begin(); 575 it != types.end(); ++it) { 576 unsigned char* data = NULL; 577 size_t data_bytes = 0; 578 ::Atom type = None; 579 if (targets.ContainsAtom(*it) && 580 PerformBlockingConvertSelection(selection_name, 581 *it, 582 &data, 583 &data_bytes, 584 NULL, 585 &type) && 586 type == *it) { 587 scoped_ptr<SelectionData> data_out(new SelectionData(&atom_cache_)); 588 data_out->Set(type, (char*)data, data_bytes, true); 589 return data_out.Pass(); 590 } 591 } 592 } 593 594 return scoped_ptr<SelectionData>(); 595} 596 597TargetList Clipboard::AuraX11Details::WaitAndGetTargetsList( 598 Buffer buffer) { 599 ::Atom selection_name = LookupSelectionForBuffer(buffer); 600 std::vector< ::Atom> out; 601 if (XGetSelectionOwner(x_display_, selection_name) == x_window_) { 602 // We can local fastpath and return the list of local targets. 603 FormatMap* format_map = LookupStorageForAtom(selection_name); 604 DCHECK(format_map); 605 606 for (FormatMap::const_iterator it = format_map->begin(); 607 it != format_map->end(); ++it) { 608 out.push_back(it->first); 609 } 610 } else { 611 unsigned char* data = NULL; 612 size_t out_data_items = 0; 613 ::Atom out_type = None; 614 615 if (PerformBlockingConvertSelection(selection_name, 616 atom_cache_.GetAtom(kTargets), 617 &data, 618 NULL, 619 &out_data_items, 620 &out_type)) { 621 ::Atom* atom_array = reinterpret_cast< ::Atom*>(data); 622 for (size_t i = 0; i < out_data_items; ++i) 623 out.push_back(atom_array[i]); 624 625 XFree(data); 626 } else { 627 // There was no target list. Most Java apps doesn't offer a TARGETS list, 628 // even though they AWT to. They will offer individual text types if you 629 // ask. If this is the case we attempt to make sense of the contents as 630 // text. This is pretty unfortunate since it means we have to actually 631 // copy the data to see if it is available, but at least this path 632 // shouldn't be hit for conforming programs. 633 std::vector< ::Atom> types = GetTextAtoms(); 634 for (std::vector< ::Atom>::const_iterator it = types.begin(); 635 it != types.end(); ++it) { 636 ::Atom type = None; 637 if (PerformBlockingConvertSelection(selection_name, 638 *it, 639 NULL, 640 NULL, 641 NULL, 642 &type) && 643 type == *it) { 644 out.push_back(*it); 645 } 646 } 647 } 648 } 649 650 return TargetList(out, &atom_cache_); 651} 652 653bool Clipboard::AuraX11Details::PerformBlockingConvertSelection( 654 ::Atom selection_name, 655 ::Atom target, 656 unsigned char** out_data, 657 size_t* out_data_bytes, 658 size_t* out_data_items, 659 ::Atom* out_type) { 660 // The name of the property we're asking to be set on |x_window_|. 661 ::Atom property_to_set = atom_cache_.GetAtom(kChromeSelection); 662 663 XConvertSelection(x_display_, 664 selection_name, 665 target, 666 property_to_set, 667 x_window_, 668 CurrentTime); 669 670 // Now that we've thrown our message off to the X11 server, we block waiting 671 // for a response. 672 MessageLoopForUI* loop = MessageLoopForUI::current(); 673 MessageLoop::ScopedNestableTaskAllower allow_nested(loop); 674 base::RunLoop run_loop(base::MessagePumpAuraX11::Current()); 675 676 current_selection_ = selection_name; 677 current_target_ = target; 678 in_nested_loop_ = true; 679 quit_closure_ = run_loop.QuitClosure(); 680 run_loop.Run(); 681 in_nested_loop_ = false; 682 current_selection_ = None; 683 current_target_ = None; 684 685 if (returned_property_ != property_to_set) 686 return false; 687 688 // Retrieve the data from our window. 689 unsigned long nitems = 0; 690 unsigned long nbytes = 0; 691 Atom prop_type = None; 692 int prop_format = 0; 693 unsigned char* property_data = NULL; 694 if (XGetWindowProperty(x_display_, 695 x_window_, 696 returned_property_, 697 0, 0x1FFFFFFF /* MAXINT32 / 4 */, False, 698 AnyPropertyType, &prop_type, &prop_format, 699 &nitems, &nbytes, &property_data) != Success) { 700 return false; 701 } 702 703 if (prop_type == None) 704 return false; 705 706 if (out_data) 707 *out_data = property_data; 708 709 if (out_data_bytes) { 710 // So even though we should theoretically have nbytes (and we can't 711 // pass NULL there), we need to manually calculate the byte length here 712 // because nbytes always returns zero. 713 switch (prop_format) { 714 case 8: 715 *out_data_bytes = nitems; 716 break; 717 case 16: 718 *out_data_bytes = sizeof(short) * nitems; 719 break; 720 case 32: 721 *out_data_bytes = sizeof(long) * nitems; 722 break; 723 default: 724 NOTREACHED(); 725 break; 726 } 727 } 728 729 if (out_data_items) 730 *out_data_items = nitems; 731 732 if (out_type) 733 *out_type = prop_type; 734 735 return true; 736} 737 738std::vector< ::Atom> Clipboard::AuraX11Details::GetTextAtoms() const { 739 return GetTextAtomsFrom(&atom_cache_); 740} 741 742std::vector< ::Atom> Clipboard::AuraX11Details::GetAtomsForFormat( 743 const Clipboard::FormatType& format) { 744 std::vector< ::Atom> atoms; 745 atoms.push_back(atom_cache_.GetAtom(format.ToString().c_str())); 746 return atoms; 747} 748 749void Clipboard::AuraX11Details::Clear(Buffer buffer) { 750 ::Atom selection_name = LookupSelectionForBuffer(buffer); 751 if (XGetSelectionOwner(x_display_, selection_name) == x_window_) 752 XSetSelectionOwner(x_display_, selection_name, None, CurrentTime); 753 754 if (buffer == BUFFER_STANDARD) 755 clipboard_selection_.reset(); 756 else 757 primary_selection_.reset(); 758} 759 760void Clipboard::AuraX11Details::HandleSelectionRequest( 761 const XSelectionRequestEvent& event) { 762 // Incrementally build our selection. By default this is a refusal, and we'll 763 // override the parts indicating success in the different cases. 764 XEvent reply; 765 reply.xselection.type = SelectionNotify; 766 reply.xselection.requestor = event.requestor; 767 reply.xselection.selection = event.selection; 768 reply.xselection.target = event.target; 769 reply.xselection.property = None; // Indicates failure 770 reply.xselection.time = event.time; 771 772 // Get the proper selection. 773 FormatMap* format_map = LookupStorageForAtom(event.selection); 774 if (format_map) { 775 ::Atom targets_atom = atom_cache_.GetAtom(kTargets); 776 if (event.target == targets_atom) { 777 std::vector< ::Atom> targets; 778 targets.push_back(targets_atom); 779 for (FormatMap::const_iterator it = format_map->begin(); 780 it != format_map->end(); ++it) { 781 targets.push_back(it->first); 782 } 783 784 XChangeProperty(x_display_, event.requestor, event.property, XA_ATOM, 32, 785 PropModeReplace, 786 reinterpret_cast<unsigned char*>(&targets.front()), 787 targets.size()); 788 reply.xselection.property = event.property; 789 } else if (event.target == atom_cache_.GetAtom(kMultiple)) { 790 // TODO(erg): Theoretically, the spec claims I'm supposed to handle the 791 // MULTIPLE case, but I haven't seen it in the wild yet. 792 NOTIMPLEMENTED(); 793 } else { 794 // Try to find the data type in map. 795 FormatMap::const_iterator it = format_map->find(event.target); 796 if (it != format_map->end()) { 797 XChangeProperty(x_display_, event.requestor, event.property, 798 event.target, 8, 799 PropModeReplace, 800 reinterpret_cast<unsigned char*>(it->second.first), 801 it->second.second); 802 reply.xselection.property = event.property; 803 } 804 // I would put error logging here, but GTK ignores TARGETS and spams us 805 // looking for its own internal types. 806 } 807 } else { 808 DLOG(ERROR) << "Requested on a selection we don't support: " 809 << XGetAtomName(x_display_, event.selection); 810 } 811 812 // Send off the reply. 813 XSendEvent(x_display_, event.requestor, False, 0, &reply); 814} 815 816void Clipboard::AuraX11Details::HandleSelectionNotify( 817 const XSelectionEvent& event) { 818 if (!in_nested_loop_) { 819 // This shouldn't happen; we're not waiting on the X server for data, but 820 // any client can send any message... 821 return; 822 } 823 824 if (current_selection_ == event.selection && 825 current_target_ == event.target) { 826 returned_property_ = event.property; 827 } else { 828 // I am assuming that if some other client sent us a message after we've 829 // asked for data, but it's malformed, we should just treat as if they sent 830 // us an error message. 831 returned_property_ = None; 832 } 833 834 quit_closure_.Run(); 835} 836 837void Clipboard::AuraX11Details::HandleSelectionClear( 838 const XSelectionClearEvent& event) { 839 DLOG(ERROR) << "SelectionClear"; 840 841 // TODO(erg): If we receive a SelectionClear event while we're handling data, 842 // we need to delay clearing. 843} 844 845void Clipboard::AuraX11Details::HandlePropertyNotify( 846 const XPropertyEvent& event) { 847 // TODO(erg): Must handle PropertyNotify events on our |x_window_| as part 848 // of receiving data during paste. 849} 850 851bool Clipboard::AuraX11Details::Dispatch(const base::NativeEvent& event) { 852 XEvent* xev = event; 853 854 switch (xev->type) { 855 case SelectionRequest: 856 HandleSelectionRequest(xev->xselectionrequest); 857 break; 858 case SelectionNotify: 859 HandleSelectionNotify(xev->xselection); 860 break; 861 case SelectionClear: 862 HandleSelectionClear(xev->xselectionclear); 863 break; 864 case PropertyNotify: 865 HandlePropertyNotify(xev->xproperty); 866 break; 867 default: 868 break; 869 } 870 871 return true; 872} 873 874/////////////////////////////////////////////////////////////////////////////// 875// Clipboard 876 877Clipboard::Clipboard() 878 : aurax11_details_(new AuraX11Details) { 879 DCHECK(CalledOnValidThread()); 880} 881 882Clipboard::~Clipboard() { 883 DCHECK(CalledOnValidThread()); 884 885 // TODO(erg): We need to do whatever the equivalent of 886 // gtk_clipboard_store(clipboard_) is here. When we shut down, we want the 887 // current selection to live on. 888} 889 890void Clipboard::WriteObjectsImpl(Buffer buffer, 891 const ObjectMap& objects, 892 SourceTag tag) { 893 DCHECK(CalledOnValidThread()); 894 DCHECK(IsValidBuffer(buffer)); 895 896 aurax11_details_->CreateNewClipboardData(); 897 for (ObjectMap::const_iterator iter = objects.begin(); 898 iter != objects.end(); ++iter) { 899 DispatchObject(static_cast<ObjectType>(iter->first), iter->second); 900 } 901 WriteSourceTag(tag); 902 aurax11_details_->TakeOwnershipOfSelection(buffer); 903 904 if (buffer == BUFFER_STANDARD) { 905 ObjectMap::const_iterator text_iter = objects.find(CBF_TEXT); 906 if (text_iter != objects.end()) { 907 aurax11_details_->CreateNewClipboardData(); 908 const ObjectMapParam& char_vector = text_iter->second[0]; 909 WriteText(&char_vector.front(), char_vector.size()); 910 WriteSourceTag(tag); 911 aurax11_details_->TakeOwnershipOfSelection(BUFFER_SELECTION); 912 } 913 } 914} 915 916bool Clipboard::IsFormatAvailable(const FormatType& format, 917 Buffer buffer) const { 918 DCHECK(CalledOnValidThread()); 919 DCHECK(IsValidBuffer(buffer)); 920 921 TargetList target_list = aurax11_details_->WaitAndGetTargetsList(buffer); 922 return target_list.ContainsFormat(format); 923} 924 925void Clipboard::Clear(Buffer buffer) { 926 DCHECK(CalledOnValidThread()); 927 DCHECK(IsValidBuffer(buffer)); 928 aurax11_details_->Clear(buffer); 929} 930 931void Clipboard::ReadAvailableTypes(Buffer buffer, std::vector<string16>* types, 932 bool* contains_filenames) const { 933 DCHECK(CalledOnValidThread()); 934 if (!types || !contains_filenames) { 935 NOTREACHED(); 936 return; 937 } 938 939 TargetList target_list = aurax11_details_->WaitAndGetTargetsList(buffer); 940 941 types->clear(); 942 943 if (target_list.ContainsText()) 944 types->push_back(UTF8ToUTF16(kMimeTypeText)); 945 if (target_list.ContainsFormat(GetHtmlFormatType())) 946 types->push_back(UTF8ToUTF16(kMimeTypeHTML)); 947 if (target_list.ContainsFormat(GetRtfFormatType())) 948 types->push_back(UTF8ToUTF16(kMimeTypeRTF)); 949 if (target_list.ContainsFormat(GetBitmapFormatType())) 950 types->push_back(UTF8ToUTF16(kMimeTypePNG)); 951 *contains_filenames = false; 952 953 scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( 954 buffer, 955 aurax11_details_->GetAtomsForFormat(GetWebCustomDataFormatType()))); 956 if (!data.get()) 957 return; 958 959 ReadCustomDataTypes(data->data(), data->size(), types); 960} 961 962void Clipboard::ReadText(Buffer buffer, string16* result) const { 963 DCHECK(CalledOnValidThread()); 964 ReportAction(buffer, READ_TEXT); 965 966 scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( 967 buffer, aurax11_details_->GetTextAtoms())); 968 if (data.get()) { 969 std::string text = data->GetText(); 970 *result = UTF8ToUTF16(text); 971 } 972} 973 974void Clipboard::ReadAsciiText(Buffer buffer, std::string* result) const { 975 DCHECK(CalledOnValidThread()); 976 ReportAction(buffer, READ_TEXT); 977 978 scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( 979 buffer, aurax11_details_->GetTextAtoms())); 980 if (data.get()) 981 result->assign(data->GetText()); 982} 983 984// TODO(estade): handle different charsets. 985// TODO(port): set *src_url. 986void Clipboard::ReadHTML(Buffer buffer, 987 string16* markup, 988 std::string* src_url, 989 uint32* fragment_start, 990 uint32* fragment_end) const { 991 DCHECK(CalledOnValidThread()); 992 markup->clear(); 993 if (src_url) 994 src_url->clear(); 995 *fragment_start = 0; 996 *fragment_end = 0; 997 998 scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( 999 buffer, aurax11_details_->GetAtomsForFormat(GetHtmlFormatType()))); 1000 if (!data.get()) 1001 return; 1002 1003 // If the data starts with 0xFEFF, i.e., Byte Order Mark, assume it is 1004 // UTF-16, otherwise assume UTF-8. 1005 if (data->size() >= 2 && 1006 reinterpret_cast<const uint16_t*>(data->data())[0] == 0xFEFF) { 1007 markup->assign(reinterpret_cast<const uint16_t*>(data->data()) + 1, 1008 (data->size() / 2) - 1); 1009 } else { 1010 UTF8ToUTF16(reinterpret_cast<const char*>(data->data()), data->size(), 1011 markup); 1012 } 1013 1014 // If there is a terminating NULL, drop it. 1015 if (!markup->empty() && markup->at(markup->length() - 1) == '\0') 1016 markup->resize(markup->length() - 1); 1017 1018 *fragment_start = 0; 1019 DCHECK(markup->length() <= kuint32max); 1020 *fragment_end = static_cast<uint32>(markup->length()); 1021} 1022 1023void Clipboard::ReadRTF(Buffer buffer, std::string* result) const { 1024 DCHECK(CalledOnValidThread()); 1025 ReportAction(buffer, READ_TEXT); 1026 1027 scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( 1028 buffer, aurax11_details_->GetAtomsForFormat(GetRtfFormatType()))); 1029 if (data.get()) 1030 data->AssignTo(result); 1031} 1032 1033SkBitmap Clipboard::ReadImage(Buffer buffer) const { 1034 DCHECK(CalledOnValidThread()); 1035 NOTIMPLEMENTED(); 1036 return SkBitmap(); 1037} 1038 1039void Clipboard::ReadCustomData(Buffer buffer, 1040 const string16& type, 1041 string16* result) const { 1042 DCHECK(CalledOnValidThread()); 1043 1044 scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( 1045 buffer, 1046 aurax11_details_->GetAtomsForFormat(GetWebCustomDataFormatType()))); 1047 if (!data.get()) 1048 return; 1049 1050 ReadCustomDataForType(data->data(), data->size(), type, result); 1051} 1052 1053void Clipboard::ReadBookmark(string16* title, std::string* url) const { 1054 DCHECK(CalledOnValidThread()); 1055 // TODO(erg): This was left NOTIMPLEMENTED() in the gtk port too. 1056 NOTIMPLEMENTED(); 1057} 1058 1059void Clipboard::ReadData(const FormatType& format, std::string* result) const { 1060 ReadDataImpl(BUFFER_STANDARD, format, result); 1061} 1062 1063void Clipboard::ReadDataImpl(Buffer buffer, 1064 const FormatType& format, 1065 std::string* result) const { 1066 DCHECK(CalledOnValidThread()); 1067 1068 scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( 1069 buffer, aurax11_details_->GetAtomsForFormat(format))); 1070 if (data.get()) 1071 data->AssignTo(result); 1072} 1073 1074Clipboard::SourceTag Clipboard::ReadSourceTag(Buffer buffer) const { 1075 std::string result; 1076 ReadDataImpl(buffer, GetSourceTagFormatType(), &result); 1077 return Binary2SourceTag(result); 1078} 1079 1080uint64 Clipboard::GetSequenceNumber(Buffer buffer) { 1081 DCHECK(CalledOnValidThread()); 1082 if (buffer == BUFFER_STANDARD) 1083 return SelectionChangeObserver::GetInstance()->clipboard_sequence_number(); 1084 else 1085 return SelectionChangeObserver::GetInstance()->primary_sequence_number(); 1086} 1087 1088void Clipboard::WriteText(const char* text_data, size_t text_len) { 1089 char* data = new char[text_len]; 1090 memcpy(data, text_data, text_len); 1091 1092 aurax11_details_->InsertMapping(kMimeTypeText, data, text_len); 1093 aurax11_details_->InsertMapping(kText, data, text_len); 1094 aurax11_details_->InsertMapping(kString, data, text_len); 1095 aurax11_details_->InsertMapping(kUtf8String, data, text_len); 1096} 1097 1098void Clipboard::WriteHTML(const char* markup_data, 1099 size_t markup_len, 1100 const char* url_data, 1101 size_t url_len) { 1102 // TODO(estade): We need to expand relative links with |url_data|. 1103 static const char* html_prefix = "<meta http-equiv=\"content-type\" " 1104 "content=\"text/html; charset=utf-8\">"; 1105 size_t html_prefix_len = strlen(html_prefix); 1106 size_t total_len = html_prefix_len + markup_len + 1; 1107 1108 char* data = new char[total_len]; 1109 snprintf(data, total_len, "%s", html_prefix); 1110 memcpy(data + html_prefix_len, markup_data, markup_len); 1111 // Some programs expect NULL-terminated data. See http://crbug.com/42624 1112 data[total_len - 1] = '\0'; 1113 1114 aurax11_details_->InsertMapping(kMimeTypeHTML, data, total_len); 1115} 1116 1117void Clipboard::WriteRTF(const char* rtf_data, size_t data_len) { 1118 WriteData(GetRtfFormatType(), rtf_data, data_len); 1119} 1120 1121void Clipboard::WriteBookmark(const char* title_data, 1122 size_t title_len, 1123 const char* url_data, 1124 size_t url_len) { 1125 // Write as a mozilla url (UTF16: URL, newline, title). 1126 string16 url = UTF8ToUTF16(std::string(url_data, url_len) + "\n"); 1127 string16 title = UTF8ToUTF16(std::string(title_data, title_len)); 1128 int data_len = 2 * (title.length() + url.length()); 1129 1130 char* data = new char[data_len]; 1131 memcpy(data, url.data(), 2 * url.length()); 1132 memcpy(data + 2 * url.length(), title.data(), 2 * title.length()); 1133 aurax11_details_->InsertMapping(kMimeTypeMozillaURL, data, data_len); 1134} 1135 1136// Write an extra flavor that signifies WebKit was the last to modify the 1137// pasteboard. This flavor has no data. 1138void Clipboard::WriteWebSmartPaste() { 1139 aurax11_details_->InsertMapping(kMimeTypeWebkitSmartPaste, NULL, 0); 1140} 1141 1142void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) { 1143 // TODO(erg): I'm not sure if we should be writting BMP data here or 1144 // not. It's what the GTK port does, but I'm not sure it's the right thing to 1145 // do. 1146 NOTIMPLEMENTED(); 1147} 1148 1149void Clipboard::WriteData(const FormatType& format, 1150 const char* data_data, 1151 size_t data_len) { 1152 // We assume that certain mapping types are only written by trusted code. 1153 // Therefore we must upkeep their integrity. 1154 if (format.Equals(GetBitmapFormatType())) 1155 return; 1156 char* data = new char[data_len]; 1157 memcpy(data, data_data, data_len); 1158 aurax11_details_->InsertMapping(format.ToString(), data, data_len); 1159} 1160 1161void Clipboard::WriteSourceTag(SourceTag tag) { 1162 if (tag != SourceTag()) { 1163 ObjectMapParam binary = SourceTag2Binary(tag); 1164 WriteData(GetSourceTagFormatType(), &binary[0], binary.size()); 1165 } 1166} 1167 1168// static 1169Clipboard::FormatType Clipboard::GetFormatType( 1170 const std::string& format_string) { 1171 return FormatType::Deserialize(format_string); 1172} 1173 1174// static 1175const Clipboard::FormatType& Clipboard::GetUrlFormatType() { 1176 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeURIList)); 1177 return type; 1178} 1179 1180// static 1181const Clipboard::FormatType& Clipboard::GetUrlWFormatType() { 1182 return GetUrlFormatType(); 1183} 1184 1185// static 1186const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() { 1187 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeText)); 1188 return type; 1189} 1190 1191// static 1192const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() { 1193 return GetPlainTextFormatType(); 1194} 1195 1196// static 1197const Clipboard::FormatType& Clipboard::GetFilenameFormatType() { 1198 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeFilename)); 1199 return type; 1200} 1201 1202// static 1203const Clipboard::FormatType& Clipboard::GetFilenameWFormatType() { 1204 return Clipboard::GetFilenameFormatType(); 1205} 1206 1207// static 1208const Clipboard::FormatType& Clipboard::GetHtmlFormatType() { 1209 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeHTML)); 1210 return type; 1211} 1212 1213// static 1214const Clipboard::FormatType& Clipboard::GetRtfFormatType() { 1215 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeRTF)); 1216 return type; 1217} 1218 1219// static 1220const Clipboard::FormatType& Clipboard::GetBitmapFormatType() { 1221 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeBitmap)); 1222 return type; 1223} 1224 1225// static 1226const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() { 1227 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebkitSmartPaste)); 1228 return type; 1229} 1230 1231// static 1232const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() { 1233 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebCustomData)); 1234 return type; 1235} 1236 1237// static 1238const Clipboard::FormatType& Clipboard::GetPepperCustomDataFormatType() { 1239 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypePepperCustomData)); 1240 return type; 1241} 1242 1243// static 1244const Clipboard::FormatType& Clipboard::GetSourceTagFormatType() { 1245 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kSourceTagType)); 1246 return type; 1247} 1248 1249} // namespace ui 1250