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

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: Fix test compilation Created 4 years, 3 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 <X11/Xlib.h>
8 #include <X11/Xlib-xcb.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 #include "ui/events/platform/platform_event_source.h"
18 #include "ui/events/platform/x11/x11_event_source.h"
19
20 namespace ui {
21
22 namespace {
23
24 const char kNetWmIcon[] = "_NET_WM_ICON";
25
26 // In case a property's value is huge, only cache the first 64KiB, and
27 // indicate in Property that the cached value is not the entire value.
28 static constexpr auto kMaxPropertySize = 0xffff;
29
30 template <typename T>
31 void CacheWindowGeometryFromResponse(XWindowCache::Window* window,
32 const T& response) {
33 window->x = response.x;
34 window->y = response.y;
35 window->width = response.width;
36 window->height = response.height;
37 window->border_width = response.border_width;
38 }
39
40 auto FindChild(XWindowCache::Window* parent, xcb_window_t child_id)
41 -> decltype(parent->children.begin()) {
42 return std::find_if(parent->children.begin(), parent->children.end(),
43 [child_id](std::unique_ptr<XWindowCache::Window>& child) {
44 return child->id == child_id;
45 });
46 }
47
48 } // anonymous namespace
49
50 const XWindowCache::Property* XWindowCache::Window::GetProperty(
51 XID atom) const {
52 auto it = properties.find(atom);
53 return it == properties.end() ? nullptr : it->second.get();
54 }
55
56 struct XWindowCache::GetWindowAttributesRequest
57 : public X11EventSource::Request {
58 GetWindowAttributesRequest(Window* window, XWindowCache* cache)
59 : Request(
60 xcb_get_window_attributes(cache->connection_, window->id).sequence),
61 cache_(cache),
62 window_(window) {
63 window_->attributes_request = this;
64 }
65
66 void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override {
67 if (error) {
68 switch (error->error_code) {
69 case BadDrawable:
70 case BadWindow:
71 cache_->DestroyWindow(window_);
72 return;
73 default:
74 NOTREACHED();
75 }
76 }
77 auto* reply = reinterpret_cast<xcb_get_window_attributes_reply_t*>(r);
78
79 window_->attributes_request = nullptr;
80 window_->override_redirect = reply->override_redirect;
81 window_->is_mapped = reply->map_state != XCB_MAP_STATE_UNMAPPED;
82 }
83
84 protected:
85 XWindowCache* cache_;
86 Window* window_;
87 };
88
89 struct XWindowCache::GetGeometryRequest : public X11EventSource::Request {
90 GetGeometryRequest(Window* window, XWindowCache* cache)
91 : Request(xcb_get_geometry(cache->connection_, window->id).sequence),
92 cache_(cache),
93 window_(window) {
94 window_->geometry_request = this;
95 }
96
97 void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override {
98 if (error) {
99 switch (error->error_code) {
100 case BadDrawable:
101 cache_->DestroyWindow(window_);
102 return;
103 default:
104 NOTREACHED();
105 }
106 }
107 auto* reply = reinterpret_cast<xcb_get_geometry_reply_t*>(r);
108
109 window_->geometry_request = nullptr;
110 CacheWindowGeometryFromResponse(window_, *reply);
111 }
112
113 protected:
114 XWindowCache* cache_;
115 Window* window_;
116 };
117
118 struct XWindowCache::ListPropertiesRequest : public X11EventSource::Request {
119 ListPropertiesRequest(Window* window, XWindowCache* cache)
120 : Request(xcb_list_properties(cache->connection_, window->id).sequence),
121 cache_(cache),
122 window_(window) {
123 window_->properties_request = this;
124 }
125
126 void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override {
127 if (error) {
128 switch (error->error_code) {
129 case BadWindow:
130 cache_->DestroyWindow(window_);
131 return;
132 default:
133 NOTREACHED();
134 }
135 }
136 auto* reply = reinterpret_cast<xcb_list_properties_reply_t*>(r);
137
138 window_->properties_request = nullptr;
139 for (int i = 0; i < xcb_list_properties_atoms_length(reply); i++) {
140 xcb_atom_t atom = xcb_list_properties_atoms(reply)[i];
141 cache_->CacheProperty(window_, atom);
142 }
143 }
144
145 protected:
146 XWindowCache* cache_;
147 Window* window_;
148 };
149
150 struct XWindowCache::QueryTreeRequest : public X11EventSource::Request {
151 QueryTreeRequest(Window* window, XWindowCache* cache)
152 : Request(xcb_query_tree(cache->connection_, window->id).sequence),
153 cache_(cache),
154 window_(window) {
155 window_->children_request = this;
156 }
157
158 void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override {
159 if (error) {
160 switch (error->error_code) {
161 case BadWindow:
162 cache_->DestroyWindow(window_);
163 return;
164 default:
165 NOTREACHED();
166 }
167 }
168 auto* reply = reinterpret_cast<xcb_query_tree_reply_t*>(r);
169
170 DCHECK(window_->children.empty());
171
172 xcb_window_t* children = xcb_query_tree_children(reply);
173 int n_children = xcb_query_tree_children_length(reply);
174
175 window_->children_request = nullptr;
176
177 // Iterate over children from top-to-bottom.
178 for (int i = 0; i < n_children; i++)
179 cache_->CreateWindow(children[i], window_);
180 }
181
182 protected:
183 XWindowCache* cache_;
184 Window* window_;
185 };
186
187 struct XWindowCache::GetPropertyRequest : public X11EventSource::Request {
188 GetPropertyRequest(Window* window,
189 Property* property,
190 xcb_atom_t atom,
191 XWindowCache* cache)
192 : Request(xcb_get_property(cache->connection_,
193 false,
194 window->id,
195 atom,
196 XCB_ATOM_ANY,
197 0,
198 kMaxPropertySize)
199 .sequence),
200 cache_(cache),
201 window_(window),
202 property_(property),
203 atom_(atom) {
204 property->property_request = this;
205 }
206
207 void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override {
208 if (error) {
209 switch (error->error_code) {
210 case BadWindow:
211 cache_->DestroyWindow(window_);
212 return;
213 case BadValue:
214 case BadAtom:
215 default:
216 NOTREACHED();
217 }
218 }
219 auto* reply = reinterpret_cast<xcb_get_property_reply_t*>(r);
220
221 if (reply->format == 0) {
222 // According to Xlib, a format of anything other than 8, 16, or 32 is a
223 // BadImplementation error. However, this occurs when creating a new
224 // xterm window, so just forget about the property in question.
225 window_->properties.erase(atom_);
226 return;
227 }
228
229 property_->property_request = nullptr;
230 property_->type = reply->type;
231 DCHECK(reply->format == 8 || reply->format == 16 || reply->format == 32);
232 property_->data_format = reply->format;
233 property_->data_length = xcb_get_property_value_length(reply);
234 uint32_t data_bytes = property_->data_length * (property_->data_format / 8);
235 property_->data.bits_8 = new uint8_t[data_bytes];
236 std::memcpy(property_->data.bits_8, xcb_get_property_value(reply),
237 data_bytes);
238 }
239
240 protected:
241 XWindowCache* cache_;
242 Window* window_;
243 Property* property_;
244 xcb_atom_t atom_;
245 };
246
247 XWindowCache::Window::Window(xcb_window_t id,
248 XWindowCache::Window* parent,
249 XWindowCache* cache)
250 : id(id),
251 parent(parent),
252 attributes_request(nullptr),
253 geometry_request(nullptr),
254 properties_request(nullptr),
255 children_request(nullptr),
256 cache_(cache) {
257 uint32_t event_mask =
258 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE;
259 if (id == cache->root_id_) {
260 event_mask |= XCB_EVENT_MASK_STRUCTURE_NOTIFY;
261 }
262 // TODO(thomasanderson): Use XForeignWindowManager to select on the mask
263 // instead of resetting it.
264 auto cookie = xcb_change_window_attributes(cache->connection_, id,
265 XCB_CW_EVENT_MASK, &event_mask);
266 xcb_discard_reply(cache->connection_, cookie.sequence);
267
268 // Get the desired window state AFTER requesting state change events to avoid
269 // race conditions.
270
271 cache->event_source_->EnqueueRequest(new QueryTreeRequest(this, cache));
272 cache->event_source_->EnqueueRequest(new ListPropertiesRequest(this, cache));
273 cache->event_source_->EnqueueRequest(
274 new GetWindowAttributesRequest(this, cache));
275 cache->event_source_->EnqueueRequest(new GetGeometryRequest(this, cache));
276 xcb_flush(cache->connection_);
277 }
278
279 XWindowCache::Window::~Window() {
280 // The window tree was created top-down, so must be destroyed bottom-up.
281 while (!children.empty())
282 cache_->DestroyWindow(children.front().get());
283
284 if (attributes_request)
285 cache_->event_source_->DiscardRequest(attributes_request);
286 if (geometry_request)
287 cache_->event_source_->DiscardRequest(geometry_request);
288 if (properties_request)
289 cache_->event_source_->DiscardRequest(properties_request);
290 if (children_request)
291 cache_->event_source_->DiscardRequest(children_request);
292
293 // TODO(thomasanderson): Use XForeignWindowManager to deselect on the mask
294 // instead of resetting it.
295 uint32_t event_mask = XCB_EVENT_MASK_NO_EVENT;
296 auto cookie = xcb_change_window_attributes(cache_->connection_, id,
297 XCB_CW_EVENT_MASK, &event_mask);
298 // Window |id| may already be destroyed at this point, so the
299 // change_attributes request may give a BadWindow error. In this case, just
300 // ignore the error.
301 xcb_discard_reply(cache_->connection_, cookie.sequence);
302 }
303
304 XWindowCache::Property::Property(xcb_atom_t name,
305 Window* window,
306 XWindowCache* cache)
307 : name(name), property_request(nullptr), cache_(cache) {
308 data.bits_8 = nullptr;
309
310 cache->event_source_->EnqueueRequest(
311 new GetPropertyRequest(window, this, name, cache));
312 xcb_flush(cache->connection_);
313 }
314
315 XWindowCache::Property::~Property() {
316 if (property_request)
317 cache_->event_source_->DiscardRequest(property_request);
318
319 delete[] data.bits_8;
320 }
321
322 // Xlib shall own the event queue.
323 XWindowCache::XWindowCache(XDisplay* display,
324 X11EventSource* event_source,
325 XID root)
326 : display_(display),
327 connection_(XGetXCBConnection(display_)),
328 root_id_(root),
329 event_source_(event_source),
330 root_(nullptr),
331 net_wm_icon_(0) {
332 DCHECK(event_source);
333 DCHECK(!xcb_connection_has_error(connection_));
334
335 if (PlatformEventSource::GetInstance())
336 PlatformEventSource::GetInstance()->AddPlatformEventObserver(this);
337
338 net_wm_icon_cookie_ =
339 xcb_intern_atom(connection_, false, sizeof(kNetWmIcon) - 1, kNetWmIcon);
340
341 CreateWindow(root, nullptr);
342 }
343
344 XWindowCache::~XWindowCache() {
345 if (PlatformEventSource::GetInstance())
346 PlatformEventSource::GetInstance()->RemovePlatformEventObserver(this);
347 }
348
349 const XWindowCache::Window* XWindowCache::GetWindow(XID id) const {
350 return GetWindowInternal(id);
351 }
352
353 XWindowCache::Window* XWindowCache::GetWindowInternal(XID id) const {
354 auto it = windows_.find(id);
355 return it == windows_.end() ? nullptr : it->second;
356 }
357
358 // TODO(thomasanderson): Call ProcessEvent directly from X11EventSource. Have
359 // ProcessEvent return an indication of if it is possible for clients other
360 // than XWindowCache to be interested in the event, so that event dispatchers
361 // don't get bogged down with the many events that XWindowCache selects.
362 void XWindowCache::WillProcessEvent(const PlatformEvent& event) {
363 ProcessEvent(event);
364 }
365
366 void XWindowCache::DidProcessEvent(const PlatformEvent& event) {}
367
368 void XWindowCache::ProcessEvent(const XEvent* e) {
369 switch (e->type) {
370 case PropertyNotify: {
371 Window* window = GetWindowInternal(e->xproperty.window);
372 if (!window)
373 break;
374
375 switch (e->xproperty.state) {
376 case PropertyDelete:
377 window->properties.erase(e->xproperty.atom);
378 break;
379 case PropertyNewValue:
380 CacheProperty(window, e->xproperty.atom);
381 break;
382 default:
383 NOTREACHED();
384 }
385 break;
386 }
387 case CirculateNotify: {
388 Window* window = GetWindowInternal(e->xcirculate.window);
389 if (!window)
390 break;
391
392 if (e->xcirculate.event == e->xcirculate.window)
393 break; // This is our root window
394
395 Window* parent = window->parent;
396 if (parent->id != e->xcirculate.event)
397 ResetCache();
398
399 auto it = FindChild(parent, window->id);
400 switch (e->xcirculate.place) {
401 case PlaceOnTop:
402 parent->children.push_front(std::move(*it));
403 break;
404 case PlaceOnBottom:
405 parent->children.push_back(std::move(*it));
406 break;
407 default:
408 NOTREACHED();
409 }
410 parent->children.erase(it);
411 break;
412 }
413 case ConfigureNotify: {
414 Window* window = GetWindowInternal(e->xconfigure.window);
415 if (!window)
416 break;
417
418 CacheWindowGeometryFromResponse(window, e->xconfigure);
419
420 if (e->xconfigure.event == e->xconfigure.window)
421 break;
422
423 Window* parent = window->parent;
424 if (parent->id != e->xconfigure.event)
425 ResetCache();
426
427 auto it = FindChild(parent, window->id);
428 if (e->xconfigure.above) {
429 auto it_above = FindChild(parent, e->xconfigure.above);
430 if (it == parent->children.end())
431 ResetCache();
432 else
433 parent->children.insert(it_above, std::move(*it));
434 } else {
435 // |window| is not above any other sibling window
436 parent->children.push_back(std::move(*it));
437 }
438 parent->children.erase(it);
439 break;
440 }
441 case CreateNotify: {
442 Window* parent = GetWindowInternal(e->xcreatewindow.parent);
443 if (!parent)
444 break;
445
446 if (parent->children_request) {
447 // |parent| is in the process of being cached, so we will pick up this
448 // window in the near future.
449 break;
450 }
451
452 CreateWindow(e->xcreatewindow.window, parent);
453 break;
454 }
455 case DestroyNotify: {
456 Window* window = GetWindowInternal(e->xdestroywindow.window);
457 if (!window)
458 break;
459 DestroyWindow(window);
460 break;
461 }
462 case GravityNotify: {
463 Window* window = GetWindowInternal(e->xgravity.window);
464 if (!window)
465 break;
466
467 window->x = e->xgravity.x;
468 window->y = e->xgravity.y;
469 break;
470 }
471 case MapNotify: {
472 Window* window = GetWindowInternal(e->xmap.window);
473 if (!window)
474 break;
475
476 window->override_redirect = e->xmap.override_redirect;
477 window->is_mapped = true;
478 break;
479 }
480 case ReparentNotify: {
481 Window* window = GetWindowInternal(e->xreparent.window);
482 if (!window)
483 break;
484
485 window->x = e->xreparent.x;
486 window->y = e->xreparent.y;
487
488 window->override_redirect = e->xreparent.override_redirect;
489 window->is_mapped = false; // Reparenting a window unmaps it
490
491 Window* old_parent = window->parent;
492 if (!old_parent)
493 break; // Don't worry about caching windows above our root.
494
495 Window* new_parent = GetWindowInternal(e->xreparent.parent);
496 if (!new_parent || new_parent->children_request) {
497 // |window| is either no longer in our tree, or we are already waiting
498 // to receive a list of |new_parent|'s children.
499 DestroyWindow(window);
500 break;
501 }
502 window->parent = new_parent;
503
504 auto it = FindChild(old_parent, window->id);
505 new_parent->children.push_front(std::move(*it));
506 old_parent->children.erase(it);
507 break;
508 }
509 case UnmapNotify: {
510 Window* window = GetWindowInternal(e->xunmap.window);
511 if (!window)
512 break;
513
514 window->is_mapped = false;
515 break;
516 }
517 default:
518 break;
519 }
520 }
521
522 void XWindowCache::ResetCache() {
523 NOTREACHED();
524
525 // On release builds, try to fix our state.
526 ResetCacheImpl();
527 }
528
529 void XWindowCache::ResetCacheImpl() {
530 // TODO(thomasanderson): Log something in UMA.
531 if (root_) {
532 DestroyWindow(root_.get());
533 DCHECK(windows_.empty());
534 CreateWindow(root_id_, nullptr);
535 }
536 }
537
538 void XWindowCache::CacheProperty(XWindowCache::Window* window,
539 xcb_atom_t atom) {
540 if (!net_wm_icon_ && net_wm_icon_cookie_.sequence) {
541 auto reply =
542 xcb_intern_atom_reply(connection_, net_wm_icon_cookie_, nullptr);
543 if (reply) {
544 net_wm_icon_ = reply->atom;
545 free(reply);
546 }
547 net_wm_icon_cookie_.sequence = 0;
548 }
549 if (atom == net_wm_icon_)
550 return;
551 window->properties[atom].reset(new Property(atom, window, this));
552 }
553
554 void XWindowCache::CreateWindow(xcb_window_t id, XWindowCache::Window* parent) {
555 auto it = windows_.find(id);
556 if (it != windows_.end()) {
557 // We're already tracking window |id|.
558 return;
559 }
560
561 Window* window = new Window(id, parent, this);
562 windows_[id] = window;
563 if (parent) {
564 parent->children.emplace_front(window);
565 } else {
566 DCHECK(!root_);
567 root_.reset(window);
568 }
569 }
570
571 void XWindowCache::DestroyWindow(Window* window) {
572 DCHECK(window);
573 xcb_window_t id = window->id;
574 if (window->parent) {
575 auto it = FindChild(window->parent, window->id);
576 DCHECK(it != window->parent->children.end());
577 window->parent->children.erase(it);
578 } else {
579 DCHECK_EQ(window, root_.get());
580 root_.reset();
581 }
582 windows_.erase(id);
Daniel Erat 2016/09/06 20:35:58 are you leaking Window objects here? please use st
583 }
584
585 } // namespace ui
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698