Add ICU message format support
[chromium-blink-merge.git] / ui / base / x / selection_owner.cc
blobd13f088d5aaf263c367fcb1f465bd6011b32b93a
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.
5 #include "ui/base/x/selection_owner.h"
7 #include <algorithm>
8 #include <X11/Xlib.h>
9 #include <X11/Xatom.h>
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"
16 namespace ui {
18 namespace {
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";
26 const char* kAtomsToCache[] = {
27 kAtomPair,
28 kIncr,
29 kMultiple,
30 kSaveTargets,
31 kTargets,
32 NULL
35 // The period of |incremental_transfer_abort_timer_|. Arbitrary but must be <=
36 // than kIncrementalTransferTimeoutMs.
37 const int kTimerPeriodMs = 1000;
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;
43 static_assert(kTimerPeriodMs <= kIncrementalTransferTimeoutMs,
44 "timer period must be <= transfer timeout");
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));
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;
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 gfx::XScopedPtr<unsigned char> scoped_properties(properties);
82 if (result != Success)
83 return false;
85 // GTK does not require |type| to be kAtomPair.
86 if (format != 32 || num_items % 2 != 0)
87 return false;
89 XAtom* atom_properties = reinterpret_cast<XAtom*>(properties);
90 value->clear();
91 for (size_t i = 0; i < num_items; i+=2)
92 value->push_back(std::make_pair(atom_properties[i], atom_properties[i+1]));
93 return true;
96 } // namespace
98 SelectionOwner::SelectionOwner(XDisplay* x_display,
99 XID x_window,
100 XAtom selection_name)
101 : x_display_(x_display),
102 x_window_(x_window),
103 selection_name_(selection_name),
104 max_request_size_(GetMaxRequestSize(x_display)),
105 atom_cache_(x_display_, kAtomsToCache) {
108 SelectionOwner::~SelectionOwner() {
109 // If we are the selection owner, we need to release the selection so we
110 // don't receive further events. However, we don't call ClearSelectionOwner()
111 // because we don't want to do this indiscriminately.
112 if (XGetSelectionOwner(x_display_, selection_name_) == x_window_)
113 XSetSelectionOwner(x_display_, selection_name_, None, CurrentTime);
116 void SelectionOwner::RetrieveTargets(std::vector<XAtom>* targets) {
117 for (SelectionFormatMap::const_iterator it = format_map_.begin();
118 it != format_map_.end(); ++it) {
119 targets->push_back(it->first);
123 void SelectionOwner::TakeOwnershipOfSelection(
124 const SelectionFormatMap& data) {
125 XSetSelectionOwner(x_display_, selection_name_, x_window_, CurrentTime);
127 if (XGetSelectionOwner(x_display_, selection_name_) == x_window_) {
128 // The X server agrees that we are the selection owner. Commit our data.
129 format_map_ = data;
133 void SelectionOwner::ClearSelectionOwner() {
134 XSetSelectionOwner(x_display_, selection_name_, None, CurrentTime);
135 format_map_ = SelectionFormatMap();
138 void SelectionOwner::OnSelectionRequest(const XEvent& event) {
139 XID requestor = event.xselectionrequest.requestor;
140 XAtom requested_target = event.xselectionrequest.target;
141 XAtom requested_property = event.xselectionrequest.property;
143 // Incrementally build our selection. By default this is a refusal, and we'll
144 // override the parts indicating success in the different cases.
145 XEvent reply;
146 reply.xselection.type = SelectionNotify;
147 reply.xselection.requestor = requestor;
148 reply.xselection.selection = event.xselectionrequest.selection;
149 reply.xselection.target = requested_target;
150 reply.xselection.property = None; // Indicates failure
151 reply.xselection.time = event.xselectionrequest.time;
153 if (requested_target == atom_cache_.GetAtom(kMultiple)) {
154 // The contents of |requested_property| should be a list of
155 // <target,property> pairs.
156 std::vector<std::pair<XAtom,XAtom> > conversions;
157 if (GetAtomPairArrayProperty(requestor,
158 requested_property,
159 &conversions)) {
160 std::vector<XAtom> conversion_results;
161 for (size_t i = 0; i < conversions.size(); ++i) {
162 bool conversion_successful = ProcessTarget(conversions[i].first,
163 requestor,
164 conversions[i].second);
165 conversion_results.push_back(conversions[i].first);
166 conversion_results.push_back(
167 conversion_successful ? conversions[i].second : None);
170 // Set the property to indicate which conversions succeeded. This matches
171 // what GTK does.
172 XChangeProperty(
173 x_display_,
174 requestor,
175 requested_property,
176 atom_cache_.GetAtom(kAtomPair),
178 PropModeReplace,
179 reinterpret_cast<const unsigned char*>(&conversion_results.front()),
180 conversion_results.size());
182 reply.xselection.property = requested_property;
184 } else {
185 if (ProcessTarget(requested_target, requestor, requested_property))
186 reply.xselection.property = requested_property;
189 // Send off the reply.
190 XSendEvent(x_display_, requestor, False, 0, &reply);
193 void SelectionOwner::OnSelectionClear(const XEvent& event) {
194 DLOG(ERROR) << "SelectionClear";
196 // TODO(erg): If we receive a SelectionClear event while we're handling data,
197 // we need to delay clearing.
200 bool SelectionOwner::CanDispatchPropertyEvent(const XEvent& event) {
201 return event.xproperty.state == PropertyDelete &&
202 FindIncrementalTransferForEvent(event) != incremental_transfers_.end();
205 void SelectionOwner::OnPropertyEvent(const XEvent& event) {
206 std::vector<IncrementalTransfer>::iterator it =
207 FindIncrementalTransferForEvent(event);
208 if (it == incremental_transfers_.end())
209 return;
211 ProcessIncrementalTransfer(&(*it));
212 if (!it->data.get())
213 CompleteIncrementalTransfer(it);
216 bool SelectionOwner::ProcessTarget(XAtom target,
217 XID requestor,
218 XAtom property) {
219 XAtom multiple_atom = atom_cache_.GetAtom(kMultiple);
220 XAtom save_targets_atom = atom_cache_.GetAtom(kSaveTargets);
221 XAtom targets_atom = atom_cache_.GetAtom(kTargets);
223 if (target == multiple_atom || target == save_targets_atom)
224 return false;
226 if (target == targets_atom) {
227 // We have been asked for TARGETS. Send an atom array back with the data
228 // types we support.
229 std::vector<XAtom> targets;
230 targets.push_back(targets_atom);
231 targets.push_back(save_targets_atom);
232 targets.push_back(multiple_atom);
233 RetrieveTargets(&targets);
235 XChangeProperty(x_display_, requestor, property, XA_ATOM, 32,
236 PropModeReplace,
237 reinterpret_cast<unsigned char*>(&targets.front()),
238 targets.size());
239 return true;
240 } else {
241 // Try to find the data type in map.
242 SelectionFormatMap::const_iterator it = format_map_.find(target);
243 if (it != format_map_.end()) {
244 if (it->second->size() > max_request_size_) {
245 // We must send the data back in several chunks due to a limitation in
246 // the size of X requests. Notify the selection requestor that the data
247 // will be sent incrementally by returning data of type "INCR".
248 long length = it->second->size();
249 XChangeProperty(x_display_,
250 requestor,
251 property,
252 atom_cache_.GetAtom(kIncr),
254 PropModeReplace,
255 reinterpret_cast<unsigned char*>(&length),
258 // Wait for the selection requestor to indicate that it has processed
259 // the selection result before sending the first chunk of data. The
260 // selection requestor indicates this by deleting |property|.
261 base::TimeTicks timeout =
262 base::TimeTicks::Now() +
263 base::TimeDelta::FromMilliseconds(kIncrementalTransferTimeoutMs);
264 int foreign_window_manager_id =
265 ui::XForeignWindowManager::GetInstance()->RequestEvents(
266 requestor, PropertyChangeMask);
267 incremental_transfers_.push_back(
268 IncrementalTransfer(requestor,
269 target,
270 property,
271 it->second,
273 timeout,
274 foreign_window_manager_id));
276 // Start a timer to abort the data transfer in case that the selection
277 // requestor does not support the INCR property or gets destroyed during
278 // the data transfer.
279 if (!incremental_transfer_abort_timer_.IsRunning()) {
280 incremental_transfer_abort_timer_.Start(
281 FROM_HERE,
282 base::TimeDelta::FromMilliseconds(kTimerPeriodMs),
283 this,
284 &SelectionOwner::AbortStaleIncrementalTransfers);
286 } else {
287 XChangeProperty(
288 x_display_,
289 requestor,
290 property,
291 target,
293 PropModeReplace,
294 const_cast<unsigned char*>(it->second->front()),
295 it->second->size());
297 return true;
299 // I would put error logging here, but GTK ignores TARGETS and spams us
300 // looking for its own internal types.
302 return false;
305 void SelectionOwner::ProcessIncrementalTransfer(IncrementalTransfer* transfer) {
306 size_t remaining = transfer->data->size() - transfer->offset;
307 size_t chunk_length = std::min(remaining, max_request_size_);
308 XChangeProperty(
309 x_display_,
310 transfer->window,
311 transfer->property,
312 transfer->target,
314 PropModeReplace,
315 const_cast<unsigned char*>(transfer->data->front() + transfer->offset),
316 chunk_length);
317 transfer->offset += chunk_length;
318 transfer->timeout = base::TimeTicks::Now() +
319 base::TimeDelta::FromMilliseconds(kIncrementalTransferTimeoutMs);
321 // When offset == data->size(), we still need to transfer a zero-sized chunk
322 // to notify the selection requestor that the transfer is complete. Clear
323 // transfer->data once the zero-sized chunk is sent to indicate that state
324 // related to this data transfer can be cleared.
325 if (chunk_length == 0)
326 transfer->data = NULL;
329 void SelectionOwner::AbortStaleIncrementalTransfers() {
330 base::TimeTicks now = base::TimeTicks::Now();
331 for (int i = static_cast<int>(incremental_transfers_.size()) - 1;
332 i >= 0; --i) {
333 if (incremental_transfers_[i].timeout <= now)
334 CompleteIncrementalTransfer(incremental_transfers_.begin() + i);
338 void SelectionOwner::CompleteIncrementalTransfer(
339 std::vector<IncrementalTransfer>::iterator it) {
340 ui::XForeignWindowManager::GetInstance()->CancelRequest(
341 it->foreign_window_manager_id);
342 incremental_transfers_.erase(it);
344 if (incremental_transfers_.empty())
345 incremental_transfer_abort_timer_.Stop();
348 std::vector<SelectionOwner::IncrementalTransfer>::iterator
349 SelectionOwner::FindIncrementalTransferForEvent(const XEvent& event) {
350 for (std::vector<IncrementalTransfer>::iterator it =
351 incremental_transfers_.begin();
352 it != incremental_transfers_.end();
353 ++it) {
354 if (it->window == event.xproperty.window &&
355 it->property == event.xproperty.atom) {
356 return it;
359 return incremental_transfers_.end();
362 SelectionOwner::IncrementalTransfer::IncrementalTransfer(
363 XID window,
364 XAtom target,
365 XAtom property,
366 const scoped_refptr<base::RefCountedMemory>& data,
367 int offset,
368 base::TimeTicks timeout,
369 int foreign_window_manager_id)
370 : window(window),
371 target(target),
372 property(property),
373 data(data),
374 offset(offset),
375 timeout(timeout),
376 foreign_window_manager_id(foreign_window_manager_id) {
379 SelectionOwner::IncrementalTransfer::~IncrementalTransfer() {
382 } // namespace ui