From 06b07c53e5f71f864c795e38d314a6b1bb390d5c Mon Sep 17 00:00:00 2001 From: Vaxry Date: Wed, 21 Feb 2024 14:08:40 +0000 Subject: [PATCH] label: add executing commands --- src/core/hyprlock.cpp | 70 ++++++++++++++++++++++++++ src/core/hyprlock.hpp | 3 ++ src/helpers/VarList.cpp | 55 ++++++++++++++++++++ src/helpers/VarList.hpp | 63 +++++++++++++++++++++++ src/renderer/AsyncResourceGatherer.cpp | 8 +-- src/renderer/widgets/IWidget.cpp | 20 ++++++++ src/renderer/widgets/IWidget.hpp | 2 + src/renderer/widgets/Label.cpp | 3 +- 8 files changed, 220 insertions(+), 4 deletions(-) create mode 100644 src/helpers/VarList.cpp create mode 100644 src/helpers/VarList.hpp diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 9184ccc..09cdba5 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -539,3 +539,73 @@ std::shared_ptr CHyprlock::addTimer(const std::chrono::system_clock::dur m_sLoopState.timerCV.notify_all(); return T; } + +void CHyprlock::spawnAsync(const std::string& args) { + Debug::log(LOG, "Executing (async) {}", args); + + int socket[2]; + if (pipe(socket) != 0) + Debug::log(LOG, "Unable to create pipe for fork"); + + pid_t child, grandchild; + child = fork(); + + if (child < 0) { + close(socket[0]); + close(socket[1]); + Debug::log(LOG, "Fail to create the first fork"); + return; + } + + if (child == 0) { + // run in child + + sigset_t set; + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, NULL); + + grandchild = fork(); + + if (grandchild == 0) { + // run in grandchild + close(socket[0]); + close(socket[1]); + execl("/bin/sh", "/bin/sh", "-c", args.c_str(), nullptr); + // exit grandchild + _exit(0); + } + + close(socket[0]); + write(socket[1], &grandchild, sizeof(grandchild)); + close(socket[1]); + // exit child + _exit(0); + } + + // run in parent + close(socket[1]); + read(socket[0], &grandchild, sizeof(grandchild)); + close(socket[0]); + // clear child and leave child to init + waitpid(child, NULL, 0); + + if (child < 0) { + Debug::log(LOG, "Failed to create the second fork"); + return; + } + + Debug::log(LOG, "Process Created with pid {}", grandchild); +} + +std::string CHyprlock::spawnSync(const std::string& cmd) { + std::array buffer; + std::string result; + const std::unique_ptr pipe(popen(cmd.c_str(), "r"), pclose); + if (!pipe) + return ""; + + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + return result; +} diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index 88a53ec..1536cc5 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -33,6 +33,9 @@ class CHyprlock { void lockSession(); void unlockSession(); + void spawnAsync(const std::string& cmd); + std::string spawnSync(const std::string& cmd); + void onKey(uint32_t key); void onPasswordCheckTimer(); bool passwordCheckWaiting(); diff --git a/src/helpers/VarList.cpp b/src/helpers/VarList.cpp new file mode 100644 index 0000000..518acde --- /dev/null +++ b/src/helpers/VarList.cpp @@ -0,0 +1,55 @@ +#include "VarList.hpp" +#include +#include + +static std::string removeBeginEndSpacesTabs(std::string str) { + if (str.empty()) + return str; + + int countBefore = 0; + while (str[countBefore] == ' ' || str[countBefore] == '\t') { + countBefore++; + } + + int countAfter = 0; + while ((int)str.length() - countAfter - 1 >= 0 && (str[str.length() - countAfter - 1] == ' ' || str[str.length() - 1 - countAfter] == '\t')) { + countAfter++; + } + + str = str.substr(countBefore, str.length() - countBefore - countAfter); + + return str; +} + +CVarList::CVarList(const std::string& in, const size_t lastArgNo, const char delim, const bool removeEmpty) { + if (in.empty()) + m_vArgs.emplace_back(""); + + std::string args{in}; + size_t idx = 0; + size_t pos = 0; + std::ranges::replace_if( + args, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0); + + for (const auto& s : args | std::views::split(0)) { + if (removeEmpty && s.empty()) + continue; + if (++idx == lastArgNo) { + m_vArgs.emplace_back(removeBeginEndSpacesTabs(in.substr(pos))); + break; + } + pos += s.size() + 1; + m_vArgs.emplace_back(removeBeginEndSpacesTabs(std::string_view{s}.data())); + } +} + +std::string CVarList::join(const std::string& joiner, size_t from, size_t to) const { + size_t last = to == 0 ? size() : to; + + std::string rolling; + for (size_t i = from; i < last; ++i) { + rolling += m_vArgs[i] + (i + 1 < last ? joiner : ""); + } + + return rolling; +} \ No newline at end of file diff --git a/src/helpers/VarList.hpp b/src/helpers/VarList.hpp new file mode 100644 index 0000000..1374da6 --- /dev/null +++ b/src/helpers/VarList.hpp @@ -0,0 +1,63 @@ +#pragma once +#include +#include +#include + +class CVarList { + public: + /** Split string into arg list + @param lastArgNo stop splitting after argv reaches maximum size, last arg will contain rest of unsplit args + @param delim if delimiter is 's', use std::isspace + @param removeEmpty remove empty args from argv + */ + CVarList(const std::string& in, const size_t maxSize = 0, const char delim = ',', const bool removeEmpty = false); + + ~CVarList() = default; + + size_t size() const { + return m_vArgs.size(); + } + + std::string join(const std::string& joiner, size_t from = 0, size_t to = 0) const; + + void map(std::function func) { + for (auto& s : m_vArgs) + func(s); + } + + void append(const std::string arg) { + m_vArgs.emplace_back(arg); + } + + std::string operator[](const size_t& idx) const { + if (idx >= m_vArgs.size()) + return ""; + return m_vArgs[idx]; + } + + // for range-based loops + std::vector::iterator begin() { + return m_vArgs.begin(); + } + std::vector::const_iterator begin() const { + return m_vArgs.begin(); + } + std::vector::iterator end() { + return m_vArgs.end(); + } + std::vector::const_iterator end() const { + return m_vArgs.end(); + } + + bool contains(const std::string& el) { + for (auto& a : m_vArgs) { + if (a == el) + return true; + } + + return false; + } + + private: + std::vector m_vArgs; +}; \ No newline at end of file diff --git a/src/renderer/AsyncResourceGatherer.cpp b/src/renderer/AsyncResourceGatherer.cpp index faf63cc..8ccf864 100644 --- a/src/renderer/AsyncResourceGatherer.cpp +++ b/src/renderer/AsyncResourceGatherer.cpp @@ -129,6 +129,8 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) { const int FONTSIZE = rq.props.contains("font_size") ? std::any_cast(rq.props.at("font_size")) : 16; const CColor FONTCOLOR = rq.props.contains("color") ? std::any_cast(rq.props.at("color")) : CColor(1.0, 1.0, 1.0, 1.0); const std::string FONTFAMILY = rq.props.contains("font_family") ? std::any_cast(rq.props.at("font_family")) : "Sans"; + const bool ISCMD = rq.props.contains("cmd") ? std::any_cast(rq.props.at("cmd")) : false; + const std::string TEXT = ISCMD ? g_pHyprlock->spawnSync(rq.asset) : rq.asset; auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* dummy value */); auto CAIRO = cairo_create(CAIROSURFACE); @@ -144,12 +146,12 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) { PangoAttrList* attrList = nullptr; GError* gError = nullptr; char* buf = nullptr; - if (pango_parse_markup(rq.asset.c_str(), -1, 0, &attrList, &buf, nullptr, &gError)) + if (pango_parse_markup(TEXT.c_str(), -1, 0, &attrList, &buf, nullptr, &gError)) pango_layout_set_text(layout, buf, -1); else { - Debug::log(ERR, "Pango markup parsing for {} failed: {}", rq.asset, gError->message); + Debug::log(ERR, "Pango markup parsing for {} failed: {}", TEXT, gError->message); g_error_free(gError); - pango_layout_set_text(layout, rq.asset.c_str(), -1); + pango_layout_set_text(layout, TEXT.c_str(), -1); } if (!attrList) diff --git a/src/renderer/widgets/IWidget.cpp b/src/renderer/widgets/IWidget.cpp index 0b9b366..bbfdd80 100644 --- a/src/renderer/widgets/IWidget.cpp +++ b/src/renderer/widgets/IWidget.cpp @@ -1,5 +1,6 @@ #include "IWidget.hpp" #include "../../helpers/Log.hpp" +#include "../../helpers/VarList.hpp" #include Vector2D IWidget::posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign) { @@ -55,6 +56,25 @@ IWidget::SFormatResult IWidget::formatString(std::string in) { result.updateEveryMs = result.updateEveryMs != 0 && result.updateEveryMs < 1000 ? result.updateEveryMs : 1000; } + if (in.starts_with("cmd[") && in.contains("]")) { + // this is a command + CVarList vars(in.substr(4, in.find_first_of(']') - 4)); + + for (const auto& v : vars) { + if (v.starts_with("update:")) { + try { + result.updateEveryMs = std::stoull(v.substr(7)); + } catch (std::exception& e) { Debug::log(ERR, "Error parsing {} in cmd[]", v); } + } else { + Debug::log(ERR, "Unknown prop in string format {}", v); + } + } + + result.alwaysUpdate = true; + in = in.substr(in.find_first_of(']') + 1); + result.cmd = true; + } + result.formatted = in; return result; } \ No newline at end of file diff --git a/src/renderer/widgets/IWidget.hpp b/src/renderer/widgets/IWidget.hpp index 71762e5..e81b53a 100644 --- a/src/renderer/widgets/IWidget.hpp +++ b/src/renderer/widgets/IWidget.hpp @@ -16,6 +16,8 @@ class IWidget { struct SFormatResult { std::string formatted; float updateEveryMs = 0; // 0 means don't (static) + bool alwaysUpdate = false; + bool cmd = false; }; virtual SFormatResult formatString(std::string in); diff --git a/src/renderer/widgets/Label.cpp b/src/renderer/widgets/Label.cpp index 2dd71ec..f3e1921 100644 --- a/src/renderer/widgets/Label.cpp +++ b/src/renderer/widgets/Label.cpp @@ -26,7 +26,7 @@ void CLabel::onTimerUpdate() { label = formatString(labelPreFormat); - if (label.formatted == oldFormatted) + if (label.formatted == oldFormatted && !label.alwaysUpdate) return; if (!pendingResourceID.empty()) @@ -60,6 +60,7 @@ CLabel::CLabel(const Vector2D& viewport_, const std::unordered_mapasyncResourceGatherer->requestAsyncAssetPreload(request);