summaryrefslogtreecommitdiff
path: root/src/x/connection.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/x/connection.cc')
-rw-r--r--src/x/connection.cc174
1 files changed, 174 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