From b36f2a836ed8ee4306db9f47daaa4f4050f29dea Mon Sep 17 00:00:00 2001 From: Lemmy Date: Sun, 10 May 2026 23:22:34 -0400 Subject: [PATCH] refactor: all your string_utils belongs to me --- .../hyprland/hyprland_workspace_backend.cpp | 12 -- .../hyprland/hyprland_workspace_backend.h | 1 - .../sway/sway_workspace_backend.cpp | 26 +--- src/compositors/sway/sway_workspace_backend.h | 1 - src/config/cli.cpp | 18 +-- src/core/files/directory_scanner.cpp | 27 +--- src/core/files/directory_scanner.h | 1 - src/dbus/mpris/mpris_service.cpp | 13 -- .../notification/notification_service.cpp | 7 +- src/dbus/tray/tray_service.cpp | 16 +-- src/ipc/ipc_arg_parse.h | 13 +- src/launcher/app_provider.cpp | 10 +- .../bar/widgets/active_window_widget.cpp | 17 +-- src/shell/bar/widgets/tray_widget.cpp | 112 ++--------------- src/shell/clipboard/clipboard_panel.cpp | 31 +---- src/shell/control_center/audio_tab.cpp | 38 ++---- src/shell/notification/notification_toast.cpp | 13 +- src/shell/settings/settings_registry.cpp | 13 +- src/shell/settings/widget_add_popup.cpp | 12 +- src/shell/tray/tray_identifier.h | 107 ++++++++++++++++ src/shell/tray/tray_menu.cpp | 51 +------- src/system/brightness_service.cpp | 38 ++---- src/system/desktop_entry.cpp | 24 ++-- src/system/distro_info.cpp | 43 +------ src/system/hardware_info.cpp | 19 +-- src/system/icon_resolver.cpp | 20 +-- src/system/system_monitor_service.cpp | 18 +-- src/theme/template_engine.cpp | 15 +-- src/ui/controls/glyph_picker.cpp | 11 +- src/ui/dialogs/file_dialog_view.cpp | 15 +-- src/util/string_utils.h | 117 ++++++++++++++++++ 31 files changed, 327 insertions(+), 532 deletions(-) create mode 100644 src/shell/tray/tray_identifier.h diff --git a/src/compositors/hyprland/hyprland_workspace_backend.cpp b/src/compositors/hyprland/hyprland_workspace_backend.cpp index 404adfacc..03a7acae1 100644 --- a/src/compositors/hyprland/hyprland_workspace_backend.cpp +++ b/src/compositors/hyprland/hyprland_workspace_backend.cpp @@ -838,18 +838,6 @@ std::vector HyprlandWorkspaceBackend::parseEventArgs(std::stri return args; } -std::string HyprlandWorkspaceBackend::quoteCommandArg(const std::string& value) { - std::string escaped = "\""; - for (const char c : value) { - if (c == '\\' || c == '"') { - escaped.push_back('\\'); - } - escaped.push_back(c); - } - escaped.push_back('"'); - return escaped; -} - Workspace HyprlandWorkspaceBackend::toWorkspace(const WorkspaceState& state) { const std::uint32_t coord = state.id >= 0 ? static_cast(state.id - 1) : static_cast(state.ordinal); diff --git a/src/compositors/hyprland/hyprland_workspace_backend.h b/src/compositors/hyprland/hyprland_workspace_backend.h index 6ff847213..830b3c915 100644 --- a/src/compositors/hyprland/hyprland_workspace_backend.h +++ b/src/compositors/hyprland/hyprland_workspace_backend.h @@ -84,7 +84,6 @@ private: [[nodiscard]] static std::optional parseHexAddress(std::string_view value); [[nodiscard]] static std::optional parseInt(std::string_view value); [[nodiscard]] static std::vector parseEventArgs(std::string_view data, std::size_t count); - [[nodiscard]] static std::string quoteCommandArg(const std::string& value); [[nodiscard]] static Workspace toWorkspace(const WorkspaceState& state); OutputNameResolver m_outputNameResolver; diff --git a/src/compositors/sway/sway_workspace_backend.cpp b/src/compositors/sway/sway_workspace_backend.cpp index bfc23af0d..6028c5773 100644 --- a/src/compositors/sway/sway_workspace_backend.cpp +++ b/src/compositors/sway/sway_workspace_backend.cpp @@ -2,6 +2,7 @@ #include "compositors/sway/sway_runtime.h" #include "core/log.h" +#include "util/string_utils.h" #include #include @@ -203,12 +204,6 @@ namespace { } } - std::string toLowerCopy(std::string value) { - std::transform(value.begin(), value.end(), value.begin(), - [](unsigned char ch) { return static_cast(std::tolower(ch)); }); - return value; - } - std::string assignmentLookupKey(std::string_view workspaceKey, std::string_view appId, std::string_view title) { std::string key; key.reserve(workspaceKey.size() + appId.size() + title.size() + 2); @@ -272,7 +267,7 @@ void SwayWorkspaceBackend::activate(const std::string& id) { return; } - sendMessage(kIpcRunCommand, "workspace " + quoteCommandArg(id)); + sendMessage(kIpcRunCommand, "workspace " + StringUtils::quoteDouble(id)); } void SwayWorkspaceBackend::activateForOutput(wl_output* /*output*/, const std::string& id) { activate(id); } @@ -352,7 +347,7 @@ SwayWorkspaceBackend::assignTaskbarWindows(const std::vector> windowsByLookupKey; windowsByLookupKey.reserve(outputWindows.size()); for (const auto& window : outputWindows) { - const std::string appIdLower = toLowerCopy(window.appId); + const std::string appIdLower = StringUtils::toLower(window.appId); windowsByLookupKey[assignmentLookupKey(window.workspaceKey, appIdLower, window.title)].push_back(&window); } @@ -376,7 +371,8 @@ SwayWorkspaceBackend::assignTaskbarWindows(const std::vector #include @@ -39,19 +40,6 @@ namespace noctalia::config { bool force = false; }; - std::string shellQuote(std::string_view raw) { - std::string out = "'"; - for (const char c : raw) { - if (c == '\'') { - out += "'\\''"; - } else { - out.push_back(c); - } - } - out.push_back('\''); - return out; - } - bool writeTextFile(const std::filesystem::path& path, std::string_view content, std::string& error) { std::error_code ec; std::filesystem::create_directories(path.parent_path(), ec); @@ -257,8 +245,8 @@ namespace noctalia::config { std::printf("Config home: %s\n", configHome.string().c_str()); std::printf("State home: %s\n\n", stateHome.string().c_str()); std::printf("Run with:\n"); - std::printf(" XDG_CONFIG_HOME=%s XDG_STATE_HOME=%s %s\n", shellQuote(configHome.string()).c_str(), - shellQuote(stateHome.string()).c_str(), shellQuote(argv0).c_str()); + std::printf(" XDG_CONFIG_HOME=%s XDG_STATE_HOME=%s %s\n", StringUtils::shellQuote(configHome.string()).c_str(), + StringUtils::shellQuote(stateHome.string()).c_str(), StringUtils::shellQuote(argv0).c_str()); return 0; } diff --git a/src/core/files/directory_scanner.cpp b/src/core/files/directory_scanner.cpp index 2dfa2d356..e9c0ddb42 100644 --- a/src/core/files/directory_scanner.cpp +++ b/src/core/files/directory_scanner.cpp @@ -1,24 +1,12 @@ #include "core/files/directory_scanner.h" +#include "util/string_utils.h" + #include #include -#include #include #include -namespace { - - std::string lowerText(std::string_view text) { - std::string out; - out.reserve(text.size()); - for (char ch : text) { - out.push_back(static_cast(std::tolower(static_cast(ch)))); - } - return out; - } - -} // namespace - std::vector DirectoryScanner::scan(const std::filesystem::path& dir, const std::vector& extensions, bool showHiddenFiles, FileDialogSortField sortField, FileDialogSortOrder sortOrder) const { @@ -92,7 +80,7 @@ std::vector DirectoryScanner::scan(const std::filesystem::path& dir, std::vector lowerNames; lowerNames.reserve(entries.size()); for (const auto& entry : entries) { - lowerNames.push_back(lowerText(entry.name)); + lowerNames.push_back(StringUtils::toLower(entry.name)); } std::vector indices(entries.size()); @@ -178,12 +166,3 @@ std::string DirectoryScanner::normalizeExtension(std::string_view extension) { } return out; } - -std::string DirectoryScanner::lower(std::string_view text) { - std::string out; - out.reserve(text.size()); - for (char ch : text) { - out.push_back(static_cast(std::tolower(static_cast(ch)))); - } - return out; -} diff --git a/src/core/files/directory_scanner.h b/src/core/files/directory_scanner.h index e50b3a5e8..cb6edffb3 100644 --- a/src/core/files/directory_scanner.h +++ b/src/core/files/directory_scanner.h @@ -38,5 +38,4 @@ private: const std::vector& extensions); [[nodiscard]] static bool isHiddenName(std::string_view name); [[nodiscard]] static std::string normalizeExtension(std::string_view extension); - [[nodiscard]] static std::string lower(std::string_view text); }; diff --git a/src/dbus/mpris/mpris_service.cpp b/src/dbus/mpris/mpris_service.cpp index e9ea9a51e..b3d876482 100644 --- a/src/dbus/mpris/mpris_service.cpp +++ b/src/dbus/mpris/mpris_service.cpp @@ -207,19 +207,6 @@ namespace { return out; } - [[maybe_unused]] std::string joinStrings(const std::vector& values) { - std::string out; - bool first = true; - for (const auto& value : values) { - if (!first) { - out += ", "; - } - first = false; - out += value; - } - return out; - } - bool hasStrongNowPlayingMetadata(const MprisPlayerInfo& info) { // Track IDs/source URLs can exist during transient "loading" states where the // user-visible metadata is still placeholder-only (e.g. app identity + logo). diff --git a/src/dbus/notification/notification_service.cpp b/src/dbus/notification/notification_service.cpp index aeb56a0b0..3597c360a 100644 --- a/src/dbus/notification/notification_service.cpp +++ b/src/dbus/notification/notification_service.cpp @@ -132,11 +132,6 @@ namespace { return std::string{s.substr(0, len)}; } - bool isBlankText(std::string_view text) { - return text.empty() || - std::all_of(text.begin(), text.end(), [](unsigned char ch) { return std::isspace(ch) != 0; }); - } - std::vector sanitize_actions(const std::vector& actions) { std::vector sanitized; sanitized.reserve(actions.size() - (actions.size() % 2)); @@ -149,7 +144,7 @@ namespace { continue; } - if (isBlankText(label)) { + if (StringUtils::isBlank(label)) { label = i18n::tr("notifications.actions.fallback"); } diff --git a/src/dbus/tray/tray_service.cpp b/src/dbus/tray/tray_service.cpp index a821fa09a..0c93031f5 100644 --- a/src/dbus/tray/tray_service.cpp +++ b/src/dbus/tray/tray_service.cpp @@ -40,20 +40,6 @@ 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 {}; @@ -69,7 +55,7 @@ namespace { std::ifstream comm(procDir / "comm"); std::string name; if (std::getline(comm, name)) { - return trim(std::move(name)); + return StringUtils::trim(name); } return {}; } diff --git a/src/ipc/ipc_arg_parse.h b/src/ipc/ipc_arg_parse.h index ac7d4fcfe..49bc0f127 100644 --- a/src/ipc/ipc_arg_parse.h +++ b/src/ipc/ipc_arg_parse.h @@ -1,23 +1,16 @@ #pragma once +#include "util/string_utils.h" + #include #include -#include #include #include #include namespace noctalia::ipc { - inline std::vector splitWords(std::string_view text) { - std::vector words; - std::istringstream stream{std::string(text)}; - std::string word; - while (stream >> word) { - words.push_back(std::move(word)); - } - return words; - } + inline std::vector splitWords(std::string_view text) { return StringUtils::splitWhitespace(text); } inline std::optional parseNormalizedOrPercent(std::string_view token, float maxPercent = 100.0f) { std::string value(token); diff --git a/src/launcher/app_provider.cpp b/src/launcher/app_provider.cpp index a75bf967d..79af7a26d 100644 --- a/src/launcher/app_provider.cpp +++ b/src/launcher/app_provider.cpp @@ -2,6 +2,7 @@ #include "util/file_utils.h" #include "util/fuzzy_match.h" +#include "util/string_utils.h" #include "wayland/wayland_connection.h" #include @@ -20,13 +21,6 @@ namespace { constexpr std::size_t kMaxSearchResults = 50; constexpr std::string_view kDefaultAppIcon = "application-x-executable"; - std::string toLower(std::string_view s) { - std::string result(s); - std::transform(result.begin(), result.end(), result.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - return result; - } - double scoreEntry(std::string_view pattern, const DesktopEntry& entry) { if (pattern.empty()) { return 0.0; @@ -288,7 +282,7 @@ void AppProvider::refreshEntriesIfNeeded() const { std::vector AppProvider::query(std::string_view text) const { refreshEntriesIfNeeded(); - const std::string normalizedText = toLower(text); + const std::string normalizedText = StringUtils::toLower(text); const std::string_view pattern = normalizedText; auto buildResult = [&](const DesktopEntry& entry, double s) { diff --git a/src/shell/bar/widgets/active_window_widget.cpp b/src/shell/bar/widgets/active_window_widget.cpp index d5c0cf829..59de6c202 100644 --- a/src/shell/bar/widgets/active_window_widget.cpp +++ b/src/shell/bar/widgets/active_window_widget.cpp @@ -10,23 +10,12 @@ #include "ui/controls/label.h" #include "ui/palette.h" #include "ui/style.h" +#include "util/string_utils.h" #include -#include #include #include -namespace { - - std::string toLower(std::string_view value) { - std::string out(value); - std::transform(out.begin(), out.end(), out.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - return out; - } - -} // namespace - ActiveWindowWidget::ActiveWindowWidget(CompositorPlatform& platform, float maxWidth, float minWidth, float iconSize, ActiveWindowTitleScrollMode titleScrollMode) : m_platform(platform), m_maxWidth(maxWidth), m_minWidth(minWidth), m_iconSize(iconSize), @@ -212,7 +201,7 @@ std::string ActiveWindowWidget::resolveIconPath(const std::string& appId) { } } - const std::string appIdLower = toLower(appId); + const std::string appIdLower = StringUtils::toLower(appId); if (auto it = m_appIcons.find(appIdLower); it != m_appIcons.end()) { const auto path = resolveByName(it->second); if (!path.empty()) { @@ -240,7 +229,7 @@ void ActiveWindowWidget::buildDesktopIconIndex() { return; } m_appIcons.try_emplace(std::string{key}, icon); - m_appIcons.try_emplace(toLower(key), icon); + m_appIcons.try_emplace(StringUtils::toLower(key), icon); }; const auto& entries = desktopEntries(); diff --git a/src/shell/bar/widgets/tray_widget.cpp b/src/shell/bar/widgets/tray_widget.cpp index a4b060e4a..9a1914245 100644 --- a/src/shell/bar/widgets/tray_widget.cpp +++ b/src/shell/bar/widgets/tray_widget.cpp @@ -8,14 +8,15 @@ #include "render/scene/node.h" #include "render/text/glyph_registry.h" #include "shell/panel/panel_manager.h" +#include "shell/tray/tray_identifier.h" #include "ui/controls/flex.h" #include "ui/controls/glyph.h" #include "ui/controls/image.h" #include "ui/palette.h" #include "ui/style.h" +#include "util/string_utils.h" #include -#include #include #include #include @@ -28,106 +29,7 @@ namespace { constexpr Logger kLog("tray"); - std::string toLower(std::string_view value) { - std::string out(value); - std::transform(out.begin(), out.end(), out.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - return out; - } - - std::vector identifierVariants(std::string_view value) { - std::vector out; - if (value.empty()) { - return out; - } - - auto pushUnique = [&out](std::string candidate) { - if (candidate.empty()) { - return; - } - if (std::ranges::find(out, candidate) == out.end()) { - out.push_back(std::move(candidate)); - } - }; - - std::string base(value); - pushUnique(base); - pushUnique(toLower(base)); - - if (const auto slash = base.find_last_of('/'); slash != std::string::npos && slash + 1 < base.size()) { - base = base.substr(slash + 1); - pushUnique(base); - pushUnique(toLower(base)); - } - - std::string dashed = base; - std::replace(dashed.begin(), dashed.end(), '_', '-'); - pushUnique(dashed); - pushUnique(toLower(dashed)); - - std::string underscored = base; - std::replace(underscored.begin(), underscored.end(), '-', '_'); - pushUnique(underscored); - pushUnique(toLower(underscored)); - - auto pushReducedForms = [&pushUnique](std::string candidate) { - if (candidate.empty()) { - return; - } - - pushUnique(candidate); - pushUnique(toLower(candidate)); - - bool changed = true; - while (changed && !candidate.empty()) { - changed = false; - - for (const auto& suffix : {"_client", "-client", ".desktop", "_indicator", "-indicator", "_tray", "-tray", - "_status", "-status", "_panel", "-panel"}) { - if (candidate.size() > std::char_traits::length(suffix) && candidate.ends_with(suffix)) { - candidate = candidate.substr(0, candidate.size() - std::char_traits::length(suffix)); - pushUnique(candidate); - pushUnique(toLower(candidate)); - changed = true; - break; - } - } - - if (changed || candidate.empty()) { - continue; - } - - const auto separator = candidate.find_last_of("-_"); - if (separator != std::string::npos && separator + 1 < candidate.size()) { - const std::string tail = candidate.substr(separator + 1); - const bool numericTail = std::ranges::all_of(tail, [](unsigned char c) { return std::isdigit(c) != 0; }); - if (numericTail) { - candidate = candidate.substr(0, separator); - pushUnique(candidate); - pushUnique(toLower(candidate)); - changed = true; - continue; - } - } - - for (const auto& suffix : {"-linux", "_linux"}) { - if (candidate.size() > std::char_traits::length(suffix) && candidate.ends_with(suffix)) { - candidate = candidate.substr(0, candidate.size() - std::char_traits::length(suffix)); - pushUnique(candidate); - pushUnique(toLower(candidate)); - changed = true; - break; - } - } - } - }; - - for (const auto& candidate : std::vector{base, dashed, underscored}) { - pushReducedForms(candidate); - } - - return out; - } + using tray::identifierVariants; void addIconAlias(std::unordered_map& index, std::string_view key, std::string_view icon) { if (key.empty() || icon.empty()) { @@ -227,7 +129,7 @@ std::string TrayWidget::resolveFromTrayThemePath(std::string_view themePath, std } const fs::path path = it->path(); - const auto extension = toLower(path.extension().string()); + const auto extension = StringUtils::toLower(path.extension().string()); if (extension != ".svg" && extension != ".png") { continue; } @@ -570,7 +472,7 @@ void TrayWidget::rebuild(Renderer& renderer) { if (const auto it = m_appIcons.find(overlayName); it != m_appIcons.end()) { return m_iconResolver.resolve(it->second); } - const std::string lower = toLower(overlayName); + const std::string lower = StringUtils::toLower(overlayName); if (const auto it = m_appIcons.find(lower); it != m_appIcons.end()) { return m_iconResolver.resolve(it->second); } @@ -803,7 +705,7 @@ std::string TrayWidget::resolveIconPath(const TrayItemInfo& item) { } } - const std::string lower = toLower(name); + const std::string lower = StringUtils::toLower(name); if (const auto it = m_appIcons.find(lower); it != m_appIcons.end()) { if (const auto mapped = m_iconResolver.resolve(it->second); !mapped.empty()) { return mapped; @@ -817,7 +719,7 @@ std::string TrayWidget::resolveIconPath(const TrayItemInfo& item) { return mapped; } } - const std::string tailLower = toLower(tail); + const std::string tailLower = StringUtils::toLower(tail); if (const auto it = m_appIcons.find(tailLower); it != m_appIcons.end()) { if (const auto mapped = m_iconResolver.resolve(it->second); !mapped.empty()) { return mapped; diff --git a/src/shell/clipboard/clipboard_panel.cpp b/src/shell/clipboard/clipboard_panel.cpp index 3692354a5..23a24c613 100644 --- a/src/shell/clipboard/clipboard_panel.cpp +++ b/src/shell/clipboard/clipboard_panel.cpp @@ -23,6 +23,7 @@ #include "ui/controls/virtual_grid_view.h" #include "ui/palette.h" #include "ui/style.h" +#include "util/string_utils.h" #include "wayland/clipboard_service.h" #include @@ -47,30 +48,6 @@ namespace { constexpr auto kFilterDebounceInterval = std::chrono::milliseconds(120); constexpr Logger kLog("clipboard"); - std::string trim(std::string_view text) { - const auto first = text.find_first_not_of(" \t\r\n"); - if (first == std::string_view::npos) { - return {}; - } - const auto last = text.find_last_not_of(" \t\r\n"); - return std::string(text.substr(first, last - first + 1)); - } - - std::string shellQuote(std::string_view text) { - std::string quoted; - quoted.reserve(text.size() + 2); - quoted.push_back('\''); - for (char ch : text) { - if (ch == '\'') { - quoted += "'\\''"; - } else { - quoted.push_back(ch); - } - } - quoted.push_back('\''); - return quoted; - } - void replaceAll(std::string& text, std::string_view needle, std::string_view replacement) { if (needle.empty()) { return; @@ -86,7 +63,7 @@ namespace { std::string buildImageActionCommand(std::string command, std::string_view imagePath) { const bool hasPathPlaceholder = command.find("{path}") != std::string::npos; const bool hasStdinPlaceholder = command.find("{stdin}") != std::string::npos; - const std::string quotedPath = shellQuote(imagePath); + const std::string quotedPath = StringUtils::shellQuote(imagePath); if (hasPathPlaceholder) { replaceAll(command, "{path}", quotedPath); @@ -866,7 +843,7 @@ void ClipboardPanel::updatePreviewActions() { bool showImageAction = false; if (m_clipboard != nullptr && m_config != nullptr && - !trim(m_config->config().shell.clipboardImageActionCommand).empty()) { + !StringUtils::trim(m_config->config().shell.clipboardImageActionCommand).empty()) { const std::size_t historyIndex = selectedHistoryIndex(); const auto& history = m_clipboard->history(); showImageAction = historyIndex != static_cast(-1) && historyIndex < history.size() && @@ -1113,7 +1090,7 @@ void ClipboardPanel::runImageAction() { return; } - const std::string configuredCommand = trim(m_config->config().shell.clipboardImageActionCommand); + const std::string configuredCommand = StringUtils::trim(m_config->config().shell.clipboardImageActionCommand); if (configuredCommand.empty()) { return; } diff --git a/src/shell/control_center/audio_tab.cpp b/src/shell/control_center/audio_tab.cpp index 9c83c20c3..fc97e7abb 100644 --- a/src/shell/control_center/audio_tab.cpp +++ b/src/shell/control_center/audio_tab.cpp @@ -23,9 +23,9 @@ #include "ui/controls/scroll_view.h" #include "ui/controls/slider.h" #include "ui/palette.h" +#include "util/string_utils.h" #include -#include #include #include #include @@ -230,29 +230,7 @@ namespace { return value; } - std::string trimAsciiWhitespaceCopy(std::string_view s) { - std::size_t i = 0; - std::size_t j = s.size(); - while (i < j && std::isspace(static_cast(s[i])) != 0) { - ++i; - } - while (j > i && std::isspace(static_cast(s[j - 1])) != 0) { - --j; - } - return std::string(s.substr(i, j - i)); - } - - [[nodiscard]] bool isBlankSearchKey(std::string_view s) { - if (s.empty()) { - return true; - } - for (unsigned char c : s) { - if (std::isspace(c) == 0) { - return false; - } - } - return true; - } + [[nodiscard]] bool isBlankSearchKey(std::string_view s) { return StringUtils::isBlank(s); } bool isDesktopTokenDelimiter(unsigned char c) { return c == '-' || c == '_' || c == '.' || std::isspace(c) != 0; } @@ -436,7 +414,7 @@ namespace { } const DesktopEntry* findDesktopEntryByTerm(std::string_view term) { - const std::string trimmed = trimAsciiWhitespaceCopy(term); + const std::string trimmed = StringUtils::trim(term); if (trimmed.empty()) { return nullptr; } @@ -460,7 +438,7 @@ namespace { DesktopEntryMatch lookupDesktopEntryForProgramStream(const AudioNode& node, std::string_view resolvedBeforeDesktop) { DesktopEntryMatch out; - const std::string binary = lowerIdentifier(trimAsciiWhitespaceCopy(node.applicationBinary)); + const std::string binary = lowerIdentifier(StringUtils::trim(node.applicationBinary)); // Wine/Proton streams report wine64-preloader etc.; matching desktop entries by that binary (or // the shared Icon=wine) incorrectly picks unrelated apps (e.g. Protontricks) before app/node name. if (!binary.empty() && !looksLikeRuntimeLauncher(node.applicationBinary)) { @@ -471,7 +449,7 @@ namespace { return out; } } - const std::string appId = lowerIdentifier(trimAsciiWhitespaceCopy(node.applicationId)); + const std::string appId = lowerIdentifier(StringUtils::trim(node.applicationId)); if (!appId.empty()) { if (const DesktopEntry* entry = findDesktopEntryByTerm(appId)) { out.entry = entry; @@ -480,7 +458,7 @@ namespace { return out; } } - const std::string appName = lowerIdentifier(trimAsciiWhitespaceCopy(node.applicationName)); + const std::string appName = lowerIdentifier(StringUtils::trim(node.applicationName)); if (!appName.empty() && !isGenericAudioLabel(appName) && !looksLikeRuntimeLauncher(appName)) { if (const DesktopEntry* entry = findDesktopEntryByTerm(appName)) { out.entry = entry; @@ -489,7 +467,7 @@ namespace { return out; } } - const std::string resolved = lowerIdentifier(trimAsciiWhitespaceCopy(resolvedBeforeDesktop)); + const std::string resolved = lowerIdentifier(StringUtils::trim(resolvedBeforeDesktop)); if (!resolved.empty() && !isGenericAudioLabel(resolved) && !looksLikeRuntimeLauncher(resolved)) { if (const DesktopEntry* entry = findDesktopEntryByTerm(resolved)) { out.entry = entry; @@ -498,7 +476,7 @@ namespace { return out; } } - const std::string nodeName = lowerIdentifier(trimAsciiWhitespaceCopy(node.name)); + const std::string nodeName = lowerIdentifier(StringUtils::trim(node.name)); if (!nodeName.empty()) { if (const DesktopEntry* entry = findDesktopEntryByTerm(nodeName)) { out.entry = entry; diff --git a/src/shell/notification/notification_toast.cpp b/src/shell/notification/notification_toast.cpp index 3e11325c1..aef8b748d 100644 --- a/src/shell/notification/notification_toast.cpp +++ b/src/shell/notification/notification_toast.cpp @@ -171,11 +171,6 @@ namespace { localY < closeTop + kCloseButtonSize; } - bool isBlankText(std::string_view text) { - return text.empty() || - std::all_of(text.begin(), text.end(), [](unsigned char ch) { return std::isspace(ch) != 0; }); - } - float measureActionsFromPairs(RenderContext& rc, const std::vector& actions) { if (actions.empty()) { return 0.0f; @@ -190,7 +185,7 @@ namespace { for (std::size_t i = 0; i + 1 < actions.size() && actionCount < kMaxActionButtons; i += 2) { const std::string& actionKey = actions[i]; std::string actionLabel = actions[i + 1]; - if (isBlankText(actionLabel)) { + if (StringUtils::isBlank(actionLabel)) { actionLabel = fallbackActionLabel(); } if (actionKey.empty()) { @@ -237,7 +232,7 @@ namespace { ToastGeometry out; - if (isBlankText(displayBody)) { + if (StringUtils::isBlank(displayBody)) { Label summaryProbe; summaryProbe.setFontSize(kSummaryFontSize); summaryProbe.setBold(true); @@ -490,7 +485,7 @@ void NotificationToast::onNotificationEvent(const Notification& n, NotificationE cs.bodyLabel->setMaxLines(std::max(1, bodyLines)); cs.bodyLabel->setText(bodyLines > 0 ? displayBody : ""); cs.bodyLabel->measure(*m_renderContext); - cs.bodyLabel->setVisible(bodyLines > 0 && !isBlankText(displayBody)); + cs.bodyLabel->setVisible(bodyLines > 0 && !StringUtils::isBlank(displayBody)); cs.bodyLabel->setPosition(notificationTextStartX(), bodyTopForSummary(summaryH)); clampBodyLabelHeight(*cs.bodyLabel, bodyHeight); } @@ -1638,7 +1633,7 @@ InputArea* NotificationToast::buildCard(const PopupEntry& entry, Node** outCardC for (std::size_t i = 0; i + 1 < entry.actions.size() && actionCount < kMaxActionButtons; i += 2) { const std::string actionKey = entry.actions[i]; std::string actionLabel = entry.actions[i + 1]; - if (isBlankText(actionLabel)) { + if (StringUtils::isBlank(actionLabel)) { actionLabel = fallbackActionLabel(); } if (actionKey.empty()) { diff --git a/src/shell/settings/settings_registry.cpp b/src/shell/settings/settings_registry.cpp index 151261525..5f47d9e08 100644 --- a/src/shell/settings/settings_registry.cpp +++ b/src/shell/settings/settings_registry.cpp @@ -5,9 +5,9 @@ #include "shell/control_center/shortcut_registry.h" #include "theme/builtin_palettes.h" #include "theme/builtin_templates.h" +#include "util/string_utils.h" #include -#include #include #include #include @@ -145,13 +145,6 @@ namespace settings { return out; } - std::string lower(std::string_view value) { - std::string out(value); - std::transform(out.begin(), out.end(), out.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - return out; - } - SettingEntry makeEntry(std::string section, std::string group, std::string title, std::string subtitle, std::vector path, SettingControl control, std::string tags = {}, bool advanced = false) { @@ -167,7 +160,7 @@ namespace settings { .path = std::move(path), .control = std::move(control), .advanced = advanced, - .searchText = lower(searchText), + .searchText = StringUtils::toLower(searchText), .visibleWhen = std::nullopt, }; } @@ -201,7 +194,7 @@ namespace settings { return names; } - std::string normalizedSettingQuery(std::string_view query) { return lower(query); } + std::string normalizedSettingQuery(std::string_view query) { return StringUtils::toLower(query); } bool matchesNormalizedSettingQuery(const SettingEntry& entry, std::string_view normalizedQuery) { if (normalizedQuery.empty()) { diff --git a/src/shell/settings/widget_add_popup.cpp b/src/shell/settings/widget_add_popup.cpp index c14aed1ca..9ebe2f4ea 100644 --- a/src/shell/settings/widget_add_popup.cpp +++ b/src/shell/settings/widget_add_popup.cpp @@ -14,11 +14,11 @@ #include "ui/controls/toggle.h" #include "ui/palette.h" #include "ui/style.h" +#include "util/string_utils.h" #include "wayland/wayland_connection.h" #include "xdg-shell-client-protocol.h" #include -#include #include #include #include @@ -51,16 +51,10 @@ namespace settings { return label; } - std::string toLowerAscii(std::string text) { - std::transform(text.begin(), text.end(), text.begin(), - [](unsigned char ch) { return static_cast(std::tolower(ch)); }); - return text; - } - void sortSearchOptions(std::vector& options) { std::sort(options.begin(), options.end(), [](const SearchPickerOption& a, const SearchPickerOption& b) { - const std::string aLabel = toLowerAscii(a.label); - const std::string bLabel = toLowerAscii(b.label); + const std::string aLabel = StringUtils::toLower(a.label); + const std::string bLabel = StringUtils::toLower(b.label); if (aLabel == bLabel) { return a.value < b.value; } diff --git a/src/shell/tray/tray_identifier.h b/src/shell/tray/tray_identifier.h new file mode 100644 index 000000000..e84145f39 --- /dev/null +++ b/src/shell/tray/tray_identifier.h @@ -0,0 +1,107 @@ +#pragma once + +#include "util/string_utils.h" + +#include +#include +#include +#include +#include + +namespace tray { + + inline std::vector identifierVariants(std::string_view value) { + std::vector out; + if (value.empty()) { + return out; + } + + auto pushUnique = [&out](std::string candidate) { + if (candidate.empty()) { + return; + } + if (std::ranges::find(out, candidate) == out.end()) { + out.push_back(std::move(candidate)); + } + }; + + std::string base(value); + pushUnique(base); + pushUnique(StringUtils::toLower(base)); + + if (const auto slash = base.find_last_of('/'); slash != std::string::npos && slash + 1 < base.size()) { + base = base.substr(slash + 1); + pushUnique(base); + pushUnique(StringUtils::toLower(base)); + } + + std::string dashed = base; + std::replace(dashed.begin(), dashed.end(), '_', '-'); + pushUnique(dashed); + pushUnique(StringUtils::toLower(dashed)); + + std::string underscored = base; + std::replace(underscored.begin(), underscored.end(), '-', '_'); + pushUnique(underscored); + pushUnique(StringUtils::toLower(underscored)); + + auto pushReducedForms = [&pushUnique](std::string candidate) { + if (candidate.empty()) { + return; + } + + pushUnique(candidate); + pushUnique(StringUtils::toLower(candidate)); + + bool changed = true; + while (changed && !candidate.empty()) { + changed = false; + + for (const auto& suffix : {"_client", "-client", ".desktop", "_indicator", "-indicator", "_tray", "-tray", + "_status", "-status", "_panel", "-panel"}) { + if (candidate.size() > std::char_traits::length(suffix) && candidate.ends_with(suffix)) { + candidate = candidate.substr(0, candidate.size() - std::char_traits::length(suffix)); + pushUnique(candidate); + pushUnique(StringUtils::toLower(candidate)); + changed = true; + break; + } + } + + if (changed || candidate.empty()) { + continue; + } + + const auto separator = candidate.find_last_of("-_"); + if (separator != std::string::npos && separator + 1 < candidate.size()) { + const std::string tail = candidate.substr(separator + 1); + const bool numericTail = std::ranges::all_of(tail, [](unsigned char c) { return std::isdigit(c) != 0; }); + if (numericTail) { + candidate = candidate.substr(0, separator); + pushUnique(candidate); + pushUnique(StringUtils::toLower(candidate)); + changed = true; + continue; + } + } + + for (const auto& suffix : {"-linux", "_linux"}) { + if (candidate.size() > std::char_traits::length(suffix) && candidate.ends_with(suffix)) { + candidate = candidate.substr(0, candidate.size() - std::char_traits::length(suffix)); + pushUnique(candidate); + pushUnique(StringUtils::toLower(candidate)); + changed = true; + break; + } + } + } + }; + + for (const auto& candidate : std::vector{base, dashed, underscored}) { + pushReducedForms(candidate); + } + + return out; + } + +} // namespace tray diff --git a/src/shell/tray/tray_menu.cpp b/src/shell/tray/tray_menu.cpp index 5c6c0e70b..55003e8d8 100644 --- a/src/shell/tray/tray_menu.cpp +++ b/src/shell/tray/tray_menu.cpp @@ -8,15 +8,16 @@ #include "i18n/i18n.h" #include "render/render_context.h" #include "shell/panel/panel_manager.h" +#include "shell/tray/tray_identifier.h" #include "ui/controls/context_menu.h" #include "ui/style.h" +#include "util/string_utils.h" #include "wayland/layer_surface.h" #include "wayland/wayland_connection.h" #include "wayland/wayland_seat.h" #include "xdg-shell-client-protocol.h" #include -#include #include #include @@ -81,55 +82,13 @@ namespace { return out; } - std::string toLower(std::string_view value) { - std::string out(value); - std::transform(out.begin(), out.end(), out.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - return out; - } - - std::vector identifierVariants(std::string_view value) { - std::vector out; - if (value.empty()) { - return out; - } - auto pushUnique = [&out](std::string candidate) { - if (candidate.empty()) { - return; - } - if (std::ranges::find(out, candidate) == out.end()) { - out.push_back(std::move(candidate)); - } - }; - - std::string base(value); - pushUnique(base); - pushUnique(toLower(base)); - - if (const auto slash = base.find_last_of('/'); slash != std::string::npos && slash + 1 < base.size()) { - base = base.substr(slash + 1); - pushUnique(base); - pushUnique(toLower(base)); - } - - std::string dashed = base; - std::replace(dashed.begin(), dashed.end(), '_', '-'); - pushUnique(dashed); - pushUnique(toLower(dashed)); - - std::string underscored = base; - std::replace(underscored.begin(), underscored.end(), '-', '_'); - pushUnique(underscored); - pushUnique(toLower(underscored)); - - return out; - } + using tray::identifierVariants; bool tokenMatchesItem(std::string_view token, const TrayItemInfo& item) { if (token.empty()) { return false; } - const auto normalizedToken = toLower(token); + const auto normalizedToken = StringUtils::toLower(token); std::vector candidates; auto appendVariants = [&candidates](std::string_view text) { @@ -157,7 +116,7 @@ namespace { if (value.empty()) { return true; } - const auto lower = toLower(value); + const auto lower = StringUtils::toLower(value); return lower.find("status_icon") != std::string::npos || lower.find("statusnotifieritem") != std::string::npos || lower.find("statusnotifier") != std::string::npos || lower.find("status-notifier") != std::string::npos || lower.find("status notifier") != std::string::npos; diff --git a/src/system/brightness_service.cpp b/src/system/brightness_service.cpp index c60be6e00..afc7bdb9b 100644 --- a/src/system/brightness_service.cpp +++ b/src/system/brightness_service.cpp @@ -8,6 +8,7 @@ #include "dbus/system_bus.h" #include "ipc/ipc_arg_parse.h" #include "ipc/ipc_service.h" +#include "util/string_utils.h" #include "wayland/wayland_connection.h" #include @@ -132,25 +133,6 @@ namespace { return out; } - std::string trim(std::string_view input) { - std::size_t start = 0; - while (start < input.size() && std::isspace(static_cast(input[start])) != 0) { - ++start; - } - std::size_t end = input.size(); - while (end > start && std::isspace(static_cast(input[end - 1])) != 0) { - --end; - } - return std::string(input.substr(start, end - start)); - } - - std::string toLower(std::string_view input) { - std::string out(input); - std::transform(out.begin(), out.end(), out.begin(), - [](unsigned char ch) { return static_cast(std::tolower(ch)); }); - return out; - } - int readSysfsInt(const std::string& path) { std::ifstream file(path); int value = -1; @@ -337,7 +319,7 @@ namespace { } std::string normalizeConnectorName(std::string_view raw) { - std::string connector = trim(raw); + std::string connector = StringUtils::trim(raw); if (connector.starts_with("card")) { const auto dash = connector.find('-'); if (dash != std::string::npos) { @@ -352,7 +334,7 @@ namespace { while (start <= output.size()) { const std::size_t end = output.find('\n', start); const std::string line = output.substr(start, end == std::string::npos ? output.size() - start : end - start); - const std::string lower = toLower(line); + const std::string lower = StringUtils::toLower(line); const std::size_t currentPos = lower.find("current value"); const std::size_t maxPos = lower.find("max value"); if (currentPos == std::string::npos || maxPos == std::string::npos || maxPos <= currentPos) { @@ -520,7 +502,8 @@ namespace { return {}; } if (detectResult.exitCode != 0) { - kLog.warn("ddcutil detect failed with exit code {}: {}", detectResult.exitCode, trim(detectResult.output)); + kLog.warn("ddcutil detect failed with exit code {}: {}", detectResult.exitCode, + StringUtils::trim(detectResult.output)); return {}; } @@ -536,7 +519,8 @@ namespace { current.currentRaw = brightness->first; current.maxRaw = brightness->second; } else { - kLog.warn("ddcutil: skipping bus {} because brightness query failed: {}", current.bus, trim(getvcpDetail)); + kLog.warn("ddcutil: skipping bus {} because brightness query failed: {}", current.bus, + StringUtils::trim(getvcpDetail)); } } if (current.currentRaw >= 0 && current.maxRaw > 0) { @@ -549,7 +533,7 @@ namespace { std::size_t start = 0; while (start <= detectResult.output.size()) { const std::size_t end = detectResult.output.find('\n', start); - const std::string line = trim(detectResult.output.substr( + const std::string line = StringUtils::trim(detectResult.output.substr( start, end == std::string::npos ? detectResult.output.size() - start : end - start)); if (line.starts_with("Display ")) { flushCurrent(); @@ -566,9 +550,9 @@ namespace { } else if (line.starts_with("DRM_connector:")) { current.connectorName = normalizeConnectorName(line.substr(std::strlen("DRM_connector:"))); } else if (line.starts_with("Monitor:")) { - current.label = trim(line.substr(std::strlen("Monitor:"))); + current.label = StringUtils::trim(line.substr(std::strlen("Monitor:"))); } else if (line.starts_with("Model:") && current.label.empty()) { - current.label = trim(line.substr(std::strlen("Model:"))); + current.label = StringUtils::trim(line.substr(std::strlen("Model:"))); } if (end == std::string::npos) { @@ -1232,7 +1216,7 @@ struct BrightnessService::Impl { display->failureCount, kDdcFailureCooldown.count()); } else { kLog.warn("ddcutil {} failed for '{}': {}", completion.type == WorkerCompletion::Type::Set ? "write" : "refresh", - display->pub.id, trim(completion.detail)); + display->pub.id, StringUtils::trim(completion.detail)); } return false; diff --git a/src/system/desktop_entry.cpp b/src/system/desktop_entry.cpp index fd51168d9..cc5c5cc09 100644 --- a/src/system/desktop_entry.cpp +++ b/src/system/desktop_entry.cpp @@ -1,6 +1,7 @@ #include "system/desktop_entry.h" #include "core/log.h" +#include "util/string_utils.h" #include #include @@ -18,15 +19,8 @@ namespace { constexpr Logger kLog("desktop_entry"); - std::string toLower(std::string_view s) { - std::string result(s); - std::transform(result.begin(), result.end(), result.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - return result; - } - bool parseDesktopBool(std::string_view value) { - const std::string lower = toLower(value); + const std::string lower = StringUtils::toLower(value); return lower == "true" || lower == "1" || lower == "yes"; } @@ -263,13 +257,13 @@ namespace { } // Pre-lowercase for matching - entry.nameLower = toLower(entry.name); - entry.genericNameLower = toLower(entry.genericName); - entry.keywordsLower = toLower(entry.keywords); - entry.categoriesLower = toLower(entry.categories); - entry.startupWmClassLower = toLower(entry.startupWmClass); - entry.idLower = toLower(entry.id); - entry.execLower = toLower(entry.exec); + entry.nameLower = StringUtils::toLower(entry.name); + entry.genericNameLower = StringUtils::toLower(entry.genericName); + entry.keywordsLower = StringUtils::toLower(entry.keywords); + entry.categoriesLower = StringUtils::toLower(entry.categories); + entry.startupWmClassLower = StringUtils::toLower(entry.startupWmClass); + entry.idLower = StringUtils::toLower(entry.id); + entry.execLower = StringUtils::toLower(entry.exec); // Build actions in the declared order. for (const auto& id : actionOrder) { diff --git a/src/system/distro_info.cpp b/src/system/distro_info.cpp index e5973f760..d154b7b38 100644 --- a/src/system/distro_info.cpp +++ b/src/system/distro_info.cpp @@ -1,6 +1,7 @@ #include "system/distro_info.h" #include "i18n/i18n.h" +#include "util/string_utils.h" #include #include @@ -21,44 +22,6 @@ namespace { - std::string trim(std::string_view value) { - std::size_t start = 0; - while (start < value.size() && std::isspace(static_cast(value[start])) != 0) { - ++start; - } - - std::size_t end = value.size(); - while (end > start && std::isspace(static_cast(value[end - 1])) != 0) { - --end; - } - - return std::string(value.substr(start, end - start)); - } - - std::string unquote(std::string value) { - if (value.size() >= 2 && - ((value.front() == '"' && value.back() == '"') || (value.front() == '\'' && value.back() == '\''))) { - value = value.substr(1, value.size() - 2); - } - - std::string out; - out.reserve(value.size()); - bool escaping = false; - for (char ch : value) { - if (escaping) { - out.push_back(ch); - escaping = false; - continue; - } - if (ch == '\\') { - escaping = true; - continue; - } - out.push_back(ch); - } - return out; - } - std::optional> parseOsRelease(const std::filesystem::path& path) { std::ifstream file(path); if (!file.is_open()) { @@ -68,7 +31,7 @@ namespace { std::unordered_map values; std::string line; while (std::getline(file, line)) { - const auto trimmed = trim(line); + const auto trimmed = StringUtils::trim(line); if (trimmed.empty() || trimmed.front() == '#') { continue; } @@ -79,7 +42,7 @@ namespace { } auto key = std::string(trimmed.substr(0, eq)); - auto value = unquote(trim(std::string_view(trimmed).substr(eq + 1))); + auto value = StringUtils::unquote(StringUtils::trim(std::string_view(trimmed).substr(eq + 1))); values[std::move(key)] = std::move(value); } diff --git a/src/system/hardware_info.cpp b/src/system/hardware_info.cpp index 5aee12705..21f2c82da 100644 --- a/src/system/hardware_info.cpp +++ b/src/system/hardware_info.cpp @@ -2,6 +2,7 @@ #include "compositors/compositor_detect.h" #include "i18n/i18n.h" +#include "util/string_utils.h" #include #include @@ -13,14 +14,6 @@ namespace { - std::string trimWhitespace(std::string s) { - while (!s.empty() && (s.back() == ' ' || s.back() == '\t' || s.back() == '\n' || s.back() == '\r')) { - s.pop_back(); - } - const auto start = s.find_first_not_of(" \t"); - return start == std::string::npos ? "" : s.substr(start); - } - std::string readCpuModel() { std::ifstream file{"/proc/cpuinfo"}; if (!file.is_open()) { @@ -32,7 +25,7 @@ namespace { if (line.starts_with("model name")) { const auto colonPos = line.find(':'); if (colonPos != std::string::npos) { - return trimWhitespace(line.substr(colonPos + 1)); + return StringUtils::trim(line.substr(colonPos + 1)); } } } @@ -61,7 +54,7 @@ namespace { if (line.starts_with(vendorId)) { const auto nameStart = line.find(" "); if (nameStart != std::string::npos) { - vendorName = trimWhitespace(line.substr(nameStart)); + vendorName = StringUtils::trim(line.substr(nameStart)); inVendor = true; } } @@ -73,11 +66,11 @@ namespace { } if (line[0] == '\t' && (line.size() < 2 || line[1] != '\t')) { - auto stripped = trimWhitespace(line); + auto stripped = StringUtils::trim(line); if (stripped.starts_with(deviceId)) { const auto nameStart = stripped.find(" "); if (nameStart != std::string::npos) { - auto deviceName = trimWhitespace(stripped.substr(nameStart)); + auto deviceName = StringUtils::trim(stripped.substr(nameStart)); if (!deviceName.empty()) { return deviceName; } @@ -99,7 +92,7 @@ namespace { } std::string line; std::getline(file, line); - return trimWhitespace(line); + return StringUtils::trim(line); } std::string detectGpu() { diff --git a/src/system/icon_resolver.cpp b/src/system/icon_resolver.cpp index 5113e6b93..3498e5bfa 100644 --- a/src/system/icon_resolver.cpp +++ b/src/system/icon_resolver.cpp @@ -1,5 +1,7 @@ #include "system/icon_resolver.h" +#include "util/string_utils.h" + #include #include #include @@ -34,17 +36,7 @@ namespace { return state; } - std::string trim(std::string_view value) { - while (!value.empty() && (value.front() == '\'' || value.front() == '"' || value.front() == ' ' || - value.front() == '\t' || value.front() == '\r' || value.front() == '\n')) { - value = value.substr(1); - } - while (!value.empty() && (value.back() == '\'' || value.back() == '"' || value.back() == ' ' || - value.back() == '\t' || value.back() == '\r' || value.back() == '\n')) { - value = value.substr(0, value.size() - 1); - } - return std::string(value); - } + std::string trimAndUnquote(std::string_view value) { return StringUtils::unquote(StringUtils::trim(value)); } void pushUnique(std::vector& values, std::string value) { if (value.empty()) { @@ -61,7 +53,7 @@ namespace { while (start <= value.size()) { const auto next = value.find(separator, start); const auto part = next == std::string_view::npos ? value.substr(start) : value.substr(start, next - start); - const std::string trimmed = trim(part); + const std::string trimmed = trimAndUnquote(part); if (!trimmed.empty()) { parts.push_back(trimmed); } @@ -181,7 +173,7 @@ namespace { return std::nullopt; } - std::string value = trim(raw); + std::string value = trimAndUnquote(raw); g_free(raw); if (value.empty()) { return std::nullopt; @@ -217,7 +209,7 @@ namespace { if (eq == std::string::npos) { continue; } - std::string value = trim(std::string_view(line.data() + eq + 1, line.size() - eq - 1)); + std::string value = trimAndUnquote(std::string_view(line.data() + eq + 1, line.size() - eq - 1)); if (!value.empty()) { candidates.emplace_back(std::move(value)); } diff --git a/src/system/system_monitor_service.cpp b/src/system/system_monitor_service.cpp index bd639cd1e..444fd8cf0 100644 --- a/src/system/system_monitor_service.cpp +++ b/src/system/system_monitor_service.cpp @@ -1,9 +1,9 @@ #include "system/system_monitor_service.h" #include "core/log.h" +#include "util/string_utils.h" #include -#include #include #include #include @@ -75,16 +75,10 @@ namespace { return static_cast(raw); } - std::string toLower(std::string value) { - std::transform(value.begin(), value.end(), value.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - return value; - } - int scoreHwmonSensor(const std::string& hwmon_name, const std::string& label) { int score = 0; - const std::string name = toLower(hwmon_name); - const std::string lbl = toLower(label); + const std::string name = StringUtils::toLower(hwmon_name); + const std::string lbl = StringUtils::toLower(label); if (name.find("coretemp") != std::string::npos || name.find("k10temp") != std::string::npos || name.find("zenpower") != std::string::npos || name.find("cpu") != std::string::npos) { @@ -100,14 +94,14 @@ namespace { } bool isCpuThermalZoneType(const std::string& type) { - const std::string t = toLower(type); + const std::string t = StringUtils::toLower(type); return t.find("x86_pkg_temp") != std::string::npos || t.find("cpu") != std::string::npos || t.find("soc") != std::string::npos || t.find("package") != std::string::npos; } int scoreGpuHwmonSensor(const std::string& hwmon_name, const std::string& label) { - const std::string name = toLower(hwmon_name); - const std::string lbl = toLower(label); + const std::string name = StringUtils::toLower(hwmon_name); + const std::string lbl = StringUtils::toLower(label); if (name.find("nvidia") != std::string::npos) { return -1; diff --git a/src/theme/template_engine.cpp b/src/theme/template_engine.cpp index ebcf2b5e2..a33af800c 100644 --- a/src/theme/template_engine.cpp +++ b/src/theme/template_engine.cpp @@ -355,25 +355,14 @@ namespace noctalia::theme { return out; } - std::string replaceAll(std::string value, const std::string& from, const std::string& to) { - if (from.empty()) - return value; - size_t pos = 0; - while ((pos = value.find(from, pos)) != std::string::npos) { - value.replace(pos, from.size(), to); - pos += to.size(); - } - return value; - } - std::string applyReplace(const std::string& value, const std::optional& arg) { if (!arg) return value; std::smatch match; if (std::regex_match(*arg, match, std::regex(R"REGEX("([^"]*?)"\s*,\s*"([^"]*?)")REGEX"))) - return replaceAll(value, match[1].str(), match[2].str()); + return StringUtils::replaceAll(value, match[1].str(), match[2].str()); if (std::regex_match(*arg, match, std::regex(R"REGEX('([^']*?)'\s*,\s*'([^']*?)')REGEX"))) - return replaceAll(value, match[1].str(), match[2].str()); + return StringUtils::replaceAll(value, match[1].str(), match[2].str()); return value; } diff --git a/src/ui/controls/glyph_picker.cpp b/src/ui/controls/glyph_picker.cpp index 9bf80ceb4..5d0e52400 100644 --- a/src/ui/controls/glyph_picker.cpp +++ b/src/ui/controls/glyph_picker.cpp @@ -12,9 +12,9 @@ #include "ui/controls/virtual_grid_view.h" #include "ui/palette.h" #include "ui/style.h" +#include "util/string_utils.h" #include -#include #include #include @@ -37,13 +37,6 @@ namespace { button.setRadius(Style::radiusMd * scale); } - std::string toLowerCopy(std::string_view value) { - std::string out(value); - std::transform(out.begin(), out.end(), out.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - return out; - } - } // namespace class GlyphGridAdapter : public VirtualGridAdapter { @@ -136,7 +129,7 @@ public: } return; } - const std::string needle = toLowerCopy(filter); + const std::string needle = StringUtils::toLower(filter); for (std::size_t i = 0; i < m_master.size(); ++i) { // Names in the registry are already lowercase; no need to lower each entry. if (m_master[i].name.find(needle) != std::string::npos) { diff --git a/src/ui/dialogs/file_dialog_view.cpp b/src/ui/dialogs/file_dialog_view.cpp index 741ac65e8..fb17d16c6 100644 --- a/src/ui/dialogs/file_dialog_view.cpp +++ b/src/ui/dialogs/file_dialog_view.cpp @@ -16,9 +16,9 @@ #include "ui/dialogs/file_entry_row.h" #include "ui/dialogs/file_entry_tile.h" #include "ui/style.h" +#include "util/string_utils.h" #include -#include #include #include #include @@ -33,15 +33,6 @@ namespace { constexpr std::size_t kGridRowOverscan = 1; constexpr float kGridMinCellWidth = 140.0f; - std::string lower(std::string_view text) { - std::string out; - out.reserve(text.size()); - for (char ch : text) { - out.push_back(static_cast(std::tolower(static_cast(ch)))); - } - return out; - } - void configureDialogActionButton(Button& button, float scale) { button.setMinHeight(Style::controlHeight * scale); button.setMinWidth(92.0f * scale); @@ -614,11 +605,11 @@ void FileDialogView::refreshDirectory() { void FileDialogView::applyFilter(bool resetScroll) { const std::filesystem::path preserved = selectedPath(); - const std::string query = lower(m_filterQuery); + const std::string query = StringUtils::toLower(m_filterQuery); m_visibleEntries.clear(); for (const auto& entry : m_entries) { - if (!query.empty() && lower(entry.name).find(query) == std::string::npos) { + if (!query.empty() && StringUtils::toLower(entry.name).find(query) == std::string::npos) { continue; } m_visibleEntries.push_back(entry); diff --git a/src/util/string_utils.h b/src/util/string_utils.h index 5c804aa8c..ab3bd01d7 100644 --- a/src/util/string_utils.h +++ b/src/util/string_utils.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace StringUtils { @@ -154,4 +155,120 @@ namespace StringUtils { return out; } + [[nodiscard]] inline std::vector splitWhitespace(std::string_view text) { + std::vector result; + std::size_t i = 0; + while (i < text.size()) { + while (i < text.size() && std::isspace(static_cast(text[i])) != 0) { + ++i; + } + if (i >= text.size()) { + break; + } + std::size_t start = i; + while (i < text.size() && std::isspace(static_cast(text[i])) == 0) { + ++i; + } + result.emplace_back(text.substr(start, i - start)); + } + return result; + } + + [[nodiscard]] inline std::vector split(std::string_view text, char delimiter) { + std::vector result; + std::size_t start = 0; + while (start <= text.size()) { + std::size_t pos = text.find(delimiter, start); + if (pos == std::string_view::npos) { + result.push_back(text.substr(start)); + break; + } + result.push_back(text.substr(start, pos - start)); + start = pos + 1; + } + return result; + } + + [[nodiscard]] inline std::string join(const std::vector& parts, std::string_view separator) { + std::string result; + for (std::size_t i = 0; i < parts.size(); ++i) { + if (i > 0) { + result.append(separator); + } + result.append(parts[i]); + } + return result; + } + + [[nodiscard]] inline std::string replaceAll(std::string_view input, std::string_view from, std::string_view to) { + if (from.empty()) { + return std::string(input); + } + std::string result; + result.reserve(input.size()); + std::size_t pos = 0; + while (pos < input.size()) { + std::size_t found = input.find(from, pos); + if (found == std::string_view::npos) { + result.append(input.substr(pos)); + break; + } + result.append(input.substr(pos, found - pos)); + result.append(to); + pos = found + from.size(); + } + return result; + } + + [[nodiscard]] inline bool isBlank(std::string_view text) { + return text.empty() || + std::all_of(text.begin(), text.end(), [](unsigned char ch) { return std::isspace(ch) != 0; }); + } + + [[nodiscard]] inline std::string shellQuote(std::string_view text) { + std::string result = "'"; + for (char ch : text) { + if (ch == '\'') { + result += "'\\''"; + } else { + result += ch; + } + } + result += '\''; + return result; + } + + [[nodiscard]] inline std::string quoteDouble(std::string_view text) { + std::string result = "\""; + for (char ch : text) { + if (ch == '\\' || ch == '"') { + result += '\\'; + } + result += ch; + } + result += '"'; + return result; + } + + [[nodiscard]] inline std::string unquote(std::string_view text) { + if (text.size() < 2) { + return std::string(text); + } + char front = text.front(); + char back = text.back(); + if ((front == '"' && back == '"') || (front == '\'' && back == '\'')) { + text = text.substr(1, text.size() - 2); + std::string result; + result.reserve(text.size()); + for (std::size_t i = 0; i < text.size(); ++i) { + if (text[i] == '\\' && i + 1 < text.size()) { + ++i; + } + result += text[i]; + } + return result; + } + return std::string(text); + } + } // namespace StringUtils