blob: d4f48d7f89786bf9776a86ff9780da02d7b0f20f [file] [log] [blame]
// Copyright 2022 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/gfx/x/window_cache.h"
#include "base/bind.h"
#include "base/containers/contains.h"
#include "base/containers/cxx20_erase_vector.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "ui/gfx/x/connection.h"
#include "ui/gfx/x/event.h"
#include "ui/gfx/x/future.h"
#include "ui/gfx/x/x11_window_event_manager.h"
#include "ui/gfx/x/xproto.h"
namespace x11 {
ScopedShapeEventSelector::ScopedShapeEventSelector(Connection* connection,
Window window)
: connection_(connection), window_(window) {
connection_->shape().SelectInput(
{.destination_window = window_, .enable = true});
}
ScopedShapeEventSelector::~ScopedShapeEventSelector() {
connection_->shape().SelectInput(
{.destination_window = window_, .enable = false});
}
WindowCache::WindowInfo::WindowInfo() = default;
WindowCache::WindowInfo::~WindowInfo() = default;
// static
WindowCache* WindowCache::instance_ = nullptr;
WindowCache::WindowCache(Connection* connection, Window root)
: connection_(connection), root_(root) {
DCHECK(!instance_) << "Only one WindowCache should be active at a time";
instance_ = this;
connection_->AddEventObserver(this);
// We select for SubstructureNotify events on all windows (to receive
// CreateNotify events), which will cause events to be sent for all child
// windows. This means we need to additionally select for StructureNotify
// changes for the root window.
root_events_ =
std::make_unique<XScopedEventSelector>(root_, EventMask::StructureNotify);
AddWindow(root_, Window::None);
}
WindowCache::~WindowCache() {
connection_->RemoveEventObserver(this);
DCHECK_EQ(instance_, this);
instance_ = nullptr;
}
void WindowCache::SyncForTest() {
do {
// Perform a blocking sync to prevent spinning while waiting for replies.
connection_->Sync();
connection_->DispatchAll();
} while (pending_requests_);
}
void WindowCache::OnEvent(const Event& event) {
// Ignore events sent by clients since the server will send everything
// we need and client events may have different semantics (eg.
// ConfigureNotifyEvents are parent-relative if sent by the server but
// root-relative when sent by the WM).
if (event.send_event())
return;
if (auto* configure = event.As<ConfigureNotifyEvent>()) {
if (auto* info = GetInfo(configure->window)) {
info->x_px = configure->x;
info->y_px = configure->y;
info->width_px = configure->width;
info->height_px = configure->height;
info->border_width_px = configure->border_width;
if (auto* siblings = GetChildren(info->parent)) {
Window window = configure->window;
Window above = configure->above_sibling;
auto src = std::find(siblings->begin(), siblings->end(), window);
auto dst = std::find(siblings->begin(), siblings->end(), above);
auto end = siblings->end();
if (src != end && (dst != end || above == Window::None)) {
dst = above == Window::None ? siblings->begin() : ++dst;
if (src < dst)
std::rotate(src, src + 1, dst);
else if (src > dst)
std::rotate(dst, src, src + 1);
}
}
}
} else if (auto* create = event.As<CreateNotifyEvent>()) {
if (auto* info = GetInfo(create->parent)) {
info->children.push_back(create->window);
AddWindow(create->window, create->parent);
}
} else if (auto* destroy = event.As<DestroyNotifyEvent>()) {
if (auto* info = GetInfo(destroy->window)) {
if (auto* siblings = GetChildren(info->parent))
base::Erase(*siblings, destroy->window);
windows_.erase(destroy->window);
}
} else if (auto* map = event.As<MapNotifyEvent>()) {
if (auto* info = GetInfo(map->window))
info->mapped = true;
} else if (auto* unmap = event.As<UnmapNotifyEvent>()) {
if (auto* info = GetInfo(unmap->window))
info->mapped = false;
} else if (auto* reparent = event.As<ReparentNotifyEvent>()) {
if (auto* info = GetInfo(reparent->window)) {
if (auto* old_siblings = GetChildren(info->parent))
base::Erase(*old_siblings, reparent->window);
if (auto* new_siblings = GetChildren(reparent->parent))
new_siblings->push_back(reparent->window);
info->parent = reparent->parent;
}
} else if (auto* gravity = event.As<GravityNotifyEvent>()) {
if (auto* info = GetInfo(gravity->window)) {
info->x_px = gravity->x;
info->y_px = gravity->y;
}
} else if (auto* circulate = event.As<CirculateEvent>()) {
if (auto* info = GetInfo(circulate->window)) {
if (auto* siblings = GetChildren(info->parent)) {
base::Erase(*siblings, circulate->window);
if (circulate->place == Place::OnTop)
siblings->push_back(circulate->window);
else
siblings->insert(siblings->begin(), circulate->window);
}
}
} else if (auto* shape = event.As<Shape::NotifyEvent>()) {
Window window = shape->affected_window;
Shape::Sk kind = shape->shape_kind;
if (base::Contains(windows_, window)) {
connection_->shape()
.GetRectangles(window, kind)
.OnResponse(base::BindOnce(&WindowCache::OnGetRectanglesResponse,
weak_factory_.GetWeakPtr(), window, kind));
pending_requests_++;
}
}
}
void WindowCache::AddWindow(Window window, Window parent) {
if (base::Contains(windows_, window))
return;
WindowInfo& info = windows_[window];
info.parent = parent;
// Events must be selected before getting the initial window info to prevent
// race conditions.
info.events = std::make_unique<XScopedEventSelector>(
window, EventMask::SubstructureNotify);
connection_->GetWindowAttributes(window).OnResponse(
base::BindOnce(&WindowCache::OnGetWindowAttributesResponse,
weak_factory_.GetWeakPtr(), window));
connection_->GetGeometry(window).OnResponse(base::BindOnce(
&WindowCache::OnGetGeometryResponse, weak_factory_.GetWeakPtr(), window));
connection_->QueryTree(window).OnResponse(base::BindOnce(
&WindowCache::OnQueryTreeResponse, weak_factory_.GetWeakPtr(), window));
pending_requests_ += 3;
auto& shape = connection_->shape();
if (shape.present()) {
info.shape_events =
std::make_unique<ScopedShapeEventSelector>(connection_, window);
for (auto kind : {Shape::Sk::Bounding, Shape::Sk::Input}) {
shape.GetRectangles(window, kind)
.OnResponse(base::BindOnce(&WindowCache::OnGetRectanglesResponse,
weak_factory_.GetWeakPtr(), window, kind));
pending_requests_++;
}
}
}
WindowCache::WindowInfo* WindowCache::GetInfo(Window window) {
auto it = windows_.find(window);
if (it == windows_.end())
return nullptr;
return &it->second;
}
std::vector<Window>* WindowCache::GetChildren(Window window) {
if (auto* info = GetInfo(window))
return &info->children;
return nullptr;
}
WindowCache::WindowInfo* WindowCache::OnResponse(Window window,
bool has_reply) {
pending_requests_--;
if (!has_reply) {
windows_.erase(window);
return nullptr;
}
auto it = windows_.find(window);
if (it == windows_.end())
return nullptr;
return &it->second;
}
void WindowCache::OnGetWindowAttributesResponse(
Window window,
GetWindowAttributesResponse response) {
if (auto* info = OnResponse(window, response.reply.get()))
info->mapped = response->map_state != MapState::Unmapped;
}
void WindowCache::OnGetGeometryResponse(Window window,
GetGeometryResponse response) {
if (auto* info = OnResponse(window, response.reply.get())) {
info->x_px = response->x;
info->y_px = response->y;
info->width_px = response->width;
info->height_px = response->height;
}
}
void WindowCache::OnQueryTreeResponse(Window window,
QueryTreeResponse response) {
if (auto* info = OnResponse(window, response.reply.get())) {
info->parent = response->parent;
info->children = std::move(response->children);
for (auto child : info->children)
AddWindow(child, window);
}
}
void WindowCache::OnGetRectanglesResponse(
Window window,
Shape::Sk kind,
Shape::GetRectanglesResponse response) {
if (auto* info = OnResponse(window, response.reply.get())) {
switch (kind) {
case Shape::Sk::Bounding:
info->bounding_rects_px = std::move(response->rectangles);
break;
case Shape::Sk::Clip:
NOTREACHED();
break;
case Shape::Sk::Input:
info->input_rects_px = std::move(response->rectangles);
break;
}
}
}
} // namespace x11