// 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