blob: 766bd3fc1c46bfb90077d263d663e880225232db [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_GFX_X_CONNECTION_H_
#define UI_GFX_X_CONNECTION_H_
#include <array>
#include <optional>
#include "base/compiler_specific.h"
#include "base/component_export.h"
#include "base/containers/circular_deque.h"
#include "base/containers/flat_map.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "base/scoped_observation_traits.h"
#include "base/sequence_checker.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/x/event_observer.h"
#include "ui/gfx/x/extension_manager.h"
#include "ui/gfx/x/future.h"
#include "ui/gfx/x/window_event_manager.h"
#include "ui/gfx/x/xlib_support.h"
#include "ui/gfx/x/xproto.h"
#include "ui/gfx/x/xproto_types.h"
typedef struct xcb_connection_t xcb_connection_t;
namespace x11 {
class AtomCache;
class Event;
class KeyboardState;
class PropertyCache;
class VisualManager;
class WmSync;
class WriteBuffer;
enum WmState : uint32_t {
WM_STATE_WITHDRAWN = 0,
WM_STATE_NORMAL = 1,
WM_STATE_ICONIC = 3,
};
enum SizeHintsFlags : int32_t {
SIZE_HINT_US_POSITION = 1 << 0,
SIZE_HINT_US_SIZE = 1 << 1,
SIZE_HINT_P_POSITION = 1 << 2,
SIZE_HINT_P_SIZE = 1 << 3,
SIZE_HINT_P_MIN_SIZE = 1 << 4,
SIZE_HINT_P_MAX_SIZE = 1 << 5,
SIZE_HINT_P_RESIZE_INC = 1 << 6,
SIZE_HINT_P_ASPECT = 1 << 7,
SIZE_HINT_BASE_SIZE = 1 << 8,
SIZE_HINT_P_WIN_GRAVITY = 1 << 9,
};
struct SizeHints {
// User specified flags
int32_t flags;
// User-specified position
int32_t x, y;
// User-specified size
int32_t width, height;
// Program-specified minimum size
int32_t min_width, min_height;
// Program-specified maximum size
int32_t max_width, max_height;
// Program-specified resize increments
int32_t width_inc, height_inc;
// Program-specified minimum aspect ratios
int32_t min_aspect_num, min_aspect_den;
// Program-specified maximum aspect ratios
int32_t max_aspect_num, max_aspect_den;
// Program-specified base size
int32_t base_width, base_height;
// Program-specified window gravity
uint32_t win_gravity;
};
enum WinGravityHint : int32_t {
WIN_GRAVITY_HINT_UNMAP_GRAVITY = 0,
WIN_GRAVITY_HINT_NORTHWEST_GRAVITY = 1,
WIN_GRAVITY_HINT_NORTH_GRAVITY = 2,
WIN_GRAVITY_HINT_NORTHEAST_GRAVITY = 3,
WIN_GRAVITY_HINT_WEST_GRAVITY = 4,
WIN_GRAVITY_HINT_CENTER_GRAVITY = 5,
WIN_GRAVITY_HINT_EAST_GRAVITY = 6,
WIN_GRAVITY_HINT_SOUTHWEST_GRAVITY = 7,
WIN_GRAVITY_HINT_SOUTH_GRAVITY = 8,
WIN_GRAVITY_HINT_SOUTHEAST_GRAVITY = 9,
WIN_GRAVITY_HINT_STATIC_GRAVITY = 10,
};
enum WmHintsFlags : uint32_t {
WM_HINT_INPUT = 1L << 0,
WM_HINT_STATE = 1L << 1,
WM_HINT_ICON_PIXMAP = 1L << 2,
WM_HINT_ICON_WINDOW = 1L << 3,
WM_HINT_ICON_POSITION = 1L << 4,
WM_HINT_ICON_MASK = 1L << 5,
WM_HINT_WINDOW_GROUP = 1L << 6,
// 1L << 7 doesn't have any defined meaning
WM_HINT_X_URGENCY = 1L << 8
};
struct WmHints {
// Marks which fields in this structure are defined
int32_t flags;
// Does this application rely on the window manager to get keyboard input?
uint32_t input;
// See below
int32_t initial_state;
// Pixmap to be used as icon
Pixmap icon_pixmap;
// Window to be used as icon
Window icon_window;
// Initial position of icon
int32_t icon_x, icon_y;
// Icon mask bitmap
Pixmap icon_mask;
// Identifier of related window group
Window window_group;
};
// 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);
}
// Represents a socket to the X11 server.
class COMPONENT_EXPORT(X11) Connection final : public XProto,
public ExtensionManager {
public:
using IOErrorHandler = base::OnceClosure;
using ExtensionVersion = std::pair<uint32_t, uint32_t>;
struct VisualInfo {
raw_ptr<const Format> format;
raw_ptr<const VisualType> visual_type;
};
// Gets or creates the thread local connection instance.
static Connection* Get();
// Sets the thread local connection instance.
static void Set(std::unique_ptr<Connection> connection);
template <typename T>
T GenerateId() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return static_cast<T>(GenerateIdImpl());
}
template <typename Reply>
Future<Reply> SendRequest(WriteBuffer* buf,
const char* request_name,
bool reply_has_fds) {
bool generates_reply = !std::is_void<Reply>::value;
return Future<Reply>(
SendRequestImpl(buf, request_name, generates_reply, reply_has_fds));
}
explicit Connection(const std::string& address = "");
~Connection();
Connection(const Connection&) = delete;
Connection(Connection&&) = delete;
// Obtain an Xlib display that's connected to the same server as |this|. This
// is meant to be used only for compatibility with components like GLX,
// Vulkan, and VAAPI. The underlying socket is not shared, so synchronization
// with |this| may be necessary.
XlibDisplay& GetXlibDisplay();
size_t MaxRequestSizeInBytes() const;
const Setup& setup() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return setup_;
}
const Screen& default_screen() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return *default_screen_;
}
Window default_root() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return default_screen().root;
}
const Depth& default_root_depth() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return *default_root_depth_;
}
const VisualType& default_root_visual() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return *default_root_visual_;
}
const Event* dispatching_event() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return dispatching_event_;
}
ExtensionVersion randr_version() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return randr_version_;
}
ExtensionVersion render_version() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return render_version_;
}
ExtensionVersion screensaver_version() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return screensaver_version_;
}
ExtensionVersion shm_version() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return shm_version_;
}
ExtensionVersion sync_version() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return sync_version_;
}
ExtensionVersion xinput_version() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return xinput_version_;
}
WindowEventManager& window_event_manager() { return window_event_manager_; }
// Indicates if the connection is able to sync with the WM, either because the
// WM is on an allowlist or the connection successfully synced with the WM to
// test support experimentally.
bool CanSyncWithWm() const;
// Returns the underlying socket's FD if the connection is valid, or -1
// otherwise.
int GetFd();
const std::string& DisplayString() const;
std::string GetConnectionHostname() const;
int DefaultScreenId() const;
// Is the connection up and error-free?
bool Ready() const;
// Write all requests to the socket.
void Flush();
// Flush and block until the server has responded to all requests.
void Sync();
// If |synchronous| is true, this makes all requests Sync().
void SynchronizeForTest(bool synchronous);
// Read all responses from the socket without blocking. This function will
// make non-blocking read() syscalls.
void ReadResponses();
// Read a single response. If |queued| is true, no read() will be done; a
// response may only be translated from buffered socket data. If |queued| is
// false, a non-blocking read() will only be done if no response is buffered.
// Returns true if an event was read.
bool ReadResponse(bool queued);
// Are there any events, errors, or replies already buffered?
bool HasPendingResponses();
// Dispatches one event, reply, or error from the server; or returns false
// if there's none available. This function doesn't read or write any data on
// the socket.
bool Dispatch();
// Dispatches all available events, replies, and errors. This function
// ensures the read and write buffers on the socket are empty upon returning.
void DispatchAll();
// Directly dispatch an event, bypassing the event queue.
void DispatchEvent(const Event& event);
void SetIOErrorHandler(IOErrorHandler new_handler);
void AddEventObserver(EventObserver* observer);
void RemoveEventObserver(EventObserver* observer);
// Returns the visual data for |id|, or nullptr if the visual with that ID
// doesn't exist or only exists on a non-default screen.
const VisualInfo* GetVisualInfoFromId(VisualId id) const;
KeyCode KeysymToKeycode(uint32_t keysym) const;
uint32_t KeycodeToKeysym(KeyCode keycode, uint32_t modifiers) const;
// Access the event buffer. Clients may modify the queue, including
// "deleting" events by setting events[i] = Event(), which will
// guarantee all calls to Event::As() will return nullptr.
base::circular_deque<Event>& events() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return events_;
}
std::unique_ptr<Connection> Clone() const;
// Releases ownership of this connection to a different thread.
void DetachFromSequence();
////////////////////////////////////////
// Utilities
////////////////////////////////////////
template <typename T>
Future<void> SendEvent(const T& event, Window target, EventMask mask) {
static_assert(T::type_id > 0, "T must be an *Event type");
auto write_buffer = Write(event);
CHECK_EQ(write_buffer.GetBuffers().size(), 1ul);
base::span<uint8_t> first_buffer = write_buffer.GetBuffers()[0];
char event_bytes[kMinimumEventSize] = {};
base::span(event_bytes).copy_prefix_from(base::as_chars(first_buffer));
SendEventRequest send_event{false, target, mask};
base::span(send_event.event).copy_from(event_bytes);
std::ranges::copy(event_bytes, send_event.event.begin());
return XProto::SendEvent(send_event);
}
template <typename T>
bool GetArrayProperty(Window window,
Atom name,
std::vector<T>* value,
Atom* out_type = nullptr,
size_t amount = 0) {
static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4, "");
size_t bytes = amount * sizeof(T);
// The length field specifies the maximum amount of data we would like the
// server to give us. It's specified in units of 4 bytes, so divide by 4.
// Add 3 before division to round up.
size_t length = (bytes + 3) / 4;
using lentype = decltype(GetPropertyRequest::long_length);
auto response =
GetProperty(
GetPropertyRequest{
.window = static_cast<Window>(window),
.property = name,
.long_length = static_cast<uint32_t>(
amount ? length : std::numeric_limits<lentype>::max())})
.Sync();
if (!response || response->format / 8u != sizeof(T)) {
return false;
}
size_t byte_len = response->value_len * response->format / 8u;
value->resize(response->value_len);
if (byte_len > 0u) {
UNSAFE_TODO(memcpy(value->data(), response->value->bytes(), byte_len));
}
if (out_type) {
*out_type = response->type;
}
return true;
}
template <typename T>
bool GetPropertyAs(Window window, const Atom name, T* value) {
std::vector<T> values;
if (!GetArrayProperty(window, name, &values, nullptr, 1) ||
values.empty()) {
return false;
}
*value = values[0];
return true;
}
template <typename T>
Future<void> SetArrayProperty(Window window,
Atom name,
Atom type,
const std::vector<T>& values) {
static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4, "");
return SetArrayPropertyImpl(window, name, type, 8u * sizeof(T),
base::as_byte_span(values));
}
template <typename T>
Future<void> SetProperty(Window window,
Atom name,
Atom type,
const T& value) {
return SetArrayProperty(window, name, type, std::vector<T>{value});
}
void DeleteProperty(Window window, Atom name);
void SetStringProperty(Window window,
Atom property,
Atom type,
const std::string& value);
Window CreateDummyWindow(const std::string& name = std::string());
VisualManager& GetOrCreateVisualManager();
bool GetWmNormalHints(Window window, SizeHints* hints);
void SetWmNormalHints(Window window, const SizeHints& hints);
bool GetWmHints(Window window, WmHints* hints);
void SetWmHints(Window window, const WmHints& hints);
void WithdrawWindow(Window window);
void RaiseWindow(Window window);
void LowerWindow(Window window);
void DefineCursor(Window window, Cursor cursor);
ScopedEventSelector ScopedSelectEvent(Window window, EventMask event_mask);
Atom GetAtom(const char* name) const;
// Returns an empty string if there is no window manager or the WM is unnamed.
std::string GetWmName() const;
bool WmSupportsHint(Atom atom) const;
const std::map<std::string, std::string> GetXResources();
// The viz compositor thread hangs a PlatformEventSource off the connection so
// that it gets destroyed at the appropriate time.
// TODO(thomasanderson): This is a layering violation and this should be moved
// somewhere else.
std::unique_ptr<ui::PlatformEventSource> platform_event_source;
private:
friend class FutureBase;
friend class FutureImpl;
template <typename Reply>
friend class Future;
struct Request {
explicit Request(ResponseCallback callback);
Request(Request&& other);
~Request();
// Takes ownership of |reply| and |error|.
// Note that raw_error is an xcb_generic_error_t, but that type is not used
// here to avoid having this header file pull in xcb.h.
void SetResponse(Connection* connection, void* raw_reply, void* raw_error);
// If |callback| is nullptr, then this request has already been processed
// out-of-order.
ResponseCallback callback;
// Indicates if |reply| and |error| are available. A separate
// |have_response| flag is necessary to distinguish the case where a request
// hasn't finished yet from the case where a request finished but didn't
// generate a reply or error.
bool have_response = false;
RawReply reply;
std::unique_ptr<Error> error;
};
xcb_connection_t* XcbConnection();
void InitRootDepthAndVisual();
void InitializeExtensions();
void ProcessNextEvent();
void ProcessNextResponse();
bool HasNextResponse();
bool HasNextEvent();
Future<void> SetArrayPropertyImpl(Window window,
Atom name,
Atom type,
uint8_t format,
base::span<const uint8_t> values);
// Creates a new Request and adds it to the end of the queue.
// |request_name_for_tracing| must be valid until the response is
// dispatched; currently the string values are only stored in .rodata, so
// this constraint is satisfied.
std::unique_ptr<FutureImpl> SendRequestImpl(
WriteBuffer* buf,
const char* request_name_for_tracing,
bool generates_reply,
bool reply_has_fds);
// Block until the reply or error for request |sequence| is received.
void WaitForResponse(FutureImpl* future);
Request* GetRequestForFuture(FutureImpl* future);
void PreDispatchEvent(const Event& event);
int ScreenIndexFromRootWindow(Window root) const;
// This function is implemented in the generated read_error.cc.
void InitErrorParsers();
std::unique_ptr<Error> ParseError(RawError error_bytes);
uint32_t GenerateIdImpl();
void OnRootPropertyChanged(Atom property, const GetPropertyResponse& value);
bool WmSupportsEwmh() const;
void AttemptSyncWithWm();
void OnWmSynced();
std::string display_string_;
int default_screen_id_ = 0;
std::unique_ptr<xcb_connection_t, void (*)(xcb_connection_t*)> connection_ = {
nullptr, nullptr};
std::unique_ptr<XlibDisplay> xlib_display_;
bool synchronous_ = false;
bool syncing_ = false;
// Extension data.
uint32_t extended_max_request_length_ = 0;
ExtensionVersion randr_version_ = {0, 0};
ExtensionVersion render_version_ = {0, 0};
ExtensionVersion screensaver_version_ = {0, 0};
ExtensionVersion shm_version_ = {0, 0};
ExtensionVersion sync_version_ = {0, 0};
ExtensionVersion xinput_version_ = {0, 0};
Setup setup_;
raw_ptr<Screen> default_screen_ = nullptr;
raw_ptr<Depth> default_root_depth_ = nullptr;
raw_ptr<VisualType> default_root_visual_ = nullptr;
base::flat_map<VisualId, VisualInfo> default_screen_visuals_;
std::unique_ptr<KeyboardState> keyboard_state_;
base::circular_deque<Event> events_;
base::ObserverList<EventObserver>::UncheckedAndDanglingUntriaged
event_observers_;
// The Event currently being dispatched, or nullptr if there is none.
raw_ptr<const Event> dispatching_event_ = nullptr;
base::circular_deque<Request> requests_;
// The sequence ID of requests_.front(), or if |requests_| is empty, then the
// ID of the next request that will go in the queue. This starts at 1 because
// the 0'th request is handled internally by XCB when opening the connection.
SequenceType first_request_id_ = 1;
// If any request in |requests_| will generate a reply, this is the ID of the
// latest one, otherwise this is std::nullopt.
std::optional<SequenceType> last_non_void_request_id_;
using ErrorParser = std::unique_ptr<Error> (*)(RawError error_bytes);
std::array<ErrorParser, 256> error_parsers_{};
IOErrorHandler io_error_handler_;
WindowEventManager window_event_manager_;
SEQUENCE_CHECKER(sequence_checker_);
// Must be after `sequence_checker_`.
std::unique_ptr<VisualManager> visual_manager_;
std::unique_ptr<AtomCache> atom_cache_;
std::unique_ptr<WmSync> wm_sync_;
bool synced_with_wm_ = false;
std::unique_ptr<PropertyCache> root_props_;
std::unique_ptr<PropertyCache> wm_props_;
std::map<std::string, std::string> xresources_;
};
// Grab/release the X server connection within a scope. This can help avoid race
// conditions that would otherwise lead to X errors.
class COMPONENT_EXPORT(X11) ScopedXGrabServer {
public:
explicit ScopedXGrabServer(x11::Connection* connection);
ScopedXGrabServer(const ScopedXGrabServer&) = delete;
ScopedXGrabServer& operator=(const ScopedXGrabServer&) = delete;
~ScopedXGrabServer();
private:
raw_ptr<x11::Connection> connection_;
};
} // namespace x11
namespace base {
template <>
struct ScopedObservationTraits<x11::Connection, x11::EventObserver> {
static void AddObserver(x11::Connection* connection,
x11::EventObserver* observer) {
connection->AddEventObserver(observer);
}
static void RemoveObserver(x11::Connection* connection,
x11::EventObserver* observer) {
connection->RemoveEventObserver(observer);
}
};
} // namespace base
#endif // UI_GFX_X_CONNECTION_H_