From dd1e2201837ef2c2cfeee9c9ab56876870292e07 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sun, 10 May 2026 22:33:04 +0200 Subject: [PATCH] fix(tray): persist pinned items with stable plain names --- src/dbus/tray/tray_service.cpp | 56 +++++++++++++++++++++++++++ src/dbus/tray/tray_service.h | 2 + src/shell/bar/widgets/tray_widget.cpp | 46 ++-------------------- src/shell/tray/tray_drawer_panel.cpp | 20 ++-------- src/shell/tray/tray_menu.cpp | 55 +++++++------------------- 5 files changed, 77 insertions(+), 102 deletions(-) diff --git a/src/dbus/tray/tray_service.cpp b/src/dbus/tray/tray_service.cpp index 024e48251..afdc0d920 100644 --- a/src/dbus/tray/tray_service.cpp +++ b/src/dbus/tray/tray_service.cpp @@ -7,8 +7,11 @@ #include #include +#include #include #include +#include +#include #include #include @@ -37,6 +40,40 @@ namespace { bool looks_like_dbus_name(std::string_view value) { return !value.empty() && value != "__path_only__"; } + std::string trim(std::string value) { + while (!value.empty() && std::isspace(static_cast(value.back())) != 0) { + value.pop_back(); + } + std::size_t first = 0; + while (first < value.size() && std::isspace(static_cast(value[first])) != 0) { + ++first; + } + if (first > 0) { + value.erase(0, first); + } + return value; + } + + std::string processNameForPid(std::uint32_t pid) { + if (pid == 0) { + return {}; + } + + const std::filesystem::path procDir = std::filesystem::path("/proc") / std::to_string(pid); + std::error_code ec; + const auto exe = std::filesystem::read_symlink(procDir / "exe", ec); + if (!ec && !exe.empty()) { + return exe.filename().string(); + } + + std::ifstream comm(procDir / "comm"); + std::string name; + if (std::getline(comm, name)) { + return trim(std::move(name)); + } + return {}; + } + std::vector path_name_hints(std::string_view objectPath) { std::vector hints; if (objectPath.empty()) { @@ -1151,6 +1188,24 @@ bool TrayService::hasServiceOwner(const std::string& serviceName) const { } } +std::string TrayService::processNameForBusName(const std::string& busName) const { + if (busName.empty() || m_dbusProxy == nullptr || !looks_like_dbus_name(busName)) { + return {}; + } + + try { + std::uint32_t pid = 0; + m_dbusProxy->callMethod("GetConnectionUnixProcessID") + .onInterface(k_dbus_interface) + .withTimeout(std::chrono::milliseconds(200)) + .withArguments(busName) + .storeResultsTo(pid); + return processNameForPid(pid); + } catch (const sdbus::Error&) { + return {}; + } +} + std::string TrayService::busNameFromItemId(const std::string& itemId) { if (itemId.empty()) { return {}; @@ -1194,6 +1249,7 @@ void TrayService::registerOrRefreshItem(const std::string& busName, const std::s .attentionIconName = {}, .menuObjectPath = {}, .itemName = {}, + .processName = processNameForBusName(busName), .title = {}, .status = {}, .iconArgb32 = {}, diff --git a/src/dbus/tray/tray_service.h b/src/dbus/tray/tray_service.h index 90e461999..cf4a211e4 100644 --- a/src/dbus/tray/tray_service.h +++ b/src/dbus/tray/tray_service.h @@ -21,6 +21,7 @@ struct TrayItemInfo { std::string attentionIconName; std::string menuObjectPath; std::string itemName; + std::string processName; std::string title; std::string status; std::vector iconArgb32; @@ -114,6 +115,7 @@ private: void sendMenuEvent(const std::string& itemId, std::int32_t entryId, const std::string& eventName); [[nodiscard]] bool ensureItemProxy(const std::string& itemId); [[nodiscard]] bool hasServiceOwner(const std::string& serviceName) const; + [[nodiscard]] std::string processNameForBusName(const std::string& busName) const; void removeItemsForBusName(const std::string& busName); void emitChanged(); diff --git a/src/shell/bar/widgets/tray_widget.cpp b/src/shell/bar/widgets/tray_widget.cpp index 11c688813..a4b060e4a 100644 --- a/src/shell/bar/widgets/tray_widget.cpp +++ b/src/shell/bar/widgets/tray_widget.cpp @@ -193,14 +193,6 @@ TrayWidget::TrayWidget(TrayService* tray, std::vector hiddenItems, std::vector normalized; normalized.reserve(tokens.size()); for (const auto& token : tokens) { - const auto lowerToken = toLower(token); - if (lowerToken.rfind("item:", 0) == 0 || lowerToken.rfind("icon:", 0) == 0 || - lowerToken.rfind("title:", 0) == 0 || lowerToken.rfind("bus:", 0) == 0) { - if (std::ranges::find(normalized, lowerToken) == normalized.end()) { - normalized.push_back(lowerToken); - } - continue; - } for (const auto& variant : identifierVariants(token)) { if (std::ranges::find(normalized, variant) == normalized.end()) { normalized.push_back(variant); @@ -710,6 +702,7 @@ bool TrayWidget::isHiddenItem(const TrayItemInfo& item) const { appendVariants(item.busName); appendVariants(item.objectPath); appendVariants(item.itemName); + appendVariants(item.processName); appendVariants(item.title); appendVariants(item.iconName); appendVariants(item.attentionIconName); @@ -728,41 +721,6 @@ bool TrayWidget::isPinnedItem(const TrayItemInfo& item) const { return false; } - auto hasVariant = [](std::string_view token, std::string_view value) { - const auto variants = identifierVariants(value); - return std::ranges::find(variants, token) != variants.end(); - }; - - for (const auto& needle : m_pinnedItems) { - if (needle.rfind("item:", 0) == 0) { - const auto value = needle.substr(5); - if (hasVariant(value, item.itemName) || hasVariant(value, item.id) || hasVariant(value, item.objectPath)) { - return true; - } - continue; - } - if (needle.rfind("icon:", 0) == 0) { - const auto value = needle.substr(5); - if (hasVariant(value, item.iconName) || hasVariant(value, item.overlayIconName) || - hasVariant(value, item.attentionIconName)) { - return true; - } - continue; - } - if (needle.rfind("title:", 0) == 0) { - if (hasVariant(needle.substr(6), item.title)) { - return true; - } - continue; - } - if (needle.rfind("bus:", 0) == 0) { - if (hasVariant(needle.substr(4), item.busName)) { - return true; - } - continue; - } - } - std::vector candidates; auto appendVariants = [&candidates](std::string_view text) { for (const auto& variant : identifierVariants(text)) { @@ -775,6 +733,7 @@ bool TrayWidget::isPinnedItem(const TrayItemInfo& item) const { appendVariants(item.id); appendVariants(item.busName); appendVariants(item.itemName); + appendVariants(item.processName); appendVariants(item.title); appendVariants(item.objectPath); appendVariants(item.iconName); @@ -892,6 +851,7 @@ std::string TrayWidget::resolveIconPath(const TrayItemInfo& item) { const bool hasTargetPixmap = item.needsAttention ? !item.attentionArgb32.empty() : !item.iconArgb32.empty(); if (preferred.empty() && !hasTargetPixmap) { candidates.emplace_back("itemName", &item.itemName); + candidates.emplace_back("processName", &item.processName); candidates.emplace_back("title", &item.title); candidates.emplace_back("objectPath", &item.objectPath); candidates.emplace_back("busName", &stableBusName); diff --git a/src/shell/tray/tray_drawer_panel.cpp b/src/shell/tray/tray_drawer_panel.cpp index c80d6bb82..1b25fbb8e 100644 --- a/src/shell/tray/tray_drawer_panel.cpp +++ b/src/shell/tray/tray_drawer_panel.cpp @@ -101,25 +101,11 @@ std::size_t TrayDrawerPanel::visibleItemCount() const { if (token.empty()) { return false; } - if (token.rfind("item:", 0) == 0) { - const auto value = token.substr(5); - return hasVariant(value, item.itemName) || hasVariant(value, item.id) || hasVariant(value, item.objectPath); - } - if (token.rfind("icon:", 0) == 0) { - const auto value = token.substr(5); - return hasVariant(value, item.iconName) || hasVariant(value, item.overlayIconName) || - hasVariant(value, item.attentionIconName); - } - if (token.rfind("title:", 0) == 0) { - return hasVariant(token.substr(6), item.title); - } - if (token.rfind("bus:", 0) == 0) { - return hasVariant(token.substr(4), item.busName); - } const auto lowered = toLower(std::string(token)); return hasVariant(lowered, item.id) || hasVariant(lowered, item.busName) || hasVariant(lowered, item.itemName) || - hasVariant(lowered, item.objectPath) || hasVariant(lowered, item.iconName) || - hasVariant(lowered, item.overlayIconName) || hasVariant(lowered, item.attentionIconName); + hasVariant(lowered, item.processName) || hasVariant(lowered, item.objectPath) || + hasVariant(lowered, item.iconName) || hasVariant(lowered, item.overlayIconName) || + hasVariant(lowered, item.attentionIconName); }; std::size_t visible = 0; for (const auto& item : m_tray->items()) { diff --git a/src/shell/tray/tray_menu.cpp b/src/shell/tray/tray_menu.cpp index c5f55b520..2e3ae91cb 100644 --- a/src/shell/tray/tray_menu.cpp +++ b/src/shell/tray/tray_menu.cpp @@ -106,39 +106,7 @@ namespace { return false; } const auto normalizedToken = toLower(token); - auto fieldMatches = [&](std::string_view tokenValue, std::string_view value) { - for (const auto& variant : identifierVariants(value)) { - if (variant == tokenValue) { - return true; - } - } - return false; - }; - // Typed token matching (preferred for new pins). - if (normalizedToken.rfind("item:", 0) == 0) { - const auto value = normalizedToken.substr(5); - return fieldMatches(value, item.itemName) || fieldMatches(value, item.id) || fieldMatches(value, item.objectPath); - } - if (normalizedToken.rfind("icon:", 0) == 0) { - const auto value = normalizedToken.substr(5); - const auto iconVariants = identifierVariants(item.iconName); - const auto attentionVariants = identifierVariants(item.attentionIconName); - return std::ranges::find(iconVariants, value) != iconVariants.end() || - std::ranges::find(attentionVariants, value) != attentionVariants.end(); - } - if (normalizedToken.rfind("title:", 0) == 0) { - const auto value = normalizedToken.substr(6); - const auto titleVariants = identifierVariants(item.title); - return std::ranges::find(titleVariants, value) != titleVariants.end(); - } - if (normalizedToken.rfind("bus:", 0) == 0) { - const auto value = normalizedToken.substr(4); - const auto busVariants = identifierVariants(item.busName); - return std::ranges::find(busVariants, value) != busVariants.end(); - } - - // Backward-compatible matching for existing untyped pins. std::vector candidates; auto appendVariants = [&candidates](std::string_view text) { for (const auto& variant : identifierVariants(text)) { @@ -150,6 +118,7 @@ namespace { appendVariants(item.id); appendVariants(item.busName); appendVariants(item.itemName); + appendVariants(item.processName); appendVariants(item.title); appendVariants(item.objectPath); appendVariants(item.iconName); @@ -922,28 +891,30 @@ bool TrayMenu::toggleActiveItemPinned() { std::erase_if(pinned, [&](const std::string& token) { return tokenMatchesItem(token, *item); }); } else { std::string token; - // Persist typed stable tokens; avoid transient :1.xxx ids. + // Persist stable human-readable tokens; avoid transient :1.xxx ids. if (!looksGenericStatusItemName(item->itemName)) { - token = "item:" + item->itemName; + token = item->itemName; } else if (!item->iconName.empty()) { - token = "icon:" + item->iconName; + token = item->iconName; } else if (!item->overlayIconName.empty()) { - token = "icon:" + item->overlayIconName; + token = item->overlayIconName; } else if (!item->attentionIconName.empty()) { - token = "icon:" + item->attentionIconName; + token = item->attentionIconName; } else if (!looksGenericStatusItemName(item->title)) { - token = "title:" + item->title; + token = item->title; + } else if (!looksGenericStatusItemName(item->processName)) { + token = item->processName; } else if (const auto objectToken = lastPathSegment(item->objectPath); !objectToken.empty() && !looksGenericStatusItemName(objectToken) && !isUniqueBusName(objectToken)) { - token = "item:" + objectToken; + token = objectToken; } else if (const auto idToken = lastPathSegment(item->id); !idToken.empty() && !looksGenericStatusItemName(idToken) && !isUniqueBusName(idToken)) { - token = "item:" + idToken; + token = idToken; } else if (!isUniqueBusName(item->busName)) { - token = "bus:" + item->busName; + token = item->busName; } if (token.empty()) { - token = "item:" + item->id; + token = item->id; } pinned.push_back(token); }