Files
salmanoff/stimBuffApis/xcbWindow/xcbWindow.cpp
T
hayodea 3e19d39853 SenseApiDesc,xcbWindow: port to sscl coro framework
SenseApiDesc's exported API now uses coro pointers instead of
CPS fn pointers.
* Do not build this version of SMO with the Livox drivers enabled,
  because SMO has been changed at the smocore level to use coros
  when calling into stimbuffAPI libs. But the Livox drivers
  haven't yet been ported from CPS to coros.

xcbWindow has been ported to expose coros to SMO in its
senseApiDesc exported iface.
2026-05-25 08:58:36 -04:00

350 lines
11 KiB
C++

#include <iostream>
#include <algorithm>
#include <stdexcept>
#include <memory>
#include <vector>
#include <sstream>
#include <dlfcn.h>
#include <xcb/xcb.h>
#include <user/senseApiDesc.h>
#include <user/deviceAttachmentSpec.h>
#include <spinscale/co/invokers.h>
#include <xcbXorg/xcbXorg.h>
#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<std::unique_ptr<xcb_window::AttachedWindow>>
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<smo::device::DeviceAttachmentSpec>& 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<xcb_xorg::XcbConnection> 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 sscl::co::ViralNonPostingInvoker<int> xcbWindow_initializeCInd(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<get_or_create_connection_fn*>(
dlsym(xcbXorg.dlopenHandle, "xcb_xorg_get_or_create_connection"));
xcbXorg.fns.cleanupConnections = reinterpret_cast<cleanup_connections_fn*>(
dlsym(xcbXorg.dlopenHandle, "xcb_xorg_cleanup_connections"));
xcbXorg.fns.findWindowById = reinterpret_cast<find_window_by_id_fn*>(
dlsym(xcbXorg.dlopenHandle, "xcb_xorg_find_window_by_id"));
xcbXorg.fns.findWindowByName = reinterpret_cast<find_window_by_name_fn*>(
dlsym(xcbXorg.dlopenHandle, "xcb_xorg_find_window_by_name"));
xcbXorg.fns.dereferenceConnection =
reinterpret_cast<dereference_connection_fn*>(
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");
}
co_return 0;
}
static sscl::co::ViralNonPostingInvoker<int> xcbWindow_finalizeCInd(void)
{
g_attachedWindows.clear();
if (xcbXorg.dlopenHandle)
{
dlclose(xcbXorg.dlopenHandle);
xcbXorg.dlopenHandle = nullptr;
xcbXorg.fns = { nullptr, nullptr, nullptr, nullptr, nullptr };
}
co_return 0;
}
static sscl::co::ViralNonPostingInvoker<smo::stim_buff::StimBuffDeviceOpResult>
xcbWindow_attachDeviceCReq(
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
const std::shared_ptr<sscl::ComponentThread>& componentThread)
{
// Not used yet, but may be used later.
(void)componentThread;
try {
g_attachedWindows.emplace_back(
std::make_unique<xcb_window::AttachedWindow>(desc));
} catch (const std::exception& exc) {
std::cerr << __func__ << ": Exception while attaching X11 window: "
<< exc.what() << "\n";
co_return smo::stim_buff::StimBuffDeviceOpResult{false, desc};
}
std::cout << __func__ << ": Attached X11 window:\n "
<< g_attachedWindows.back()->stringify()
<< "\n";
co_return smo::stim_buff::StimBuffDeviceOpResult{true, desc};
}
static sscl::co::ViralNonPostingInvoker<smo::stim_buff::StimBuffDeviceOpResult>
xcbWindow_detachDeviceCReq(
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec)
{
auto it = std::find_if(g_attachedWindows.begin(), g_attachedWindows.end(),
[&spec](const std::unique_ptr<xcb_window::AttachedWindow>& window) {
return window->getDeviceAttachmentSpec() == spec;
}
);
if (it == g_attachedWindows.end())
{
std::cerr << __func__ << ": Device not found for detachment:\n"
<< spec->stringify() << "\n";
co_return smo::stim_buff::StimBuffDeviceOpResult{false, spec};
}
g_attachedWindows.erase(it);
std::cout << __func__ << ": Detached X11 window device:\n"
<< spec->stringify() << "\n";
co_return smo::stim_buff::StimBuffDeviceOpResult{true, spec};
}
// SenseApi descriptor
static smo::stim_buff::StimBuffApiDesc xcbWindowApiDesc = {
.name = "xcb",
.exportedQualeIfaceApis = { { "visual-qualeiface" } },
.sal_mgmt_libOps = {
.initializeCInd = xcbWindow_initializeCInd,
.finalizeCInd = xcbWindow_finalizeCInd,
.attachDeviceCReq = xcbWindow_attachDeviceCReq,
.detachDeviceCReq = xcbWindow_detachDeviceCReq
}
};
// 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;
}