blob: dbf43568d385355ece2aeb7719aad342a5102fef [file] [log] [blame]
// Copyright 2020 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/connection.h"
#include <X11/Xlib-xcb.h>
#include <X11/Xlib.h>
#include <xcb/xcb.h>
#include <algorithm>
#include "base/command_line.h"
#include "base/memory/scoped_refptr.h"
#include "ui/gfx/x/bigreq.h"
#include "ui/gfx/x/event.h"
#include "ui/gfx/x/randr.h"
#include "ui/gfx/x/x11_switches.h"
#include "ui/gfx/x/xproto_internal.h"
#include "ui/gfx/x/xproto_types.h"
namespace x11 {
namespace {
// On the wire, sequence IDs are 16 bits. In xcb, they're usually extended to
// 32 and sometimes 64 bits. In Xlib, they're extended to unsigned long, which
// may be 32 or 64 bits depending on the platform. This function is intended to
// prevent bugs caused by comparing two differently sized sequences. Also
// handles rollover. To use, compare the result of this function with 0. For
// example, to compare seq1 <= seq2, use CompareSequenceIds(seq1, seq2) <= 0.
template <typename T, typename U>
auto CompareSequenceIds(T t, U u) {
static_assert(std::is_unsigned<T>::value, "");
static_assert(std::is_unsigned<U>::value, "");
// Cast to the smaller of the two types so that comparisons will always work.
// If we casted to the larger type, then the smaller type will be zero-padded
// and may incorrectly compare less than the other value.
using SmallerType =
typename std::conditional<sizeof(T) <= sizeof(U), T, U>::type;
SmallerType t0 = static_cast<SmallerType>(t);
SmallerType u0 = static_cast<SmallerType>(u);
using SignedType = typename std::make_signed<SmallerType>::type;
return static_cast<SignedType>(t0 - u0);
}
XDisplay* OpenNewXDisplay() {
if (!XInitThreads())
return nullptr;
std::string display_str =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kX11Display);
return XOpenDisplay(display_str.empty() ? nullptr : display_str.c_str());
}
} // namespace
Connection* Connection::Get() {
static Connection* instance = new Connection;
return instance;
}
Connection::Connection() : XProto(this), display_(OpenNewXDisplay()) {
if (display_) {
XSetEventQueueOwner(display_, XCBOwnsEventQueue);
auto buf = ReadBuffer(base::MakeRefCounted<UnretainedRefCountedMemory>(
xcb_get_setup(XcbConnection())));
setup_ = Read<Setup>(&buf);
default_screen_ = &setup_.roots[DefaultScreenId()];
default_root_depth_ = &*std::find_if(
default_screen_->allowed_depths.begin(),
default_screen_->allowed_depths.end(), [&](const Depth& depth) {
return depth.depth == default_screen_->root_depth;
});
default_root_visual_ = &*std::find_if(
default_root_depth_->visuals.begin(),
default_root_depth_->visuals.end(), [&](const VisualType visual) {
return visual.visual_id == default_screen_->root_visual;
});
} else {
// Default-initialize the setup data so we always have something to return.
setup_.roots.emplace_back();
default_screen_ = &setup_.roots[0];
default_screen_->allowed_depths.emplace_back();
default_root_depth_ = &default_screen_->allowed_depths[0];
default_root_depth_->visuals.emplace_back();
default_root_visual_ = &default_root_depth_->visuals[0];
}
ExtensionManager::Init(this);
if (auto response = bigreq().Enable({}).Sync())
extended_max_request_length_ = response->maximum_request_length;
}
Connection::~Connection() {
if (display_)
XCloseDisplay(display_);
}
xcb_connection_t* Connection::XcbConnection() {
if (!display())
return nullptr;
return XGetXCBConnection(display());
}
Connection::Request::Request(unsigned int sequence,
FutureBase::ResponseCallback callback)
: sequence(sequence), callback(std::move(callback)) {}
Connection::Request::Request(Request&& other)
: sequence(other.sequence), callback(std::move(other.callback)) {}
Connection::Request::~Request() = default;
bool Connection::HasNextResponse() const {
return !requests_.empty() &&
CompareSequenceIds(XLastKnownRequestProcessed(display_),
requests_.front().sequence) >= 0;
}
int Connection::DefaultScreenId() const {
// This is not part of the setup data as the server has no concept of a
// default screen. Instead, it's part of the display name. Eg in
// "localhost:0.0", the screen ID is the second "0".
return DefaultScreen(display_);
}
bool Connection::Ready() const {
return display_ && !xcb_connection_has_error(XGetXCBConnection(display_));
}
void Connection::Flush() {
XFlush(display_);
}
void Connection::Sync() {
GetInputFocus({}).Sync();
}
void Connection::ReadResponses() {
while (auto* event = xcb_poll_for_event(XcbConnection())) {
events_.emplace_back(base::MakeRefCounted<MallocedRefCountedMemory>(event),
this);
}
}
bool Connection::HasPendingResponses() const {
return !events_.empty() || HasNextResponse();
}
void Connection::Dispatch(Delegate* delegate) {
DCHECK(display_);
auto process_next_response = [&] {
xcb_connection_t* connection = XGetXCBConnection(display_);
auto request = std::move(requests_.front());
requests_.pop();
void* raw_reply = nullptr;
xcb_generic_error_t* raw_error = nullptr;
xcb_poll_for_reply(connection, request.sequence, &raw_reply, &raw_error);
scoped_refptr<MallocedRefCountedMemory> reply;
if (raw_reply)
reply = base::MakeRefCounted<MallocedRefCountedMemory>(raw_reply);
std::move(request.callback).Run(reply, FutureBase::RawError{raw_error});
};
auto process_next_event = [&] {
DCHECK(!events_.empty());
Event event = std::move(events_.front());
events_.pop_front();
PreDispatchEvent(event);
delegate->DispatchXEvent(&event);
};
// Handle all pending events.
while (delegate->ShouldContinueStream()) {
Flush();
ReadResponses();
if (HasNextResponse() && !events_.empty()) {
if (!events_.front().sequence_valid()) {
process_next_event();
continue;
}
auto next_response_sequence = requests_.front().sequence;
auto next_event_sequence = events_.front().sequence();
// All events have the sequence number of the last processed request
// included in them. So if a reply and an event have the same sequence,
// the reply must have been received first.
if (CompareSequenceIds(next_event_sequence, next_response_sequence) <= 0)
process_next_response();
else
process_next_event();
} else if (HasNextResponse()) {
process_next_response();
} else if (!events_.empty()) {
process_next_event();
} else {
break;
}
}
}
void Connection::AddRequest(unsigned int sequence,
FutureBase::ResponseCallback callback) {
DCHECK(requests_.empty() ||
CompareSequenceIds(requests_.back().sequence, sequence) < 0);
requests_.emplace(sequence, std::move(callback));
}
void Connection::PreDispatchEvent(const Event& event) {
// This is adapted from XRRUpdateConfiguration.
if (auto* configure = event.As<x11::ConfigureNotifyEvent>()) {
int index = ScreenIndexFromRootWindow(configure->window);
if (index != -1) {
setup_.roots[index].width_in_pixels = configure->width;
setup_.roots[index].height_in_pixels = configure->height;
}
} else if (auto* screen = event.As<x11::RandR::ScreenChangeNotifyEvent>()) {
int index = ScreenIndexFromRootWindow(screen->root);
DCHECK_GE(index, 0);
bool portrait = static_cast<bool>(
screen->rotation &
(x11::RandR::Rotation::Rotate_90 | x11::RandR::Rotation::Rotate_270));
if (portrait) {
setup_.roots[index].width_in_pixels = screen->height;
setup_.roots[index].height_in_pixels = screen->width;
setup_.roots[index].width_in_millimeters = screen->mheight;
setup_.roots[index].height_in_millimeters = screen->mwidth;
} else {
setup_.roots[index].width_in_pixels = screen->width;
setup_.roots[index].height_in_pixels = screen->height;
setup_.roots[index].width_in_millimeters = screen->mwidth;
setup_.roots[index].height_in_millimeters = screen->mheight;
}
}
}
int Connection::ScreenIndexFromRootWindow(x11::Window root) const {
for (size_t i = 0; i < setup_.roots.size(); i++) {
if (setup_.roots[i].root == root)
return i;
}
return -1;
}
} // namespace x11