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

Unified 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, 5 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 side-by-side diff with in-line comments
Download patch
Index: ui/base/x/x11_window_cache.cc
diff --git a/ui/base/x/x11_window_cache.cc b/ui/base/x/x11_window_cache.cc
new file mode 100644
index 0000000000000000000000000000000000000000..88c9e21aeaf6ec7fc1b544ee5175268bd4daa079
--- /dev/null
+++ b/ui/base/x/x11_window_cache.cc
@@ -0,0 +1,583 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/x/x11_window_cache.h"
+
+#include <poll.h>
+#include <X11/Xlib.h>
+#include <xcb/xcbext.h>
+
+#include <algorithm>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include "base/logging.h"
+
+namespace ui {
+
+namespace {
+
+const char kNetWmIcon[] = "_NET_WM_ICON";
+
+template <typename T>
+void CacheWindowGeometryFromEvent(XWindowCache::Window* window, T* event) {
+ window->cached_geometry = true;
+ window->x = event->x;
+ window->y = event->y;
+ window->width = event->width;
+ window->height = event->height;
+ window->border_width = event->border_width;
+}
+
+} // anonymous namespace
+
+XWindowCache::Property::Property(xcb_atom_t name)
+ : name(name), cached_property(false) {
+ data.bits_8 = nullptr;
+}
+
+XWindowCache::Property::~Property() {
+ delete[] data.bits_8;
+}
+
+const XWindowCache::Property* XWindowCache::Window::GetProperty(
+ XID atom) const {
+ auto it = properties.find(atom);
+ return it == properties.end() ? nullptr : it->second.get();
+}
+
+XWindowCache::Window::Window(xcb_window_t id, XWindowCache::Window* parent)
+ : id(id),
+ parent(parent),
+ cached_attributes(false),
+ cached_geometry(false),
+ cached_properties(false),
+ cached_children(false) {}
+
+XWindowCache::Window::~Window() {}
+
+XWindowCache::Request::Request() {}
+XWindowCache::Request::~Request() {}
+
+XWindowCache::GetWindowAttributesRequest::GetWindowAttributesRequest(
+ xcb_window_t window,
+ XWindowCache* cache)
+ : id(window) {
+ sequence = xcb_get_window_attributes(cache->connection_, window).sequence;
+}
+
+void XWindowCache::GetWindowAttributesRequest::OnReply(void* r,
+ XWindowCache* cache) {
+ auto* reply = reinterpret_cast<xcb_get_window_attributes_reply_t*>(r);
+
+ // Don't bother getting stale attributes if the window was destroyed.
+ Window* window = cache->GetWindow(id);
+ if (!window)
+ return;
+
+ window->cached_attributes = true;
+ window->override_redirect = reply->override_redirect;
+ window->is_mapped = reply->map_state != XCB_MAP_STATE_UNMAPPED;
+}
+
+XWindowCache::GetGeometryRequest::GetGeometryRequest(xcb_window_t window,
+ XWindowCache* cache)
+ : id(window) {
+ sequence = xcb_get_geometry(cache->connection_, window).sequence;
+}
+
+void XWindowCache::GetGeometryRequest::OnReply(void* r, XWindowCache* cache) {
+ auto* reply = reinterpret_cast<xcb_get_geometry_reply_t*>(r);
+
+ // Don't bother getting stale attributes if the window was destroyed.
+ Window* window = cache->GetWindow(id);
+ if (!window)
+ return;
+
+ window->cached_geometry = true;
+ window->x = reply->x;
+ window->y = reply->y;
+ window->width = reply->width;
+ window->height = reply->height;
+ window->border_width = reply->border_width;
+}
+
+XWindowCache::ListPropertiesRequest::ListPropertiesRequest(xcb_window_t window,
+ XWindowCache* cache)
+ : id(window) {
+ sequence = xcb_list_properties(cache->connection_, window).sequence;
+}
+
+void XWindowCache::ListPropertiesRequest::OnReply(void* r,
+ XWindowCache* cache) {
+ auto* reply = reinterpret_cast<xcb_list_properties_reply_t*>(r);
+
+ // Don't bother getting stale properties if the window was destroyed.
+ Window* window = cache->GetWindow(id);
+ if (!window)
+ return;
+
+ window->cached_properties = true;
+ for (int i = 0; i < xcb_list_properties_atoms_length(reply); i++) {
+ xcb_atom_t atom = xcb_list_properties_atoms(reply)[i];
+ cache->CacheProperty(window, atom);
+ }
+}
+
+XWindowCache::QueryTreeRequest::QueryTreeRequest(xcb_window_t window,
+ XWindowCache* cache)
+ : id(window) {
+ sequence = xcb_query_tree(cache->connection_, window).sequence;
+}
+
+void XWindowCache::QueryTreeRequest::OnReply(void* r, XWindowCache* cache) {
+ auto* reply = reinterpret_cast<xcb_query_tree_reply_t*>(r);
+
+ // Don't bother getting stale children if the parent was destroyed.
+ Window* p = cache->GetWindow(id);
+ if (!p)
+ return;
+
+ xcb_window_t* children = xcb_query_tree_children(reply);
+ int n_children = xcb_query_tree_children_length(reply);
+
+ p->cached_children = true;
+
+ // Iterate over children from top-to-bottom.
+ for (int i = n_children - 1; i >= 0; i--) {
+ Window* child = new Window(children[i], p);
+ p->children.push_back(child);
+ cache->CacheWindow(child);
+ }
+}
+
+XWindowCache::GetPropertyRequest::GetPropertyRequest(xcb_window_t window,
+ xcb_atom_t atom,
+ XWindowCache* cache)
+ : id(window), atom(atom) {
+ // In case a property's value is huge, only cache the first 64KiB, and
+ // indicate in Property that the cached value is not the entire value.
+ static constexpr auto max_property_size = 0xffff;
+ sequence = xcb_get_property(cache->connection_, false, window, atom,
+ XCB_ATOM_ANY, 0, max_property_size)
+ .sequence;
+}
+
+void XWindowCache::GetPropertyRequest::OnReply(void* r, XWindowCache* cache) {
+ auto* reply = reinterpret_cast<xcb_get_property_reply_t*>(r);
+
+ Window* window = cache->GetWindow(id);
+ if (!window)
+ return;
+
+ if (window->properties.find(atom) == window->properties.end())
+ return;
+ Property* property = window->properties[atom].get();
+
+ property->cached_property = true;
+ property->type = reply->type;
+ DCHECK(reply->format == 8 || reply->format == 16 || reply->format == 32);
+ property->data_format = reply->format;
+ property->data_length = xcb_get_property_value_length(reply);
+ uint32_t data_bytes = property->data_length * (property->data_format / 8);
+ property->data.bits_8 = new uint8_t[data_bytes];
+ std::memcpy(property->data.bits_8, xcb_get_property_value(reply), data_bytes);
+}
+
+XWindowCache::XWindowCache(XDisplay* display, XID root)
+ : root_(nullptr), net_wm_icon_(0) {
+ connection_ = xcb_connect(DisplayString(display), nullptr);
+ if (xcb_connection_has_error(connection_))
+ return;
+
+ net_wm_icon_cookie_ =
+ xcb_intern_atom(connection_, false, sizeof(kNetWmIcon) - 1, kNetWmIcon);
+
+ root_ = new Window(root, nullptr);
+ CacheWindow(root_);
+
+ xcb_flush(connection_);
+}
+
+XWindowCache::~XWindowCache() {
+ xcb_disconnect(connection_);
+}
+
+bool XWindowCache::ConnectionHasError() const {
+ return xcb_connection_has_error(connection_);
+}
+
+int XWindowCache::ConnectionFd() const {
+ return xcb_get_file_descriptor(connection_);
+}
+
+void XWindowCache::PrintWindowTreeForTesting() const {
+ PrintWindowTreeForTestingAux(root_, 0);
+}
+
+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.
+ int indent) const {
+ if (!window)
+ return;
+ for (int i = 0; i < indent; i++)
+ printf("\t");
+ printf("%d\n", window->id);
+ if (window->cached_children) {
+ for (Window* child : window->children)
+ PrintWindowTreeForTestingAux(child, indent + 1);
+ }
+}
+
+bool XWindowCache::BlockUntilConnectionIsReadable() {
+ if (ConnectionHasError())
+ return false;
+
+ int conn_fd = ConnectionFd();
+
+ struct pollfd rfd;
+ rfd.fd = conn_fd;
+ rfd.events = POLLIN;
+ rfd.revents = 0;
+ poll(&rfd, 1, 3000); // Use a timeout of 3s.
+ return rfd.revents & POLLIN;
+}
+
+const XWindowCache::Window* XWindowCache::GetWindow(XID id) const {
+ return GetWindow(static_cast<xcb_window_t>(id));
+}
+
+XWindowCache::Window* XWindowCache::GetWindow(xcb_window_t id) const {
+ auto it = windows_.find(id);
+ return it == windows_.end() ? nullptr : it->second.get();
+}
+
+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
+ // xcb serially reads events and replies and then stores them in two queues.
+ // There's no perfect way to merge these responses back into a chronological
+ // stream, so handle all replies before events. This avoids getting stuck in
+ // an old state.
+ //
+ // xcb_poll_for_reply does not read any new data, while xcb_poll_for_event
+ // does, so get all available events first and store them so they can be
+ // processed last.
+
+ // Get all events.
+ std::vector<xcb_generic_event_t*> events;
+ xcb_generic_event_t* event;
+ while ((event = xcb_poll_for_event(connection_)))
+ events.push_back(event);
+
+ // Process all replies.
+ while (!requests_.empty()) {
+ void* reply = nullptr;
+ Request* request = requests_.front().get();
+ if (!xcb_poll_for_reply(connection_, request->sequence, &reply, nullptr))
+ break;
+ if (reply) {
+ request->OnReply(reply, this);
+ free(reply);
+ }
+ requests_.pop();
+ }
+
+ // Process all events.
+ for (auto* event : events) {
+ ProcessEvent(event);
+ free(event);
+ }
+
+ // Write out any requests we may have made.
+ xcb_flush(connection_);
+}
+
+bool XWindowCache::BlockUntilTreeIsCached() {
+ while (!requests_.empty()) {
+ if (!BlockUntilConnectionIsReadable())
+ return false;
+ OnConnectionReadable();
+ }
+ return true;
+}
+
+bool XWindowCache::Synchronize() {
+ if (!root_)
+ return true; // There's nothing to synchronize if the root was destroyed.
+
+ // Try getting some info about the root window and wait until we get a
+ // response or an error.
+ auto sequence = xcb_get_window_attributes(connection_, root_->id).sequence;
+ xcb_flush(connection_);
+
+ while (BlockUntilConnectionIsReadable()) {
+ OnConnectionReadable();
+ void* reply = nullptr;
+ if (!xcb_poll_for_reply(connection_, sequence, &reply, nullptr))
+ continue;
+ if (reply) {
+ free(reply);
+ return true;
+ }
+ // There was some sort of error. This may just be because the root was
+ // destroyed. Only return false if there was a connection error.
+ return !ConnectionHasError();
+ }
+ xcb_discard_reply(connection_, sequence);
+ return false;
+}
+
+bool XWindowCache::SynchronizeWith(XDisplay* display) {
+ XSync(display, False);
+ return Synchronize();
+}
+
+void XWindowCache::ProcessEvent(xcb_generic_event_t* g) {
+ switch (g->response_type & ~0x80) {
+ case XCB_PROPERTY_NOTIFY: {
+ auto* event = reinterpret_cast<xcb_property_notify_event_t*>(g);
+
+ Window* window = GetWindow(event->window);
+ if (!window)
+ break;
+
+ switch (event->state) {
+ case XCB_PROPERTY_DELETE:
+ window->properties.erase(event->atom);
+ break;
+ case XCB_PROPERTY_NEW_VALUE:
+ CacheProperty(window, event->atom);
+ break;
+ default:
+ NOTREACHED();
+ }
+ break;
+ }
+ case XCB_CIRCULATE_NOTIFY: {
+ auto* event = reinterpret_cast<xcb_circulate_notify_event_t*>(g);
+
+ Window* window = GetWindow(event->window);
+ if (!window)
+ break;
+
+ if (event->event == event->window)
+ break; // This is our root window
+
+ Window* parent = window->parent;
+ DCHECK_EQ(parent->id, event->event);
+
+ parent->children.remove(window);
+ switch (event->place) {
+ case XCB_PLACE_ON_TOP:
+ parent->children.push_front(window);
+ break;
+ case XCB_PLACE_ON_BOTTOM:
+ parent->children.push_back(window);
+ break;
+ default:
+ NOTREACHED();
+ }
+ break;
+ }
+ case XCB_CONFIGURE_NOTIFY: {
+ auto* event = reinterpret_cast<xcb_configure_notify_event_t*>(g);
+
+ Window* window = GetWindow(event->window);
+ if (!window)
+ break;
+
+ CacheWindowGeometryFromEvent(window, event);
+
+ if (event->event == event->window)
+ break;
+
+ Window* parent = window->parent;
+ DCHECK_EQ(parent->id, event->event);
+
+ parent->children.remove(window);
+ if (event->above_sibling) {
+ auto it = std::find_if(parent->children.begin(), parent->children.end(),
+ [&event](Window* child) {
+ return child->id == event->above_sibling;
+ });
+ if (it == parent->children.end()) {
+ NOTREACHED();
+ // We got into a bad state. Recache |parent|.
+ Window* grandparent = parent->parent;
+ xcb_window_t id = parent->id;
+ DeleteSubtree(parent);
+ CacheWindow(new Window(id, grandparent));
+ } else {
+ parent->children.insert(it, window);
+ }
+ } else {
+ // |window| is not above any other sibling window
+ parent->children.push_back(window);
+ }
+ break;
+ }
+ case XCB_CREATE_NOTIFY: {
+ auto* event = reinterpret_cast<xcb_create_notify_event_t*>(g);
+
+ Window* parent = GetWindow(event->parent);
+ if (!parent)
+ break;
+
+ Window* window = new Window(event->window, parent);
+
+ window->cached_attributes = true;
+ window->override_redirect = event->override_redirect;
+ window->is_mapped = false;
+
+ CacheWindowGeometryFromEvent(window, event);
+
+ CacheWindow(window);
+ parent->children.push_front(window);
+ break;
+ }
+ case XCB_DESTROY_NOTIFY: {
+ auto* event = reinterpret_cast<xcb_destroy_notify_event_t*>(g);
+
+ Window* window = GetWindow(event->window);
+ if (!window)
+ break;
+
+ if (window->parent) {
+ auto& children = window->parent->children;
+ auto it = std::find(children.begin(), children.end(), window);
+ if (it != children.end())
+ window->parent->children.erase(it);
+ } else {
+ // Window has no parent, so this must be the root that's going away.
+ root_ = nullptr;
+ }
+
+ DeleteSubtree(window);
+ break;
+ }
+ case XCB_GRAVITY_NOTIFY: {
+ auto* event = reinterpret_cast<xcb_gravity_notify_event_t*>(g);
+
+ Window* window = GetWindow(event->window);
+ if (!window)
+ break;
+
+ window->x = event->x;
+ window->y = event->y;
+ break;
+ }
+ case XCB_MAP_NOTIFY: {
+ auto* event = reinterpret_cast<xcb_map_notify_event_t*>(g);
+
+ Window* window = GetWindow(event->window);
+ if (!window)
+ break;
+
+ window->cached_attributes = true;
+ window->override_redirect = event->override_redirect;
+ window->is_mapped = true;
+ break;
+ }
+ case XCB_REPARENT_NOTIFY: {
+ auto* event = reinterpret_cast<xcb_reparent_notify_event_t*>(g);
+
+ Window* window = GetWindow(event->window);
+ if (!window)
+ break;
+
+ window->x = event->x;
+ window->y = event->y;
+
+ window->cached_attributes = true;
+ window->override_redirect = event->override_redirect;
+ window->is_mapped = false; // Reparenting a window unmaps it
+
+ Window* parent = window->parent;
+ if (parent)
+ parent->children.remove(window);
+ else
+ break; // Don't worry about caching windows above our root.
+
+ parent = GetWindow(event->parent);
+ if (!parent) {
+ // |window| is no longer in our tree
+ DeleteSubtree(window);
+ break;
+ }
+ window->parent = parent;
+
+ parent->children.push_front(window);
+ break;
+ }
+ case XCB_UNMAP_NOTIFY: {
+ auto* event = reinterpret_cast<xcb_unmap_notify_event_t*>(g);
+
+ Window* window = GetWindow(event->window);
+ if (!window)
+ break;
+
+ window->is_mapped = false;
+ break;
+ }
+ default:
+ // We can't opt-out of receiving events like MappingNotify or
+ // ClientMessage.
+ break;
+ }
+}
+
+void XWindowCache::CacheProperty(XWindowCache::Window* window,
+ xcb_atom_t atom) {
+ if (!net_wm_icon_ && net_wm_icon_cookie_.sequence) {
+ auto reply =
+ xcb_intern_atom_reply(connection_, net_wm_icon_cookie_, nullptr);
+ if (reply) {
+ net_wm_icon_ = reply->atom;
+ free(reply);
+ }
+ net_wm_icon_cookie_.sequence = 0;
+ }
+ if (atom == net_wm_icon_)
+ return;
+ xcb_window_t id = window->id;
+ Property* property = new Property(atom);
+ window->properties[atom].reset(property);
+ if (!property->cached_property)
+ requests_.emplace(new GetPropertyRequest(id, atom, this));
+}
+
+void XWindowCache::CacheWindow(XWindowCache::Window* window) {
+ xcb_window_t id = window->id;
+ uint32_t event_mask =
+ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE;
+ if (id == root_->id) {
+ event_mask |= XCB_EVENT_MASK_STRUCTURE_NOTIFY;
+ }
+ xcb_change_window_attributes(connection_, id, XCB_CW_EVENT_MASK, &event_mask);
+
+ // Get the desired window state AFTER requesting state change events to avoid
+ // race conditions.
+
+ if (!window->cached_attributes)
+ requests_.emplace(new GetWindowAttributesRequest(id, this));
+
+ if (!window->cached_geometry)
+ requests_.emplace(new GetGeometryRequest(id, this));
+
+ if (!window->cached_properties)
+ requests_.emplace(new ListPropertiesRequest(id, this));
+
+ if (!window->cached_children)
+ requests_.emplace(new QueryTreeRequest(id, this));
+
+ windows_[id].reset(window);
+}
+
+void XWindowCache::DeleteSubtree(XWindowCache::Window* window) {
+ DCHECK(window);
+ for (auto* child : window->children)
+ DeleteSubtree(child);
+ windows_.erase(window->id);
+}
+
+} // namespace ui

Powered by Google App Engine
This is Rietveld 408576698