fix(tray): persist pinned items with stable plain names

This commit is contained in:
Ly-sec
2026-05-10 22:33:04 +02:00
parent b62f6fead8
commit dd1e220183
5 changed files with 77 additions and 102 deletions
+56
View File
@@ -7,8 +7,11 @@
#include <algorithm>
#include <array>
#include <cctype>
#include <chrono>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <optional>
#include <string_view>
@@ -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<unsigned char>(value.back())) != 0) {
value.pop_back();
}
std::size_t first = 0;
while (first < value.size() && std::isspace(static_cast<unsigned char>(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<std::string> path_name_hints(std::string_view objectPath) {
std::vector<std::string> 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 = {},
+2
View File
@@ -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<std::uint8_t> 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();
+3 -43
View File
@@ -193,14 +193,6 @@ TrayWidget::TrayWidget(TrayService* tray, std::vector<std::string> hiddenItems,
std::vector<std::string> 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<std::string> 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);
+3 -17
View File
@@ -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()) {
+13 -42
View File
@@ -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<std::string> 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);
}