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"
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"
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
[] = {
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
);
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
,
60 std::vector
<std::pair
<XAtom
,XAtom
> >* value
) {
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(),
70 0, // offset into property data to
72 (~0L), // entire array
80 gfx::XScopedPtr
<unsigned char> scoped_properties(properties
);
82 if (result
!= Success
)
85 // GTK does not require |type| to be kAtomPair.
86 if (format
!= 32 || num_items
% 2 != 0)
89 XAtom
* atom_properties
= reinterpret_cast<XAtom
*>(properties
);
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]));
98 SelectionOwner::SelectionOwner(XDisplay
* x_display
,
100 XAtom selection_name
)
101 : x_display_(x_display
),
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.
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.
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
,
160 std::vector
<XAtom
> conversion_results
;
161 for (size_t i
= 0; i
< conversions
.size(); ++i
) {
162 bool conversion_successful
= ProcessTarget(conversions
[i
].first
,
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
176 atom_cache_
.GetAtom(kAtomPair
),
179 reinterpret_cast<const unsigned char*>(&conversion_results
.front()),
180 conversion_results
.size());
182 reply
.xselection
.property
= requested_property
;
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())
211 ProcessIncrementalTransfer(&(*it
));
213 CompleteIncrementalTransfer(it
);
216 bool SelectionOwner::ProcessTarget(XAtom target
,
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
)
226 if (target
== targets_atom
) {
227 // We have been asked for TARGETS. Send an atom array back with the data
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,
237 reinterpret_cast<unsigned char*>(&targets
.front()),
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_
,
252 atom_cache_
.GetAtom(kIncr
),
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
,
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(
282 base::TimeDelta::FromMilliseconds(kTimerPeriodMs
),
284 &SelectionOwner::AbortStaleIncrementalTransfers
);
294 const_cast<unsigned char*>(it
->second
->front()),
299 // I would put error logging here, but GTK ignores TARGETS and spams us
300 // looking for its own internal types.
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_
);
315 const_cast<unsigned char*>(transfer
->data
->front() + transfer
->offset
),
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;
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();
354 if (it
->window
== event
.xproperty
.window
&&
355 it
->property
== event
.xproperty
.atom
) {
359 return incremental_transfers_
.end();
362 SelectionOwner::IncrementalTransfer::IncrementalTransfer(
366 const scoped_refptr
<base::RefCountedMemory
>& data
,
368 base::TimeTicks timeout
,
369 int foreign_window_manager_id
)
376 foreign_window_manager_id(foreign_window_manager_id
) {
379 SelectionOwner::IncrementalTransfer::~IncrementalTransfer() {