Rename: Sense API => Stim Buff API
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
add_subdirectory(xcbWindow)
|
||||
add_subdirectory(livoxGen1)
|
||||
@@ -0,0 +1,23 @@
|
||||
cmake_dependent_option(ENABLE_STIMBUFFAPI_livoxGen1
|
||||
"Enable Livox Gen1 LiDAR stim buff API" ON
|
||||
"ENABLE_LIB_livoxProto1" OFF)
|
||||
|
||||
if(ENABLE_STIMBUFFAPI_livoxGen1)
|
||||
# Set CONFIG variable for config.h
|
||||
set(CONFIG_STIMBUFFAPI_LIVOXGEN1_ENABLED 1)
|
||||
|
||||
add_library(livoxGen1 SHARED
|
||||
livoxGen1.cpp
|
||||
)
|
||||
|
||||
target_include_directories(livoxGen1 PUBLIC
|
||||
${Boost_INCLUDE_DIRS}
|
||||
${CMAKE_SOURCE_DIR}/commonLibs
|
||||
)
|
||||
target_link_libraries(livoxGen1
|
||||
${Boost_LIBRARIES}
|
||||
)
|
||||
|
||||
# Install rules
|
||||
install(TARGETS livoxGen1 DESTINATION lib)
|
||||
endif()
|
||||
@@ -0,0 +1,416 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <dlfcn.h>
|
||||
#include <opts.h>
|
||||
#include <user/senseApiDesc.h>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <callback.h>
|
||||
#include <livoxProto1/livoxProto1.h>
|
||||
#include <livoxProto1/device.h>
|
||||
#include <asynchronousContinuation.h>
|
||||
|
||||
|
||||
namespace smo {
|
||||
namespace stim_buff {
|
||||
|
||||
// Salmanoff hooks, obtained from SMO_GET_STIM_BUFF_API_DESC_FN_NAME().
|
||||
static const SmoCallbacks* smoHooksPtr = nullptr;
|
||||
static SmoThreadingModelDesc smoThreadingModelDesc;
|
||||
|
||||
// LivoxProto1 library state
|
||||
struct LivoxProto1DllState
|
||||
{
|
||||
LivoxProto1DllState()
|
||||
: dlopenHandle(nullptr, DlCloser),
|
||||
livoxProto1_main(nullptr),
|
||||
livoxProto1_exit(nullptr),
|
||||
livoxProto1_getOrCreateDeviceReq(nullptr),
|
||||
livoxProto1_destroyDeviceReq(nullptr)
|
||||
{}
|
||||
|
||||
static void DlCloser(void* handle)
|
||||
{
|
||||
if (handle) {
|
||||
dlclose(handle);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<void, void(*)(void*)> dlopenHandle;
|
||||
livoxProto1_mainFn *livoxProto1_main;
|
||||
livoxProto1_exitFn *livoxProto1_exit;
|
||||
livoxProto1_getOrCreateDeviceReqFn *livoxProto1_getOrCreateDeviceReq;
|
||||
livoxProto1_destroyDeviceReqFn *livoxProto1_destroyDeviceReq;
|
||||
};
|
||||
|
||||
static LivoxProto1DllState livoxProto1;
|
||||
|
||||
// Attached Livox devices
|
||||
static std::vector<std::shared_ptr<livoxProto1::Device>> g_attachedDevices;
|
||||
|
||||
// Continuation classes for async operations
|
||||
class AttachDeviceReq
|
||||
: public smo::NonPostedAsynchronousContinuation<sal_mlo_attachDeviceReqCbFn>
|
||||
{
|
||||
public:
|
||||
AttachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
|
||||
smo::Callback<sal_mlo_attachDeviceReqCbFn> cb)
|
||||
: smo::NonPostedAsynchronousContinuation<sal_mlo_attachDeviceReqCbFn>(
|
||||
std::move(cb)),
|
||||
spec(spec)
|
||||
{}
|
||||
|
||||
public:
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec> spec;
|
||||
|
||||
public:
|
||||
void attachDeviceReq1(
|
||||
std::shared_ptr<AttachDeviceReq> context,
|
||||
bool success, std::shared_ptr<livoxProto1::Device> dev)
|
||||
{
|
||||
if (!dev)
|
||||
{
|
||||
std::cerr << __func__ << ": Failed to create Livox device: "
|
||||
<< context->spec->deviceSelector << std::endl;
|
||||
context->callOriginalCb(false, context->spec);
|
||||
return;
|
||||
}
|
||||
|
||||
g_attachedDevices.push_back(dev);
|
||||
if (1 || OptionParser::getOptions().verbose)
|
||||
{
|
||||
std::cout << __func__ << ": Successfully attached Livox "
|
||||
"device: " << context->spec->deviceSelector << " (ID: "
|
||||
<< context->spec->deviceIdentifier << ")\n";
|
||||
}
|
||||
|
||||
context->callOriginalCb(success, context->spec);
|
||||
}
|
||||
};
|
||||
|
||||
class DetachDeviceReq
|
||||
: public smo::NonPostedAsynchronousContinuation<sal_mlo_detachDeviceReqCbFn>
|
||||
{
|
||||
public:
|
||||
DetachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec,
|
||||
smo::Callback<sal_mlo_detachDeviceReqCbFn> cb)
|
||||
: smo::NonPostedAsynchronousContinuation<sal_mlo_detachDeviceReqCbFn>(
|
||||
std::move(cb)),
|
||||
spec(spec)
|
||||
{}
|
||||
|
||||
public:
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec> spec;
|
||||
|
||||
public:
|
||||
void detachDeviceReq1(
|
||||
std::shared_ptr<DetachDeviceReq> context,
|
||||
bool success)
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
std::cerr << __func__ << ": Failed to destroy Livox device: "
|
||||
<< context->spec->deviceIdentifier << "\n";
|
||||
context->callOriginalCb(false, context->spec);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the device in g_attachedDevices and remove it.
|
||||
auto eraseIt = std::find_if(
|
||||
g_attachedDevices.begin(), g_attachedDevices.end(),
|
||||
[context](const std::shared_ptr<livoxProto1::Device>& dev)
|
||||
{
|
||||
const std::string& devId = dev->discoveredDevice.deviceIdentifier;
|
||||
std::string devIdPrefix = devId.substr(
|
||||
0, std::min<size_t>(14, devId.size()));
|
||||
return devIdPrefix == context->spec->deviceSelector.substr(
|
||||
0, std::min<size_t>(14, context->spec->deviceSelector.size()));
|
||||
}
|
||||
);
|
||||
|
||||
if (eraseIt == g_attachedDevices.end())
|
||||
{
|
||||
std::cerr << __func__ << ": Race condition: device not found "
|
||||
"in g_attachedDevices for detachment: "
|
||||
<< context->spec->deviceIdentifier << "\n";
|
||||
context->callOriginalCb(false, context->spec);
|
||||
return;
|
||||
}
|
||||
|
||||
g_attachedDevices.erase(eraseIt);
|
||||
std::cout << __func__ << ": Successfully detached Livox device: "
|
||||
<< context->spec->deviceIdentifier << "\n";
|
||||
|
||||
context->callOriginalCb(success, context->spec);
|
||||
}
|
||||
};
|
||||
|
||||
// Callback function declarations
|
||||
extern "C" sal_mlo_initializeIndFn livoxGen1_initializeInd;
|
||||
extern "C" sal_mlo_finalizeIndFn livoxGen1_finalizeInd;
|
||||
extern "C" sal_mlo_attachDeviceReqFn livoxGen1_attachDeviceReq;
|
||||
extern "C" sal_mlo_detachDeviceReqFn livoxGen1_detachDeviceReq;
|
||||
|
||||
// Stim Buff API descriptor
|
||||
static const StimBuffApiDesc livoxGen1ApiDesc = {
|
||||
.name = "livoxGen1",
|
||||
.exportedQualeIfaceApis = {
|
||||
{.name = "pcloud"},
|
||||
{.name = "pcloudIntensity"},
|
||||
{.name = "gyro"},
|
||||
{.name = "accel"}
|
||||
},
|
||||
.sal_mgmt_libOps = {
|
||||
.initializeInd = livoxGen1_initializeInd,
|
||||
.finalizeInd = livoxGen1_finalizeInd,
|
||||
.attachDeviceReq = livoxGen1_attachDeviceReq,
|
||||
.detachDeviceReq = livoxGen1_detachDeviceReq
|
||||
}
|
||||
};
|
||||
|
||||
// Callback function implementations
|
||||
extern "C" int livoxGen1_initializeInd(void)
|
||||
{
|
||||
if (!smoHooksPtr)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__) + ": SMO hooks "
|
||||
"pointers not filled in.");
|
||||
}
|
||||
|
||||
// Load LivoxProto1 library
|
||||
auto libPath = smoHooksPtr->searchForLibInSmoSearchPaths(
|
||||
"liblivoxProto1.so");
|
||||
|
||||
livoxProto1.dlopenHandle.reset(dlopen(
|
||||
libPath.value_or("liblivoxProto1.so").c_str(), RTLD_LAZY));
|
||||
|
||||
if (!livoxProto1.dlopenHandle)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": Failed to load LivoxProto1 library: " +
|
||||
(dlerror() ? dlerror() : "unknown error"));
|
||||
}
|
||||
|
||||
// Get LivoxProto1 library functions
|
||||
livoxProto1.livoxProto1_main = reinterpret_cast<livoxProto1_mainFn *>(
|
||||
dlsym(livoxProto1.dlopenHandle.get(), "livoxProto1_main"));
|
||||
livoxProto1.livoxProto1_exit = reinterpret_cast<livoxProto1_exitFn *>(
|
||||
dlsym(livoxProto1.dlopenHandle.get(), "livoxProto1_exit"));
|
||||
livoxProto1.livoxProto1_getOrCreateDeviceReq = reinterpret_cast<
|
||||
livoxProto1_getOrCreateDeviceReqFn *>(
|
||||
dlsym(
|
||||
livoxProto1.dlopenHandle.get(),
|
||||
"livoxProto1_getOrCreateDeviceReq"));
|
||||
livoxProto1.livoxProto1_destroyDeviceReq = reinterpret_cast<
|
||||
livoxProto1_destroyDeviceReqFn *>(
|
||||
dlsym(
|
||||
livoxProto1.dlopenHandle.get(),
|
||||
"livoxProto1_destroyDeviceReq"));
|
||||
|
||||
if (!livoxProto1.livoxProto1_main || !livoxProto1.livoxProto1_exit
|
||||
|| !livoxProto1.livoxProto1_getOrCreateDeviceReq
|
||||
|| !livoxProto1.livoxProto1_destroyDeviceReq)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) +
|
||||
": Failed to get LivoxProto1 library functions");
|
||||
}
|
||||
|
||||
// Call LivoxProto1 library main function
|
||||
(*livoxProto1.livoxProto1_main)(
|
||||
smoThreadingModelDesc.componentThread, *smoHooksPtr);
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
extern "C" int livoxGen1_finalizeInd(void)
|
||||
{
|
||||
// Clear all attached devices
|
||||
g_attachedDevices.clear();
|
||||
|
||||
// Call LivoxProto1 library exit function
|
||||
if (livoxProto1.livoxProto1_exit) {
|
||||
(*livoxProto1.livoxProto1_exit)();
|
||||
}
|
||||
|
||||
if (livoxProto1.dlopenHandle)
|
||||
{
|
||||
dlclose(livoxProto1.dlopenHandle.get());
|
||||
livoxProto1.dlopenHandle.reset();
|
||||
}
|
||||
|
||||
livoxProto1 = LivoxProto1DllState();
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
extern "C" void livoxGen1_attachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
Callback<smo::stim_buff::sal_mlo_attachDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
if (!livoxProto1.livoxProto1_getOrCreateDeviceReq)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": LivoxProto1 getOrCreateDevice function "
|
||||
"not available");
|
||||
}
|
||||
|
||||
for (const auto& dev : g_attachedDevices)
|
||||
{
|
||||
if (dev->discoveredDevice.deviceIdentifier == desc->deviceIdentifier)
|
||||
{
|
||||
cb.callbackFn(true, desc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse integer parameters from provider params with defaults
|
||||
/* The Livox Avia will generally respond to a handshake request within
|
||||
* 50ms. So we set the handshake timeout to 300ms to be safe.
|
||||
*/
|
||||
int handshakeTimeoutMs = 300; // Default: 50ms
|
||||
/* Based on testing on a Livox Avia, the device will generally resume
|
||||
* sending broadcast advertisement dgrams after about 5 seconds at most.
|
||||
* Generally, it will resume sending them within 1-2 seconds.
|
||||
*/
|
||||
int retryDelayMs = 5250; // Default: 500ms
|
||||
uint8_t smoSubnetNbits = 24; // Default: /24 subnet
|
||||
uint16_t dataPort = 56000; // Default data port
|
||||
uint16_t cmdPort = 56001; // Default command port
|
||||
uint16_t imuPort = 56002; // Default IMU port
|
||||
// Default: empty string (will trigger IP auto-detection)
|
||||
std::string smoIp = "";
|
||||
|
||||
// Parse optional integer parameters from provider params
|
||||
for (const auto& param : desc->providerParams)
|
||||
{
|
||||
if (param.first == "handshake-timeout-ms")
|
||||
{
|
||||
handshakeTimeoutMs = smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "handshake-timeout-ms");
|
||||
} else if (param.first == "retry-delay-ms")
|
||||
{
|
||||
retryDelayMs = smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "retry-delay-ms");
|
||||
} else if (param.first == "smo-subnet-nbits")
|
||||
{
|
||||
smoSubnetNbits = static_cast<uint8_t>(
|
||||
smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "smo-subnet-nbits"));
|
||||
} else if (param.first == "data-port")
|
||||
{
|
||||
dataPort = static_cast<uint16_t>(
|
||||
smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "data-port"));
|
||||
} else if (param.first == "cmd-port")
|
||||
{
|
||||
cmdPort = static_cast<uint16_t>(
|
||||
smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "cmd-port"));
|
||||
} else if (param.first == "imu-port")
|
||||
{
|
||||
imuPort = static_cast<uint16_t>(
|
||||
smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*desc, "imu-port"));
|
||||
} else if (param.first == "smo-ip")
|
||||
{
|
||||
if (param.second.empty())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": smo-ip parameter is empty");
|
||||
}
|
||||
if (param.second.find('.') == std::string::npos ||
|
||||
std::count(param.second.begin(), param.second.end(), '.') != 3)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": smo-ip parameter is not an "
|
||||
"IPv4 address");
|
||||
}
|
||||
smoIp = param.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(
|
||||
std::string(__func__) + ": Unknown provider parameter: "
|
||||
+ param.first);
|
||||
}
|
||||
}
|
||||
|
||||
auto request = std::make_shared<AttachDeviceReq>(desc, cb);
|
||||
|
||||
(*livoxProto1.livoxProto1_getOrCreateDeviceReq)(
|
||||
desc->deviceSelector, // deviceIdentifier (broadcast code)
|
||||
componentThread,
|
||||
handshakeTimeoutMs, retryDelayMs,
|
||||
smoIp, smoSubnetNbits,
|
||||
dataPort, cmdPort, imuPort,
|
||||
{request, std::bind(
|
||||
&AttachDeviceReq::attachDeviceReq1,
|
||||
request.get(), request,
|
||||
std::placeholders::_1, std::placeholders::_2)});
|
||||
}
|
||||
|
||||
extern "C" void livoxGen1_detachDeviceReq(
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>& desc,
|
||||
Callback<smo::stim_buff::sal_mlo_detachDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
// Find and remove the device from our collection
|
||||
auto it = std::find_if(g_attachedDevices.begin(), g_attachedDevices.end(),
|
||||
[&desc](const std::shared_ptr<livoxProto1::Device>& dev) {
|
||||
/** EXPLANATION:
|
||||
* Compare the first 14 characters of the deviceIdentifier with
|
||||
* the first 14 characters of the deviceSelector
|
||||
*/
|
||||
const std::string& devId = dev->discoveredDevice.deviceIdentifier;
|
||||
std::string devIdPrefix = devId.substr(
|
||||
0, std::min<size_t>(14, devId.size()));
|
||||
|
||||
return devIdPrefix == desc->deviceSelector.substr(
|
||||
0, std::min<size_t>(14, desc->deviceSelector.size()));
|
||||
}
|
||||
);
|
||||
|
||||
if (it == g_attachedDevices.end())
|
||||
{
|
||||
std::cerr << std::string(__func__)
|
||||
<< ": Device not found for detachment: "
|
||||
<< desc->deviceIdentifier << std::endl;
|
||||
cb.callbackFn(false, desc);
|
||||
return;
|
||||
}
|
||||
|
||||
auto request = std::make_shared<DetachDeviceReq>(desc, cb);
|
||||
|
||||
(*livoxProto1.livoxProto1_destroyDeviceReq)(
|
||||
*it,
|
||||
{request, std::bind(
|
||||
&DetachDeviceReq::detachDeviceReq1,
|
||||
request.get(), request,
|
||||
std::placeholders::_1)});
|
||||
}
|
||||
|
||||
// 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 livoxGen1ApiDesc;
|
||||
}
|
||||
|
||||
} // namespace stim_buff
|
||||
} // namespace smo
|
||||
@@ -0,0 +1,27 @@
|
||||
# XCB/Xorg Window Attaching StimBuffAPI backend
|
||||
cmake_dependent_option(ENABLE_STIMBUFFAPI_xcbWindow
|
||||
"Enable XCB/Xorg Window Attaching StimBuffAPI backend" ON
|
||||
"ENABLE_LIB_xcbXorg" OFF)
|
||||
|
||||
if(ENABLE_STIMBUFFAPI_xcbWindow)
|
||||
# Set CONFIG variable for config.h
|
||||
set(CONFIG_STIMBUFFAPI_XCBWINDOW_ENABLED 1)
|
||||
|
||||
add_library(xcbWindow SHARED
|
||||
xcbWindow.cpp
|
||||
)
|
||||
|
||||
target_include_directories(xcbWindow PUBLIC
|
||||
${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(xcbWindow ${XCB_LIBRARIES})
|
||||
|
||||
# Install rules
|
||||
install(TARGETS xcbWindow DESTINATION lib)
|
||||
endif()
|
||||
@@ -0,0 +1,353 @@
|
||||
#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 <callback.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, "display");
|
||||
windowSelector.screen = smo::device::DeviceAttachmentSpec
|
||||
::parseRequiredParamAsInt(*spec, "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 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<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");
|
||||
}
|
||||
|
||||
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<smo::device::DeviceAttachmentSpec>& desc,
|
||||
const std::shared_ptr<smo::ComponentThread>& componentThread,
|
||||
smo::Callback<smo::stim_buff::sal_mlo_attachDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
// 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";
|
||||
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<smo::device::DeviceAttachmentSpec>& spec,
|
||||
smo::Callback<smo::stim_buff::sal_mlo_detachDeviceReqCbFn> cb
|
||||
)
|
||||
{
|
||||
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";
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#ifndef XCB_WINDOW_SENSE_API_H
|
||||
#define XCB_WINDOW_SENSE_API_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <user/senseApiDesc.h>
|
||||
#include <user/deviceAttachmentSpec.h>
|
||||
#include <xcbXorg/xcbXorg.h>
|
||||
|
||||
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 std::shared_ptr<smo::device::DeviceAttachmentSpec>& spec);
|
||||
~AttachedWindow();
|
||||
|
||||
const std::shared_ptr<smo::device::DeviceAttachmentSpec>&
|
||||
getDeviceAttachmentSpec() const
|
||||
{ return deviceAttachmentSpec; }
|
||||
const WindowSelector& getWindowSelector() const { return windowSelector; }
|
||||
const std::string& getActualWindowName() const { return actualWindowName; }
|
||||
void* getXcbConnection() const { return xcbConnectionShared.get(); }
|
||||
std::string stringify() const;
|
||||
|
||||
private:
|
||||
void parseWindowSelector(const smo::device::DeviceAttachmentSpec& spec);
|
||||
|
||||
std::shared_ptr<smo::device::DeviceAttachmentSpec> deviceAttachmentSpec;
|
||||
WindowSelector windowSelector;
|
||||
std::string actualWindowName;
|
||||
std::shared_ptr<xcb_xorg::XcbConnection> xcbConnectionShared;
|
||||
};
|
||||
|
||||
} // namespace xcb_window
|
||||
|
||||
#endif // XCB_WINDOW_SENSE_API_H
|
||||
Reference in New Issue
Block a user