Add scratchpad mode for sway with icons
parent
f4da203915
commit
2df0e7cf9e
|
@ -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);
|
||||||
|
|
|
@ -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} {
|
||||||
|
|
||||||
|
if (handle_) {
|
||||||
zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this);
|
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());
|
||||||
|
if (handle_) {
|
||||||
zwlr_foreign_toplevel_handle_v1_destroy(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();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue