Merge pull request #4910 from khaneliman/hyprland
fix(hyprland): misc hardening with ipc socket and events
This commit is contained in:
@@ -20,8 +20,8 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
struct Workspace {
|
struct Workspace {
|
||||||
int id;
|
int id = 0;
|
||||||
int windows;
|
int windows = 0;
|
||||||
std::string last_window;
|
std::string last_window;
|
||||||
std::string last_window_title;
|
std::string last_window_title;
|
||||||
|
|
||||||
@@ -29,14 +29,14 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct WindowData {
|
struct WindowData {
|
||||||
bool floating;
|
bool floating = false;
|
||||||
int monitor = -1;
|
int monitor = -1;
|
||||||
std::string class_name;
|
std::string class_name;
|
||||||
std::string initial_class_name;
|
std::string initial_class_name;
|
||||||
std::string title;
|
std::string title;
|
||||||
std::string initial_title;
|
std::string initial_title;
|
||||||
bool fullscreen;
|
bool fullscreen = false;
|
||||||
bool grouped;
|
bool grouped = false;
|
||||||
|
|
||||||
static auto parse(const Json::Value&) -> WindowData;
|
static auto parse(const Json::Value&) -> WindowData;
|
||||||
};
|
};
|
||||||
@@ -47,7 +47,7 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
|
|||||||
void queryActiveWorkspace();
|
void queryActiveWorkspace();
|
||||||
void setClass(const std::string&, bool enable);
|
void setClass(const std::string&, bool enable);
|
||||||
|
|
||||||
bool separateOutputs_;
|
bool separateOutputs_ = false;
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
const Bar& bar_;
|
const Bar& bar_;
|
||||||
util::JsonParser parser_;
|
util::JsonParser parser_;
|
||||||
@@ -55,11 +55,11 @@ class Window : public waybar::AAppIconLabel, public EventHandler {
|
|||||||
Workspace workspace_;
|
Workspace workspace_;
|
||||||
std::string soloClass_;
|
std::string soloClass_;
|
||||||
std::string lastSoloClass_;
|
std::string lastSoloClass_;
|
||||||
bool solo_;
|
bool solo_ = false;
|
||||||
bool allFloating_;
|
bool allFloating_ = false;
|
||||||
bool swallowing_;
|
bool swallowing_ = false;
|
||||||
bool fullscreen_;
|
bool fullscreen_ = false;
|
||||||
bool focused_;
|
bool focused_ = false;
|
||||||
|
|
||||||
IPC& m_ipc;
|
IPC& m_ipc;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <gtkmm/enums.h>
|
#include <gtkmm/enums.h>
|
||||||
#include <gtkmm/label.h>
|
#include <gtkmm/label.h>
|
||||||
#include <json/value.h>
|
#include <json/value.h>
|
||||||
|
#include <sigc++/connection.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -208,6 +209,7 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
std::mutex m_mutex;
|
std::mutex m_mutex;
|
||||||
const Bar& m_bar;
|
const Bar& m_bar;
|
||||||
Gtk::Box m_box;
|
Gtk::Box m_box;
|
||||||
|
sigc::connection m_scrollEventConnection_;
|
||||||
IPC& m_ipc;
|
IPC& m_ipc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,27 +25,23 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) {
|
|||||||
static std::mutex folderMutex;
|
static std::mutex folderMutex;
|
||||||
std::unique_lock lock(folderMutex);
|
std::unique_lock lock(folderMutex);
|
||||||
|
|
||||||
// socket path, specified by EventManager of Hyprland
|
if (socketFolder_.empty()) {
|
||||||
if (!socketFolder_.empty()) {
|
const char* xdgRuntimeDirEnv = std::getenv("XDG_RUNTIME_DIR");
|
||||||
return socketFolder_;
|
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");
|
return socketFolder_ / instanceSig;
|
||||||
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_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IPC::IPC() {
|
IPC::IPC() {
|
||||||
@@ -91,7 +87,7 @@ void IPC::socketListener() {
|
|||||||
|
|
||||||
spdlog::info("Hyprland IPC starting");
|
spdlog::info("Hyprland IPC starting");
|
||||||
|
|
||||||
struct sockaddr_un addr;
|
struct sockaddr_un addr = {};
|
||||||
const int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
const int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
|
||||||
if (socketfd == -1) {
|
if (socketfd == -1) {
|
||||||
@@ -102,10 +98,13 @@ void IPC::socketListener() {
|
|||||||
addr.sun_family = AF_UNIX;
|
addr.sun_family = AF_UNIX;
|
||||||
|
|
||||||
auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock";
|
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);
|
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);
|
int l = sizeof(struct sockaddr_un);
|
||||||
|
|
||||||
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
|
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";
|
std::string socketPath = IPC::getSocketFolder(instanceSig) / ".socket.sock";
|
||||||
|
|
||||||
// Use snprintf to copy the socketPath string into serverAddress.sun_path
|
// Use snprintf to copy the socketPath string into serverAddress.sun_path
|
||||||
if (snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str()) <
|
const auto socketPathLength =
|
||||||
0) {
|
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)");
|
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)");
|
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) {
|
if (sizeWritten < 0) {
|
||||||
spdlog::error("Hyprland IPC: Couldn't write (4)");
|
if (errno == EINTR) {
|
||||||
return "";
|
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::array<char, 8192> buffer = {0};
|
||||||
std::string response;
|
std::string response;
|
||||||
|
ssize_t sizeWritten = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
sizeWritten = read(serverSocket, buffer.data(), 8192);
|
sizeWritten = read(serverSocket, buffer.data(), 8192);
|
||||||
|
|||||||
@@ -63,19 +63,35 @@ auto Language::update() -> void {
|
|||||||
|
|
||||||
void Language::onEvent(const std::string& ev) {
|
void Language::onEvent(const std::string& ev) {
|
||||||
std::lock_guard<std::mutex> lg(mutex_);
|
std::lock_guard<std::mutex> lg(mutex_);
|
||||||
std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(','));
|
const auto payloadStart = ev.find(">>");
|
||||||
|
if (payloadStart == std::string::npos) {
|
||||||
|
spdlog::warn("hyprland language received malformed event: {}", ev);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto payload = ev.substr(payloadStart + 2);
|
||||||
|
const auto kbSeparator = payload.find(',');
|
||||||
|
if (kbSeparator == std::string::npos) {
|
||||||
|
spdlog::warn("hyprland language received malformed event payload: {}", ev);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string kbName = payload.substr(0, kbSeparator);
|
||||||
|
|
||||||
// Last comma before variants parenthesis, eg:
|
// Last comma before variants parenthesis, eg:
|
||||||
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
|
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
|
||||||
// with dead keys)
|
// with dead keys)
|
||||||
std::string beforeParenthesis;
|
std::string beforeParenthesis;
|
||||||
auto parenthesisPos = ev.find_last_of('(');
|
auto parenthesisPos = payload.find_last_of('(');
|
||||||
if (parenthesisPos == std::string::npos) {
|
if (parenthesisPos == std::string::npos) {
|
||||||
beforeParenthesis = ev;
|
beforeParenthesis = payload;
|
||||||
} else {
|
} else {
|
||||||
beforeParenthesis = std::string(begin(ev), begin(ev) + parenthesisPos);
|
beforeParenthesis = payload.substr(0, parenthesisPos);
|
||||||
}
|
}
|
||||||
auto layoutName = ev.substr(beforeParenthesis.find_last_of(',') + 1);
|
const auto layoutSeparator = beforeParenthesis.find_last_of(',');
|
||||||
|
if (layoutSeparator == std::string::npos) {
|
||||||
|
spdlog::warn("hyprland language received malformed layout payload: {}", ev);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto layoutName = payload.substr(layoutSeparator + 1);
|
||||||
|
|
||||||
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
|
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
|
||||||
return; // ignore
|
return; // ignore
|
||||||
|
|||||||
@@ -75,7 +75,12 @@ void Submap::onEvent(const std::string& ev) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto submapName = ev.substr(ev.find_first_of('>') + 2);
|
const auto separator = ev.find(">>");
|
||||||
|
if (separator == std::string::npos) {
|
||||||
|
spdlog::warn("hyprland submap received malformed event: {}", ev);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto submapName = ev.substr(separator + 2);
|
||||||
|
|
||||||
submap_ = submapName;
|
submap_ = submapName;
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
|||||||
|
|
||||||
windowIpcUniqueLock.unlock();
|
windowIpcUniqueLock.unlock();
|
||||||
|
|
||||||
queryActiveWorkspace();
|
|
||||||
update();
|
update();
|
||||||
dp.emit();
|
dp.emit();
|
||||||
}
|
}
|
||||||
@@ -177,66 +176,65 @@ auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Window::queryActiveWorkspace() {
|
void Window::queryActiveWorkspace() {
|
||||||
std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);
|
|
||||||
|
|
||||||
if (separateOutputs_) {
|
if (separateOutputs_) {
|
||||||
workspace_ = getActiveWorkspace(this->bar_.output->name);
|
workspace_ = getActiveWorkspace(this->bar_.output->name);
|
||||||
} else {
|
} else {
|
||||||
workspace_ = getActiveWorkspace();
|
workspace_ = getActiveWorkspace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
focused_ = false;
|
||||||
|
windowData_ = WindowData{};
|
||||||
|
allFloating_ = false;
|
||||||
|
swallowing_ = false;
|
||||||
|
fullscreen_ = false;
|
||||||
|
solo_ = false;
|
||||||
|
soloClass_.clear();
|
||||||
|
|
||||||
|
if (workspace_.windows <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto clients = m_ipc.getSocket1JsonReply("clients");
|
||||||
|
if (!clients.isArray()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto activeWindow = std::ranges::find_if(clients, [&](const Json::Value& window) {
|
||||||
|
return window["address"] == workspace_.last_window;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (activeWindow == std::end(clients)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
focused_ = true;
|
focused_ = true;
|
||||||
if (workspace_.windows > 0) {
|
windowData_ = WindowData::parse(*activeWindow);
|
||||||
const auto clients = m_ipc.getSocket1JsonReply("clients");
|
updateAppIconName(windowData_.class_name, windowData_.initial_class_name);
|
||||||
if (clients.isArray()) {
|
std::vector<Json::Value> workspaceWindows;
|
||||||
auto activeWindow = std::ranges::find_if(clients, [&](const Json::Value& window) {
|
std::ranges::copy_if(
|
||||||
return window["address"] == workspace_.last_window;
|
clients, std::back_inserter(workspaceWindows), [&](const Json::Value& window) {
|
||||||
|
return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool();
|
||||||
});
|
});
|
||||||
|
swallowing_ = std::ranges::any_of(workspaceWindows, [&](const Json::Value& window) {
|
||||||
|
return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0";
|
||||||
|
});
|
||||||
|
std::vector<Json::Value> visibleWindows;
|
||||||
|
std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows),
|
||||||
|
[&](const Json::Value& window) { return !window["hidden"].asBool(); });
|
||||||
|
solo_ = 1 == std::count_if(
|
||||||
|
visibleWindows.begin(), visibleWindows.end(),
|
||||||
|
[&](const Json::Value& window) { return !window["floating"].asBool(); });
|
||||||
|
allFloating_ = std::ranges::all_of(
|
||||||
|
visibleWindows, [&](const Json::Value& window) { return window["floating"].asBool(); });
|
||||||
|
fullscreen_ = windowData_.fullscreen;
|
||||||
|
|
||||||
if (activeWindow == std::end(clients)) {
|
// Fullscreen windows look like they are solo
|
||||||
focused_ = false;
|
if (fullscreen_) {
|
||||||
return;
|
solo_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
windowData_ = WindowData::parse(*activeWindow);
|
if (solo_) {
|
||||||
updateAppIconName(windowData_.class_name, windowData_.initial_class_name);
|
soloClass_ = windowData_.class_name;
|
||||||
std::vector<Json::Value> workspaceWindows;
|
|
||||||
std::ranges::copy_if(
|
|
||||||
clients, std::back_inserter(workspaceWindows), [&](const Json::Value& window) {
|
|
||||||
return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool();
|
|
||||||
});
|
|
||||||
swallowing_ = std::ranges::any_of(workspaceWindows, [&](const Json::Value& window) {
|
|
||||||
return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0";
|
|
||||||
});
|
|
||||||
std::vector<Json::Value> visibleWindows;
|
|
||||||
std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows),
|
|
||||||
[&](const Json::Value& window) { return !window["hidden"].asBool(); });
|
|
||||||
solo_ = 1 == std::count_if(
|
|
||||||
visibleWindows.begin(), visibleWindows.end(),
|
|
||||||
[&](const Json::Value& window) { return !window["floating"].asBool(); });
|
|
||||||
allFloating_ = std::ranges::all_of(
|
|
||||||
visibleWindows, [&](const Json::Value& window) { return window["floating"].asBool(); });
|
|
||||||
fullscreen_ = windowData_.fullscreen;
|
|
||||||
|
|
||||||
// Fullscreen windows look like they are solo
|
|
||||||
if (fullscreen_) {
|
|
||||||
solo_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (solo_) {
|
|
||||||
soloClass_ = windowData_.class_name;
|
|
||||||
} else {
|
|
||||||
soloClass_ = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
focused_ = false;
|
|
||||||
windowData_ = WindowData{};
|
|
||||||
allFloating_ = false;
|
|
||||||
swallowing_ = false;
|
|
||||||
fullscreen_ = false;
|
|
||||||
solo_ = false;
|
|
||||||
soloClass_ = "";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value&
|
|||||||
}
|
}
|
||||||
|
|
||||||
Workspaces::~Workspaces() {
|
Workspaces::~Workspaces() {
|
||||||
|
if (m_scrollEventConnection_.connected()) {
|
||||||
|
m_scrollEventConnection_.disconnect();
|
||||||
|
}
|
||||||
m_ipc.unregisterForIPC(this);
|
m_ipc.unregisterForIPC(this);
|
||||||
// wait for possible event handler to finish
|
// wait for possible event handler to finish
|
||||||
std::lock_guard<std::mutex> lg(m_mutex);
|
std::lock_guard<std::mutex> lg(m_mutex);
|
||||||
@@ -44,10 +47,14 @@ void Workspaces::init() {
|
|||||||
|
|
||||||
initializeWorkspaces();
|
initializeWorkspaces();
|
||||||
|
|
||||||
|
if (m_scrollEventConnection_.connected()) {
|
||||||
|
m_scrollEventConnection_.disconnect();
|
||||||
|
}
|
||||||
if (barScroll()) {
|
if (barScroll()) {
|
||||||
auto& window = const_cast<Bar&>(m_bar).window;
|
auto& window = const_cast<Bar&>(m_bar).window;
|
||||||
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||||
window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
|
m_scrollEventConnection_ =
|
||||||
|
window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
|
||||||
}
|
}
|
||||||
|
|
||||||
dp.emit();
|
dp.emit();
|
||||||
@@ -271,7 +278,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const& clientsJs
|
|||||||
// key is the workspace and value is array of monitors to create on
|
// key is the workspace and value is array of monitors to create on
|
||||||
for (const Json::Value& monitor : value) {
|
for (const Json::Value& monitor : value) {
|
||||||
if (monitor.isString() && monitor.asString() == currentMonitor) {
|
if (monitor.isString() && monitor.asString() == currentMonitor) {
|
||||||
persistentWorkspacesToCreate.emplace_back(currentMonitor);
|
persistentWorkspacesToCreate.emplace_back(key);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,8 +339,13 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& c
|
|||||||
|
|
||||||
void Workspaces::onEvent(const std::string& ev) {
|
void Workspaces::onEvent(const std::string& ev) {
|
||||||
std::lock_guard<std::mutex> lock(m_mutex);
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
|
const auto separator = ev.find(">>");
|
||||||
std::string payload = ev.substr(eventName.size() + 2);
|
if (separator == std::string::npos) {
|
||||||
|
spdlog::warn("Malformed Hyprland workspace event: {}", ev);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string eventName = ev.substr(0, separator);
|
||||||
|
std::string payload = ev.substr(separator + 2);
|
||||||
|
|
||||||
if (eventName == "workspacev2") {
|
if (eventName == "workspacev2") {
|
||||||
onWorkspaceActivated(payload);
|
onWorkspaceActivated(payload);
|
||||||
@@ -496,19 +508,21 @@ void Workspaces::onMonitorFocused(std::string const& payload) {
|
|||||||
void Workspaces::onWindowOpened(std::string const& payload) {
|
void Workspaces::onWindowOpened(std::string const& payload) {
|
||||||
spdlog::trace("Window opened: {}", payload);
|
spdlog::trace("Window opened: {}", payload);
|
||||||
updateWindowCount();
|
updateWindowCount();
|
||||||
size_t lastCommaIdx = 0;
|
const auto firstComma = payload.find(',');
|
||||||
size_t nextCommaIdx = payload.find(',');
|
const auto secondComma =
|
||||||
std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx);
|
firstComma == std::string::npos ? std::string::npos : payload.find(',', firstComma + 1);
|
||||||
|
const auto thirdComma =
|
||||||
|
secondComma == std::string::npos ? std::string::npos : payload.find(',', secondComma + 1);
|
||||||
|
if (firstComma == std::string::npos || secondComma == std::string::npos ||
|
||||||
|
thirdComma == std::string::npos) {
|
||||||
|
spdlog::warn("Malformed Hyprland openwindow payload: {}", payload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lastCommaIdx = nextCommaIdx;
|
std::string windowAddress = payload.substr(0, firstComma);
|
||||||
nextCommaIdx = payload.find(',', nextCommaIdx + 1);
|
std::string workspaceName = payload.substr(firstComma + 1, secondComma - firstComma - 1);
|
||||||
std::string workspaceName = payload.substr(lastCommaIdx + 1, nextCommaIdx - lastCommaIdx - 1);
|
std::string windowClass = payload.substr(secondComma + 1, thirdComma - secondComma - 1);
|
||||||
|
std::string windowTitle = payload.substr(thirdComma + 1);
|
||||||
lastCommaIdx = nextCommaIdx;
|
|
||||||
nextCommaIdx = payload.find(',', nextCommaIdx + 1);
|
|
||||||
std::string windowClass = payload.substr(lastCommaIdx + 1, nextCommaIdx - lastCommaIdx - 1);
|
|
||||||
|
|
||||||
std::string windowTitle = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx);
|
|
||||||
|
|
||||||
bool isActive = m_currentActiveWindowAddress == windowAddress;
|
bool isActive = m_currentActiveWindowAddress == windowAddress;
|
||||||
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive);
|
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive);
|
||||||
@@ -1002,10 +1016,12 @@ void Workspaces::sortWorkspaces() {
|
|||||||
|
|
||||||
void Workspaces::setUrgentWorkspace(std::string const& windowaddress) {
|
void Workspaces::setUrgentWorkspace(std::string const& windowaddress) {
|
||||||
const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients");
|
const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients");
|
||||||
|
const std::string normalizedAddress =
|
||||||
|
windowaddress.starts_with("0x") ? windowaddress : fmt::format("0x{}", windowaddress);
|
||||||
int workspaceId = -1;
|
int workspaceId = -1;
|
||||||
|
|
||||||
for (const auto& clientJson : clientsJson) {
|
for (const auto& clientJson : clientsJson) {
|
||||||
if (clientJson["address"].asString().ends_with(windowaddress)) {
|
if (clientJson["address"].asString() == normalizedAddress) {
|
||||||
workspaceId = clientJson["workspace"]["id"].asInt();
|
workspaceId = clientJson["workspace"]["id"].asInt();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1134,7 +1150,11 @@ std::string Workspaces::makePayload(Args const&... args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::string, std::string> Workspaces::splitDoublePayload(std::string const& payload) {
|
std::pair<std::string, std::string> Workspaces::splitDoublePayload(std::string const& payload) {
|
||||||
const std::string part1 = payload.substr(0, payload.find(','));
|
const auto separator = payload.find(',');
|
||||||
|
if (separator == std::string::npos) {
|
||||||
|
throw std::invalid_argument("Expected a two-part Hyprland payload");
|
||||||
|
}
|
||||||
|
const std::string part1 = payload.substr(0, separator);
|
||||||
const std::string part2 = payload.substr(part1.size() + 1);
|
const std::string part2 = payload.substr(part1.size() + 1);
|
||||||
return {part1, part2};
|
return {part1, part2};
|
||||||
}
|
}
|
||||||
@@ -1143,6 +1163,9 @@ std::tuple<std::string, std::string, std::string> Workspaces::splitTriplePayload
|
|||||||
std::string const& payload) {
|
std::string const& payload) {
|
||||||
const size_t firstComma = payload.find(',');
|
const size_t firstComma = payload.find(',');
|
||||||
const size_t secondComma = payload.find(',', firstComma + 1);
|
const size_t secondComma = payload.find(',', firstComma + 1);
|
||||||
|
if (firstComma == std::string::npos || secondComma == std::string::npos) {
|
||||||
|
throw std::invalid_argument("Expected a three-part Hyprland payload");
|
||||||
|
}
|
||||||
|
|
||||||
const std::string part1 = payload.substr(0, firstComma);
|
const std::string part1 = payload.substr(0, firstComma);
|
||||||
const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1));
|
const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1));
|
||||||
|
|||||||
@@ -86,6 +86,24 @@ TEST_CASE("XDGRuntimeDirExistsNoHyprDir", "[getSocketFolder]") {
|
|||||||
fs::remove_all(tempDir, ec);
|
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]") {
|
TEST_CASE("getSocket1Reply throws on no socket", "[getSocket1Reply]") {
|
||||||
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
|
unsetenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||||
IPCTestHelper::resetSocketFolder();
|
IPCTestHelper::resetSocketFolder();
|
||||||
|
|||||||
Reference in New Issue
Block a user