diff --git a/include/modules/sni/host.hpp b/include/modules/sni/host.hpp index 7223b17a..49771fac 100644 --- a/include/modules/sni/host.hpp +++ b/include/modules/sni/host.hpp @@ -14,7 +14,7 @@ namespace waybar::modules::SNI { class Host { public: - Host(const std::size_t id, const Json::Value&, const Bar&, const std::vector&, + Host(std::size_t id, const Json::Value&, const Bar&, const std::vector&, const std::function&)>&, const std::function&)>&, const std::function&); ~Host(); @@ -22,11 +22,13 @@ class Host { void checkIgnoreList(const std::vector& ignore_list, const std::function&)>& on_remove); + void reorderItems(); + private: - void busAcquired(const Glib::RefPtr&, Glib::ustring); - void nameAppeared(const Glib::RefPtr&, Glib::ustring, + void busAcquired(const Glib::RefPtr&, const Glib::ustring&); + void nameAppeared(const Glib::RefPtr&, const Glib::ustring&, const Glib::ustring&); - void nameVanished(const Glib::RefPtr&, Glib::ustring); + void nameVanished(const Glib::RefPtr&, const Glib::ustring&); static void proxyReady(GObject*, GAsyncResult*, gpointer); static void registerHost(GObject*, GAsyncResult*, gpointer); static void itemRegistered(SnWatcher*, const gchar*, gpointer); @@ -36,7 +38,7 @@ class Host { void removeItem(std::vector>::iterator); void clearItems(); - std::tuple getBusNameAndObjectPath(const std::string); + static std::tuple getBusNameAndObjectPath(const std::string&); void addRegisteredItem(const std::string& service); std::vector> items_; @@ -53,6 +55,8 @@ class Host { const std::vector ignore_list_; const std::function&)> on_add_; const std::function&)> on_remove_; + + ItemOrderMap orders_; const std::function on_update_; }; diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index d1e3562d..f2e8b3ce 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -24,11 +24,15 @@ struct ToolTip { Glib::ustring text; }; +class Host; + +using ItemOrderMap = std::unordered_map; + class Item : public sigc::trackable { public: Item(const std::string&, const std::string&, const Json::Value&, const Bar&, const std::function&, const std::function&, - const std::function&); + const std::function&, Host&, const ItemOrderMap&); ~Item(); bool isReady() const; @@ -64,6 +68,7 @@ class Item : public sigc::trackable { * while compliant SNI implementation would always reset the flag to desired value. */ bool item_is_menu = true; + int order_ = -1; // -1 means not set private: void onConfigure(GdkEventConfigure* ev); @@ -114,6 +119,9 @@ class Item : public sigc::trackable { Glib::RefPtr proxy_; Glib::RefPtr cancellable_; std::set update_pending_; + + Host& host_; + const ItemOrderMap& orders_; }; } // namespace waybar::modules::SNI diff --git a/include/modules/sni/tray.hpp b/include/modules/sni/tray.hpp index f8b004ce..a2fd08eb 100644 --- a/include/modules/sni/tray.hpp +++ b/include/modules/sni/tray.hpp @@ -13,7 +13,7 @@ namespace waybar::modules::SNI { class Tray : public AModule { public: Tray(const std::string&, const Bar&, const Json::Value&); - virtual ~Tray() = default; + ~Tray() override = default; auto update() -> void override; private: diff --git a/man/waybar-tray.5.scd b/man/waybar-tray.5.scd index 1d787e50..975ba1f2 100644 --- a/man/waybar-tray.5.scd +++ b/man/waybar-tray.5.scd @@ -49,6 +49,12 @@ Addressed by *tray* - *string*: Custom icon name or path to icon file ++ - *false*: Hide this application's tray icon completely +*orders*: ++ + typeof: object ++ + Orders for items to be placed in tray. When not set, orders are controlled by DBus. ++ + Key: name of item. ++ + Value: integer, higher value means to place this item to the right. + # EXAMPLES ``` @@ -59,6 +65,9 @@ Addressed by *tray* "blueman": "bluetooth", "TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png", "spotify": false + }, + "orders": { + "wechat": 99 } } diff --git a/src/modules/sni/host.cpp b/src/modules/sni/host.cpp index f77d26a2..6e9e95d0 100644 --- a/src/modules/sni/host.cpp +++ b/src/modules/sni/host.cpp @@ -2,6 +2,9 @@ #include +#include + +#include "modules/sni/item.hpp" #include "util/scope_guard.hpp" namespace waybar::modules::SNI { @@ -9,7 +12,7 @@ namespace waybar::modules::SNI { static const unsigned RETRY_DELAY_MS = 200; static const unsigned MAX_RETRIES = 10; -Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar, +Host::Host(std::size_t id, const Json::Value& config, const Bar& bar, const std::vector& ignore_list, const std::function&)>& on_add, const std::function&)>& on_remove, @@ -24,7 +27,18 @@ Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar, ignore_list_(ignore_list), on_add_(on_add), on_remove_(on_remove), - on_update_(on_update) {} + on_update_(on_update) { + auto orders = config["orders"]; + if (!orders.isNull()) { + for (auto itr = orders.begin(); itr != orders.end(); ++itr) { + auto key = itr.name(); + auto& value = *itr; + assert(value.isInt()); + + orders_[key] = value.asInt(); + } + } +} Host::~Host() { retry_connection_.disconnect(); @@ -77,13 +91,13 @@ void Host::checkIgnoreList(const std::vector& ignore_list, } } -void Host::busAcquired(const Glib::RefPtr& conn, Glib::ustring name) { +void Host::busAcquired(const Glib::RefPtr& conn, const Glib::ustring& name) { watcher_id_ = Gio::DBus::watch_name(conn, "org.kde.StatusNotifierWatcher", sigc::mem_fun(*this, &Host::nameAppeared), sigc::mem_fun(*this, &Host::nameVanished)); } -void Host::nameAppeared(const Glib::RefPtr& conn, const Glib::ustring name, +void Host::nameAppeared(const Glib::RefPtr& conn, const Glib::ustring& name, const Glib::ustring& name_owner) { if (cancellable_ != nullptr) { // TODO @@ -94,7 +108,8 @@ void Host::nameAppeared(const Glib::RefPtr& conn, const G "/StatusNotifierWatcher", cancellable_, &Host::proxyReady, this); } -void Host::nameVanished(const Glib::RefPtr& conn, const Glib::ustring name) { +void Host::nameVanished(const Glib::RefPtr& conn, + const Glib::ustring& name) { retry_connection_.disconnect(); retry_count_ = 0; g_cancellable_cancel(cancellable_); @@ -111,11 +126,11 @@ void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) { } }); SnWatcher* watcher = sn_watcher_proxy_new_finish(res, &error); - if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) != 0) { spdlog::error("Host: {}", error->message); return; } - auto host = static_cast(data); + auto* host = static_cast(data); if (error != nullptr) { spdlog::error("Host: {}", error->message); g_clear_object(&host->cancellable_); @@ -153,18 +168,18 @@ void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) { } }); sn_watcher_call_register_host_finish(SN_WATCHER(src), res, &error); - if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) != 0) { spdlog::error("Host: {}", error->message); return; } - auto host = static_cast(data); + auto* host = static_cast(data); if (error != nullptr) { spdlog::error("Host: {}", error->message); return; } g_signal_connect(host->watcher_, "item-registered", G_CALLBACK(&Host::itemRegistered), data); g_signal_connect(host->watcher_, "item-unregistered", G_CALLBACK(&Host::itemUnregistered), data); - auto items = sn_watcher_dup_registered_items(host->watcher_); + auto* items = sn_watcher_dup_registered_items(host->watcher_); if (items != nullptr) { spdlog::info("Host: Found {} pre-registered SNI items", g_strv_length(items)); for (uint32_t i = 0; items[i] != nullptr; i += 1) { @@ -176,7 +191,7 @@ void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) { } void Host::itemRegistered(SnWatcher* watcher, const gchar* service, gpointer data) { - auto host = static_cast(data); + auto* host = static_cast(data); spdlog::info("Host::itemRegistered called with service: {}", service); host->addRegisteredItem(service); // host->checkIgnoreList(host->ignore_list_, std::bind(&Host::itemUnregistered, host, @@ -184,8 +199,8 @@ void Host::itemRegistered(SnWatcher* watcher, const gchar* service, gpointer dat } void Host::itemUnregistered(SnWatcher* watcher, const gchar* service, gpointer data) { - auto host = static_cast(data); - auto [bus_name, object_path] = host->getBusNameAndObjectPath(service); + auto* host = static_cast(data); + auto [bus_name, object_path] = waybar::modules::SNI::Host::getBusNameAndObjectPath(service); for (auto it = host->items_.begin(); it != host->items_.end(); ++it) { if ((*it)->bus_name == bus_name && (*it)->object_path == object_path) { host->removeItem(it); @@ -232,7 +247,7 @@ void Host::clearItems() { } } -std::tuple Host::getBusNameAndObjectPath(const std::string service) { +std::tuple Host::getBusNameAndObjectPath(const std::string& service) { auto it = service.find('/'); if (it != std::string::npos) { return {service.substr(0, it), service.substr(it)}; @@ -248,19 +263,28 @@ void Host::addRegisteredItem(const std::string& service) { return; } } - std::string bus_name, object_path; + std::string bus_name; + std::string object_path; std::tie(bus_name, object_path) = getBusNameAndObjectPath(service); spdlog::debug("SNI item registered: bus_name={}, object_path={}, full_service={}", bus_name, object_path, service); - auto it = std::find_if(items_.begin(), items_.end(), [&bus_name, &object_path](const auto& item) { + auto it = std::ranges::find_if(items_, [&bus_name, &object_path](const auto& item) { return bus_name == item->bus_name && object_path == item->object_path; }); if (it == items_.end()) { spdlog::debug("Adding SNI item: {}", bus_name); items_.emplace_back(std::make_unique( bus_name, object_path, config_, bar_, [this](Item& item) { itemReady(item); }, - [this](Item& item) { itemInvalidated(item); }, on_update_)); + [this](Item& item) { itemInvalidated(item); }, on_update_, *this, orders_)); } } +void Host::reorderItems() { + std::ranges::for_each(items_, on_remove_); + std::ranges::sort(items_, [](std::unique_ptr& item1, std::unique_ptr& item2) { + return item1->order_ < item2->order_; + }); + std::ranges::for_each(items_, on_add_); +} + } // namespace waybar::modules::SNI diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index b97a596b..1c28d7af 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -6,13 +6,16 @@ #include #include +#include #include #include #include +#include #include "gdk/gdk.h" +#include "modules/sni/host.hpp" #include "modules/sni/icon_manager.hpp" -#include "util/format.hpp" +#include "util/format.hpp" // IWYU pragma: keep #include "util/gtk_icon.hpp" template <> @@ -40,7 +43,8 @@ static const unsigned UPDATE_DEBOUNCE_TIME = 10; Item::Item(const std::string& bn, const std::string& op, const Json::Value& config, const Bar& bar, const std::function& on_ready, - const std::function& on_invalidate, const std::function& on_updated) + const std::function& on_invalidate, const std::function& on_updated, + Host& host, const ItemOrderMap& orders) : bus_name(bn), object_path(op), icon_size(16), @@ -49,7 +53,9 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf bar_(bar), on_ready_(on_ready), on_invalidate_(on_invalidate), - on_updated_(on_updated) { + on_updated_(on_updated), + host_(host), + orders_(orders) { if (config["icon-size"].isUInt()) { icon_size = config["icon-size"].asUInt(); } @@ -279,6 +285,17 @@ void Item::invalidate() { void Item::setCustomIcon(const std::string& id) { spdlog::debug("SNI tray id: {}", id); + if (order_ == -1) { + auto iter = orders_.find(id); + if (iter != orders_.end()) { + order_ = iter->second; + spdlog::debug("reordering tray item {}, order: {}", id, order_); + } else { + order_ = 0; + } + host_.reorderItems(); + } + std::string custom_icon = IconManager::instance().getIconForApp(id); if (!custom_icon.empty()) { if (std::filesystem::exists(custom_icon)) { diff --git a/src/modules/sni/watcher.cpp b/src/modules/sni/watcher.cpp index 969806cf..be52a401 100644 --- a/src/modules/sni/watcher.cpp +++ b/src/modules/sni/watcher.cpp @@ -25,11 +25,12 @@ Watcher::~Watcher() { items_ = nullptr; } Gio::DBus::unown_name(bus_name_id_); - auto iface = G_DBUS_INTERFACE_SKELETON(watcher_); + auto* iface = G_DBUS_INTERFACE_SKELETON(watcher_); g_dbus_interface_skeleton_unexport(iface); } -void Watcher::busAcquired(const Glib::RefPtr& conn, Glib::ustring name) { +void Watcher::busAcquired(const Glib::RefPtr& conn, + Glib::ustring name) { // NOLINT GError* error = nullptr; waybar::util::ScopeGuard error_deleter([&error]() { if (error) { @@ -65,7 +66,7 @@ gboolean Watcher::handleRegisterHost(Watcher* obj, GDBusMethodInvocation* invoca "D-Bus bus name '%s' is not valid", bus_name); return TRUE; } - auto watch = gfWatchFind(obj->hosts_, bus_name, object_path); + auto* watch = gfWatchFind(obj->hosts_, bus_name, object_path); if (watch != nullptr) { g_warning("Status Notifier Host with bus name '%s' and object path '%s' is already registered", bus_name, object_path); @@ -74,7 +75,7 @@ gboolean Watcher::handleRegisterHost(Watcher* obj, GDBusMethodInvocation* invoca } watch = gfWatchNew(GF_WATCH_TYPE_HOST, service, bus_name, object_path, obj); obj->hosts_ = g_slist_prepend(obj->hosts_, watch); - if (!sn_watcher_get_is_host_registered(obj->watcher_)) { + if (sn_watcher_get_is_host_registered(obj->watcher_) == 0) { sn_watcher_set_is_host_registered(obj->watcher_, TRUE); sn_watcher_emit_host_registered(obj->watcher_); } @@ -96,7 +97,7 @@ gboolean Watcher::handleRegisterItem(Watcher* obj, GDBusMethodInvocation* invoca "D-Bus bus name '%s' is not valid", bus_name); return TRUE; } - auto watch = gfWatchFind(obj->items_, bus_name, object_path); + auto* watch = gfWatchFind(obj->items_, bus_name, object_path); if (watch != nullptr) { spdlog::debug("Ignoring duplicate Status Notifier Item registration for '{}' at '{}'", bus_name, object_path); @@ -116,7 +117,7 @@ gboolean Watcher::handleRegisterItem(Watcher* obj, GDBusMethodInvocation* invoca Watcher::GfWatch* Watcher::gfWatchFind(GSList* list, const gchar* bus_name, const gchar* object_path) { for (GSList* l = list; l != nullptr; l = g_slist_next(l)) { - auto watch = static_cast(l->data); + auto* watch = static_cast(l->data); if (g_strcmp0(watch->bus_name, bus_name) == 0 && g_strcmp0(watch->object_path, object_path) == 0) { return watch; @@ -126,7 +127,7 @@ Watcher::GfWatch* Watcher::gfWatchFind(GSList* list, const gchar* bus_name, } void Watcher::gfWatchFree(gpointer data) { - auto watch = static_cast(data); + auto* watch = static_cast(data); if (watch->watch_id > 0) { g_bus_unwatch_name(watch->watch_id); @@ -153,7 +154,7 @@ Watcher::GfWatch* Watcher::gfWatchNew(GfWatchType type, const gchar* service, co } void Watcher::nameVanished(GDBusConnection* connection, const char* name, gpointer data) { - auto watch = static_cast(data); + auto* watch = static_cast(data); if (watch->type == GF_WATCH_TYPE_HOST) { watch->watcher->hosts_ = g_slist_remove(watch->watcher->hosts_, watch); if (watch->watcher->hosts_ == nullptr) { @@ -174,7 +175,7 @@ void Watcher::updateRegisteredItems(SnWatcher* obj) { GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE("as")); for (GSList* l = items_; l != nullptr; l = g_slist_next(l)) { - auto watch = static_cast(l->data); + auto* watch = static_cast(l->data); gchar* item = g_strdup_printf("%s%s", watch->bus_name, watch->object_path); g_variant_builder_add(&builder, "s", item); g_free(item);