Add scratchpad mode for sway with icons

master
Jonas Letzbor 2024-03-24 21:20:06 +01:00
parent f4da203915
commit 2df0e7cf9e
Signed by: RPJosh
GPG Key ID: 43ACB900522EA740
2 changed files with 191 additions and 5 deletions

View File

@ -21,11 +21,19 @@
#include "giomm/desktopappinfo.h" #include "giomm/desktopappinfo.h"
#include "util/json.hpp" #include "util/json.hpp"
#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" #include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"
#include "modules/sway/ipc/client.hpp"
namespace waybar::modules::wlr { namespace waybar::modules::wlr {
class Taskbar; class Taskbar;
class SwayScratchpad {
public:
int id;
std::string app_id;
std::string title;
};
class Task { class Task {
public: public:
Task(const waybar::Bar &, const Json::Value &, Taskbar *, Task(const waybar::Bar &, const Json::Value &, Taskbar *,
@ -54,6 +62,7 @@ class Task {
struct wl_seat *seat_; struct wl_seat *seat_;
uint32_t id_; uint32_t id_;
uint32_t swayId_;
Gtk::Box content_; Gtk::Box content_;
Gtk::Image icon_; Gtk::Image icon_;
@ -62,6 +71,8 @@ class Task {
Glib::RefPtr<Gio::DesktopAppInfo> app_info_; Glib::RefPtr<Gio::DesktopAppInfo> app_info_;
bool button_visible_ = false; bool button_visible_ = false;
bool ignored_ = false; bool ignored_ = false;
bool only_scratchpad_ = false;
bool isScratchpadMode_ = false;
bool with_icon_ = false; bool with_icon_ = false;
bool with_name_ = false; bool with_name_ = false;
@ -86,12 +97,15 @@ class Task {
bool image_load_icon(Gtk::Image &image, const Glib::RefPtr<Gtk::IconTheme> &icon_theme, bool image_load_icon(Gtk::Image &image, const Glib::RefPtr<Gtk::IconTheme> &icon_theme,
Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size); Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size);
void hide_if_ignored(); void hide_if_ignored();
bool hideNoScratchpad();
public: public:
/* Getter functions */ /* Getter functions */
uint32_t id() const { return id_; } uint32_t id() const { return id_; }
uint32_t swayId() const { return swayId_; }
std::string title() const { return title_; } std::string title() const { return title_; }
std::string app_id() const { return app_id_; } std::string app_id() const { return app_id_; }
bool isScratchpadMode() const { return isScratchpadMode_; }
uint32_t state() const { return state_; } uint32_t state() const { return state_; }
bool maximized() const { return state_ & MAXIMIZED; } bool maximized() const { return state_ & MAXIMIZED; }
bool minimized() const { return state_ & MINIMIZED; } bool minimized() const { return state_ & MINIMIZED; }
@ -117,6 +131,8 @@ class Task {
void handle_drag_data_received(const Glib::RefPtr<Gdk::DragContext> &context, int x, int y, void handle_drag_data_received(const Glib::RefPtr<Gdk::DragContext> &context, int x, int y,
Gtk::SelectionData selection_data, guint info, guint time); Gtk::SelectionData selection_data, guint info, guint time);
void setManualForSway(const int id, const std::string app_id, const std::string title);
public: public:
bool operator==(const Task &) const; bool operator==(const Task &) const;
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 zwlr_foreign_toplevel_manager_v1 *manager_;
struct wl_seat *seat_; 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<std::string, Task> swayWorkspaceMap;
public: public:
/* Callbacks for global registration */ /* Callbacks for global registration */
void register_manager(struct wl_registry *, uint32_t name, uint32_t version); void register_manager(struct wl_registry *, uint32_t name, uint32_t version);

View File

@ -23,6 +23,11 @@
#include "util/gtk_icon.hpp" #include "util/gtk_icon.hpp"
#include "util/rewrite_string.hpp" #include "util/rewrite_string.hpp"
#include "util/string.hpp" #include "util/string.hpp"
#include <stdio.h>
#include "modules/sway/ipc/ipc.hpp"
#include "modules/sway/ipc/client.hpp"
#include <cstdlib>
#include <string>
namespace waybar::modules::wlr { namespace waybar::modules::wlr {
@ -278,7 +283,10 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
seat_{seat}, seat_{seat},
id_{global_id++}, id_{global_id++},
content_{bar.orientation, 0} { 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); 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()) { config_["on-click-right"].isString()) {
} }
/* Only show apps within scratchpad */
only_scratchpad_ = config_["only-scratchpad"].asBool();
button.add_events(Gdk::BUTTON_PRESS_MASK); button.add_events(Gdk::BUTTON_PRESS_MASK);
button.signal_button_release_event().connect(sigc::mem_fun(*this, &Task::handle_clicked), false); 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::string Task::repr() const {
std::stringstream ss; std::stringstream ss;
//std::cout << "##### ID: " << id_ << " Title: " << title_ << " App_ID: " <<app_id_ << "\n";
ss << "Task (" << id_ << ") " << title_ << " [" << app_id_ << "] <" << (active() ? "A" : "a") ss << "Task (" << id_ << ") " << title_ << " [" << app_id_ << "] <" << (active() ? "A" : "a")
<< (maximized() ? "M" : "m") << (minimized() ? "I" : "i") << (fullscreen() ? "F" : "f") << ">"; << (maximized() ? "M" : "m") << (minimized() ? "I" : "i") << (fullscreen() ? "F" : "f") << ">";
@ -387,8 +399,21 @@ void Task::handle_title(const char *title) {
hide_if_ignored(); 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() { 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; ignored_ = true;
if (button_visible_) { if (button_visible_) {
auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); 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) { void Task::handle_app_id(const char *app_id) {
if (app_id_.empty()) { if (app_id_.empty()) {
spdlog::debug(fmt::format("Task ({}) setting app_id to {}", id_, app_id)); 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); 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 */ /* The task entered the output of the current bar make the button visible */
tbar_->add_button(button); tbar_->add_button(button);
button.show(); button.show();
@ -467,7 +499,7 @@ void Task::handle_output_enter(struct wl_output *output) {
void Task::handle_output_leave(struct wl_output *output) { void Task::handle_output_leave(struct wl_output *output) {
spdlog::debug("{} left output {}", repr(), (void *)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 */ /* The task left the output of the current bar, make the button invisible */
tbar_->remove_button(button); tbar_->remove_button(button);
button.hide(); button.hide();
@ -523,7 +555,9 @@ void Task::handle_done() {
void Task::handle_closed() { void Task::handle_closed() {
spdlog::debug("{} closed", repr()); spdlog::debug("{} closed", repr());
zwlr_foreign_toplevel_handle_v1_destroy(handle_); if (handle_) {
zwlr_foreign_toplevel_handle_v1_destroy(handle_);
}
handle_ = nullptr; handle_ = nullptr;
if (button_visible_) { if (button_visible_) {
tbar_->remove_button(button); tbar_->remove_button(button);
@ -549,6 +583,27 @@ bool Task::handle_clicked(GdkEventButton *bt) {
else if (config_["on-click-right"].isString() && bt->button == 3) else if (config_["on-click-right"].isString() && bt->button == 3)
action = config_["on-click-right"].asString(); 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()) if (action.empty())
return true; return true;
else if (action == "activate") 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()); 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<std::mutex> lock(mutex_);
auto tree = parser_.parse(res.payload);
// Sort the sway IDs aufsteigend
std::vector<SwayScratchpad> 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<uint32_t> 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<Task>(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() { Taskbar::~Taskbar() {
if (manager_) { if (manager_) {
struct wl_display *display = Client::inst()->wl_display; struct wl_display *display = Client::inst()->wl_display;
@ -810,6 +969,8 @@ Taskbar::~Taskbar() {
} }
void Taskbar::update() { void Taskbar::update() {
std::lock_guard<std::mutex> lock(mutex_);
for (auto &t : tasks_) { for (auto &t : tasks_) {
t->update(); t->update();
} }