Index: ui/base/x/selection_owner.cc |
diff --git a/ui/base/x/selection_owner.cc b/ui/base/x/selection_owner.cc |
index 1664e5912e334d90304b56499f305c3ff97aa592..ac0b594a0f8abff1d5da03f45369bd43e94cb95d 100644 |
--- a/ui/base/x/selection_owner.cc |
+++ b/ui/base/x/selection_owner.cc |
@@ -4,11 +4,13 @@ |
#include "ui/base/x/selection_owner.h" |
+#include <algorithm> |
#include <X11/Xlib.h> |
#include <X11/Xatom.h> |
#include "base/logging.h" |
#include "ui/base/x/selection_utils.h" |
+#include "ui/base/x/x11_foreign_window_manager.h" |
#include "ui/base/x/x11_util.h" |
namespace ui { |
@@ -16,18 +18,37 @@ namespace ui { |
namespace { |
const char kAtomPair[] = "ATOM_PAIR"; |
+const char kIncr[] = "INCR"; |
const char kMultiple[] = "MULTIPLE"; |
const char kSaveTargets[] = "SAVE_TARGETS"; |
const char kTargets[] = "TARGETS"; |
const char* kAtomsToCache[] = { |
kAtomPair, |
+ kIncr, |
kMultiple, |
kSaveTargets, |
kTargets, |
NULL |
}; |
+// The period of |incr_timer_|. Arbitrary but must be <= than |
+// kIncrTransferTimeoutMs. |
+const int kIncrTimerPeriodMs = 1000; |
Daniel Erat
2014/07/25 19:38:44
s/Incr/Incremental/ here and in other constants
|
+ |
+// The amount of time to wait for the selection requestor to process the data |
+// sent by the selection owner before aborting an incremental data transfer. |
+const int kIncrTransferTimeoutMs = 10000; |
+ |
+// Returns a conservative max size of the data we can pass into |
+// XChangeProperty(). Copied from GTK. |
+size_t GetMaxRequestSize(XDisplay* display) { |
+ long extended_max_size = XExtendedMaxRequestSize(display); |
+ return std::min( |
+ static_cast<long>(0x40000), |
+ (extended_max_size ? extended_max_size : XMaxRequestSize(display)) - 100); |
Daniel Erat
2014/07/25 19:38:43
nit: check for negative wraparound
pkotwicz
2014/07/26 20:09:53
Done.
|
+} |
+ |
// Gets the value of an atom pair array property. On success, true is returned |
// and the value is stored in |value|. |
bool GetAtomPairArrayProperty(XID window, |
@@ -78,6 +99,7 @@ SelectionOwner::SelectionOwner(XDisplay* x_display, |
: x_display_(x_display), |
x_window_(x_window), |
selection_name_(selection_name), |
+ max_request_size_(GetMaxRequestSize(x_display)), |
atom_cache_(x_display_, kAtomsToCache) { |
} |
@@ -173,6 +195,21 @@ void SelectionOwner::OnSelectionClear(const XEvent& event) { |
// we need to delay clearing. |
} |
+bool SelectionOwner::CanDispatchPropertyEvent(const XEvent& event) { |
+ return event.xproperty.state == PropertyDelete && |
+ FindIncrTransferForEvent(event) != incrs_.end(); |
+} |
+ |
+void SelectionOwner::OnPropertyEvent(const XEvent& event) { |
+ std::vector<IncrTransfer>::iterator it = FindIncrTransferForEvent(event); |
+ if (it == incrs_.end()) |
+ return; |
+ |
+ ProcessIncr(&(*it)); |
+ if (!it->data.get()) |
+ CompleteIncrTransfer(it); |
+} |
+ |
bool SelectionOwner::ProcessTarget(XAtom target, |
XID requestor, |
XAtom property) { |
@@ -201,12 +238,58 @@ bool SelectionOwner::ProcessTarget(XAtom target, |
// Try to find the data type in map. |
SelectionFormatMap::const_iterator it = format_map_.find(target); |
if (it != format_map_.end()) { |
- XChangeProperty(x_display_, requestor, property, target, 8, |
- PropModeReplace, |
- const_cast<unsigned char*>( |
- reinterpret_cast<const unsigned char*>( |
- it->second->front())), |
- it->second->size()); |
+ if (it->second->size() > max_request_size_) { |
+ // We must send the data back in several chunks due to a limitation in |
+ // the size of X requests. Notify the selection requestor that the data |
+ // will be sent incrementally by returning data of type "INCR". |
+ int length = it->second->size(); |
+ XChangeProperty(x_display_, |
+ requestor, |
+ property, |
+ atom_cache_.GetAtom(kIncr), |
+ 32, |
+ PropModeReplace, |
+ reinterpret_cast<unsigned char*>(&length), |
+ 1); |
+ |
+ // Wait for the selection requestor to indicate that it has processed |
+ // the selection result before sending the first chunk of data. The |
+ // selection requestor indicates this by deleting |property|. |
+ base::TimeTicks timeout = |
+ base::TimeTicks::Now() + |
+ base::TimeDelta::FromMilliseconds(kIncrTransferTimeoutMs); |
+ int foreign_window_manager_id = |
+ ui::XForeignWindowManager::GetInstance()->RequestEvents( |
+ requestor, PropertyChangeMask); |
+ incrs_.push_back(IncrTransfer(requestor, |
+ target, |
+ property, |
+ it->second, |
+ 0, |
+ timeout, |
+ foreign_window_manager_id)); |
+ |
+ // Start a timer to abort the data transfer in case that the selection |
+ // requestor does not support the INCR property or gets destroyed during |
+ // the data transfer. |
+ if (!incr_timer_.IsRunning()) { |
+ incr_timer_.Start( |
+ FROM_HERE, |
+ base::TimeDelta::FromMilliseconds(kIncrTimerPeriodMs), |
+ this, |
+ &SelectionOwner::AbortStaleIncrTransfers); |
+ } |
+ } else { |
+ XChangeProperty( |
+ x_display_, |
+ requestor, |
+ property, |
+ target, |
+ 8, |
+ PropModeReplace, |
+ const_cast<unsigned char*>(it->second->front()), |
+ it->second->size()); |
+ } |
return true; |
} |
// I would put error logging here, but GTK ignores TARGETS and spams us |
@@ -215,4 +298,78 @@ bool SelectionOwner::ProcessTarget(XAtom target, |
return false; |
} |
+void SelectionOwner::ProcessIncr(IncrTransfer* transfer) { |
+ size_t remaining = transfer->data->size() - transfer->offset; |
+ size_t chunk_length = std::min(remaining, max_request_size_); |
+ XChangeProperty( |
+ x_display_, |
+ transfer->window, |
+ transfer->property, |
+ transfer->target, |
+ 8, |
+ PropModeReplace, |
+ const_cast<unsigned char*>(transfer->data->front() + transfer->offset), |
+ chunk_length); |
+ transfer->offset += chunk_length; |
+ transfer->timeout = base::TimeTicks::Now() + |
+ base::TimeDelta::FromMilliseconds(kIncrTransferTimeoutMs); |
+ |
+ // When offset == data->size(), we still need to transfer a zero-sized chunk |
+ // to notify the selection requestor that the transfer is complete. Clear |
+ // transfer->data once the zero-sized chunk is sent to indicate that state |
+ // related to this data transfer can be cleared. |
+ if (chunk_length == 0) |
+ transfer->data = NULL; |
+} |
+ |
+void SelectionOwner::AbortStaleIncrTransfers() { |
+ base::TimeTicks now = base::TimeTicks::Now(); |
+ for (int i = static_cast<int>(incrs_.size()) - 1; i >= 0; --i) { |
+ if (incrs_[i].timeout < now) |
+ CompleteIncrTransfer(incrs_.begin() + i); |
+ } |
+} |
+ |
+void SelectionOwner::CompleteIncrTransfer( |
+ std::vector<IncrTransfer>::iterator it) { |
+ ui::XForeignWindowManager::GetInstance()->CancelRequest( |
+ it->foreign_window_manager_id); |
+ incrs_.erase(it); |
+ |
+ if (incrs_.empty()) |
+ incr_timer_.Stop(); |
+} |
+ |
+std::vector<SelectionOwner::IncrTransfer>::iterator |
+ SelectionOwner::FindIncrTransferForEvent(const XEvent& event) { |
+ for (std::vector<IncrTransfer>::iterator it = incrs_.begin(); |
+ it != incrs_.end(); ++it) { |
+ if (it->window == event.xproperty.window && |
+ it->property == event.xproperty.atom) { |
+ return it; |
+ } |
+ } |
+ return incrs_.end(); |
+} |
+ |
+SelectionOwner::IncrTransfer::IncrTransfer( |
+ XID window, |
+ XAtom target, |
+ XAtom property, |
+ const scoped_refptr<base::RefCountedMemory>& data, |
+ int offset, |
+ base::TimeTicks timeout, |
+ int foreign_window_manager_id) |
+ : window(window), |
+ target(target), |
+ property(property), |
+ data(data), |
+ offset(offset), |
+ timeout(timeout), |
+ foreign_window_manager_id(foreign_window_manager_id) { |
+} |
+ |
+SelectionOwner::IncrTransfer::~IncrTransfer() { |
+} |
+ |
} // namespace ui |