mirror of
https://github.com/hyprwm/Hyprland.git
synced 2025-05-19 00:20:23 -07:00
878 lines
32 KiB
C++
878 lines
32 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
|
|
|
|
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_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<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_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<WORKSPACEID> 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<WORKSPACEID> 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<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_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<WORKSPACEID> 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<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);
|
|
}
|
|
|
|
// 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<char>(ifs)), (std::istreambuf_iterator<char>()));
|
|
|
|
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;
|
|
}
|