Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(73)

Side by Side Diff: ui/base/x/x11_window_cache.cc

Issue 2177823002: X11: Add window cache Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Refactor Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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.
4
5 #include "ui/base/x/x11_window_cache.h"
6
7 #include <poll.h>
8 #include <X11/Xlib.h>
9 #include <xcb/xcbext.h>
10
11 #include <algorithm>
12 #include <cstdio>
13 #include <cstdlib>
14 #include <cstring>
15
16 #include "base/logging.h"
17
18 namespace ui {
19
20 namespace {
21
22 const char kNetWmIcon[] = "_NET_WM_ICON";
23
24 template <typename T>
25 void CacheWindowGeometryFromEvent(XWindowCache::Window* window, T* event) {
26 window->cached_geometry = true;
27 window->x = event->x;
28 window->y = event->y;
29 window->width = event->width;
30 window->height = event->height;
31 window->border_width = event->border_width;
32 }
33
34 auto FindChild(XWindowCache::Window* parent, xcb_window_t child_id)
35 -> decltype(parent->children.begin()) {
36 return std::find_if(parent->children.begin(), parent->children.end(),
37 [child_id](XWindowCache::Window* child) {
38 return child->id == child_id;
39 });
40 }
41
42 } // anonymous namespace
43
44 XWindowCache::Property::Property(xcb_atom_t name)
45 : name(name), cached_property(false) {
46 data.bits_8 = nullptr;
47 }
48
49 XWindowCache::Property::~Property() {
50 delete[] data.bits_8;
51 }
52
53 const XWindowCache::Property* XWindowCache::Window::GetProperty(
54 XID atom) const {
55 auto it = properties.find(atom);
56 return it == properties.end() ? nullptr : it->second.get();
57 }
58
59 XWindowCache::Window::Window(xcb_window_t id, XWindowCache::Window* parent)
60 : id(id),
61 parent(parent),
62 cached_attributes(false),
63 cached_geometry(false),
64 cached_properties(false),
65 cached_children(false) {}
66
67 XWindowCache::Window::~Window() {}
68
69 XWindowCache::Request::Request(xcb_window_t id) : id_(id) {}
70
71 XWindowCache::GetWindowAttributesRequest::GetWindowAttributesRequest(
72 xcb_window_t window,
73 XWindowCache* cache)
74 : Request(window) {
75 sequence_ = xcb_get_window_attributes(cache->connection_, window).sequence;
76 }
77
78 void XWindowCache::GetWindowAttributesRequest::OnReply(void* r,
79 XWindowCache* cache) {
80 auto* reply = reinterpret_cast<xcb_get_window_attributes_reply_t*>(r);
81
82 // Don't bother getting stale attributes if the window was destroyed.
83 Window* window = cache->GetWindow(id_);
84 if (!window)
85 return;
86
87 window->cached_attributes = true;
88 window->override_redirect = reply->override_redirect;
89 window->is_mapped = reply->map_state != XCB_MAP_STATE_UNMAPPED;
90 }
91
92 XWindowCache::GetGeometryRequest::GetGeometryRequest(xcb_window_t window,
93 XWindowCache* cache)
94 : Request(window) {
95 sequence_ = xcb_get_geometry(cache->connection_, window).sequence;
96 }
97
98 void XWindowCache::GetGeometryRequest::OnReply(void* r, XWindowCache* cache) {
99 auto* reply = reinterpret_cast<xcb_get_geometry_reply_t*>(r);
100
101 // Don't bother getting stale attributes if the window was destroyed.
102 Window* window = cache->GetWindow(id_);
103 if (!window)
104 return;
105
106 window->cached_geometry = true;
107 window->x = reply->x;
108 window->y = reply->y;
109 window->width = reply->width;
110 window->height = reply->height;
111 window->border_width = reply->border_width;
112 }
113
114 XWindowCache::ListPropertiesRequest::ListPropertiesRequest(xcb_window_t window,
115 XWindowCache* cache)
116 : Request(window) {
117 sequence_ = xcb_list_properties(cache->connection_, window).sequence;
118 }
119
120 void XWindowCache::ListPropertiesRequest::OnReply(void* r,
121 XWindowCache* cache) {
122 auto* reply = reinterpret_cast<xcb_list_properties_reply_t*>(r);
123
124 // Don't bother getting stale properties if the window was destroyed.
125 Window* window = cache->GetWindow(id_);
126 if (!window)
127 return;
128
129 window->cached_properties = true;
130 for (int i = 0; i < xcb_list_properties_atoms_length(reply); i++) {
131 xcb_atom_t atom = xcb_list_properties_atoms(reply)[i];
132 cache->CacheProperty(window, atom);
133 }
134 }
135
136 XWindowCache::QueryTreeRequest::QueryTreeRequest(xcb_window_t window,
137 XWindowCache* cache)
138 : Request(window) {
139 sequence_ = xcb_query_tree(cache->connection_, window).sequence;
140 }
141
142 void XWindowCache::QueryTreeRequest::OnReply(void* r, XWindowCache* cache) {
143 auto* reply = reinterpret_cast<xcb_query_tree_reply_t*>(r);
144
145 // Don't bother getting stale children if the parent was destroyed.
146 Window* p = cache->GetWindow(id_);
147 if (!p)
148 return;
149
150 DCHECK(p->children.empty());
151
152 xcb_window_t* children = xcb_query_tree_children(reply);
153 int n_children = xcb_query_tree_children_length(reply);
154
155 p->cached_children = true;
156
157 // Iterate over children from top-to-bottom.
158 for (int i = n_children - 1; i >= 0; i--) {
159 Window* child = new Window(children[i], p);
160 p->children.push_back(child);
161 cache->CacheWindow(child);
162 }
163 }
164
165 XWindowCache::GetPropertyRequest::GetPropertyRequest(xcb_window_t window,
166 xcb_atom_t atom,
167 XWindowCache* cache)
168 : Request(window), atom_(atom) {
169 // In case a property's value is huge, only cache the first 64KiB, and
170 // indicate in Property that the cached value is not the entire value.
171 static constexpr auto max_property_size = 0xffff;
172 sequence_ = xcb_get_property(cache->connection_, false, window, atom,
173 XCB_ATOM_ANY, 0, max_property_size)
174 .sequence;
175 }
176
177 void XWindowCache::GetPropertyRequest::OnReply(void* r, XWindowCache* cache) {
178 auto* reply = reinterpret_cast<xcb_get_property_reply_t*>(r);
179
180 Window* window = cache->GetWindow(id_);
181 if (!window)
182 return;
183
184 if (window->properties.find(atom_) == window->properties.end())
185 return;
186 if (reply->format == 0) {
187 // According to Xlib, a format of anything other than 8, 16, or 32 is a
188 // BadImplementation error. However, this occurs when creating a new xterm
189 // window, so just forget about the property in question.
190 window->properties.erase(atom_);
191 return;
192 }
193
194 Property* property = window->properties[atom_].get();
195
196 property->cached_property = true;
197 property->type = reply->type;
198 DCHECK(reply->format == 8 || reply->format == 16 || reply->format == 32);
199 property->data_format = reply->format;
200 property->data_length = xcb_get_property_value_length(reply);
201 // TODO(thomasanderson): verify data_bytes is correct
202 uint32_t data_bytes = property->data_length * (property->data_format / 8);
203 property->data.bits_8 = new uint8_t[data_bytes];
204 std::memcpy(property->data.bits_8, xcb_get_property_value(reply), data_bytes);
205 }
206
207 XWindowCache::XWindowCache(XDisplay* display, XID root)
208 : root_(nullptr), sequence_(0), net_wm_icon_(0) {
209 connection_ = xcb_connect(DisplayString(display), nullptr);
Daniel Erat 2016/08/06 00:49:22 this is establishing a second connection to the X
Tom (Use chromium acct) 2016/08/08 23:23:38 Acknowledged.
210 if (xcb_connection_has_error(connection_))
211 return;
212
213 net_wm_icon_cookie_ =
214 xcb_intern_atom(connection_, false, sizeof(kNetWmIcon) - 1, kNetWmIcon);
215
216 root_ = new Window(root, nullptr);
217 CacheWindow(root_);
218
219 xcb_flush(connection_);
220 }
221
222 XWindowCache::~XWindowCache() {
223 xcb_disconnect(connection_);
224 }
225
226 bool XWindowCache::ConnectionHasError() const {
227 return xcb_connection_has_error(connection_);
228 }
229
230 int XWindowCache::ConnectionFd() const {
231 return xcb_get_file_descriptor(connection_);
232 }
233
234 bool XWindowCache::BlockUntilConnectionIsReadable() {
235 if (ConnectionHasError())
236 return false;
237
238 int conn_fd = ConnectionFd();
239
240 struct pollfd rfd;
241 rfd.fd = conn_fd;
242 rfd.events = POLLIN;
243 rfd.revents = 0;
244 // TODO(thomasanderson): should we handle EAGAIN?
Elliot Glaysher 2016/08/08 18:17:28 I suspect that you might. If you EAGAIN here, will
Tom (Use chromium acct) 2016/08/08 23:23:38 yeah pretty much. So I guess the best thing to do
Tom (Use chromium acct) 2016/08/15 19:57:00 Fixed. Turns out we have a convention just to hand
245 poll(&rfd, 1, 3000); // Use a timeout of 3s.
246 return rfd.revents & POLLIN;
247 }
248
249 const XWindowCache::Window* XWindowCache::GetWindow(XID id) const {
250 return GetWindow(static_cast<xcb_window_t>(id));
251 }
252
253 XWindowCache::Window* XWindowCache::GetWindow(xcb_window_t id) const {
254 auto it = windows_.find(id);
255 return it == windows_.end() ? nullptr : it->second.get();
256 }
257
258 void XWindowCache::OnConnectionReadable() {
259 // xcb serially reads events and replies and then stores them in two queues.
260 // We want to merge these queues into one to make sure we process events and
261 // replies in order.
262 //
263 // xcb_poll_for_reply does not read any new data, while xcb_poll_for_event
264 // does, so get all available events first, followed by all replies.
265 //
266 // xcb_poll_for_event reads all available data, so we cannot have an outer
267 // loop reading events and an inner loop reading replies. That would be too
268 // easy. The only way to guarantee ordering is to read all events at once,
269 // followed by all replies.
270
271 // Get all events.
272 std::queue<xcb_generic_event_t*> events;
273 xcb_generic_event_t* event;
274 while ((event = xcb_poll_for_event(connection_)))
275 events.emplace(event);
276
277 // Get all replies for which we have a queued request. Some requests, such as
278 // xcb_change_window_attributes, do not have a corresponding reply, so skip
279 // over those sequence numbers. We may also want to poll for replies
280 // ourselves, such as when getting the _NET_WM_ICON atom, or when
281 // synchronizing. Skip over these too.
282 std::queue<std::unique_ptr<Request>> requests;
283 std::queue<xcb_generic_reply_t*> replies;
284 while (!requests_.empty()) {
285 uint16_t front_sequence = requests_.front()->sequence();
286 void* reply;
287 if (!xcb_poll_for_reply(connection_, front_sequence, &reply, nullptr))
288 break;
289 if (reply) {
290 requests.emplace(std::move(requests_.front()));
291 replies.emplace(reinterpret_cast<xcb_generic_reply_t*>(reply));
292 }
293 // Remove the request from the queue even if there was an error.
294 requests_.pop();
295 }
296
297 // Process events and replies in order.
298 while (!events.empty() || !replies.empty()) {
299 // Give precedence to replies.
300 if (!replies.empty() && sequence_ == replies.front()->sequence) {
301 xcb_generic_reply_t* reply = replies.front();
302 requests.front()->OnReply(reply, this);
303 free(reply);
304 requests.pop();
305 replies.pop();
306 } else if (!events.empty() && sequence_ == events.front()->sequence) {
307 xcb_generic_event_t* event = events.front();
308 ProcessEvent(event);
309 free(event);
310 events.pop();
311 } else {
312 sequence_++;
313 }
314 }
315
316 // Write out any requests we may have made.
317 xcb_flush(connection_);
318 }
319
320 bool XWindowCache::BlockUntilTreeIsCached() {
321 while (!requests_.empty()) {
322 if (!BlockUntilConnectionIsReadable())
323 return false;
324 OnConnectionReadable();
325 }
326 return true;
327 }
328
329 bool XWindowCache::Synchronize() {
330 if (!root_)
331 return true; // There's nothing to synchronize if the root was destroyed.
332
333 // Try getting some info about the root window and wait until we get a
334 // response or an error.
335 auto sequence = xcb_get_window_attributes(connection_, root_->id).sequence;
336 xcb_flush(connection_);
337
338 while (BlockUntilConnectionIsReadable()) {
339 OnConnectionReadable();
340 void* reply = nullptr;
341 if (!xcb_poll_for_reply(connection_, sequence, &reply, nullptr))
342 continue;
343 if (reply) {
344 free(reply);
345 return true;
346 }
347 // There was some sort of error. This may just be because the root was
348 // destroyed. Only return false if there was a connection error.
349 return !ConnectionHasError();
350 }
351 xcb_discard_reply(connection_, sequence);
352 return false;
353 }
354
355 bool XWindowCache::SynchronizeWith(XDisplay* display) {
356 XSync(display, False);
357 return Synchronize();
358 }
359
360 void XWindowCache::ProcessEvent(xcb_generic_event_t* g) {
361 switch (g->response_type & ~0x80) {
362 case XCB_PROPERTY_NOTIFY: {
363 auto* event = reinterpret_cast<xcb_property_notify_event_t*>(g);
364
365 Window* window = GetWindow(event->window);
366 if (!window)
367 break;
368
369 switch (event->state) {
370 case XCB_PROPERTY_DELETE:
371 window->properties.erase(event->atom);
372 break;
373 case XCB_PROPERTY_NEW_VALUE:
374 CacheProperty(window, event->atom);
375 break;
376 default:
377 NOTREACHED();
378 }
379 break;
380 }
381 case XCB_CIRCULATE_NOTIFY: {
382 auto* event = reinterpret_cast<xcb_circulate_notify_event_t*>(g);
383
384 Window* window = GetWindow(event->window);
385 if (!window)
386 break;
387
388 if (event->event == event->window)
389 break; // This is our root window
390
391 Window* parent = window->parent;
392 DCHECK_EQ(parent->id, event->event);
393
394 parent->children.remove(window);
395 switch (event->place) {
396 case XCB_PLACE_ON_TOP:
397 parent->children.push_front(window);
398 break;
399 case XCB_PLACE_ON_BOTTOM:
400 parent->children.push_back(window);
401 break;
402 default:
403 NOTREACHED();
404 }
405 break;
406 }
407 case XCB_CONFIGURE_NOTIFY: {
408 auto* event = reinterpret_cast<xcb_configure_notify_event_t*>(g);
409
410 Window* window = GetWindow(event->window);
411 if (!window)
412 break;
413
414 CacheWindowGeometryFromEvent(window, event);
415
416 if (event->event == event->window)
417 break;
418
419 Window* parent = window->parent;
420 DCHECK_EQ(parent->id, event->event);
421
422 parent->children.remove(window);
423 if (event->above_sibling) {
424 auto it = FindChild(parent, event->above_sibling);
425 if (it == parent->children.end()) {
426 NOTREACHED();
427 // We got into a bad state. Recache |parent|.
428 Window* grandparent = parent->parent;
429 xcb_window_t id = parent->id;
430 DeleteSubtree(parent);
431 CacheWindow(new Window(id, grandparent));
432 } else {
433 parent->children.insert(it, window);
434 }
435 } else {
436 // |window| is not above any other sibling window
437 parent->children.push_back(window);
438 }
439 break;
440 }
441 case XCB_CREATE_NOTIFY: {
442 auto* event = reinterpret_cast<xcb_create_notify_event_t*>(g);
443
444 Window* parent = GetWindow(event->parent);
445 if (!parent)
446 break;
447
448 if (!parent->cached_children) {
449 // |parent| is in the process of being cached, so we will pick up this
450 // window in the near future.
451 break;
452 }
453
454 Window* window = new Window(event->window, parent);
455
456 window->cached_attributes = true;
457 window->override_redirect = event->override_redirect;
458 window->is_mapped = false;
459
460 CacheWindowGeometryFromEvent(window, event);
461
462 CacheWindow(window);
463 parent->children.push_front(window);
464 break;
465 }
466 case XCB_DESTROY_NOTIFY: {
467 auto* event = reinterpret_cast<xcb_destroy_notify_event_t*>(g);
468
469 Window* window = GetWindow(event->window);
470 if (!window)
471 break;
472
473 if (window->parent) {
474 auto& children = window->parent->children;
475 auto it = std::find(children.begin(), children.end(), window);
476 if (it != children.end())
477 window->parent->children.erase(it);
478 } else {
479 // Window has no parent, so this must be the root that's going away.
480 root_ = nullptr;
481 }
482
483 DeleteSubtree(window);
484 break;
485 }
486 case XCB_GRAVITY_NOTIFY: {
487 auto* event = reinterpret_cast<xcb_gravity_notify_event_t*>(g);
488
489 Window* window = GetWindow(event->window);
490 if (!window)
491 break;
492
493 window->x = event->x;
494 window->y = event->y;
495 break;
496 }
497 case XCB_MAP_NOTIFY: {
498 auto* event = reinterpret_cast<xcb_map_notify_event_t*>(g);
499
500 Window* window = GetWindow(event->window);
501 if (!window)
502 break;
503
504 window->cached_attributes = true;
505 window->override_redirect = event->override_redirect;
506 window->is_mapped = true;
507 break;
508 }
509 case XCB_REPARENT_NOTIFY: {
510 auto* event = reinterpret_cast<xcb_reparent_notify_event_t*>(g);
511
512 Window* window = GetWindow(event->window);
513 if (!window)
514 break;
515
516 window->x = event->x;
517 window->y = event->y;
518
519 window->cached_attributes = true;
520 window->override_redirect = event->override_redirect;
521 window->is_mapped = false; // Reparenting a window unmaps it
522
523 Window* parent = window->parent;
524 if (parent)
525 parent->children.remove(window);
526 else
527 break; // Don't worry about caching windows above our root.
528
529 parent = GetWindow(event->parent);
530 if (!parent || !parent->cached_children) {
531 // |window| is either no longer in our tree, or we are already waiting
532 // to receive a list of |parent|'s children.
533 DeleteSubtree(window);
534 break;
535 }
536 window->parent = parent;
537
538 parent->children.push_front(window);
539 break;
540 }
541 case XCB_UNMAP_NOTIFY: {
542 auto* event = reinterpret_cast<xcb_unmap_notify_event_t*>(g);
543
544 Window* window = GetWindow(event->window);
545 if (!window)
546 break;
547
548 window->is_mapped = false;
549 break;
550 }
551 default:
552 // We can't opt-out of receiving events like MappingNotify or
553 // ClientMessage.
554 break;
555 }
556 }
557
558 void XWindowCache::CacheProperty(XWindowCache::Window* window,
559 xcb_atom_t atom) {
560 if (!net_wm_icon_ && net_wm_icon_cookie_.sequence) {
561 auto reply =
562 xcb_intern_atom_reply(connection_, net_wm_icon_cookie_, nullptr);
563 if (reply) {
564 net_wm_icon_ = reply->atom;
565 free(reply);
566 }
567 net_wm_icon_cookie_.sequence = 0;
568 }
569 if (atom == net_wm_icon_)
570 return;
571 xcb_window_t id = window->id;
572 Property* property = new Property(atom);
573 window->properties[atom].reset(property);
574 if (!property->cached_property)
575 requests_.emplace(new GetPropertyRequest(id, atom, this));
576 }
577
578 void XWindowCache::CacheWindow(XWindowCache::Window* window) {
579 DCHECK(window);
580 xcb_window_t id = window->id;
581 DCHECK(windows_.find(id) == windows_.end());
582
583 uint32_t event_mask =
584 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE;
585 if (id == root_->id) {
586 event_mask |= XCB_EVENT_MASK_STRUCTURE_NOTIFY;
587 }
588 auto cookie = xcb_change_window_attributes(connection_, id, XCB_CW_EVENT_MASK,
589 &event_mask);
590 xcb_discard_reply(connection_, cookie.sequence);
591
592 // Get the desired window state AFTER requesting state change events to avoid
593 // race conditions.
594
595 if (!window->cached_children)
596 requests_.emplace(new QueryTreeRequest(id, this));
597
598 if (!window->cached_properties)
599 requests_.emplace(new ListPropertiesRequest(id, this));
600
601 if (!window->cached_attributes)
602 requests_.emplace(new GetWindowAttributesRequest(id, this));
603
604 if (!window->cached_geometry)
605 requests_.emplace(new GetGeometryRequest(id, this));
606
607 windows_[id].reset(window);
608 }
609
610 void XWindowCache::DeleteSubtree(XWindowCache::Window* window) {
611 DCHECK(window);
612 for (auto* child : window->children)
613 DeleteSubtree(child);
614 windows_.erase(window->id);
615 }
616
617 } // namespace ui
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698