diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp index 3b456249..1d149cbe 100644 --- a/include/modules/sway/ipc/client.hpp +++ b/include/modules/sway/ipc/client.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "ipc.hpp" namespace waybar::modules::sway { @@ -24,9 +25,9 @@ class Ipc { sigc::signal signal_event; sigc::signal signal_cmd; - void sendCmd(uint32_t type, const std::string &payload = "") const; - void subscribe(const std::string &payload) const; - void handleEvent() const; + void sendCmd(uint32_t type, const std::string &payload = ""); + void subscribe(const std::string &payload); + void handleEvent(); protected: static inline const std::string ipc_magic_ = "i3-ipc"; @@ -34,11 +35,13 @@ class Ipc { const std::string getSocketPath() const; int open(const std::string &) const; - struct ipc_response send(int fd, uint32_t type, const std::string &payload = "") const; - struct ipc_response recv(int fd) const; + struct ipc_response send(int fd, uint32_t type, const std::string &payload = ""); + struct ipc_response recv(int fd); - int fd_; - int fd_event_; + int fd_; + int fd_event_; + std::mutex mutex_; + std::mutex mutex_event_; }; } // namespace waybar::modules::sway diff --git a/include/modules/sway/workspaces.hpp b/include/modules/sway/workspaces.hpp index dca90b5f..187e5cbf 100644 --- a/include/modules/sway/workspaces.hpp +++ b/include/modules/sway/workspaces.hpp @@ -21,25 +21,24 @@ class Workspaces : public IModule { private: void onCmd(const struct Ipc::ipc_response); + void onEvent(const struct Ipc::ipc_response); void worker(); - void addWorkspace(const Json::Value&); + bool filterButtons(); + Gtk::Button& addButton(const Json::Value&); void onButtonReady(const Json::Value&, Gtk::Button&); std::string getIcon(const std::string&, const Json::Value&); bool handleScroll(GdkEventScroll*); - const std::string getCycleWorkspace(const Json::Value& workspaces, uint8_t current, - bool prev) const; - uint16_t getWorkspaceIndex(const Json::Value& workspaces, const std::string& name) const; + const std::string getCycleWorkspace(std::vector::iterator, bool prev) const; + uint16_t getWorkspaceIndex(const std::string& name) const; std::string trimWorkspaceName(std::string); - const Json::Value getWorkspaces(); const Bar& bar_; const Json::Value& config_; - Json::Value workspaces_; + std::vector workspaces_; waybar::util::SleeperThread thread_; Gtk::Box box_; util::JsonParser parser_; Ipc ipc_; - std::mutex mutex_; bool scrolling_; std::unordered_map buttons_; }; diff --git a/src/modules/sway/ipc/client.cpp b/src/modules/sway/ipc/client.cpp index ffcabeb8..eafca419 100644 --- a/src/modules/sway/ipc/client.cpp +++ b/src/modules/sway/ipc/client.cpp @@ -69,7 +69,7 @@ int Ipc::open(const std::string& socketPath) const { return fd; } -struct Ipc::ipc_response Ipc::recv(int fd) const { +struct Ipc::ipc_response Ipc::recv(int fd) { std::string header; header.resize(ipc_header_size_); auto data32 = reinterpret_cast(header.data() + ipc_magic_.size()); @@ -86,7 +86,6 @@ struct Ipc::ipc_response Ipc::recv(int fd) const { } total += res; } - auto magic = std::string(header.data(), header.data() + ipc_magic_.size()); if (magic != ipc_magic_) { throw std::runtime_error("Invalid IPC magic"); @@ -105,7 +104,7 @@ struct Ipc::ipc_response Ipc::recv(int fd) const { return {data32[0], data32[1], &payload.front()}; } -struct Ipc::ipc_response Ipc::send(int fd, uint32_t type, const std::string& payload) const { +struct Ipc::ipc_response Ipc::send(int fd, uint32_t type, const std::string& payload) { std::string header; header.resize(ipc_header_size_); auto data32 = reinterpret_cast(header.data() + ipc_magic_.size()); @@ -122,19 +121,22 @@ struct Ipc::ipc_response Ipc::send(int fd, uint32_t type, const std::string& pay return Ipc::recv(fd); } -void Ipc::sendCmd(uint32_t type, const std::string& payload) const { +void Ipc::sendCmd(uint32_t type, const std::string& payload) { + std::lock_guard lock(mutex_); const auto res = Ipc::send(fd_, type, payload); signal_cmd.emit(res); } -void Ipc::subscribe(const std::string& payload) const { +void Ipc::subscribe(const std::string& payload) { + std::lock_guard lock(mutex_event_); auto res = Ipc::send(fd_event_, IPC_SUBSCRIBE, payload); if (res.payload != "{\"success\": true}") { throw std::runtime_error("Unable to subscribe ipc event"); } } -void Ipc::handleEvent() const { +void Ipc::handleEvent() { + std::lock_guard lock(mutex_event_); const auto res = Ipc::recv(fd_event_); signal_event.emit(res); } diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 119a04d4..b3ebeef1 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -12,47 +12,52 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value box_.get_style_context()->add_class(id); } ipc_.subscribe("[ \"workspace\" ]"); + ipc_.signal_event.connect(sigc::mem_fun(*this, &Workspaces::onEvent)); ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Workspaces::onCmd)); + ipc_.sendCmd(IPC_GET_WORKSPACES); // Launch worker worker(); } -void Workspaces::onCmd(const struct Ipc::ipc_response res) { - if (thread_.isRunning()) { - std::lock_guard lock(mutex_); - workspaces_ = parser_.parse(res.payload); - dp.emit(); - } -} +void Workspaces::onEvent(const struct Ipc::ipc_response res) { ipc_.sendCmd(IPC_GET_WORKSPACES); } -const Json::Value Workspaces::getWorkspaces() { - std::lock_guard lock(mutex_); - return workspaces_; +void Workspaces::onCmd(const struct Ipc::ipc_response res) { + if (res.type == IPC_GET_WORKSPACES) { + auto workspaces = parser_.parse(res.payload); + workspaces_.clear(); + std::copy_if(workspaces.begin(), + workspaces.end(), + std::back_inserter(workspaces_), + [&](const auto &workspace) { + return !config_["all-outputs"].asBool() + ? workspace["output"].asString() == bar_.output->name + : true; + }); + dp.emit(); + } else { + if (scrolling_) { + scrolling_ = false; + } + } } void Workspaces::worker() { thread_ = [this] { try { - if (!getWorkspaces().empty()) { - ipc_.handleEvent(); - } - if (thread_.isRunning()) { - ipc_.sendCmd(IPC_GET_WORKSPACES); - } + ipc_.handleEvent(); } catch (const std::exception &e) { std::cerr << "Workspaces: " << e.what() << std::endl; } }; } -auto Workspaces::update() -> void { +bool Workspaces::filterButtons() { bool needReorder = false; - auto workspaces = getWorkspaces(); for (auto it = buttons_.begin(); it != buttons_.end();) { - auto ws = std::find_if(workspaces.begin(), workspaces.end(), [it](auto node) -> bool { + auto ws = std::find_if(workspaces_.begin(), workspaces_.end(), [it](const auto &node) { return node["name"].asString() == it->first; }); - if (ws == workspaces.end() || + if (ws == workspaces_.end() || (!config_["all-outputs"].asBool() && (*ws)["output"].asString() != bar_.output->name)) { it = buttons_.erase(it); needReorder = true; @@ -60,75 +65,60 @@ auto Workspaces::update() -> void { ++it; } } - for (auto const &node : workspaces) { - if (!config_["all-outputs"].asBool() && bar_.output->name != node["output"].asString()) { - continue; - } - auto it = buttons_.find(node["name"].asString()); - if (it == buttons_.end()) { - addWorkspace(node); + return needReorder; +} + +auto Workspaces::update() -> void { + bool needReorder = filterButtons(); + for (auto it = workspaces_.begin(); it != workspaces_.end(); ++it) { + auto bit = buttons_.find((*it)["name"].asString()); + if (bit == buttons_.end()) { needReorder = true; - } else { - auto &button = it->second; - if (node["focused"].asBool()) { - button.get_style_context()->add_class("focused"); - } else { - button.get_style_context()->remove_class("focused"); - } - if (node["visible"].asBool()) { - button.get_style_context()->add_class("visible"); - } else { - button.get_style_context()->remove_class("visible"); - } - if (node["urgent"].asBool()) { - button.get_style_context()->add_class("urgent"); - } else { - button.get_style_context()->remove_class("urgent"); - } - if (needReorder) { - box_.reorder_child(button, getWorkspaceIndex(workspaces, node["name"].asString())); - } - auto icon = getIcon(node["name"].asString(), node); - std::string output = icon; - if (config_["format"].isString()) { - auto format = config_["format"].asString(); - output = fmt::format(format, - fmt::arg("icon", icon), - fmt::arg("name", trimWorkspaceName(node["name"].asString())), - fmt::arg("index", node["num"].asString())); - } - if (!config_["disable-markup"].asBool()) { - static_cast(button.get_children()[0])->set_markup(output); - } else { - button.set_label(output); - } - onButtonReady(node, button); } - } - if (scrolling_) { - scrolling_ = false; + auto &button = bit == buttons_.end() ? addButton(*it) : bit->second; + if ((*it)["focused"].asBool()) { + button.get_style_context()->add_class("focused"); + } else { + button.get_style_context()->remove_class("focused"); + } + if ((*it)["visible"].asBool()) { + button.get_style_context()->add_class("visible"); + } else { + button.get_style_context()->remove_class("visible"); + } + if ((*it)["urgent"].asBool()) { + button.get_style_context()->add_class("urgent"); + } else { + button.get_style_context()->remove_class("urgent"); + } + if (needReorder) { + box_.reorder_child(button, it - workspaces_.begin()); + } + std::string output = getIcon((*it)["name"].asString(), *it); + if (config_["format"].isString()) { + auto format = config_["format"].asString(); + output = fmt::format(format, + fmt::arg("icon", output), + fmt::arg("name", trimWorkspaceName((*it)["name"].asString())), + fmt::arg("index", (*it)["num"].asString())); + } + if (!config_["disable-markup"].asBool()) { + static_cast(button.get_children()[0])->set_markup(output); + } else { + button.set_label(output); + } + onButtonReady(*it, button); } } -void Workspaces::addWorkspace(const Json::Value &node) { - auto icon = getIcon(node["name"].asString(), node); - auto format = config_["format"].isString() - ? fmt::format(config_["format"].asString(), - fmt::arg("icon", icon), - fmt::arg("name", trimWorkspaceName(node["name"].asString())), - fmt::arg("index", node["num"].asString())) - : icon; - auto pair = buttons_.emplace(node["name"].asString(), format); +Gtk::Button &Workspaces::addButton(const Json::Value &node) { + auto pair = buttons_.emplace(node["name"].asString(), node["name"].asString()); auto &button = pair.first->second; - if (!config_["disable-markup"].asBool()) { - static_cast(button.get_children()[0])->set_markup(format); - } box_.pack_start(button, false, false, 0); button.set_relief(Gtk::RELIEF_NONE); button.signal_clicked().connect([this, pair] { try { - auto cmd = fmt::format("workspace \"{}\"", pair.first->first); - ipc_.sendCmd(IPC_COMMAND, cmd); + ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", pair.first->first)); } catch (const std::exception &e) { std::cerr << e.what() << std::endl; } @@ -137,19 +127,7 @@ void Workspaces::addWorkspace(const Json::Value &node) { button.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); button.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll)); } - auto workspaces = getWorkspaces(); - box_.reorder_child(button, getWorkspaceIndex(workspaces, node["name"].asString())); - if (node["focused"].asBool()) { - button.get_style_context()->add_class("focused"); - } - if (node["visible"].asBool()) { - button.get_style_context()->add_class("visible"); - } - if (node["urgent"].asBool()) { - button.get_style_context()->add_class("urgent"); - } - - onButtonReady(node, button); + return button; } std::string Workspaces::getIcon(const std::string &name, const Json::Value &node) { @@ -171,82 +149,57 @@ bool Workspaces::handleScroll(GdkEventScroll *e) { if (scrolling_) { return false; } - auto workspaces = getWorkspaces(); - uint8_t idx; scrolling_ = true; - for (idx = 0; idx < workspaces.size(); idx += 1) { - if (workspaces[idx]["focused"].asBool()) { - break; - } - } - if (idx == workspaces.size()) { + std::string name; + auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [](const auto &workspace) { + return workspace["focused"].asBool(); + }); + if (it == workspaces_.end()) { scrolling_ = false; return false; } - std::string name; - if (e->direction == GDK_SCROLL_UP) { - name = getCycleWorkspace(workspaces, idx, true); + switch (e->direction) { + case GDK_SCROLL_DOWN: + case GDK_SCROLL_RIGHT: + name = getCycleWorkspace(it, false); + break; + case GDK_SCROLL_UP: + case GDK_SCROLL_LEFT: + name = getCycleWorkspace(it, true); + break; + case GDK_SCROLL_SMOOTH: + gdouble delta_x, delta_y; + gdk_event_get_scroll_deltas(reinterpret_cast(e), &delta_x, &delta_y); + if (delta_y < 0) { + name = getCycleWorkspace(it, true); + } else if (delta_y > 0) { + name = getCycleWorkspace(it, false); + } + break; + default: + break; } - if (e->direction == GDK_SCROLL_DOWN) { - name = getCycleWorkspace(workspaces, idx, false); - } - if (e->direction == GDK_SCROLL_SMOOTH) { - gdouble delta_x, delta_y; - gdk_event_get_scroll_deltas(reinterpret_cast(e), &delta_x, &delta_y); - if (delta_y < 0) { - name = getCycleWorkspace(workspaces, idx, true); - } else if (delta_y > 0) { - name = getCycleWorkspace(workspaces, idx, false); - } - } - if (name.empty() || name == workspaces[idx]["name"].asString()) { + if (name.empty() || name == (*it)["name"].asString()) { scrolling_ = false; return false; } ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", name)); - std::this_thread::sleep_for(std::chrono::milliseconds(150)); return true; } -const std::string Workspaces::getCycleWorkspace(const Json::Value &workspaces, - uint8_t focused_workspace, bool prev) const { - auto inc = prev ? -1 : 1; - int size = workspaces.size(); - uint8_t idx = 0; - for (int i = focused_workspace; i < size && i >= 0; i += inc) { - bool same_output = (workspaces[i]["output"].asString() == bar_.output->name && - !config_["all-outputs"].asBool()) || - config_["all-outputs"].asBool(); - bool same_name = - workspaces[i]["name"].asString() == workspaces[focused_workspace]["name"].asString(); - if (same_output && !same_name) { - return workspaces[i]["name"].asString(); - } - if (prev && i - 1 < 0) { - i = size; - } else if (!prev && i + 1 >= size) { - i = -1; - } else if (idx >= workspaces.size()) { - return ""; - } - idx += 1; +const std::string Workspaces::getCycleWorkspace(std::vector::iterator it, + bool prev) const { + if (prev && it == workspaces_.begin()) { + return (*(--workspaces_.end()))["name"].asString(); } - return ""; -} - -uint16_t Workspaces::getWorkspaceIndex(const Json::Value &workspaces, - const std::string &name) const { - uint16_t idx = 0; - for (const auto &workspace : workspaces) { - if (workspace["name"].asString() == name) { - return idx; - } - if (!(!config_["all-outputs"].asBool() && - workspace["output"].asString() != bar_.output->name)) { - idx += 1; - } + if (prev && it != workspaces_.begin()) + --it; + else if (!prev && it != workspaces_.end()) + ++it; + if (!prev && it == workspaces_.end()) { + return (*(++workspaces_.begin()))["name"].asString(); } - return workspaces.size(); + return (*it)["name"].asString(); } std::string Workspaces::trimWorkspaceName(std::string name) {