xcbXorg: Implement window search by ID and name
The name search doesn't quite seem to work, but we captured all 4 of our regularly active windows (including the browser!!!) using window IDs.
This commit is contained in:
+205
-16
@@ -33,6 +33,37 @@ struct XcbConnection
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
XcbConnection(const XConnectionIdentifier& id)
|
||||||
: connection(nullptr, &xcb_disconnect),
|
: connection(nullptr, &xcb_disconnect),
|
||||||
connectionIdentifier(id), refCount(0)
|
connectionIdentifier(id), refCount(0)
|
||||||
@@ -88,6 +119,22 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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); }
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
class AttachedDevice
|
class AttachedDevice
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -117,16 +164,51 @@ public:
|
|||||||
AttachedDevice(const hk::device::SenseDeviceSpec &spec,
|
AttachedDevice(const hk::device::SenseDeviceSpec &spec,
|
||||||
std::shared_ptr<XcbConnection> conn)
|
std::shared_ptr<XcbConnection> conn)
|
||||||
: deviceSpec(spec)
|
: deviceSpec(spec)
|
||||||
|
// This std::move is moving ownership from a shared_ptr to a shared_ptr
|
||||||
, connection(std::move(conn))
|
, connection(std::move(conn))
|
||||||
{
|
{
|
||||||
windowSelector.xconn.display = getRequiredParamAsInt(spec, "display");
|
windowSelector.xconn.display = getRequiredParamAsInt(spec, "display");
|
||||||
windowSelector.xconn.screen = getRequiredParamAsInt(spec, "screen");
|
windowSelector.xconn.screen = getRequiredParamAsInt(spec, "screen");
|
||||||
parseWindowSelector(spec.deviceSelector, windowSelector);
|
parseWindowSelector(spec.deviceSelector, 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.useWindowId)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetWindow)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Failed to find window "
|
||||||
|
+ (windowSelector.useWindowId
|
||||||
|
? std::to_string(windowSelector.window.id)
|
||||||
|
: "\"" + windowSelector.window.name + "\"")
|
||||||
|
+ " on display " + std::to_string(windowSelector.xconn.display)
|
||||||
|
+ ", screen " + std::to_string(windowSelector.xconn.screen));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hk::device::SenseDeviceSpec deviceSpec;
|
hk::device::SenseDeviceSpec deviceSpec;
|
||||||
WindowSelector windowSelector;
|
WindowSelector windowSelector;
|
||||||
std::shared_ptr<XcbConnection> connection;
|
std::shared_ptr<XcbConnection> connection;
|
||||||
|
std::string actualWindowName;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static int getRequiredParamAsInt(
|
static int getRequiredParamAsInt(
|
||||||
@@ -172,7 +254,8 @@ public:
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
windowSelector.window.id = std::stoul(selector);
|
// Allow hex numbers
|
||||||
|
windowSelector.window.id = std::stoul(selector, nullptr, 0);
|
||||||
windowSelector.useWindowId = true;
|
windowSelector.useWindowId = true;
|
||||||
} catch (const std::exception&) {
|
} catch (const std::exception&) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
@@ -189,6 +272,114 @@ std::map<XcbConnection::XConnectionIdentifier, std::shared_ptr<XcbConnection>>
|
|||||||
|
|
||||||
static std::vector<AttachedDevice> attachedDevices;
|
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_query_tree_cookie_t cookie = xcb_query_tree(conn, root);
|
||||||
|
std::unique_ptr<xcb_query_tree_reply_t, XcbReplyDeleter> reply(
|
||||||
|
xcb_query_tree_reply(conn, cookie, nullptr));
|
||||||
|
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());
|
||||||
|
int num_children = xcb_query_tree_children_length(reply.get());
|
||||||
|
|
||||||
|
for (int i = 0; i < num_children; ++i)
|
||||||
|
{
|
||||||
|
if (children[i] == targetId) {
|
||||||
|
return children[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively search this child's subtree
|
||||||
|
if (xcb_window_t result = findById(
|
||||||
|
conn, children[i], targetId))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static xcb_window_t findByName(
|
||||||
|
xcb_connection_t* conn, xcb_window_t root,
|
||||||
|
const std::string& targetName, std::string& outWindowName)
|
||||||
|
{
|
||||||
|
xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, root);
|
||||||
|
std::unique_ptr<xcb_query_tree_reply_t, XcbReplyDeleter> reply(
|
||||||
|
xcb_query_tree_reply(conn, cookie, nullptr));
|
||||||
|
if (!reply) return 0;
|
||||||
|
|
||||||
|
// First check current window
|
||||||
|
xcb_get_property_cookie_t prop_cookie = xcb_get_property(
|
||||||
|
conn, 0, root, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 1024);
|
||||||
|
|
||||||
|
std::unique_ptr<xcb_get_property_reply_t, XcbReplyDeleter> prop_reply(
|
||||||
|
xcb_get_property_reply(conn, prop_cookie, nullptr));
|
||||||
|
|
||||||
|
if (prop_reply)
|
||||||
|
{
|
||||||
|
int len = xcb_get_property_value_length(prop_reply.get());
|
||||||
|
char* name = static_cast<char*>(
|
||||||
|
xcb_get_property_value(prop_reply.get()));
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
std::string windowName(name, len);
|
||||||
|
if (windowName == targetName)
|
||||||
|
{
|
||||||
|
outWindowName = windowName;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check all children
|
||||||
|
xcb_window_t* children = xcb_query_tree_children(reply.get());
|
||||||
|
int num_children = xcb_query_tree_children_length(reply.get());
|
||||||
|
|
||||||
|
for (int i = 0; i < num_children; ++i)
|
||||||
|
{
|
||||||
|
prop_cookie = xcb_get_property(
|
||||||
|
conn, 0, children[i],
|
||||||
|
XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 1024);
|
||||||
|
|
||||||
|
prop_reply.reset(xcb_get_property_reply(conn, prop_cookie, nullptr));
|
||||||
|
if (prop_reply)
|
||||||
|
{
|
||||||
|
int len = xcb_get_property_value_length(prop_reply.get());
|
||||||
|
char* name = static_cast<char*>(xcb_get_property_value(
|
||||||
|
prop_reply.get()));
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
std::string windowName(name, len);
|
||||||
|
if (windowName == targetName)
|
||||||
|
{
|
||||||
|
outWindowName = windowName;
|
||||||
|
return children[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively search this child's subtree
|
||||||
|
if (xcb_window_t result = findByName(
|
||||||
|
conn, children[i], targetName, outWindowName))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace xcb_window_search
|
||||||
|
|
||||||
static hk::sense_api::sal_mlo_initializeIndFn xcbXorg_initializeInd;
|
static hk::sense_api::sal_mlo_initializeIndFn xcbXorg_initializeInd;
|
||||||
static hk::sense_api::sal_mlo_finalizeIndFn xcbXorg_finalizeInd;
|
static hk::sense_api::sal_mlo_finalizeIndFn xcbXorg_finalizeInd;
|
||||||
static hk::sense_api::sal_mlo_attachDeviceReqFn xcbXorg_attachDeviceReq;
|
static hk::sense_api::sal_mlo_attachDeviceReqFn xcbXorg_attachDeviceReq;
|
||||||
@@ -227,29 +418,27 @@ int xcbXorg_finalizeInd(void)
|
|||||||
|
|
||||||
int xcbXorg_attachDeviceReq(const hk::device::SenseDeviceSpec &desc)
|
int xcbXorg_attachDeviceReq(const hk::device::SenseDeviceSpec &desc)
|
||||||
{
|
{
|
||||||
// Create temporary device to validate parameters
|
// Ensure connection exists before creating device. Create conn'tion if not.
|
||||||
AttachedDevice::WindowSelector tempSelector;
|
|
||||||
|
|
||||||
tempSelector.xconn.display = AttachedDevice::getRequiredParamAsInt(
|
|
||||||
desc, "display");
|
|
||||||
tempSelector.xconn.screen = AttachedDevice::getRequiredParamAsInt(
|
|
||||||
desc, "screen");
|
|
||||||
|
|
||||||
// Get or create connection before constructing device
|
|
||||||
XcbConnection::XConnectionIdentifier id{
|
XcbConnection::XConnectionIdentifier id{
|
||||||
tempSelector.xconn.display,
|
AttachedDevice::getRequiredParamAsInt(desc, "display"),
|
||||||
tempSelector.xconn.screen
|
AttachedDevice::getRequiredParamAsInt(desc, "screen")
|
||||||
};
|
};
|
||||||
auto conn = XcbConnection::getOrCreateConnection(id);
|
auto conn = XcbConnection::getOrCreateConnection(id);
|
||||||
|
// RAII protection in case AttachDevice construction below fails
|
||||||
|
XcbConnection::ConnectionGuard guard(conn);
|
||||||
|
|
||||||
// Create device with validated connection
|
// Create device and increment connection refcount
|
||||||
attachedDevices.emplace_back(desc, conn);
|
attachedDevices.emplace_back(desc, conn);
|
||||||
|
// Successfully attached device, so decouple guard from RAII cleanup
|
||||||
conn->refCount++;
|
conn->refCount++;
|
||||||
|
guard.commit();
|
||||||
|
|
||||||
std::cout << "Attaching X11 window:\n "
|
std::cout << "Attaching X11 window:\n "
|
||||||
<< attachedDevices.back().windowSelector.stringify() << "\n"
|
<< attachedDevices.back().windowSelector.stringify() << "\n"
|
||||||
<< " Using " << (conn->refCount > 1 ? "existing" : "new")
|
<< " Actual window name: \""
|
||||||
<< " connection to X server\n";
|
<< attachedDevices.back().actualWindowName << "\"\n"
|
||||||
|
<< " Using " << (conn->refCount > 1 ? "existing" : "new")
|
||||||
|
<< " connection to X server\n";
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user