From b94f1498542be0e83fe8060a50bce5ff2d5a1c39 Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Fri, 8 May 2026 15:31:02 +0100 Subject: [PATCH] config: allow hashes for parsing colors (#14337) --- src/config/legacy/ConfigManager.cpp | 17 +- .../lua/bindings/LuaBindingsNotification.cpp | 3 +- src/config/lua/objects/LuaNotification.cpp | 5 +- src/config/lua/types/LuaConfigColor.cpp | 4 +- src/config/lua/types/LuaConfigGradient.cpp | 3 +- src/config/shared/actions/ConfigActions.cpp | 15 +- src/config/shared/parserUtils/ParserUtils.cpp | 150 ++++++++++++++++++ src/config/shared/parserUtils/ParserUtils.hpp | 10 ++ src/debug/HyprCtl.cpp | 5 +- src/desktop/Workspace.cpp | 5 +- src/desktop/rule/windowRule/WindowRule.cpp | 3 +- src/errorOverlay/Overlay.hpp | 7 +- src/helpers/MiscFunctions.cpp | 113 ------------- src/helpers/MiscFunctions.hpp | 2 - src/managers/VersionKeeperManager.cpp | 9 +- src/plugins/HookSystem.cpp | 6 +- tests/config/shared/ParserUtils.cpp | 69 ++++++++ tests/helpers/MiscFunctions.cpp | 46 ------ 18 files changed, 277 insertions(+), 195 deletions(-) create mode 100644 src/config/shared/parserUtils/ParserUtils.cpp create mode 100644 src/config/shared/parserUtils/ParserUtils.hpp create mode 100644 tests/config/shared/ParserUtils.cpp diff --git a/src/config/legacy/ConfigManager.cpp b/src/config/legacy/ConfigManager.cpp index 7b4cc696f..09608fa89 100644 --- a/src/config/legacy/ConfigManager.cpp +++ b/src/config/legacy/ConfigManager.cpp @@ -14,6 +14,7 @@ #include "../shared/workspace/WorkspaceRuleManager.hpp" #include "../shared/animation/AnimationTree.hpp" #include "../shared/monitor/Parser.hpp" +#include "../shared/parserUtils/ParserUtils.hpp" #include "../supplementary/executor/Executor.hpp" #include "../supplementary/jeremy/Jeremy.hpp" #include "../../protocols/LayerShell.hpp" @@ -131,7 +132,7 @@ static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** } try { - const auto COL = configStringToInt(std::string(var)); + const auto COL = ParserUtils::parseColor(var); if (!COL) throw std::runtime_error(std::format("failed to parse {} as a color", var)); DATA->m_colors.emplace_back(COL.value()); @@ -1424,7 +1425,7 @@ std::optional CConfigManager::handleAnimation(const std::string& co return "no such animation"; // This helper casts strings like "1", "true", "off", "yes"... to int. - int64_t enabledInt = configStringToInt(ARGS[1]).value_or(0) == 1; + int64_t enabledInt = ParserUtils::parseInt(ARGS[1]).value_or(0) == 1; // Checking that the int is 1 or 0 because the helper can return integers out of range. if (enabledInt != 0 && enabledInt != 1) @@ -1715,24 +1716,24 @@ std::optional CConfigManager::handleWorkspaceRules(const std::strin wsRule.m_borderSize = std::stoi(rule.substr(delim + 11)); } catch (...) { return "Error parsing workspace rule bordersize: {}", rule.substr(delim + 11); } else if ((delim = rule.find("border:")) != std::string::npos) { - CHECK_OR_THROW(configStringToInt(rule.substr(delim + 7))) + CHECK_OR_THROW(ParserUtils::parseInt(rule.substr(delim + 7))) wsRule.m_noBorder = !*X; } else if ((delim = rule.find("shadow:")) != std::string::npos) { - CHECK_OR_THROW(configStringToInt(rule.substr(delim + 7))) + CHECK_OR_THROW(ParserUtils::parseInt(rule.substr(delim + 7))) wsRule.m_noShadow = !*X; } else if ((delim = rule.find("rounding:")) != std::string::npos) { - CHECK_OR_THROW(configStringToInt(rule.substr(delim + 9))) + CHECK_OR_THROW(ParserUtils::parseInt(rule.substr(delim + 9))) wsRule.m_noRounding = !*X; } else if ((delim = rule.find("decorate:")) != std::string::npos) { - CHECK_OR_THROW(configStringToInt(rule.substr(delim + 9))) + CHECK_OR_THROW(ParserUtils::parseInt(rule.substr(delim + 9))) wsRule.m_decorate = *X; } else if ((delim = rule.find("monitor:")) != std::string::npos) wsRule.m_monitor = rule.substr(delim + 8); else if ((delim = rule.find("default:")) != std::string::npos) { - CHECK_OR_THROW(configStringToInt(rule.substr(delim + 8))) + CHECK_OR_THROW(ParserUtils::parseInt(rule.substr(delim + 8))) wsRule.m_isDefault = *X; } else if ((delim = rule.find("persistent:")) != std::string::npos) { - CHECK_OR_THROW(configStringToInt(rule.substr(delim + 11))) + CHECK_OR_THROW(ParserUtils::parseInt(rule.substr(delim + 11))) wsRule.m_isPersistent = *X; } else if ((delim = rule.find("defaultName:")) != std::string::npos) wsRule.m_defaultName = trim(rule.substr(delim + 12)); diff --git a/src/config/lua/bindings/LuaBindingsNotification.cpp b/src/config/lua/bindings/LuaBindingsNotification.cpp index b7a54546a..3ba14ce72 100644 --- a/src/config/lua/bindings/LuaBindingsNotification.cpp +++ b/src/config/lua/bindings/LuaBindingsNotification.cpp @@ -1,6 +1,7 @@ #include "LuaBindingsInternal.hpp" #include "../objects/LuaNotification.hpp" +#include "../../shared/parserUtils/ParserUtils.hpp" #include "../../../helpers/MiscFunctions.hpp" #include "../../../notification/NotificationOverlay.hpp" @@ -52,7 +53,7 @@ static std::optional parseColorArg(lua_State* L, int idx) { return CHyprColor(sc(lua_tonumber(L, idx))); if (lua_isstring(L, idx)) { - auto parsed = configStringToInt(lua_tostring(L, idx)); + auto parsed = ParserUtils::parseColor(lua_tostring(L, idx)); if (!parsed) return std::nullopt; diff --git a/src/config/lua/objects/LuaNotification.cpp b/src/config/lua/objects/LuaNotification.cpp index f116e2ebe..99382bf6c 100644 --- a/src/config/lua/objects/LuaNotification.cpp +++ b/src/config/lua/objects/LuaNotification.cpp @@ -8,6 +8,9 @@ #include #include +#include "../../shared/parserUtils/ParserUtils.hpp" + +using namespace Config; using namespace Config::Lua; static constexpr const char* MT = "HL.Notification"; @@ -23,7 +26,7 @@ namespace { return CHyprColor(sc(lua_tonumber(L, idx))); if (lua_isstring(L, idx)) { - auto parsed = configStringToInt(lua_tostring(L, idx)); + auto parsed = ParserUtils::parseColor(lua_tostring(L, idx)); if (!parsed) return std::nullopt; diff --git a/src/config/lua/types/LuaConfigColor.cpp b/src/config/lua/types/LuaConfigColor.cpp index c58b232b2..e8c79088b 100644 --- a/src/config/lua/types/LuaConfigColor.cpp +++ b/src/config/lua/types/LuaConfigColor.cpp @@ -3,6 +3,8 @@ #include #include +#include "../../shared/parserUtils/ParserUtils.hpp" + #include "../../../helpers/Color.hpp" #include "../../../helpers/MiscFunctions.hpp" @@ -10,7 +12,7 @@ using namespace Config; using namespace Config::Lua; static std::expected parseColorString(const std::string& str) { - auto result = configStringToInt(str); + auto result = ParserUtils::parseColor(str); if (!result) return std::unexpected(std::format("invalid color \"{}\"", str)); return CHyprColor(sc(*result)); diff --git a/src/config/lua/types/LuaConfigGradient.cpp b/src/config/lua/types/LuaConfigGradient.cpp index 6e5838a92..cc936509c 100644 --- a/src/config/lua/types/LuaConfigGradient.cpp +++ b/src/config/lua/types/LuaConfigGradient.cpp @@ -1,4 +1,5 @@ #include "LuaConfigGradient.hpp" +#include "../../shared/parserUtils/ParserUtils.hpp" #include "../../../helpers/MiscFunctions.hpp" #include @@ -7,7 +8,7 @@ using namespace Config; using namespace Config::Lua; static std::expected parseColorString(const std::string& str) { - auto result = configStringToInt(str); + auto result = ParserUtils::parseColor(str); if (!result) return std::unexpected(std::format("invalid color \"{}\"", str)); return CHyprColor(sc(*result)); diff --git a/src/config/shared/actions/ConfigActions.cpp b/src/config/shared/actions/ConfigActions.cpp index 3f586b9f6..a59b4ab3b 100644 --- a/src/config/shared/actions/ConfigActions.cpp +++ b/src/config/shared/actions/ConfigActions.cpp @@ -1,4 +1,5 @@ #include "ConfigActions.hpp" +#include "../parserUtils/ParserUtils.hpp" #include "../../../desktop/state/FocusState.hpp" #include "../../../desktop/view/Window.hpp" #include "../../../desktop/view/Group.hpp" @@ -726,15 +727,15 @@ ActionResult Actions::setProp(const std::string& PROP, const std::string& VAL, s if (TOKEN.ends_with("deg")) colorData.m_angle = std::stoi(std::string(TOKEN.substr(0, TOKEN.size() - 3))) * (PI / 180.0); else - configStringToInt(std::string(TOKEN)).and_then([&colorData](const auto& e) { + ParserUtils::parseColor(std::string(TOKEN)).and_then([&colorData](const auto& e) { colorData.m_colors.push_back(e); - return std::invoke_result_t(1); + return std::invoke_result_t(1); }); } } else if (VAL != "-1") - configStringToInt(VAL).and_then([&colorData](const auto& e) { + ParserUtils::parseColor(VAL).and_then([&colorData](const auto& e) { colorData.m_colors.push_back(e); - return std::invoke_result_t(1); + return std::invoke_result_t(1); }); colorData.updateColorsOk(); @@ -754,15 +755,15 @@ ActionResult Actions::setProp(const std::string& PROP, const std::string& VAL, s Desktop::Types::SAlphaValue{std::stof(VAL), PWINDOW->m_ruleApplicator->alphaFullscreen().valueOrDefault().overridden}, Desktop::Types::PRIORITY_SET_PROP)); } else if (PROP == "opacity_override") { PWINDOW->m_ruleApplicator->alphaOverride(Desktop::Types::COverridableVar( - Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alpha().valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, + Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alpha().valueOrDefault().alpha, sc(ParserUtils::parseInt(VAL).value_or(0))}, Desktop::Types::PRIORITY_SET_PROP)); } else if (PROP == "opacity_inactive_override") { PWINDOW->m_ruleApplicator->alphaInactiveOverride(Desktop::Types::COverridableVar( - Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alphaInactive().valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, + Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alphaInactive().valueOrDefault().alpha, sc(ParserUtils::parseInt(VAL).value_or(0))}, Desktop::Types::PRIORITY_SET_PROP)); } else if (PROP == "opacity_fullscreen_override") { PWINDOW->m_ruleApplicator->alphaFullscreenOverride(Desktop::Types::COverridableVar( - Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alphaFullscreen().valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, + Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alphaFullscreen().valueOrDefault().alpha, sc(ParserUtils::parseInt(VAL).value_or(0))}, Desktop::Types::PRIORITY_SET_PROP)); } else if (PROP == "allows_input") parsePropTrivial(PWINDOW->m_ruleApplicator->allowsInput(), VAL); diff --git a/src/config/shared/parserUtils/ParserUtils.cpp b/src/config/shared/parserUtils/ParserUtils.cpp new file mode 100644 index 000000000..dec0bb2e1 --- /dev/null +++ b/src/config/shared/parserUtils/ParserUtils.cpp @@ -0,0 +1,150 @@ +#include "ParserUtils.hpp" + +#include +#include + +#include +#include +#include + +#include "../../../helpers/memory/Memory.hpp" + +using namespace Config; +using namespace Config::ParserUtils; +using namespace Hyprutils::String; + +static std::expected parseHex(std::string_view value) { + auto res = value.starts_with("0x") ? strToNumber(value) : strToNumber(std::string{"0x"} + value); + if (!res) + return std::unexpected(std::format("invalid hex \"{}\"", value)); + return *res; +} + +std::expected ParserUtils::parseColor(std::string_view val) { + + if (val.starts_with("#")) { + // parse either rgb or rgba + val = val.substr(1); + + if (val.length() != 6 && val.length() != 8 && val.length() != 3) + return std::unexpected(std::format("couldn't parse \"{}\" as a color", val)); + + if (val.length() == 3) { + auto r = parseHex(val.substr(0, 1)); + auto g = parseHex(val.substr(1, 1)); + auto b = parseHex(val.substr(2, 1)); + + if (!r || !g || !b) + return std::unexpected(std::format("couldn't parse \"{}\" as a color (bad hex)", val)); + + return 0xFF000000 | ((*r | (*r << 4)) << 16) | ((*g | (*g << 4)) << 8) | (*b | (*b << 4)); + } + + if (val.length() == 6) { + auto r = parseHex(val.substr(0, 2)); + auto g = parseHex(val.substr(2, 2)); + auto b = parseHex(val.substr(4, 2)); + + if (!r || !g || !b) + return std::unexpected(std::format("couldn't parse \"{}\" as a color (bad hex)", val)); + + return 0xFF000000 | (*r << 16) | (*g << 8) | *b; + } + + if (val.length() == 8) { + auto r = parseHex(val.substr(0, 2)); + auto g = parseHex(val.substr(2, 2)); + auto b = parseHex(val.substr(4, 2)); + auto a = parseHex(val.substr(6, 2)); + + if (!r || !g || !b || !a) + return std::unexpected(std::format("couldn't parse \"{}\" as a color (bad hex)", val)); + + return (*a << 24) | (*r << 16) | (*g << 8) | *b; + } + } + + if (val.starts_with("0x")) + return parseHex(val); + + if (val.starts_with("rgba(") && val.ends_with(')')) { + const auto VALUEWITHOUTFUNC = trim(val.substr(5, val.length() - 6)); + + // try doing it the comma way first + if (std::ranges::count(VALUEWITHOUTFUNC, ',') == 3) { + // cool + std::string_view rolling = VALUEWITHOUTFUNC; + auto r = strToNumber(trim(rolling.substr(0, rolling.find(',')))); + rolling = rolling.substr(rolling.find(',') + 1); + auto g = strToNumber(trim(rolling.substr(0, rolling.find(',')))); + rolling = rolling.substr(rolling.find(',') + 1); + auto b = strToNumber(trim(rolling.substr(0, rolling.find(',')))); + rolling = rolling.substr(rolling.find(',') + 1); + auto a = strToNumber(trim(rolling.substr(0, rolling.find(',')))); + + if (!r || !g || !b || !a) + return std::unexpected(std::format("failed parsing \"{}\" as a color", val)); + + return (sc(std::floor(*a * 255.F)) << 24) | (*r << 16) | (*g << 8) | *b; + } else if (VALUEWITHOUTFUNC.length() == 8) { + const auto RGBA = parseHex(VALUEWITHOUTFUNC); + + if (!RGBA) + return RGBA; + // now we need to RGBA -> ARGB. The config holds ARGB only. + return (*RGBA >> 8) + (0x1000000 * (*RGBA & 0xFF)); + } + + return std::unexpected("rgba() expects length of 8 characters (4 bytes) or 4 comma separated values"); + } + + if (val.starts_with("rgb(") && val.ends_with(')')) { + const auto VALUEWITHOUTFUNC = trim(val.substr(4, val.length() - 5)); + + // try doing it the comma way first + if (std::ranges::count(VALUEWITHOUTFUNC, ',') == 2) { + // cool + std::string_view rolling = VALUEWITHOUTFUNC; + auto r = strToNumber(trim(rolling.substr(0, rolling.find(',')))); + rolling = rolling.substr(rolling.find(',') + 1); + auto g = strToNumber(trim(rolling.substr(0, rolling.find(',')))); + rolling = rolling.substr(rolling.find(',') + 1); + auto b = strToNumber(trim(rolling.substr(0, rolling.find(',')))); + + if (!r || !g || !b) + return std::unexpected(std::format("failed parsing \"{}\" as a color", val)); + + return 0xFF000000 | (*r << 16) | (*g << 8) | *b; + } else if (VALUEWITHOUTFUNC.length() == 6) { + const auto r = parseHex(VALUEWITHOUTFUNC); + return r ? *r + 0xFF000000 : r; + } + + return std::unexpected("rgb() expects length of 6 characters (3 bytes) or 3 comma separated values"); + } + + if (isNumber2(val)) { + if (const auto v = strToNumber(val); v) + return *v; + } + + return std::unexpected(std::format("cannot parse \"{}\" as a color", val)); +} + +std::expected ParserUtils::parseInt(std::string_view val) { + if (val.starts_with("0x")) + return parseHex(val); + + if (val.starts_with("true") || val.starts_with("on") || val.starts_with("yes")) + return 1; + + if (val.starts_with("false") || val.starts_with("off") || val.starts_with("no")) + return 0; + + auto res = strToNumber(val); + + if (!res) + return std::unexpected(std::format("Failed to parse \"{}\" as an integer", val)); + + return *res; +} diff --git a/src/config/shared/parserUtils/ParserUtils.hpp b/src/config/shared/parserUtils/ParserUtils.hpp new file mode 100644 index 000000000..9dfe37f41 --- /dev/null +++ b/src/config/shared/parserUtils/ParserUtils.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +#include + +namespace Config::ParserUtils { + std::expected parseColor(std::string_view val); + std::expected parseInt(std::string_view val); +}; \ No newline at end of file diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index b6777de36..fdb9c89cb 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -36,6 +36,7 @@ using namespace Hyprutils::OS; #include "../config/legacy/ConfigManager.hpp" #include "../config/lua/ConfigManager.hpp" #include "../config/ConfigValue.hpp" +#include "../config/shared/parserUtils/ParserUtils.hpp" #include "../config/shared/complex/ComplexDataTypes.hpp" #include "../config/shared/inotify/ConfigWatcher.hpp" #include "../config/shared/workspace/WorkspaceRuleManager.hpp" @@ -1404,7 +1405,7 @@ static std::string dispatchSeterror(eHyprCtlOutputFormat format, std::string req return "ok"; } - const CHyprColor COLOR = configStringToInt(vars[1]).value_or(0); + const CHyprColor COLOR = Config::ParserUtils::parseColor(vars[1]).value_or(0); for (size_t i = 2; i < vars.size(); ++i) errorMessage += vars[i] + ' '; @@ -1871,7 +1872,7 @@ static std::string dispatchNotify(eHyprCtlOutputFormat format, std::string reque time = std::stoi(TIME); } catch (std::exception& e) { return "invalid arg 2"; } - const auto COLOR_RESULT = configStringToInt(vars[3]); + const auto COLOR_RESULT = Config::ParserUtils::parseColor(vars[3]); if (!COLOR_RESULT) return "invalid arg 3"; CHyprColor color = *COLOR_RESULT; diff --git a/src/desktop/Workspace.cpp b/src/desktop/Workspace.cpp index 18f77a63a..293c57b5f 100644 --- a/src/desktop/Workspace.cpp +++ b/src/desktop/Workspace.cpp @@ -2,6 +2,7 @@ #include "view/Group.hpp" #include "view/LayerSurface.hpp" #include "../Compositor.hpp" +#include "../config/shared/parserUtils/ParserUtils.hpp" #include "../config/shared/animation/AnimationTree.hpp" #include "../config/shared/workspace/WorkspaceRuleManager.hpp" #include "../config/supplementary/executor/Executor.hpp" @@ -199,7 +200,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { prop = prop.substr(2, prop.length() - 3); - const auto SHOULDBESPECIAL = configStringToInt(prop); + const auto SHOULDBESPECIAL = Config::ParserUtils::parseInt(prop); if (SHOULDBESPECIAL && sc(*SHOULDBESPECIAL) != m_isSpecialWorkspace) return false; @@ -234,7 +235,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { if (prop.starts_with("e:") && !m_name.ends_with(prop.substr(2))) return false; - const auto WANTSNAMED = configStringToInt(prop); + const auto WANTSNAMED = Config::ParserUtils::parseInt(prop); if (WANTSNAMED && *WANTSNAMED != (m_id <= -1337)) return false; diff --git a/src/desktop/rule/windowRule/WindowRule.cpp b/src/desktop/rule/windowRule/WindowRule.cpp index 5bbf2abe4..0262697a9 100644 --- a/src/desktop/rule/windowRule/WindowRule.cpp +++ b/src/desktop/rule/windowRule/WindowRule.cpp @@ -6,6 +6,7 @@ #include "../../../managers/TokenManager.hpp" #include "../../../desktop/state/FocusState.hpp" #include "../../../protocols/types/ContentType.hpp" +#include "../../../config/shared/parserUtils/ParserUtils.hpp" #include #include @@ -45,7 +46,7 @@ static std::expected parseFloat(std::string_view effectName, } static std::expected parseBorderColorToken(const std::string& raw, const std::string& token) { - auto parsed = configStringToInt(token); + auto parsed = Config::ParserUtils::parseColor(token); if (!parsed) return std::unexpected(std::format(R"(border_color rule "{}" has invalid color "{}": {})", raw, token, parsed.error())); diff --git a/src/errorOverlay/Overlay.hpp b/src/errorOverlay/Overlay.hpp index 68f0e5dcc..08c043368 100644 --- a/src/errorOverlay/Overlay.hpp +++ b/src/errorOverlay/Overlay.hpp @@ -5,7 +5,6 @@ #include "../defines.hpp" #include "../render/Texture.hpp" #include "../helpers/AnimatedVariable.hpp" -#include "../helpers/MiscFunctions.hpp" #include "../config/shared/complex/ComplexDataTypes.hpp" namespace ErrorOverlay { @@ -13,10 +12,8 @@ namespace ErrorOverlay { namespace Colors { constexpr const float ANGLE_30 = 0.52359877; - static const Config::CGradientValueData ERROR = - Config::CGradientValueData{std::vector{*configStringToInt("0xffff6666"), *configStringToInt("0xff800000")}, ANGLE_30}; - static const Config::CGradientValueData WARNING = - Config::CGradientValueData{std::vector{*configStringToInt("0xffffdb4d"), *configStringToInt("0xff665200")}, ANGLE_30}; + static const Config::CGradientValueData ERROR = Config::CGradientValueData{std::vector{0xffff6666, 0xff800000}, ANGLE_30}; + static const Config::CGradientValueData WARNING = Config::CGradientValueData{std::vector{0xffffdb4d, 0xff665200}, ANGLE_30}; }; class COverlay { diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp index 5aa58a2c8..e09030690 100644 --- a/src/helpers/MiscFunctions.cpp +++ b/src/helpers/MiscFunctions.cpp @@ -572,119 +572,6 @@ int64_t getPPIDof(int64_t pid) { #endif } -std::expected configStringToInt(const std::string& VALUE) { - auto parseHex = [](const std::string& value) -> std::expected { - try { - size_t position; - auto result = stoll(value, &position, 16); - if (position == value.size()) - return result; - } catch (const std::exception&) {} - return std::unexpected("invalid hex " + value); - }; - if (VALUE.starts_with("0x")) { - // Values with 0x are hex - return parseHex(VALUE); - } else if (VALUE.starts_with("rgba(") && VALUE.ends_with(')')) { - const auto VALUEWITHOUTFUNC = trim(VALUE.substr(5, VALUE.length() - 6)); - - // try doing it the comma way first - if (std::ranges::count(VALUEWITHOUTFUNC, ',') == 3) { - // cool - std::string rolling = VALUEWITHOUTFUNC; - auto r = configStringToInt(trim(rolling.substr(0, rolling.find(',')))); - rolling = rolling.substr(rolling.find(',') + 1); - auto g = configStringToInt(trim(rolling.substr(0, rolling.find(',')))); - rolling = rolling.substr(rolling.find(',') + 1); - auto b = configStringToInt(trim(rolling.substr(0, rolling.find(',')))); - rolling = rolling.substr(rolling.find(',') + 1); - uint8_t a = 0; - - if (!r || !g || !b) - return std::unexpected("failed parsing " + VALUEWITHOUTFUNC); - - try { - a = std::round(std::stof(trim(rolling.substr(0, rolling.find(',')))) * 255.f); - } catch (std::exception& e) { return std::unexpected("failed parsing " + VALUEWITHOUTFUNC); } - - return a * sc(0x1000000) + *r * sc(0x10000) + *g * sc(0x100) + *b; - } else if (VALUEWITHOUTFUNC.length() == 8) { - const auto RGBA = parseHex(VALUEWITHOUTFUNC); - - if (!RGBA) - return RGBA; - // now we need to RGBA -> ARGB. The config holds ARGB only. - return (*RGBA >> 8) + 0x1000000 * (*RGBA & 0xFF); - } - - return std::unexpected("rgba() expects length of 8 characters (4 bytes) or 4 comma separated values"); - - } else if (VALUE.starts_with("rgb(") && VALUE.ends_with(')')) { - const auto VALUEWITHOUTFUNC = trim(VALUE.substr(4, VALUE.length() - 5)); - - // try doing it the comma way first - if (std::ranges::count(VALUEWITHOUTFUNC, ',') == 2) { - // cool - std::string rolling = VALUEWITHOUTFUNC; - auto r = configStringToInt(trim(rolling.substr(0, rolling.find(',')))); - rolling = rolling.substr(rolling.find(',') + 1); - auto g = configStringToInt(trim(rolling.substr(0, rolling.find(',')))); - rolling = rolling.substr(rolling.find(',') + 1); - auto b = configStringToInt(trim(rolling.substr(0, rolling.find(',')))); - - if (!r || !g || !b) - return std::unexpected("failed parsing " + VALUEWITHOUTFUNC); - - return sc(0xFF000000) + *r * sc(0x10000) + *g * sc(0x100) + *b; - } else if (VALUEWITHOUTFUNC.length() == 6) { - auto r = parseHex(VALUEWITHOUTFUNC); - return r ? *r + 0xFF000000 : r; - } - - return std::unexpected("rgb() expects length of 6 characters (3 bytes) or 3 comma separated values"); - } else if (VALUE.starts_with("true") || VALUE.starts_with("on") || VALUE.starts_with("yes")) { - return 1; - } else if (VALUE.starts_with("false") || VALUE.starts_with("off") || VALUE.starts_with("no")) { - return 0; - } - - if (VALUE.empty() || !isNumber(VALUE, false)) - return std::unexpected("cannot parse \"" + VALUE + "\" as an int."); - - try { - const auto RES = std::stoll(VALUE); - return RES; - } catch (std::exception& e) { return std::unexpected(std::string{"stoll threw: "} + e.what()); } - - return std::unexpected("parse error"); -} - -Vector2D configStringToVector2D(const std::string& VALUE) { - std::istringstream iss(VALUE); - std::string token; - - if (!std::getline(iss, token, ' ') && !std::getline(iss, token, ',')) - throw std::invalid_argument("Invalid string format"); - - if (!isNumber(token)) - throw std::invalid_argument("Invalid x value"); - - long long x = std::stoll(token); - - if (!std::getline(iss, token)) - throw std::invalid_argument("Invalid string format"); - - if (!isNumber(token)) - throw std::invalid_argument("Invalid y value"); - - long long y = std::stoll(token); - - if (std::getline(iss, token)) - throw std::invalid_argument("Invalid string format"); - - return Vector2D(sc(x), sc(y)); -} - double normalizeAngleRad(double ang) { if (ang > M_PI * 2) { while (ang > M_PI * 2) diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp index 0528be58c..3cba5d572 100644 --- a/src/helpers/MiscFunctions.hpp +++ b/src/helpers/MiscFunctions.hpp @@ -29,8 +29,6 @@ std::optional cleanCmdForWorkspace(const std::string&, float vecToRectDistanceSquared(const Vector2D& vec, const Vector2D& p1, const Vector2D& p2); std::string execAndGet(const char*); int64_t getPPIDof(int64_t pid); -std::expected configStringToInt(const std::string&); -Vector2D configStringToVector2D(const std::string&); std::optional getPlusMinusKeywordResult(std::string in, float relative); double normalizeAngleRad(double ang); std::vector getBacktrace(); diff --git a/src/managers/VersionKeeperManager.cpp b/src/managers/VersionKeeperManager.cpp index 9a12adacf..12fde80de 100644 --- a/src/managers/VersionKeeperManager.cpp +++ b/src/managers/VersionKeeperManager.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include using namespace Hyprutils::String; @@ -62,13 +63,13 @@ CVersionKeeperManager::CVersionKeeperManager() { bool CVersionKeeperManager::isMajorVersionOlderThanRunning(const std::string& ver) { const CVarList verStrings(ver, 0, '.', true); - const int V1 = configStringToInt(verStrings[0]).value_or(0); - const int V2 = configStringToInt(verStrings[1]).value_or(0); + const int V1 = strToNumber(verStrings[0]).value_or(0); + const int V2 = strToNumber(verStrings[1]).value_or(0); static const CVarList runningStrings(HYPRLAND_VERSION, 0, '.', true); - static const int R1 = configStringToInt(runningStrings[0]).value_or(0); - static const int R2 = configStringToInt(runningStrings[1]).value_or(0); + static const int R1 = strToNumber(runningStrings[0]).value_or(0); + static const int R2 = strToNumber(runningStrings[1]).value_or(0); if (R1 > V1) return true; diff --git a/src/plugins/HookSystem.cpp b/src/plugins/HookSystem.cpp index f17a4556d..15ae0f50b 100644 --- a/src/plugins/HookSystem.cpp +++ b/src/plugins/HookSystem.cpp @@ -14,6 +14,10 @@ #include #include +#include + +using namespace Hyprutils::String; + CFunctionHook::CFunctionHook(HANDLE owner, void* source, void* destination) : m_source(source), m_destination(destination), m_owner(owner) { ; } @@ -92,7 +96,7 @@ CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstr size_t plusPresent = tokens[1][0] == '+' ? 1 : 0; size_t minusPresent = tokens[1][0] == '-' ? 1 : 0; std::string addr = tokens[1].substr((plusPresent || minusPresent), tokens[1].find("(%rip)") - (plusPresent || minusPresent)); - auto addrResult = configStringToInt(addr); + auto addrResult = strToNumber(addr); if (!addrResult) return {}; const int32_t OFFSET = (minusPresent ? -1 : 1) * *addrResult; diff --git a/tests/config/shared/ParserUtils.cpp b/tests/config/shared/ParserUtils.cpp new file mode 100644 index 000000000..76b516b41 --- /dev/null +++ b/tests/config/shared/ParserUtils.cpp @@ -0,0 +1,69 @@ + +#include + +#include + +using namespace Config; + +TEST(ParserUtils, parseIntDecimal) { + EXPECT_EQ(ParserUtils::parseInt("42").value(), 42); + EXPECT_EQ(ParserUtils::parseInt("0").value(), 0); + EXPECT_EQ(ParserUtils::parseInt("-1").value(), -1); +} + +TEST(ParserUtils, parseIntHex) { + EXPECT_EQ(ParserUtils::parseInt("0xFF").value(), 255); + EXPECT_EQ(ParserUtils::parseInt("0x00").value(), 0); + EXPECT_EQ(ParserUtils::parseInt("0x10").value(), 16); +} + +TEST(ParserUtils, parseIntBooleanStrings) { + // "true", "yes", "on" -> 1; "false", "no", "off" -> 0 + EXPECT_EQ(ParserUtils::parseInt("true").value(), 1); + EXPECT_EQ(ParserUtils::parseInt("yes").value(), 1); + EXPECT_EQ(ParserUtils::parseInt("on").value(), 1); + EXPECT_EQ(ParserUtils::parseInt("false").value(), 0); + EXPECT_EQ(ParserUtils::parseInt("no").value(), 0); + EXPECT_EQ(ParserUtils::parseInt("off").value(), 0); +} + +TEST(ParserUtils, parseIntInvalid) { + EXPECT_FALSE(ParserUtils::parseInt("").has_value()); + EXPECT_FALSE(ParserUtils::parseInt("abc").has_value()); + EXPECT_FALSE(ParserUtils::parseInt("rgba(20,20,20,5)").has_value()); + EXPECT_FALSE(ParserUtils::parseInt("#fff").has_value()); +} + +TEST(ParserUtils, parseColor) { + EXPECT_EQ(ParserUtils::parseColor("0xDEADBEEF").value_or(0), 0xDEADBEEF); + EXPECT_EQ(ParserUtils::parseColor("rgba(20, 21, 22, 0.5)").value_or(0), 0x7F141516); + EXPECT_EQ(ParserUtils::parseColor("rgb(20, 21, 22)").value_or(0), 0xFF141516); + EXPECT_EQ(ParserUtils::parseColor("rgb(141516)").value_or(0), 0xFF141516); + EXPECT_EQ(ParserUtils::parseColor("rgba(1415167f)").value_or(0), 0x7F141516); + EXPECT_EQ(ParserUtils::parseColor("4279506198").value_or(0), 0xFF141516); + EXPECT_EQ(ParserUtils::parseColor("1").value_or(0), 0x1); + EXPECT_EQ(ParserUtils::parseColor("#fed").value_or(0), 0xFFFFEEDD); + EXPECT_EQ(ParserUtils::parseColor("#FED").value_or(0), 0xFFFFEEDD); + EXPECT_EQ(ParserUtils::parseColor("#deffad").value_or(0), 0xFFDEFFAD); + EXPECT_EQ(ParserUtils::parseColor("#DEFFaD").value_or(0), 0xFFDEFFAD); + EXPECT_EQ(ParserUtils::parseColor("#DEFFaDAA").value_or(0), 0xAADEFFAD); + EXPECT_EQ(ParserUtils::parseColor("#DEFFaDaa").value_or(0), 0xAADEFFAD); +} + +TEST(ParserUtils, parseColorBad) { + EXPECT_FALSE(!!ParserUtils::parseColor("mak")); + EXPECT_FALSE(!!ParserUtils::parseColor("true")); + EXPECT_FALSE(!!ParserUtils::parseColor("on")); + EXPECT_FALSE(!!ParserUtils::parseColor("fucker")); + EXPECT_FALSE(!!ParserUtils::parseColor("I sniff glue")); + EXPECT_FALSE(!!ParserUtils::parseColor("0DEADBEEF")); + EXPECT_FALSE(!!ParserUtils::parseColor("6270000000")); + EXPECT_FALSE(!!ParserUtils::parseColor("rgba(20, 21, 22)")); + EXPECT_FALSE(!!ParserUtils::parseColor("rgb(20, 21, 22, 0.2)")); + EXPECT_FALSE(!!ParserUtils::parseColor("#afed")); + EXPECT_FALSE(!!ParserUtils::parseColor("#FE")); + EXPECT_FALSE(!!ParserUtils::parseColor("#defd")); + EXPECT_FALSE(!!ParserUtils::parseColor("#DEFFD")); + EXPECT_FALSE(!!ParserUtils::parseColor("#DEFFaDAAe")); + EXPECT_FALSE(!!ParserUtils::parseColor("#DEFFaDa")); +} \ No newline at end of file diff --git a/tests/helpers/MiscFunctions.cpp b/tests/helpers/MiscFunctions.cpp index e0857987d..4eefb5d35 100644 --- a/tests/helpers/MiscFunctions.cpp +++ b/tests/helpers/MiscFunctions.cpp @@ -97,49 +97,3 @@ TEST(Helpers, truthyFalse) { EXPECT_FALSE(truthy("")); EXPECT_FALSE(truthy("random")); } - -// configStringToInt - -TEST(Helpers, configStringToIntDecimal) { - EXPECT_EQ(configStringToInt("42").value(), 42); - EXPECT_EQ(configStringToInt("0").value(), 0); - EXPECT_EQ(configStringToInt("-1").value(), -1); -} - -TEST(Helpers, configStringToIntHex) { - EXPECT_EQ(configStringToInt("0xFF").value(), 255); - EXPECT_EQ(configStringToInt("0x00").value(), 0); - EXPECT_EQ(configStringToInt("0x10").value(), 16); -} - -TEST(Helpers, configStringToIntRgba) { - auto result = configStringToInt("rgba(255, 0, 0, 1.0)"); - EXPECT_TRUE(result.has_value()); -} - -TEST(Helpers, configStringToIntBooleanStrings) { - // "true", "yes", "on" -> 1; "false", "no", "off" -> 0 - EXPECT_EQ(configStringToInt("true").value(), 1); - EXPECT_EQ(configStringToInt("yes").value(), 1); - EXPECT_EQ(configStringToInt("on").value(), 1); - EXPECT_EQ(configStringToInt("false").value(), 0); - EXPECT_EQ(configStringToInt("no").value(), 0); - EXPECT_EQ(configStringToInt("off").value(), 0); -} - -TEST(Helpers, configStringToIntInvalid) { - EXPECT_FALSE(configStringToInt("").has_value()); - EXPECT_FALSE(configStringToInt("abc").has_value()); -} - -// configStringToVector2D - -TEST(Helpers, configStringToVector2DValid) { - EXPECT_EQ(configStringToVector2D("1920 1080"), Vector2D(1920, 1080)); - EXPECT_EQ(configStringToVector2D("0 0"), Vector2D(0, 0)); -} - -TEST(Helpers, configStringToVector2DInvalid) { - EXPECT_THROW(configStringToVector2D("notvalid"), std::invalid_argument); - EXPECT_THROW(configStringToVector2D(""), std::invalid_argument); -}