#include "MiscFunctions.hpp" #include "../defines.hpp" #include #include "../Compositor.hpp" #include "../managers/TokenManager.hpp" #include "Monitor.hpp" #include "../config/ConfigManager.hpp" #include "fs/FsUtils.hpp" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAS_EXECINFO #include #endif #include #include using namespace Hyprutils::String; using namespace Hyprutils::OS; #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) #include #if defined(__DragonFly__) #include // struct kinfo_proc #elif defined(__FreeBSD__) #include // 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 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(c); } else { oss << c; } } } return oss.str(); } std::optional 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_id : 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_id; } 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_lastMonitor) { Debug::log(ERR, "Empty monitor workspace on monitor null!"); return {WORKSPACE_INVALID}; } std::set 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_lastMonitor->ID)) invalidWSes.insert(rule.workspaceId); } } WORKSPACEID id = next ? g_pCompositor->m_lastMonitor->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_lastMonitor) return {WORKSPACE_INVALID}; const auto PWORKSPACE = g_pCompositor->m_lastMonitor->activeWorkspace; if (!valid(PWORKSPACE)) return {WORKSPACE_INVALID}; const auto PREVWORKSPACEIDNAME = PWORKSPACE->getPrevWorkspaceIDName(); if (PREVWORKSPACEIDNAME.id == -1) return {WORKSPACE_INVALID}; const auto PLASTWORKSPACE = g_pCompositor->getWorkspaceByID(PREVWORKSPACEIDNAME.id); if (!PLASTWORKSPACE) { Debug::log(LOG, "previous workspace {} doesn't exist yet", PREVWORKSPACEIDNAME.id); return {PREVWORKSPACEIDNAME.id, PREVWORKSPACEIDNAME.name}; } return {PLASTWORKSPACE->m_id, PLASTWORKSPACE->m_name}; } else if (in == "next") { if (!g_pCompositor->m_lastMonitor || !g_pCompositor->m_lastMonitor->activeWorkspace) { Debug::log(ERR, "no active monitor or workspace for 'next'"); return {WORKSPACE_INVALID}; } auto PCURRENTWORKSPACE = g_pCompositor->m_lastMonitor->activeWorkspace; WORKSPACEID nextId = PCURRENTWORKSPACE->m_id + 1; if (nextId <= 0) return {WORKSPACE_INVALID}; result.id = nextId; result.name = std::to_string(nextId); return result; } else { if (in[0] == 'r' && (in[1] == '-' || in[1] == '+' || in[1] == '~') && isNumber(in.substr(2))) { bool absolute = in[1] == '~'; if (!g_pCompositor->m_lastMonitor) { 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 invalidWSes; // Collect all the workspaces we can't jump to. for (auto const& ws : g_pCompositor->m_workspaces) { if (ws->m_isSpecialWorkspace || (ws->m_monitor != g_pCompositor->m_lastMonitor)) { // Can't jump to this workspace invalidWSes.insert(ws->m_id); } } for (auto const& rule : g_pConfigManager->getAllWorkspaceRules()) { const auto PMONITOR = g_pCompositor->getMonitorFromName(rule.monitor); if (!PMONITOR || PMONITOR->ID == g_pCompositor->m_lastMonitor->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 namedWSes; for (auto const& ws : g_pCompositor->m_workspaces) { if (ws->m_isSpecialWorkspace || (ws->m_monitor != g_pCompositor->m_lastMonitor) || ws->m_id >= 0) continue; namedWSes.push_back(ws->m_id); } 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_lastMonitor->activeWorkspace ? g_pCompositor->m_lastMonitor->activeWorkspace->m_id : 1; WORKSPACEID predictedWSID = activeWSID + remains; int remainingWSes = 0; char walkDir = in[1]; // sanitize. 0 means invalid oob in - predictedWSID = std::max(predictedWSID, static_cast(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(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(0), namedWSes.size() - static_cast(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_name; 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_lastMonitor) { 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 validWSes; for (auto const& ws : g_pCompositor->m_workspaces) { if (ws->m_isSpecialWorkspace || (ws->m_monitor != g_pCompositor->m_lastMonitor && !onAllMonitors)) continue; validWSes.push_back(ws->m_id); } 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_lastMonitor->activeWorkspace ? g_pCompositor->m_lastMonitor->activeWorkspace->m_id : 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_name; } else { if (in[0] == '+' || in[0] == '-') { if (g_pCompositor->m_lastMonitor) { const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in, g_pCompositor->m_lastMonitor->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_id; } result.name = std::to_string(result.id); } } return result; } std::optional 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(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(file), std::istreambuf_iterator()); } }); } }); } } 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 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::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 getBacktrace() { std::vector 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 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); } // Checks if Nvidia driver major version is at least given version. // Useful for explicit_sync_kms and ctm_animation as they only work // past certain driver versions. bool isNvidiaDriverVersionAtLeast(int threshold) { static int driverMajor = 0; static bool once = true; if (once) { once = false; std::error_code ec; if (std::filesystem::exists("/sys/module/nvidia_drm/version", ec) && !ec) { std::ifstream ifs("/sys/module/nvidia_drm/version"); if (ifs.good()) { try { std::string driverInfo((std::istreambuf_iterator(ifs)), (std::istreambuf_iterator())); size_t firstDot = driverInfo.find('.'); if (firstDot != std::string::npos) driverMajor = std::stoi(driverInfo.substr(0, firstDot)); Debug::log(LOG, "Parsed NVIDIA major version: {}", driverMajor); } catch (std::exception& e) { driverMajor = 0; // Default to 0 if parsing fails } ifs.close(); } } } return driverMajor >= threshold; }