Chromium Code Reviews| 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..8496896f8445554d009800d2f1c97c5343c60b01 |
| --- /dev/null |
| +++ b/ui/base/x/x11_window_cache.cc |
| @@ -0,0 +1,585 @@ |
| +// 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 <X11/Xlib.h> |
| +#include <X11/Xlib-xcb.h> |
| +#include <xcb/xcbext.h> |
| + |
| +#include <algorithm> |
| +#include <cstdio> |
| +#include <cstdlib> |
| +#include <cstring> |
| + |
| +#include "base/logging.h" |
| +#include "ui/events/platform/platform_event_source.h" |
| +#include "ui/events/platform/x11/x11_event_source.h" |
| + |
| +namespace ui { |
| + |
| +namespace { |
| + |
| +const char kNetWmIcon[] = "_NET_WM_ICON"; |
| + |
| +// 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 kMaxPropertySize = 0xffff; |
| + |
| +template <typename T> |
| +void CacheWindowGeometryFromResponse(XWindowCache::Window* window, |
| + const T& response) { |
| + window->x = response.x; |
| + window->y = response.y; |
| + window->width = response.width; |
| + window->height = response.height; |
| + window->border_width = response.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](std::unique_ptr<XWindowCache::Window>& child) { |
| + return child->id == child_id; |
| + }); |
| +} |
| + |
| +} // anonymous namespace |
| + |
| +const XWindowCache::Property* XWindowCache::Window::GetProperty( |
| + XID atom) const { |
| + auto it = properties.find(atom); |
| + return it == properties.end() ? nullptr : it->second.get(); |
| +} |
| + |
| +struct XWindowCache::GetWindowAttributesRequest |
| + : public X11EventSource::Request { |
| + GetWindowAttributesRequest(Window* window, XWindowCache* cache) |
| + : Request( |
| + xcb_get_window_attributes(cache->connection_, window->id).sequence), |
| + cache_(cache), |
| + window_(window) { |
| + window_->attributes_request = this; |
| + } |
| + |
| + void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override { |
| + if (error) { |
| + switch (error->error_code) { |
| + case BadDrawable: |
| + case BadWindow: |
| + cache_->DestroyWindow(window_); |
| + return; |
| + default: |
| + NOTREACHED(); |
| + } |
| + } |
| + auto* reply = reinterpret_cast<xcb_get_window_attributes_reply_t*>(r); |
| + |
| + window_->attributes_request = nullptr; |
| + window_->override_redirect = reply->override_redirect; |
| + window_->is_mapped = reply->map_state != XCB_MAP_STATE_UNMAPPED; |
| + } |
| + |
| + protected: |
| + XWindowCache* cache_; |
| + Window* window_; |
| +}; |
| + |
| +struct XWindowCache::GetGeometryRequest : public X11EventSource::Request { |
| + GetGeometryRequest(Window* window, XWindowCache* cache) |
| + : Request(xcb_get_geometry(cache->connection_, window->id).sequence), |
| + cache_(cache), |
| + window_(window) { |
| + window_->geometry_request = this; |
| + } |
| + |
| + void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override { |
| + if (error) { |
| + switch (error->error_code) { |
| + case BadDrawable: |
| + cache_->DestroyWindow(window_); |
| + return; |
| + default: |
| + NOTREACHED(); |
| + } |
| + } |
| + auto* reply = reinterpret_cast<xcb_get_geometry_reply_t*>(r); |
| + |
| + window_->geometry_request = nullptr; |
| + CacheWindowGeometryFromResponse(window_, *reply); |
| + } |
| + |
| + protected: |
| + XWindowCache* cache_; |
| + Window* window_; |
| +}; |
| + |
| +struct XWindowCache::ListPropertiesRequest : public X11EventSource::Request { |
| + ListPropertiesRequest(Window* window, XWindowCache* cache) |
| + : Request(xcb_list_properties(cache->connection_, window->id).sequence), |
| + cache_(cache), |
| + window_(window) { |
| + window_->properties_request = this; |
| + } |
| + |
| + void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override { |
| + if (error) { |
| + switch (error->error_code) { |
| + case BadWindow: |
| + cache_->DestroyWindow(window_); |
| + return; |
| + default: |
| + NOTREACHED(); |
| + } |
| + } |
| + auto* reply = reinterpret_cast<xcb_list_properties_reply_t*>(r); |
| + |
| + window_->properties_request = nullptr; |
| + 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); |
| + } |
| + } |
| + |
| + protected: |
| + XWindowCache* cache_; |
| + Window* window_; |
| +}; |
| + |
| +struct XWindowCache::QueryTreeRequest : public X11EventSource::Request { |
| + QueryTreeRequest(Window* window, XWindowCache* cache) |
| + : Request(xcb_query_tree(cache->connection_, window->id).sequence), |
| + cache_(cache), |
| + window_(window) { |
| + window_->children_request = this; |
| + } |
| + |
| + void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override { |
| + if (error) { |
| + switch (error->error_code) { |
| + case BadWindow: |
| + cache_->DestroyWindow(window_); |
| + return; |
| + default: |
| + NOTREACHED(); |
| + } |
| + } |
| + auto* reply = reinterpret_cast<xcb_query_tree_reply_t*>(r); |
| + |
| + DCHECK(window_->children.empty()); |
| + |
| + xcb_window_t* children = xcb_query_tree_children(reply); |
| + int n_children = xcb_query_tree_children_length(reply); |
| + |
| + window_->children_request = nullptr; |
| + |
| + // Iterate over children from top-to-bottom. |
| + for (int i = 0; i < n_children; i++) |
| + cache_->CreateWindow(children[i], window_); |
| + } |
| + |
| + protected: |
| + XWindowCache* cache_; |
| + Window* window_; |
| +}; |
| + |
| +struct XWindowCache::GetPropertyRequest : public X11EventSource::Request { |
| + GetPropertyRequest(Window* window, |
| + Property* property, |
| + xcb_atom_t atom, |
| + XWindowCache* cache) |
| + : Request(xcb_get_property(cache->connection_, |
| + false, |
| + window->id, |
| + atom, |
| + XCB_ATOM_ANY, |
| + 0, |
| + kMaxPropertySize) |
| + .sequence), |
| + cache_(cache), |
| + window_(window), |
| + property_(property), |
| + atom_(atom) { |
| + property->property_request = this; |
| + } |
| + |
| + void OnReply(xcb_generic_reply_t* r, xcb_generic_error_t* error) override { |
| + if (error) { |
| + switch (error->error_code) { |
| + case BadWindow: |
| + cache_->DestroyWindow(window_); |
| + return; |
| + case BadValue: |
| + case BadAtom: |
| + default: |
| + NOTREACHED(); |
| + } |
| + } |
| + auto* reply = reinterpret_cast<xcb_get_property_reply_t*>(r); |
| + |
| + 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_request = nullptr; |
| + 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); |
| + } |
| + |
| + protected: |
| + XWindowCache* cache_; |
| + Window* window_; |
| + Property* property_; |
| + xcb_atom_t atom_; |
| +}; |
| + |
| +XWindowCache::Window::Window(xcb_window_t id, |
| + XWindowCache::Window* parent, |
| + XWindowCache* cache) |
| + : id(id), |
| + parent(parent), |
| + attributes_request(nullptr), |
| + geometry_request(nullptr), |
| + properties_request(nullptr), |
| + children_request(nullptr), |
| + cache_(cache) { |
| + uint32_t event_mask = |
| + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE; |
| + if (id == cache->root_id_) { |
| + event_mask |= XCB_EVENT_MASK_STRUCTURE_NOTIFY; |
| + } |
| + // TODO(thomasanderson): Use XForeignWindowManager to select on the mask |
| + // instead of resetting it. |
| + auto cookie = xcb_change_window_attributes(cache->connection_, id, |
| + XCB_CW_EVENT_MASK, &event_mask); |
| + xcb_discard_reply(cache->connection_, cookie.sequence); |
| + |
| + // Get the desired window state AFTER requesting state change events to avoid |
| + // race conditions. |
| + |
| + cache->event_source_->EnqueueRequest(new QueryTreeRequest(this, cache)); |
| + cache->event_source_->EnqueueRequest(new ListPropertiesRequest(this, cache)); |
| + cache->event_source_->EnqueueRequest( |
| + new GetWindowAttributesRequest(this, cache)); |
| + cache->event_source_->EnqueueRequest(new GetGeometryRequest(this, cache)); |
| + xcb_flush(cache->connection_); |
| +} |
| + |
| +XWindowCache::Window::~Window() { |
| + // The window tree was created top-down, so must be destroyed bottom-up. |
| + while (!children.empty()) |
| + cache_->DestroyWindow(children.front().get()); |
| + |
| + if (attributes_request) |
| + cache_->event_source_->DiscardRequest(attributes_request); |
| + if (geometry_request) |
| + cache_->event_source_->DiscardRequest(geometry_request); |
| + if (properties_request) |
| + cache_->event_source_->DiscardRequest(properties_request); |
| + if (children_request) |
| + cache_->event_source_->DiscardRequest(children_request); |
| + |
| + // TODO(thomasanderson): Use XForeignWindowManager to deselect on the mask |
| + // instead of resetting it. |
| + uint32_t event_mask = XCB_EVENT_MASK_NO_EVENT; |
| + auto cookie = xcb_change_window_attributes(cache_->connection_, id, |
| + XCB_CW_EVENT_MASK, &event_mask); |
| + // Window |id| may already be destroyed at this point, so the |
| + // change_attributes request may give a BadWindow error. In this case, just |
| + // ignore the error. |
| + xcb_discard_reply(cache_->connection_, cookie.sequence); |
| +} |
| + |
| +XWindowCache::Property::Property(xcb_atom_t name, |
| + Window* window, |
| + XWindowCache* cache) |
| + : name(name), property_request(nullptr), cache_(cache) { |
| + data.bits_8 = nullptr; |
| + |
| + cache->event_source_->EnqueueRequest( |
| + new GetPropertyRequest(window, this, name, cache)); |
| + xcb_flush(cache->connection_); |
| +} |
| + |
| +XWindowCache::Property::~Property() { |
| + if (property_request) |
| + cache_->event_source_->DiscardRequest(property_request); |
| + |
| + delete[] data.bits_8; |
| +} |
| + |
| +// Xlib shall own the event queue. |
| +XWindowCache::XWindowCache(XDisplay* display, |
| + X11EventSource* event_source, |
| + XID root) |
| + : display_(display), |
| + connection_(XGetXCBConnection(display_)), |
| + root_id_(root), |
| + event_source_(event_source), |
| + root_(nullptr), |
| + net_wm_icon_(0) { |
| + DCHECK(event_source); |
| + DCHECK(!xcb_connection_has_error(connection_)); |
| + |
| + if (PlatformEventSource::GetInstance()) |
| + PlatformEventSource::GetInstance()->AddPlatformEventObserver(this); |
| + |
| + net_wm_icon_cookie_ = |
| + xcb_intern_atom(connection_, false, sizeof(kNetWmIcon) - 1, kNetWmIcon); |
| + |
| + CreateWindow(root, nullptr); |
| +} |
| + |
| +XWindowCache::~XWindowCache() { |
| + if (PlatformEventSource::GetInstance()) |
| + PlatformEventSource::GetInstance()->RemovePlatformEventObserver(this); |
| +} |
| + |
| +const XWindowCache::Window* XWindowCache::GetWindow(XID id) const { |
| + return GetWindowInternal(id); |
| +} |
| + |
| +XWindowCache::Window* XWindowCache::GetWindowInternal(XID id) const { |
| + auto it = windows_.find(id); |
| + return it == windows_.end() ? nullptr : it->second; |
| +} |
| + |
| +// TODO(thomasanderson): Call ProcessEvent directly from X11EventSource. Have |
| +// ProcessEvent return an indication of if it is possible for clients other |
| +// than XWindowCache to be interested in the event, so that event dispatchers |
| +// don't get bogged down with the many events that XWindowCache selects. |
| +void XWindowCache::WillProcessEvent(const PlatformEvent& event) { |
| + ProcessEvent(event); |
| +} |
| + |
| +void XWindowCache::DidProcessEvent(const PlatformEvent& event) {} |
| + |
| +void XWindowCache::ProcessEvent(const XEvent* e) { |
| + switch (e->type) { |
| + case PropertyNotify: { |
| + Window* window = GetWindowInternal(e->xproperty.window); |
| + if (!window) |
| + break; |
| + |
| + switch (e->xproperty.state) { |
| + case PropertyDelete: |
| + window->properties.erase(e->xproperty.atom); |
| + break; |
| + case PropertyNewValue: |
| + CacheProperty(window, e->xproperty.atom); |
| + break; |
| + default: |
| + NOTREACHED(); |
| + } |
| + break; |
| + } |
| + case CirculateNotify: { |
| + Window* window = GetWindowInternal(e->xcirculate.window); |
| + if (!window) |
| + break; |
| + |
| + if (e->xcirculate.event == e->xcirculate.window) |
| + break; // This is our root window |
| + |
| + Window* parent = window->parent; |
| + if (parent->id != e->xcirculate.event) |
| + ResetCache(); |
| + |
| + auto it = FindChild(parent, window->id); |
| + switch (e->xcirculate.place) { |
| + case PlaceOnTop: |
| + parent->children.push_front(std::move(*it)); |
| + break; |
| + case PlaceOnBottom: |
| + parent->children.push_back(std::move(*it)); |
| + break; |
| + default: |
| + NOTREACHED(); |
| + } |
| + parent->children.erase(it); |
| + break; |
| + } |
| + case ConfigureNotify: { |
| + Window* window = GetWindowInternal(e->xconfigure.window); |
| + if (!window) |
| + break; |
| + |
| + CacheWindowGeometryFromResponse(window, e->xconfigure); |
| + |
| + if (e->xconfigure.event == e->xconfigure.window) |
| + break; |
| + |
| + Window* parent = window->parent; |
| + if (parent->id != e->xconfigure.event) |
| + ResetCache(); |
| + |
| + auto it = FindChild(parent, window->id); |
| + if (e->xconfigure.above) { |
| + auto it_above = FindChild(parent, e->xconfigure.above); |
| + if (it == parent->children.end()) |
| + ResetCache(); |
| + else |
| + parent->children.insert(it_above, std::move(*it)); |
| + } else { |
| + // |window| is not above any other sibling window |
| + parent->children.push_back(std::move(*it)); |
| + } |
| + parent->children.erase(it); |
| + break; |
| + } |
| + case CreateNotify: { |
| + Window* parent = GetWindowInternal(e->xcreatewindow.parent); |
| + if (!parent) |
| + break; |
| + |
| + if (parent->children_request) { |
| + // |parent| is in the process of being cached, so we will pick up this |
| + // window in the near future. |
| + break; |
| + } |
| + |
| + CreateWindow(e->xcreatewindow.window, parent); |
| + break; |
| + } |
| + case DestroyNotify: { |
| + Window* window = GetWindowInternal(e->xdestroywindow.window); |
| + if (!window) |
| + break; |
| + DestroyWindow(window); |
| + break; |
| + } |
| + case GravityNotify: { |
| + Window* window = GetWindowInternal(e->xgravity.window); |
| + if (!window) |
| + break; |
| + |
| + window->x = e->xgravity.x; |
| + window->y = e->xgravity.y; |
| + break; |
| + } |
| + case MapNotify: { |
| + Window* window = GetWindowInternal(e->xmap.window); |
| + if (!window) |
| + break; |
| + |
| + window->override_redirect = e->xmap.override_redirect; |
| + window->is_mapped = true; |
| + break; |
| + } |
| + case ReparentNotify: { |
| + Window* window = GetWindowInternal(e->xreparent.window); |
| + if (!window) |
| + break; |
| + |
| + window->x = e->xreparent.x; |
| + window->y = e->xreparent.y; |
| + |
| + window->override_redirect = e->xreparent.override_redirect; |
| + window->is_mapped = false; // Reparenting a window unmaps it |
| + |
| + Window* old_parent = window->parent; |
| + if (!old_parent) |
| + break; // Don't worry about caching windows above our root. |
| + |
| + Window* new_parent = GetWindowInternal(e->xreparent.parent); |
| + if (!new_parent || new_parent->children_request) { |
| + // |window| is either no longer in our tree, or we are already waiting |
| + // to receive a list of |new_parent|'s children. |
| + DestroyWindow(window); |
| + break; |
| + } |
| + window->parent = new_parent; |
| + |
| + auto it = FindChild(old_parent, window->id); |
| + new_parent->children.push_front(std::move(*it)); |
| + old_parent->children.erase(it); |
| + break; |
| + } |
| + case UnmapNotify: { |
| + Window* window = GetWindowInternal(e->xunmap.window); |
| + if (!window) |
| + break; |
| + |
| + window->is_mapped = false; |
| + break; |
| + } |
| + default: |
| + break; |
| + } |
| +} |
| + |
| +void XWindowCache::ResetCache() { |
| + NOTREACHED(); |
| + |
| + // On release builds, try to fix our state. |
| + ResetCacheImpl(); |
| +} |
| + |
| +void XWindowCache::ResetCacheImpl() { |
| + // TODO(thomasanderson): Log something in UMA. |
| + if (root_) { |
| + DestroyWindow(root_.get()); |
| + DCHECK(windows_.empty()); |
| + CreateWindow(root_id_, nullptr); |
| + } |
| +} |
| + |
| +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; |
| + window->properties[atom].reset(new Property(atom, window, this)); |
| +} |
| + |
| +void XWindowCache::CreateWindow(xcb_window_t id, XWindowCache::Window* parent) { |
| + auto it = windows_.find(id); |
| + if (it != windows_.end()) { |
| + // We're already tracking window |id|. |
| + return; |
| + } |
| + |
| + Window* window = new Window(id, parent, this); |
| + windows_[id] = window; |
| + if (parent) { |
| + parent->children.emplace_front(window); |
| + } else { |
| + DCHECK(!root_); |
| + root_.reset(window); |
| + } |
| +} |
| + |
| +void XWindowCache::DestroyWindow(Window* window) { |
| + DCHECK(window); |
| + xcb_window_t id = window->id; |
| + if (window->parent) { |
| + auto it = FindChild(window->parent, window->id); |
| + DCHECK(it != window->parent->children.end()); |
| + window->parent->children.erase(it); |
| + } else { |
| + DCHECK_EQ(window, root_.get()); |
| + root_.reset(); |
| + } |
| + windows_.erase(id); |
|
Daniel Erat
2016/09/06 20:35:58
are you leaking Window objects here? please use st
|
| +} |
| + |
| +} // namespace ui |