From 64baa7906bef71c306a74153ecbddcd57d8ce08b Mon Sep 17 00:00:00 2001 From: Hayodea Hakol Date: Tue, 14 Jan 2025 20:22:12 -0400 Subject: [PATCH] xcbXorg: Implement window search by ID and name The name search doesn't quite seem to work, but we captured all 4 of our regularly active windows (including the browser!!!) using window IDs. --- senseApis/xcbXorg/xcbXorg.cpp | 221 +++++++++++++++++++++++++++++++--- 1 file changed, 205 insertions(+), 16 deletions(-) diff --git a/senseApis/xcbXorg/xcbXorg.cpp b/senseApis/xcbXorg/xcbXorg.cpp index 6f009b6..267cc3a 100644 --- a/senseApis/xcbXorg/xcbXorg.cpp +++ b/senseApis/xcbXorg/xcbXorg.cpp @@ -33,6 +33,37 @@ struct XcbConnection } }; + /** + * @brief RAII guard for managing X server connection lifetime + * + * This guard ensures proper cleanup of X server connections in case of + * errors during attachDeviceReq. If a connection is created but the device + * attachment fails, and the connection's refcount is 0, this guard will + * automatically remove the connection from the connections map. + * + * The guard can be "committed" using commit() to indicate successful + * device attachment, in which case it will not perform cleanup on destruction. + */ + class ConnectionGuard + { + std::shared_ptr conn; + bool committed = false; + + public: + explicit ConnectionGuard(std::shared_ptr c) + : conn(std::move(c)) + {} + + void commit(void) { committed = true; } + + ~ConnectionGuard() + { + if (!committed && conn && conn->refCount == 0) { + XcbConnection::connections.erase(conn->connectionIdentifier); + } + } + }; + XcbConnection(const XConnectionIdentifier& id) : connection(nullptr, &xcb_disconnect), connectionIdentifier(id), refCount(0) @@ -88,6 +119,22 @@ public: } }; +namespace xcb_window_search { + + // Custom deleters for XCB reply types + struct XcbReplyDeleter { + void operator()(xcb_query_tree_reply_t* p) { free(p); } + void operator()(xcb_get_property_reply_t* p) { free(p); } + }; + + static xcb_window_t findById( + xcb_connection_t* conn, xcb_window_t root, uint32_t targetId); + + static xcb_window_t findByName( + xcb_connection_t* conn, xcb_window_t root, + const std::string& targetName, std::string& outWindowName); +} + class AttachedDevice { public: @@ -117,16 +164,51 @@ public: AttachedDevice(const hk::device::SenseDeviceSpec &spec, std::shared_ptr conn) : deviceSpec(spec) + // This std::move is moving ownership from a shared_ptr to a shared_ptr , connection(std::move(conn)) { windowSelector.xconn.display = getRequiredParamAsInt(spec, "display"); windowSelector.xconn.screen = getRequiredParamAsInt(spec, "screen"); parseWindowSelector(spec.deviceSelector, windowSelector); + + // Get the root window + const xcb_setup_t* setup = xcb_get_setup(connection->connection.get()); + xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); + for (int i = 0; i < windowSelector.xconn.screen; ++i) { + xcb_screen_next(&iter); + } + xcb_window_t root = iter.data->root; + + // Search for window + xcb_window_t targetWindow = 0; + if (windowSelector.useWindowId) + { + targetWindow = xcb_window_search::findById( + connection->connection.get(), root, windowSelector.window.id); + } + else + { + targetWindow = xcb_window_search::findByName( + connection->connection.get(), root, + windowSelector.window.name, actualWindowName); + } + + if (!targetWindow) + { + throw std::runtime_error( + "Failed to find window " + + (windowSelector.useWindowId + ? std::to_string(windowSelector.window.id) + : "\"" + windowSelector.window.name + "\"") + + " on display " + std::to_string(windowSelector.xconn.display) + + ", screen " + std::to_string(windowSelector.xconn.screen)); + } } hk::device::SenseDeviceSpec deviceSpec; WindowSelector windowSelector; std::shared_ptr connection; + std::string actualWindowName; public: static int getRequiredParamAsInt( @@ -172,7 +254,8 @@ public: else { try { - windowSelector.window.id = std::stoul(selector); + // Allow hex numbers + windowSelector.window.id = std::stoul(selector, nullptr, 0); windowSelector.useWindowId = true; } catch (const std::exception&) { throw std::runtime_error( @@ -189,6 +272,114 @@ std::map> static std::vector attachedDevices; +namespace xcb_window_search { + +static xcb_window_t findById( + xcb_connection_t* conn, xcb_window_t root, uint32_t targetId) +{ + xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, root); + std::unique_ptr reply( + xcb_query_tree_reply(conn, cookie, nullptr)); + if (!reply) return 0; + + // First check if current window is the target + if (root == targetId) { + return root; + } + + // Then check all children + xcb_window_t* children = xcb_query_tree_children(reply.get()); + int num_children = xcb_query_tree_children_length(reply.get()); + + for (int i = 0; i < num_children; ++i) + { + if (children[i] == targetId) { + return children[i]; + } + + // Recursively search this child's subtree + if (xcb_window_t result = findById( + conn, children[i], targetId)) + { + return result; + } + } + + return 0; +} + +static xcb_window_t findByName( + xcb_connection_t* conn, xcb_window_t root, + const std::string& targetName, std::string& outWindowName) +{ + xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, root); + std::unique_ptr reply( + xcb_query_tree_reply(conn, cookie, nullptr)); + if (!reply) return 0; + + // First check current window + xcb_get_property_cookie_t prop_cookie = xcb_get_property( + conn, 0, root, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 1024); + + std::unique_ptr prop_reply( + xcb_get_property_reply(conn, prop_cookie, nullptr)); + + if (prop_reply) + { + int len = xcb_get_property_value_length(prop_reply.get()); + char* name = static_cast( + xcb_get_property_value(prop_reply.get())); + if (len > 0) + { + std::string windowName(name, len); + if (windowName == targetName) + { + outWindowName = windowName; + return root; + } + } + } + + // Then check all children + xcb_window_t* children = xcb_query_tree_children(reply.get()); + int num_children = xcb_query_tree_children_length(reply.get()); + + for (int i = 0; i < num_children; ++i) + { + prop_cookie = xcb_get_property( + conn, 0, children[i], + XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 1024); + + prop_reply.reset(xcb_get_property_reply(conn, prop_cookie, nullptr)); + if (prop_reply) + { + int len = xcb_get_property_value_length(prop_reply.get()); + char* name = static_cast(xcb_get_property_value( + prop_reply.get())); + if (len > 0) + { + std::string windowName(name, len); + if (windowName == targetName) + { + outWindowName = windowName; + return children[i]; + } + } + } + + // Recursively search this child's subtree + if (xcb_window_t result = findByName( + conn, children[i], targetName, outWindowName)) + { + return result; + } + } + + return 0; +} + +} // namespace xcb_window_search + static hk::sense_api::sal_mlo_initializeIndFn xcbXorg_initializeInd; static hk::sense_api::sal_mlo_finalizeIndFn xcbXorg_finalizeInd; static hk::sense_api::sal_mlo_attachDeviceReqFn xcbXorg_attachDeviceReq; @@ -227,29 +418,27 @@ int xcbXorg_finalizeInd(void) int xcbXorg_attachDeviceReq(const hk::device::SenseDeviceSpec &desc) { - // Create temporary device to validate parameters - AttachedDevice::WindowSelector tempSelector; - - tempSelector.xconn.display = AttachedDevice::getRequiredParamAsInt( - desc, "display"); - tempSelector.xconn.screen = AttachedDevice::getRequiredParamAsInt( - desc, "screen"); - - // Get or create connection before constructing device + // Ensure connection exists before creating device. Create conn'tion if not. XcbConnection::XConnectionIdentifier id{ - tempSelector.xconn.display, - tempSelector.xconn.screen + AttachedDevice::getRequiredParamAsInt(desc, "display"), + AttachedDevice::getRequiredParamAsInt(desc, "screen") }; auto conn = XcbConnection::getOrCreateConnection(id); + // RAII protection in case AttachDevice construction below fails + XcbConnection::ConnectionGuard guard(conn); - // Create device with validated connection + // Create device and increment connection refcount attachedDevices.emplace_back(desc, conn); + // Successfully attached device, so decouple guard from RAII cleanup conn->refCount++; + guard.commit(); std::cout << "Attaching X11 window:\n " - << attachedDevices.back().windowSelector.stringify() << "\n" - << " Using " << (conn->refCount > 1 ? "existing" : "new") - << " connection to X server\n"; + << attachedDevices.back().windowSelector.stringify() << "\n" + << " Actual window name: \"" + << attachedDevices.back().actualWindowName << "\"\n" + << " Using " << (conn->refCount > 1 ? "existing" : "new") + << " connection to X server\n"; return 0; }