Merge remote-tracking branch 'origin/master' into hyprland-window_no_title_from_special
# Conflicts: # src/modules/hyprland/window.cpp
This commit is contained in:
@@ -9,90 +9,148 @@
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#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) {
|
||||
// socket path, specified by EventManager of Hyprland
|
||||
if (!socketFolder_.empty()) {
|
||||
return socketFolder_;
|
||||
static std::mutex folderMutex;
|
||||
std::unique_lock lock(folderMutex);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void IPC::startIPC() {
|
||||
IPC::IPC() {
|
||||
// will start IPC and relay events to parseIPC
|
||||
socketOwnerPid_ = getpid();
|
||||
ipcThread_ = std::thread([this]() { socketListener(); });
|
||||
}
|
||||
|
||||
std::thread([&]() {
|
||||
// check for hyprland
|
||||
const char* his = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
IPC::~IPC() {
|
||||
// Do no stop Hyprland IPC if a child process (with successful fork() but
|
||||
// failed exec()) exits.
|
||||
if (getpid() != socketOwnerPid_) return;
|
||||
|
||||
if (his == nullptr) {
|
||||
spdlog::warn("Hyprland is not running, Hyprland IPC will not be available.");
|
||||
return;
|
||||
running_.store(false, std::memory_order_relaxed);
|
||||
spdlog::info("Hyprland IPC stopping...");
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ipcThread_.joinable()) {
|
||||
ipcThread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
IPC& IPC::inst() {
|
||||
static IPC ipc;
|
||||
return ipc;
|
||||
}
|
||||
|
||||
void IPC::socketListener() {
|
||||
// check for hyprland
|
||||
const char* his = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
|
||||
if (his == nullptr) {
|
||||
spdlog::warn("Hyprland is not running, Hyprland IPC will not be available.");
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::info("Hyprland IPC starting");
|
||||
|
||||
struct sockaddr_un addr = {};
|
||||
const int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (socketfd == -1) {
|
||||
spdlog::error("Hyprland IPC: socketfd failed");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
int l = sizeof(struct sockaddr_un);
|
||||
|
||||
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
|
||||
spdlog::error("Hyprland IPC: Unable to connect? {}", std::strerror(errno));
|
||||
close(socketfd);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(socketMutex_);
|
||||
socketfd_ = socketfd;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
if (bytes_read == 0) {
|
||||
if (running_.load(std::memory_order_relaxed)) {
|
||||
spdlog::warn("Hyprland IPC: Socket closed by peer");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!modulesReady) return;
|
||||
|
||||
spdlog::info("Hyprland IPC starting");
|
||||
|
||||
struct sockaddr_un addr;
|
||||
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (socketfd == -1) {
|
||||
spdlog::error("Hyprland IPC: socketfd failed");
|
||||
return;
|
||||
}
|
||||
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock";
|
||||
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?");
|
||||
return;
|
||||
}
|
||||
|
||||
auto* file = fdopen(socketfd, "r");
|
||||
|
||||
while (true) {
|
||||
std::array<char, 1024> buffer; // Hyprland socket2 events are max 1024 bytes
|
||||
|
||||
auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file);
|
||||
|
||||
if (receivedCharPtr == nullptr) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
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::string messageReceived(buffer.data());
|
||||
messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n'));
|
||||
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 {
|
||||
@@ -102,10 +160,18 @@ void IPC::startIPC() {
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
void IPC::parseIPC(const std::string& ev) {
|
||||
@@ -148,30 +214,18 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) {
|
||||
std::string IPC::getSocket1Reply(const std::string& rq) {
|
||||
// basically hyprctl
|
||||
|
||||
struct addrinfo aiHints;
|
||||
struct addrinfo* aiRes = nullptr;
|
||||
const auto serverSocket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
util::ScopedFd serverSocket(socket(AF_UNIX, SOCK_STREAM, 0));
|
||||
|
||||
if (serverSocket < 0) {
|
||||
spdlog::error("Hyprland IPC: Couldn't open a socket (1)");
|
||||
return "";
|
||||
}
|
||||
|
||||
memset(&aiHints, 0, sizeof(struct addrinfo));
|
||||
aiHints.ai_family = AF_UNSPEC;
|
||||
aiHints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
if (getaddrinfo("localhost", nullptr, &aiHints, &aiRes) != 0) {
|
||||
spdlog::error("Hyprland IPC: Couldn't get host (2)");
|
||||
return "";
|
||||
throw std::runtime_error("Hyprland IPC: Couldn't open a socket (1)");
|
||||
}
|
||||
|
||||
// get the instance signature
|
||||
auto* instanceSig = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
|
||||
if (instanceSig == nullptr) {
|
||||
spdlog::error("Hyprland IPC: HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)");
|
||||
return "";
|
||||
throw std::runtime_error(
|
||||
"Hyprland IPC: HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)");
|
||||
}
|
||||
|
||||
sockaddr_un serverAddress = {0};
|
||||
@@ -180,40 +234,51 @@ 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) {
|
||||
spdlog::error("Hyprland IPC: Couldn't copy socket path (6)");
|
||||
return "";
|
||||
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)");
|
||||
}
|
||||
|
||||
if (connect(serverSocket, reinterpret_cast<sockaddr*>(&serverAddress), sizeof(serverAddress)) <
|
||||
0) {
|
||||
spdlog::error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)");
|
||||
return "";
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -227,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", on_current_monitor = true })
|
||||
//
|
||||
// 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 + "\", on_current_monitor = true })";
|
||||
}
|
||||
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
|
||||
|
||||
@@ -10,13 +10,7 @@
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
Language::Language(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: ALabel(config, "language", id, "{}", 0, true), bar_(bar) {
|
||||
modulesReady = true;
|
||||
|
||||
if (!gIPC) {
|
||||
gIPC = std::make_unique<IPC>();
|
||||
}
|
||||
|
||||
: ALabel(config, "language", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
|
||||
// get the active layout when open
|
||||
initLanguage();
|
||||
|
||||
@@ -24,11 +18,11 @@ Language::Language(const std::string& id, const Bar& bar, const Json::Value& con
|
||||
update();
|
||||
|
||||
// register for hyprland ipc
|
||||
gIPC->registerForIPC("activelayout", this);
|
||||
m_ipc.registerForIPC("activelayout", this);
|
||||
}
|
||||
|
||||
Language::~Language() {
|
||||
gIPC->unregisterForIPC(this);
|
||||
m_ipc.unregisterForIPC(this);
|
||||
// wait for possible event handler to finish
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
}
|
||||
@@ -69,8 +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(','));
|
||||
auto layoutName = ev.substr(ev.find_last_of(',') + 1);
|
||||
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 = payload.find_last_of('(');
|
||||
if (parenthesisPos == std::string::npos) {
|
||||
beforeParenthesis = payload;
|
||||
} else {
|
||||
beforeParenthesis = payload.substr(0, parenthesisPos);
|
||||
}
|
||||
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
|
||||
@@ -85,7 +106,7 @@ void Language::onEvent(const std::string& ev) {
|
||||
}
|
||||
|
||||
void Language::initLanguage() {
|
||||
const auto inputDevices = gIPC->getSocket1Reply("devices");
|
||||
const auto inputDevices = m_ipc.getSocket1Reply("devices");
|
||||
|
||||
const auto kbName = config_["keyboard-name"].asString();
|
||||
|
||||
|
||||
@@ -2,37 +2,29 @@
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "util/sanitize_str.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: ALabel(config, "submap", id, "{}", 0, true), bar_(bar) {
|
||||
modulesReady = true;
|
||||
|
||||
: ALabel(config, "submap", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
|
||||
parseConfig(config);
|
||||
|
||||
if (!gIPC) {
|
||||
gIPC = std::make_unique<IPC>();
|
||||
}
|
||||
|
||||
label_.hide();
|
||||
ALabel::update();
|
||||
|
||||
// Displays widget immediately if always_on_ assuming default submap
|
||||
// Needs an actual way to retrive current submap on startup
|
||||
// Needs an actual way to retrieve current submap on startup
|
||||
if (always_on_) {
|
||||
submap_ = default_submap_;
|
||||
label_.get_style_context()->add_class(submap_);
|
||||
}
|
||||
|
||||
// register for hyprland ipc
|
||||
gIPC->registerForIPC("submap", this);
|
||||
m_ipc.registerForIPC("submap", this);
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Submap::~Submap() {
|
||||
gIPC->unregisterForIPC(this);
|
||||
m_ipc.unregisterForIPC(this);
|
||||
// wait for possible event handler to finish
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
}
|
||||
@@ -52,12 +44,23 @@ auto Submap::parseConfig(const Json::Value& config) -> void {
|
||||
auto Submap::update() -> void {
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
|
||||
// Handle style class changes
|
||||
if (!prev_submap_.empty()) {
|
||||
label_.get_style_context()->remove_class(prev_submap_);
|
||||
}
|
||||
|
||||
if (!submap_.empty()) {
|
||||
label_.get_style_context()->add_class(submap_);
|
||||
}
|
||||
|
||||
prev_submap_ = submap_;
|
||||
|
||||
if (submap_.empty()) {
|
||||
event_box_.hide();
|
||||
} 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();
|
||||
}
|
||||
@@ -72,12 +75,12 @@ void Submap::onEvent(const std::string& ev) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto submapName = ev.substr(ev.find_last_of('>') + 1);
|
||||
submapName = waybar::util::sanitize_string(submapName);
|
||||
|
||||
if (!submap_.empty()) {
|
||||
label_.get_style_context()->remove_class(submap_);
|
||||
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;
|
||||
|
||||
@@ -85,8 +88,6 @@ void Submap::onEvent(const std::string& ev) {
|
||||
submap_ = default_submap_;
|
||||
}
|
||||
|
||||
label_.get_style_context()->add_class(submap_);
|
||||
|
||||
spdlog::debug("hyprland submap onevent with {}", submap_);
|
||||
|
||||
dp.emit();
|
||||
|
||||
+138
-111
@@ -6,6 +6,7 @@
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <shared_mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/hyprland/backend.hpp"
|
||||
@@ -14,54 +15,71 @@
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
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) {
|
||||
modulesReady = true;
|
||||
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
|
||||
separateOutputs_ = config["separate-outputs"].asBool();
|
||||
|
||||
if (!gIPC) {
|
||||
gIPC = std::make_unique<IPC>();
|
||||
}
|
||||
|
||||
queryActiveWorkspace();
|
||||
update();
|
||||
dp.emit();
|
||||
|
||||
// register for hyprland ipc
|
||||
gIPC->registerForIPC("activewindow", this);
|
||||
gIPC->registerForIPC("closewindow", this);
|
||||
gIPC->registerForIPC("movewindow", this);
|
||||
gIPC->registerForIPC("changefloatingmode", this);
|
||||
gIPC->registerForIPC("fullscreen", this);
|
||||
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();
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Window::~Window() {
|
||||
gIPC->unregisterForIPC(this);
|
||||
// wait for possible event handler to finish
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
|
||||
m_ipc.unregisterForIPC(this);
|
||||
}
|
||||
|
||||
auto Window::update() -> void {
|
||||
// fix ampersands
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
std::shared_lock<std::shared_mutex> windowIpcShareLock(windowIpcSmtx);
|
||||
|
||||
queryActiveWorkspace();
|
||||
|
||||
std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title);
|
||||
std::string windowAddress = workspace_.last_window;
|
||||
|
||||
windowData_.title = windowName;
|
||||
|
||||
std::string label_text;
|
||||
if (!format_.empty()) {
|
||||
label_.show();
|
||||
label_.set_markup(waybar::util::rewriteString(
|
||||
label_text = waybar::util::rewriteString(
|
||||
fmt::format(fmt::runtime(format_), fmt::arg("title", windowName),
|
||||
fmt::arg("initialTitle", windowData_.initial_title),
|
||||
fmt::arg("class", windowData_.class_name),
|
||||
fmt::arg("initialClass", windowData_.initial_class_name)),
|
||||
config_["rewrite"]));
|
||||
config_["rewrite"]);
|
||||
label_.set_markup(label_text);
|
||||
} else {
|
||||
label_.hide();
|
||||
}
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
std::string tooltip_format;
|
||||
if (config_["tooltip-format"].isString()) {
|
||||
tooltip_format = config_["tooltip-format"].asString();
|
||||
}
|
||||
if (!tooltip_format.empty()) {
|
||||
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_markup(label_text);
|
||||
}
|
||||
}
|
||||
|
||||
if (focused_) {
|
||||
image_.show();
|
||||
} else {
|
||||
@@ -90,121 +108,130 @@ auto Window::update() -> void {
|
||||
AAppIconLabel::update();
|
||||
}
|
||||
|
||||
auto Window::getActiveWorkspace(const std::string& monitorName = "") -> Workspace {
|
||||
const auto monitors = gIPC->getSocket1JsonReply("monitors");
|
||||
assert(monitors.isArray());
|
||||
auto monitor = std::find_if(monitors.begin(), monitors.end(), [&](Json::Value monitor) {
|
||||
return monitorName == "" ? monitor["focused"].asBool() : monitor["name"] == monitorName;
|
||||
});
|
||||
if (monitor == std::end(monitors)) {
|
||||
spdlog::warn("Monitor not found: {}", monitorName);
|
||||
return Workspace{-1, 0, "", ""};
|
||||
}
|
||||
const int special_id = (*monitor)["specialWorkspace"]["id"].asInt();
|
||||
const int id = special_id != 0 ? special_id : (*monitor)["activeWorkspace"]["id"].asInt();
|
||||
auto Window::getActiveWorkspace() -> Workspace { return getActiveWorkspace(""); }
|
||||
|
||||
const auto workspaces = gIPC->getSocket1JsonReply("workspaces");
|
||||
assert(workspaces.isArray());
|
||||
auto workspace = std::find_if(workspaces.begin(), workspaces.end(),
|
||||
[&](Json::Value workspace) { return workspace["id"] == id; });
|
||||
if (workspace == std::end(workspaces)) {
|
||||
spdlog::warn("No workspace with id {}", id);
|
||||
return Workspace{-1, 0, "", ""};
|
||||
}
|
||||
return Workspace::parse(*workspace);
|
||||
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, [&](const Json::Value& monitor) {
|
||||
return monitorName.empty() ? monitor["focused"].asBool() : monitor["name"] == monitorName;
|
||||
});
|
||||
if (monitor == std::end(monitors)) {
|
||||
spdlog::warn("Monitor not found: {}", monitorName);
|
||||
return Workspace{
|
||||
.id = -1,
|
||||
.windows = 0,
|
||||
.last_window = "",
|
||||
.last_window_title = "",
|
||||
};
|
||||
}
|
||||
const int special_id = (*monitor)["specialWorkspace"]["id"].asInt();
|
||||
const int id = special_id != 0 ? special_id : (*monitor)["activeWorkspace"]["id"].asInt();
|
||||
|
||||
const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces");
|
||||
if (workspaces.isArray()) {
|
||||
auto workspace = std::ranges::find_if(
|
||||
workspaces, [&](const Json::Value& workspace) { return workspace["id"] == id; });
|
||||
if (workspace == std::end(workspaces)) {
|
||||
spdlog::warn("No workspace with id {}", id);
|
||||
return Workspace{
|
||||
.id = -1,
|
||||
.windows = 0,
|
||||
.last_window = "",
|
||||
.last_window_title = "",
|
||||
};
|
||||
}
|
||||
return Workspace::parse(*workspace);
|
||||
};
|
||||
};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Window::Workspace::parse(const Json::Value& value) -> Window::Workspace {
|
||||
return Workspace{
|
||||
value["id"].asInt(),
|
||||
value["windows"].asInt(),
|
||||
value["lastwindow"].asString(),
|
||||
value["lastwindowtitle"].asString(),
|
||||
.id = value["id"].asInt(),
|
||||
.windows = value["windows"].asInt(),
|
||||
.last_window = value["lastwindow"].asString(),
|
||||
.last_window_title = value["lastwindowtitle"].asString(),
|
||||
};
|
||||
}
|
||||
|
||||
auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData {
|
||||
return WindowData{value["floating"].asBool(), value["monitor"].asInt(),
|
||||
value["class"].asString(), value["initialClass"].asString(),
|
||||
value["title"].asString(), value["initialTitle"].asString(),
|
||||
value["fullscreen"].asBool(), !value["grouped"].empty()};
|
||||
return WindowData{.floating = value["floating"].asBool(),
|
||||
.monitor = value["monitor"].asInt(),
|
||||
.class_name = value["class"].asString(),
|
||||
.initial_class_name = value["initialClass"].asString(),
|
||||
.title = value["title"].asString(),
|
||||
.initial_title = value["initialTitle"].asString(),
|
||||
.fullscreen = value["fullscreen"].asBool(),
|
||||
.grouped = !value["grouped"].empty()};
|
||||
}
|
||||
|
||||
void Window::queryActiveWorkspace() {
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
|
||||
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 = gIPC->getSocket1JsonReply("clients");
|
||||
assert(clients.isArray());
|
||||
auto activeWindow = std::find_if(clients.begin(), clients.end(), [&](Json::Value window) {
|
||||
return window["address"] == workspace_.last_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, [&](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)) {
|
||||
focused_ = false;
|
||||
return;
|
||||
}
|
||||
// Fullscreen windows look like they are solo
|
||||
if (fullscreen_) {
|
||||
solo_ = true;
|
||||
}
|
||||
|
||||
windowData_ = WindowData::parse(*activeWindow);
|
||||
updateAppIconName(windowData_.class_name, windowData_.initial_class_name);
|
||||
std::vector<Json::Value> workspaceWindows;
|
||||
std::copy_if(clients.begin(), clients.end(), std::back_inserter(workspaceWindows),
|
||||
[&](Json::Value window) {
|
||||
return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool();
|
||||
});
|
||||
swallowing_ =
|
||||
std::any_of(workspaceWindows.begin(), workspaceWindows.end(), [&](Json::Value window) {
|
||||
return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0";
|
||||
});
|
||||
std::vector<Json::Value> visibleWindows;
|
||||
std::copy_if(workspaceWindows.begin(), workspaceWindows.end(),
|
||||
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::all_of(visibleWindows.begin(), visibleWindows.end(),
|
||||
[&](Json::Value window) { return window["floating"].asBool(); });
|
||||
fullscreen_ = windowData_.fullscreen;
|
||||
|
||||
// Fullscreen windows look like they are solo
|
||||
if (fullscreen_) {
|
||||
solo_ = true;
|
||||
}
|
||||
|
||||
// Grouped windows have a tab bar and therefore don't look fullscreen or solo
|
||||
if (windowData_.grouped) {
|
||||
fullscreen_ = false;
|
||||
solo_ = false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void Window::onEvent(const std::string& ev) {
|
||||
queryActiveWorkspace();
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
void Window::onEvent(const std::string& ev) { dp.emit(); }
|
||||
|
||||
void Window::setClass(const std::string& classname, bool enable) {
|
||||
if (enable) {
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
#include "modules/hyprland/windowcount.hpp"
|
||||
|
||||
#include <glibmm/fileutils.h>
|
||||
#include <glibmm/keyfile.h>
|
||||
#include <glibmm/miscutils.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/hyprland/backend.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
WindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: AAppIconLabel(config, "windowcount", id, "{count}", 0, true), bar_(bar), m_ipc(IPC::inst()) {
|
||||
separateOutputs_ =
|
||||
config.isMember("separate-outputs") ? config["separate-outputs"].asBool() : true;
|
||||
|
||||
queryActiveWorkspace();
|
||||
update();
|
||||
dp.emit();
|
||||
|
||||
// register for hyprland ipc
|
||||
m_ipc.registerForIPC("fullscreen", this);
|
||||
m_ipc.registerForIPC("workspace", this);
|
||||
m_ipc.registerForIPC("focusedmon", this);
|
||||
m_ipc.registerForIPC("openwindow", this);
|
||||
m_ipc.registerForIPC("closewindow", this);
|
||||
m_ipc.registerForIPC("movewindow", this);
|
||||
}
|
||||
|
||||
WindowCount::~WindowCount() {
|
||||
m_ipc.unregisterForIPC(this);
|
||||
// wait for possible event handler to finish
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
}
|
||||
|
||||
auto WindowCount::update() -> void {
|
||||
std::lock_guard<std::mutex> lg(mutex_);
|
||||
|
||||
queryActiveWorkspace();
|
||||
|
||||
std::string format = config_["format"].asString();
|
||||
std::string formatEmpty = config_["format-empty"].asString();
|
||||
std::string formatWindowed = config_["format-windowed"].asString();
|
||||
std::string formatFullscreen = config_["format-fullscreen"].asString();
|
||||
|
||||
setClass("empty", workspace_.windows == 0);
|
||||
setClass("fullscreen", workspace_.hasfullscreen);
|
||||
|
||||
if (workspace_.windows == 0 && !formatEmpty.empty()) {
|
||||
label_.set_markup(fmt::format(fmt::runtime(formatEmpty), workspace_.windows));
|
||||
} else if (!workspace_.hasfullscreen && !formatWindowed.empty()) {
|
||||
label_.set_markup(fmt::format(fmt::runtime(formatWindowed), workspace_.windows));
|
||||
} else if (workspace_.hasfullscreen && !formatFullscreen.empty()) {
|
||||
label_.set_markup(fmt::format(fmt::runtime(formatFullscreen), workspace_.windows));
|
||||
} else if (!format.empty()) {
|
||||
label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows));
|
||||
} else {
|
||||
label_.set_markup(fmt::format("{}", workspace_.windows));
|
||||
}
|
||||
|
||||
label_.show();
|
||||
AAppIconLabel::update();
|
||||
}
|
||||
|
||||
auto WindowCount::getActiveWorkspace() -> Workspace {
|
||||
const auto workspace = m_ipc.getSocket1JsonReply("activeworkspace");
|
||||
|
||||
if (workspace.isObject()) {
|
||||
return Workspace::parse(workspace);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspace {
|
||||
const auto monitors = m_ipc.getSocket1JsonReply("monitors");
|
||||
if (monitors.isArray()) {
|
||||
auto monitor = std::ranges::find_if(
|
||||
monitors, [&](const Json::Value& monitor) { return monitor["name"] == monitorName; });
|
||||
if (monitor == std::end(monitors)) {
|
||||
spdlog::warn("Monitor not found: {}", monitorName);
|
||||
return Workspace{
|
||||
.id = -1,
|
||||
.windows = 0,
|
||||
.hasfullscreen = false,
|
||||
};
|
||||
}
|
||||
const int id = (*monitor)["activeWorkspace"]["id"].asInt();
|
||||
|
||||
const auto workspaces = m_ipc.getSocket1JsonReply("workspaces");
|
||||
if (workspaces.isArray()) {
|
||||
auto workspace = std::ranges::find_if(
|
||||
workspaces, [&](const Json::Value& workspace) { return workspace["id"] == id; });
|
||||
if (workspace == std::end(workspaces)) {
|
||||
spdlog::warn("No workspace with id {}", id);
|
||||
return Workspace{
|
||||
.id = -1,
|
||||
.windows = 0,
|
||||
.hasfullscreen = false,
|
||||
};
|
||||
}
|
||||
return Workspace::parse(*workspace);
|
||||
};
|
||||
};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Workspace {
|
||||
return Workspace{
|
||||
.id = value["id"].asInt(),
|
||||
.windows = value["windows"].asInt(),
|
||||
.hasfullscreen = value["hasfullscreen"].asBool(),
|
||||
};
|
||||
}
|
||||
|
||||
void WindowCount::queryActiveWorkspace() {
|
||||
if (separateOutputs_) {
|
||||
workspace_ = getActiveWorkspace(this->bar_.output->name);
|
||||
} else {
|
||||
workspace_ = getActiveWorkspace();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowCount::onEvent(const std::string& ev) { dp.emit(); }
|
||||
|
||||
void WindowCount::setClass(const std::string& classname, bool enable) {
|
||||
if (enable) {
|
||||
if (!bar_.window.get_style_context()->has_class(classname)) {
|
||||
bar_.window.get_style_context()->add_class(classname);
|
||||
}
|
||||
} else {
|
||||
bar_.window.get_style_context()->remove_class(classname);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
WindowCreationPayload::WindowCreationPayload(Json::Value const &client_data)
|
||||
WindowCreationPayload::WindowCreationPayload(Json::Value const& client_data)
|
||||
: m_window(std::make_pair(client_data["class"].asString(), client_data["title"].asString())),
|
||||
m_windowAddress(client_data["address"].asString()),
|
||||
m_workspaceName(client_data["workspace"]["name"].asString()) {
|
||||
@@ -19,8 +19,8 @@ WindowCreationPayload::WindowCreationPayload(Json::Value const &client_data)
|
||||
clearWorkspaceName();
|
||||
}
|
||||
|
||||
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
|
||||
WindowAddress window_address, std::string window_repr)
|
||||
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)),
|
||||
m_workspaceName(std::move(workspace_name)) {
|
||||
@@ -28,12 +28,14 @@ WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
|
||||
clearWorkspaceName();
|
||||
}
|
||||
|
||||
WindowCreationPayload::WindowCreationPayload(std::string workspace_name,
|
||||
WindowAddress window_address, std::string window_class,
|
||||
std::string window_title)
|
||||
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)) {
|
||||
m_workspaceName(std::move(workspace_name)),
|
||||
m_isActive(is_active) {
|
||||
clearAddr();
|
||||
clearWorkspaceName();
|
||||
}
|
||||
@@ -72,7 +74,7 @@ void WindowCreationPayload::clearWorkspaceName() {
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowCreationPayload::isEmpty(Workspaces &workspace_manager) {
|
||||
bool WindowCreationPayload::isEmpty(Workspaces& workspace_manager) {
|
||||
if (std::holds_alternative<Repr>(m_window)) {
|
||||
return std::get<Repr>(m_window).empty();
|
||||
}
|
||||
@@ -88,17 +90,18 @@ bool WindowCreationPayload::isEmpty(Workspaces &workspace_manager) {
|
||||
|
||||
int WindowCreationPayload::incrementTimeSpentUncreated() { return m_timeSpentUncreated++; }
|
||||
|
||||
void WindowCreationPayload::moveToWorksace(std::string &new_workspace_name) {
|
||||
void WindowCreationPayload::moveToWorkspace(std::string& new_workspace_name) {
|
||||
m_workspaceName = new_workspace_name;
|
||||
}
|
||||
|
||||
std::string WindowCreationPayload::repr(Workspaces &workspace_manager) {
|
||||
WindowRepr WindowCreationPayload::repr(Workspaces& workspace_manager) {
|
||||
if (std::holds_alternative<Repr>(m_window)) {
|
||||
return std::get<Repr>(m_window);
|
||||
}
|
||||
if (std::holds_alternative<ClassAndTitle>(m_window)) {
|
||||
auto [window_class, window_title] = std::get<ClassAndTitle>(m_window);
|
||||
return workspace_manager.getRewrite(window_class, window_title);
|
||||
auto const& [window_class, window_title] = std::get<ClassAndTitle>(m_window);
|
||||
return {m_windowAddress, window_class, window_title,
|
||||
workspace_manager.getRewrite(window_class, window_title), m_isActive};
|
||||
}
|
||||
// Unreachable
|
||||
spdlog::error("WorkspaceWindow::repr: Unreachable");
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
#include <utility>
|
||||
|
||||
#include "modules/hyprland/workspaces.hpp"
|
||||
#include "util/command.hpp"
|
||||
#include "util/icon_loader.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_manager,
|
||||
const Json::Value &clients_data)
|
||||
Workspace::Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager,
|
||||
const Json::Value& clients_data)
|
||||
: m_workspaceManager(workspace_manager),
|
||||
m_id(workspace_data["id"].asInt()),
|
||||
m_name(workspace_data["name"].asString()),
|
||||
@@ -18,7 +20,8 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma
|
||||
m_windows(workspace_data["windows"].asInt()),
|
||||
m_isActive(true),
|
||||
m_isPersistentRule(workspace_data["persistent-rule"].asBool()),
|
||||
m_isPersistentConfig(workspace_data["persistent-config"].asBool()) {
|
||||
m_isPersistentConfig(workspace_data["persistent-config"].asBool()),
|
||||
m_ipc(IPC::inst()) {
|
||||
if (m_name.starts_with("name:")) {
|
||||
m_name = m_name.substr(5);
|
||||
} else if (m_name.starts_with("special")) {
|
||||
@@ -31,14 +34,19 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma
|
||||
false);
|
||||
|
||||
m_button.set_relief(Gtk::RELIEF_NONE);
|
||||
m_content.set_center_widget(m_label);
|
||||
if (m_workspaceManager.enableTaskbar()) {
|
||||
m_content.set_orientation(m_workspaceManager.taskbarOrientation());
|
||||
m_content.pack_start(m_labelBefore, false, false);
|
||||
} else {
|
||||
m_content.set_center_widget(m_labelBefore);
|
||||
}
|
||||
m_button.add(m_content);
|
||||
|
||||
initializeWindowMap(clients_data);
|
||||
}
|
||||
|
||||
void addOrRemoveClass(const Glib::RefPtr<Gtk::StyleContext> &context, bool condition,
|
||||
const std::string &class_name) {
|
||||
void addOrRemoveClass(const Glib::RefPtr<Gtk::StyleContext>& context, bool condition,
|
||||
const std::string& class_name) {
|
||||
if (condition) {
|
||||
context->add_class(class_name);
|
||||
} else {
|
||||
@@ -46,71 +54,105 @@ void addOrRemoveClass(const Glib::RefPtr<Gtk::StyleContext> &context, bool condi
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> Workspace::closeWindow(WindowAddress const &addr) {
|
||||
if (m_windowMap.contains(addr)) {
|
||||
return removeWindow(addr);
|
||||
std::optional<WindowRepr> Workspace::closeWindow(WindowAddress const& addr) {
|
||||
auto it = std::ranges::find_if(m_windowMap,
|
||||
[&addr](const auto& window) { return window.address == addr; });
|
||||
// If the vector contains the address, remove it and return the window representation
|
||||
if (it != m_windowMap.end()) {
|
||||
WindowRepr windowRepr = *it;
|
||||
m_windowMap.erase(it);
|
||||
return windowRepr;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool Workspace::handleClicked(GdkEventButton *bt) const {
|
||||
bool Workspace::handleClicked(GdkEventButton* bt) const {
|
||||
if (bt->type == GDK_BUTTON_PRESS) {
|
||||
try {
|
||||
if (id() > 0) { // normal
|
||||
if (m_workspaceManager.moveToMonitor()) {
|
||||
gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id()));
|
||||
IPC::dispatch("focusworkspaceoncurrentmonitor", std::to_string(id()));
|
||||
} else {
|
||||
gIPC->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()) {
|
||||
gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name());
|
||||
IPC::dispatch("focusworkspaceoncurrentmonitor", "name:" + name());
|
||||
} else {
|
||||
gIPC->getSocket1Reply("dispatch workspace name:" + name());
|
||||
IPC::dispatch("workspace", "name:" + name());
|
||||
}
|
||||
} else if (id() != -99) { // named special
|
||||
gIPC->getSocket1Reply("dispatch togglespecialworkspace " + name());
|
||||
IPC::dispatch("togglespecialworkspace", name());
|
||||
} else { // special
|
||||
gIPC->getSocket1Reply("dispatch togglespecialworkspace");
|
||||
IPC::dispatch("togglespecialworkspace", "");
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Failed to dispatch workspace: {}", e.what());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Workspace::initializeWindowMap(const Json::Value &clients_data) {
|
||||
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});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Workspace::insertWindow(WindowCreationPayload create_window_paylod) {
|
||||
if (!create_window_paylod.isEmpty(m_workspaceManager)) {
|
||||
m_windowMap[create_window_paylod.getAddress()] = create_window_paylod.repr(m_workspaceManager);
|
||||
void Workspace::setActiveWindow(WindowAddress const& addr) {
|
||||
std::optional<long> activeIdx;
|
||||
for (size_t i = 0; i < m_windowMap.size(); ++i) {
|
||||
auto& window = m_windowMap[i];
|
||||
bool isActive = (window.address == addr);
|
||||
window.setActive(isActive);
|
||||
if (isActive) {
|
||||
activeIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
auto activeWindowPos = m_workspaceManager.activeWindowPosition();
|
||||
if (activeIdx.has_value() && activeWindowPos != Workspaces::ActiveWindowPosition::NONE) {
|
||||
auto window = std::move(m_windowMap[*activeIdx]);
|
||||
m_windowMap.erase(m_windowMap.begin() + *activeIdx);
|
||||
if (activeWindowPos == Workspaces::ActiveWindowPosition::FIRST) {
|
||||
m_windowMap.insert(m_windowMap.begin(), std::move(window));
|
||||
} else if (activeWindowPos == Workspaces::ActiveWindowPosition::LAST) {
|
||||
m_windowMap.emplace_back(std::move(window));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Workspace::insertWindow(WindowCreationPayload create_window_payload) {
|
||||
if (!create_window_payload.isEmpty(m_workspaceManager)) {
|
||||
auto repr = create_window_payload.repr(m_workspaceManager);
|
||||
|
||||
if (!repr.empty() || m_workspaceManager.enableTaskbar()) {
|
||||
auto addr = create_window_payload.getAddress();
|
||||
auto it = std::ranges::find_if(
|
||||
m_windowMap, [&addr](const auto& window) { return window.address == addr; });
|
||||
// If the vector contains the address, update the window representation, otherwise insert it
|
||||
if (it != m_windowMap.end()) {
|
||||
*it = repr;
|
||||
} else {
|
||||
m_windowMap.emplace_back(repr);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_paylod) {
|
||||
if (create_window_paylod.getWorkspaceName() == name()) {
|
||||
insertWindow(create_window_paylod);
|
||||
bool Workspace::onWindowOpened(WindowCreationPayload const& create_window_payload) {
|
||||
if (create_window_payload.getWorkspaceName() == name()) {
|
||||
insertWindow(create_window_payload);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string Workspace::removeWindow(WindowAddress const &addr) {
|
||||
std::string windowRepr = m_windowMap[addr];
|
||||
m_windowMap.erase(addr);
|
||||
return windowRepr;
|
||||
}
|
||||
|
||||
std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map) {
|
||||
std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map) {
|
||||
spdlog::trace("Selecting icon for workspace {}", name());
|
||||
if (isUrgent()) {
|
||||
auto urgentIconIt = icons_map.find("urgent");
|
||||
@@ -167,7 +209,11 @@ std::string &Workspace::selectIcon(std::map<std::string, std::string> &icons_map
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void Workspace::update(const std::string &format, const std::string &icon) {
|
||||
void Workspace::update(const std::string& workspace_icon) {
|
||||
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
|
||||
m_button.hide();
|
||||
return;
|
||||
}
|
||||
// clang-format off
|
||||
if (this->m_workspaceManager.activeOnly() && \
|
||||
!this->isActive() && \
|
||||
@@ -195,21 +241,147 @@ void Workspace::update(const std::string &format, const std::string &icon) {
|
||||
addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor");
|
||||
|
||||
std::string windows;
|
||||
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
||||
// Optimization: The {windows} substitution string is only possible if the taskbar is disabled, no
|
||||
// need to compute this if enableTaskbar() is true
|
||||
if (!m_workspaceManager.enableTaskbar()) {
|
||||
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
||||
|
||||
bool isNotFirst = false;
|
||||
bool isNotFirst = false;
|
||||
|
||||
for (auto &[_pid, window_repr] : m_windowMap) {
|
||||
if (isNotFirst) {
|
||||
windows.append(windowSeparator);
|
||||
for (const auto& window_repr : m_windowMap) {
|
||||
if (isNotFirst) {
|
||||
windows.append(windowSeparator);
|
||||
}
|
||||
isNotFirst = true;
|
||||
windows.append(window_repr.repr_rewrite);
|
||||
}
|
||||
isNotFirst = true;
|
||||
windows.append(window_repr);
|
||||
}
|
||||
|
||||
m_label.set_markup(fmt::format(fmt::runtime(format), fmt::arg("id", id()),
|
||||
fmt::arg("name", name()), fmt::arg("icon", icon),
|
||||
fmt::arg("windows", windows)));
|
||||
auto formatBefore = m_workspaceManager.formatBefore();
|
||||
m_labelBefore.set_markup(fmt::format(fmt::runtime(formatBefore), fmt::arg("id", id()),
|
||||
fmt::arg("name", name()), fmt::arg("icon", workspace_icon),
|
||||
fmt::arg("windows", windows)));
|
||||
m_labelBefore.get_style_context()->add_class("workspace-label");
|
||||
|
||||
if (m_workspaceManager.enableTaskbar()) {
|
||||
updateTaskbar(workspace_icon);
|
||||
}
|
||||
}
|
||||
|
||||
bool Workspace::isEmpty() const {
|
||||
auto ignore_list = m_workspaceManager.getIgnoredWindows();
|
||||
if (ignore_list.empty()) {
|
||||
return m_windows == 0;
|
||||
}
|
||||
// If there are windows but they are all ignored, consider the workspace empty
|
||||
return std::all_of(
|
||||
m_windowMap.begin(), m_windowMap.end(),
|
||||
[this, &ignore_list](const auto& window_repr) { return shouldSkipWindow(window_repr); });
|
||||
}
|
||||
|
||||
void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
||||
for (auto child : m_content.get_children()) {
|
||||
if (child != &m_labelBefore) {
|
||||
m_content.remove(*child);
|
||||
}
|
||||
}
|
||||
|
||||
bool isFirst = true;
|
||||
auto processWindow = [&](const WindowRepr& window_repr) {
|
||||
if (shouldSkipWindow(window_repr)) {
|
||||
return; // skip
|
||||
}
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else if (m_workspaceManager.getWindowSeparator() != "") {
|
||||
auto windowSeparator = Gtk::make_managed<Gtk::Label>(m_workspaceManager.getWindowSeparator());
|
||||
m_content.pack_start(*windowSeparator, false, false);
|
||||
windowSeparator->show();
|
||||
}
|
||||
|
||||
auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL);
|
||||
window_box->set_tooltip_markup(window_repr.window_title);
|
||||
|
||||
auto button = Gtk::manage(new Gtk::Button());
|
||||
button->set_relief(Gtk::RELIEF_NONE);
|
||||
button->add(*window_box);
|
||||
button->get_style_context()->add_class("taskbar-window");
|
||||
if (window_repr.isActive) {
|
||||
button->get_style_context()->add_class("active");
|
||||
}
|
||||
if (m_workspaceManager.onClickWindow() != "") {
|
||||
button->signal_button_press_event().connect(
|
||||
sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address),
|
||||
false);
|
||||
}
|
||||
|
||||
auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),
|
||||
fmt::arg("title", window_repr.window_title));
|
||||
if (!text_before.empty()) {
|
||||
auto window_label_before = Gtk::make_managed<Gtk::Label>(text_before);
|
||||
window_box->pack_start(*window_label_before, true, true);
|
||||
}
|
||||
|
||||
if (m_workspaceManager.taskbarWithIcon()) {
|
||||
auto app_info_ = IconLoader::get_app_info_from_app_id_list(window_repr.window_class);
|
||||
int icon_size = m_workspaceManager.taskbarIconSize();
|
||||
auto window_icon = Gtk::make_managed<Gtk::Image>();
|
||||
m_workspaceManager.iconLoader().image_load_icon(*window_icon, app_info_, icon_size);
|
||||
window_box->pack_start(*window_icon, false, false);
|
||||
}
|
||||
|
||||
auto text_after = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatAfter()),
|
||||
fmt::arg("title", window_repr.window_title));
|
||||
if (!text_after.empty()) {
|
||||
auto window_label_after = Gtk::make_managed<Gtk::Label>(text_after);
|
||||
window_box->pack_start(*window_label_after, true, true);
|
||||
}
|
||||
|
||||
m_content.pack_start(*button, true, false);
|
||||
button->show_all();
|
||||
};
|
||||
|
||||
if (m_workspaceManager.taskbarReverseDirection()) {
|
||||
for (auto it = m_windowMap.rbegin(); it != m_windowMap.rend(); ++it) {
|
||||
processWindow(*it);
|
||||
}
|
||||
} else {
|
||||
for (const auto& window_repr : m_windowMap) {
|
||||
processWindow(window_repr);
|
||||
}
|
||||
}
|
||||
|
||||
auto formatAfter = m_workspaceManager.formatAfter();
|
||||
if (!formatAfter.empty()) {
|
||||
m_labelAfter.set_markup(fmt::format(fmt::runtime(formatAfter), fmt::arg("id", id()),
|
||||
fmt::arg("name", name()),
|
||||
fmt::arg("icon", workspace_icon)));
|
||||
m_content.pack_end(m_labelAfter, false, false);
|
||||
m_labelAfter.show();
|
||||
}
|
||||
}
|
||||
|
||||
bool Workspace::handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const {
|
||||
if (event_button->type == GDK_BUTTON_PRESS) {
|
||||
std::string command = std::regex_replace(m_workspaceManager.onClickWindow(),
|
||||
std::regex("\\{address\\}"), "0x" + addr);
|
||||
command = std::regex_replace(command, std::regex("\\{button\\}"),
|
||||
std::to_string(event_button->button));
|
||||
auto res = util::command::execNoRead(command);
|
||||
if (res.exit_code != 0) {
|
||||
spdlog::error("Failed to execute {}: {}", command, res.out);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Workspace::shouldSkipWindow(const WindowRepr& window_repr) const {
|
||||
auto ignore_list = m_workspaceManager.getIgnoredWindows();
|
||||
auto it = std::ranges::find_if(ignore_list, [&window_repr](const auto& ignoreItem) {
|
||||
return std::regex_match(window_repr.window_class, ignoreItem) ||
|
||||
std::regex_match(window_repr.window_title, ignoreItem);
|
||||
});
|
||||
return it != ignore_list.end();
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
||||
|
||||
+617
-301
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user