diff --git a/commonLibs/xcbXorg/xcbXorg.cpp b/commonLibs/xcbXorg/xcbXorg.cpp index b5f8926..6b4f314 100644 --- a/commonLibs/xcbXorg/xcbXorg.cpp +++ b/commonLibs/xcbXorg/xcbXorg.cpp @@ -6,329 +6,91 @@ #include #include #include -#include -#include #include #include "xcbXorg.h" -/** - * @brief Manages X server connections using XCB - * - * This struct manages connections to the X server using the XCB library. It - * ensures that each unique display and screen combination has a single - * connection, and provides RAII management for these connections. - */ -struct XcbConnection +namespace xcb_xorg { + +// Static member initialization +std::map> + ConnectionManager::connections; + +std::string ConnectionIdentifier::stringify() const { - struct XConnectionIdentifier - { - int display; - int screen; - - bool operator<(const XConnectionIdentifier& other) const - { - if (display != other.display) return display < other.display; - return screen < other.screen; - } - - std::string stringify() const - { - std::ostringstream os; - os << "display=" << display << ", screen=" << screen; - return os.str(); - } - }; - - /** - * @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) - { - // Convert to X display string format (e.g., ":0.1") - std::string displayString = ":" + std::to_string(id.display) - + "." + std::to_string(id.screen); - - int screenNum; - connection.reset(xcb_connect(displayString.c_str(), &screenNum)); - if (xcb_connection_has_error(connection.get())) - { - throw std::runtime_error( - std::string(__func__) + ": Failed to connect to X server " - + connectionIdentifier.stringify()); - } - - // Verify we got the screen we asked for - if (screenNum != id.screen) - { - throw std::runtime_error( - std::string(__func__) + ": Connected to wrong screen. " - "Requested " + connectionIdentifier.stringify() - + " but got screen " + std::to_string(screenNum)); - } - } - - // Delete copy/move operations - we'll manage instances through pointers - XcbConnection(const XcbConnection&) = delete; - XcbConnection& operator=(const XcbConnection&) = delete; - XcbConnection(XcbConnection&&) = delete; - XcbConnection& operator=(XcbConnection&&) = delete; - - std::unique_ptr connection; - XConnectionIdentifier connectionIdentifier; - std::atomic refCount; - -public: - static std::map> - connections; - - static std::shared_ptr getOrCreateConnection( - const XConnectionIdentifier& id) - { - auto it = connections.find(id); - if (it != connections.end()) { - return it->second; - } - - auto conn = std::make_shared(id); - connections.emplace(id, conn); - return conn; - } -}; - -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); } - }; - - enum class MatchType { SUBSTRING, EXACT, ID }; - - 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, - MatchType matchType); + std::ostringstream os; + os << "display=" << display << ", screen=" << screen; + return os.str(); } -/** - * @brief Represents an attached device and its associated window - * - * This class represents a device that has been attached to the X server. It - * manages the connection to the X server and the window selection criteria. - */ -class AttachedDevice +XcbConnection::XcbConnection(const ConnectionIdentifier& id) +: connection(nullptr, &xcb_disconnect), + connectionIdentifier(id), refCount(0) { -public: - struct WindowSelector + // Convert to X display string format (e.g., ":0.1") + std::string displayString = ":" + std::to_string(id.display) + + "." + std::to_string(id.screen); + + int screenNum; + + connection.reset(xcb_connect(displayString.c_str(), &screenNum)); + if (xcb_connection_has_error(connection.get())) { - xcb_window_search::MatchType matchType; - - XcbConnection::XConnectionIdentifier xconn; - struct - { - uint32_t id; - std::string name; - } window; - - std::string stringify() const - { - std::ostringstream os; - os << "Display: " << xconn.display - << ", Screen: " << xconn.screen << ", Window: "; - if (matchType == xcb_window_search::MatchType::ID) { - os << window.id; - } else { - os << "\"" << window.name << "\""; - } - os << " (matchType=" - << (matchType == xcb_window_search::MatchType::EXACT - ? "exact" : - (matchType == xcb_window_search::MatchType::SUBSTRING) - ? "substring" : "id") - << ")"; - return os.str(); - } - }; - - AttachedDevice(const smo::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, 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.matchType == xcb_window_search::MatchType::ID) - { - 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, - windowSelector.matchType); - } - - if (!targetWindow) - { - throw std::runtime_error( - "Failed to find window " - + (windowSelector.matchType == xcb_window_search::MatchType::ID - ? std::to_string(windowSelector.window.id) - : "\"" + windowSelector.window.name + "\"") - + " on display " + std::to_string(windowSelector.xconn.display) - + ", screen " + std::to_string(windowSelector.xconn.screen)); - } + throw std::runtime_error( + std::string(__func__) + ": Failed to connect to X server " + + connectionIdentifier.stringify()); } - smo::device::SenseDeviceSpec deviceSpec; - WindowSelector windowSelector; - std::shared_ptr connection; - std::string actualWindowName; - -public: - static int getRequiredParamAsInt( - const smo::device::SenseDeviceSpec& spec, - const std::string& paramName) + // Verify we got the screen we asked for + if (screenNum != id.screen) { - auto it = std::find_if( - spec.providerParams.begin(), - spec.providerParams.end(), - [¶mName](const auto& param) { - return param.first == paramName; - } - ); + throw std::runtime_error( + std::string(__func__) + ": Connected to wrong screen. " + "Requested " + connectionIdentifier.stringify() + + " but got screen " + std::to_string(screenNum)); + } +} - if (it == spec.providerParams.end()) - { - throw std::runtime_error( - "No " + paramName + " specified in provider params"); - } - - try { - return std::stoi(it->second); - } catch (const std::exception& e) { - throw std::runtime_error( - "Failed to parse '" + paramName + "' param value '" - + it->second + "' as integer: " + e.what()); - } +std::shared_ptr ConnectionManager::getOrCreateConnection( + const ConnectionIdentifier& id) +{ + auto it = connections.find(id); + if (it != connections.end()) { + return it->second; } - static void parseWindowSelector( - const smo::device::SenseDeviceSpec& spec, - WindowSelector& windowSelector) - { - // Default match type - windowSelector.matchType = xcb_window_search::MatchType::SUBSTRING; + auto conn = std::make_shared(id); + connections.emplace(id, conn); + return conn; +} - // Check if 'dev-id', 'dev-string', or 'dev-substring' is specified - for (const auto& param : spec.apiParams) - { - if (param.first == "dev-id" || param.first == "devid") - { - windowSelector.matchType = xcb_window_search::MatchType::ID; - break; - } - if (param.first == "dev-string" || param.first == "dev-str" - || param.first == "devstr" || param.first == "devstring") - { - windowSelector.matchType = xcb_window_search::MatchType::EXACT; - } - if (param.first == "dev-substring" || param.first == "dev-substr" - || param.first == "devsubstr" || param.first == "devsubstring") - { - windowSelector.matchType = xcb_window_search:: - MatchType::SUBSTRING; - } - } +void ConnectionManager::cleanupAllConnections() +{ + connections.clear(); +} - if (windowSelector.matchType == xcb_window_search::MatchType::ID) - { - try { - windowSelector.window.id = std::stoul( - spec.deviceSelector, nullptr, 0); - } catch (const std::exception&) { - throw std::runtime_error( - "Window selector: 'dev-id' present, but selector is not " - "numeric"); - } - } - else { - windowSelector.window.name = spec.deviceSelector; - } - } +size_t ConnectionManager::getConnectionCount() +{ + return connections.size(); +} + +namespace window_search { + +struct XcbReplyDeleter { + void operator()(xcb_query_tree_reply_t* p) { free(p); } + void operator()(xcb_get_property_reply_t* p) { free(p); } }; -// Define the static member -std::map> - XcbConnection::connections; - -static std::vector attachedDevices; - -namespace xcb_window_search { - -static xcb_window_t findById( +xcb_window_t findById( xcb_connection_t* conn, xcb_window_t root, uint32_t targetId) { + if (root == targetId) return root; + 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()); @@ -339,9 +101,7 @@ static xcb_window_t findById( } // Recursively search this child's subtree - if (xcb_window_t result = findById( - conn, children[i], targetId)) - { + if (xcb_window_t result = findById(conn, children[i], targetId)) { return result; } } @@ -349,7 +109,7 @@ static xcb_window_t findById( return 0; } -static xcb_window_t findByName( +xcb_window_t findByName( xcb_connection_t* conn, xcb_window_t root, const std::string& targetName, std::string& outWindowName, MatchType matchType) @@ -357,6 +117,7 @@ static xcb_window_t findByName( 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 @@ -426,133 +187,78 @@ static xcb_window_t findByName( return 0; } -} // namespace xcb_window_search +} // namespace window_search -static smo::sense_api::sal_mlo_initializeIndFn xcbXorg_initializeInd; -static smo::sense_api::sal_mlo_finalizeIndFn xcbXorg_finalizeInd; -static smo::sense_api::sal_mlo_attachDeviceReqFn xcbXorg_attachDeviceReq; -static smo::sense_api::sal_mlo_detachDeviceReqFn xcbXorg_detachDeviceReq; +} // namespace xcb_xorg -static smo::sense_api::SenseApiDesc xcbXorgApiDesc = +// Export functions for external libraries +/** + * @brief Get or create a connection to the X server + * @param display Display number + * @param screen Screen number + * @return Shared pointer to connection (as void* for C compatibility) + */ +extern "C" get_or_create_connection_fn xcb_xorg_get_or_create_connection; +std::shared_ptr xcb_xorg_get_or_create_connection( + int display, int screen) { - .name = "xcb", - .exportedImplexorApis = { { "video-implexor" } }, - .sal_mgmt_libOps = { - .initializeInd = xcbXorg_initializeInd, - .finalizeInd = xcbXorg_finalizeInd, - .attachDeviceReq = xcbXorg_attachDeviceReq, - .detachDeviceReq = xcbXorg_detachDeviceReq - } -}; - -/* Below here are the exported functions that Salmanoff will use both to load - * and use this library. - ******************************************************************************/ + xcb_xorg::ConnectionIdentifier id{display, screen}; + auto conn = xcb_xorg::ConnectionManager::getOrCreateConnection(id); + conn->incrementRefCount(); + return conn; +} /** - * @brief Get the Sense API Descriptor - * - * This function is used to retrieve the descriptor for the Sense API provided - * by this library. The descriptor contains information about the API, such as - * its name, the implexors it exports, and the management operations it - * supports. - * - * @param callbacks Hooks provided by Salmanoff for library operations - * @return A reference to the SenseApiDesc structure describing the API. + * @brief Clean up all connections */ -extern SMO_UNMANGLED smo::sense_api::SMO_GET_SENSE_API_DESC_FN_TYPEDEF - SMO_GET_SENSE_API_DESC_FN_NAME; - -static const smo::sense_api::SalmanoffCallbacks* xcbXorg_callbacks = nullptr; - -const smo::sense_api::SenseApiDesc &SMO_GET_SENSE_API_DESC_FN_NAME( - const smo::sense_api::SalmanoffCallbacks& callbacks) +extern "C" cleanup_connections_fn xcb_xorg_cleanup_connections; +void xcb_xorg_cleanup_connections() { - xcbXorg_callbacks = &callbacks; - return xcbXorgApiDesc; + xcb_xorg::ConnectionManager::cleanupAllConnections(); } -int xcbXorg_initializeInd(void) +/** + * @brief Get the number of active connections + * @return Number of active connections + */ +size_t xcb_xorg_get_connection_count() { - return 0; + return xcb_xorg::ConnectionManager::getConnectionCount(); } -int xcbXorg_finalizeInd(void) +/** + * @brief Find window by ID + * @param conn Connection pointer (from xcb_xorg_get_connection) + * @param root Root window + * @param targetId Target window ID + * @return Window ID if found, 0 if not found + */ +extern "C" find_window_by_id_fn xcb_xorg_find_window_by_id; +xcb_window_t xcb_xorg_find_window_by_id(void* conn, xcb_window_t root, uint32_t targetId) { - XcbConnection::connections.clear(); - return 0; + if (!conn) return 0; + auto connection = static_cast(conn); + return xcb_xorg::window_search::findById( + connection->getConnection(), root, targetId); } -int xcbXorg_attachDeviceReq(const smo::device::SenseDeviceSpec &desc) +/** + * @brief Find window by name + * @param conn Connection pointer (from xcb_xorg_get_connection) + * @param root Root window + * @param targetName Target window name + * @param outWindowName Output parameter for actual window name + * @param matchType Type of name matching + * @return Window ID if found, 0 if not found + */ +extern "C" find_window_by_name_fn xcb_xorg_find_window_by_name; +xcb_window_t xcb_xorg_find_window_by_name(void* conn, xcb_window_t root, + const std::string& targetName, std::string& outWindowName, + xcb_xorg::window_search::MatchType matchType) { - // Ensure connection exists before creating device. Create conn'tion if not. - XcbConnection::XConnectionIdentifier id{ - 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 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" - << " Actual window name: \"" - << attachedDevices.back().actualWindowName << "\"\n" - << " Using " << (conn->refCount > 1 ? "existing" : "new") - << " connection to X server\n"; - - return 0; -} - -int xcbXorg_detachDeviceReq(const smo::device::SenseDeviceSpec &spec) -{ - auto it = std::find_if(attachedDevices.begin(), attachedDevices.end(), - [&spec](const AttachedDevice &device) { - return device.deviceSpec == spec; - } - ); - - if (it == attachedDevices.end()) - { - auto displayIt = std::find_if( - spec.providerParams.begin(), spec.providerParams.end(), - [](const auto& param) { return param.first == "display"; }); - auto screenIt = std::find_if( - spec.providerParams.begin(), spec.providerParams.end(), - [](const auto& param) { return param.first == "screen"; }); - - std::cerr << __func__ << ": Device not attached: " - << "display=" << (displayIt != spec.providerParams.end() - ? displayIt->second : "not specified") - << ", screen=" << (screenIt != spec.providerParams.end() - ? screenIt->second : "not specified") - << ", selector=" << spec.deviceSelector << "\n"; - return 0; - } - - XcbConnection::XConnectionIdentifier id{ - it->windowSelector.xconn.display, - it->windowSelector.xconn.screen - }; - - // Atomic decrement refcount - int newCount = --it->connection->refCount; - - // If no more references, close the connection - if (newCount == 0) - { - XcbConnection::connections.erase(id); - std::cout << "Closed X server connection (display=" - << id.display << ", screen=" << id.screen << ")\n"; - } - - attachedDevices.erase(it); - std::cout << __func__ << ": Detached device\n"; - return 0; + if (!conn) return 0; + auto connection = static_cast(conn); + return xcb_xorg::window_search::findByName( + connection->getConnection(), root, targetName, outWindowName, + matchType); } diff --git a/commonLibs/xcbXorg/xcbXorg.h b/commonLibs/xcbXorg/xcbXorg.h index 6d8c976..faf88e4 100644 --- a/commonLibs/xcbXorg/xcbXorg.h +++ b/commonLibs/xcbXorg/xcbXorg.h @@ -1,22 +1,176 @@ -#ifndef X11_XCB_API_H -#define X11_XCB_API_H +#ifndef XCB_XORG_API_H +#define XCB_XORG_API_H #include #include #include -#include +#include +#include +#include -class Xcb_XorgApi +namespace xcb_xorg { + +/** + * @brief Connection identifier for X server connections + */ +struct ConnectionIdentifier { -public: - Xcb_XorgApi(const std::string& _displayName) - : displayName(_displayName) - {} + int display; + int screen; - ~Xcb_XorgApi() = default; + bool operator<(const ConnectionIdentifier& other) const + { + if (display != other.display) return display < other.display; + return screen < other.screen; + } -private: - std::string displayName; + std::string stringify() const; }; -#endif // X11_XCB_API_H +/** + * @brief Represents a single X server connection using XCB + * + * This class manages a single connection to the X server. It provides + * RAII management for the connection and maintains a reference count + * for shared usage. + */ +class XcbConnection +{ +public: + /** + * @brief Constructor for creating a new connection + * @param id Connection identifier specifying display and screen + * @throws std::runtime_error if connection fails + */ + explicit XcbConnection(const ConnectionIdentifier& id); + + // Delete copy/move operations - we'll manage instances through pointers + XcbConnection(const XcbConnection&) = delete; + XcbConnection& operator=(const XcbConnection&) = delete; + XcbConnection(XcbConnection&&) = delete; + XcbConnection& operator=(XcbConnection&&) = delete; + + /** + * @brief Destructor - automatically closes the connection + */ + ~XcbConnection() = default; + + /** + * @brief Get the underlying XCB connection + * @return Raw XCB connection pointer + */ + xcb_connection_t* getConnection() const { return connection.get(); } + + /** + * @brief Get the connection identifier + * @return Connection identifier + */ + const ConnectionIdentifier& getIdentifier() const { return connectionIdentifier; } + + /** + * @brief Increment reference count + */ + void incrementRefCount() { refCount++; } + + /** + * @brief Decrement reference count + * @return New reference count + */ + int decrementRefCount() { return --refCount; } + + /** + * @brief Get current reference count + * @return Current reference count + */ + int getRefCount() const { return refCount; } + + /** + * @brief Check if connection is valid + * @return true if connection is valid, false otherwise + */ + bool isValid() const { return connection && !xcb_connection_has_error(connection.get()); } + +private: + std::unique_ptr connection; + ConnectionIdentifier connectionIdentifier; + std::atomic refCount; +}; + +/** + * @brief Manages multiple X server connections + * + * This class manages a collection of XcbConnection instances. It ensures + * that each unique display and screen combination has a single connection, + * and provides RAII management for these connections. + */ +class ConnectionManager +{ +public: + /** + * @brief Get or create a connection to the X server + * @param id Connection identifier specifying display and screen + * @return Shared pointer to the connection + * @throws std::runtime_error if connection fails + */ + static std::shared_ptr getOrCreateConnection( + const ConnectionIdentifier& id); + + /** + * @brief Clean up all connections + */ + static void cleanupAllConnections(); + + /** + * @brief Get the number of active connections + * @return Number of active connections + */ + static size_t getConnectionCount(); + +private: + static std::map> connections; +}; + +/** + * @brief Window search functionality + */ +namespace window_search { + +enum class MatchType { SUBSTRING, EXACT, ID }; + +/** + * @brief Find window by ID + * @param conn XCB connection + * @param root Root window + * @param targetId Target window ID + * @return Window ID if found, 0 if not found + */ +xcb_window_t findById(xcb_connection_t* conn, xcb_window_t root, uint32_t targetId); + +/** + * @brief Find window by name + * @param conn XCB connection + * @param root Root window + * @param targetName Target window name + * @param outWindowName Output parameter for actual window name + * @param matchType Type of name matching to perform + * @return Window ID if found, 0 if not found + */ +xcb_window_t findByName(xcb_connection_t* conn, xcb_window_t root, + const std::string& targetName, std::string& outWindowName, + MatchType matchType); + +} // namespace window_search + +} // namespace xcb_xorg + +// Function signature types for dynamic loading of libxcbXorg +typedef std::shared_ptr get_or_create_connection_fn( + int display, int screen); +typedef void cleanup_connections_fn(); +typedef xcb_window_t find_window_by_id_fn( + void* conn, xcb_window_t root, uint32_t targetId); +typedef xcb_window_t find_window_by_name_fn(void* conn, xcb_window_t root, + const std::string& targetName, std::string& outWindowName, + xcb_xorg::window_search::MatchType matchType); + +#endif // XCB_XORG_API_H diff --git a/senseApis/xcbWindow/CMakeLists.txt b/senseApis/xcbWindow/CMakeLists.txt index 62bf661..2697f23 100644 --- a/senseApis/xcbWindow/CMakeLists.txt +++ b/senseApis/xcbWindow/CMakeLists.txt @@ -1,22 +1,27 @@ # XCB/Xorg Window Attaching SenseAPI backend cmake_dependent_option(ENABLE_SENSEAPI_xcbWindow - "Enable XCB/Xorg Window Attaching SenseAPI backend" OFF - "ENABLE_LIB_xcbXorg" OFF) + "Enable XCB/Xorg Window Attaching SenseAPI backend" ON + "ENABLE_LIB_xcbXorg" ON) if(ENABLE_SENSEAPI_xcbWindow) - # For now, just create a placeholder library - add_library(senseApiXcbWindow STATIC - # xcbWindow.cpp would go here - ) - - target_link_libraries(senseApiXcbWindow - xcbXorg + add_library(senseApiXcbWindow SHARED + xcbWindow.cpp ) target_include_directories(senseApiXcbWindow PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../../include + ${CMAKE_CURRENT_SOURCE_DIR}/../../smocore/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../commonLibs ) + # Link against XCB library directly (libxcbXorg will be loaded dynamically) + pkg_check_modules(XCB REQUIRED xcb) + target_link_libraries(senseApiXcbWindow ${XCB_LIBRARIES}) + # Set config define for header generation add_compile_definitions(CONFIG_SENSEAPI_XCBWINDOW_ENABLED) + + # Install rules + install(TARGETS senseApiXcbWindow DESTINATION lib) endif() diff --git a/senseApis/xcbWindow/xcbWindow.cpp b/senseApis/xcbWindow/xcbWindow.cpp new file mode 100644 index 0000000..df56bfa --- /dev/null +++ b/senseApis/xcbWindow/xcbWindow.cpp @@ -0,0 +1,330 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xcbWindow.h" +#include "../../commonLibs/xcbXorg/xcbXorg.h" + +// Function pointers to API entry points exported by libxcbXorg. +struct XcbXorgFunctions { + get_or_create_connection_fn* getOrCreateConnection = nullptr; + cleanup_connections_fn* cleanupConnections = nullptr; + find_window_by_id_fn* findWindowById = nullptr; + find_window_by_name_fn* findWindowByName = nullptr; +}; + +static void* g_xcbXorgHandle = nullptr; +static XcbXorgFunctions xcbXorgFns; + +// Salmanoff hooks, obtained from SMO_GET_SENSE_API_DESC_FN_NAME(). +static const smo::sense_api::SalmanoffCallbacks* smoHooksPtr = nullptr; + +// Attached windows. +static std::vector> + g_attachedWindows; + +namespace xcb_window { + +std::string WindowSelector::stringify() const +{ + std::ostringstream os; + + os << "Display: " << display + << ", Screen: " << screen << ", Window: "; + if (matchType == xcb_xorg::window_search::MatchType::ID) { + os << windowId; + } else { + os << "\"" << windowName << "\""; + } + os << " (matchType=" + << (matchType == xcb_xorg::window_search::MatchType::EXACT ? "exact" : + (matchType == xcb_xorg::window_search::MatchType::SUBSTRING) ? "substring" : "id") + << ")"; + return os.str(); +} + +AttachedWindow::AttachedWindow(const smo::device::SenseDeviceSpec& spec) + : deviceSpec(spec), xcbConnection(nullptr) +{ + // Validate required function pointers are available + if (!xcbXorgFns.getOrCreateConnection || + !xcbXorgFns.findWindowById || !xcbXorgFns.findWindowByName) + { + throw std::runtime_error("xcbWindow:" + std::string(__func__) + + ": Required xcbXorg function pointers not available"); + } + + windowSelector.display = getRequiredParamAsInt(spec, "display"); + windowSelector.screen = getRequiredParamAsInt(spec, "screen"); + parseWindowSelector(spec); + + // Get connection from libxcbXorg + std::shared_ptr conn = + (*xcbXorgFns.getOrCreateConnection)( + windowSelector.display, windowSelector.screen); + + xcbConnection = conn.get(); + + // Find the target window + xcb_window_t foundWindow = 0; + const xcb_setup_t* setup = xcb_get_setup(conn->getConnection()); + const xcb_screen_t* screen = xcb_setup_roots_iterator(setup).data; + + if (windowSelector.matchType == xcb_xorg::window_search::MatchType::ID) + { + foundWindow = (*xcbXorgFns.findWindowById)( + xcbConnection, screen->root, + windowSelector.windowId); + } + else + { + foundWindow = (*xcbXorgFns.findWindowByName)( + xcbConnection, screen->root, + windowSelector.windowName, actualWindowName, + windowSelector.matchType); + } + + if (!foundWindow) + { + throw std::runtime_error("xcbWindow:" + std::string(__func__) + + ": Window not found: " + windowSelector.stringify()); + } +} + +void AttachedWindow::parseWindowSelector( + const smo::device::SenseDeviceSpec& spec + ) +{ + // Default match type + windowSelector.matchType = xcb_xorg::window_search::MatchType::SUBSTRING; + + // Check if 'dev-id', 'dev-string', or 'dev-substring' is specified + for (const auto& param : spec.apiParams) + { + if (param.first == "dev-id" || param.first == "devid") + { + windowSelector.matchType = xcb_xorg::window_search::MatchType::ID; + break; + } + if (param.first == "dev-string" || param.first == "dev-str" + || param.first == "devstr" || param.first == "devstring") + { + windowSelector.matchType = xcb_xorg::window_search::MatchType::EXACT; + } + if (param.first == "dev-substring" || param.first == "dev-substr" + || param.first == "devsubstr" || param.first == "devsubstring") + { + windowSelector.matchType = xcb_xorg::window_search::MatchType::SUBSTRING; + } + } + + if (windowSelector.matchType == xcb_xorg::window_search::MatchType::ID) + { + try { + windowSelector.windowId = std::stoul( + spec.deviceSelector, nullptr, 0); + } catch (const std::exception&) { + throw std::runtime_error( + "xcbWindow:" + std::string(__func__) + ": Window selector: " + "'dev-id' present, but selector is not numeric"); + } + } + else { + windowSelector.windowName = spec.deviceSelector; + } +} + +int AttachedWindow::getRequiredParamAsInt(const smo::device::SenseDeviceSpec& spec, + const std::string& paramName) +{ + auto it = std::find_if( + spec.providerParams.begin(), + spec.providerParams.end(), + [¶mName](const auto& param) { + return param.first == paramName; + } + ); + + if (it == spec.providerParams.end()) + { + throw std::runtime_error( + "No " + paramName + " specified in provider params"); + } + + try { + return std::stoi(it->second); + } catch (const std::exception& e) { + throw std::runtime_error( + "Failed to parse '" + paramName + "' param value '" + + it->second + "' as integer: " + e.what()); + } +} + +std::string AttachedWindow::stringify() const { + std::ostringstream os; + + auto matchTypeStr = [](xcb_xorg::window_search::MatchType mt) -> const char* { + switch (mt) { + case xcb_xorg::window_search::MatchType::SUBSTRING: + return "substring"; + case xcb_xorg::window_search::MatchType::EXACT: + return "exact"; + case xcb_xorg::window_search::MatchType::ID: + return "id"; + default: + return "unknown"; + } + }; + + auto formatWindowId = [](uint32_t id) -> std::string { + std::ostringstream oss; + oss << "Window ID 0x" << std::hex << id << std::dec; + return oss.str(); + }; + + os << "Display: " << windowSelector.display + << ", Screen: " << windowSelector.screen + << ", MatchType: " << matchTypeStr(windowSelector.matchType) + << ", Target: "; + + if (windowSelector.matchType == xcb_xorg::window_search::MatchType::ID) { + os << formatWindowId(windowSelector.windowId); + } else { + os << '"' << windowSelector.windowName << '"'; + } + + os << ", Found: "; + + if (windowSelector.matchType == xcb_xorg::window_search::MatchType::ID) { + os << formatWindowId(windowSelector.windowId); + } else { + os << '"' << actualWindowName << '"'; + if (windowSelector.matchType == + xcb_xorg::window_search::MatchType::SUBSTRING && + actualWindowName != windowSelector.windowName) + { + os << " (matched substring '" + << windowSelector.windowName << "')"; + } + } + + return os.str(); +} + +} // namespace xcb_window + +// SenseApi functions +static int xcbWindow_initializeInd(void) +{ + if (!smoHooksPtr) + { + throw std::runtime_error(std::string(__func__) + ": SMO hooks " + "pointers not filled in."); + } + + // Try to load libxcbXorg using the search path hook + auto libPath = smoHooksPtr->searchForLibInSmoSearchPaths("libxcbXorg.so"); + g_xcbXorgHandle = dlopen( + libPath.value_or("libxcbXorg.so").c_str(), + RTLD_LAZY); + + if (!g_xcbXorgHandle) + { + throw std::runtime_error("xcbWindow:" + std::string(__func__) + + ": Failed to load libxcbXorg: " + std::string(dlerror())); + } + + // Fill in function pointers from libxcbXorg. + xcbXorgFns.getOrCreateConnection = + reinterpret_cast( + dlsym(g_xcbXorgHandle, "xcb_xorg_get_or_create_connection")); + xcbXorgFns.cleanupConnections = reinterpret_cast( + dlsym(g_xcbXorgHandle, "xcb_xorg_cleanup_connections")); + xcbXorgFns.findWindowById = reinterpret_cast( + dlsym(g_xcbXorgHandle, "xcb_xorg_find_window_by_id")); + xcbXorgFns.findWindowByName = reinterpret_cast( + dlsym(g_xcbXorgHandle, "xcb_xorg_find_window_by_name")); + + if (!xcbXorgFns.getOrCreateConnection || !xcbXorgFns.cleanupConnections + || !xcbXorgFns.findWindowById || !xcbXorgFns.findWindowByName) + { + throw std::runtime_error( + std::string("xcbWindow:") + __func__ + + ": Failed to get required function pointers from libxcbXorg"); + } + + return 0; +} + +static int xcbWindow_finalizeInd(void) +{ + g_attachedWindows.clear(); + + if (g_xcbXorgHandle) + { + dlclose(g_xcbXorgHandle); + g_xcbXorgHandle = nullptr; + xcbXorgFns = { nullptr, nullptr, nullptr, nullptr }; + } + + return 0; +} + +static int xcbWindow_attachDeviceReq(const smo::device::SenseDeviceSpec& desc) +{ + g_attachedWindows.emplace_back( + std::make_unique(desc)); + + std::cout << __func__ << ": Attached X11 window:\n " + << g_attachedWindows.back()->stringify() + << "\n"; + return 0; +} + +static int xcbWindow_detachDeviceReq(const smo::device::SenseDeviceSpec& spec) +{ + auto it = std::find_if(g_attachedWindows.begin(), g_attachedWindows.end(), + [&spec](const std::unique_ptr& window) { + return window->getDeviceSpec() == spec; + } + ); + + if (it != g_attachedWindows.end()) + { + g_attachedWindows.erase(it); + std::cout << __func__ << ": Detached X11 window device\n"; + return 0; + } + + std::cerr << __func__ << ": Device not found for detachment\n"; + return -1; +} + +// SenseApi descriptor +static smo::sense_api::SenseApiDesc xcbWindowApiDesc = { + .name = "xcb", + .exportedImplexorApis = { { "video-implexor" } }, + .sal_mgmt_libOps = { + .initializeInd = xcbWindow_initializeInd, + .finalizeInd = xcbWindow_finalizeInd, + .attachDeviceReq = xcbWindow_attachDeviceReq, + .detachDeviceReq = xcbWindow_detachDeviceReq + } +}; + +// Exported function +extern "C" smo::sense_api::SMO_GET_SENSE_API_DESC_FN_TYPEDEF + SMO_GET_SENSE_API_DESC_FN_NAME; + +const smo::sense_api::SenseApiDesc& SMO_GET_SENSE_API_DESC_FN_NAME( + const smo::sense_api::SalmanoffCallbacks& callbacks) +{ + smoHooksPtr = &callbacks; + return xcbWindowApiDesc; +} diff --git a/senseApis/xcbWindow/xcbWindow.h b/senseApis/xcbWindow/xcbWindow.h new file mode 100644 index 0000000..fd645a7 --- /dev/null +++ b/senseApis/xcbWindow/xcbWindow.h @@ -0,0 +1,55 @@ +#ifndef XCB_WINDOW_SENSE_API_H +#define XCB_WINDOW_SENSE_API_H + +#include +#include +#include +#include +#include +#include + +namespace xcb_window { + +/** + * @brief Window selector for X11 windows + */ +struct WindowSelector +{ + xcb_xorg::window_search::MatchType matchType; + int display; + int screen; + uint32_t windowId; + std::string windowName; + + std::string stringify() const; +}; + +/** + * @brief Represents an attached X11 window device + */ +class AttachedWindow +{ +public: + AttachedWindow(const smo::device::SenseDeviceSpec& spec); + ~AttachedWindow() = default; + + const smo::device::SenseDeviceSpec& getDeviceSpec() const { return deviceSpec; } + const WindowSelector& getWindowSelector() const { return windowSelector; } + const std::string& getActualWindowName() const { return actualWindowName; } + void* getXcbConnection() const { return xcbConnection; } + std::string stringify() const; + +private: + void parseWindowSelector(const smo::device::SenseDeviceSpec& spec); + int getRequiredParamAsInt(const smo::device::SenseDeviceSpec& spec, + const std::string& paramName); + + smo::device::SenseDeviceSpec deviceSpec; + WindowSelector windowSelector; + std::string actualWindowName; + void* xcbConnection; // Raw pointer to XCB connection from libxcbXorg +}; + +} // namespace xcb_window + +#endif // XCB_WINDOW_SENSE_API_H diff --git a/smocore/senseApis/senseApiManager.cpp b/smocore/senseApis/senseApiManager.cpp index f3f3384..056991e 100644 --- a/smocore/senseApis/senseApiManager.cpp +++ b/smocore/senseApis/senseApiManager.cpp @@ -13,15 +13,6 @@ namespace fs = std::filesystem; namespace smo { namespace sense_api { -/* Hooks to be provided to senseApiLibs, enabling them to call into Salmanoff - * code. - */ -static SalmanoffCallbacks salmanoffCallbacks = -{ - .searchForLibInSmoSearchPaths = &SenseApiManager - ::searchForLibInSmoSearchPaths -}; - /** * @brief Searches for a library in predefined locations * @param libraryPath The name or path of the library to find @@ -75,6 +66,14 @@ static std::optional searchForLibInSmoSearchPaths( return std::nullopt; } +/* Hooks to be provided to senseApiLibs, enabling them to call into Salmanoff + * code. + */ +static SalmanoffCallbacks salmanoffCallbacks = +{ + .searchForLibInSmoSearchPaths = searchForLibInSmoSearchPaths +}; + std::optional SenseApiManager::searchForLibInSmoSearchPaths( const std::string& libraryPath) {