mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Merge branch 'v5' of github.com:noctalia-dev/noctalia-shell into v5
This commit is contained in:
@@ -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 = {},
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user