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:
2025-01-14 20:22:12 -04:00
parent d31530e0bd
commit 64baa7906b
+205 -16
View File
@@ -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)
: connection(nullptr, &xcb_disconnect),
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
{
public:
@@ -117,16 +164,51 @@ public:
AttachedDevice(const hk::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.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;
WindowSelector windowSelector;
std::shared_ptr<XcbConnection> connection;
std::string actualWindowName;
public:
static int getRequiredParamAsInt(
@@ -172,7 +254,8 @@ public:
else
{
try {
windowSelector.window.id = std::stoul(selector);
// Allow hex numbers
windowSelector.window.id = std::stoul(selector, nullptr, 0);
windowSelector.useWindowId = true;
} catch (const std::exception&) {
throw std::runtime_error(
@@ -189,6 +272,114 @@ std::map<XcbConnection::XConnectionIdentifier, std::shared_ptr<XcbConnection>>
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_finalizeIndFn xcbXorg_finalizeInd;
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)
{
// Create temporary device to validate parameters
AttachedDevice::WindowSelector tempSelector;
tempSelector.xconn.display = AttachedDevice::getRequiredParamAsInt(
desc, "display");
tempSelector.xconn.screen = AttachedDevice::getRequiredParamAsInt(
desc, "screen");
// Get or create connection before constructing device
// Ensure connection exists before creating device. Create conn'tion if not.
XcbConnection::XConnectionIdentifier id{
tempSelector.xconn.display,
tempSelector.xconn.screen
AttachedDevice::getRequiredParamAsInt(desc, "display"),
AttachedDevice::getRequiredParamAsInt(desc, "screen")
};
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);
// Successfully attached device, so decouple guard from RAII cleanup
conn->refCount++;
guard.commit();
std::cout << "Attaching X11 window:\n "
<< attachedDevices.back().windowSelector.stringify() << "\n"
<< " Using " << (conn->refCount > 1 ? "existing" : "new")
<< " connection to X server\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;
}