hyprland/src/helpers/MiscFunctions.cpp
Tom Englund 32c0fa2f2f
core: begin using CFileDescriptor from hyprutils (#9122)
* config: make fd use CFileDescriptor

make use of the new hyprutils CFileDescriptor instead of manual FD
handling.

* hyprctl: make fd use CFileDescriptor

make use of the new hyprutils CFileDescriptor instead of manual FD
handling.

* ikeyboard: make fd use CFileDescriptor

make use of the new CFileDescriptor instead of manual FD handling, also
in sendKeymap remove dead code, it already early returns if keyboard
isnt valid, and dont try to close the FD that ikeyboard owns.

* core: make SHMFile functions use CFileDescriptor

make SHMFile misc functions use CFileDescriptor and its associated usage
in dmabuf and keyboard.

* core: make explicit sync use CFileDescriptor

begin using CFileDescriptor in explicit sync and its timelines and
eglsync usage in opengl, there is still a bit left with manual handling
that requires future aquamarine change aswell.

* eventmgr: make fd and sockets use CFileDescriptor

make use of the hyprutils CFileDescriptor instead of manual FD and
socket handling and closing.

* eventloopmgr: make timerfd use CFileDescriptor

make the timerfd use CFileDescriptor instead of manual fd handling

* opengl: make gbm fd use CFileDescriptor

make the gbm rendernode fd use CFileDescriptor instead of manual fd
handling

* core: make selection source/offer use CFileDescriptor

make data selection source and offers use CFileDescriptor and its
associated use in xwm and protocols

* protocols: convert protocols fd to CFileDescriptor

make most fd handling use CFileDescriptor in protocols

* shm: make SHMPool use CfileDescriptor

make SHMPool use CFileDescriptor instead of manual fd handling.

* opengl: duplicate fd with CFileDescriptor

duplicate fenceFD with CFileDescriptor duplicate instead.

* xwayland: make sockets and fds use CFileDescriptor

instead of manual opening/closing make sockets and fds use
CFileDescriptor

* keybindmgr: make sockets and fds use CFileDescriptor

make sockets and fds use CFileDescriptor instead of manual handling.
2025-01-30 11:30:12 +00:00

911 lines
31 KiB
C++

#include "MiscFunctions.hpp"
#include "../defines.hpp"
#include <algorithm>
#include "../Compositor.hpp"
#include "../managers/TokenManager.hpp"
#include "Monitor.hpp"
#include "../config/ConfigManager.hpp"
#include "fs/FsUtils.hpp"
#include <optional>
#include <cstring>
#include <cmath>
#include <filesystem>
#include <set>
#include <sys/utsname.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iomanip>
#include <sstream>
#ifdef HAS_EXECINFO
#include <execinfo.h>
#endif
#include <hyprutils/string/String.hpp>
#include <hyprutils/os/Process.hpp>
using namespace Hyprutils::String;
using namespace Hyprutils::OS;
#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include <sys/sysctl.h>
#if defined(__DragonFly__)
#include <sys/kinfo.h> // struct kinfo_proc
#elif defined(__FreeBSD__)
#include <sys/user.h> // struct kinfo_proc
#endif
#if defined(__NetBSD__)
#undef KERN_PROC
#define KERN_PROC KERN_PROC2
#define KINFO_PROC struct kinfo_proc2
#else
#define KINFO_PROC struct kinfo_proc
#endif
#if defined(__DragonFly__)
#define KP_PPID(kp) kp.kp_ppid
#elif defined(__FreeBSD__)
#define KP_PPID(kp) kp.ki_ppid
#else
#define KP_PPID(kp) kp.p_ppid
#endif
#endif
static const float transforms[][9] = {
{
1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
{
0.0f,
1.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
{
-1.0f,
0.0f,
0.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
{
0.0f,
-1.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
{
-1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
{
0.0f,
1.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
{
1.0f,
0.0f,
0.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
{
0.0f,
-1.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
},
};
std::string absolutePath(const std::string& rawpath, const std::string& currentPath) {
auto value = rawpath;
if (value[0] == '~') {
static const char* const ENVHOME = getenv("HOME");
value.replace(0, 1, std::string(ENVHOME));
} else if (value[0] != '/') {
auto currentDir = currentPath.substr(0, currentPath.find_last_of('/'));
if (value[0] == '.') {
if (value[1] == '.' && value[2] == '/') {
auto parentDir = currentDir.substr(0, currentDir.find_last_of('/'));
value.replace(0, 2 + currentPath.empty(), parentDir);
} else if (value[1] == '/')
value.replace(0, 1 + currentPath.empty(), currentDir);
else
value = currentDir + '/' + value;
} else
value = currentDir + '/' + value;
}
return value;
}
std::string escapeJSONStrings(const std::string& str) {
std::ostringstream oss;
for (auto const& c : str) {
switch (c) {
case '"': oss << "\\\""; break;
case '\\': oss << "\\\\"; break;
case '\b': oss << "\\b"; break;
case '\f': oss << "\\f"; break;
case '\n': oss << "\\n"; break;
case '\r': oss << "\\r"; break;
case '\t': oss << "\\t"; break;
default:
if ('\x00' <= c && c <= '\x1f') {
oss << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast<int>(c);
} else {
oss << c;
}
}
}
return oss.str();
}
std::optional<float> getPlusMinusKeywordResult(std::string source, float relative) {
try {
return relative + stof(source);
} catch (...) {
Debug::log(ERR, "Invalid arg \"{}\" in getPlusMinusKeywordResult!", source);
return {};
}
}
bool isDirection(const std::string& arg) {
return arg == "l" || arg == "r" || arg == "u" || arg == "d" || arg == "t" || arg == "b";
}
bool isDirection(const char& arg) {
return arg == 'l' || arg == 'r' || arg == 'u' || arg == 'd' || arg == 't' || arg == 'b';
}
SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
SWorkspaceIDName result = {WORKSPACE_INVALID, ""};
if (in.starts_with("special")) {
result.name = "special:special";
if (in.length() > 8) {
const auto NAME = in.substr(8);
const auto WS = g_pCompositor->getWorkspaceByName("special:" + NAME);
return {WS ? WS->m_iID : g_pCompositor->getNewSpecialID(), "special:" + NAME};
}
result.id = SPECIAL_WORKSPACE_START;
return result;
} else if (in.starts_with("name:")) {
const auto WORKSPACENAME = in.substr(in.find_first_of(':') + 1);
const auto WORKSPACE = g_pCompositor->getWorkspaceByName(WORKSPACENAME);
if (!WORKSPACE) {
result.id = g_pCompositor->getNextAvailableNamedWorkspace();
} else {
result.id = WORKSPACE->m_iID;
}
result.name = WORKSPACENAME;
} else if (in.starts_with("empty")) {
const bool same_mon = in.substr(5).contains("m");
const bool next = in.substr(5).contains("n");
if ((same_mon || next) && !g_pCompositor->m_pLastMonitor) {
Debug::log(ERR, "Empty monitor workspace on monitor null!");
return {WORKSPACE_INVALID};
}
std::set<WORKSPACEID> invalidWSes;
if (same_mon) {
for (auto const& rule : g_pConfigManager->getAllWorkspaceRules()) {
const auto PMONITOR = g_pCompositor->getMonitorFromName(rule.monitor);
if (PMONITOR && (PMONITOR->ID != g_pCompositor->m_pLastMonitor->ID))
invalidWSes.insert(rule.workspaceId);
}
}
WORKSPACEID id = next ? g_pCompositor->m_pLastMonitor->activeWorkspaceID() : 0;
while (++id < LONG_MAX) {
const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(id);
if (!invalidWSes.contains(id) && (!PWORKSPACE || PWORKSPACE->getWindows() == 0)) {
result.id = id;
return result;
}
}
} else if (in.starts_with("prev")) {
if (!g_pCompositor->m_pLastMonitor)
return {WORKSPACE_INVALID};
const auto PWORKSPACE = g_pCompositor->m_pLastMonitor->activeWorkspace;
if (!valid(PWORKSPACE))
return {WORKSPACE_INVALID};
const auto PLASTWORKSPACE = g_pCompositor->getWorkspaceByID(PWORKSPACE->getPrevWorkspaceIDName().id);
if (!PLASTWORKSPACE)
return {WORKSPACE_INVALID};
return {PLASTWORKSPACE->m_iID, PLASTWORKSPACE->m_szName};
} else {
if (in[0] == 'r' && (in[1] == '-' || in[1] == '+' || in[1] == '~') && isNumber(in.substr(2))) {
bool absolute = in[1] == '~';
if (!g_pCompositor->m_pLastMonitor) {
Debug::log(ERR, "Relative monitor workspace on monitor null!");
return {WORKSPACE_INVALID};
}
const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in.substr(absolute ? 2 : 1), 0);
if (!PLUSMINUSRESULT.has_value())
return {WORKSPACE_INVALID};
result.id = (int)PLUSMINUSRESULT.value();
WORKSPACEID remains = result.id;
std::set<WORKSPACEID> invalidWSes;
// Collect all the workspaces we can't jump to.
for (auto const& ws : g_pCompositor->m_vWorkspaces) {
if (ws->m_bIsSpecialWorkspace || (ws->m_pMonitor != g_pCompositor->m_pLastMonitor)) {
// Can't jump to this workspace
invalidWSes.insert(ws->m_iID);
}
}
for (auto const& rule : g_pConfigManager->getAllWorkspaceRules()) {
const auto PMONITOR = g_pCompositor->getMonitorFromName(rule.monitor);
if (!PMONITOR || PMONITOR->ID == g_pCompositor->m_pLastMonitor->ID) {
// Can't be invalid
continue;
}
// WS is bound to another monitor, can't jump to this
invalidWSes.insert(rule.workspaceId);
}
// Prepare all named workspaces in case when we need them
std::vector<WORKSPACEID> namedWSes;
for (auto const& ws : g_pCompositor->m_vWorkspaces) {
if (ws->m_bIsSpecialWorkspace || (ws->m_pMonitor != g_pCompositor->m_pLastMonitor) || ws->m_iID >= 0)
continue;
namedWSes.push_back(ws->m_iID);
}
std::sort(namedWSes.begin(), namedWSes.end());
if (absolute) {
// 1-index
remains -= 1;
// traverse valid workspaces until we reach the remains
if ((size_t)remains < namedWSes.size()) {
result.id = namedWSes[remains];
} else {
remains -= namedWSes.size();
result.id = 0;
while (remains >= 0) {
result.id++;
if (!invalidWSes.contains(result.id)) {
remains--;
}
}
}
} else {
// Just take a blind guess at where we'll probably end up
WORKSPACEID activeWSID = g_pCompositor->m_pLastMonitor->activeWorkspace ? g_pCompositor->m_pLastMonitor->activeWorkspace->m_iID : 1;
WORKSPACEID predictedWSID = activeWSID + remains;
int remainingWSes = 0;
char walkDir = in[1];
// sanitize. 0 means invalid oob in -
predictedWSID = std::max(predictedWSID, static_cast<int64_t>(0));
// Count how many invalidWSes are in between (how bad the prediction was)
WORKSPACEID beginID = in[1] == '+' ? activeWSID + 1 : predictedWSID;
WORKSPACEID endID = in[1] == '+' ? predictedWSID : activeWSID;
auto begin = invalidWSes.upper_bound(beginID - 1); // upper_bound is >, we want >=
for (auto it = begin; it != invalidWSes.end() && *it <= endID; it++) {
remainingWSes++;
}
// Handle named workspaces. They are treated like always before other workspaces
if (activeWSID < 0) {
// Behaviour similar to 'm'
// Find current
size_t currentItem = -1;
for (size_t i = 0; i < namedWSes.size(); i++) {
if (namedWSes[i] == activeWSID) {
currentItem = i;
break;
}
}
currentItem += remains;
currentItem = std::max(currentItem, static_cast<size_t>(0));
if (currentItem >= namedWSes.size()) {
// At the seam between namedWSes and normal WSes. Behave like r+[diff] at imaginary ws 0
size_t diff = currentItem - (namedWSes.size() - 1);
predictedWSID = diff;
WORKSPACEID beginID = 1;
WORKSPACEID endID = predictedWSID;
auto begin = invalidWSes.upper_bound(beginID - 1); // upper_bound is >, we want >=
for (auto it = begin; it != invalidWSes.end() && *it <= endID; it++) {
remainingWSes++;
}
walkDir = '+';
} else {
// We found our final ws.
remainingWSes = 0;
predictedWSID = namedWSes[currentItem];
}
}
// Go in the search direction for remainingWSes
// The performance impact is directly proportional to the number of open and bound workspaces
WORKSPACEID finalWSID = predictedWSID;
if (walkDir == '-') {
WORKSPACEID beginID = finalWSID;
WORKSPACEID curID = finalWSID;
while (--curID > 0 && remainingWSes > 0) {
if (!invalidWSes.contains(curID)) {
remainingWSes--;
}
finalWSID = curID;
}
if (finalWSID <= 0 || invalidWSes.contains(finalWSID)) {
if (namedWSes.size()) {
// Go to the named workspaces
// Need remainingWSes more
auto namedWSIdx = namedWSes.size() - remainingWSes;
// Sanitze
namedWSIdx = std::clamp(namedWSIdx, static_cast<size_t>(0), namedWSes.size() - static_cast<size_t>(1));
finalWSID = namedWSes[namedWSIdx];
} else {
// Couldn't find valid workspace in negative direction, search last first one back up positive direction
walkDir = '+';
// We know, that everything less than beginID is invalid, so don't bother with that
finalWSID = beginID;
remainingWSes = 1;
}
}
}
if (walkDir == '+') {
WORKSPACEID curID = finalWSID;
while (++curID < INT32_MAX && remainingWSes > 0) {
if (!invalidWSes.contains(curID)) {
remainingWSes--;
}
finalWSID = curID;
}
}
result.id = finalWSID;
}
const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(result.id);
if (PWORKSPACE)
result.name = g_pCompositor->getWorkspaceByID(result.id)->m_szName;
else
result.name = std::to_string(result.id);
} else if ((in[0] == 'm' || in[0] == 'e') && (in[1] == '-' || in[1] == '+' || in[1] == '~') && isNumber(in.substr(2))) {
bool onAllMonitors = in[0] == 'e';
bool absolute = in[1] == '~';
if (!g_pCompositor->m_pLastMonitor) {
Debug::log(ERR, "Relative monitor workspace on monitor null!");
return {WORKSPACE_INVALID};
}
// monitor relative
const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in.substr(absolute ? 2 : 1), 0);
if (!PLUSMINUSRESULT.has_value())
return {WORKSPACE_INVALID};
result.id = (int)PLUSMINUSRESULT.value();
// result now has +/- what we should move on mon
int remains = (int)result.id;
std::vector<WORKSPACEID> validWSes;
for (auto const& ws : g_pCompositor->m_vWorkspaces) {
if (ws->m_bIsSpecialWorkspace || (ws->m_pMonitor != g_pCompositor->m_pLastMonitor && !onAllMonitors))
continue;
validWSes.push_back(ws->m_iID);
}
std::sort(validWSes.begin(), validWSes.end());
ssize_t currentItem = -1;
if (absolute) {
// 1-index
currentItem = remains - 1;
// clamp
if (currentItem < 0) {
currentItem = 0;
} else if (currentItem >= (ssize_t)validWSes.size()) {
currentItem = validWSes.size() - 1;
}
} else {
// get the offset
remains = remains < 0 ? -((-remains) % validWSes.size()) : remains % validWSes.size();
// get the current item
WORKSPACEID activeWSID = g_pCompositor->m_pLastMonitor->activeWorkspace ? g_pCompositor->m_pLastMonitor->activeWorkspace->m_iID : 1;
for (ssize_t i = 0; i < (ssize_t)validWSes.size(); i++) {
if (validWSes[i] == activeWSID) {
currentItem = i;
break;
}
}
// apply
currentItem += remains;
// sanitize
if (currentItem >= (ssize_t)validWSes.size()) {
currentItem = currentItem % validWSes.size();
} else if (currentItem < 0) {
currentItem = validWSes.size() + currentItem;
}
}
result.id = validWSes[currentItem];
result.name = g_pCompositor->getWorkspaceByID(validWSes[currentItem])->m_szName;
} else {
if (in[0] == '+' || in[0] == '-') {
if (g_pCompositor->m_pLastMonitor) {
const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in, g_pCompositor->m_pLastMonitor->activeWorkspaceID());
if (!PLUSMINUSRESULT.has_value())
return {WORKSPACE_INVALID};
result.id = std::max((int)PLUSMINUSRESULT.value(), 1);
} else {
Debug::log(ERR, "Relative workspace on no mon!");
return {WORKSPACE_INVALID};
}
} else if (isNumber(in))
result.id = std::max(std::stoi(in), 1);
else {
// maybe name
const auto PWORKSPACE = g_pCompositor->getWorkspaceByName(in);
if (PWORKSPACE)
result.id = PWORKSPACE->m_iID;
}
result.name = std::to_string(result.id);
}
}
return result;
}
std::optional<std::string> cleanCmdForWorkspace(const std::string& inWorkspaceName, std::string dirtyCmd) {
std::string cmd = trim(dirtyCmd);
if (!cmd.empty()) {
std::string rules;
const std::string workspaceRule = "workspace " + inWorkspaceName;
if (cmd[0] == '[') {
const auto closingBracketIdx = cmd.find_last_of(']');
auto tmpRules = cmd.substr(1, closingBracketIdx - 1);
cmd = cmd.substr(closingBracketIdx + 1);
auto rulesList = CVarList(tmpRules, 0, ';');
bool hadWorkspaceRule = false;
rulesList.map([&](std::string& rule) {
if (rule.find("workspace") == 0) {
rule = workspaceRule;
hadWorkspaceRule = true;
}
});
if (!hadWorkspaceRule)
rulesList.append(workspaceRule);
rules = "[" + rulesList.join(";") + "]";
} else {
rules = "[" + workspaceRule + "]";
}
return std::optional<std::string>(rules + " " + cmd);
}
return std::nullopt;
}
float vecToRectDistanceSquared(const Vector2D& vec, const Vector2D& p1, const Vector2D& p2) {
const float DX = std::max({0.0, p1.x - vec.x, vec.x - p2.x});
const float DY = std::max({0.0, p1.y - vec.y, vec.y - p2.y});
return DX * DX + DY * DY;
}
// Execute a shell command and get the output
std::string execAndGet(const char* cmd) {
CProcess proc("/bin/sh", {"-c", cmd});
if (!proc.runSync())
return "error";
return proc.stdOut();
}
void logSystemInfo() {
struct utsname unameInfo;
uname(&unameInfo);
Debug::log(LOG, "System name: {}", std::string{unameInfo.sysname});
Debug::log(LOG, "Node name: {}", std::string{unameInfo.nodename});
Debug::log(LOG, "Release: {}", std::string{unameInfo.release});
Debug::log(LOG, "Version: {}", std::string{unameInfo.version});
Debug::log(NONE, "\n");
#if defined(__DragonFly__) || defined(__FreeBSD__)
const std::string GPUINFO = execAndGet("pciconf -lv | grep -F -A4 vga");
#elif defined(__arm__) || defined(__aarch64__)
std::string GPUINFO;
const std::filesystem::path dev_tree = "/proc/device-tree";
try {
if (std::filesystem::exists(dev_tree) && std::filesystem::is_directory(dev_tree)) {
std::for_each(std::filesystem::directory_iterator(dev_tree), std::filesystem::directory_iterator{}, [&](const std::filesystem::directory_entry& entry) {
if (std::filesystem::is_directory(entry) && entry.path().filename().string().starts_with("soc")) {
std::for_each(std::filesystem::directory_iterator(entry.path()), std::filesystem::directory_iterator{}, [&](const std::filesystem::directory_entry& sub_entry) {
if (std::filesystem::is_directory(sub_entry) && sub_entry.path().filename().string().starts_with("gpu")) {
std::filesystem::path file_path = sub_entry.path() / "compatible";
std::ifstream file(file_path);
if (file)
GPUINFO.append(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
}
});
}
});
}
} catch (...) { GPUINFO = "error"; }
#else
const std::string GPUINFO = execAndGet("lspci -vnn | grep -E '(VGA|Display|3D)'");
#endif
Debug::log(LOG, "GPU information:\n{}\n", GPUINFO);
if (GPUINFO.contains("NVIDIA")) {
Debug::log(WARN, "Warning: you're using an NVIDIA GPU. Make sure you follow the instructions on the wiki if anything is amiss.\n");
}
// log etc
Debug::log(LOG, "os-release:");
Debug::log(NONE, "{}", NFsUtils::readFileAsString("/etc/os-release").value_or("error"));
}
int64_t getPPIDof(int64_t pid) {
#if defined(KERN_PROC_PID)
int mib[] = {
CTL_KERN, KERN_PROC, KERN_PROC_PID, (int)pid,
#if defined(__NetBSD__) || defined(__OpenBSD__)
sizeof(KINFO_PROC), 1,
#endif
};
u_int miblen = sizeof(mib) / sizeof(mib[0]);
KINFO_PROC kp;
size_t sz = sizeof(KINFO_PROC);
if (sysctl(mib, miblen, &kp, &sz, NULL, 0) != -1)
return KP_PPID(kp);
return 0;
#else
std::string dir = "/proc/" + std::to_string(pid) + "/status";
FILE* infile;
infile = fopen(dir.c_str(), "r");
if (!infile)
return 0;
char* line = nullptr;
size_t len = 0;
ssize_t len2 = 0;
std::string pidstr;
while ((len2 = getline(&line, &len, infile)) != -1) {
if (strstr(line, "PPid:")) {
pidstr = std::string(line, len2);
const auto tabpos = pidstr.find_last_of('\t');
if (tabpos != std::string::npos)
pidstr = pidstr.substr(tabpos);
break;
}
}
fclose(infile);
if (line)
free(line);
try {
return std::stoll(pidstr);
} catch (std::exception& e) { return 0; }
#endif
}
std::expected<int64_t, std::string> configStringToInt(const std::string& VALUE) {
auto parseHex = [](const std::string& value) -> std::expected<int64_t, std::string> {
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::count(VALUEWITHOUTFUNC.begin(), VALUEWITHOUTFUNC.end(), ',') == 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 * (Hyprlang::INT)0x1000000 + *r * (Hyprlang::INT)0x10000 + *g * (Hyprlang::INT)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::count(VALUEWITHOUTFUNC.begin(), VALUEWITHOUTFUNC.end(), ',') == 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 (Hyprlang::INT)0xFF000000 + *r * (Hyprlang::INT)0x10000 + *g * (Hyprlang::INT)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((double)x, (double)y);
}
double normalizeAngleRad(double ang) {
if (ang > M_PI * 2) {
while (ang > M_PI * 2)
ang -= M_PI * 2;
return ang;
}
if (ang < 0.0) {
while (ang < 0.0)
ang += M_PI * 2;
return ang;
}
return ang;
}
std::vector<SCallstackFrameInfo> getBacktrace() {
std::vector<SCallstackFrameInfo> callstack;
#ifdef HAS_EXECINFO
void* bt[1024];
int btSize;
char** btSymbols;
btSize = backtrace(bt, 1024);
btSymbols = backtrace_symbols(bt, btSize);
for (auto i = 0; i < btSize; ++i) {
callstack.emplace_back(SCallstackFrameInfo{bt[i], std::string{btSymbols[i]}});
}
#else
callstack.emplace_back(SCallstackFrameInfo{nullptr, "configuration does not support execinfo.h"});
#endif
return callstack;
}
void throwError(const std::string& err) {
Debug::log(CRIT, "Critical error thrown: {}", err);
throw std::runtime_error(err);
}
bool envEnabled(const std::string& env) {
const auto ENV = getenv(env.c_str());
if (!ENV)
return false;
return std::string(ENV) == "1";
}
std::pair<CFileDescriptor, std::string> openExclusiveShm() {
// Only absolute paths can be shared across different shm_open() calls
std::string name = "/" + g_pTokenManager->getRandomUUID();
for (size_t i = 0; i < 69; ++i) {
CFileDescriptor fd{shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600)};
if (fd.isValid())
return {std::move(fd), name};
}
return {{}, ""};
}
CFileDescriptor allocateSHMFile(size_t len) {
auto [fd, name] = openExclusiveShm();
if (!fd.isValid())
return {};
shm_unlink(name.c_str());
int ret;
do {
ret = ftruncate(fd.get(), len);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
return {};
}
return std::move(fd);
}
bool allocateSHMFilePair(size_t size, CFileDescriptor& rw_fd_ptr, CFileDescriptor& ro_fd_ptr) {
auto [fd, name] = openExclusiveShm();
if (!fd.isValid()) {
return false;
}
// CLOEXEC is guaranteed to be set by shm_open
CFileDescriptor ro_fd{shm_open(name.c_str(), O_RDONLY, 0)};
if (!ro_fd.isValid()) {
shm_unlink(name.c_str());
return false;
}
shm_unlink(name.c_str());
// Make sure the file cannot be re-opened in read-write mode (e.g. via
// "/proc/self/fd/" on Linux)
if (fchmod(fd.get(), 0) != 0) {
return false;
}
int ret;
do {
ret = ftruncate(fd.get(), size);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
return false;
}
rw_fd_ptr = std::move(fd);
ro_fd_ptr = std::move(ro_fd);
return true;
}
float stringToPercentage(const std::string& VALUE, const float REL) {
if (VALUE.ends_with('%'))
return (std::stof(VALUE.substr(0, VALUE.length() - 1)) * REL) / 100.f;
else
return std::stof(VALUE);
}