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)
|
||||
: 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user