#include "modules/mango/workspaces.hpp" #include #include namespace waybar::modules::mango { Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config) : AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) { box_.set_name("workspaces"); if (!id.empty()) box_.get_style_context()->add_class(id); box_.get_style_context()->add_class(MODULE_CLASS); event_box_.add(box_); if (config_["on-click"].isString()) on_click_left_ = config_["on-click"].asString(); if (config_["on-click-middle"].isString()) on_click_middle_ = config_["on-click-middle"].asString(); if (config_["on-click-right"].isString()) on_click_right_ = config_["on-click-right"].asString(); overview_button_ = new Gtk::Button("OVERVIEW"); overview_button_->set_relief(Gtk::RELIEF_NONE); box_.pack_start(*overview_button_, false, false, 0); if (!on_click_left_.empty() || !on_click_middle_.empty() || !on_click_right_.empty()) { overview_button_->add_events(Gdk::BUTTON_PRESS_MASK); overview_button_->signal_button_press_event().connect( [this](GdkEventButton* event) -> bool { return handleButtonClick(event, 0, true); }, false); } IPC::getInstance().registerForIPC("monitor", this); dp.emit(); } Workspaces::~Workspaces() { IPC::getInstance().unregisterForIPC(this); if (overview_button_) { box_.remove(*overview_button_); delete overview_button_; overview_button_ = nullptr; } for (auto& [idx, btn] : buttons_) { box_.remove(btn); } buttons_.clear(); } void Workspaces::onEvent(const Json::Value& ev) { dp.emit(); } void Workspaces::doUpdate() { Json::Value monitor = IPC::getInstance().getMonitor(bar_.output->name); if (monitor.isNull()) return; const auto& tags = monitor["tags"]; bool overview_mode = false; if (monitor.isMember("active_tags") && monitor["active_tags"].isArray()) { const auto& active_tags = monitor["active_tags"]; if (active_tags.size() == 1 && active_tags[0].asInt() == 0) { overview_mode = true; } } for (auto& [idx, btn] : buttons_) { btn.hide(); } if (overview_mode) { overview_button_->show(); auto style = overview_button_->get_style_context(); style->add_class("overview"); if (monitor["active"].asBool()) style->add_class("current_output"); else style->remove_class("current_output"); std::string label = config_["overview-label"].isString() ? config_["overview-label"].asString() : "OVERVIEW"; if (!config_["disable-markup"].asBool()) { if (auto gtk_label = dynamic_cast(overview_button_->get_child())) { gtk_label->set_markup(label); } } else { overview_button_->set_label(label); } } else { overview_button_->hide(); for (auto btn_it = buttons_.begin(); btn_it != buttons_.end();) { uint64_t id = btn_it->first; bool found = std::any_of(tags.begin(), tags.end(), [id](const Json::Value& tag) { return tag["index"].asUInt64() == id; }); if (!found) { box_.remove(btn_it->second); btn_it = buttons_.erase(btn_it); } else { ++btn_it; } } for (const auto& tag : tags) { uint64_t idx = tag["index"].asUInt64(); auto btn_it = buttons_.find(idx); Gtk::Button& button = (btn_it == buttons_.end()) ? addButton(idx) : btn_it->second; updateButtonState(button, tag, monitor); } std::vector indices; for (const auto& tag : tags) indices.push_back(tag["index"].asUInt64()); std::sort(indices.begin(), indices.end()); int pos = 0; for (uint64_t idx : indices) { box_.reorder_child(buttons_[idx], pos + 1); pos++; } } } void Workspaces::update() { doUpdate(); AModule::update(); } Gtk::Button& Workspaces::addButton(uint64_t idx) { auto [it, _] = buttons_.emplace(idx, std::to_string(idx)); auto& button = it->second; box_.pack_start(button, false, false, 0); button.set_relief(Gtk::RELIEF_NONE); if (!on_click_left_.empty() || !on_click_middle_.empty() || !on_click_right_.empty()) { button.add_events(Gdk::BUTTON_PRESS_MASK); button.signal_button_press_event().connect( [this, idx](GdkEventButton* event) -> bool { return handleButtonClick(event, idx, false); }, false); } button.show_all(); return button; } void Workspaces::updateButtonState(Gtk::Button& button, const Json::Value& tag, const Json::Value& monitor) { auto style = button.get_style_context(); bool active = tag["is_active"].asBool(); bool urgent = tag["is_urgent"].asBool(); bool empty = (tag["client_count"].asInt() == 0); if (active) style->add_class("active"); else style->remove_class("active"); if (urgent) style->add_class("urgent"); else style->remove_class("urgent"); if (empty) style->add_class("empty"); else style->remove_class("empty"); if (monitor["active"].asBool()) style->add_class("current_output"); else style->remove_class("current_output"); uint64_t idx = tag["index"].asUInt64(); std::string name = std::to_string(idx); if (config_["format"].isString()) { name = fmt::format(fmt::runtime(config_["format"].asString()), fmt::arg("icon", getIcon(name, tag)), fmt::arg("value", name), fmt::arg("index", idx), fmt::arg("output", monitor["name"].asString())); } if (!config_["disable-markup"].asBool()) { if (auto gtk_label = dynamic_cast(button.get_child())) { gtk_label->set_markup(name); } } else { button.set_label(name); } if (config_["current-only"].asBool()) { if (active) button.show(); else button.hide(); } else if (config_["hide-empty"].asBool() && empty && !active) { button.hide(); } else { button.show(); } } std::string Workspaces::getIcon(const std::string& value, const Json::Value& tag) { const auto& icons = config_["format-icons"]; if (!icons) return value; if (tag["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString(); if (tag["is_active"].asBool() && icons["active"]) return icons["active"].asString(); if (tag["client_count"].asInt() == 0 && icons["empty"]) return icons["empty"].asString(); std::string idx = std::to_string(tag["index"].asUInt()); if (icons[idx]) return icons[idx].asString(); if (icons["default"]) return icons["default"].asString(); return value; } bool Workspaces::handleButtonClick(GdkEventButton* event, uint64_t idx, bool isOverview) { std::string action; if (event->button == 1) action = on_click_left_; else if (event->button == 2) action = on_click_middle_; else if (event->button == 3) action = on_click_right_; if (action.empty()) return true; try { std::string cmd; if (isOverview) { if (action == "activate") cmd = "dispatch overview"; else if (action == "toggle") cmd = "dispatch toggleoverview"; } else { if (action == "activate") cmd = "dispatch view," + std::to_string(idx); else if (action == "toggle") cmd = "dispatch toggleview," + std::to_string(idx); } if (!cmd.empty()) { Json::Value req; req["command"] = cmd; IPC::sendAsync(req); } } catch (const std::exception& e) { spdlog::error("Error sending IPC command: {}", e.what()); } return true; } } // namespace waybar::modules::mango