| Index: chrome/browser/gtk/xclipboard.cc
|
| diff --git a/chrome/browser/gtk/xclipboard.cc b/chrome/browser/gtk/xclipboard.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..57c426f21456d1e01d944ac8e92ee485babcc850
|
| --- /dev/null
|
| +++ b/chrome/browser/gtk/xclipboard.cc
|
| @@ -0,0 +1,242 @@
|
| +// Have the ICCCM in hand when reading this code:
|
| +// http://tronche.com/gui/x/icccm/sec-2.html#s-2
|
| +
|
| +struct InflightDownRequest {
|
| + Window requestor; // requesting window
|
| + Atom selection; // clipboard requested
|
| + Atom target; // type of the data requested
|
| + Atom property; // target property name on |requestor|
|
| + Time time; // request timestamp
|
| +};
|
| +
|
| +struct ReplyReorderBufferKey {
|
| + Window requestor;
|
| + Atom selection;
|
| + Atom target;
|
| + Time time;
|
| +
|
| + ReplyReorderBufferKey(const InflightDownRequest& downreq)
|
| + : requestor(downreq.requestor),
|
| + selection(downreq.selection),
|
| + target(downreq.target),
|
| + time(downreq.time) {
|
| + }
|
| +
|
| + bool operator==(const ReplyReorderBufferKey& other) {
|
| + return other.requestor == requestor &&
|
| + other.selection == selection &&
|
| + other.target == target &&
|
| + other.time == time;
|
| + }
|
| +};
|
| +
|
| +struct PendingReply {
|
| + explicit PendingReply(const InflightDownRequest& in_downreq)
|
| + : downreq(in_downreq),
|
| + data(NULL),
|
| + length(0) {
|
| + }
|
| +
|
| + PendingReply(const InflightDownRequest& in_downreq, const uint8_t* data,
|
| + size_t in_length)
|
| + : downreq(in_downreq),
|
| + data(in_data),
|
| + length(in_length) {
|
| + }
|
| +
|
| + InflightDownRequest downreq;
|
| + const uint8_t* data; // if NULL for |ReplyWithFailure|
|
| + size_t length;
|
| +};
|
| +
|
| +struct XClipboardImpl {
|
| + Display* display;
|
| + Window window;
|
| + uint64_t current_tag;
|
| +
|
| + std::map<uint64_t, InflightUpRequest> up_requests;
|
| + std::map<uint64_t, InflightDownRequest> down_requests;
|
| +
|
| + // ICCCM says: "If the owner receives more than one SelectionRequest event
|
| + // with the same requestor, selection, target, and timestamp, it must respond
|
| + // to them in the same order in which they were received."
|
| + //
|
| + // Thus, we may need to reorder the replies when we are sending. We have a
|
| + // map from |ReplyReorderBufferKey| to a list of uint64_t tags. This list is
|
| + // the received order and we check that we don't transmit a reply
|
| + // out-of-order. If we do, we stick the reply in the reorder buffer and it'll
|
| + // get sent when its predecessor is sent.
|
| + std::map<ReplyReorderBufferKey, std::deque<uint64_t> > reply_order_map_;
|
| + std::map<uint64_t, PendingReply> pending_replies_;
|
| +
|
| + // Some replies to other X clients will take multiple round trips (because of
|
| + // the size of the data). Sometimes we'll get an async X error from the X
|
| + // server if it's out of memory. For these reasons, we need to keep track of
|
| + // the curently uploading replies. This map is indexed by X serial number.
|
| + std::map<unsigned long, InflightReply> inflight_replies_;
|
| +};
|
| +
|
| +XClipboard::XClipboard(void* display, Delegate* delegate)
|
| + : delegate_(delegate_),
|
| + pimpl_(new XClipboardImpl) {
|
| + const Window root_window = XDefaultRootWindow(display);
|
| + const int default_screen = XDefaultScreen(display);
|
| + Visual *const default_visual = XDefaultVisual(display, default_screen);
|
| +
|
| + pimpl_->current_tag = 0;
|
| + pimpl_->display = reinterpret_cast<Display*>(display);
|
| +
|
| + pimpl_->window = XCreateWindow(
|
| + display, root_window, 0, 0 /* x, y */, 1, 1 /* width, height */,
|
| + 0 /* border width */, 0 /* depth */, InputOnly, default_visual,
|
| + 0 /* value mask */, NULL /* values */);
|
| + CHECK(pimpl_->window);
|
| +
|
| + XSelectInput(display, pimpl_->window, PropertyChangeMask);
|
| +}
|
| +
|
| +XClipboard::~XClipboard() {
|
| + if (pimpl_->up_requests.size() ||
|
| + pimpl_->down_requests.size()) {
|
| + LOG(ERROR) << "XClipboard deleted with outstanding requests.";
|
| + }
|
| +
|
| + delete pimpl_;
|
| +}
|
| +
|
| +static Atom TypeToAtom(Display* display, XClipboard::Type clipboard_type) {
|
| + switch(clipboard_type) {
|
| + case CLIPBOARD_PRIMARY:
|
| + return XA_PRIMARY;
|
| + case CLIPBOARD_SECONDARY:
|
| + return XA_SECONDARY;
|
| + case CLIPBOARD_CLIPBOARD:
|
| + return XInternAtom(display, "CLIPBOARD");
|
| + default:
|
| + CHECK(false) << "Unknown clipboard type: " << clipboard_type;
|
| + }
|
| +}
|
| +
|
| +bool XClipboard::AssertOwnership(Type clipboard_type) {
|
| + const Atom clipboard_atom = TypeToAtom(pimpl_->display, clipboard_type);
|
| + XSetSelectionOwner(pimpl_->display, clipboard_atom, pimpl_->window,
|
| + CurrentAtom);
|
| + if (XGetSelectionOwner(pimpl_->display, clipboard_atom) != pimpl_->window)
|
| + return false;
|
| +
|
| + return true;
|
| +}
|
| +
|
| +static void SendReplyWithFailure(Display* dpy,
|
| + const InflightDownRequest& req) {
|
| + XEvent reply;
|
| + reply.xselection.type = SelectionNotify;
|
| + reply.xselection.display = dpy;
|
| + reply.xselection.requestor = req.requestor;
|
| + reply.xselection.selection = req.selection;
|
| + reply.xselection.target = req.target;
|
| + reply.xselection.time = req.time;
|
| + reply.xselection.property = None;
|
| + XSendEvent(dpy, req.requestor, 0 /* propagate */, 0 /* event mask */, &reply);
|
| +}
|
| +
|
| +static void SendReplyWithContents(Display* dpy,
|
| + const InflightDownRequest& req,
|
| + const uint8_t* data, size_t length) {
|
| + // XMaxRequestSize returns the maximum request size in 4-byte words.
|
| + const size_t max_request_size = XMaxRequestSize(dpy);
|
| + // No matter what the X server requests, we won't transfer in chunks larger
|
| + // than this.
|
| + static const size_t kMaxUploadSize = 1 * 1024 * 1024;
|
| +
|
| + // If either of the two bits are set, we'll overflow when shifting.
|
| + if (max_request_size >> (sizeof(max_request_size) * 8) - 2) {
|
| + max_request_size = LONG_MAX;
|
| + } else {
|
| + max_request_size <<= 2;
|
| + }
|
| +
|
| + if (max_request_size > kMaxUploadSize)
|
| + max_request_size = kMaxUploadSize;
|
| +
|
| + if (length < kMaxUploadSize) {
|
| + // We'll try to do the whole thing in one go
|
| + XChangeProperty(dpy, req.requestor, req.property, req.target,
|
| + 8 /* 8-bit words */, PropModeReplace,
|
| + data, length);
|
| +
|
| + // At this point we need to wait to either get an event that the property
|
| + // was set, or an error. Since the errors are async, we need to get the
|
| + // next serial number (before calling XChangeProperty) with
|
| + // |XNextRequest(dpy)| and, in the error handler, fallback to INCR with
|
| + // smaller chunks (maybe).
|
| + } else {
|
| + // INCR protocol
|
| + }
|
| +
|
| + XEvent reply;
|
| + reply.xselection.type = SelectionNotify;
|
| + reply.xselection.display = dpy;
|
| + reply.xselection.requestor = req.requestor;
|
| + reply.xselection.selection = req.selection;
|
| + reply.xselection.target = req.target;
|
| + reply.xselection.time = req.time;
|
| + reply.xselection.property = None;
|
| + XSendEvent(dpy, req.requestor, 0 /* propagate */, 0 /* event mask */, &reply);
|
| +}
|
| +
|
| +void XClipboard::PumpReorderBuffer(std::deque<uint64_t> *reply_order) {
|
| + for (;;) {
|
| + reply_order->pop_front();
|
| + if (reply_order->empty()) {
|
| + pimpl_->reply_order_map_.erase(j);
|
| + break;
|
| + } else {
|
| + // A pending reply in the reorder buffer might have been blocked on
|
| + // sending this reply.
|
| + const std::map<uint64_t, PendingReply>::iterator k =
|
| + pimpl_->pending_replies_.find(reply_order->front());
|
| + if (k == pimpl_->pending_replies_.end())
|
| + break;
|
| +
|
| + if (k->second.data == NULL) {
|
| + SendReplyWithFailure(pimpl_->display, k->second.downreq);
|
| + } else {
|
| + SendReplyWithContents(pimpl_->display, k->second.downreq,
|
| + k->second.data, k->second.length);
|
| + }
|
| +
|
| + pimpl_->pending_replies_.erase(k);
|
| + }
|
| + }
|
| +}
|
| +
|
| +void XClipboard::ReplyWithFailure(uint64_t tag) {
|
| + const std::map<uint64_t, InflightDownRequest>::const_iterator i =
|
| + pimpl_->down_requests.find(tag);
|
| +
|
| + if (i == pimpl_->down_requests.end())
|
| + return;
|
| +
|
| + const ReplyReorderBufferKey reorder_key(i->second);
|
| + const std::map<ReplyReorderBufferKey, std::deque<uint64_t> >::iterator j =
|
| + pimpl_->reply_order_map_.find(reorder_key);
|
| + DCHECK(j != reply_order_map_.end());
|
| + DCHECK_GT(j->second.size(), 0u);
|
| +
|
| + if (j->second.front() != tag) {
|
| + // This reply is out of order. Enqueue it.
|
| + PendingReply pending(i->second);
|
| + pimpl_->pending_replies_[tag] = pending;
|
| + pimpl_->down_requests.erase(i);
|
| + return;
|
| + }
|
| +
|
| + SendReplyWithFailure(pimpl_->display, i->second);
|
| + pimpl_->down_requests.erase(i);
|
| + PumpReorderBuffer();
|
| +}
|
| +
|
| +void XClipboard::ReplyWithContents(uint64_t tag, const uint8_t* data,
|
| + size_t length) {
|
| +}
|
|
|