mirror of
https://github.com/hyprwm/Hyprland.git
synced 2025-05-19 08:30:22 -07:00
* 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.
911 lines
31 KiB
C++
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);
|
|
}
|