fix(hyprland/ipc): honor the requested instance signature
The Hyprland IPC helper cached the socket folder with the first instance signature already appended, so later calls ignored their instanceSig argument and always reused the first path. That made the helper violate its own API even though most real Waybar sessions only talk to a single Hyprland instance. Cache only the base socket directory and append the requested signature per lookup. This fixes correctness for tests, nested or debug multi-instance setups, and future code that needs to resolve a different signature, without claiming support for one Waybar process managing multiple Hyprland sessions. Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
This commit is contained in:
@@ -25,27 +25,23 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
|
||||
static std::mutex folderMutex;
|
||||
std::unique_lock lock(folderMutex);
|
||||
|
||||
// socket path, specified by EventManager of Hyprland
|
||||
if (!socketFolder_.empty()) {
|
||||
return socketFolder_;
|
||||
if (socketFolder_.empty()) {
|
||||
const char* xdgRuntimeDirEnv = std::getenv("XDG_RUNTIME_DIR");
|
||||
std::filesystem::path xdgRuntimeDir;
|
||||
// Only set path if env variable is set
|
||||
if (xdgRuntimeDirEnv != nullptr) {
|
||||
xdgRuntimeDir = std::filesystem::path(xdgRuntimeDirEnv);
|
||||
}
|
||||
|
||||
if (!xdgRuntimeDir.empty() && std::filesystem::exists(xdgRuntimeDir / "hypr")) {
|
||||
socketFolder_ = xdgRuntimeDir / "hypr";
|
||||
} else {
|
||||
spdlog::warn("$XDG_RUNTIME_DIR/hypr does not exist, falling back to /tmp/hypr");
|
||||
socketFolder_ = std::filesystem::path("/tmp") / "hypr";
|
||||
}
|
||||
}
|
||||
|
||||
const char* xdgRuntimeDirEnv = std::getenv("XDG_RUNTIME_DIR");
|
||||
std::filesystem::path xdgRuntimeDir;
|
||||
// Only set path if env variable is set
|
||||
if (xdgRuntimeDirEnv != nullptr) {
|
||||
xdgRuntimeDir = std::filesystem::path(xdgRuntimeDirEnv);
|
||||
}
|
||||
|
||||
if (!xdgRuntimeDir.empty() && std::filesystem::exists(xdgRuntimeDir / "hypr")) {
|
||||
socketFolder_ = xdgRuntimeDir / "hypr";
|
||||
} else {
|
||||
spdlog::warn("$XDG_RUNTIME_DIR/hypr does not exist, falling back to /tmp/hypr");
|
||||
socketFolder_ = std::filesystem::path("/tmp") / "hypr";
|
||||
}
|
||||
|
||||
socketFolder_ = socketFolder_ / instanceSig;
|
||||
return socketFolder_;
|
||||
return socketFolder_ / instanceSig;
|
||||
}
|
||||
|
||||
IPC::IPC() {
|
||||
@@ -91,7 +87,7 @@ void IPC::socketListener() {
|
||||
|
||||
spdlog::info("Hyprland IPC starting");
|
||||
|
||||
struct sockaddr_un addr;
|
||||
struct sockaddr_un addr = {};
|
||||
const int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (socketfd == -1) {
|
||||
@@ -102,10 +98,13 @@ void IPC::socketListener() {
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock";
|
||||
if (socketPath.native().size() >= sizeof(addr.sun_path)) {
|
||||
spdlog::error("Hyprland IPC: Socket path is too long: {}", socketPath.string());
|
||||
close(socketfd);
|
||||
return;
|
||||
}
|
||||
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
|
||||
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||
|
||||
int l = sizeof(struct sockaddr_un);
|
||||
|
||||
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
|
||||
@@ -233,8 +232,10 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
|
||||
std::string socketPath = IPC::getSocketFolder(instanceSig) / ".socket.sock";
|
||||
|
||||
// Use snprintf to copy the socketPath string into serverAddress.sun_path
|
||||
if (snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str()) <
|
||||
0) {
|
||||
const auto socketPathLength =
|
||||
snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str());
|
||||
if (socketPathLength < 0 ||
|
||||
socketPathLength >= static_cast<int>(sizeof(serverAddress.sun_path))) {
|
||||
throw std::runtime_error("Hyprland IPC: Couldn't copy socket path (6)");
|
||||
}
|
||||
|
||||
@@ -243,15 +244,28 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
|
||||
throw std::runtime_error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)");
|
||||
}
|
||||
|
||||
auto sizeWritten = write(serverSocket, rq.c_str(), rq.length());
|
||||
std::size_t totalWritten = 0;
|
||||
while (totalWritten < rq.length()) {
|
||||
const auto sizeWritten =
|
||||
write(serverSocket, rq.c_str() + totalWritten, rq.length() - totalWritten);
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
spdlog::error("Hyprland IPC: Couldn't write (4)");
|
||||
return "";
|
||||
if (sizeWritten < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
spdlog::error("Hyprland IPC: Couldn't write (4)");
|
||||
return "";
|
||||
}
|
||||
if (sizeWritten == 0) {
|
||||
spdlog::error("Hyprland IPC: Socket write made no progress");
|
||||
return "";
|
||||
}
|
||||
totalWritten += static_cast<std::size_t>(sizeWritten);
|
||||
}
|
||||
|
||||
std::array<char, 8192> buffer = {0};
|
||||
std::string response;
|
||||
ssize_t sizeWritten = 0;
|
||||
|
||||
do {
|
||||
sizeWritten = read(serverSocket, buffer.data(), 8192);
|
||||
|
||||
@@ -86,6 +86,24 @@ TEST_CASE("XDGRuntimeDirExistsNoHyprDir", "[getSocketFolder]") {
|
||||
fs::remove_all(tempDir, ec);
|
||||
}
|
||||
|
||||
TEST_CASE("Socket folder is resolved per instance signature", "[getSocketFolder]") {
|
||||
const fs::path tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000";
|
||||
std::error_code ec;
|
||||
fs::remove_all(tempDir, ec);
|
||||
fs::create_directories(tempDir / "hypr");
|
||||
setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1);
|
||||
IPCTestHelper::resetSocketFolder();
|
||||
|
||||
const auto firstPath = hyprland::IPC::getSocketFolder("instance_a");
|
||||
const auto secondPath = hyprland::IPC::getSocketFolder("instance_b");
|
||||
|
||||
REQUIRE(firstPath == tempDir / "hypr" / "instance_a");
|
||||
REQUIRE(secondPath == tempDir / "hypr" / "instance_b");
|
||||
REQUIRE(firstPath != secondPath);
|
||||
|
||||
fs::remove_all(tempDir, ec);
|
||||
}
|
||||
|
||||
TEST_CASE("getSocket1Reply throws on no socket", "[getSocket1Reply]") {
|
||||
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
IPCTestHelper::resetSocketFolder();
|
||||
|
||||
Reference in New Issue
Block a user