From 2df0e7cf9e9d1e4c78e3dce5f9b1e9cc32a2860b Mon Sep 17 00:00:00 2001 From: RPJosh Date: Sun, 24 Mar 2024 21:20:06 +0100 Subject: [PATCH] Add scratchpad mode for sway with icons --- include/modules/wlr/taskbar.hpp | 25 +++++ src/modules/wlr/taskbar.cpp | 171 +++++++++++++++++++++++++++++++- 2 files changed, 191 insertions(+), 5 deletions(-) diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 4465dd06..1342872e 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -21,11 +21,19 @@ #include "giomm/desktopappinfo.h" #include "util/json.hpp" #include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" +#include "modules/sway/ipc/client.hpp" namespace waybar::modules::wlr { class Taskbar; +class SwayScratchpad { + public: + int id; + std::string app_id; + std::string title; +}; + class Task { public: Task(const waybar::Bar &, const Json::Value &, Taskbar *, @@ -54,6 +62,7 @@ class Task { struct wl_seat *seat_; uint32_t id_; + uint32_t swayId_; Gtk::Box content_; Gtk::Image icon_; @@ -62,6 +71,8 @@ class Task { Glib::RefPtr app_info_; bool button_visible_ = false; bool ignored_ = false; + bool only_scratchpad_ = false; + bool isScratchpadMode_ = false; bool with_icon_ = false; bool with_name_ = false; @@ -86,12 +97,15 @@ class Task { bool image_load_icon(Gtk::Image &image, const Glib::RefPtr &icon_theme, Glib::RefPtr app_info, int size); void hide_if_ignored(); + bool hideNoScratchpad(); public: /* Getter functions */ uint32_t id() const { return id_; } + uint32_t swayId() const { return swayId_; } std::string title() const { return title_; } std::string app_id() const { return app_id_; } + bool isScratchpadMode() const { return isScratchpadMode_; } uint32_t state() const { return state_; } bool maximized() const { return state_ & MAXIMIZED; } bool minimized() const { return state_ & MINIMIZED; } @@ -117,6 +131,8 @@ class Task { void handle_drag_data_received(const Glib::RefPtr &context, int x, int y, Gtk::SelectionData selection_data, guint info, guint time); + void setManualForSway(const int id, const std::string app_id, const std::string title); + public: bool operator==(const Task &) const; bool operator!=(const Task &) const; @@ -153,6 +169,15 @@ class Taskbar : public waybar::AModule { struct zwlr_foreign_toplevel_manager_v1 *manager_; struct wl_seat *seat_; + /* Sway scratchpad indicator */ + std::mutex mutex_; + waybar::modules::sway::Ipc ipc_; + auto getSwayTree() -> void; + auto onSwayCmd(const struct waybar::modules::sway::Ipc::ipc_response&) -> void; + auto onSwayEvent(const struct waybar::modules::sway::Ipc::ipc_response&) -> void; + util::JsonParser parser_; + std::map swayWorkspaceMap; + public: /* Callbacks for global registration */ void register_manager(struct wl_registry *, uint32_t name, uint32_t version); diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 41d46748..987366a5 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -23,6 +23,11 @@ #include "util/gtk_icon.hpp" #include "util/rewrite_string.hpp" #include "util/string.hpp" +#include +#include "modules/sway/ipc/ipc.hpp" +#include "modules/sway/ipc/client.hpp" +#include +#include namespace waybar::modules::wlr { @@ -278,7 +283,10 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, seat_{seat}, id_{global_id++}, content_{bar.orientation, 0} { - zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); + + if (handle_) { + zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); + } button.set_relief(Gtk::RELIEF_NONE); @@ -333,6 +341,9 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, config_["on-click-right"].isString()) { } + /* Only show apps within scratchpad */ + only_scratchpad_ = config_["only-scratchpad"].asBool(); + button.add_events(Gdk::BUTTON_PRESS_MASK); button.signal_button_release_event().connect(sigc::mem_fun(*this, &Task::handle_clicked), false); @@ -360,6 +371,7 @@ Task::~Task() { std::string Task::repr() const { std::stringstream ss; + //std::cout << "##### ID: " << id_ << " Title: " << title_ << " App_ID: " <"; @@ -387,8 +399,21 @@ void Task::handle_title(const char *title) { hide_if_ignored(); } +void Task::setManualForSway(const int id, const std::string app_id, const std::string title) { + swayId_ = id; + handle_app_id(app_id.c_str()); + title_ = title; + name_ = ""; + isScratchpadMode_ = true; + + //std::cout << "[[[[]]]] Having id " << global_id << "with app_id " << app_id_ << " and title: " << name_; + hide_if_ignored(); + handle_output_enter(nullptr); + handle_done(); +} + void Task::hide_if_ignored() { - if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) { + if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_) || hideNoScratchpad()) { ignored_ = true; if (button_visible_) { auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); @@ -404,6 +429,13 @@ void Task::hide_if_ignored() { } } +/** + * Checks if the current app_id_ should be hidden if it is not a scratchpad element +*/ +bool Task::hideNoScratchpad() { + return only_scratchpad_ && !isScratchpadMode_; +} + void Task::handle_app_id(const char *app_id) { if (app_id_.empty()) { spdlog::debug(fmt::format("Task ({}) setting app_id to {}", id_, app_id)); @@ -455,7 +487,7 @@ void Task::handle_output_enter(struct wl_output *output) { spdlog::debug("{} entered output {}", repr(), (void *)output); - if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) { + if (!button_visible_ && (isScratchpadMode_ || tbar_->all_outputs() || tbar_->show_output(output))) { /* The task entered the output of the current bar make the button visible */ tbar_->add_button(button); button.show(); @@ -467,7 +499,7 @@ void Task::handle_output_enter(struct wl_output *output) { void Task::handle_output_leave(struct wl_output *output) { spdlog::debug("{} left output {}", repr(), (void *)output); - if (button_visible_ && !tbar_->all_outputs() && tbar_->show_output(output)) { + if (button_visible_ && (isScratchpadMode_ || (!tbar_->all_outputs() && tbar_->show_output(output)) )) { /* The task left the output of the current bar, make the button invisible */ tbar_->remove_button(button); button.hide(); @@ -523,7 +555,9 @@ void Task::handle_done() { void Task::handle_closed() { spdlog::debug("{} closed", repr()); - zwlr_foreign_toplevel_handle_v1_destroy(handle_); + if (handle_) { + zwlr_foreign_toplevel_handle_v1_destroy(handle_); + } handle_ = nullptr; if (button_visible_) { tbar_->remove_button(button); @@ -549,6 +583,27 @@ bool Task::handle_clicked(GdkEventButton *bt) { else if (config_["on-click-right"].isString() && bt->button == 3) action = config_["on-click-right"].asString(); + // We can't handle click events because we have no wayland handler. + // So we implement it on our own :) + if (action == "close" || action == "activate") { + // Change focus to workspace + std::string command = "swaymsg [con_id=" + std::to_string(swayId_) + "] scratchpad show"; + if (system(command.c_str()) != 0) { + spdlog::warn("Error while execution sway command {}", command); + } + } + // Kill the window + if (action == "close") { + auto command = "swaymsg kill"; + if (system(command) != 0) { + spdlog::warn("Error while execution sway command {}", command); + } + } + + if (isScratchpadMode_) { + return true; + } + if (action.empty()) return true; else if (action == "activate") @@ -788,8 +843,112 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu } icon_themes_.push_back(Gtk::IconTheme::get_default()); + + // Sway IPC register + ipc_.subscribe(R"(["window"])"); + ipc_.signal_event.connect(sigc::mem_fun(*this, &Taskbar::onSwayEvent)); + ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Taskbar::onSwayCmd)); + + getSwayTree(); + + ipc_.setWorker([this] { + try { + ipc_.handleEvent(); + } catch (const std::exception& e) { + spdlog::error("Scratchpad: {}", e.what()); + } + }); } +auto Taskbar::getSwayTree() -> void { + try { + ipc_.sendCmd(IPC_GET_TREE); + } catch (const std::exception& e) { + spdlog::error("Scratchpad: {}", e.what()); + } +} + +auto Taskbar::onSwayCmd(const struct waybar::modules::sway::Ipc::ipc_response& res) -> void { + try { + std::lock_guard lock(mutex_); + auto tree = parser_.parse(res.payload); + + // Sort the sway IDs aufsteigend + std::vector scratchpad; + for (const auto& window: tree["nodes"][0]["nodes"][0]["floating_nodes"]) { + SwayScratchpad scratch; + scratch.id = window["id"].asInt(); + scratch.app_id = window["app_id"].asString(); + scratch.title = window["name"].asString(); + + scratchpad.push_back(scratch); + } + std::sort(scratchpad.begin(), scratchpad.end(), [](SwayScratchpad a, SwayScratchpad b) -> bool { + return a.id < b.id; + }); + + // We can't remove elements while iterating. + // So we cache them to remove them after iterating + std::vector removals; + + uint32_t lastId = 0; + for (const auto& window: scratchpad) { + + // The list of apps within the scratchpad is ordered ascending. + // If we have an ID higher than the last one and the current one, we remove it. + bool taskFound = false; + uint32_t currentId = window.id; + + for (auto &t : tasks_) { + // Only use scratchpad tasks + if (!t->isScratchpadMode()) { + continue; + } + + if (t->swayId() == currentId) { + taskFound = true; + } + + if (t->swayId() > lastId && t->swayId() < currentId) { + removals.push_back(t->id()); + } + } + + lastId = currentId; + + // No entry found -> add a new one + if (!taskFound) { + tasks_.push_back(std::make_unique(bar_, config_, this, nullptr, seat_)); + const int i = tasks_.size() - 1; + tasks_[i]->setManualForSway(window.id, window.app_id, window.title); + } + } + + // Cleanup + for (auto &t : tasks_) { + // Only use scratchpad tasks + if (!t->isScratchpadMode()) { + continue; + } + + if (t->swayId() > lastId) { + removals.push_back(t->id()); + } + } + + // Remove them + for (const auto& id: removals) { + remove_task(id); + } + + dp.emit(); + } catch (const std::exception& e) { + spdlog::error("Scratchpad: {}", e.what()); + } +} + +auto Taskbar::onSwayEvent(const struct waybar::modules::sway::Ipc::ipc_response& res) -> void { getSwayTree(); } + Taskbar::~Taskbar() { if (manager_) { struct wl_display *display = Client::inst()->wl_display; @@ -810,6 +969,8 @@ Taskbar::~Taskbar() { } void Taskbar::update() { + std::lock_guard lock(mutex_); + for (auto &t : tasks_) { t->update(); }