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

Unified Diff: ui/base/x/x11_window_cache.cc

Issue 2199063003: Linux: Add xcb FD to glib event loop (Closed) 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ui/base/x/x11_window_cache.h ('k') | ui/base/x/x11_window_cache_event_source.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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..9fcacc8ab6e6939edb17047220fdcafb3df08c47
--- /dev/null
+++ b/ui/base/x/x11_window_cache.cc
@@ -0,0 +1,617 @@
+// 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;
+}
+
+auto FindChild(XWindowCache::Window* parent, xcb_window_t child_id)
+ -> decltype(parent->children.begin()) {
+ return std::find_if(parent->children.begin(), parent->children.end(),
+ [child_id](XWindowCache::Window* child) {
+ return child->id == child_id;
+ });
+}
+
+} // 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(xcb_window_t id) : id_(id) {}
+
+XWindowCache::GetWindowAttributesRequest::GetWindowAttributesRequest(
+ xcb_window_t window,
+ XWindowCache* cache)
+ : Request(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)
+ : Request(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)
+ : Request(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)
+ : Request(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;
+
+ DCHECK(p->children.empty());
+
+ 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)
+ : Request(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;
+ if (reply->format == 0) {
+ // According to Xlib, a format of anything other than 8, 16, or 32 is a
+ // BadImplementation error. However, this occurs when creating a new xterm
+ // window, so just forget about the property in question.
+ window->properties.erase(atom_);
+ 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);
+ // TODO(thomasanderson): verify data_bytes is correct
+ 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), sequence_(0), 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_);
+}
+
+bool XWindowCache::BlockUntilConnectionIsReadable() {
+ if (ConnectionHasError())
+ return false;
+
+ int conn_fd = ConnectionFd();
+
+ struct pollfd rfd;
+ rfd.fd = conn_fd;
+ rfd.events = POLLIN;
+ rfd.revents = 0;
+ // TODO(thomasanderson): should we handle EAGAIN?
+ 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() {
+ // xcb serially reads events and replies and then stores them in two queues.
+ // We want to merge these queues into one to make sure we process events and
+ // replies in order.
+ //
+ // xcb_poll_for_reply does not read any new data, while xcb_poll_for_event
+ // does, so get all available events first, followed by all replies.
+ //
+ // xcb_poll_for_event reads all available data, so we cannot have an outer
+ // loop reading events and an inner loop reading replies. That would be too
+ // easy. The only way to guarantee ordering is to read all events at once,
+ // followed by all replies.
+
+ // Get all events.
+ std::queue<xcb_generic_event_t*> events;
+ xcb_generic_event_t* event;
+ while ((event = xcb_poll_for_event(connection_)))
+ events.emplace(event);
+
+ // Get all replies for which we have a queued request. Some requests, such as
+ // xcb_change_window_attributes, do not have a corresponding reply, so skip
+ // over those sequence numbers. We may also want to poll for replies
+ // ourselves, such as when getting the _NET_WM_ICON atom, or when
+ // synchronizing. Skip over these too.
+ std::queue<std::unique_ptr<Request>> requests;
+ std::queue<xcb_generic_reply_t*> replies;
+ while (!requests_.empty()) {
+ uint16_t front_sequence = requests_.front()->sequence();
+ void* reply;
+ if (!xcb_poll_for_reply(connection_, front_sequence, &reply, nullptr))
+ break;
+ if (reply) {
+ requests.emplace(std::move(requests_.front()));
+ replies.emplace(reinterpret_cast<xcb_generic_reply_t*>(reply));
+ }
+ // Remove the request from the queue even if there was an error.
+ requests_.pop();
+ }
+
+ // Process events and replies in order.
+ while (!events.empty() || !replies.empty()) {
+ // Give precedence to replies.
+ if (!replies.empty() && sequence_ == replies.front()->sequence) {
+ xcb_generic_reply_t* reply = replies.front();
+ requests.front()->OnReply(reply, this);
+ free(reply);
+ requests.pop();
+ replies.pop();
+ } else if (!events.empty() && sequence_ == events.front()->sequence) {
+ xcb_generic_event_t* event = events.front();
+ ProcessEvent(event);
+ free(event);
+ events.pop();
+ } else {
+ sequence_++;
+ }
+ }
+
+ // 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 = FindChild(parent, 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;
+
+ if (!parent->cached_children) {
+ // |parent| is in the process of being cached, so we will pick up this
+ // window in the near future.
+ 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 || !parent->cached_children) {
+ // |window| is either no longer in our tree, or we are already waiting
+ // to receive a list of |parent|'s children.
+ 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) {
+ DCHECK(window);
+ xcb_window_t id = window->id;
+ DCHECK(windows_.find(id) == windows_.end());
+
+ 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;
+ }
+ auto cookie = xcb_change_window_attributes(connection_, id, XCB_CW_EVENT_MASK,
+ &event_mask);
+ xcb_discard_reply(connection_, cookie.sequence);
+
+ // Get the desired window state AFTER requesting state change events to avoid
+ // race conditions.
+
+ if (!window->cached_children)
+ requests_.emplace(new QueryTreeRequest(id, this));
+
+ if (!window->cached_properties)
+ requests_.emplace(new ListPropertiesRequest(id, this));
+
+ if (!window->cached_attributes)
+ requests_.emplace(new GetWindowAttributesRequest(id, this));
+
+ if (!window->cached_geometry)
+ requests_.emplace(new GetGeometryRequest(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
« no previous file with comments | « ui/base/x/x11_window_cache.h ('k') | ui/base/x/x11_window_cache_event_source.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698