#include "MiscFunctions.hpp" #include "../defines.hpp" #include #include "../Compositor.hpp" #include "../managers/TokenManager.hpp" #include #include #include #include #include #include #include #include #include #include #ifdef HAS_EXECINFO #include #endif #include using namespace Hyprutils::String; #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 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; } void addWLSignal(wl_signal* pSignal, wl_listener* pListener, void* pOwner, const std::string& ownerString) { ASSERT(pSignal); ASSERT(pListener); wl_signal_add(pSignal, pListener); Debug::log(LOG, "Registered signal for owner {:x}: {:x} -> {:x} (owner: {})", (uintptr_t)pOwner, (uintptr_t)pSignal, (uintptr_t)pListener, ownerString); } void removeWLSignal(wl_listener* pListener) { wl_list_remove(&pListener->link); wl_list_init(&pListener->link); Debug::log(LOG, "Removed listener {:x}", (uintptr_t)pListener); } void handleNoop(struct wl_listener* listener, void* data) { // Do nothing } 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_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 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 || g_pCompositor->getWindowsOnWorkspace(id) == 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->m_sPrevWorkspace.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 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 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(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_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 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 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) { std::array buffer; std::string result; using PcloseType = int (*)(FILE*); const std::unique_ptr pipe(popen(cmd, "r"), static_cast(pclose)); if (!pipe) { Debug::log(ERR, "execAndGet: failed in pipe"); return ""; } while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { result += buffer.data(); } return result; } 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 | fgrep -A4 vga"); #elif defined(__arm__) || defined(__aarch64__) const std::string GPUINFO = execAndGet("cat /proc/device-tree/soc*/gpu*/compatible"); #else const std::string GPUINFO = execAndGet("lspci -vnn | grep VGA"); #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, "{}", execAndGet("cat /etc/os-release")); } 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 } int64_t configStringToInt(const std::string& VALUE) { if (VALUE.starts_with("0x")) { // Values with 0x are hex const auto VALUEWITHOUTHEX = VALUE.substr(2); return stol(VALUEWITHOUTHEX, nullptr, 16); } else if (VALUE.starts_with("rgba(") && VALUE.ends_with(')')) { const auto VALUEWITHOUTFUNC = VALUE.substr(5, VALUE.length() - 6); if (trim(VALUEWITHOUTFUNC).length() != 8) { Debug::log(WARN, "invalid length {} for rgba", VALUEWITHOUTFUNC.length()); throw std::invalid_argument("rgba() expects length of 8 characters (4 bytes)"); } const auto RGBA = std::stol(VALUEWITHOUTFUNC, nullptr, 16); // now we need to RGBA -> ARGB. The config holds ARGB only. return (RGBA >> 8) + 0x1000000 * (RGBA & 0xFF); } else if (VALUE.starts_with("rgb(") && VALUE.ends_with(')')) { const auto VALUEWITHOUTFUNC = VALUE.substr(4, VALUE.length() - 5); if (trim(VALUEWITHOUTFUNC).length() != 6) { Debug::log(WARN, "invalid length {} for rgb", VALUEWITHOUTFUNC.length()); throw std::invalid_argument("rgb() expects length of 6 characters (3 bytes)"); } const auto RGB = std::stol(VALUEWITHOUTFUNC, nullptr, 16); return RGB + 0xFF000000; // 0xFF for opaque } 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)) return 0; return std::stoll(VALUE); } 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) { int fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) return {fd, name}; } return {-1, ""}; } int allocateSHMFile(size_t len) { auto [fd, name] = openExclusiveShm(); if (fd < 0) return -1; shm_unlink(name.c_str()); int ret; do { ret = ftruncate(fd, len); } while (ret < 0 && errno == EINTR); if (ret < 0) { close(fd); return -1; } return fd; } bool allocateSHMFilePair(size_t size, int* rw_fd_ptr, int* ro_fd_ptr) { auto [fd, name] = openExclusiveShm(); if (fd < 0) { return false; } // CLOEXEC is guaranteed to be set by shm_open int ro_fd = shm_open(name.c_str(), O_RDONLY, 0); if (ro_fd < 0) { shm_unlink(name.c_str()); close(fd); 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, 0) != 0) { close(fd); close(ro_fd); return false; } int ret; do { ret = ftruncate(fd, size); } while (ret < 0 && errno == EINTR); if (ret < 0) { close(fd); close(ro_fd); return false; } *rw_fd_ptr = fd; *ro_fd_ptr = ro_fd; return true; }