293 lines
8.6 KiB
C++
293 lines
8.6 KiB
C++
#include <iostream>
|
|
#include <algorithm>
|
|
#include <stdexcept>
|
|
#include <memory>
|
|
#include <vector>
|
|
#include <sstream>
|
|
#include <map>
|
|
#include <atomic>
|
|
#include <xcb/xcb.h>
|
|
#include "xcbXorg.h"
|
|
|
|
namespace xcb_xorg {
|
|
|
|
// Static member initialization
|
|
std::map<ConnectionIdentifier, std::shared_ptr<XcbConnection>>
|
|
ConnectionManager::connections;
|
|
|
|
std::string ConnectionIdentifier::stringify() const
|
|
{
|
|
std::ostringstream os;
|
|
os << "display=" << display << ", screen=" << screen;
|
|
return os.str();
|
|
}
|
|
|
|
XcbConnection::XcbConnection(const ConnectionIdentifier& 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));
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<XcbConnection> ConnectionManager::getOrCreateConnection(
|
|
const ConnectionIdentifier& 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;
|
|
}
|
|
|
|
void ConnectionManager::cleanupAllConnections()
|
|
{
|
|
connections.clear();
|
|
}
|
|
|
|
size_t ConnectionManager::getConnectionCount()
|
|
{
|
|
return connections.size();
|
|
}
|
|
|
|
/**
|
|
* @brief Remove a specific connection from the manager
|
|
* @param id Connection identifier to remove
|
|
*/
|
|
void ConnectionManager::removeConnection(const ConnectionIdentifier& id)
|
|
{
|
|
auto it = connections.find(id);
|
|
if (it != connections.end()) {
|
|
connections.erase(it);
|
|
}
|
|
}
|
|
|
|
namespace window_search {
|
|
|
|
struct XcbReplyDeleter {
|
|
void operator()(xcb_query_tree_reply_t* p) { free(p); }
|
|
void operator()(xcb_get_property_reply_t* p) { free(p); }
|
|
};
|
|
|
|
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;
|
|
|
|
if (root == targetId) return root;
|
|
|
|
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;
|
|
}
|
|
|
|
xcb_window_t findByName(
|
|
xcb_connection_t* conn, xcb_window_t root,
|
|
const std::string& targetName, std::string& outWindowName,
|
|
MatchType matchType)
|
|
{
|
|
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 ((matchType == MatchType::EXACT
|
|
&& windowName == targetName)
|
|
|| (matchType == MatchType::SUBSTRING
|
|
&& windowName.find(targetName) != std::string::npos))
|
|
{
|
|
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 ((matchType == MatchType::EXACT
|
|
&& windowName == targetName)
|
|
|| (matchType == MatchType::SUBSTRING
|
|
&& windowName.find(targetName) != std::string::npos))
|
|
{
|
|
outWindowName = windowName;
|
|
return children[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recursively search this child's subtree
|
|
if (xcb_window_t result = findByName(
|
|
conn, children[i], targetName, outWindowName, matchType))
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
} // namespace window_search
|
|
|
|
} // namespace xcb_xorg
|
|
|
|
// 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)
|
|
{
|
|
xcb_xorg::ConnectionIdentifier id{display, screen};
|
|
auto conn = xcb_xorg::ConnectionManager::getOrCreateConnection(id);
|
|
conn->incrementRefCount();
|
|
return conn;
|
|
}
|
|
|
|
/**
|
|
* @brief Clean up all connections
|
|
*/
|
|
extern "C" cleanup_connections_fn xcb_xorg_cleanup_connections;
|
|
void xcb_xorg_cleanup_connections()
|
|
{
|
|
xcb_xorg::ConnectionManager::cleanupAllConnections();
|
|
}
|
|
|
|
/**
|
|
* @brief Get the number of active connections
|
|
* @return Number of active connections
|
|
*/
|
|
size_t xcb_xorg_get_connection_count()
|
|
{
|
|
return xcb_xorg::ConnectionManager::getConnectionCount();
|
|
}
|
|
|
|
/**
|
|
* @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)
|
|
{
|
|
if (!conn) return 0;
|
|
auto connection = static_cast<xcb_xorg::XcbConnection*>(conn);
|
|
return xcb_xorg::window_search::findById(
|
|
connection->getConnection(), root, targetId);
|
|
}
|
|
|
|
/**
|
|
* @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)
|
|
{
|
|
if (!conn) return 0;
|
|
auto connection = static_cast<xcb_xorg::XcbConnection*>(conn);
|
|
return xcb_xorg::window_search::findByName(
|
|
connection->getConnection(), root, targetName, outWindowName,
|
|
matchType);
|
|
}
|
|
|
|
/**
|
|
* @brief Dereference a connection (decrements ref count and closes if zero)
|
|
* @param conn Shared pointer to the connection to dereference
|
|
*/
|
|
extern "C" dereference_connection_fn xcb_xorg_dereference_connection;
|
|
void xcb_xorg_dereference_connection(std::shared_ptr<xcb_xorg::XcbConnection> conn)
|
|
{
|
|
if (!conn) return;
|
|
|
|
int newRefCount = conn->decrementRefCount();
|
|
// Remove from connection manager if ref count reaches zero
|
|
if (newRefCount <= 0) {
|
|
xcb_xorg::ConnectionManager::removeConnection(conn->getIdentifier());
|
|
}
|
|
}
|