summaryrefslogtreecommitdiff
path: root/src/x
diff options
context:
space:
mode:
Diffstat (limited to 'src/x')
-rw-r--r--src/x/connection.cc174
-rw-r--r--src/x/connection.h204
-rw-r--r--src/x/error.cc76
-rw-r--r--src/x/error.h32
-rw-r--r--src/x/event.cc59
-rw-r--r--src/x/event.h137
-rw-r--r--src/x/init.cc33
-rw-r--r--src/x/init.h26
-rw-r--r--src/x/rpc.cc53
-rw-r--r--src/x/rpc.h78
-rw-r--r--src/x/screen.h51
-rw-r--r--src/x/types.h28
-rw-r--r--src/x/util.h33
13 files changed, 984 insertions, 0 deletions
diff --git a/src/x/connection.cc b/src/x/connection.cc
new file mode 100644
index 0000000..f376a2a
--- /dev/null
+++ b/src/x/connection.cc
@@ -0,0 +1,174 @@
+// Copyright 2021, 2022 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "src/x/connection.h"
+
+#include <X11/Xlib-xcb.h>
+#include <X11/Xlib.h>
+#include <stdint.h>
+#include <xcb/xcb.h>
+
+#include <array>
+#include <stdexcept>
+#include <utility>
+#include <vector>
+
+#include "src/undo_xlib_dot_h_namespace_pollution.h"
+//
+
+#include "src/util.h"
+#include "src/x/event.h"
+#include "src/x/util.h"
+#include "third_party/abseil/absl/cleanup/cleanup.h"
+#include "third_party/abseil/absl/container/btree_map.h"
+#include "third_party/abseil/absl/strings/string_view.h"
+#include "third_party/abseil/absl/synchronization/mutex.h"
+#include "third_party/abseil/absl/types/optional.h"
+
+namespace x {
+
+namespace {
+
+std::pair<uint32_t, std::vector<uint32_t>> MarshalValueList(
+ const absl::btree_map<uint32_t, absl::optional<uint32_t>>&
+ values) noexcept {
+ uint32_t value_mask = 0;
+ std::vector<uint32_t> value_list;
+ for (auto& [k, v] : values) {
+ if (v.has_value()) {
+ value_mask |= k;
+ value_list.push_back(*v);
+ }
+ }
+ return {value_mask, value_list};
+}
+
+} // namespace
+
+Connection::Connection(const char* display_name) {
+ if (display_name == nullptr) {
+ throw std::invalid_argument("X: null display name");
+ }
+
+ xlib_ = XOpenDisplay(display_name);
+ if (xlib_ == nullptr) {
+ throw std::runtime_error("X: could not connect to X server");
+ }
+ XSetEventQueueOwner(xlib_, XCBOwnsEventQueue);
+
+ // We make a lot of calls into XCB, so cache the XCB handle.
+ xcb_ = XGetXCBConnection(xlib_);
+}
+
+VoidCompletion Connection::CreateWindow(
+ const CreateWindowOptions& options) noexcept {
+ const auto& [value_mask, value_list] =
+ MarshalValueList({{XCB_CW_BACK_PIXEL, options.background_pixel},
+ {XCB_CW_EVENT_MASK, options.event_mask}});
+ return VoidCompletion(
+ xcb_, xcb_create_window_checked(
+ xcb_, options.depth, options.window, options.parent, options.x,
+ options.y, options.width, options.height, options.border_width,
+ FromEnum(options.window_class), options.visual_id, value_mask,
+ value_list.data()));
+}
+
+VoidCompletion Connection::SendEvent(const Event& event, bool propagate,
+ Id destination_window,
+ const std::vector<EventMask>& event_mask) {
+ uint32_t serialized_event_mask = 0;
+ for (EventMask m : event_mask) {
+ serialized_event_mask |= FromEnum(m);
+ }
+ std::array<char, 32> serialized_event = SerializeEvent(event);
+ return VoidCompletion(
+ xcb_,
+ xcb_send_event_checked(xcb_, propagate, destination_window,
+ serialized_event_mask, serialized_event.data()));
+}
+
+EventMonitor::EventMonitor(Connection& x)
+ : x_(x), communication_window_(x_.GenerateId()) {
+ // Create a window for us to communicate with the watcher thread.
+ VoidCompletion create_window_completion =
+ x_.CreateWindow({.depth = 0, // as required for an InputOnly window
+ .window = communication_window_,
+ .parent = x_.DefaultScreen().root(),
+ .x = 0,
+ .y = 0,
+ .width = 1,
+ .height = 1,
+ .border_width = 0,
+ .window_class = WindowClass::kInputOnly,
+ .visual_id = XCB_COPY_FROM_PARENT});
+ InternAtomCompletion intern_atom_completion =
+ x_.InternOrGetAtomByName(kDoneAtomName);
+ std::move(create_window_completion).Check();
+ close_connection_atom_ = std::move(intern_atom_completion).Get();
+ watcher_thread_.emplace(&EventMonitor::WatcherThreadMain, this);
+}
+
+EventMonitor::~EventMonitor() {
+ // Send a client message through the connection so the waiter thread knows to
+ // exit.
+ xcb_client_message_event_t event = {.response_type = XCB_CLIENT_MESSAGE,
+ .format = 32,
+ .sequence = 0,
+ .window = communication_window_,
+ .type = close_connection_atom_,
+ .data = {}};
+ x_.SendEvent(ClientMessageEvent(event), /*propagate=*/false,
+ communication_window_, {EventMask::kNoEvent})
+ .Check();
+ watcher_thread_->join();
+ x_.DestroyWindow(communication_window_);
+}
+
+absl::optional<Event> EventMonitor::WaitForEventWithTimeout(
+ absl::Duration timeout) noexcept {
+ bool events_present = mu_.LockWhenWithTimeout(
+ absl::Condition(this, &EventMonitor::EventsPresent), timeout);
+ absl::Cleanup unlock = [&]() noexcept { mu_.Unlock(); };
+ if (events_present) {
+ Event event = std::move(pending_events_.front());
+ pending_events_.pop_front();
+ return event;
+ } else {
+ return absl::nullopt;
+ }
+}
+
+void EventMonitor::WatcherThreadMain() noexcept {
+ while (true) {
+ auto generic_event =
+ std::unique_ptr<xcb_generic_event_t, x_internal::FreeDeleter>(
+ xcb_wait_for_event(x_.AsXcbConnection()));
+ if (generic_event == nullptr) {
+ // The connection dropped or something.
+ return;
+ }
+
+ Event event = FromXcbGenericEvent(*generic_event);
+ if (const auto* client_message = absl::get_if<ClientMessageEvent>(&event);
+ client_message != nullptr &&
+ client_message->type() == close_connection_atom_) {
+ // Our destructor is running.
+ return;
+ }
+ absl::MutexLock lock(&mu_);
+ pending_events_.push_back(event);
+ }
+}
+
+} // namespace x
diff --git a/src/x/connection.h b/src/x/connection.h
new file mode 100644
index 0000000..83c6b18
--- /dev/null
+++ b/src/x/connection.h
@@ -0,0 +1,204 @@
+// Copyright 2021, 2022 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// The main connection to the X server.
+
+#ifndef GLPLANET_SRC_X_CONNECTION_H_
+#define GLPLANET_SRC_X_CONNECTION_H_
+
+#include <X11/Xlib.h>
+#include <stdint.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+
+#include <deque>
+#include <thread>
+#include <vector>
+
+#include "src/undo_xlib_dot_h_namespace_pollution.h"
+//
+
+#include "src/x/event.h"
+#include "src/x/rpc.h"
+#include "src/x/screen.h"
+#include "src/x/types.h"
+#include "third_party/abseil/absl/base/thread_annotations.h"
+#include "third_party/abseil/absl/strings/string_view.h"
+#include "third_party/abseil/absl/synchronization/mutex.h"
+#include "third_party/abseil/absl/time/time.h"
+#include "third_party/abseil/absl/types/optional.h"
+
+namespace x {
+
+enum class WindowClass : uint16_t {
+ kCopyFromParent = XCB_WINDOW_CLASS_COPY_FROM_PARENT,
+ kInputOutput = XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ kInputOnly = XCB_WINDOW_CLASS_INPUT_ONLY,
+};
+
+enum EventMask : uint32_t {
+ kNoEvent = XCB_EVENT_MASK_NO_EVENT,
+ kKeyPress = XCB_EVENT_MASK_KEY_PRESS,
+ kKeyRelease = XCB_EVENT_MASK_KEY_RELEASE,
+ kButtonPress = XCB_EVENT_MASK_BUTTON_PRESS,
+ kButtonRelease = XCB_EVENT_MASK_BUTTON_RELEASE,
+ kEnterWindow = XCB_EVENT_MASK_ENTER_WINDOW,
+ kLeaveWindow = XCB_EVENT_MASK_LEAVE_WINDOW,
+ kPointerMotion = XCB_EVENT_MASK_POINTER_MOTION,
+ kPointerMotionHint = XCB_EVENT_MASK_POINTER_MOTION_HINT,
+ kButton1Motion = XCB_EVENT_MASK_BUTTON_1_MOTION,
+ kButton2Motion = XCB_EVENT_MASK_BUTTON_2_MOTION,
+ kButton3Motion = XCB_EVENT_MASK_BUTTON_3_MOTION,
+ kButton4Motion = XCB_EVENT_MASK_BUTTON_4_MOTION,
+ kButton5Motion = XCB_EVENT_MASK_BUTTON_5_MOTION,
+ kButtonMotion = XCB_EVENT_MASK_BUTTON_MOTION,
+ kKeymapState = XCB_EVENT_MASK_KEYMAP_STATE,
+ kExposure = XCB_EVENT_MASK_EXPOSURE,
+ kVisibilityChange = XCB_EVENT_MASK_VISIBILITY_CHANGE,
+ kStructureNotify = XCB_EVENT_MASK_STRUCTURE_NOTIFY,
+ kResizeRedirect = XCB_EVENT_MASK_RESIZE_REDIRECT,
+ kSubstructureNotify = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
+ kSubstructureRedirect = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
+ kFocusChange = XCB_EVENT_MASK_FOCUS_CHANGE,
+ kPropertyChange = XCB_EVENT_MASK_PROPERTY_CHANGE,
+ kColorMapChange = XCB_EVENT_MASK_COLOR_MAP_CHANGE,
+ kOwnerGrabButton = XCB_EVENT_MASK_OWNER_GRAB_BUTTON,
+};
+
+// A connection to the X server.
+//
+// This class is thread-safe.
+class Connection final {
+ public:
+ struct CreateWindowOptions {
+ uint8_t depth;
+ Id window;
+ Id parent;
+ int16_t x;
+ int16_t y;
+ uint16_t width;
+ uint16_t height;
+ uint16_t border_width;
+ WindowClass window_class;
+ Id visual_id;
+
+ // TODO(bbarenblat@gmail.com): Support additional attributes.
+ absl::optional<Id> background_pixel = absl::nullopt;
+ absl::optional<uint32_t> event_mask = absl::nullopt;
+ };
+
+ explicit Connection(const char* display_name);
+
+ Connection(Connection&&) noexcept = default;
+ Connection& operator=(Connection&&) noexcept = default;
+
+ ~Connection() noexcept { XCloseDisplay(xlib_); }
+
+ Id GenerateId() noexcept { return xcb_generate_id(xcb_); }
+
+ Screen DefaultScreen() const noexcept {
+ return Screen(xcb_aux_get_screen(xcb_, xlib::DefaultScreen(xlib_)));
+ }
+
+ VoidCompletion CreateWindow(const CreateWindowOptions& options) noexcept;
+
+ VoidCompletion DestroyWindow(Id window) noexcept {
+ return VoidCompletion(xcb_, xcb_destroy_window_checked(xcb_, window));
+ }
+
+ VoidCompletion MapWindow(Id window) noexcept {
+ return VoidCompletion(xcb_, xcb_map_window_checked(xcb_, window));
+ }
+
+ // Per the X specification, name should be encoded as ISO 8859-1.
+ InternAtomCompletion InternOrGetAtomByName(absl::string_view name) noexcept {
+ return InternAtom(name, /*only_if_exists=*/false);
+ }
+
+ // Per the X specification, name should be encoded as ISO 8859-1.
+ InternAtomCompletion GetAtomByName(absl::string_view name) noexcept {
+ return InternAtom(name, /*only_if_exists=*/true);
+ }
+
+ VoidCompletion SendEvent(const Event&, bool propagate, Id destination_window,
+ const std::vector<EventMask>&);
+
+ // Escape hatches: raw handles to the X server as Xlib and XCB objects. Use
+ // these to interface with other libraries.
+ //
+ // Be careful with the Xlib handle--XCB, not Xlib, owns the event loop.
+ Display* AsXlibDisplay() noexcept { return xlib_; }
+ xcb_connection_t* AsXcbConnection() noexcept { return xcb_; }
+
+ private:
+ InternAtomCompletion InternAtom(absl::string_view name,
+ bool only_if_exists) noexcept {
+ return InternAtomCompletion(
+ xcb_, xcb_intern_atom(xcb_, only_if_exists, name.size(), name.data()));
+ }
+
+ Display* xlib_;
+ xcb_connection_t* xcb_;
+};
+
+// A class that monitors for X events. You probably only want one of these for
+// each connection; otherwise, events will be delivered nondeterministically to
+// the monitors.
+//
+// This class is thread-safe.
+class EventMonitor final {
+ public:
+ // Starts monitoring for X events on the specified connection.
+ explicit EventMonitor(Connection&);
+
+ EventMonitor(EventMonitor&&) noexcept = default;
+ EventMonitor& operator=(EventMonitor&&) noexcept = default;
+
+ ~EventMonitor();
+
+ absl::optional<Event> GetEventIfReady() noexcept ABSL_LOCKS_EXCLUDED(mu_) {
+ return WaitForEventWithTimeout(absl::ZeroDuration());
+ }
+
+ Event WaitForEvent() noexcept ABSL_LOCKS_EXCLUDED(mu_) {
+ return *WaitForEventWithTimeout(absl::InfiniteDuration());
+ }
+
+ absl::optional<Event> WaitForEventWithTimeout(absl::Duration) noexcept
+ ABSL_LOCKS_EXCLUDED(mu_);
+
+ private:
+ static constexpr absl::string_view kDoneAtomName =
+ "_GLPLANET_SRC_X_CONNECTION_EVENTMONITOR_DONE";
+
+ void WatcherThreadMain() noexcept ABSL_LOCKS_EXCLUDED(mu_);
+
+ bool EventsPresent() const noexcept {
+ mu_.AssertReaderHeld();
+ return !pending_events_.empty();
+ };
+
+ Connection& x_;
+ Id communication_window_;
+ Id close_connection_atom_;
+
+ absl::Mutex mu_;
+ std::deque<Event> pending_events_ ABSL_GUARDED_BY(mu_);
+
+ absl::optional<std::thread> watcher_thread_;
+};
+
+} // namespace x
+
+#endif // GLPLANET_SRC_X_CONNECTION_H_
diff --git a/src/x/error.cc b/src/x/error.cc
new file mode 100644
index 0000000..5b060c8
--- /dev/null
+++ b/src/x/error.cc
@@ -0,0 +1,76 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "src/x/error.h"
+
+#include <stdint.h>
+#include <xcb/xcb.h>
+
+#include <stdexcept>
+
+#include "src/util.h"
+#include "third_party/abseil/absl/strings/str_cat.h"
+
+namespace x {
+
+namespace {
+
+const char* StatusCodeName(uint8_t code) noexcept {
+ switch (code) {
+ case XCB_REQUEST:
+ return "Request";
+ case XCB_VALUE:
+ return "Value";
+ case XCB_WINDOW:
+ return "Window";
+ case XCB_PIXMAP:
+ return "Pixmap";
+ case XCB_ATOM:
+ return "Atom";
+ case XCB_CURSOR:
+ return "Cursor";
+ case XCB_FONT:
+ return "Font";
+ case XCB_MATCH:
+ return "Match";
+ case XCB_DRAWABLE:
+ return "Drawable";
+ case XCB_ACCESS:
+ return "Access";
+ case XCB_ALLOC:
+ return "Alloc";
+ case XCB_COLORMAP:
+ return "Colormap";
+ case XCB_G_CONTEXT:
+ return "XCB_Context";
+ case XCB_ID_CHOICE:
+ return "XCB_Choice";
+ case XCB_NAME:
+ return "Name";
+ case XCB_LENGTH:
+ return "Length";
+ case XCB_IMPLEMENTATION:
+ return "Implementation";
+ default:
+ DCHECK(false);
+ return "unknown error";
+ }
+}
+
+} // namespace
+
+Error::Error(uint8_t code) noexcept
+ : std::runtime_error(absl::StrCat("X error: ", StatusCodeName(code))) {}
+
+} // namespace x
diff --git a/src/x/error.h b/src/x/error.h
new file mode 100644
index 0000000..13510e8
--- /dev/null
+++ b/src/x/error.h
@@ -0,0 +1,32 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef GLPLANET_SRC_X_ERROR_H_
+#define GLPLANET_SRC_X_ERROR_H_
+
+#include <stdint.h>
+
+#include <stdexcept>
+
+namespace x {
+
+// An X protocol error.
+class Error final : public std::runtime_error {
+ public:
+ explicit Error(uint8_t code) noexcept;
+};
+
+} // namespace x
+
+#endif // GLPLANET_SRC_X_ERROR_H_
diff --git a/src/x/event.cc b/src/x/event.cc
new file mode 100644
index 0000000..8346b40
--- /dev/null
+++ b/src/x/event.cc
@@ -0,0 +1,59 @@
+// Copyright 2021, 2022 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "src/x/event.h"
+
+#include <stdint.h>
+#include <string.h>
+#include <xcb/xcb.h>
+
+#include <algorithm>
+#include <array>
+#include <stdexcept>
+
+#include "third_party/abseil/absl/types/variant.h"
+
+namespace x {
+
+namespace {
+
+constexpr uint8_t kXcbResponseTypeMask = 0x7f;
+
+} // namespace
+
+Event FromXcbGenericEvent(const xcb_generic_event_t& event) noexcept {
+ switch (event.response_type & kXcbResponseTypeMask) {
+ case XCB_EXPOSE:
+ return ExposeEvent(event);
+ case XCB_CONFIGURE_NOTIFY:
+ return ConfigureNotifyEvent(event);
+ case XCB_CLIENT_MESSAGE:
+ return ClientMessageEvent(event);
+ default:
+ return UnknownEvent(event);
+ }
+}
+
+std::array<char, 32> SerializeEvent(const Event& event) noexcept {
+ std::array<char, 32> raw = {};
+ absl::visit(
+ [&](auto&& ev) {
+ const auto& xcb_ev = ev.AsXcbEvent();
+ memcpy(raw.data(), &xcb_ev, std::min(raw.size(), sizeof(xcb_ev)));
+ },
+ event);
+ return raw;
+}
+
+} // namespace x
diff --git a/src/x/event.h b/src/x/event.h
new file mode 100644
index 0000000..8b9dd24
--- /dev/null
+++ b/src/x/event.h
@@ -0,0 +1,137 @@
+// Copyright 2021, 2022 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// X events.
+
+#ifndef GLPLANET_SRC_X_EVENT_H_
+#define GLPLANET_SRC_X_EVENT_H_
+
+#include <string.h>
+#include <xcb/xcb.h>
+
+#include <array>
+
+#include "src/x/types.h"
+#include "third_party/abseil/absl/types/variant.h"
+
+namespace x_internal {
+
+template <typename From, typename To>
+void XcbDowncast(const From& from, To& to) noexcept {
+ static_assert(sizeof(From) >= sizeof(To));
+ memcpy(&to, &from, sizeof(To));
+}
+
+} // namespace x_internal
+
+namespace x {
+
+class ExposeEvent final {
+ public:
+ explicit ExposeEvent(const xcb_expose_event_t& event) noexcept
+ : event_(event) {}
+ explicit ExposeEvent(const xcb_generic_event_t& event) noexcept {
+ x_internal::XcbDowncast(event, event_);
+ }
+
+ ExposeEvent(const ExposeEvent&) noexcept = default;
+ ExposeEvent& operator=(const ExposeEvent&) noexcept = default;
+ ExposeEvent(ExposeEvent&&) noexcept = default;
+ ExposeEvent& operator=(ExposeEvent&&) noexcept = default;
+
+ xcb_expose_event_t& AsXcbEvent() noexcept { return event_; }
+ const xcb_expose_event_t& AsXcbEvent() const noexcept { return event_; }
+
+ private:
+ xcb_expose_event_t event_;
+};
+
+class ConfigureNotifyEvent final {
+ public:
+ explicit ConfigureNotifyEvent(
+ const xcb_configure_notify_event_t& event) noexcept
+ : event_(event) {}
+ explicit ConfigureNotifyEvent(const xcb_generic_event_t& event) noexcept {
+ x_internal::XcbDowncast(event, event_);
+ }
+
+ ConfigureNotifyEvent(const ConfigureNotifyEvent&) noexcept = default;
+ ConfigureNotifyEvent& operator=(const ConfigureNotifyEvent&) noexcept =
+ default;
+ ConfigureNotifyEvent(ConfigureNotifyEvent&&) noexcept = default;
+ ConfigureNotifyEvent& operator=(ConfigureNotifyEvent&&) noexcept = default;
+
+ int width() const noexcept { return event_.width; }
+ int height() const noexcept { return event_.height; }
+
+ xcb_configure_notify_event_t& AsXcbEvent() noexcept { return event_; }
+ const xcb_configure_notify_event_t& AsXcbEvent() const noexcept {
+ return event_;
+ }
+
+ private:
+ xcb_configure_notify_event_t event_;
+};
+
+class ClientMessageEvent final {
+ public:
+ explicit ClientMessageEvent(const xcb_client_message_event_t& event) noexcept
+ : event_(event) {}
+ explicit ClientMessageEvent(const xcb_generic_event_t& event) noexcept {
+ x_internal::XcbDowncast(event, event_);
+ }
+
+ ClientMessageEvent(const ClientMessageEvent&) noexcept = default;
+ ClientMessageEvent& operator=(const ClientMessageEvent&) noexcept = default;
+ ClientMessageEvent(ClientMessageEvent&&) noexcept = default;
+ ClientMessageEvent& operator=(ClientMessageEvent&&) noexcept = default;
+
+ Id type() const noexcept { return event_.type; }
+
+ xcb_client_message_event_t& AsXcbEvent() noexcept { return event_; }
+ const xcb_client_message_event_t& AsXcbEvent() const noexcept {
+ return event_;
+ }
+
+ private:
+ xcb_client_message_event_t event_;
+};
+
+// An X event that doesn't fit into any of the previous classes.
+class UnknownEvent final {
+ public:
+ explicit UnknownEvent(const xcb_generic_event_t& event) : event_(event) {}
+
+ UnknownEvent(const UnknownEvent&) noexcept = default;
+ UnknownEvent& operator=(const UnknownEvent&) noexcept = default;
+ UnknownEvent(UnknownEvent&&) noexcept = default;
+ UnknownEvent& operator=(UnknownEvent&&) noexcept = default;
+
+ xcb_generic_event_t& AsXcbEvent() noexcept { return event_; }
+ const xcb_generic_event_t& AsXcbEvent() const noexcept { return event_; }
+
+ private:
+ xcb_generic_event_t event_;
+};
+
+using Event = absl::variant<UnknownEvent, ExposeEvent, ConfigureNotifyEvent,
+ ClientMessageEvent>;
+
+Event FromXcbGenericEvent(const xcb_generic_event_t&) noexcept;
+
+std::array<char, 32> SerializeEvent(const Event&) noexcept;
+
+} // namespace x
+
+#endif // GLPLANET_SRC_X_EVENT_H_
diff --git a/src/x/init.cc b/src/x/init.cc
new file mode 100644
index 0000000..577f074
--- /dev/null
+++ b/src/x/init.cc
@@ -0,0 +1,33 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "src/x/init.h"
+
+#include <X11/Xlib.h>
+
+#include <stdexcept>
+
+#include "src/undo_xlib_dot_h_namespace_pollution.h"
+//
+
+namespace x {
+
+void Initialize() {
+ if (!XInitThreads()) {
+ throw std::runtime_error(
+ "X: could not initialize Xlib support for concurrent threads");
+ }
+}
+
+} // namespace x
diff --git a/src/x/init.h b/src/x/init.h
new file mode 100644
index 0000000..332d450
--- /dev/null
+++ b/src/x/init.h
@@ -0,0 +1,26 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef GLPLANET_SRC_X_INIT_H_
+#define GLPLANET_SRC_X_INIT_H_
+
+namespace x {
+
+// You must call this function before using any of the code in this library.
+// (Call it at the start of main.)
+void Initialize();
+
+} // namespace x
+
+#endif // GLPLANET_SRC_X_INIT_H_
diff --git a/src/x/rpc.cc b/src/x/rpc.cc
new file mode 100644
index 0000000..7786d9b
--- /dev/null
+++ b/src/x/rpc.cc
@@ -0,0 +1,53 @@
+// Copyright 2021, 2022 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "src/x/rpc.h"
+
+#include <xcb/xcb.h>
+
+#include <memory>
+
+#include "src/x/error.h"
+#include "src/x/types.h"
+#include "src/x/util.h"
+
+namespace x {
+
+namespace {
+
+template <typename T>
+using ManagedPtr = std::unique_ptr<T, x_internal::FreeDeleter>;
+
+} // namespace
+
+void VoidCompletion::Check() const&& {
+ auto error =
+ ManagedPtr<xcb_generic_error_t>(xcb_request_check(xcb_, cookie_));
+ if (error != nullptr) {
+ throw Error(error->error_code);
+ }
+}
+
+Id InternAtomCompletion::Get() const&& {
+ xcb_generic_error_t* error_raw;
+ auto reply = ManagedPtr<xcb_intern_atom_reply_t>(
+ xcb_intern_atom_reply(xcb_, cookie_, &error_raw));
+ if (reply == nullptr) {
+ auto error = ManagedPtr<xcb_generic_error_t>(error_raw);
+ throw Error(error->error_code);
+ }
+ return reply->atom;
+}
+
+} // namespace x
diff --git a/src/x/rpc.h b/src/x/rpc.h
new file mode 100644
index 0000000..c2fa652
--- /dev/null
+++ b/src/x/rpc.h
@@ -0,0 +1,78 @@
+// Copyright 2021, 2022 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// In-flight RPCs.
+//
+// Each of these classes represents an individual X RPC, possibly still in
+// flight. Inspecting an instance will block until the RPC has completed.
+
+#ifndef GLPLANET_SRC_X_RPC_H_
+#define GLPLANET_SRC_X_RPC_H_
+
+#include <xcb/xcb.h>
+
+#include "src/x/types.h"
+
+namespace x {
+
+// An RPC that returns nothing.
+//
+// This class is thread-safe.
+class VoidCompletion final {
+ public:
+ explicit VoidCompletion(xcb_connection_t* xcb,
+ xcb_void_cookie_t cookie) noexcept
+ : xcb_(xcb), cookie_(cookie) {}
+
+ VoidCompletion(const VoidCompletion&) noexcept = default;
+ VoidCompletion& operator=(const VoidCompletion&) noexcept = default;
+ VoidCompletion(VoidCompletion&&) noexcept = default;
+ VoidCompletion& operator=(VoidCompletion&&) noexcept = default;
+
+ // Blocks until the RPC completes and checks to make sure it was successful.
+ // Throws Error if it was not.
+ void Check() const&&;
+
+ private:
+ xcb_connection_t* xcb_;
+ xcb_void_cookie_t cookie_;
+};
+
+// An InternAtom RPC.
+//
+// This class is thread-safe.
+class InternAtomCompletion final {
+ public:
+ explicit InternAtomCompletion(xcb_connection_t* xcb,
+ xcb_intern_atom_cookie_t cookie) noexcept
+ : xcb_(xcb), cookie_(cookie) {}
+
+ InternAtomCompletion(const InternAtomCompletion&) noexcept = default;
+ InternAtomCompletion& operator=(const InternAtomCompletion&) noexcept =
+ default;
+ InternAtomCompletion(InternAtomCompletion&&) noexcept = default;
+ InternAtomCompletion& operator=(InternAtomCompletion&&) noexcept = default;
+
+ // Blocks until the RPC completes, checks to make sure it was successful, and
+ // returns the atom. Throws Error if the RPC failed.
+ Id Get() const&&;
+
+ private:
+ xcb_connection_t* xcb_;
+ xcb_intern_atom_cookie_t cookie_;
+};
+
+} // namespace x
+
+#endif // GLPLANET_SRC_X_RPC_H_
diff --git a/src/x/screen.h b/src/x/screen.h
new file mode 100644
index 0000000..88acae5
--- /dev/null
+++ b/src/x/screen.h
@@ -0,0 +1,51 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef GLPLANET_SRC_X_SCREEN_H_
+#define GLPLANET_SRC_X_SCREEN_H_
+
+#include <stdint.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+
+#include "src/x/types.h"
+
+namespace x {
+
+// An X screen.
+//
+// This class is thread-safe.
+class Screen final {
+ public:
+ explicit Screen(xcb_screen_t* screen) noexcept : screen_(screen) {}
+
+ Screen(Screen&&) noexcept = default;
+ Screen& operator=(Screen&&) noexcept = default;
+
+ Id root() const noexcept { return screen_->root; }
+ Id black_pixel() const noexcept { return screen_->black_pixel; }
+
+ uint8_t DepthOfVisual(Id visual) const noexcept {
+ return xcb_aux_get_depth_of_visual(screen_, visual);
+ }
+
+ xcb_screen_t* AsXcbScreen() const noexcept { return screen_; }
+
+ private:
+ xcb_screen_t* screen_;
+};
+
+} // namespace x
+
+#endif // GLPLANET_SRC_X_SCREEN_H_
diff --git a/src/x/types.h b/src/x/types.h
new file mode 100644
index 0000000..441df89
--- /dev/null
+++ b/src/x/types.h
@@ -0,0 +1,28 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef GLPLANET_SRC_X_TYPES_H_
+#define GLPLANET_SRC_X_TYPES_H_
+
+#include <stdint.h>
+
+namespace x {
+
+// An X protocol identifier. These are used for all kinds of X
+// resources--windows, colormaps, etc.
+using Id = uint32_t;
+
+} // namespace x
+
+#endif // GLPLANET_SRC_X_TYPES_H_
diff --git a/src/x/util.h b/src/x/util.h
new file mode 100644
index 0000000..5dbb1a0
--- /dev/null
+++ b/src/x/util.h
@@ -0,0 +1,33 @@
+// Copyright 2021 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef GLPLANET_SRC_X_UTIL_H_
+#define GLPLANET_SRC_X_UTIL_H_
+
+#include <stdlib.h>
+
+namespace x_internal {
+
+// A deleter for std::unique_ptr that `free`s instead of calling `delete`.
+class FreeDeleter final {
+ public:
+ template <typename T>
+ void operator()(T* p) noexcept {
+ free(p);
+ }
+};
+
+} // namespace x_internal
+
+#endif // GLPLANET_SRC_X_UTIL_H_