diff --git a/WORKSPACE b/WORKSPACE index 1da6007..237f8c5 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -39,6 +39,10 @@ load("//:repositories.bzl", "actions_repositories") actions_repositories() -load("//:setup.bzl", "actions_setup") +load("@actions//display:display_configure.bzl", "display_configure") -actions_setup() +display_configure() + +load("@display//:local_display.bzl", "display_repositories") + +display_repositories() diff --git a/actions/BUILD b/actions/BUILD index 9299d1f..2b52470 100644 --- a/actions/BUILD +++ b/actions/BUILD @@ -5,6 +5,7 @@ cc_library( hdrs = ["action.h"], deps = [ "//actions/action:keystroke", + "//actions/action:mouse", "//actions/action:target", "@com_google_absl//absl/types:variant", ], @@ -14,6 +15,10 @@ cc_library( name = "actions", srcs = ["actions.cc"], hdrs = ["actions.h"], + copts = select({ + "@display//:x11": ["-D __x11__"], + "//conditions:default": [], + }), deps = [ ":action", "//actions/internal:connection", @@ -21,7 +26,7 @@ cc_library( "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", ] + select({ - "@platforms//os:linux": ["//actions/internal/linux:xcb_connection"], + "@display//:x11": ["//actions/internal/x11:xcb_connection"], "//conditions:default": ["//actions/internal/stub:stub_connection"], }), ) diff --git a/actions/action.h b/actions/action.h index f02e135..ad37579 100644 --- a/actions/action.h +++ b/actions/action.h @@ -3,12 +3,22 @@ #include "absl/types/variant.h" #include "actions/action/keystroke.h" +#include "actions/action/mouse.h" #include "actions/action/target.h" namespace actions { -using Action = absl::variant; +namespace action { -} +typedef struct NoOp { +} NoOp; +} // namespace action + +using Action = + absl::variant; + +} // namespace actions #endif // ACTIONS_ACTION_H \ No newline at end of file diff --git a/actions/action/BUILD b/actions/action/BUILD index 88bd351..8d3d2a7 100644 --- a/actions/action/BUILD +++ b/actions/action/BUILD @@ -4,16 +4,25 @@ cc_library( name = "keystroke", srcs = ["keystroke.cc"], hdrs = ["keystroke.h"], + copts = select({ + "@display//:x11": ["-D __x11__"], + "//conditions:default": [], + }), deps = [ "@com_google_absl//absl/types:variant", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", ] + select({ - "@platforms//os:linux": ["//actions/internal/linux:keystroke"], + "@display//:x11": ["//actions/internal/x11:keystroke"], "//conditions:default": [], }), ) +cc_library( + name = "mouse", + hdrs = ["mouse.h"], +) + cc_library( name = "target", hdrs = ["target.h"], diff --git a/actions/action/keystroke.cc b/actions/action/keystroke.cc index bd05b88..b4b42e4 100644 --- a/actions/action/keystroke.cc +++ b/actions/action/keystroke.cc @@ -1,3 +1,11 @@ #include "actions/action/keystroke.h" -namespace actions::action {} // namespace actions::action \ No newline at end of file +namespace actions::action { +absl::StatusOr > ParseKeystroke(std::string str) noexcept { +#if defined(__x11__) + return internal::x11::ParseKeystroke(str); +#else + return std::vector(); +#endif +} +} // namespace actions::action \ No newline at end of file diff --git a/actions/action/keystroke.h b/actions/action/keystroke.h index beaea7c..655a1a9 100644 --- a/actions/action/keystroke.h +++ b/actions/action/keystroke.h @@ -7,25 +7,35 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" -#if defined(__linux__) -#include "actions/internal/linux/keystroke.h" +#if defined(__x11__) +#include "actions/internal/x11/keystroke.h" #endif namespace actions::action { -#if defined(__linux__) -using internal::linux::Key; -using internal::linux::ParseKeystroke; +#if defined(__x11__) +using internal::x11::Key; #else using Key = unsigned int; -absl::StatusOr > ParseKeystroke(std::string) noexcept { - return std::vector(); -} #endif +absl::StatusOr > ParseKeystroke(std::string) noexcept; + /// @brief Represents a keystroke. A collection of keys with associated /// modifiers. -using Keystroke = std::vector; +using KeySequence = std::vector; + +typedef struct Keystroke { + KeySequence sequence; +} Keystroke; + +typedef struct KeysPress { + KeySequence sequence; +} KeysPress; + +typedef struct KeysRelease { + KeySequence sequence; +} KeysRelease; } // namespace actions::action diff --git a/actions/action/mouse.h b/actions/action/mouse.h new file mode 100644 index 0000000..4dddf31 --- /dev/null +++ b/actions/action/mouse.h @@ -0,0 +1,24 @@ +#ifndef ACTIONS_ACTION_MOUSE_CLICK_H +#define ACTIONS_ACTION_MOUSE_CLICK_H + +namespace actions::action { + +typedef struct CursorMove { + double x = 0; + double y = 0; + bool relative = true; +} CursorMove; + +typedef enum MouseClick { LeftClick, RightClick, MiddleClick } MouseClick; + +typedef enum MousePress { LeftPress, RightPress, MiddlePress } MousePress; + +typedef enum MouseRelease { + LeftRelease, + RightRelease, + MiddleRelease +} MouseRelease; + +} // namespace actions::action + +#endif // ACTIONS_ACTION_MOUSE_CLICK_H \ No newline at end of file diff --git a/actions/actions.cc b/actions/actions.cc index aaac12f..844499e 100644 --- a/actions/actions.cc +++ b/actions/actions.cc @@ -5,8 +5,8 @@ #include "actions/internal/connection.h" #include "actions/internal/util.h" -#if defined(__linux) -#include "actions/internal/linux/xcb_connection.h" +#if defined(__x11__) +#include "actions/internal/x11/xcb_connection.h" #else #include "actions/internal/stub/stub_connection.h" #endif @@ -18,8 +18,8 @@ absl::StatusOr Actions::Create( } absl::StatusOr Actions::Create() noexcept { -#if defined(__linux) - auto conn = internal::linux::XcbConnection::Create(); +#if defined(__x11__) + auto conn = internal::x11::XcbConnection::Create(); #else auto conn = internal::stub::StubConnection::Create(); #endif @@ -45,10 +45,26 @@ Actions& Actions::operator=(Actions&& other) noexcept { std::future Actions::Perform( const Action& action, const action::Target& target) noexcept { if (absl::holds_alternative(action)) { - return conn->SendKeystroke(absl::get(action), - target); + return conn->Keystroke(absl::get(action), target); + } else if (absl::holds_alternative(action)) { + return conn->KeysPress(absl::get(action), target); + } else if (absl::holds_alternative(action)) { + return conn->KeysRelease(absl::get(action), + target); + } else if (absl::holds_alternative(action)) { + return conn->MoveCursor(absl::get(action), target); + } else if (absl::holds_alternative(action)) { + return conn->MousePress(absl::get(action), target); + } else if (absl::holds_alternative(action)) { + return conn->MouseRelease(absl::get(action), + target); + } else if (absl::holds_alternative(action)) { + return conn->MouseClick(absl::get(action), target); + } else if (absl::holds_alternative(action)) { + return internal::util::Resolve(absl::OkStatus()); } + // Should be unreachable, but you can never be too careful return internal::util::Resolve( absl::UnimplementedError("Not Implemented.")); } diff --git a/actions/examples/BUILD b/actions/examples/BUILD index 494da05..ae34a20 100644 --- a/actions/examples/BUILD +++ b/actions/examples/BUILD @@ -10,3 +10,23 @@ cc_binary( "@com_google_absl//absl/status", ], ) + +cc_binary( + name = "actions_mouse", + srcs = ["actions_mouse.cc"], + deps = [ + "//actions", + "//actions:action", + "@com_google_absl//absl/status", + ], +) + +cc_binary( + name = "actions_hybrid", + srcs = ["actions_hybrid.cc"], + deps = [ + "//actions", + "//actions:action", + "@com_google_absl//absl/status", + ], +) diff --git a/actions/examples/actions_hybrid.cc b/actions/examples/actions_hybrid.cc new file mode 100644 index 0000000..8bec533 --- /dev/null +++ b/actions/examples/actions_hybrid.cc @@ -0,0 +1,61 @@ +#include + +#include "absl/status/status.h" +#include "actions/action.h" +#include "actions/actions.h" + +using namespace actions; + +absl::Status run() { + auto keysequence = action::ParseKeystroke("shift"); + + if (!keysequence.ok()) return keysequence.status(); + + auto actions = Actions::Create(); + + if (!actions.ok()) return actions.status(); + + absl::Status status = actions + ->Perform(action::KeysPress{keysequence.value()}, + action::target::Focused()) + .get(); + + if (!status.ok()) return status; + + status = + actions + ->Perform(action::MousePress::LeftPress, action::target::Focused()) + .get(); + + if (!status.ok()) return status; + + status = actions + ->Perform(action::CursorMove{.x = 0.1, .y = 0}, + action::target::Focused()) + .get(); + + if (!status.ok()) return status; + + status = actions + ->Perform(action::KeysRelease{keysequence.value()}, + action::target::Focused()) + .get(); + + if (!status.ok()) return status; + + return actions + ->Perform(action::MouseRelease::LeftRelease, action::target::Focused()) + .get(); +} + +int main(int argc, char** argv) { + std::cout << "Running Example" << std::endl; + absl::Status status = run(); + + if (!status.ok()) { + std::cout << status << std::endl; + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/actions/examples/actions_keystroke.cc b/actions/examples/actions_keystroke.cc index 52a737d..30b066a 100644 --- a/actions/examples/actions_keystroke.cc +++ b/actions/examples/actions_keystroke.cc @@ -15,11 +15,14 @@ absl::Status run() { if (!actions.ok()) return actions.status(); - auto keystroke = action::ParseKeystroke(FLAGS_keystroke.CurrentValue()); + auto keysequence = action::ParseKeystroke(FLAGS_keystroke.CurrentValue()); - if (!keystroke.ok()) return keystroke.status(); + if (!keysequence.ok()) return keysequence.status(); - return actions->Perform(keystroke.value(), action::target::Focused()).get(); + return actions + ->Perform(action::Keystroke{keysequence.value()}, + action::target::Focused()) + .get(); } int main(int argc, char** argv) { diff --git a/actions/examples/actions_mouse.cc b/actions/examples/actions_mouse.cc new file mode 100644 index 0000000..dc4e2a5 --- /dev/null +++ b/actions/examples/actions_mouse.cc @@ -0,0 +1,36 @@ +#include + +#include "absl/status/status.h" +#include "actions/action.h" +#include "actions/actions.h" + +using namespace actions; + +absl::Status run() { + auto actions = Actions::Create(); + + if (!actions.ok()) return actions.status(); + + absl::Status status = actions + ->Perform(action::CursorMove{.x = 0.1, .y = 0.1}, + action::target::Focused()) + .get(); + + if (!status.ok()) return status; + + return actions + ->Perform(action::MouseClick::LeftClick, action::target::Focused()) + .get(); +} + +int main(int argc, char** argv) { + std::cout << "Running Example" << std::endl; + absl::Status status = run(); + + if (!status.ok()) { + std::cout << status << std::endl; + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/actions/internal/connection.h b/actions/internal/connection.h index c96f7cd..dc31ec1 100644 --- a/actions/internal/connection.h +++ b/actions/internal/connection.h @@ -18,9 +18,33 @@ class Connection { /// @param target The target of the keystroke. /// @return A future which resolves to an ok status if the keystroke was /// successfully sent, or an error status otherwise. - virtual std::future SendKeystroke( + virtual std::future Keystroke( const action::Keystroke& keystroke, const action::Target& target) noexcept = 0; + + virtual std::future KeysPress( + const action::KeysPress& keys_press, + const action::Target& target) noexcept = 0; + + virtual std::future KeysRelease( + const action::KeysRelease& keys_release, + const action::Target& target) noexcept = 0; + + virtual std::future MoveCursor( + const action::CursorMove& cursor_move, + const action::Target& target) noexcept = 0; + + virtual std::future MousePress( + const action::MousePress& mouse_press, + const action::Target& target) noexcept = 0; + + virtual std::future MouseRelease( + const action::MouseRelease& mouse_release, + const action::Target& target) noexcept = 0; + + virtual std::future MouseClick( + const action::MouseClick& mouse_click, + const action::Target& target) noexcept = 0; }; } // namespace actions::internal diff --git a/actions/internal/linux/keystroke.cc b/actions/internal/linux/keystroke.cc deleted file mode 100644 index 4ba2f6b..0000000 --- a/actions/internal/linux/keystroke.cc +++ /dev/null @@ -1,47 +0,0 @@ -#include "actions/internal/linux/keystroke.h" - -#include - -#include -#include -#include -#include - -namespace actions::internal::linux { -const static std::map symbol_map{ - {"alt", "Alt_L"}, - {"ctrl", "Control_L"}, - {"control", "Control_L"}, - {"meta", "Meta_L"}, - {"super", "Super_L"}, - {"shift", "Shift_L"}, - {"enter", "Return"}, - { - "return", - "Return", - }}; - -absl::StatusOr> ParseKeystroke(std::string str) noexcept { - std::stringstream ss(std::move(str)); - std::string s; - std::vector keys; - - while (std::getline(ss, str, '+')) { - // std::transform(str.begin(), str.end(), str.begin(), ::toupper); - - auto it = symbol_map.find(str); - - if (it != symbol_map.end()) str = it->second; - - KeySym sym = XStringToKeysym(str.c_str()); - - if (sym == NoSymbol) { - return absl::InvalidArgumentError("Unrecognized key: " + str); - } - - keys.push_back(sym); - } - - return keys; -} -} // namespace actions::internal::linux \ No newline at end of file diff --git a/actions/internal/linux/keystroke.h b/actions/internal/linux/keystroke.h deleted file mode 100644 index 45112e2..0000000 --- a/actions/internal/linux/keystroke.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef ACTIONS_INTERNAL_LINUX_KEYSTROKE_H -#define ACTIONS_INTERNAL_LINUX_KEYSTROKE_H - -#include - -#include -#include - -#include "absl/status/statusor.h" - -namespace actions::internal::linux { - -using Key = xcb_keysym_t; - -absl::StatusOr> ParseKeystroke(std::string) noexcept; - -} // namespace actions::internal::linux - -#endif // ACTIONS_INTERNAL_LINUX_KEYSTROKE_H \ No newline at end of file diff --git a/actions/internal/linux/xcb_connection.cc b/actions/internal/linux/xcb_connection.cc deleted file mode 100644 index 83ef3a6..0000000 --- a/actions/internal/linux/xcb_connection.cc +++ /dev/null @@ -1,112 +0,0 @@ -#include "actions/internal/linux/xcb_connection.h" - -#include -#include - -#include - -#include "absl/status/status.h" -#include "absl/types/variant.h" -#include "actions/internal/linux/xcb_error.h" -#include "actions/internal/linux/xcb_keyboard.h" -#include "actions/internal/util.h" - -namespace actions::internal::linux { -absl::StatusOr> -XcbConnection::Create() noexcept { - // Setup connection - absl::Status status = absl::OkStatus(); - int screen_num; - xcb_connection_t* conn = xcb_connect(NULL, &screen_num); - - if (conn == NULL) { - return absl::UnavailableError("Failed to open connection to X server"); - } - - status.Update(XcbStatus(xcb_connection_has_error(conn))); - - if (!status.ok()) { - xcb_disconnect(conn); - return status; - } - - // Get primary screen - const xcb_setup_t* setup = xcb_get_setup(conn); - xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(setup); - - if (xcb_setup_roots_length(setup) <= screen_num) { - xcb_disconnect(conn); - return absl::OutOfRangeError("Invalid screen number"); - } - - for (int i = 0; i < screen_num; i++) { - xcb_screen_next(&screen_iter); - } - - xcb_screen_t* screen = screen_iter.data; - - // Ensure necessary extensions are present - const xcb_query_extension_reply_t* xtest_data = - xcb_get_extension_data(conn, &xcb_test_id); - - status.Update(XcbStatus(xcb_connection_has_error(conn))); - - if (!status.ok()) { - xcb_disconnect(conn); - return status; - } - - if (!xtest_data->present) { - xcb_disconnect(conn); - return absl::UnavailableError("XTest extension not present"); - } - - // Construct keyboard - XcbKeyboard keyboard(conn); - - return std::unique_ptr( - new XcbConnection(conn, screen, std::move(keyboard))); -} - -XcbConnection::XcbConnection(xcb_connection_t* conn, xcb_screen_t* screen, - XcbKeyboard&& keyboard) noexcept - : conn(conn), screen(screen), keyboard(std::move(keyboard)) {} - -XcbConnection::~XcbConnection() noexcept { xcb_disconnect(conn); } - -XcbConnection::XcbConnection(XcbConnection&& other) noexcept - : keyboard(std::move(other.keyboard)) { - conn = other.conn; - other.conn = nullptr; - - screen = other.screen; - other.screen = nullptr; -} - -XcbConnection& XcbConnection::operator=(XcbConnection&& other) noexcept { - if (this != &other) { - keyboard = std::move(other.keyboard); - - conn = other.conn; - other.conn = nullptr; - - screen = other.screen; - other.screen = nullptr; - } - - return *this; -} - -std::future XcbConnection::SendKeystroke( - const action::Keystroke& keystroke, const action::Target& target) noexcept { - xcb_window_t root; - - if (absl::holds_alternative(target)) { - root = screen->root; - } else { - return util::Resolve(absl::UnimplementedError("Not Implemented.")); - } - - return keyboard.SendKeystrokes(keystroke, root); -} -} // namespace actions::internal::linux \ No newline at end of file diff --git a/actions/internal/linux/xcb_connection.h b/actions/internal/linux/xcb_connection.h deleted file mode 100644 index 179d645..0000000 --- a/actions/internal/linux/xcb_connection.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef ACTIONS_INTERNAL_LINUX_XCB_CONNECTION_H -#define ACTIONS_INTERNAL_LINUX_XCB_CONNECTION_H - -#include - -#include - -#include "absl/status/status.h" -#include "absl/status/statusor.h" -#include "actions/internal/connection.h" -#include "actions/internal/linux/xcb_keyboard.h" - -namespace actions::internal::linux { - -class XcbConnection : public Connection { - private: - xcb_connection_t* conn; - xcb_screen_t* screen; - XcbKeyboard keyboard; - - public: - static absl::StatusOr> Create() noexcept; - - XcbConnection(xcb_connection_t* conn, xcb_screen_t* screen, - XcbKeyboard&& keyboard) noexcept; - ~XcbConnection() noexcept; - - XcbConnection(XcbConnection&) = delete; - XcbConnection& operator=(XcbConnection&) = delete; - - XcbConnection(XcbConnection&&) noexcept; - XcbConnection& operator=(XcbConnection&&) noexcept; - - std::future SendKeystroke( - const action::Keystroke& keystroke, - const action::Target& target) noexcept override; -}; - -} // namespace actions::internal::linux - -#endif // ACTIONS_INTERNAL_LINUX_XCB_CONNECTION_H \ No newline at end of file diff --git a/actions/internal/linux/xcb_keyboard.h b/actions/internal/linux/xcb_keyboard.h deleted file mode 100644 index d615593..0000000 --- a/actions/internal/linux/xcb_keyboard.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef ACTIONS_INTERNAL_LINUX_XCB_KEYBOARD_H -#define ACTIONS_INTERNAL_LINUX_XCB_KEYBOARD_H - -#include -#include - -#include -#include -#include - -#include "absl/status/status.h" -#include "absl/status/statusor.h" -#include "actions/action/keystroke.h" - -namespace actions::internal::linux { - -class XcbKeyboard { - private: - xcb_connection_t* conn; - xcb_key_symbols_t* key_symbols; - - std::future SendKey(bool press, xcb_keysym_t key, - xcb_window_t root) noexcept; - - std::future SendFakeInput(uint8_t type, uint8_t detail, - xcb_window_t root) noexcept; - - public: - XcbKeyboard(xcb_connection_t* conn) noexcept; - ~XcbKeyboard() noexcept; - - XcbKeyboard(XcbKeyboard&) = delete; - XcbKeyboard& operator=(XcbKeyboard&) = delete; - - XcbKeyboard(XcbKeyboard&&) noexcept; - XcbKeyboard& operator=(XcbKeyboard&&) noexcept; - - std::future SendKeystrokes(const action::Keystroke& keystroke, - xcb_window_t root) noexcept; -}; - -} // namespace actions::internal::linux - -#endif // ACTIONS_INTERNAL_LINUX_XCB_KEYBOARD_H \ No newline at end of file diff --git a/actions/internal/stub/stub_connection.cc b/actions/internal/stub/stub_connection.cc index 0407a12..60db587 100644 --- a/actions/internal/stub/stub_connection.cc +++ b/actions/internal/stub/stub_connection.cc @@ -12,9 +12,45 @@ StubConnection::Create() noexcept { return std::make_unique(); } -std::future StubConnection::SendKeystroke( +std::future StubConnection::Keystroke( const action::Keystroke& keystroke, const action::Target& target) noexcept { return util::Resolve(absl::OkStatus()); } +std::future StubConnection::KeysPress( + const action::KeysPress& keys_press, + const action::Target& target) noexcept { + return util::Resolve(absl::OkStatus()); +} + +std::future StubConnection::KeysRelease( + const action::KeysRelease& keys_release, + const action::Target& target) noexcept { + return util::Resolve(absl::OkStatus()); +} + +std::future StubConnection::MoveCursor( + const action::CursorMove& cursor_move, + const action::Target& target) noexcept { + return util::Resolve(absl::OkStatus()); +} + +std::future StubConnection::MousePress( + const action::MousePress& mouse_press, + const action::Target& target) noexcept { + return util::Resolve(absl::OkStatus()); +} + +std::future StubConnection::MouseRelease( + const action::MouseRelease& mouse_release, + const action::Target& target) noexcept { + return util::Resolve(absl::OkStatus()); +} + +std::future StubConnection::MouseClick( + const action::MouseClick& mouse_click, + const action::Target& target) noexcept { + return util::Resolve(absl::OkStatus()); +} + } // namespace actions::internal::stub \ No newline at end of file diff --git a/actions/internal/stub/stub_connection.h b/actions/internal/stub/stub_connection.h index 460b98c..a864da1 100644 --- a/actions/internal/stub/stub_connection.h +++ b/actions/internal/stub/stub_connection.h @@ -12,9 +12,33 @@ class StubConnection : public Connection { public: static absl::StatusOr> Create() noexcept; - std::future SendKeystroke( + std::future Keystroke( const action::Keystroke& keystroke, const action::Target& target) noexcept override; + + std::future KeysPress( + const action::KeysPress& keys_press, + const action::Target& target) noexcept override; + + std::future KeysRelease( + const action::KeysRelease& keys_release, + const action::Target& target) noexcept override; + + std::future MoveCursor( + const action::CursorMove& cursor_move, + const action::Target& target) noexcept override; + + std::future MousePress( + const action::MousePress& mouse_press, + const action::Target& target) noexcept override; + + std::future MouseRelease( + const action::MouseRelease& mouse_release, + const action::Target& target) noexcept override; + + std::future MouseClick( + const action::MouseClick& mouse_click, + const action::Target& target) noexcept override; }; } // namespace actions::internal::stub diff --git a/actions/internal/linux/BUILD b/actions/internal/x11/BUILD similarity index 83% rename from actions/internal/linux/BUILD rename to actions/internal/x11/BUILD index 569a850..16076a3 100644 --- a/actions/internal/linux/BUILD +++ b/actions/internal/x11/BUILD @@ -4,6 +4,7 @@ cc_library( name = "keystroke", srcs = ["keystroke.cc"], hdrs = ["keystroke.h"], + target_compatible_with = ["@display//:x11"], deps = [ "@X11", "@com_google_absl//absl/status", @@ -17,6 +18,7 @@ cc_library( name = "xcb_connection", srcs = ["xcb_connection.cc"], hdrs = ["xcb_connection.h"], + target_compatible_with = ["@display//:x11"], deps = [ ":xcb_error", ":xcb_keyboard", @@ -33,8 +35,9 @@ cc_library( name = "xcb_error", srcs = ["xcb_error.cc"], hdrs = ["xcb_error.h"], + target_compatible_with = ["@display//:x11"], deps = [ - "@com_google_absl//absl/status:status", + "@com_google_absl//absl/status", "@xcb", ], ) @@ -43,6 +46,7 @@ cc_library( name = "xcb_keyboard", srcs = ["xcb_keyboard.cc"], hdrs = ["xcb_keyboard.h"], + target_compatible_with = ["@display//:x11"], deps = [ ":xcb_error", "//actions/action:keystroke", diff --git a/actions/internal/x11/keystroke.cc b/actions/internal/x11/keystroke.cc new file mode 100644 index 0000000..b3a74f2 --- /dev/null +++ b/actions/internal/x11/keystroke.cc @@ -0,0 +1,100 @@ +#include "actions/internal/x11/keystroke.h" + +#include + +#include +#include +#include +#include + +namespace actions::internal::x11 { +const static std::map symbol_map{ + {"alt", "Alt_L"}, + {"ctrl", "Control_L"}, + {"control", "Control_L"}, + {"meta", "Meta_L"}, + {"super", "Super_L"}, + {"shift", "Shift_L"}, + {"enter", "Return"}, + { + "return", + "Return", + }, + {"pgup", "Page_Up"}, + {"pgdown", "Page_Down"}, + {"backspace", "BackSpace"}, + {"capslock", "Caps_Lock"}, + {"space", "space"}, + {"!", "exclam"}, + {"@", "at"}, + {"#", "numbersign"}, + {"$", "dollar"}, + {"%", "percent"}, + {"^", "caret"}, + {"&", "ampersand"}, + {"*", "asterisk"}, + {"(", "parenleft"}, + {")", "parenright"}, + {"-", "minus"}, + {"=", "equal"}, + {"_", "underscore"}, + {"+", "plus"}, + {"[", "bracketleft"}, + {"]", "bracketright"}, + {"{", "braceleft"}, + {"}", "braceright"}, + {"|", "bar"}, + {"\\", "backslash"}, + {".", "period"}, + {"<", "less"}, + {">", "greater"}, + {"?", "question"}, + {";", "semicolon"}, + {":", "colon"}, + {",", "comma"}, + {"\"", "quoteright"}, + {"'", "apostrophe"}, + {"/", "slash"}, + {"~", "asciitilde"}, + {"`", "grave"}, + {"scrolllock", "Scroll_Lock"}, + {"ins", "Insert"}, + {"del", "Delete"}, + {"numlock", "Num_Lock"}, +}; + +absl::StatusOr> ParseKeystroke(std::string str) noexcept { + std::stringstream ss(std::move(str)); + std::string s; + std::vector keys; + + while (std::getline(ss, str, ',')) { + std::stringstream ss2(std::move(str)); + while (std::getline(ss2, str, '+')) { + str.erase(std::remove_if(str.begin(), str.end(), ::isspace), + str.end()); + + if (str == "") continue; + + std::string lookup_str = str; + std::transform(lookup_str.begin(), lookup_str.end(), + lookup_str.begin(), ::tolower); + + auto it = symbol_map.find(lookup_str); + + if (it != symbol_map.end()) str = it->second; + + KeySym sym = XStringToKeysym(str.c_str()); + + if (sym == NoSymbol) { + return absl::InvalidArgumentError("Unrecognized key: \"" + str + + "\""); + } + + keys.push_back(sym); + } + } + + return keys; +} +} // namespace actions::internal::x11 \ No newline at end of file diff --git a/actions/internal/x11/keystroke.h b/actions/internal/x11/keystroke.h new file mode 100644 index 0000000..dfb9c19 --- /dev/null +++ b/actions/internal/x11/keystroke.h @@ -0,0 +1,19 @@ +#ifndef ACTIONS_INTERNAL_X11_KEYSTROKE_H +#define ACTIONS_INTERNAL_X11_KEYSTROKE_H + +#include + +#include +#include + +#include "absl/status/statusor.h" + +namespace actions::internal::x11 { + +using Key = xcb_keysym_t; + +absl::StatusOr> ParseKeystroke(std::string) noexcept; + +} // namespace actions::internal::x11 + +#endif // ACTIONS_INTERNAL_X11_KEYSTROKE_H \ No newline at end of file diff --git a/actions/internal/x11/xcb_connection.cc b/actions/internal/x11/xcb_connection.cc new file mode 100644 index 0000000..eca2d4a --- /dev/null +++ b/actions/internal/x11/xcb_connection.cc @@ -0,0 +1,322 @@ +#include "actions/internal/x11/xcb_connection.h" + +#include +#include +#include + +#include + +#include "absl/status/status.h" +#include "absl/types/variant.h" +#include "actions/internal/util.h" +#include "actions/internal/x11/xcb_error.h" +#include "actions/internal/x11/xcb_keyboard.h" + +namespace actions::internal::x11 { +absl::StatusOr> +XcbConnection::Create() noexcept { + // Setup connection + absl::Status status = absl::OkStatus(); + int screen_num; + xcb_connection_t* conn = xcb_connect(NULL, &screen_num); + + if (conn == NULL) { + return absl::UnavailableError("Failed to open connection to X server"); + } + + status.Update(XcbStatus(xcb_connection_has_error(conn))); + + if (!status.ok()) { + xcb_disconnect(conn); + return status; + } + + // Get primary screen + const xcb_setup_t* setup = xcb_get_setup(conn); + xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(setup); + + if (xcb_setup_roots_length(setup) <= screen_num) { + xcb_disconnect(conn); + return absl::OutOfRangeError("Invalid screen number"); + } + + for (int i = 0; i < screen_num; i++) { + xcb_screen_next(&screen_iter); + } + + xcb_screen_t* screen = screen_iter.data; + + // Ensure necessary extensions are present + const xcb_query_extension_reply_t* xtest_data = + xcb_get_extension_data(conn, &xcb_test_id); + + status.Update(XcbStatus(xcb_connection_has_error(conn))); + + if (!status.ok()) { + xcb_disconnect(conn); + return status; + } + + if (!xtest_data->present) { + xcb_disconnect(conn); + return absl::UnavailableError("XTest extension not present"); + } + + // Construct keyboard + XcbKeyboard keyboard(conn); + + return std::unique_ptr( + new XcbConnection(conn, screen, std::move(keyboard))); +} + +XcbConnection::XcbConnection(xcb_connection_t* conn, xcb_screen_t* screen, + XcbKeyboard&& keyboard) noexcept + : conn(conn), screen(screen), keyboard(std::move(keyboard)) {} + +XcbConnection::~XcbConnection() noexcept { xcb_disconnect(conn); } + +XcbConnection::XcbConnection(XcbConnection&& other) noexcept + : keyboard(std::move(other.keyboard)) { + conn = other.conn; + other.conn = nullptr; + + screen = other.screen; + other.screen = nullptr; +} + +XcbConnection& XcbConnection::operator=(XcbConnection&& other) noexcept { + if (this != &other) { + keyboard = std::move(other.keyboard); + + conn = other.conn; + other.conn = nullptr; + + screen = other.screen; + other.screen = nullptr; + } + + return *this; +} + +std::future XcbConnection::Keystroke( + const action::Keystroke& keystroke, const action::Target& target) noexcept { + xcb_window_t root; + + if (absl::holds_alternative(target)) { + root = screen->root; + } else { + return util::Resolve(absl::UnimplementedError("Not Implemented.")); + } + + return keyboard.Keystroke(keystroke, root); +} + +std::future XcbConnection::KeysPress( + const action::KeysPress& keys_press, + const action::Target& target) noexcept { + xcb_window_t root; + + if (absl::holds_alternative(target)) { + root = screen->root; + } else { + return util::Resolve(absl::UnimplementedError("Not Implemented.")); + } + + return keyboard.KeysPress(keys_press, root); +} + +std::future XcbConnection::KeysRelease( + const action::KeysRelease& keys_release, + const action::Target& target) noexcept { + xcb_window_t root; + + if (absl::holds_alternative(target)) { + root = screen->root; + } else { + return util::Resolve(absl::UnimplementedError("Not Implemented.")); + } + + return keyboard.KeysRelease(keys_release, root); +} + +std::future XcbConnection::MoveCursor( + const action::CursorMove& cursor_move, + const action::Target& target) noexcept { + // TODO: get size of window to properly scale coordinates + + xcb_window_t dest_window; + + if (absl::holds_alternative(target)) { + dest_window = screen->root; + } else { + return util::Resolve(absl::UnimplementedError("Not Implemented.")); + } + + return std::async(std::launch::deferred, [=]() { + int16_t src_x = 0; + int16_t src_y = 0; + + if (cursor_move.relative) { + auto pointer_loc = QueryPointer().get(); + + if (!pointer_loc.ok()) return pointer_loc.status(); + + src_x = pointer_loc.value()->root_x; + src_y = pointer_loc.value()->root_y; + } + + auto geometry = GetWindowGeometry(dest_window).get(); + + if (!geometry.ok()) return geometry.status(); + + auto dest_width = geometry.value()->width; + auto dest_height = geometry.value()->height; + + auto x = cursor_move.x * dest_width + src_x; + auto y = cursor_move.y * dest_height + src_y; + + xcb_void_cookie_t cookie = xcb_warp_pointer_checked( + conn, XCB_NONE, dest_window, 0, 0, 0, 0, x, y); + + xcb_flush(conn); + + return CheckCookie(cookie).get(); + }); +} + +std::future XcbConnection::MousePress( + const action::MousePress& mouse_press, + const action::Target& _target) noexcept { + xcb_button_index_t button; + + switch (mouse_press) { + case action::MousePress::LeftPress: + button = XCB_BUTTON_INDEX_1; + break; + case action::MousePress::RightPress: + button = XCB_BUTTON_INDEX_2; + break; + case action::MousePress::MiddlePress: + button = XCB_BUTTON_INDEX_3; + break; + default: + return util::Resolve(absl::UnimplementedError("Not implemented")); + } + + return SendMouseInput(button, true); +} + +std::future XcbConnection::MouseRelease( + const action::MouseRelease& mouse_release, + const action::Target& target) noexcept { + xcb_button_index_t button; + + switch (mouse_release) { + case action::MouseRelease::LeftRelease: + button = XCB_BUTTON_INDEX_1; + break; + case action::MouseRelease::RightRelease: + button = XCB_BUTTON_INDEX_2; + break; + case action::MouseRelease::MiddleRelease: + button = XCB_BUTTON_INDEX_3; + break; + default: + return util::Resolve(absl::UnimplementedError("Not implemented")); + } + + return SendMouseInput(button, false); +} + +std::future XcbConnection::MouseClick( + const action::MouseClick& mouse_click, + const action::Target& target) noexcept { + xcb_button_index_t button; + + switch (mouse_click) { + case action::MouseClick::LeftClick: + button = XCB_BUTTON_INDEX_1; + break; + case action::MouseClick::RightClick: + button = XCB_BUTTON_INDEX_2; + break; + case action::MouseClick::MiddleClick: + button = XCB_BUTTON_INDEX_3; + break; + default: + return util::Resolve(absl::UnimplementedError("Not implemented")); + } + + return SendMouseClick(button); +} + +std::future> +XcbConnection::GetWindowAttributes(xcb_window_t window) { + xcb_get_window_attributes_cookie_t cookie = + xcb_get_window_attributes(conn, window); + + xcb_flush(conn); + + return ResolveReply( + cookie, xcb_get_window_attributes_reply); +} + +std::future> +XcbConnection::GetWindowGeometry(xcb_window_t window) { + xcb_get_geometry_cookie_t cookie = xcb_get_geometry(conn, window); + + xcb_flush(conn); + + return ResolveReply( + cookie, xcb_get_geometry_reply); +} + +std::future> XcbConnection::QueryPointer() { + xcb_query_pointer_cookie_t cookie = xcb_query_pointer(conn, screen->root); + + xcb_flush(conn); + + return ResolveReply( + cookie, xcb_query_pointer_reply); +} + +std::future XcbConnection::SendMouseClick( + xcb_button_index_t button) { + return std::async(std::launch::deferred, [=]() { + auto status = SendMouseInput(button, true).get(); + + if (!status.ok()) return status; + + return SendMouseInput(button, false).get(); + }); +} + +std::future XcbConnection::SendMouseInput( + xcb_button_index_t button, bool press) { + return SendFakeInput(press ? XCB_BUTTON_PRESS : XCB_BUTTON_RELEASE, button); +} + +std::future XcbConnection::SendFakeInput(uint8_t type, + uint8_t detail) { + xcb_void_cookie_t cookie = xcb_test_fake_input_checked( + conn, type, detail, 0, screen->root, 0, 0, 0); + + xcb_flush(conn); + + return CheckCookie(cookie); +} + +std::future XcbConnection::CheckCookie(xcb_void_cookie_t cookie) { + return std::async(std::launch::deferred, [this, cookie]() { + xcb_generic_error_t* error = xcb_request_check(conn, cookie); + + if (error) { + return XcbErrorToStatus(conn, error); + } + + return absl::OkStatus(); + }); +} + +} // namespace actions::internal::x11 \ No newline at end of file diff --git a/actions/internal/x11/xcb_connection.h b/actions/internal/x11/xcb_connection.h new file mode 100644 index 0000000..5ef26a9 --- /dev/null +++ b/actions/internal/x11/xcb_connection.h @@ -0,0 +1,104 @@ +#ifndef ACTIONS_INTERNAL_X11_XCB_CONNECTION_H +#define ACTIONS_INTERNAL_X11_XCB_CONNECTION_H + +#include + +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "actions/internal/connection.h" +#include "actions/internal/x11/xcb_error.h" +#include "actions/internal/x11/xcb_keyboard.h" + +namespace actions::internal::x11 { +template +using XcbReply = absl::StatusOr; + +class XcbConnection : public Connection { + private: + xcb_connection_t* conn; + xcb_screen_t* screen; + XcbKeyboard keyboard; + + public: + static absl::StatusOr> Create() noexcept; + + XcbConnection(xcb_connection_t* conn, xcb_screen_t* screen, + XcbKeyboard&& keyboard) noexcept; + ~XcbConnection() noexcept; + + XcbConnection(XcbConnection&) = delete; + XcbConnection& operator=(XcbConnection&) = delete; + + XcbConnection(XcbConnection&&) noexcept; + XcbConnection& operator=(XcbConnection&&) noexcept; + + std::future Keystroke( + const action::Keystroke& keystroke, + const action::Target& target) noexcept override; + + std::future KeysPress( + const action::KeysPress& keys_press, + const action::Target& target) noexcept override; + + std::future KeysRelease( + const action::KeysRelease& keys_release, + const action::Target& target) noexcept override; + + std::future MoveCursor( + const action::CursorMove& cursor_move, + const action::Target& target) noexcept override; + + std::future MousePress( + const action::MousePress& mouse_press, + const action::Target& target) noexcept override; + + std::future MouseRelease( + const action::MouseRelease& mouse_release, + const action::Target& target) noexcept override; + + std::future MouseClick( + const action::MouseClick& mouse_click, + const action::Target& target) noexcept override; + + private: + std::future> + GetWindowAttributes(xcb_window_t window); + + std::future> GetWindowGeometry( + xcb_window_t window); + + std::future> QueryPointer(); + + template + std::future> ResolveReply( + Cookie cookie, + std::function + reply_getter) { + return std::async(std::launch::deferred, + [this, cookie, reply_getter]() -> XcbReply { + xcb_generic_error_t* error; + + T* reply = reply_getter(conn, cookie, &error); + + if (error) { + return XcbErrorToStatus(conn, error); + } + + return reply; + }); + } + + std::future SendMouseClick(xcb_button_index_t button); + + std::future SendMouseInput(xcb_button_index_t button, + bool press); + + std::future SendFakeInput(uint8_t type, uint8_t detail); + + std::future CheckCookie(xcb_void_cookie_t cookie); +}; +} // namespace actions::internal::x11 + +#endif // ACTIONS_INTERNAL_X11_XCB_CONNECTION_H \ No newline at end of file diff --git a/actions/internal/linux/xcb_error.cc b/actions/internal/x11/xcb_error.cc similarity index 94% rename from actions/internal/linux/xcb_error.cc rename to actions/internal/x11/xcb_error.cc index 71cb89b..5fb1090 100644 --- a/actions/internal/linux/xcb_error.cc +++ b/actions/internal/x11/xcb_error.cc @@ -1,10 +1,10 @@ -#include "actions/internal/linux/xcb_error.h" +#include "actions/internal/x11/xcb_error.h" #include #include "absl/status/status.h" -namespace actions::internal::linux { +namespace actions::internal::x11 { absl::Status XcbStatus(int code) noexcept { switch (code) { case 0: @@ -58,4 +58,4 @@ absl::Status XcbErrorToStatus(xcb_connection_t *conn, std::to_string(error->major_code) + "." + std::to_string(error->minor_code)); } -} // namespace actions::internal::linux \ No newline at end of file +} // namespace actions::internal::x11 \ No newline at end of file diff --git a/actions/internal/linux/xcb_error.h b/actions/internal/x11/xcb_error.h similarity index 74% rename from actions/internal/linux/xcb_error.h rename to actions/internal/x11/xcb_error.h index bedcc9e..1e6564a 100644 --- a/actions/internal/linux/xcb_error.h +++ b/actions/internal/x11/xcb_error.h @@ -1,11 +1,11 @@ -#ifndef ACTIONS_INTERNAL_LINUX_XCB_ERROR_H -#define ACTIONS_INTERNAL_LINUX_XCB_ERROR_H +#ifndef ACTIONS_INTERNAL_X11_XCB_ERROR_H +#define ACTIONS_INTERNAL_X11_XCB_ERROR_H #include #include "absl/status/status.h" -namespace actions::internal::linux { +namespace actions::internal::x11 { /// @brief Converts an xcb status code to an `absl::Status`. /// @param code The xcb status code. /// @return The associated status. @@ -18,6 +18,6 @@ absl::Status XcbStatus(int code) noexcept; /// @return The associated status. absl::Status XcbErrorToStatus(xcb_connection_t *conn, xcb_generic_error_t *error) noexcept; -} // namespace actions::internal::linux +} // namespace actions::internal::x11 -#endif // ACTIONS_INTERNAL_LINUX_XCB_ERROR_H \ No newline at end of file +#endif // ACTIONS_INTERNAL_X11_XCB_ERROR_H \ No newline at end of file diff --git a/actions/internal/linux/xcb_keyboard.cc b/actions/internal/x11/xcb_keyboard.cc similarity index 58% rename from actions/internal/linux/xcb_keyboard.cc rename to actions/internal/x11/xcb_keyboard.cc index 5e33b4b..c31ef20 100644 --- a/actions/internal/linux/xcb_keyboard.cc +++ b/actions/internal/x11/xcb_keyboard.cc @@ -1,4 +1,4 @@ -#include "actions/internal/linux/xcb_keyboard.h" +#include "actions/internal/x11/xcb_keyboard.h" #include #include @@ -11,10 +11,10 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" #include "actions/action/keystroke.h" -#include "actions/internal/linux/xcb_error.h" #include "actions/internal/util.h" +#include "actions/internal/x11/xcb_error.h" -namespace actions::internal::linux { +namespace actions::internal::x11 { XcbKeyboard::XcbKeyboard(xcb_connection_t* conn) noexcept : conn(conn), key_symbols(xcb_key_symbols_alloc(conn)) {} @@ -41,16 +41,55 @@ XcbKeyboard& XcbKeyboard::operator=(XcbKeyboard&& other) noexcept { return *this; } -std::future XcbKeyboard::SendKeystrokes( +std::future XcbKeyboard::Keystroke( const action::Keystroke& keystroke, xcb_window_t root) noexcept { + return SendKeysClick(keystroke.sequence, root); +} + +std::future XcbKeyboard::KeysPress( + const action::KeysPress& keys_press, xcb_window_t root) noexcept { + return SendKeysPress(keys_press.sequence, root); +} + +std::future XcbKeyboard::KeysRelease( + const action::KeysRelease& keys_release, xcb_window_t root) noexcept { + return SendKeysRelease(keys_release.sequence, root); +} + +std::future XcbKeyboard::SendKeysClick( + const action::KeySequence& keysequence, xcb_window_t root) noexcept { std::vector> futures; - futures.reserve(keystroke.size() * 2); + futures.reserve(keysequence.size()); - for (xcb_keysym_t key : keystroke) { + for (xcb_keysym_t key : keysequence) { futures.push_back(SendKey(true, key, root)); } - for (xcb_keysym_t key : keystroke) { + for (xcb_keysym_t key : keysequence) { + futures.push_back(SendKey(false, key, root)); + } + + return util::AllOk(std::move(futures)); +} + +std::future XcbKeyboard::SendKeysPress( + const action::KeySequence& keysequence, xcb_window_t root) noexcept { + std::vector> futures; + futures.reserve(keysequence.size()); + + for (xcb_keysym_t key : keysequence) { + futures.push_back(SendKey(false, key, root)); + } + + return util::AllOk(std::move(futures)); +} + +std::future XcbKeyboard::SendKeysRelease( + const action::KeySequence& keysequence, xcb_window_t root) noexcept { + std::vector> futures; + futures.reserve(keysequence.size()); + + for (xcb_keysym_t key : keysequence) { futures.push_back(SendKey(false, key, root)); } @@ -93,4 +132,4 @@ std::future XcbKeyboard::SendFakeInput( }); } -} // namespace actions::internal::linux \ No newline at end of file +} // namespace actions::internal::x11 \ No newline at end of file diff --git a/actions/internal/x11/xcb_keyboard.h b/actions/internal/x11/xcb_keyboard.h new file mode 100644 index 0000000..934a757 --- /dev/null +++ b/actions/internal/x11/xcb_keyboard.h @@ -0,0 +1,60 @@ +#ifndef ACTIONS_INTERNAL_X11_XCB_KEYBOARD_H +#define ACTIONS_INTERNAL_X11_XCB_KEYBOARD_H + +#include +#include + +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "actions/action/keystroke.h" + +namespace actions::internal::x11 { + +class XcbKeyboard { + private: + xcb_connection_t* conn; + xcb_key_symbols_t* key_symbols; + + public: + XcbKeyboard(xcb_connection_t* conn) noexcept; + ~XcbKeyboard() noexcept; + + XcbKeyboard(XcbKeyboard&) = delete; + XcbKeyboard& operator=(XcbKeyboard&) = delete; + + XcbKeyboard(XcbKeyboard&&) noexcept; + XcbKeyboard& operator=(XcbKeyboard&&) noexcept; + + std::future Keystroke(const action::Keystroke& keystroke, + xcb_window_t root) noexcept; + + std::future KeysPress(const action::KeysPress& keys_press, + xcb_window_t root) noexcept; + + std::future KeysRelease( + const action::KeysRelease& keys_release, xcb_window_t root) noexcept; + + private: + std::future SendKeysClick( + const action::KeySequence& keysequence, xcb_window_t root) noexcept; + + std::future SendKeysPress( + const action::KeySequence& keysequence, xcb_window_t root) noexcept; + + std::future SendKeysRelease( + const action::KeySequence& keysequence, xcb_window_t root) noexcept; + + std::future SendKey(bool press, xcb_keysym_t key, + xcb_window_t root) noexcept; + + std::future SendFakeInput(uint8_t type, uint8_t detail, + xcb_window_t root) noexcept; +}; + +} // namespace actions::internal::x11 + +#endif // ACTIONS_INTERNAL_X11_XCB_KEYBOARD_H \ No newline at end of file diff --git a/display/BUILD b/display/BUILD new file mode 100644 index 0000000..9c8c520 --- /dev/null +++ b/display/BUILD @@ -0,0 +1,5 @@ +load("@display//:local_display.bzl", "display_constraints") + +package(default_visibility = ["//visibility:public"]) + +display_constraints(name = "display_constraints") diff --git a/display/display.tpl.BUILD b/display/display.tpl.BUILD new file mode 100644 index 0000000..d40faf6 --- /dev/null +++ b/display/display.tpl.BUILD @@ -0,0 +1,16 @@ +package(default_visibility = ["//visibility:public"]) + +constraint_setting( + name = "display_type", + default_constraint_value = "${default_display_type}", +) + +constraint_value( + name = "unknown", + constraint_setting = ":display_type", +) + +constraint_value( + name = "x11", + constraint_setting = ":display_type", +) diff --git a/display/display_configure.bzl b/display/display_configure.bzl new file mode 100644 index 0000000..777e860 --- /dev/null +++ b/display/display_configure.bzl @@ -0,0 +1,131 @@ +""" +Rules for detecting and setting up display server dependencies +""" + +load("@bazel_skylib//lib:paths.bzl", "paths") + +_x11_install_paths = { + "default": { + "path": "/usr", + "include": "include/X11", + "lib": "lib", + }, + "xquartz": { + "path": "/usr/X11", + "include": "include", + "lib": "lib", + }, +} + +_xcb_install_paths = { + "default": { + "path": "/usr", + "include": "include/xcb", + "lib": "lib", + }, + "xquartz": { + "path": "/usr/xcb", + "include": "include", + "lib": "lib", + }, +} + +def x11_install( + name, + path, + include, + lib): + if name in _x11_install_paths: + fail("x11 install with name %s already exists") + + _x11_install_paths[name] = { + "path": path, + "include": include, + "lib": lib, + } + +def xcb_install( + name, + path, + include, + lib): + if name in _xcb_install_paths: + fail("xcb install with name %s already exists") + + _xcb_install_paths[name] = { + "path": path, + "include": include, + "lib": lib, + } + +def _find_x11_install(repository_ctx): + for name, install_paths in _x11_install_paths.items(): + base = install_paths["path"] + + inc_abs = paths.join(base, install_paths["include"]) + lib_abs = paths.join(base, install_paths["lib"]) + + # print(inc_abs) + + if repository_ctx.path(inc_abs).exists and \ + repository_ctx.path(lib_abs).exists: + return True, install_paths + + return False, _x11_install_paths["default"] + +def _find_xcb_install(repository_ctx): + for name, install_paths in _xcb_install_paths.items(): + base = install_paths["path"] + + inc_abs = paths.join(base, install_paths["include"]) + lib_abs = paths.join(base, install_paths["lib"]) + + # print(inc_abs) + + if repository_ctx.path(inc_abs).exists and \ + repository_ctx.path(lib_abs).exists: + return True, install_paths + + return False, _xcb_install_paths["default"] + +def _display_autoconf_impl(repository_ctx): + display_type = "unknown" + + has_x11, local_x11 = _find_x11_install(repository_ctx) + has_xcb, local_xcb = _find_xcb_install(repository_ctx) + + if has_x11 and has_xcb: + display_type = "x11" + else: + print("Failed to detect display server") # buildifier: disable=print + + repository_ctx.file("BUILD", "# empty BUILD file so that bazel sees this as a valid package directory") + repository_ctx.template( + "BUILD", + repository_ctx.path(Label("@actions//display:display.tpl.BUILD")), + { + "${default_display_type}": display_type, + }, + ) + + repository_ctx.template( + "local_display.bzl", + repository_ctx.path(Label("@actions//display:local_display.tpl.bzl")), + { + "${x11_path}": local_x11["path"], + "${x11_include_path}": local_x11["include"], + "${x11_lib_path}": local_x11["lib"], + "${xcb_path}": local_xcb["path"], + "${xcb_include_path}": local_xcb["include"], + "${xcb_lib_path}": local_xcb["lib"], + "${default_display_type}": display_type, + }, + ) + +display_autoconf = repository_rule( + implementation = _display_autoconf_impl, + configure = True, +) + +def display_configure(): + display_autoconf(name = "display") diff --git a/display/local_display.tpl.bzl b/display/local_display.tpl.bzl new file mode 100644 index 0000000..d336593 --- /dev/null +++ b/display/local_display.tpl.bzl @@ -0,0 +1,49 @@ +""" +Local Qt installation helper functions +""" + +# load("@bazel_skylib//lib:paths.bzl", "paths") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +def local_x11_path(): + return "${x11_path}" + +def local_x11_include_path(): + return "${x11_include_path}" + +def local_x11_lib_path(): + return "${x11_lib_path}" + +def local_xcb_path(): + return "${xcb_path}" + +def local_xcb_include_path(): + return "${xcb_include_path}" + +def local_xcb_lib_path(): + return "${xcb_lib_path}" + +def display_constraints(name): + native.constraint_setting( + name = "display_type", + default_constraint_value = "${default_display_type}", + ) + + native.constraint_value(name = "unknown", constraint_setting = ":display_type") + + native.constraint_value(name = "x11", constraint_setting = ":display_type") + +def display_repositories(): + maybe( + native.new_local_repository, + name = "X11", + path = local_x11_path(), + build_file = "@actions//third_party:X11.BUILD", + ) + + maybe( + native.new_local_repository, + name = "xcb", + path = local_xcb_path(), + build_file = "@actions//third_party:xcb.BUILD", + ) diff --git a/repositories.bzl b/repositories.bzl index ea5529f..5e4b280 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -22,17 +22,3 @@ def actions_repositories(): strip_prefix = "abseil-cpp-20220623.1", sha256 = "91ac87d30cc6d79f9ab974c51874a704de9c2647c40f6932597329a282217ba8", ) - - maybe( - native.new_local_repository, - name = "xcb", - path = "/usr", - build_file = "@actions//third_party:xcb.BUILD", - ) - - maybe( - native.new_local_repository, - name = "X11", - path = "/usr", - build_file = "@actions//third_party:X11.BUILD", - ) diff --git a/setup.bzl b/setup.bzl deleted file mode 100644 index 89b3cf0..0000000 --- a/setup.bzl +++ /dev/null @@ -1,5 +0,0 @@ -"""Setup for a workspace dependent on actions""" - -def actions_setup(): - """Setup for a workspace dependent on actions""" - pass diff --git a/third_party/X11.BUILD b/third_party/X11.BUILD index b4366d2..9b63f23 100644 --- a/third_party/X11.BUILD +++ b/third_party/X11.BUILD @@ -1,14 +1,26 @@ +load("@bazel_skylib//lib:paths.bzl", "paths") +load("@display//:local_display.bzl", "local_x11_include_path", "local_x11_lib_path", "local_x11_path") + cc_library( name = "X11", # buildifier: disable=constant-globs hdrs = glob([ - "include/X11/**/*.h*", + ("%s/**/*.h" % local_x11_include_path()), ]), includes = [ - "include/X11/", - ], - linkopts = [ - "-l:libX11.so", + local_x11_include_path(), ], + linkopts = select({ + "@platforms//os:linux": ["-lX11"], + # TODO: Check that this works on osx + "@platforms//os:osx": [ + ("-F%s" % paths.join( + local_x11_path(), + local_x11_lib_path(), + )), + "-framework X11", + ], + }), + target_compatible_with = ["@display//:x11"], visibility = ["//visibility:public"], ) diff --git a/third_party/xcb.BUILD b/third_party/xcb.BUILD index e85dfbb..38a980a 100644 --- a/third_party/xcb.BUILD +++ b/third_party/xcb.BUILD @@ -1,17 +1,34 @@ +load("@bazel_skylib//lib:paths.bzl", "paths") +load("@display//:local_display.bzl", "local_xcb_include_path", "local_xcb_lib_path", "local_xcb_path") + cc_library( name = "xcb", # buildifier: disable=constant-globs hdrs = glob([ - "include/xcb/**/*.h*", + ("%s/**/*.h" % local_xcb_include_path()), ]), includes = [ - "include/xcb/", - ], - linkopts = [ - "-l:libxcb-keysyms.so", - "-l:libxcb-util.so", - "-l:libxcb-xtest.so", - "-l:libxcb.so", + local_xcb_include_path(), ], + linkopts = select({ + "@platforms//os:linux": [ + "-lxcb", + "-lxcb-keysyms", + "-lxcb-util", + "-lxcb-xtest", + ], + # TODO: Check that this works on osx + "@platforms//os:osx": [ + ("-F%s" % paths.join( + local_xcb_path(), + local_xcb_lib_path(), + )), + "-framework xcb", + "-framework xcb-keysyms", + "-framework xcb-util", + "-framework xcb-xtest", + ], + }), + target_compatible_with = ["@display//:x11"], visibility = ["//visibility:public"], )