From d0e18bdb7924c71cdca8dd983711171d87ef28be Mon Sep 17 00:00:00 2001 From: Benjamin Barenblat Date: Mon, 17 Jan 2022 23:12:32 -0500 Subject: glplanet, an OpenGL-based planetary renderer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit glplanet draws Earth like it currently appears from space, putting nighttime areas in shadow and daytime areas in light. It’s modeled after Xplanet (http://xplanet.sourceforge.net/), but whereas Xplanet is entirely a CPU-resident program, glplanet draws using OpenGL. It’s thus much less resource-intensive, particularly when using high-resolution textures. --- src/x/connection.cc | 174 ++++++++++++++++++++++++++++++++++++++++++++ src/x/connection.h | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/x/error.cc | 76 ++++++++++++++++++++ src/x/error.h | 32 +++++++++ src/x/event.cc | 59 +++++++++++++++ src/x/event.h | 137 +++++++++++++++++++++++++++++++++++ src/x/init.cc | 33 +++++++++ src/x/init.h | 26 +++++++ src/x/rpc.cc | 53 ++++++++++++++ src/x/rpc.h | 78 ++++++++++++++++++++ src/x/screen.h | 51 +++++++++++++ src/x/types.h | 28 ++++++++ src/x/util.h | 33 +++++++++ 13 files changed, 984 insertions(+) create mode 100644 src/x/connection.cc create mode 100644 src/x/connection.h create mode 100644 src/x/error.cc create mode 100644 src/x/error.h create mode 100644 src/x/event.cc create mode 100644 src/x/event.h create mode 100644 src/x/init.cc create mode 100644 src/x/init.h create mode 100644 src/x/rpc.cc create mode 100644 src/x/rpc.h create mode 100644 src/x/screen.h create mode 100644 src/x/types.h create mode 100644 src/x/util.h (limited to 'src/x') 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 +#include +#include +#include + +#include +#include +#include +#include + +#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> MarshalValueList( + const absl::btree_map>& + values) noexcept { + uint32_t value_mask = 0; + std::vector 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& event_mask) { + uint32_t serialized_event_mask = 0; + for (EventMask m : event_mask) { + serialized_event_mask |= FromEnum(m); + } + std::array 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 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_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(&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 +#include +#include +#include + +#include +#include +#include + +#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 background_pixel = absl::nullopt; + absl::optional 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&); + + // 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 GetEventIfReady() noexcept ABSL_LOCKS_EXCLUDED(mu_) { + return WaitForEventWithTimeout(absl::ZeroDuration()); + } + + Event WaitForEvent() noexcept ABSL_LOCKS_EXCLUDED(mu_) { + return *WaitForEventWithTimeout(absl::InfiniteDuration()); + } + + absl::optional 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 pending_events_ ABSL_GUARDED_BY(mu_); + + absl::optional 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 +#include + +#include + +#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 + +#include + +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 +#include +#include + +#include +#include +#include + +#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 SerializeEvent(const Event& event) noexcept { + std::array 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 +#include + +#include + +#include "src/x/types.h" +#include "third_party/abseil/absl/types/variant.h" + +namespace x_internal { + +template +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; + +Event FromXcbGenericEvent(const xcb_generic_event_t&) noexcept; + +std::array 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 + +#include + +#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 + +#include + +#include "src/x/error.h" +#include "src/x/types.h" +#include "src/x/util.h" + +namespace x { + +namespace { + +template +using ManagedPtr = std::unique_ptr; + +} // namespace + +void VoidCompletion::Check() const&& { + auto error = + ManagedPtr(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(xcb_, cookie_, &error_raw)); + if (reply == nullptr) { + auto error = ManagedPtr(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 + +#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 +#include +#include + +#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 + +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 + +namespace x_internal { + +// A deleter for std::unique_ptr that `free`s instead of calling `delete`. +class FreeDeleter final { + public: + template + void operator()(T* p) noexcept { + free(p); + } +}; + +} // namespace x_internal + +#endif // GLPLANET_SRC_X_UTIL_H_ -- cgit v1.2.3