2025-01-08 17:16:49 -04:00
|
|
|
#include <iostream>
|
|
|
|
|
#include <algorithm>
|
2025-01-08 18:00:07 -04:00
|
|
|
#include <stdexcept>
|
2025-01-12 14:31:33 -04:00
|
|
|
#include <memory>
|
2025-01-13 11:53:38 -04:00
|
|
|
#include <vector>
|
2025-01-14 14:13:56 -04:00
|
|
|
#include <sstream>
|
|
|
|
|
#include <map>
|
|
|
|
|
#include <atomic>
|
2025-01-13 21:57:11 -04:00
|
|
|
#include <user/senseDeviceSpec.h>
|
|
|
|
|
#include <user/senseApiDesc.h>
|
2025-01-12 14:31:33 -04:00
|
|
|
#include <xcb/xcb.h>
|
2025-01-08 17:16:49 -04:00
|
|
|
#include "xcbXorg.h"
|
|
|
|
|
|
2025-01-14 14:13:56 -04:00
|
|
|
// Key for identifying unique X server connections
|
|
|
|
|
struct XcbConnection
|
|
|
|
|
{
|
|
|
|
|
struct XConnectionIdentifier
|
|
|
|
|
{
|
|
|
|
|
int display;
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
XcbConnection(const XConnectionIdentifier& id)
|
2025-01-14 17:01:16 -04:00
|
|
|
: connection(nullptr, &xcb_disconnect),
|
|
|
|
|
connectionIdentifier(id), refCount(0)
|
2025-01-14 14:13:56 -04:00
|
|
|
{
|
2025-01-14 16:50:37 -04:00
|
|
|
// Convert to X display string format (e.g., ":0.1")
|
|
|
|
|
std::string displayString = ":" + std::to_string(id.display)
|
|
|
|
|
+ "." + std::to_string(id.screen);
|
2025-01-14 14:13:56 -04:00
|
|
|
|
2025-01-14 16:50:37 -04:00
|
|
|
int screenNum;
|
2025-01-14 14:13:56 -04:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class AttachedDevice
|
|
|
|
|
{
|
2025-01-13 11:53:38 -04:00
|
|
|
public:
|
2025-01-14 14:13:56 -04:00
|
|
|
struct WindowSelector
|
|
|
|
|
{
|
2025-01-14 16:58:22 -04:00
|
|
|
XcbConnection::XConnectionIdentifier xconn;
|
2025-01-14 14:13:56 -04:00
|
|
|
struct
|
|
|
|
|
{
|
|
|
|
|
uint32_t id;
|
|
|
|
|
std::string name;
|
|
|
|
|
} window;
|
|
|
|
|
bool useWindowId;
|
|
|
|
|
|
|
|
|
|
std::string stringify() const
|
|
|
|
|
{
|
|
|
|
|
std::ostringstream os;
|
2025-01-14 17:01:16 -04:00
|
|
|
|
2025-01-14 16:58:22 -04:00
|
|
|
os << "Display: " << xconn.display
|
2025-01-14 17:01:16 -04:00
|
|
|
<< ", Screen: " << xconn.screen
|
|
|
|
|
<< ", Window: " << (useWindowId
|
|
|
|
|
? std::to_string(window.id)
|
|
|
|
|
: "\"" + window.name + "\"");
|
2025-01-14 14:13:56 -04:00
|
|
|
return os.str();
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-01-13 11:53:38 -04:00
|
|
|
|
2025-01-14 14:13:56 -04:00
|
|
|
AttachedDevice(const hk::device::SenseDeviceSpec &spec,
|
|
|
|
|
std::shared_ptr<XcbConnection> conn)
|
|
|
|
|
: deviceSpec(spec)
|
|
|
|
|
, connection(std::move(conn))
|
|
|
|
|
{
|
2025-01-14 16:58:22 -04:00
|
|
|
windowSelector.xconn.display = getRequiredParamAsInt(spec, "display");
|
|
|
|
|
windowSelector.xconn.screen = getRequiredParamAsInt(spec, "screen");
|
2025-01-14 14:13:56 -04:00
|
|
|
parseWindowSelector(spec.deviceSelector, windowSelector);
|
2025-01-13 11:53:38 -04:00
|
|
|
}
|
|
|
|
|
|
2025-01-13 21:57:11 -04:00
|
|
|
hk::device::SenseDeviceSpec deviceSpec;
|
2025-01-14 14:13:56 -04:00
|
|
|
WindowSelector windowSelector;
|
|
|
|
|
std::shared_ptr<XcbConnection> connection;
|
2025-01-13 11:53:38 -04:00
|
|
|
|
2025-01-14 14:13:56 -04:00
|
|
|
public:
|
|
|
|
|
static int getRequiredParamAsInt(
|
|
|
|
|
const hk::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;
|
|
|
|
|
}
|
|
|
|
|
);
|
2025-01-12 14:31:33 -04:00
|
|
|
|
2025-01-14 14:13:56 -04:00
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void parseWindowSelector(
|
|
|
|
|
const std::string& selector,
|
|
|
|
|
WindowSelector& windowSelector)
|
|
|
|
|
{
|
|
|
|
|
if (selector.length() >= 2 &&
|
|
|
|
|
((selector[0] == '"' && selector.back() == '"') ||
|
|
|
|
|
(selector[0] == '\'' && selector.back() == '\'') ||
|
|
|
|
|
(selector[0] == '`' && selector.back() == '`')))
|
|
|
|
|
{
|
|
|
|
|
windowSelector.window.name = selector.substr(
|
|
|
|
|
1, selector.length() - 2);
|
|
|
|
|
windowSelector.useWindowId = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
windowSelector.window.id = std::stoul(selector);
|
|
|
|
|
windowSelector.useWindowId = true;
|
|
|
|
|
} catch (const std::exception&) {
|
|
|
|
|
throw std::runtime_error(
|
|
|
|
|
"Window selector must be either a quoted string or a "
|
|
|
|
|
"numeric ID");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-12 14:31:33 -04:00
|
|
|
};
|
|
|
|
|
|
2025-01-14 14:13:56 -04:00
|
|
|
// Define the static member
|
|
|
|
|
std::map<XcbConnection::XConnectionIdentifier, std::shared_ptr<XcbConnection>>
|
|
|
|
|
XcbConnection::connections;
|
|
|
|
|
|
2025-01-13 21:57:11 -04:00
|
|
|
static std::vector<AttachedDevice> attachedDevices;
|
2025-01-08 17:16:49 -04:00
|
|
|
|
2025-01-13 21:57:11 -04:00
|
|
|
static hk::sense_api::sal_mlo_initializeIndFn xcbXorg_initializeInd;
|
|
|
|
|
static hk::sense_api::sal_mlo_finalizeIndFn xcbXorg_finalizeInd;
|
|
|
|
|
static hk::sense_api::sal_mlo_attachDeviceReqFn xcbXorg_attachDeviceReq;
|
|
|
|
|
static hk::sense_api::sal_mlo_detachDeviceReqFn xcbXorg_detachDeviceReq;
|
2025-01-09 06:03:43 -04:00
|
|
|
|
2025-01-13 21:57:11 -04:00
|
|
|
static hk::sense_api::SenseApiDesc xcbXorgApiDesc =
|
2025-01-08 17:16:49 -04:00
|
|
|
{
|
2025-01-09 17:18:24 -04:00
|
|
|
.name = "xcb-xorg",
|
2025-01-13 21:57:11 -04:00
|
|
|
.exportedImplexorApis = { { "video-implexor" } },
|
|
|
|
|
.sal_mgmt_libOps = {
|
|
|
|
|
.initializeInd = xcbXorg_initializeInd,
|
|
|
|
|
.finalizeInd = xcbXorg_finalizeInd,
|
|
|
|
|
.attachDeviceReq = xcbXorg_attachDeviceReq,
|
|
|
|
|
.detachDeviceReq = xcbXorg_detachDeviceReq
|
|
|
|
|
}
|
2025-01-08 17:16:49 -04:00
|
|
|
};
|
|
|
|
|
|
2025-01-13 21:57:11 -04:00
|
|
|
extern HK_UNMANGLED hk::sense_api::getSenseApiDescFn
|
|
|
|
|
HK_GET_SENSE_API_DESC_FN_NAME;
|
2025-01-08 17:16:49 -04:00
|
|
|
|
2025-01-13 21:57:11 -04:00
|
|
|
const hk::sense_api::SenseApiDesc &HK_GET_SENSE_API_DESC_FN_NAME(void)
|
2025-01-08 17:16:49 -04:00
|
|
|
{
|
2025-01-13 21:57:11 -04:00
|
|
|
return xcbXorgApiDesc;
|
2025-01-08 17:16:49 -04:00
|
|
|
}
|
2025-01-09 06:03:43 -04:00
|
|
|
|
2025-01-12 09:44:49 -04:00
|
|
|
int xcbXorg_initializeInd(void)
|
2025-01-09 06:03:43 -04:00
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int xcbXorg_finalizeInd(void)
|
|
|
|
|
{
|
2025-01-14 14:13:56 -04:00
|
|
|
XcbConnection::connections.clear();
|
2025-01-09 06:03:43 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-14 14:13:56 -04:00
|
|
|
int xcbXorg_attachDeviceReq(const hk::device::SenseDeviceSpec &desc)
|
2025-01-09 06:03:43 -04:00
|
|
|
{
|
2025-01-14 14:13:56 -04:00
|
|
|
// Create temporary device to validate parameters
|
|
|
|
|
AttachedDevice::WindowSelector tempSelector;
|
|
|
|
|
|
2025-01-14 16:58:22 -04:00
|
|
|
tempSelector.xconn.display = AttachedDevice::getRequiredParamAsInt(
|
2025-01-14 14:13:56 -04:00
|
|
|
desc, "display");
|
2025-01-14 16:58:22 -04:00
|
|
|
tempSelector.xconn.screen = AttachedDevice::getRequiredParamAsInt(
|
|
|
|
|
desc, "screen");
|
2025-01-14 14:13:56 -04:00
|
|
|
|
|
|
|
|
// Get or create connection before constructing device
|
|
|
|
|
XcbConnection::XConnectionIdentifier id{
|
2025-01-14 16:58:22 -04:00
|
|
|
tempSelector.xconn.display,
|
|
|
|
|
tempSelector.xconn.screen
|
2025-01-14 14:13:56 -04:00
|
|
|
};
|
|
|
|
|
auto conn = XcbConnection::getOrCreateConnection(id);
|
|
|
|
|
|
|
|
|
|
// Create device with validated connection
|
|
|
|
|
attachedDevices.emplace_back(desc, conn);
|
|
|
|
|
conn->refCount++;
|
|
|
|
|
|
|
|
|
|
std::cout << "Attaching X11 window:\n "
|
|
|
|
|
<< attachedDevices.back().windowSelector.stringify() << "\n"
|
|
|
|
|
<< " Using " << (conn->refCount > 1 ? "existing" : "new")
|
|
|
|
|
<< " connection to X server\n";
|
2025-01-13 11:53:38 -04:00
|
|
|
|
2025-01-09 06:03:43 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-14 14:13:56 -04:00
|
|
|
int xcbXorg_detachDeviceReq(const hk::device::SenseDeviceSpec &spec)
|
2025-01-09 06:03:43 -04:00
|
|
|
{
|
2025-01-14 14:13:56 -04:00
|
|
|
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{
|
2025-01-14 16:58:22 -04:00
|
|
|
it->windowSelector.xconn.display,
|
|
|
|
|
it->windowSelector.xconn.screen
|
2025-01-14 14:13:56 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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";
|
2025-01-09 06:03:43 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|