#include #include #include #include #include #include #include #include #include #include #include #include #include "xcbWindow.h" // Function pointers to API entry points exported by libxcbXorg. struct XcbXorgDllState { struct XcbXorgFunctions { get_or_create_connection_fn* getOrCreateConnection = nullptr; cleanup_connections_fn* cleanupConnections = nullptr; dereference_connection_fn* dereferenceConnection = nullptr; find_window_by_id_fn* findWindowById = nullptr; find_window_by_name_fn* findWindowByName = nullptr; } fns; void* dlopenHandle = nullptr; }; static XcbXorgDllState xcbXorg; // Salmanoff hooks, obtained from SMO_GET_STIM_BUFF_API_DESC_FN_NAME(). static const smo::stim_buff::SmoCallbacks* smoHooksPtr = nullptr; static smo::stim_buff::SmoThreadingModelDesc smoThreadingModelDesc; // 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 std::shared_ptr& spec ) : deviceAttachmentSpec(spec) { // Validate required function pointers are available if (!xcbXorg.fns.getOrCreateConnection || !xcbXorg.fns.findWindowById || !xcbXorg.fns.findWindowByName || !xcbXorg.fns.dereferenceConnection) { throw std::runtime_error("xcbWindow:" + std::string(__func__) + ": Required xcbXorg function pointers not available"); } windowSelector.display = smo::device::DeviceAttachmentSpec ::parseRequiredParamAsInt(spec->providerParams, "display"); windowSelector.screen = smo::device::DeviceAttachmentSpec ::parseRequiredParamAsInt(spec->providerParams, "screen"); parseWindowSelector(*spec); // Get connection from libxcbXorg std::shared_ptr conn = (*xcbXorg.fns.getOrCreateConnection)( windowSelector.display, windowSelector.screen); xcbConnectionShared = conn; // 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 = (*xcbXorg.fns.findWindowById)( conn.get(), screen->root, windowSelector.windowId); } else { foundWindow = (*xcbXorg.fns.findWindowByName)( conn.get(), 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::DeviceAttachmentSpec& 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.stimBuffApiParams) { 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; } } 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(); } AttachedWindow::~AttachedWindow() { if (xcbConnectionShared && xcbXorg.fns.dereferenceConnection) { (*xcbXorg.fns.dereferenceConnection)(xcbConnectionShared); } } } // 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"); xcbXorg.dlopenHandle = dlopen( libPath.value_or("libxcbXorg.so").c_str(), RTLD_LAZY); if (!xcbXorg.dlopenHandle) { throw std::runtime_error("xcbWindow:" + std::string(__func__) + ": Failed to load libxcbXorg: " + std::string(dlerror())); } // Fill in function pointers from libxcbXorg. xcbXorg.fns.getOrCreateConnection = reinterpret_cast( dlsym(xcbXorg.dlopenHandle, "xcb_xorg_get_or_create_connection")); xcbXorg.fns.cleanupConnections = reinterpret_cast( dlsym(xcbXorg.dlopenHandle, "xcb_xorg_cleanup_connections")); xcbXorg.fns.findWindowById = reinterpret_cast( dlsym(xcbXorg.dlopenHandle, "xcb_xorg_find_window_by_id")); xcbXorg.fns.findWindowByName = reinterpret_cast( dlsym(xcbXorg.dlopenHandle, "xcb_xorg_find_window_by_name")); xcbXorg.fns.dereferenceConnection = reinterpret_cast( dlsym(xcbXorg.dlopenHandle, "xcb_xorg_dereference_connection")); if (!xcbXorg.fns.getOrCreateConnection || !xcbXorg.fns.cleanupConnections || !xcbXorg.fns.findWindowById || !xcbXorg.fns.findWindowByName || !xcbXorg.fns.dereferenceConnection) { 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 (xcbXorg.dlopenHandle) { dlclose(xcbXorg.dlopenHandle); xcbXorg.dlopenHandle = nullptr; xcbXorg.fns = { nullptr, nullptr, nullptr, nullptr, nullptr }; } return 0; } static void xcbWindow_attachDeviceReq( const std::shared_ptr& desc, const std::shared_ptr& componentThread, smo::Callback cb ) { // Not used yet, but may be used later. (void)componentThread; try { g_attachedWindows.emplace_back( std::make_unique(desc)); } catch (const std::exception& exc) { std::cerr << __func__ << ": Exception while attaching X11 window: " << exc.what() << "\n"; cb.callbackFn(false, desc); return; } std::cout << __func__ << ": Attached X11 window:\n " << g_attachedWindows.back()->stringify() << "\n"; cb.callbackFn(true, desc); } static void xcbWindow_detachDeviceReq( const std::shared_ptr& spec, smo::Callback cb ) { auto it = std::find_if(g_attachedWindows.begin(), g_attachedWindows.end(), [&spec](const std::unique_ptr& window) { return window->getDeviceAttachmentSpec() == spec; } ); if (it == g_attachedWindows.end()) { std::cerr << __func__ << ": Device not found for detachment:\n" << spec->stringify() << "\n"; cb.callbackFn(false, spec); return; } g_attachedWindows.erase(it); std::cout << __func__ << ": Detached X11 window device:\n" << spec->stringify() << "\n"; cb.callbackFn(true, spec); } // SenseApi descriptor static smo::stim_buff::StimBuffApiDesc xcbWindowApiDesc = { .name = "xcb", .exportedQualeIfaceApis = { { "visual-qualeiface" } }, .sal_mgmt_libOps = { .initializeInd = xcbWindow_initializeInd, .finalizeInd = xcbWindow_finalizeInd, .attachDeviceReq = xcbWindow_attachDeviceReq, .detachDeviceReq = xcbWindow_detachDeviceReq } }; // Exported function extern "C" smo::stim_buff::SMO_GET_STIM_BUFF_API_DESC_FN_TYPEDEF SMO_GET_STIM_BUFF_API_DESC_FN_NAME; const smo::stim_buff::StimBuffApiDesc& SMO_GET_STIM_BUFF_API_DESC_FN_NAME( const smo::stim_buff::SmoCallbacks& callbacks, const smo::stim_buff::SmoThreadingModelDesc& threadingModel) { smoHooksPtr = &callbacks; smoThreadingModelDesc = threadingModel; return xcbWindowApiDesc; }