#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; }