Split xcbXorg into xcbXorg and xcbWindow
This commit is contained in:
+121
-415
@@ -6,329 +6,91 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <user/senseDeviceSpec.h>
|
|
||||||
#include <user/senseApiDesc.h>
|
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
#include "xcbXorg.h"
|
#include "xcbXorg.h"
|
||||||
|
|
||||||
/**
|
namespace xcb_xorg {
|
||||||
* @brief Manages X server connections using XCB
|
|
||||||
*
|
// Static member initialization
|
||||||
* This struct manages connections to the X server using the XCB library. It
|
std::map<ConnectionIdentifier, std::shared_ptr<XcbConnection>>
|
||||||
* ensures that each unique display and screen combination has a single
|
ConnectionManager::connections;
|
||||||
* connection, and provides RAII management for these connections.
|
|
||||||
*/
|
std::string ConnectionIdentifier::stringify() const
|
||||||
struct XcbConnection
|
|
||||||
{
|
{
|
||||||
struct XConnectionIdentifier
|
std::ostringstream os;
|
||||||
{
|
os << "display=" << display << ", screen=" << screen;
|
||||||
int display;
|
return os.str();
|
||||||
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<XcbConnection> conn;
|
|
||||||
bool committed = false;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ConnectionGuard(std::shared_ptr<XcbConnection> 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<xcb_connection_t, decltype(&xcb_disconnect)> connection;
|
|
||||||
XConnectionIdentifier connectionIdentifier;
|
|
||||||
std::atomic<int> refCount;
|
|
||||||
|
|
||||||
public:
|
|
||||||
static std::map<XConnectionIdentifier, std::shared_ptr<XcbConnection>>
|
|
||||||
connections;
|
|
||||||
|
|
||||||
static std::shared_ptr<XcbConnection> getOrCreateConnection(
|
|
||||||
const XConnectionIdentifier& id)
|
|
||||||
{
|
|
||||||
auto it = connections.find(id);
|
|
||||||
if (it != connections.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto conn = std::make_shared<XcbConnection>(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
XcbConnection::XcbConnection(const ConnectionIdentifier& id)
|
||||||
* @brief Represents an attached device and its associated window
|
: connection(nullptr, &xcb_disconnect),
|
||||||
*
|
connectionIdentifier(id), refCount(0)
|
||||||
* 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
|
|
||||||
{
|
{
|
||||||
public:
|
// Convert to X display string format (e.g., ":0.1")
|
||||||
struct WindowSelector
|
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;
|
throw std::runtime_error(
|
||||||
|
std::string(__func__) + ": Failed to connect to X server "
|
||||||
XcbConnection::XConnectionIdentifier xconn;
|
+ connectionIdentifier.stringify());
|
||||||
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<XcbConnection> 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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
smo::device::SenseDeviceSpec deviceSpec;
|
// Verify we got the screen we asked for
|
||||||
WindowSelector windowSelector;
|
if (screenNum != id.screen)
|
||||||
std::shared_ptr<XcbConnection> connection;
|
|
||||||
std::string actualWindowName;
|
|
||||||
|
|
||||||
public:
|
|
||||||
static int getRequiredParamAsInt(
|
|
||||||
const smo::device::SenseDeviceSpec& spec,
|
|
||||||
const std::string& paramName)
|
|
||||||
{
|
{
|
||||||
auto it = std::find_if(
|
throw std::runtime_error(
|
||||||
spec.providerParams.begin(),
|
std::string(__func__) + ": Connected to wrong screen. "
|
||||||
spec.providerParams.end(),
|
"Requested " + connectionIdentifier.stringify()
|
||||||
[¶mName](const auto& param) {
|
+ " but got screen " + std::to_string(screenNum));
|
||||||
return param.first == paramName;
|
}
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
if (it == spec.providerParams.end())
|
std::shared_ptr<XcbConnection> ConnectionManager::getOrCreateConnection(
|
||||||
{
|
const ConnectionIdentifier& id)
|
||||||
throw std::runtime_error(
|
{
|
||||||
"No " + paramName + " specified in provider params");
|
auto it = connections.find(id);
|
||||||
}
|
if (it != connections.end()) {
|
||||||
|
return it->second;
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void parseWindowSelector(
|
auto conn = std::make_shared<XcbConnection>(id);
|
||||||
const smo::device::SenseDeviceSpec& spec,
|
connections.emplace(id, conn);
|
||||||
WindowSelector& windowSelector)
|
return conn;
|
||||||
{
|
}
|
||||||
// Default match type
|
|
||||||
windowSelector.matchType = xcb_window_search::MatchType::SUBSTRING;
|
|
||||||
|
|
||||||
// Check if 'dev-id', 'dev-string', or 'dev-substring' is specified
|
void ConnectionManager::cleanupAllConnections()
|
||||||
for (const auto& param : spec.apiParams)
|
{
|
||||||
{
|
connections.clear();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowSelector.matchType == xcb_window_search::MatchType::ID)
|
size_t ConnectionManager::getConnectionCount()
|
||||||
{
|
{
|
||||||
try {
|
return connections.size();
|
||||||
windowSelector.window.id = std::stoul(
|
}
|
||||||
spec.deviceSelector, nullptr, 0);
|
|
||||||
} catch (const std::exception&) {
|
namespace window_search {
|
||||||
throw std::runtime_error(
|
|
||||||
"Window selector: 'dev-id' present, but selector is not "
|
struct XcbReplyDeleter {
|
||||||
"numeric");
|
void operator()(xcb_query_tree_reply_t* p) { free(p); }
|
||||||
}
|
void operator()(xcb_get_property_reply_t* p) { free(p); }
|
||||||
}
|
|
||||||
else {
|
|
||||||
windowSelector.window.name = spec.deviceSelector;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define the static member
|
xcb_window_t findById(
|
||||||
std::map<XcbConnection::XConnectionIdentifier, std::shared_ptr<XcbConnection>>
|
|
||||||
XcbConnection::connections;
|
|
||||||
|
|
||||||
static std::vector<AttachedDevice> attachedDevices;
|
|
||||||
|
|
||||||
namespace xcb_window_search {
|
|
||||||
|
|
||||||
static xcb_window_t findById(
|
|
||||||
xcb_connection_t* conn, xcb_window_t root, uint32_t targetId)
|
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);
|
xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, root);
|
||||||
std::unique_ptr<xcb_query_tree_reply_t, XcbReplyDeleter> reply(
|
std::unique_ptr<xcb_query_tree_reply_t, XcbReplyDeleter> reply(
|
||||||
xcb_query_tree_reply(conn, cookie, nullptr));
|
xcb_query_tree_reply(conn, cookie, nullptr));
|
||||||
|
|
||||||
if (!reply) return 0;
|
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());
|
xcb_window_t* children = xcb_query_tree_children(reply.get());
|
||||||
int num_children = xcb_query_tree_children_length(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
|
// Recursively search this child's subtree
|
||||||
if (xcb_window_t result = findById(
|
if (xcb_window_t result = findById(conn, children[i], targetId)) {
|
||||||
conn, children[i], targetId))
|
|
||||||
{
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,7 +109,7 @@ static xcb_window_t findById(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static xcb_window_t findByName(
|
xcb_window_t findByName(
|
||||||
xcb_connection_t* conn, xcb_window_t root,
|
xcb_connection_t* conn, xcb_window_t root,
|
||||||
const std::string& targetName, std::string& outWindowName,
|
const std::string& targetName, std::string& outWindowName,
|
||||||
MatchType matchType)
|
MatchType matchType)
|
||||||
@@ -357,6 +117,7 @@ static xcb_window_t findByName(
|
|||||||
xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, root);
|
xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, root);
|
||||||
std::unique_ptr<xcb_query_tree_reply_t, XcbReplyDeleter> reply(
|
std::unique_ptr<xcb_query_tree_reply_t, XcbReplyDeleter> reply(
|
||||||
xcb_query_tree_reply(conn, cookie, nullptr));
|
xcb_query_tree_reply(conn, cookie, nullptr));
|
||||||
|
|
||||||
if (!reply) return 0;
|
if (!reply) return 0;
|
||||||
|
|
||||||
// First check current window
|
// First check current window
|
||||||
@@ -426,133 +187,78 @@ static xcb_window_t findByName(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace xcb_window_search
|
} // namespace window_search
|
||||||
|
|
||||||
static smo::sense_api::sal_mlo_initializeIndFn xcbXorg_initializeInd;
|
} // namespace xcb_xorg
|
||||||
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;
|
|
||||||
|
|
||||||
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::XcbConnection> xcb_xorg_get_or_create_connection(
|
||||||
|
int display, int screen)
|
||||||
{
|
{
|
||||||
.name = "xcb",
|
xcb_xorg::ConnectionIdentifier id{display, screen};
|
||||||
.exportedImplexorApis = { { "video-implexor" } },
|
auto conn = xcb_xorg::ConnectionManager::getOrCreateConnection(id);
|
||||||
.sal_mgmt_libOps = {
|
conn->incrementRefCount();
|
||||||
.initializeInd = xcbXorg_initializeInd,
|
return conn;
|
||||||
.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.
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the Sense API Descriptor
|
* @brief Clean up all connections
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
extern SMO_UNMANGLED smo::sense_api::SMO_GET_SENSE_API_DESC_FN_TYPEDEF
|
extern "C" cleanup_connections_fn xcb_xorg_cleanup_connections;
|
||||||
SMO_GET_SENSE_API_DESC_FN_NAME;
|
void xcb_xorg_cleanup_connections()
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
xcbXorg_callbacks = &callbacks;
|
xcb_xorg::ConnectionManager::cleanupAllConnections();
|
||||||
return xcbXorgApiDesc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
if (!conn) return 0;
|
||||||
return 0;
|
auto connection = static_cast<xcb_xorg::XcbConnection*>(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.
|
if (!conn) return 0;
|
||||||
XcbConnection::XConnectionIdentifier id{
|
auto connection = static_cast<xcb_xorg::XcbConnection*>(conn);
|
||||||
AttachedDevice::getRequiredParamAsInt(desc, "display"),
|
return xcb_xorg::window_search::findByName(
|
||||||
AttachedDevice::getRequiredParamAsInt(desc, "screen")
|
connection->getConnection(), root, targetName, outWindowName,
|
||||||
};
|
matchType);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+166
-12
@@ -1,22 +1,176 @@
|
|||||||
#ifndef X11_XCB_API_H
|
#ifndef XCB_XORG_API_H
|
||||||
#define X11_XCB_API_H
|
#define XCB_XORG_API_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <user/senseApiDesc.h>
|
#include <atomic>
|
||||||
|
#include <map>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
class Xcb_XorgApi
|
namespace xcb_xorg {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Connection identifier for X server connections
|
||||||
|
*/
|
||||||
|
struct ConnectionIdentifier
|
||||||
{
|
{
|
||||||
public:
|
int display;
|
||||||
Xcb_XorgApi(const std::string& _displayName)
|
int screen;
|
||||||
: displayName(_displayName)
|
|
||||||
{}
|
|
||||||
|
|
||||||
~Xcb_XorgApi() = default;
|
bool operator<(const ConnectionIdentifier& other) const
|
||||||
|
{
|
||||||
|
if (display != other.display) return display < other.display;
|
||||||
|
return screen < other.screen;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
std::string stringify() const;
|
||||||
std::string displayName;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#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<xcb_connection_t, decltype(&xcb_disconnect)> connection;
|
||||||
|
ConnectionIdentifier connectionIdentifier;
|
||||||
|
std::atomic<int> 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<XcbConnection> 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<ConnectionIdentifier, std::shared_ptr<XcbConnection>> 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<xcb_xorg::XcbConnection> 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
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
# XCB/Xorg Window Attaching SenseAPI backend
|
# XCB/Xorg Window Attaching SenseAPI backend
|
||||||
cmake_dependent_option(ENABLE_SENSEAPI_xcbWindow
|
cmake_dependent_option(ENABLE_SENSEAPI_xcbWindow
|
||||||
"Enable XCB/Xorg Window Attaching SenseAPI backend" OFF
|
"Enable XCB/Xorg Window Attaching SenseAPI backend" ON
|
||||||
"ENABLE_LIB_xcbXorg" OFF)
|
"ENABLE_LIB_xcbXorg" ON)
|
||||||
|
|
||||||
if(ENABLE_SENSEAPI_xcbWindow)
|
if(ENABLE_SENSEAPI_xcbWindow)
|
||||||
# For now, just create a placeholder library
|
add_library(senseApiXcbWindow SHARED
|
||||||
add_library(senseApiXcbWindow STATIC
|
xcbWindow.cpp
|
||||||
# xcbWindow.cpp would go here
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(senseApiXcbWindow
|
|
||||||
xcbXorg
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(senseApiXcbWindow PUBLIC
|
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
|
# Set config define for header generation
|
||||||
add_compile_definitions(CONFIG_SENSEAPI_XCBWINDOW_ENABLED)
|
add_compile_definitions(CONFIG_SENSEAPI_XCBWINDOW_ENABLED)
|
||||||
|
|
||||||
|
# Install rules
|
||||||
|
install(TARGETS senseApiXcbWindow DESTINATION lib)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -0,0 +1,330 @@
|
|||||||
|
#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/senseDeviceSpec.h>
|
||||||
|
#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<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 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<xcb_xorg::XcbConnection> 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<get_or_create_connection_fn*>(
|
||||||
|
dlsym(g_xcbXorgHandle, "xcb_xorg_get_or_create_connection"));
|
||||||
|
xcbXorgFns.cleanupConnections = reinterpret_cast<cleanup_connections_fn*>(
|
||||||
|
dlsym(g_xcbXorgHandle, "xcb_xorg_cleanup_connections"));
|
||||||
|
xcbXorgFns.findWindowById = reinterpret_cast<find_window_by_id_fn*>(
|
||||||
|
dlsym(g_xcbXorgHandle, "xcb_xorg_find_window_by_id"));
|
||||||
|
xcbXorgFns.findWindowByName = reinterpret_cast<find_window_by_name_fn*>(
|
||||||
|
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<xcb_window::AttachedWindow>(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<xcb_window::AttachedWindow>& 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef XCB_WINDOW_SENSE_API_H
|
||||||
|
#define XCB_WINDOW_SENSE_API_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <user/senseApiDesc.h>
|
||||||
|
#include <user/senseDeviceSpec.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 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
|
||||||
@@ -13,15 +13,6 @@ namespace fs = std::filesystem;
|
|||||||
namespace smo {
|
namespace smo {
|
||||||
namespace sense_api {
|
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
|
* @brief Searches for a library in predefined locations
|
||||||
* @param libraryPath The name or path of the library to find
|
* @param libraryPath The name or path of the library to find
|
||||||
@@ -75,6 +66,14 @@ static std::optional<std::string> searchForLibInSmoSearchPaths(
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hooks to be provided to senseApiLibs, enabling them to call into Salmanoff
|
||||||
|
* code.
|
||||||
|
*/
|
||||||
|
static SalmanoffCallbacks salmanoffCallbacks =
|
||||||
|
{
|
||||||
|
.searchForLibInSmoSearchPaths = searchForLibInSmoSearchPaths
|
||||||
|
};
|
||||||
|
|
||||||
std::optional<std::string> SenseApiManager::searchForLibInSmoSearchPaths(
|
std::optional<std::string> SenseApiManager::searchForLibInSmoSearchPaths(
|
||||||
const std::string& libraryPath)
|
const std::string& libraryPath)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user