diff --git a/include/factory.hpp b/include/factory.hpp index a853841c..6521cbc9 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -9,6 +9,7 @@ #ifdef HAVE_SWAY #include "modules/sway/language.hpp" #include "modules/sway/mode.hpp" +#include "modules/sway/scratchpad.hpp" #include "modules/sway/window.hpp" #include "modules/sway/workspaces.hpp" #endif diff --git a/include/modules/sway/scratchpad.hpp b/include/modules/sway/scratchpad.hpp new file mode 100644 index 00000000..e68e7726 --- /dev/null +++ b/include/modules/sway/scratchpad.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include +#include + +#include "ALabel.hpp" +#include "bar.hpp" +#include "client.hpp" +#include "modules/sway/ipc/client.hpp" +#include "util/json.hpp" + +namespace waybar::modules::sway { +class Scratchpad : public ALabel { + public: + Scratchpad(const std::string&, const Json::Value&); + ~Scratchpad() = default; + auto update() -> void; + + private: + auto getTree() -> void; + auto onCmd(const struct Ipc::ipc_response&) -> void; + auto onEvent(const struct Ipc::ipc_response&) -> void; + + std::string tooltip_format_; + bool show_empty_; + bool tooltip_enabled_; + std::string tooltip_text_; + int count_; + std::mutex mutex_; + Ipc ipc_; + util::JsonParser parser_; +}; +} // namespace waybar::modules::sway \ No newline at end of file diff --git a/man/waybar-sway-scratchpad.5.scd b/man/waybar-sway-scratchpad.5.scd new file mode 100644 index 00000000..11fc32c0 --- /dev/null +++ b/man/waybar-sway-scratchpad.5.scd @@ -0,0 +1,64 @@ +waybar-sway-scratchpad(5) + +# NAME + +waybar - sway scratchpad module + +# DESCRIPTION + +The *scratchpad* module displays the scratchpad status in Sway + +# CONFIGURATION + +Addressed by *sway/scratchpad* + +*format*: ++ + typeof: string ++ + default: {icon} {count} ++ + The format, how information should be displayed. + +*show-empty*: ++ + typeof: bool ++ + default: false ++ + Option to show module when scratchpad is empty. + +*format-icons*: ++ + typeof: array/object ++ + Based on the current scratchpad window counts, the corresponding icon gets selected. + +*tooltip*: ++ + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. + +*tooltip-format*: ++ + typeof: string ++ + default: {app}: {title} ++ + The format, how information in the tooltip should be displayed. + +# FORMAT REPLACEMENTS + +*{icon}*: Icon, as defined in *format-icons*. + +*{count}*: Number of windows in the scratchpad. + +*{app}*: Name of the application in the scratchpad. + +*{title}*: Title of the application in the scratchpad. + +# EXAMPLES + +``` +"sway/scratchpad": { + "format": "{icon} {count}", + "show-empty": false, + "format-icons": ["", ""], + "tooltip": true, + "tooltip-format": "{app}: {title}" +} +``` + +# STYLE + +- *#scratchpad* +- *#scratchpad.empty* diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index fafc2b36..54340f21 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -273,6 +273,7 @@ A module group is defined by specifying a module named "group/some-group-name". - *waybar-river-window(5)* - *waybar-states(5)* - *waybar-sway-mode(5)* +- *waybar-sway-scratchpad(5)* - *waybar-sway-window(5)* - *waybar-sway-workspaces(5)* - *waybar-wlr-taskbar(5)* diff --git a/meson.build b/meson.build index 6eccb7a5..ed72ffdc 100644 --- a/meson.build +++ b/meson.build @@ -187,7 +187,8 @@ src_files += [ 'src/modules/sway/mode.cpp', 'src/modules/sway/language.cpp', 'src/modules/sway/window.cpp', - 'src/modules/sway/workspaces.cpp' + 'src/modules/sway/workspaces.cpp', + 'src/modules/sway/scratchpad.cpp' ] if true @@ -374,6 +375,7 @@ if scdoc.found() 'waybar-river-window.5.scd', 'waybar-sway-language.5.scd', 'waybar-sway-mode.5.scd', + 'waybar-sway-scratchpad.5.scd', 'waybar-sway-window.5.scd', 'waybar-sway-workspaces.5.scd', 'waybar-temperature.5.scd', diff --git a/resources/config b/resources/config index 6a753acf..ad76e937 100644 --- a/resources/config +++ b/resources/config @@ -5,7 +5,7 @@ // "width": 1280, // Waybar width "spacing": 4, // Gaps between modules (4px) // Choose the order of the modules - "modules-left": ["sway/workspaces", "sway/mode", "custom/media"], + "modules-left": ["sway/workspaces", "sway/mode", "sway/scratchpad", "custom/media"], "modules-center": ["sway/window"], "modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "keyboard-state", "sway/language", "battery", "battery#bat2", "clock", "tray"], // Modules configuration @@ -36,6 +36,13 @@ "sway/mode": { "format": "{}" }, + "sway/scratchpad": { + "format": "{icon} {count}", + "show-empty": false, + "format-icons": ["", ""], + "tooltip": true, + "tooltip-format": "{app}: {title}" + }, "mpd": { "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ", "format-disconnected": "Disconnected ", diff --git a/resources/style.css b/resources/style.css index bfee80de..2a8c3199 100644 --- a/resources/style.css +++ b/resources/style.css @@ -82,6 +82,7 @@ button:hover { #tray, #mode, #idle_inhibitor, +#scratchpad, #mpd { padding: 0 10px; color: #ffffff; @@ -256,3 +257,11 @@ label:focus { #keyboard-state > label.locked { background: rgba(0, 0, 0, 0.2); } + +#scratchpad { + background: rgba(0, 0, 0, 0.2); +} + +#scratchpad.empty { + background-color: transparent; +} diff --git a/src/factory.cpp b/src/factory.cpp index 13b7803f..a4f25e2e 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -35,6 +35,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "sway/language") { return new waybar::modules::sway::Language(id, config_[name]); } + if (ref == "sway/scratchpad") { + return new waybar::modules::sway::Scratchpad(id, config_[name]); + } #endif #ifdef HAVE_WLR if (ref == "wlr/taskbar") { diff --git a/src/modules/sway/scratchpad.cpp b/src/modules/sway/scratchpad.cpp new file mode 100644 index 00000000..59e30530 --- /dev/null +++ b/src/modules/sway/scratchpad.cpp @@ -0,0 +1,82 @@ +#include "modules/sway/scratchpad.hpp" + +#include + +#include + +namespace waybar::modules::sway { +Scratchpad::Scratchpad(const std::string& id, const Json::Value& config) + : ALabel(config, "scratchpad", id, + config["format"].isString() ? config["format"].asString() : "{icon} {count}"), + tooltip_format_(config_["tooltip-format"].isString() ? config_["tooltip-format"].asString() + : "{app}: {title}"), + show_empty_(config_["show-empty"].isBool() ? config_["show-empty"].asBool() : false), + tooltip_enabled_(config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true), + tooltip_text_(""), + count_(0) { + ipc_.subscribe(R"(["window"])"); + ipc_.signal_event.connect(sigc::mem_fun(*this, &Scratchpad::onEvent)); + ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Scratchpad::onCmd)); + + getTree(); + + ipc_.setWorker([this] { + try { + ipc_.handleEvent(); + } catch (const std::exception& e) { + spdlog::error("Scratchpad: {}", e.what()); + } + }); +} +auto Scratchpad::update() -> void { + if (count_ || show_empty_) { + event_box_.show(); + label_.set_markup( + fmt::format(format_, fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())), + fmt::arg("count", count_))); + if (tooltip_enabled_) { + label_.set_tooltip_markup(tooltip_text_); + } + } else { + event_box_.hide(); + } + if (count_) { + label_.get_style_context()->remove_class("empty"); + } else { + label_.get_style_context()->add_class("empty"); + } + ALabel::update(); +} + +auto Scratchpad::getTree() -> void { + try { + ipc_.sendCmd(IPC_GET_TREE); + } catch (const std::exception& e) { + spdlog::error("Scratchpad: {}", e.what()); + } +} + +auto Scratchpad::onCmd(const struct Ipc::ipc_response& res) -> void { + try { + std::lock_guard lock(mutex_); + auto tree = parser_.parse(res.payload); + count_ = tree["nodes"][0]["nodes"][0]["floating_nodes"].size(); + if (tooltip_enabled_) { + tooltip_text_.clear(); + for (const auto& window : tree["nodes"][0]["nodes"][0]["floating_nodes"]) { + tooltip_text_.append(fmt::format(tooltip_format_ + '\n', + fmt::arg("app", window["app_id"].asString()), + fmt::arg("title", window["name"].asString()))); + } + if (!tooltip_text_.empty()) { + tooltip_text_.pop_back(); + } + } + dp.emit(); + } catch (const std::exception& e) { + spdlog::error("Scratchpad: {}", e.what()); + } +} + +auto Scratchpad::onEvent(const struct Ipc::ipc_response& res) -> void { getTree(); } +} // namespace waybar::modules::sway \ No newline at end of file