| OLD | NEW |
| (Empty) | |
| 1 // Have the ICCCM in hand when reading this code: |
| 2 // http://tronche.com/gui/x/icccm/sec-2.html#s-2 |
| 3 |
| 4 struct InflightDownRequest { |
| 5 Window requestor; // requesting window |
| 6 Atom selection; // clipboard requested |
| 7 Atom target; // type of the data requested |
| 8 Atom property; // target property name on |requestor| |
| 9 Time time; // request timestamp |
| 10 }; |
| 11 |
| 12 struct ReplyReorderBufferKey { |
| 13 Window requestor; |
| 14 Atom selection; |
| 15 Atom target; |
| 16 Time time; |
| 17 |
| 18 ReplyReorderBufferKey(const InflightDownRequest& downreq) |
| 19 : requestor(downreq.requestor), |
| 20 selection(downreq.selection), |
| 21 target(downreq.target), |
| 22 time(downreq.time) { |
| 23 } |
| 24 |
| 25 bool operator==(const ReplyReorderBufferKey& other) { |
| 26 return other.requestor == requestor && |
| 27 other.selection == selection && |
| 28 other.target == target && |
| 29 other.time == time; |
| 30 } |
| 31 }; |
| 32 |
| 33 struct PendingReply { |
| 34 explicit PendingReply(const InflightDownRequest& in_downreq) |
| 35 : downreq(in_downreq), |
| 36 data(NULL), |
| 37 length(0) { |
| 38 } |
| 39 |
| 40 PendingReply(const InflightDownRequest& in_downreq, const uint8_t* data, |
| 41 size_t in_length) |
| 42 : downreq(in_downreq), |
| 43 data(in_data), |
| 44 length(in_length) { |
| 45 } |
| 46 |
| 47 InflightDownRequest downreq; |
| 48 const uint8_t* data; // if NULL for |ReplyWithFailure| |
| 49 size_t length; |
| 50 }; |
| 51 |
| 52 struct XClipboardImpl { |
| 53 Display* display; |
| 54 Window window; |
| 55 uint64_t current_tag; |
| 56 |
| 57 std::map<uint64_t, InflightUpRequest> up_requests; |
| 58 std::map<uint64_t, InflightDownRequest> down_requests; |
| 59 |
| 60 // ICCCM says: "If the owner receives more than one SelectionRequest event |
| 61 // with the same requestor, selection, target, and timestamp, it must respond |
| 62 // to them in the same order in which they were received." |
| 63 // |
| 64 // Thus, we may need to reorder the replies when we are sending. We have a |
| 65 // map from |ReplyReorderBufferKey| to a list of uint64_t tags. This list is |
| 66 // the received order and we check that we don't transmit a reply |
| 67 // out-of-order. If we do, we stick the reply in the reorder buffer and it'll |
| 68 // get sent when its predecessor is sent. |
| 69 std::map<ReplyReorderBufferKey, std::deque<uint64_t> > reply_order_map_; |
| 70 std::map<uint64_t, PendingReply> pending_replies_; |
| 71 |
| 72 // Some replies to other X clients will take multiple round trips (because of |
| 73 // the size of the data). Sometimes we'll get an async X error from the X |
| 74 // server if it's out of memory. For these reasons, we need to keep track of |
| 75 // the curently uploading replies. This map is indexed by X serial number. |
| 76 std::map<unsigned long, InflightReply> inflight_replies_; |
| 77 }; |
| 78 |
| 79 XClipboard::XClipboard(void* display, Delegate* delegate) |
| 80 : delegate_(delegate_), |
| 81 pimpl_(new XClipboardImpl) { |
| 82 const Window root_window = XDefaultRootWindow(display); |
| 83 const int default_screen = XDefaultScreen(display); |
| 84 Visual *const default_visual = XDefaultVisual(display, default_screen); |
| 85 |
| 86 pimpl_->current_tag = 0; |
| 87 pimpl_->display = reinterpret_cast<Display*>(display); |
| 88 |
| 89 pimpl_->window = XCreateWindow( |
| 90 display, root_window, 0, 0 /* x, y */, 1, 1 /* width, height */, |
| 91 0 /* border width */, 0 /* depth */, InputOnly, default_visual, |
| 92 0 /* value mask */, NULL /* values */); |
| 93 CHECK(pimpl_->window); |
| 94 |
| 95 XSelectInput(display, pimpl_->window, PropertyChangeMask); |
| 96 } |
| 97 |
| 98 XClipboard::~XClipboard() { |
| 99 if (pimpl_->up_requests.size() || |
| 100 pimpl_->down_requests.size()) { |
| 101 LOG(ERROR) << "XClipboard deleted with outstanding requests."; |
| 102 } |
| 103 |
| 104 delete pimpl_; |
| 105 } |
| 106 |
| 107 static Atom TypeToAtom(Display* display, XClipboard::Type clipboard_type) { |
| 108 switch(clipboard_type) { |
| 109 case CLIPBOARD_PRIMARY: |
| 110 return XA_PRIMARY; |
| 111 case CLIPBOARD_SECONDARY: |
| 112 return XA_SECONDARY; |
| 113 case CLIPBOARD_CLIPBOARD: |
| 114 return XInternAtom(display, "CLIPBOARD"); |
| 115 default: |
| 116 CHECK(false) << "Unknown clipboard type: " << clipboard_type; |
| 117 } |
| 118 } |
| 119 |
| 120 bool XClipboard::AssertOwnership(Type clipboard_type) { |
| 121 const Atom clipboard_atom = TypeToAtom(pimpl_->display, clipboard_type); |
| 122 XSetSelectionOwner(pimpl_->display, clipboard_atom, pimpl_->window, |
| 123 CurrentAtom); |
| 124 if (XGetSelectionOwner(pimpl_->display, clipboard_atom) != pimpl_->window) |
| 125 return false; |
| 126 |
| 127 return true; |
| 128 } |
| 129 |
| 130 static void SendReplyWithFailure(Display* dpy, |
| 131 const InflightDownRequest& req) { |
| 132 XEvent reply; |
| 133 reply.xselection.type = SelectionNotify; |
| 134 reply.xselection.display = dpy; |
| 135 reply.xselection.requestor = req.requestor; |
| 136 reply.xselection.selection = req.selection; |
| 137 reply.xselection.target = req.target; |
| 138 reply.xselection.time = req.time; |
| 139 reply.xselection.property = None; |
| 140 XSendEvent(dpy, req.requestor, 0 /* propagate */, 0 /* event mask */, &reply); |
| 141 } |
| 142 |
| 143 static void SendReplyWithContents(Display* dpy, |
| 144 const InflightDownRequest& req, |
| 145 const uint8_t* data, size_t length) { |
| 146 // XMaxRequestSize returns the maximum request size in 4-byte words. |
| 147 const size_t max_request_size = XMaxRequestSize(dpy); |
| 148 // No matter what the X server requests, we won't transfer in chunks larger |
| 149 // than this. |
| 150 static const size_t kMaxUploadSize = 1 * 1024 * 1024; |
| 151 |
| 152 // If either of the two bits are set, we'll overflow when shifting. |
| 153 if (max_request_size >> (sizeof(max_request_size) * 8) - 2) { |
| 154 max_request_size = LONG_MAX; |
| 155 } else { |
| 156 max_request_size <<= 2; |
| 157 } |
| 158 |
| 159 if (max_request_size > kMaxUploadSize) |
| 160 max_request_size = kMaxUploadSize; |
| 161 |
| 162 if (length < kMaxUploadSize) { |
| 163 // We'll try to do the whole thing in one go |
| 164 XChangeProperty(dpy, req.requestor, req.property, req.target, |
| 165 8 /* 8-bit words */, PropModeReplace, |
| 166 data, length); |
| 167 |
| 168 // At this point we need to wait to either get an event that the property |
| 169 // was set, or an error. Since the errors are async, we need to get the |
| 170 // next serial number (before calling XChangeProperty) with |
| 171 // |XNextRequest(dpy)| and, in the error handler, fallback to INCR with |
| 172 // smaller chunks (maybe). |
| 173 } else { |
| 174 // INCR protocol |
| 175 } |
| 176 |
| 177 XEvent reply; |
| 178 reply.xselection.type = SelectionNotify; |
| 179 reply.xselection.display = dpy; |
| 180 reply.xselection.requestor = req.requestor; |
| 181 reply.xselection.selection = req.selection; |
| 182 reply.xselection.target = req.target; |
| 183 reply.xselection.time = req.time; |
| 184 reply.xselection.property = None; |
| 185 XSendEvent(dpy, req.requestor, 0 /* propagate */, 0 /* event mask */, &reply); |
| 186 } |
| 187 |
| 188 void XClipboard::PumpReorderBuffer(std::deque<uint64_t> *reply_order) { |
| 189 for (;;) { |
| 190 reply_order->pop_front(); |
| 191 if (reply_order->empty()) { |
| 192 pimpl_->reply_order_map_.erase(j); |
| 193 break; |
| 194 } else { |
| 195 // A pending reply in the reorder buffer might have been blocked on |
| 196 // sending this reply. |
| 197 const std::map<uint64_t, PendingReply>::iterator k = |
| 198 pimpl_->pending_replies_.find(reply_order->front()); |
| 199 if (k == pimpl_->pending_replies_.end()) |
| 200 break; |
| 201 |
| 202 if (k->second.data == NULL) { |
| 203 SendReplyWithFailure(pimpl_->display, k->second.downreq); |
| 204 } else { |
| 205 SendReplyWithContents(pimpl_->display, k->second.downreq, |
| 206 k->second.data, k->second.length); |
| 207 } |
| 208 |
| 209 pimpl_->pending_replies_.erase(k); |
| 210 } |
| 211 } |
| 212 } |
| 213 |
| 214 void XClipboard::ReplyWithFailure(uint64_t tag) { |
| 215 const std::map<uint64_t, InflightDownRequest>::const_iterator i = |
| 216 pimpl_->down_requests.find(tag); |
| 217 |
| 218 if (i == pimpl_->down_requests.end()) |
| 219 return; |
| 220 |
| 221 const ReplyReorderBufferKey reorder_key(i->second); |
| 222 const std::map<ReplyReorderBufferKey, std::deque<uint64_t> >::iterator j = |
| 223 pimpl_->reply_order_map_.find(reorder_key); |
| 224 DCHECK(j != reply_order_map_.end()); |
| 225 DCHECK_GT(j->second.size(), 0u); |
| 226 |
| 227 if (j->second.front() != tag) { |
| 228 // This reply is out of order. Enqueue it. |
| 229 PendingReply pending(i->second); |
| 230 pimpl_->pending_replies_[tag] = pending; |
| 231 pimpl_->down_requests.erase(i); |
| 232 return; |
| 233 } |
| 234 |
| 235 SendReplyWithFailure(pimpl_->display, i->second); |
| 236 pimpl_->down_requests.erase(i); |
| 237 PumpReorderBuffer(); |
| 238 } |
| 239 |
| 240 void XClipboard::ReplyWithContents(uint64_t tag, const uint8_t* data, |
| 241 size_t length) { |
| 242 } |
| OLD | NEW |