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

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: Remove changes from dependent patchset 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 } // anonymous namespace
35
36 XWindowCache::Property::Property(xcb_atom_t name)
37 : name(name), cached_property(false) {
38 data.bits_8 = nullptr;
39 }
40
41 XWindowCache::Property::~Property() {
42 delete[] data.bits_8;
43 }
44
45 const XWindowCache::Property* XWindowCache::Window::GetProperty(
46 XID atom) const {
47 auto it = properties.find(atom);
48 return it == properties.end() ? nullptr : it->second.get();
49 }
50
51 XWindowCache::Window::Window(xcb_window_t id, XWindowCache::Window* parent)
52 : id(id),
53 parent(parent),
54 cached_attributes(false),
55 cached_geometry(false),
56 cached_properties(false),
57 cached_children(false) {}
58
59 XWindowCache::Window::~Window() {}
60
61 XWindowCache::Request::Request() {}
62 XWindowCache::Request::~Request() {}
63
64 XWindowCache::GetWindowAttributesRequest::GetWindowAttributesRequest(
65 xcb_window_t window,
66 XWindowCache* cache)
67 : id(window) {
68 sequence = xcb_get_window_attributes(cache->connection_, window).sequence;
69 }
70
71 void XWindowCache::GetWindowAttributesRequest::OnReply(void* r,
72 XWindowCache* cache) {
73 auto* reply = reinterpret_cast<xcb_get_window_attributes_reply_t*>(r);
74
75 // Don't bother getting stale attributes if the window was destroyed.
76 Window* window = cache->GetWindow(id);
77 if (!window)
78 return;
79
80 window->cached_attributes = true;
81 window->override_redirect = reply->override_redirect;
82 window->is_mapped = reply->map_state != XCB_MAP_STATE_UNMAPPED;
83 }
84
85 XWindowCache::GetGeometryRequest::GetGeometryRequest(xcb_window_t window,
86 XWindowCache* cache)
87 : id(window) {
88 sequence = xcb_get_geometry(cache->connection_, window).sequence;
89 }
90
91 void XWindowCache::GetGeometryRequest::OnReply(void* r, XWindowCache* cache) {
92 auto* reply = reinterpret_cast<xcb_get_geometry_reply_t*>(r);
93
94 // Don't bother getting stale attributes if the window was destroyed.
95 Window* window = cache->GetWindow(id);
96 if (!window)
97 return;
98
99 window->cached_geometry = true;
100 window->x = reply->x;
101 window->y = reply->y;
102 window->width = reply->width;
103 window->height = reply->height;
104 window->border_width = reply->border_width;
105 }
106
107 XWindowCache::ListPropertiesRequest::ListPropertiesRequest(xcb_window_t window,
108 XWindowCache* cache)
109 : id(window) {
110 sequence = xcb_list_properties(cache->connection_, window).sequence;
111 }
112
113 void XWindowCache::ListPropertiesRequest::OnReply(void* r,
114 XWindowCache* cache) {
115 auto* reply = reinterpret_cast<xcb_list_properties_reply_t*>(r);
116
117 // Don't bother getting stale properties if the window was destroyed.
118 Window* window = cache->GetWindow(id);
119 if (!window)
120 return;
121
122 window->cached_properties = true;
123 for (int i = 0; i < xcb_list_properties_atoms_length(reply); i++) {
124 xcb_atom_t atom = xcb_list_properties_atoms(reply)[i];
125 cache->CacheProperty(window, atom);
126 }
127 }
128
129 XWindowCache::QueryTreeRequest::QueryTreeRequest(xcb_window_t window,
130 XWindowCache* cache)
131 : id(window) {
132 sequence = xcb_query_tree(cache->connection_, window).sequence;
133 }
134
135 void XWindowCache::QueryTreeRequest::OnReply(void* r, XWindowCache* cache) {
136 auto* reply = reinterpret_cast<xcb_query_tree_reply_t*>(r);
137
138 // Don't bother getting stale children if the parent was destroyed.
139 Window* p = cache->GetWindow(id);
140 if (!p)
141 return;
142
143 xcb_window_t* children = xcb_query_tree_children(reply);
144 int n_children = xcb_query_tree_children_length(reply);
145
146 p->cached_children = true;
147
148 // Iterate over children from top-to-bottom.
149 for (int i = n_children - 1; i >= 0; i--) {
150 Window* child = new Window(children[i], p);
151 p->children.push_back(child);
152 cache->CacheWindow(child);
153 }
154 }
155
156 XWindowCache::GetPropertyRequest::GetPropertyRequest(xcb_window_t window,
157 xcb_atom_t atom,
158 XWindowCache* cache)
159 : id(window), atom(atom) {
160 // In case a property's value is huge, only cache the first 64KiB, and
161 // indicate in Property that the cached value is not the entire value.
162 static constexpr auto max_property_size = 0xffff;
163 sequence = xcb_get_property(cache->connection_, false, window, atom,
164 XCB_ATOM_ANY, 0, max_property_size)
165 .sequence;
166 }
167
168 void XWindowCache::GetPropertyRequest::OnReply(void* r, XWindowCache* cache) {
169 auto* reply = reinterpret_cast<xcb_get_property_reply_t*>(r);
170
171 Window* window = cache->GetWindow(id);
172 if (!window)
173 return;
174
175 if (window->properties.find(atom) == window->properties.end())
176 return;
177 Property* property = window->properties[atom].get();
178
179 property->cached_property = true;
180 property->type = reply->type;
181 DCHECK(reply->format == 8 || reply->format == 16 || reply->format == 32);
182 property->data_format = reply->format;
183 property->data_length = xcb_get_property_value_length(reply);
184 uint32_t data_bytes = property->data_length * (property->data_format / 8);
185 property->data.bits_8 = new uint8_t[data_bytes];
186 std::memcpy(property->data.bits_8, xcb_get_property_value(reply), data_bytes);
187 }
188
189 XWindowCache::XWindowCache(XDisplay* display, XID root)
190 : root_(nullptr), net_wm_icon_(0) {
191 connection_ = xcb_connect(DisplayString(display), nullptr);
192 if (xcb_connection_has_error(connection_))
193 return;
194
195 net_wm_icon_cookie_ =
196 xcb_intern_atom(connection_, false, sizeof(kNetWmIcon) - 1, kNetWmIcon);
197
198 root_ = new Window(root, nullptr);
199 CacheWindow(root_);
200
201 xcb_flush(connection_);
202 }
203
204 XWindowCache::~XWindowCache() {
205 xcb_disconnect(connection_);
206 }
207
208 bool XWindowCache::ConnectionHasError() const {
209 return xcb_connection_has_error(connection_);
210 }
211
212 int XWindowCache::ConnectionFd() const {
213 return xcb_get_file_descriptor(connection_);
214 }
215
216 void XWindowCache::PrintWindowTreeForTesting() const {
217 PrintWindowTreeForTestingAux(root_, 0);
218 }
219
220 void XWindowCache::PrintWindowTreeForTestingAux(Window* window,
Tom (Use chromium acct) 2016/07/26 20:17:42 This was useful when developing, but it causes a p
Tom (Use chromium acct) 2016/08/06 00:38:17 No longer applicable. Removed.
221 int indent) const {
222 if (!window)
223 return;
224 for (int i = 0; i < indent; i++)
225 printf("\t");
226 printf("%d\n", window->id);
227 if (window->cached_children) {
228 for (Window* child : window->children)
229 PrintWindowTreeForTestingAux(child, indent + 1);
230 }
231 }
232
233 bool XWindowCache::BlockUntilConnectionIsReadable() {
234 if (ConnectionHasError())
235 return false;
236
237 int conn_fd = ConnectionFd();
238
239 struct pollfd rfd;
240 rfd.fd = conn_fd;
241 rfd.events = POLLIN;
242 rfd.revents = 0;
243 poll(&rfd, 1, 3000); // Use a timeout of 3s.
244 return rfd.revents & POLLIN;
245 }
246
247 const XWindowCache::Window* XWindowCache::GetWindow(XID id) const {
248 return GetWindow(static_cast<xcb_window_t>(id));
249 }
250
251 XWindowCache::Window* XWindowCache::GetWindow(xcb_window_t id) const {
252 auto it = windows_.find(id);
253 return it == windows_.end() ? nullptr : it->second.get();
254 }
255
256 void XWindowCache::OnConnectionReadable() {
Tom (Use chromium acct) 2016/07/26 20:17:42 So we process events last as the comment points ou
Tom (Use chromium acct) 2016/08/06 00:38:17 No longer applicable. Reordered events/replies
257 // xcb serially reads events and replies and then stores them in two queues.
258 // There's no perfect way to merge these responses back into a chronological
259 // stream, so handle all replies before events. This avoids getting stuck in
260 // an old state.
261 //
262 // xcb_poll_for_reply does not read any new data, while xcb_poll_for_event
263 // does, so get all available events first and store them so they can be
264 // processed last.
265
266 // Get all events.
267 std::vector<xcb_generic_event_t*> events;
268 xcb_generic_event_t* event;
269 while ((event = xcb_poll_for_event(connection_)))
270 events.push_back(event);
271
272 // Process all replies.
273 while (!requests_.empty()) {
274 void* reply = nullptr;
275 Request* request = requests_.front().get();
276 if (!xcb_poll_for_reply(connection_, request->sequence, &reply, nullptr))
277 break;
278 if (reply) {
279 request->OnReply(reply, this);
280 free(reply);
281 }
282 requests_.pop();
283 }
284
285 // Process all events.
286 for (auto* event : events) {
287 ProcessEvent(event);
288 free(event);
289 }
290
291 // Write out any requests we may have made.
292 xcb_flush(connection_);
293 }
294
295 bool XWindowCache::BlockUntilTreeIsCached() {
296 while (!requests_.empty()) {
297 if (!BlockUntilConnectionIsReadable())
298 return false;
299 OnConnectionReadable();
300 }
301 return true;
302 }
303
304 bool XWindowCache::Synchronize() {
305 if (!root_)
306 return true; // There's nothing to synchronize if the root was destroyed.
307
308 // Try getting some info about the root window and wait until we get a
309 // response or an error.
310 auto sequence = xcb_get_window_attributes(connection_, root_->id).sequence;
311 xcb_flush(connection_);
312
313 while (BlockUntilConnectionIsReadable()) {
314 OnConnectionReadable();
315 void* reply = nullptr;
316 if (!xcb_poll_for_reply(connection_, sequence, &reply, nullptr))
317 continue;
318 if (reply) {
319 free(reply);
320 return true;
321 }
322 // There was some sort of error. This may just be because the root was
323 // destroyed. Only return false if there was a connection error.
324 return !ConnectionHasError();
325 }
326 xcb_discard_reply(connection_, sequence);
327 return false;
328 }
329
330 bool XWindowCache::SynchronizeWith(XDisplay* display) {
331 XSync(display, False);
332 return Synchronize();
333 }
334
335 void XWindowCache::ProcessEvent(xcb_generic_event_t* g) {
336 switch (g->response_type & ~0x80) {
337 case XCB_PROPERTY_NOTIFY: {
338 auto* event = reinterpret_cast<xcb_property_notify_event_t*>(g);
339
340 Window* window = GetWindow(event->window);
341 if (!window)
342 break;
343
344 switch (event->state) {
345 case XCB_PROPERTY_DELETE:
346 window->properties.erase(event->atom);
347 break;
348 case XCB_PROPERTY_NEW_VALUE:
349 CacheProperty(window, event->atom);
350 break;
351 default:
352 NOTREACHED();
353 }
354 break;
355 }
356 case XCB_CIRCULATE_NOTIFY: {
357 auto* event = reinterpret_cast<xcb_circulate_notify_event_t*>(g);
358
359 Window* window = GetWindow(event->window);
360 if (!window)
361 break;
362
363 if (event->event == event->window)
364 break; // This is our root window
365
366 Window* parent = window->parent;
367 DCHECK_EQ(parent->id, event->event);
368
369 parent->children.remove(window);
370 switch (event->place) {
371 case XCB_PLACE_ON_TOP:
372 parent->children.push_front(window);
373 break;
374 case XCB_PLACE_ON_BOTTOM:
375 parent->children.push_back(window);
376 break;
377 default:
378 NOTREACHED();
379 }
380 break;
381 }
382 case XCB_CONFIGURE_NOTIFY: {
383 auto* event = reinterpret_cast<xcb_configure_notify_event_t*>(g);
384
385 Window* window = GetWindow(event->window);
386 if (!window)
387 break;
388
389 CacheWindowGeometryFromEvent(window, event);
390
391 if (event->event == event->window)
392 break;
393
394 Window* parent = window->parent;
395 DCHECK_EQ(parent->id, event->event);
396
397 parent->children.remove(window);
398 if (event->above_sibling) {
399 auto it = std::find_if(parent->children.begin(), parent->children.end(),
400 [&event](Window* child) {
401 return child->id == event->above_sibling;
402 });
403 if (it == parent->children.end()) {
404 NOTREACHED();
405 // We got into a bad state. Recache |parent|.
406 Window* grandparent = parent->parent;
407 xcb_window_t id = parent->id;
408 DeleteSubtree(parent);
409 CacheWindow(new Window(id, grandparent));
410 } else {
411 parent->children.insert(it, window);
412 }
413 } else {
414 // |window| is not above any other sibling window
415 parent->children.push_back(window);
416 }
417 break;
418 }
419 case XCB_CREATE_NOTIFY: {
420 auto* event = reinterpret_cast<xcb_create_notify_event_t*>(g);
421
422 Window* parent = GetWindow(event->parent);
423 if (!parent)
424 break;
425
426 Window* window = new Window(event->window, parent);
427
428 window->cached_attributes = true;
429 window->override_redirect = event->override_redirect;
430 window->is_mapped = false;
431
432 CacheWindowGeometryFromEvent(window, event);
433
434 CacheWindow(window);
435 parent->children.push_front(window);
436 break;
437 }
438 case XCB_DESTROY_NOTIFY: {
439 auto* event = reinterpret_cast<xcb_destroy_notify_event_t*>(g);
440
441 Window* window = GetWindow(event->window);
442 if (!window)
443 break;
444
445 if (window->parent) {
446 auto& children = window->parent->children;
447 auto it = std::find(children.begin(), children.end(), window);
448 if (it != children.end())
449 window->parent->children.erase(it);
450 } else {
451 // Window has no parent, so this must be the root that's going away.
452 root_ = nullptr;
453 }
454
455 DeleteSubtree(window);
456 break;
457 }
458 case XCB_GRAVITY_NOTIFY: {
459 auto* event = reinterpret_cast<xcb_gravity_notify_event_t*>(g);
460
461 Window* window = GetWindow(event->window);
462 if (!window)
463 break;
464
465 window->x = event->x;
466 window->y = event->y;
467 break;
468 }
469 case XCB_MAP_NOTIFY: {
470 auto* event = reinterpret_cast<xcb_map_notify_event_t*>(g);
471
472 Window* window = GetWindow(event->window);
473 if (!window)
474 break;
475
476 window->cached_attributes = true;
477 window->override_redirect = event->override_redirect;
478 window->is_mapped = true;
479 break;
480 }
481 case XCB_REPARENT_NOTIFY: {
482 auto* event = reinterpret_cast<xcb_reparent_notify_event_t*>(g);
483
484 Window* window = GetWindow(event->window);
485 if (!window)
486 break;
487
488 window->x = event->x;
489 window->y = event->y;
490
491 window->cached_attributes = true;
492 window->override_redirect = event->override_redirect;
493 window->is_mapped = false; // Reparenting a window unmaps it
494
495 Window* parent = window->parent;
496 if (parent)
497 parent->children.remove(window);
498 else
499 break; // Don't worry about caching windows above our root.
500
501 parent = GetWindow(event->parent);
502 if (!parent) {
503 // |window| is no longer in our tree
504 DeleteSubtree(window);
505 break;
506 }
507 window->parent = parent;
508
509 parent->children.push_front(window);
510 break;
511 }
512 case XCB_UNMAP_NOTIFY: {
513 auto* event = reinterpret_cast<xcb_unmap_notify_event_t*>(g);
514
515 Window* window = GetWindow(event->window);
516 if (!window)
517 break;
518
519 window->is_mapped = false;
520 break;
521 }
522 default:
523 // We can't opt-out of receiving events like MappingNotify or
524 // ClientMessage.
525 break;
526 }
527 }
528
529 void XWindowCache::CacheProperty(XWindowCache::Window* window,
530 xcb_atom_t atom) {
531 if (!net_wm_icon_ && net_wm_icon_cookie_.sequence) {
532 auto reply =
533 xcb_intern_atom_reply(connection_, net_wm_icon_cookie_, nullptr);
534 if (reply) {
535 net_wm_icon_ = reply->atom;
536 free(reply);
537 }
538 net_wm_icon_cookie_.sequence = 0;
539 }
540 if (atom == net_wm_icon_)
541 return;
542 xcb_window_t id = window->id;
543 Property* property = new Property(atom);
544 window->properties[atom].reset(property);
545 if (!property->cached_property)
546 requests_.emplace(new GetPropertyRequest(id, atom, this));
547 }
548
549 void XWindowCache::CacheWindow(XWindowCache::Window* window) {
550 xcb_window_t id = window->id;
551 uint32_t event_mask =
552 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE;
553 if (id == root_->id) {
554 event_mask |= XCB_EVENT_MASK_STRUCTURE_NOTIFY;
555 }
556 xcb_change_window_attributes(connection_, id, XCB_CW_EVENT_MASK, &event_mask);
557
558 // Get the desired window state AFTER requesting state change events to avoid
559 // race conditions.
560
561 if (!window->cached_attributes)
562 requests_.emplace(new GetWindowAttributesRequest(id, this));
563
564 if (!window->cached_geometry)
565 requests_.emplace(new GetGeometryRequest(id, this));
566
567 if (!window->cached_properties)
568 requests_.emplace(new ListPropertiesRequest(id, this));
569
570 if (!window->cached_children)
571 requests_.emplace(new QueryTreeRequest(id, this));
572
573 windows_[id].reset(window);
574 }
575
576 void XWindowCache::DeleteSubtree(XWindowCache::Window* window) {
577 DCHECK(window);
578 for (auto* child : window->children)
579 DeleteSubtree(child);
580 windows_.erase(window->id);
581 }
582
583 } // namespace ui
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698