summaryrefslogtreecommitdiff
path: root/src/x/connection.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/x/connection.h')
-rw-r--r--src/x/connection.h204
1 files changed, 204 insertions, 0 deletions
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_