The window module registers itself with the Hyprland IPC singleton at the start of its constructor, before calling update(). If update() throws an exception (e.g. from an invalid format string), the object is destroyed without the destructor running, leaving a dangling pointer in the IPC callback list. When the IPC thread receives an event, it attempts to call onEvent() on this invalid memory, causing a crash. Moving the update() call before IPC registration ensures that any initialization errors occur before the pointer is shared. If the configuration is invalid, the module fails to construct and is gracefully disabled by the factory without leaving a "landmine" in the background IPC thread. Fixes: #4923 Signed-off-by: Emir Baha Yıldırım <jayshozie@gmail.com>
253 lines
7.9 KiB
C++
253 lines
7.9 KiB
C++
#include "modules/hyprland/window.hpp"
|
|
|
|
#include <glibmm/fileutils.h>
|
|
#include <glibmm/keyfile.h>
|
|
#include <glibmm/miscutils.h>
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#include <algorithm>
|
|
#include <shared_mutex>
|
|
#include <vector>
|
|
|
|
#include "modules/hyprland/backend.hpp"
|
|
#include "util/rewrite_string.hpp"
|
|
#include "util/sanitize_str.hpp"
|
|
|
|
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), m_ipc(IPC::inst()) {
|
|
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();
|
|
|
|
dp.emit();
|
|
}
|
|
|
|
Window::~Window() {
|
|
std::unique_lock<std::shared_mutex> windowIpcUniqueLock(windowIpcSmtx);
|
|
m_ipc.unregisterForIPC(this);
|
|
}
|
|
|
|
auto Window::update() -> void {
|
|
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_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"]);
|
|
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 {
|
|
image_.hide();
|
|
}
|
|
|
|
setClass("empty", workspace_.windows == 0);
|
|
setClass("solo", solo_);
|
|
setClass("floating", allFloating_);
|
|
setClass("swallowing", swallowing_);
|
|
setClass("fullscreen", fullscreen_);
|
|
|
|
if (!lastSoloClass_.empty() && soloClass_ != lastSoloClass_) {
|
|
if (bar_.window.get_style_context()->has_class(lastSoloClass_)) {
|
|
bar_.window.get_style_context()->remove_class(lastSoloClass_);
|
|
spdlog::trace("Removing solo class: {}", lastSoloClass_);
|
|
}
|
|
}
|
|
|
|
if (!soloClass_.empty() && soloClass_ != lastSoloClass_) {
|
|
bar_.window.get_style_context()->add_class(soloClass_);
|
|
spdlog::trace("Adding solo class: {}", soloClass_);
|
|
}
|
|
lastSoloClass_ = soloClass_;
|
|
|
|
AAppIconLabel::update();
|
|
}
|
|
|
|
auto Window::getActiveWorkspace() -> Workspace {
|
|
const auto workspace = IPC::inst().getSocket1JsonReply("activeworkspace");
|
|
|
|
if (workspace.isObject()) {
|
|
return Workspace::parse(workspace);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
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 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 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{
|
|
.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{.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() {
|
|
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;
|
|
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;
|
|
|
|
// Fullscreen windows look like they are solo
|
|
if (fullscreen_) {
|
|
solo_ = true;
|
|
}
|
|
|
|
if (solo_) {
|
|
soloClass_ = windowData_.class_name;
|
|
}
|
|
}
|
|
|
|
void Window::onEvent(const std::string& ev) { dp.emit(); }
|
|
|
|
void Window::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
|