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_requestor.h" 6 7 #include "base/run_loop.h" 8 #include "ui/base/x/selection_utils.h" 9 #include "ui/base/x/x11_util.h" 10 #include "ui/events/platform/platform_event_dispatcher.h" 11 #include "ui/events/platform/platform_event_source.h" 12 #include "ui/gfx/x/x11_types.h" 13 14 namespace ui { 15 16 namespace { 17 18 const char kChromeSelection[] = "CHROME_SELECTION"; 19 20 const char* kAtomsToCache[] = { 21 kChromeSelection, 22 NULL 23 }; 24 25 } // namespace 26 27 SelectionRequestor::SelectionRequestor(Display* x_display, 28 Window x_window, 29 Atom selection_name, 30 PlatformEventDispatcher* dispatcher) 31 : x_display_(x_display), 32 x_window_(x_window), 33 selection_name_(selection_name), 34 dispatcher_(dispatcher), 35 atom_cache_(x_display_, kAtomsToCache) { 36 } 37 38 SelectionRequestor::~SelectionRequestor() {} 39 40 bool SelectionRequestor::PerformBlockingConvertSelection( 41 Atom target, 42 scoped_refptr<base::RefCountedMemory>* out_data, 43 size_t* out_data_bytes, 44 size_t* out_data_items, 45 Atom* out_type) { 46 // The name of the property that we are either: 47 // - Passing as a parameter with the XConvertSelection() request. 48 // OR 49 // - Asking the selection owner to set on |x_window_|. 50 Atom property = atom_cache_.GetAtom(kChromeSelection); 51 52 XConvertSelection(x_display_, 53 selection_name_, 54 target, 55 property, 56 x_window_, 57 CurrentTime); 58 59 // Now that we've thrown our message off to the X11 server, we block waiting 60 // for a response. 61 PendingRequest pending_request(target); 62 BlockTillSelectionNotifyForRequest(&pending_request); 63 64 bool success = false; 65 if (pending_request.returned_property == property) { 66 success = ui::GetRawBytesOfProperty(x_window_, 67 pending_request.returned_property, 68 out_data, out_data_bytes, 69 out_data_items, out_type); 70 } 71 if (pending_request.returned_property != None) 72 XDeleteProperty(x_display_, x_window_, pending_request.returned_property); 73 return success; 74 } 75 76 void SelectionRequestor::PerformBlockingConvertSelectionWithParameter( 77 Atom target, 78 const std::vector< ::Atom>& parameter) { 79 SetAtomArrayProperty(x_window_, kChromeSelection, "ATOM", parameter); 80 PerformBlockingConvertSelection(target, NULL, NULL, NULL, NULL); 81 } 82 83 SelectionData SelectionRequestor::RequestAndWaitForTypes( 84 const std::vector< ::Atom>& types) { 85 for (std::vector< ::Atom>::const_iterator it = types.begin(); 86 it != types.end(); ++it) { 87 scoped_refptr<base::RefCountedMemory> data; 88 size_t data_bytes = 0; 89 ::Atom type = None; 90 if (PerformBlockingConvertSelection(*it, 91 &data, 92 &data_bytes, 93 NULL, 94 &type) && 95 type == *it) { 96 return SelectionData(type, data); 97 } 98 } 99 100 return SelectionData(); 101 } 102 103 void SelectionRequestor::OnSelectionNotify(const XSelectionEvent& event) { 104 // Find the PendingRequest for the corresponding XConvertSelection call. If 105 // there are multiple pending requests on the same target, satisfy them in 106 // FIFO order. 107 PendingRequest* request_notified = NULL; 108 if (selection_name_ == event.selection) { 109 for (std::list<PendingRequest*>::iterator iter = pending_requests_.begin(); 110 iter != pending_requests_.end(); ++iter) { 111 PendingRequest* request = *iter; 112 if (request->returned) 113 continue; 114 if (request->target != event.target) 115 continue; 116 request_notified = request; 117 break; 118 } 119 } 120 121 // This event doesn't correspond to any XConvertSelection calls that we 122 // issued in PerformBlockingConvertSelection. This shouldn't happen, but any 123 // client can send any message, so it can happen. 124 if (!request_notified) { 125 // ICCCM requires us to delete the property passed into SelectionNotify. If 126 // |request_notified| is true, the property will be deleted when the run 127 // loop has quit. 128 if (event.property != None) 129 XDeleteProperty(x_display_, x_window_, event.property); 130 return; 131 } 132 133 request_notified->returned_property = event.property; 134 request_notified->returned = true; 135 136 if (!request_notified->quit_closure.is_null()) 137 request_notified->quit_closure.Run(); 138 } 139 140 void SelectionRequestor::BlockTillSelectionNotifyForRequest( 141 PendingRequest* request) { 142 pending_requests_.push_back(request); 143 144 const int kMaxWaitTimeForClipboardResponse = 300; 145 if (PlatformEventSource::GetInstance()) { 146 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); 147 base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop); 148 base::RunLoop run_loop; 149 150 request->quit_closure = run_loop.QuitClosure(); 151 loop->PostDelayedTask( 152 FROM_HERE, 153 request->quit_closure, 154 base::TimeDelta::FromMilliseconds(kMaxWaitTimeForClipboardResponse)); 155 156 run_loop.Run(); 157 } else { 158 // This occurs if PerformBlockingConvertSelection() is called during 159 // shutdown and the PlatformEventSource has already been destroyed. 160 base::TimeTicks start = base::TimeTicks::Now(); 161 while (!request->returned) { 162 if (XPending(x_display_)) { 163 XEvent event; 164 XNextEvent(x_display_, &event); 165 dispatcher_->DispatchEvent(&event); 166 } 167 base::TimeDelta wait_time = base::TimeTicks::Now() - start; 168 if (wait_time.InMilliseconds() > kMaxWaitTimeForClipboardResponse) 169 break; 170 } 171 } 172 173 DCHECK(!pending_requests_.empty()); 174 DCHECK_EQ(request, pending_requests_.back()); 175 pending_requests_.pop_back(); 176 } 177 178 SelectionRequestor::PendingRequest::PendingRequest(Atom target) 179 : target(target), 180 returned_property(None), 181 returned(false) { 182 } 183 184 SelectionRequestor::PendingRequest::~PendingRequest() { 185 } 186 187 } // namespace ui 188