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