Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -9,44 +9,47 @@
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "util/scoped_fd.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
std::filesystem::path IPC::socketFolder_;
|
||||
std::optional<bool> IPC::s_luaProtocolDetected_;
|
||||
|
||||
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() {
|
||||
// will start IPC and relay events to parseIPC
|
||||
ipcThread_ = std::thread([this]() { socketListener(); });
|
||||
socketOwnerPid_ = getpid();
|
||||
ipcThread_ = std::thread([this]() { socketListener(); });
|
||||
}
|
||||
|
||||
IPC::~IPC() {
|
||||
@@ -54,19 +57,20 @@ IPC::~IPC() {
|
||||
// failed exec()) exits.
|
||||
if (getpid() != socketOwnerPid_) return;
|
||||
|
||||
running_ = false;
|
||||
running_.store(false, std::memory_order_relaxed);
|
||||
spdlog::info("Hyprland IPC stopping...");
|
||||
if (socketfd_ != -1) {
|
||||
spdlog::trace("Shutting down socket");
|
||||
if (shutdown(socketfd_, SHUT_RDWR) == -1) {
|
||||
spdlog::error("Hyprland IPC: Couldn't shutdown socket");
|
||||
}
|
||||
spdlog::trace("Closing socket");
|
||||
if (close(socketfd_) == -1) {
|
||||
spdlog::error("Hyprland IPC: Couldn't close socket");
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(socketMutex_);
|
||||
if (socketfd_ != -1) {
|
||||
spdlog::trace("Shutting down socket");
|
||||
if (shutdown(socketfd_, SHUT_RDWR) == -1 && errno != ENOTCONN) {
|
||||
spdlog::error("Hyprland IPC: Couldn't shutdown socket");
|
||||
}
|
||||
}
|
||||
}
|
||||
ipcThread_.join();
|
||||
if (ipcThread_.joinable()) {
|
||||
ipcThread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
IPC& IPC::inst() {
|
||||
@@ -85,10 +89,10 @@ void IPC::socketListener() {
|
||||
|
||||
spdlog::info("Hyprland IPC starting");
|
||||
|
||||
struct sockaddr_un addr;
|
||||
socketfd_ = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
struct sockaddr_un addr = {};
|
||||
const int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (socketfd_ == -1) {
|
||||
if (socketfd == -1) {
|
||||
spdlog::error("Hyprland IPC: socketfd failed");
|
||||
return;
|
||||
}
|
||||
@@ -96,44 +100,76 @@ 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) {
|
||||
spdlog::error("Hyprland IPC: Unable to connect?");
|
||||
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
|
||||
spdlog::error("Hyprland IPC: Unable to connect? {}", std::strerror(errno));
|
||||
close(socketfd);
|
||||
return;
|
||||
}
|
||||
auto* file = fdopen(socketfd_, "r");
|
||||
if (file == nullptr) {
|
||||
spdlog::error("Hyprland IPC: Couldn't open file descriptor");
|
||||
return;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(socketMutex_);
|
||||
socketfd_ = socketfd;
|
||||
}
|
||||
while (running_) {
|
||||
|
||||
std::string pending;
|
||||
while (running_.load(std::memory_order_relaxed)) {
|
||||
std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes
|
||||
const ssize_t bytes_read = read(socketfd, buffer.data(), buffer.size());
|
||||
|
||||
auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file);
|
||||
|
||||
if (receivedCharPtr == nullptr) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
if (bytes_read == 0) {
|
||||
if (running_.load(std::memory_order_relaxed)) {
|
||||
spdlog::warn("Hyprland IPC: Socket closed by peer");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
std::string messageReceived(buffer.data());
|
||||
messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n'));
|
||||
spdlog::debug("hyprland IPC received {}", messageReceived);
|
||||
|
||||
try {
|
||||
parseIPC(messageReceived);
|
||||
} catch (std::exception& e) {
|
||||
spdlog::warn("Failed to parse IPC message: {}, reason: {}", messageReceived, e.what());
|
||||
} catch (...) {
|
||||
throw;
|
||||
if (bytes_read < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (!running_.load(std::memory_order_relaxed)) {
|
||||
break;
|
||||
}
|
||||
spdlog::error("Hyprland IPC: read failed: {}", std::strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
pending.append(buffer.data(), static_cast<std::size_t>(bytes_read));
|
||||
for (auto newline_pos = pending.find('\n'); newline_pos != std::string::npos;
|
||||
newline_pos = pending.find('\n')) {
|
||||
std::string messageReceived = pending.substr(0, newline_pos);
|
||||
pending.erase(0, newline_pos + 1);
|
||||
if (messageReceived.empty()) {
|
||||
continue;
|
||||
}
|
||||
spdlog::debug("hyprland IPC received {}", messageReceived);
|
||||
|
||||
try {
|
||||
parseIPC(messageReceived);
|
||||
} catch (std::exception& e) {
|
||||
spdlog::warn("Failed to parse IPC message: {}, reason: {}", messageReceived, e.what());
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(socketMutex_);
|
||||
if (socketfd_ != -1) {
|
||||
if (close(socketfd_) == -1) {
|
||||
spdlog::error("Hyprland IPC: Couldn't close socket");
|
||||
}
|
||||
socketfd_ = -1;
|
||||
}
|
||||
}
|
||||
spdlog::debug("Hyprland IPC stopped");
|
||||
}
|
||||
@@ -178,7 +214,7 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) {
|
||||
std::string IPC::getSocket1Reply(const std::string& rq) {
|
||||
// basically hyprctl
|
||||
|
||||
const auto serverSocket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
util::ScopedFd serverSocket(socket(AF_UNIX, SOCK_STREAM, 0));
|
||||
|
||||
if (serverSocket < 0) {
|
||||
throw std::runtime_error("Hyprland IPC: Couldn't open a socket (1)");
|
||||
@@ -198,8 +234,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)");
|
||||
}
|
||||
|
||||
@@ -208,28 +246,39 @@ 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);
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
spdlog::error("Hyprland IPC: Couldn't read (5)");
|
||||
close(serverSocket);
|
||||
return "";
|
||||
}
|
||||
response.append(buffer.data(), sizeWritten);
|
||||
} while (sizeWritten > 0);
|
||||
|
||||
close(serverSocket);
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -243,4 +292,69 @@ Json::Value IPC::getSocket1JsonReply(const std::string& rq) {
|
||||
return parser_.parse(reply);
|
||||
}
|
||||
|
||||
bool IPC::isLuaProtocol() {
|
||||
if (s_luaProtocolDetected_.has_value()) {
|
||||
return *s_luaProtocolDetected_;
|
||||
}
|
||||
|
||||
// Probe: send a harmless old-style dispatch and check the error.
|
||||
// In Lua-based Hyprland (>= 0.54) the error contains "hl.dispatch".
|
||||
// In older versions it returns "ok" or a different error.
|
||||
auto reply = getSocket1Reply("dispatch workspace __waybar_probe__");
|
||||
bool luaProto = reply.find("hl.dispatch") != std::string::npos;
|
||||
|
||||
if (luaProto) {
|
||||
spdlog::info("Hyprland IPC: detected Lua-based dispatch protocol (Hyprland >= 0.54)");
|
||||
} else {
|
||||
spdlog::info("Hyprland IPC: detected legacy dispatch protocol");
|
||||
}
|
||||
|
||||
s_luaProtocolDetected_ = luaProto;
|
||||
return luaProto;
|
||||
}
|
||||
|
||||
std::string IPC::buildLuaDispatch(const std::string& dispatcher, const std::string& arg) {
|
||||
// Map old-style dispatchers to the new Lua hl.dsp API.
|
||||
//
|
||||
// Old format: dispatch workspace 1
|
||||
// New format: /dispatch hl.dsp.focus({ workspace = "1" })
|
||||
//
|
||||
// Old format: dispatch focusworkspaceoncurrentmonitor 2
|
||||
// New format: /dispatch hl.dsp.focus({ workspace = "2", monitor = "current" })
|
||||
//
|
||||
// Old format: dispatch togglespecialworkspace name
|
||||
// New format: /dispatch hl.dsp.workspace.toggle_special("name")
|
||||
|
||||
if (dispatcher == "workspace") {
|
||||
return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\" })";
|
||||
}
|
||||
if (dispatcher == "focusworkspaceoncurrentmonitor") {
|
||||
return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\", monitor = \"current\" })";
|
||||
}
|
||||
if (dispatcher == "togglespecialworkspace") {
|
||||
if (arg.empty()) {
|
||||
return "/dispatch hl.dsp.workspace.toggle_special()";
|
||||
}
|
||||
return "/dispatch hl.dsp.workspace.toggle_special(\"" + arg + "\")";
|
||||
}
|
||||
|
||||
// Fallback for any other dispatcher: try the old format wrapped in dispatch().
|
||||
// This may not work for all dispatchers, but it's a reasonable default.
|
||||
spdlog::warn("Hyprland IPC: unknown dispatcher '{}' in Lua mode, attempting generic format",
|
||||
dispatcher);
|
||||
return "/dispatch hl.dsp." + dispatcher + "(\"" + arg + "\")";
|
||||
}
|
||||
|
||||
std::string IPC::dispatch(const std::string& dispatcher, const std::string& arg) {
|
||||
if (isLuaProtocol()) {
|
||||
return getSocket1Reply(buildLuaDispatch(dispatcher, arg));
|
||||
}
|
||||
// Legacy format: "dispatch <dispatcher> <arg>"
|
||||
std::string cmd = "dispatch " + dispatcher;
|
||||
if (!arg.empty()) {
|
||||
cmd += " " + arg;
|
||||
}
|
||||
return getSocket1Reply(cmd);
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
||||
|
||||
@@ -63,19 +63,35 @@ auto Language::update() -> void {
|
||||
|
||||
void Language::onEvent(const std::string& ev) {
|
||||
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:
|
||||
// activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl.,
|
||||
// with dead keys)
|
||||
std::string beforeParenthesis;
|
||||
auto parenthesisPos = ev.find_last_of('(');
|
||||
auto parenthesisPos = payload.find_last_of('(');
|
||||
if (parenthesisPos == std::string::npos) {
|
||||
beforeParenthesis = ev;
|
||||
beforeParenthesis = payload;
|
||||
} 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())
|
||||
return; // ignore
|
||||
|
||||
@@ -60,7 +60,7 @@ auto Submap::update() -> void {
|
||||
} else {
|
||||
label_.set_markup(fmt::format(fmt::runtime(format_), submap_));
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(submap_);
|
||||
label_.set_tooltip_markup(submap_);
|
||||
}
|
||||
event_box_.show();
|
||||
}
|
||||
@@ -75,7 +75,12 @@ void Submap::onEvent(const std::string& ev) {
|
||||
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;
|
||||
|
||||
|
||||
@@ -19,21 +19,19 @@ std::shared_mutex windowIpcSmtx;
|
||||
|
||||
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
|
||||
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
|
||||
|
||||
separateOutputs_ = config["separate-outputs"].asBool();
|
||||
|
||||
update();
|
||||
|
||||
// register for hyprland ipc
|
||||
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
|
||||
m_ipc.registerForIPC("activewindow", this);
|
||||
m_ipc.registerForIPC("closewindow", this);
|
||||
m_ipc.registerForIPC("movewindow", this);
|
||||
m_ipc.registerForIPC("changefloatingmode", this);
|
||||
m_ipc.registerForIPC("fullscreen", this);
|
||||
|
||||
windowIpcUniqueLock.unlock();
|
||||
|
||||
queryActiveWorkspace();
|
||||
update();
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
@@ -72,13 +70,13 @@ auto Window::update() -> void {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
if (!tooltip_format.empty()) {
|
||||
label_.set_tooltip_text(
|
||||
label_.set_tooltip_markup(
|
||||
fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName),
|
||||
fmt::arg("initialTitle", windowData_.initial_title),
|
||||
fmt::arg("class", windowData_.class_name),
|
||||
fmt::arg("initialClass", windowData_.initial_class_name)));
|
||||
} else if (!label_text.empty()) {
|
||||
label_.set_tooltip_text(label_text);
|
||||
label_.set_tooltip_markup(label_text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +122,7 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
|
||||
const auto monitors = IPC::inst().getSocket1JsonReply("monitors");
|
||||
if (monitors.isArray()) {
|
||||
auto monitor = std::ranges::find_if(
|
||||
monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; });
|
||||
monitors, [&](const Json::Value& monitor) { return monitor["name"] == monitorName; });
|
||||
if (monitor == std::end(monitors)) {
|
||||
spdlog::warn("Monitor not found: {}", monitorName);
|
||||
return Workspace{
|
||||
@@ -139,7 +137,7 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
|
||||
const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces");
|
||||
if (workspaces.isArray()) {
|
||||
auto workspace = std::ranges::find_if(
|
||||
workspaces, [&](Json::Value workspace) { return workspace["id"] == id; });
|
||||
workspaces, [&](const Json::Value& workspace) { return workspace["id"] == id; });
|
||||
if (workspace == std::end(workspaces)) {
|
||||
spdlog::warn("No workspace with id {}", id);
|
||||
return Workspace{
|
||||
@@ -177,63 +175,65 @@ auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData {
|
||||
}
|
||||
|
||||
void Window::queryActiveWorkspace() {
|
||||
std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);
|
||||
|
||||
if (separateOutputs_) {
|
||||
workspace_ = getActiveWorkspace(this->bar_.output->name);
|
||||
} else {
|
||||
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;
|
||||
if (workspace_.windows > 0) {
|
||||
const auto clients = m_ipc.getSocket1JsonReply("clients");
|
||||
if (clients.isArray()) {
|
||||
auto activeWindow = std::ranges::find_if(
|
||||
clients, [&](Json::Value window) { return window["address"] == workspace_.last_window; });
|
||||
|
||||
if (activeWindow == std::end(clients)) {
|
||||
focused_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
windowData_ = WindowData::parse(*activeWindow);
|
||||
updateAppIconName(windowData_.class_name, windowData_.initial_class_name);
|
||||
std::vector<Json::Value> workspaceWindows;
|
||||
std::ranges::copy_if(clients, std::back_inserter(workspaceWindows), [&](Json::Value window) {
|
||||
windowData_ = WindowData::parse(*activeWindow);
|
||||
updateAppIconName(windowData_.class_name, windowData_.initial_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, [&](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),
|
||||
[&](Json::Value window) { return !window["hidden"].asBool(); });
|
||||
solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(),
|
||||
[&](Json::Value window) { return !window["floating"].asBool(); });
|
||||
allFloating_ = std::ranges::all_of(
|
||||
visibleWindows, [&](Json::Value window) { return window["floating"].asBool(); });
|
||||
fullscreen_ = windowData_.fullscreen;
|
||||
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;
|
||||
}
|
||||
// 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_ = "";
|
||||
if (solo_) {
|
||||
soloClass_ = windowData_.class_name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ auto WindowCount::update() -> void {
|
||||
} else if (!format.empty()) {
|
||||
label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows));
|
||||
} else {
|
||||
label_.set_text(fmt::format("{}", workspace_.windows));
|
||||
label_.set_markup(fmt::format("{}", workspace_.windows));
|
||||
}
|
||||
|
||||
label_.show();
|
||||
@@ -79,7 +79,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac
|
||||
const auto monitors = m_ipc.getSocket1JsonReply("monitors");
|
||||
if (monitors.isArray()) {
|
||||
auto monitor = std::ranges::find_if(
|
||||
monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; });
|
||||
monitors, [&](const Json::Value& monitor) { return monitor["name"] == monitorName; });
|
||||
if (monitor == std::end(monitors)) {
|
||||
spdlog::warn("Monitor not found: {}", monitorName);
|
||||
return Workspace{
|
||||
@@ -93,7 +93,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac
|
||||
const auto workspaces = m_ipc.getSocket1JsonReply("workspaces");
|
||||
if (workspaces.isArray()) {
|
||||
auto workspace = std::ranges::find_if(
|
||||
workspaces, [&](Json::Value workspace) { return workspace["id"] == id; });
|
||||
workspaces, [&](const Json::Value& workspace) { return workspace["id"] == id; });
|
||||
if (workspace == std::end(workspaces)) {
|
||||
spdlog::warn("No workspace with id {}", id);
|
||||
return Workspace{
|
||||
|
||||
@@ -19,7 +19,7 @@ WindowCreationPayload::WindowCreationPayload(Json::Value const& client_data)
|
||||
clearWorkspaceName();
|
||||
}
|
||||
|
||||
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
|
||||
WindowCreationPayload::WindowCreationPayload(const std::string& workspace_name,
|
||||
WindowAddress window_address, WindowRepr window_repr)
|
||||
: m_window(std::move(window_repr)),
|
||||
m_windowAddress(std::move(window_address)),
|
||||
@@ -28,9 +28,10 @@ WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
|
||||
clearWorkspaceName();
|
||||
}
|
||||
|
||||
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
|
||||
WindowAddress window_address, std::string window_class,
|
||||
std::string window_title, bool is_active)
|
||||
WindowCreationPayload::WindowCreationPayload(const std::string& workspace_name,
|
||||
WindowAddress window_address,
|
||||
const std::string& window_class,
|
||||
const std::string& window_title, bool is_active)
|
||||
: m_window(std::make_pair(std::move(window_class), std::move(window_title))),
|
||||
m_windowAddress(std::move(window_address)),
|
||||
m_workspaceName(std::move(workspace_name)),
|
||||
|
||||
@@ -71,20 +71,20 @@ bool Workspace::handleClicked(GdkEventButton* bt) const {
|
||||
try {
|
||||
if (id() > 0) { // normal
|
||||
if (m_workspaceManager.moveToMonitor()) {
|
||||
m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id()));
|
||||
IPC::dispatch("focusworkspaceoncurrentmonitor", std::to_string(id()));
|
||||
} else {
|
||||
m_ipc.getSocket1Reply("dispatch workspace " + std::to_string(id()));
|
||||
IPC::dispatch("workspace", std::to_string(id()));
|
||||
}
|
||||
} else if (!isSpecial()) { // named (this includes persistent)
|
||||
if (m_workspaceManager.moveToMonitor()) {
|
||||
m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name());
|
||||
IPC::dispatch("focusworkspaceoncurrentmonitor", "name:" + name());
|
||||
} else {
|
||||
m_ipc.getSocket1Reply("dispatch workspace name:" + name());
|
||||
IPC::dispatch("workspace", "name:" + name());
|
||||
}
|
||||
} else if (id() != -99) { // named special
|
||||
m_ipc.getSocket1Reply("dispatch togglespecialworkspace " + name());
|
||||
IPC::dispatch("togglespecialworkspace", name());
|
||||
} else { // special
|
||||
m_ipc.getSocket1Reply("dispatch togglespecialworkspace");
|
||||
IPC::dispatch("togglespecialworkspace", "");
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
@@ -96,7 +96,7 @@ bool Workspace::handleClicked(GdkEventButton* bt) const {
|
||||
|
||||
void Workspace::initializeWindowMap(const Json::Value& clients_data) {
|
||||
m_windowMap.clear();
|
||||
for (auto client : clients_data) {
|
||||
for (const auto& client : clients_data) {
|
||||
if (client["workspace"]["id"].asInt() == id()) {
|
||||
insertWindow({client});
|
||||
}
|
||||
@@ -300,7 +300,7 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
||||
}
|
||||
|
||||
auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL);
|
||||
window_box->set_tooltip_text(window_repr.window_title);
|
||||
window_box->set_tooltip_markup(window_repr.window_title);
|
||||
window_box->get_style_context()->add_class("taskbar-window");
|
||||
if (window_repr.isActive) {
|
||||
window_box->get_style_context()->add_class("active");
|
||||
|
||||
@@ -34,6 +34,9 @@ Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value&
|
||||
}
|
||||
|
||||
Workspaces::~Workspaces() {
|
||||
if (m_scrollEventConnection_.connected()) {
|
||||
m_scrollEventConnection_.disconnect();
|
||||
}
|
||||
m_ipc.unregisterForIPC(this);
|
||||
// wait for possible event handler to finish
|
||||
std::lock_guard<std::mutex> lg(m_mutex);
|
||||
@@ -43,6 +46,17 @@ void Workspaces::init() {
|
||||
m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt();
|
||||
|
||||
initializeWorkspaces();
|
||||
|
||||
if (m_scrollEventConnection_.connected()) {
|
||||
m_scrollEventConnection_.disconnect();
|
||||
}
|
||||
if (barScroll()) {
|
||||
auto& window = const_cast<Bar&>(m_bar).window;
|
||||
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||
m_scrollEventConnection_ =
|
||||
window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
|
||||
}
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
@@ -148,7 +162,8 @@ void Workspaces::extendOrphans(int workspaceId, Json::Value const& clientsJson)
|
||||
}
|
||||
}
|
||||
|
||||
std::string Workspaces::getRewrite(std::string window_class, std::string window_title) {
|
||||
std::string Workspaces::getRewrite(const std::string& window_class,
|
||||
const std::string& window_title) {
|
||||
std::string windowReprKey;
|
||||
if (windowRewriteConfigUsesTitle()) {
|
||||
windowReprKey = fmt::format("class<{}> title<{}>", window_class, window_title);
|
||||
@@ -189,7 +204,7 @@ void Workspaces::initializeWorkspaces() {
|
||||
auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
|
||||
auto const clientsJson = m_ipc.getSocket1JsonReply("clients");
|
||||
|
||||
for (Json::Value workspaceJson : workspacesJson) {
|
||||
for (const auto& workspaceJson : workspacesJson) {
|
||||
std::string workspaceName = workspaceJson["name"].asString();
|
||||
if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) &&
|
||||
(!workspaceName.starts_with("special") || showSpecial()) &&
|
||||
@@ -263,7 +278,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const& clientsJs
|
||||
// key is the workspace and value is array of monitors to create on
|
||||
for (const Json::Value& monitor : value) {
|
||||
if (monitor.isString() && monitor.asString() == currentMonitor) {
|
||||
persistentWorkspacesToCreate.emplace_back(currentMonitor);
|
||||
persistentWorkspacesToCreate.emplace_back(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -328,8 +343,13 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& c
|
||||
|
||||
void Workspaces::onEvent(const std::string& ev) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
|
||||
std::string payload = ev.substr(eventName.size() + 2);
|
||||
const auto separator = ev.find(">>");
|
||||
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") {
|
||||
onWorkspaceActivated(payload);
|
||||
@@ -397,7 +417,7 @@ void Workspaces::onWorkspaceCreated(std::string const& payload, Json::Value cons
|
||||
auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules");
|
||||
auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces");
|
||||
|
||||
for (Json::Value workspaceJson : workspacesJson) {
|
||||
for (auto workspaceJson : workspacesJson) {
|
||||
const auto currentId = workspaceJson["id"].asInt();
|
||||
if (currentId == *workspaceId) {
|
||||
std::string workspaceName = workspaceJson["name"].asString();
|
||||
@@ -492,19 +512,21 @@ void Workspaces::onMonitorFocused(std::string const& payload) {
|
||||
void Workspaces::onWindowOpened(std::string const& payload) {
|
||||
spdlog::trace("Window opened: {}", payload);
|
||||
updateWindowCount();
|
||||
size_t lastCommaIdx = 0;
|
||||
size_t nextCommaIdx = payload.find(',');
|
||||
std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx);
|
||||
const auto firstComma = payload.find(',');
|
||||
const auto secondComma =
|
||||
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;
|
||||
nextCommaIdx = payload.find(',', nextCommaIdx + 1);
|
||||
std::string workspaceName = payload.substr(lastCommaIdx + 1, nextCommaIdx - lastCommaIdx - 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);
|
||||
std::string windowAddress = payload.substr(0, firstComma);
|
||||
std::string workspaceName = payload.substr(firstComma + 1, secondComma - firstComma - 1);
|
||||
std::string windowClass = payload.substr(secondComma + 1, thirdComma - secondComma - 1);
|
||||
std::string windowTitle = payload.substr(thirdComma + 1);
|
||||
|
||||
bool isActive = m_currentActiveWindowAddress == windowAddress;
|
||||
m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive);
|
||||
@@ -640,6 +662,7 @@ auto Workspaces::parseConfig(const Json::Value& config) -> void {
|
||||
populateBoolConfig(config, "persistent-only", m_persistentOnly);
|
||||
populateBoolConfig(config, "active-only", m_activeOnly);
|
||||
populateBoolConfig(config, "move-to-monitor", m_moveToMonitor);
|
||||
populateBoolConfig(config, "enable-bar-scroll", m_barScroll);
|
||||
|
||||
m_persistentWorkspaceConfig = config.get("persistent-workspaces", Json::Value());
|
||||
populateSortByConfig(config);
|
||||
@@ -997,10 +1020,12 @@ void Workspaces::sortWorkspaces() {
|
||||
|
||||
void Workspaces::setUrgentWorkspace(std::string const& windowaddress) {
|
||||
const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients");
|
||||
const std::string normalizedAddress =
|
||||
windowaddress.starts_with("0x") ? windowaddress : fmt::format("0x{}", windowaddress);
|
||||
int workspaceId = -1;
|
||||
|
||||
for (Json::Value clientJson : clientsJson) {
|
||||
if (clientJson["address"].asString().ends_with(windowaddress)) {
|
||||
for (const auto& clientJson : clientsJson) {
|
||||
if (clientJson["address"].asString() == normalizedAddress) {
|
||||
workspaceId = clientJson["workspace"]["id"].asInt();
|
||||
break;
|
||||
}
|
||||
@@ -1129,7 +1154,11 @@ std::string Workspaces::makePayload(Args const&... args) {
|
||||
}
|
||||
|
||||
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);
|
||||
return {part1, part2};
|
||||
}
|
||||
@@ -1138,6 +1167,9 @@ std::tuple<std::string, std::string, std::string> Workspaces::splitTriplePayload
|
||||
std::string const& payload) {
|
||||
const size_t firstComma = payload.find(',');
|
||||
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 part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1));
|
||||
@@ -1155,4 +1187,31 @@ std::optional<int> Workspaces::parseWorkspaceId(std::string const& workspaceIdSt
|
||||
}
|
||||
}
|
||||
|
||||
bool Workspaces::handleScroll(GdkEventScroll* e) {
|
||||
// Ignore emulated scroll events on window
|
||||
if (gdk_event_get_pointer_emulated((GdkEvent*)e)) {
|
||||
return false;
|
||||
}
|
||||
auto dir = AModule::getScrollDir(e);
|
||||
if (dir == SCROLL_DIR::NONE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {
|
||||
if (allOutputs()) {
|
||||
IPC::dispatch("workspace", "e+1");
|
||||
} else {
|
||||
IPC::dispatch("workspace", "m+1");
|
||||
}
|
||||
} else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {
|
||||
if (allOutputs()) {
|
||||
IPC::dispatch("workspace", "e-1");
|
||||
} else {
|
||||
IPC::dispatch("workspace", "m-1");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
||||
|
||||
Reference in New Issue
Block a user