ObsidianDragon - DragonX ImGui Wallet
Full-node GUI wallet for DragonX cryptocurrency. Built with Dear ImGui, SDL3, and OpenGL3/DX11. Features: - Send/receive shielded and transparent transactions - Autoshield with merged transaction display - Built-in CPU mining (xmrig) - Peer management and network monitoring - Wallet encryption with PIN lock - QR code generation for receive addresses - Transaction history with pagination - Console for direct RPC commands - Cross-platform (Linux, Windows)
This commit is contained in:
600
src/util/platform.cpp
Normal file
600
src/util/platform.cpp
Normal file
@@ -0,0 +1,600 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
|
||||
// Embedded ui.toml (generated at build time)
|
||||
#if __has_include("ui_toml_embedded.h")
|
||||
#include "ui_toml_embedded.h"
|
||||
#define HAS_EMBEDDED_UI_TOML 1
|
||||
#else
|
||||
#define HAS_EMBEDDED_UI_TOML 0
|
||||
#endif
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <shlobj.h>
|
||||
#include <psapi.h>
|
||||
#include <tlhelp32.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <dirent.h>
|
||||
#ifdef __APPLE__
|
||||
#include <mach-o/dyld.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <mach/mach.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "../util/logger.h"
|
||||
|
||||
namespace dragonx {
|
||||
namespace util {
|
||||
|
||||
bool Platform::openUrl(const std::string& url)
|
||||
{
|
||||
if (url.empty()) return false;
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows: Use ShellExecute
|
||||
HINSTANCE result = ShellExecuteA(nullptr, "open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
return (reinterpret_cast<intptr_t>(result) > 32);
|
||||
#elif defined(__APPLE__)
|
||||
// macOS: Use 'open' command
|
||||
std::string cmd = "open \"" + url + "\" &";
|
||||
return (system(cmd.c_str()) == 0);
|
||||
#else
|
||||
// Linux: Use xdg-open
|
||||
std::string cmd = "xdg-open \"" + url + "\" >/dev/null 2>&1 &";
|
||||
return (system(cmd.c_str()) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Platform::openFolder(const std::string& path, bool createIfMissing)
|
||||
{
|
||||
if (path.empty()) return false;
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if (createIfMissing) {
|
||||
#ifdef _WIN32
|
||||
// Windows: Create directory recursively
|
||||
std::string cmd = "mkdir \"" + path + "\" 2>nul";
|
||||
(void)system(cmd.c_str()); // Ignore return value - dir may already exist
|
||||
#else
|
||||
// Linux/macOS: Create directory with parents
|
||||
std::string cmd = "mkdir -p \"" + path + "\" 2>/dev/null";
|
||||
(void)system(cmd.c_str()); // Ignore return value - dir may already exist
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows: Use explorer.exe to open folder
|
||||
HINSTANCE result = ShellExecuteA(nullptr, "explore", path.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
return (reinterpret_cast<intptr_t>(result) > 32);
|
||||
#elif defined(__APPLE__)
|
||||
// macOS: Use 'open' command (works for folders too)
|
||||
std::string cmd = "open \"" + path + "\" &";
|
||||
return (system(cmd.c_str()) == 0);
|
||||
#else
|
||||
// Linux: Use xdg-open (works for folders too)
|
||||
std::string cmd = "xdg-open \"" + path + "\" >/dev/null 2>&1 &";
|
||||
return (system(cmd.c_str()) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t Platform::getFileSize(const std::string& path)
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) == 0) {
|
||||
return static_cast<uint64_t>(st.st_size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string Platform::formatFileSize(uint64_t bytes)
|
||||
{
|
||||
char buf[64];
|
||||
|
||||
if (bytes >= 1024ULL * 1024ULL * 1024ULL) {
|
||||
snprintf(buf, sizeof(buf), "%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0));
|
||||
} else if (bytes >= 1024ULL * 1024ULL) {
|
||||
snprintf(buf, sizeof(buf), "%.2f MB", bytes / (1024.0 * 1024.0));
|
||||
} else if (bytes >= 1024ULL) {
|
||||
snprintf(buf, sizeof(buf), "%.2f KB", bytes / 1024.0);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "%llu bytes", static_cast<unsigned long long>(bytes));
|
||||
}
|
||||
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string Platform::getHomeDir()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
char path[MAX_PATH];
|
||||
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_PROFILE, nullptr, 0, path))) {
|
||||
return std::string(path);
|
||||
}
|
||||
// Fallback
|
||||
const char* home = getenv("USERPROFILE");
|
||||
return home ? home : "C:\\";
|
||||
#else
|
||||
const char* home = getenv("HOME");
|
||||
if (home) return home;
|
||||
|
||||
// Fallback to passwd entry
|
||||
struct passwd* pw = getpwuid(getuid());
|
||||
if (pw && pw->pw_dir) return pw->pw_dir;
|
||||
|
||||
return "/tmp";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string Platform::getDragonXDataDir()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
char path[MAX_PATH];
|
||||
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path))) {
|
||||
return std::string(path) + "\\Hush\\DRAGONX\\";
|
||||
}
|
||||
return getHomeDir() + "\\AppData\\Roaming\\Hush\\DRAGONX\\";
|
||||
#else
|
||||
return getHomeDir() + "/.hush/DRAGONX/";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string Platform::getDataDir()
|
||||
{
|
||||
return getDragonXDataDir();
|
||||
}
|
||||
|
||||
std::string Platform::getConfigDir()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
char path[MAX_PATH];
|
||||
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path))) {
|
||||
return std::string(path) + "\\ObsidianDragon\\";
|
||||
}
|
||||
return getHomeDir() + "\\AppData\\Roaming\\ObsidianDragon\\";
|
||||
#else
|
||||
return getHomeDir() + "/.config/ObsidianDragon/";
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Platform::deleteFile(const std::string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return DeleteFileA(path.c_str()) != 0 || GetLastError() == ERROR_FILE_NOT_FOUND;
|
||||
#else
|
||||
return (remove(path.c_str()) == 0 || errno == ENOENT);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string Platform::getExecutableDirectory()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
char path[MAX_PATH];
|
||||
DWORD len = GetModuleFileNameA(nullptr, path, MAX_PATH);
|
||||
if (len > 0 && len < MAX_PATH) {
|
||||
std::string fullPath(path);
|
||||
size_t pos = fullPath.find_last_of("\\/");
|
||||
if (pos != std::string::npos) {
|
||||
return fullPath.substr(0, pos);
|
||||
}
|
||||
}
|
||||
return ".";
|
||||
#elif defined(__APPLE__)
|
||||
char path[1024];
|
||||
uint32_t size = sizeof(path);
|
||||
if (_NSGetExecutablePath(path, &size) == 0) {
|
||||
char* resolved = realpath(path, nullptr);
|
||||
if (resolved) {
|
||||
std::string fullPath(resolved);
|
||||
free(resolved);
|
||||
size_t pos = fullPath.find_last_of('/');
|
||||
if (pos != std::string::npos) {
|
||||
return fullPath.substr(0, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ".";
|
||||
#else
|
||||
// Linux: read /proc/self/exe
|
||||
char path[1024];
|
||||
ssize_t len = readlink("/proc/self/exe", path, sizeof(path) - 1);
|
||||
if (len > 0) {
|
||||
path[len] = '\0';
|
||||
std::string fullPath(path);
|
||||
size_t pos = fullPath.find_last_of('/');
|
||||
if (pos != std::string::npos) {
|
||||
return fullPath.substr(0, pos);
|
||||
}
|
||||
}
|
||||
return ".";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string getExecutableDirectory()
|
||||
{
|
||||
return Platform::getExecutableDirectory();
|
||||
}
|
||||
|
||||
std::string Platform::getObsidianDragonDir()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const char* appdata = std::getenv("APPDATA");
|
||||
if (appdata) {
|
||||
return (std::filesystem::path(appdata) / "ObsidianDragon").string();
|
||||
}
|
||||
return (std::filesystem::path(getHomeDir()) / "AppData" / "Roaming" / "ObsidianDragon").string();
|
||||
#elif defined(__APPLE__)
|
||||
return (std::filesystem::path(getHomeDir()) / "Library" / "Application Support" / "ObsidianDragon").string();
|
||||
#else
|
||||
const char* xdg_config = std::getenv("XDG_CONFIG_HOME");
|
||||
if (xdg_config) {
|
||||
return (std::filesystem::path(xdg_config) / "ObsidianDragon").string();
|
||||
}
|
||||
return (std::filesystem::path(getHomeDir()) / ".config" / "ObsidianDragon").string();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Platform::ensureObsidianDragonSetup()
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
fs::path base = getObsidianDragonDir();
|
||||
fs::path themes_dir = base / "themes";
|
||||
fs::path example_dir = base / "example" / "example_theme";
|
||||
fs::path example_img = example_dir / "img";
|
||||
|
||||
// Create directory structure
|
||||
std::error_code ec;
|
||||
fs::create_directories(themes_dir, ec);
|
||||
fs::create_directories(example_img, ec);
|
||||
if (ec) {
|
||||
DEBUG_LOGF("Warning: Could not create ObsidianDragon directories: %s\n", ec.message().c_str());
|
||||
return;
|
||||
}
|
||||
DEBUG_LOGF("ObsidianDragon: Config directory: %s\n", base.string().c_str());
|
||||
|
||||
// Write example theme.toml — a full copy of ui.toml so users can see
|
||||
// and modify the complete layout+theme structure. UISchema can load
|
||||
// themes from TOML format.
|
||||
// Regenerate whenever the binary is newer than the example file, so
|
||||
// layout changes in ui.toml are always reflected.
|
||||
fs::path example_toml = example_dir / "theme.toml";
|
||||
bool needsWrite = !fs::exists(example_toml);
|
||||
if (!needsWrite) {
|
||||
// Regenerate if the running binary is newer than the example file
|
||||
fs::path exePath = fs::path(getExecutableDirectory()) /
|
||||
#ifdef _WIN32
|
||||
"ObsidianDragon.exe";
|
||||
#else
|
||||
"ObsidianDragon";
|
||||
#endif
|
||||
std::error_code tec;
|
||||
auto exeTime = fs::last_write_time(exePath, tec);
|
||||
auto fileTime = fs::last_write_time(example_toml, tec);
|
||||
if (!tec && exeTime > fileTime)
|
||||
needsWrite = true;
|
||||
}
|
||||
if (needsWrite) {
|
||||
std::ofstream f(example_toml);
|
||||
if (f.is_open()) {
|
||||
// Use the build-time embedded copy first; fall back to the
|
||||
// bundled file on disk.
|
||||
std::string content;
|
||||
#if HAS_EMBEDDED_UI_TOML
|
||||
content.assign(reinterpret_cast<const char*>(embedded::ui_toml_data),
|
||||
embedded::ui_toml_size);
|
||||
#else
|
||||
{
|
||||
fs::path bundled = fs::path(getExecutableDirectory()) / "res" / "themes" / "ui.toml";
|
||||
std::ifstream src(bundled);
|
||||
if (src.is_open()) {
|
||||
std::ostringstream ss;
|
||||
ss << src.rdbuf();
|
||||
content = ss.str();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!content.empty()) {
|
||||
// Patch the theme metadata so the example is clearly
|
||||
// distinguishable from the bundled default.
|
||||
// In TOML: name = "DragonX" → name = "Example Theme"
|
||||
auto replaceFirst = [&](const std::string& key,
|
||||
const std::string& oldVal,
|
||||
const std::string& newVal) {
|
||||
// Find 'key = "oldVal"' pattern and replace oldVal
|
||||
std::string needle = key + " = ";
|
||||
auto pos = content.find(needle);
|
||||
if (pos == std::string::npos) return;
|
||||
auto vpos = content.find(oldVal, pos);
|
||||
if (vpos != std::string::npos && vpos - pos < 80)
|
||||
content.replace(vpos, oldVal.size(), newVal);
|
||||
};
|
||||
replaceFirst("name", "\"DragonX\"", "\"Example Theme\"");
|
||||
replaceFirst("author", "\"The Hush Developers\"", "\"Your Name\"");
|
||||
|
||||
// Prepend comment block (TOML uses # for comments natively)
|
||||
std::string header =
|
||||
"# === DragonX Wallet Theme File ===\n"
|
||||
"# Copy this entire folder into the 'themes' folder to use it.\n"
|
||||
"# The folder name becomes the theme ID.\n"
|
||||
"#\n"
|
||||
"# === Images ===\n"
|
||||
"# Place image files in the 'img' subfolder.\n"
|
||||
"# Specify custom filenames below, or use the defaults:\n"
|
||||
"# backgrounds/gradient/dark_gradient.png - background gradient overlay\n"
|
||||
"# logos/logo_ObsidianDragon_dark.png - sidebar logo (128x128 recommended)\n"
|
||||
"#\n"
|
||||
"# === Colors ===\n"
|
||||
"# Colors use CSS-style formats: #RRGGBB, #RRGGBBAA, or rgba(r,g,b,a)\n"
|
||||
"# Palette variables can be referenced elsewhere with var(--name)\n\n";
|
||||
content = header + content;
|
||||
} else {
|
||||
DEBUG_LOGF("ObsidianDragon: Warning — ui.toml not available, skipping example theme\n");
|
||||
}
|
||||
|
||||
f << content;
|
||||
f.close();
|
||||
DEBUG_LOGF("ObsidianDragon: Created example theme: %s\n", example_toml.string().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Copy bundled images into example/example_theme/img/ for reference
|
||||
fs::path exe_dir = getExecutableDirectory();
|
||||
fs::path res_img = exe_dir / "res" / "img";
|
||||
|
||||
auto copyIfMissing = [&](const char* srcRelPath, const char* dstFilename) {
|
||||
fs::path src = res_img / srcRelPath;
|
||||
fs::path dst = example_img / dstFilename;
|
||||
if (fs::exists(src) && !fs::exists(dst)) {
|
||||
std::error_code cpec;
|
||||
fs::copy_file(src, dst, cpec);
|
||||
if (!cpec) {
|
||||
DEBUG_LOGF("ObsidianDragon: Copied %s to example theme\n", dstFilename);
|
||||
}
|
||||
}
|
||||
};
|
||||
copyIfMissing("backgrounds/gradient/dark_gradient.png", "dark_gradient.png");
|
||||
copyIfMissing("logos/logo_ObsidianDragon_dark.png", "logo_ObsidianDragon_dark.png");
|
||||
copyIfMissing("logos/logo_ObsidianDragon_light.png", "logo_ObsidianDragon_light.png");
|
||||
}
|
||||
|
||||
double Platform::getTotalSystemRAM_MB()
|
||||
{
|
||||
static double cached = 0.0;
|
||||
if (cached > 0.0) return cached;
|
||||
|
||||
#ifdef _WIN32
|
||||
MEMORYSTATUSEX memInfo;
|
||||
memInfo.dwLength = sizeof(memInfo);
|
||||
if (GlobalMemoryStatusEx(&memInfo)) {
|
||||
cached = (double)memInfo.ullTotalPhys / (1024.0 * 1024.0);
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
int64_t memSize = 0;
|
||||
size_t len = sizeof(memSize);
|
||||
if (sysctlbyname("hw.memsize", &memSize, &len, nullptr, 0) == 0) {
|
||||
cached = (double)memSize / (1024.0 * 1024.0);
|
||||
}
|
||||
#else
|
||||
// Linux: use sysconf
|
||||
long pages = sysconf(_SC_PHYS_PAGES);
|
||||
long pageSize = sysconf(_SC_PAGE_SIZE);
|
||||
if (pages > 0 && pageSize > 0) {
|
||||
cached = (double)pages * (double)pageSize / (1024.0 * 1024.0);
|
||||
}
|
||||
#endif
|
||||
return cached;
|
||||
}
|
||||
|
||||
double Platform::getUsedSystemRAM_MB()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
MEMORYSTATUSEX memInfo;
|
||||
memInfo.dwLength = sizeof(memInfo);
|
||||
if (GlobalMemoryStatusEx(&memInfo)) {
|
||||
double total = (double)memInfo.ullTotalPhys / (1024.0 * 1024.0);
|
||||
double avail = (double)memInfo.ullAvailPhys / (1024.0 * 1024.0);
|
||||
return total - avail;
|
||||
}
|
||||
return 0.0;
|
||||
#elif defined(__APPLE__)
|
||||
// macOS: vm_statistics64 for free/active/inactive/wired pages
|
||||
vm_size_t pageSize = 0;
|
||||
host_page_size(mach_host_self(), &pageSize);
|
||||
vm_statistics64_data_t vmStats;
|
||||
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
|
||||
if (host_statistics64(mach_host_self(), HOST_VM_INFO64,
|
||||
(host_info64_t)&vmStats, &count) == KERN_SUCCESS) {
|
||||
// "used" = total - free - inactive (matches Activity Monitor "Memory Used")
|
||||
double total = getTotalSystemRAM_MB();
|
||||
double freeMB = (double)(vmStats.free_count + vmStats.inactive_count) * (double)pageSize / (1024.0 * 1024.0);
|
||||
return total - freeMB;
|
||||
}
|
||||
return 0.0;
|
||||
#else
|
||||
// Linux: parse /proc/meminfo for MemAvailable
|
||||
FILE* f = fopen("/proc/meminfo", "r");
|
||||
if (!f) return 0.0;
|
||||
double totalMB = 0.0, availMB = 0.0;
|
||||
char line[256];
|
||||
int found = 0;
|
||||
while (fgets(line, sizeof(line), f) && found < 2) {
|
||||
long val = 0;
|
||||
if (strncmp(line, "MemTotal:", 9) == 0) {
|
||||
if (sscanf(line + 9, "%ld", &val) == 1) { totalMB = (double)val / 1024.0; found++; }
|
||||
} else if (strncmp(line, "MemAvailable:", 13) == 0) {
|
||||
if (sscanf(line + 13, "%ld", &val) == 1) { availMB = (double)val / 1024.0; found++; }
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
if (found == 2) return totalMB - availMB;
|
||||
return 0.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
double Platform::getSelfMemoryUsageMB()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
PROCESS_MEMORY_COUNTERS pmc;
|
||||
if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
|
||||
return (double)pmc.WorkingSetSize / (1024.0 * 1024.0);
|
||||
}
|
||||
return 0.0;
|
||||
#elif defined(__APPLE__)
|
||||
struct mach_task_basic_info info;
|
||||
mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
|
||||
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO,
|
||||
(task_info_t)&info, &count) == KERN_SUCCESS) {
|
||||
return (double)info.resident_size / (1024.0 * 1024.0);
|
||||
}
|
||||
return 0.0;
|
||||
#else
|
||||
// Linux: read /proc/self/status for VmRSS
|
||||
FILE* f = fopen("/proc/self/status", "r");
|
||||
if (!f) return 0.0;
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
if (strncmp(line, "VmRSS:", 6) == 0) {
|
||||
long val = 0;
|
||||
if (sscanf(line + 6, "%ld", &val) == 1) {
|
||||
fclose(f);
|
||||
return (double)val / 1024.0; // kB -> MB
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
return 0.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
double Platform::getDaemonMemoryUsageMB()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Windows: use CreateToolhelp32Snapshot to find hushd.exe processes
|
||||
// and query their working set size.
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snap == INVALID_HANDLE_VALUE) return 0.0;
|
||||
|
||||
PROCESSENTRY32 entry;
|
||||
entry.dwSize = sizeof(entry);
|
||||
|
||||
double totalMB = 0.0;
|
||||
if (Process32First(snap, &entry)) {
|
||||
do {
|
||||
if (_stricmp(entry.szExeFile, "hushd.exe") == 0) {
|
||||
// GetProcessMemoryInfo requires PROCESS_QUERY_INFORMATION + PROCESS_VM_READ.
|
||||
// Try full access first; fall back to limited for elevated processes.
|
||||
HANDLE hProc = OpenProcess(
|
||||
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
|
||||
FALSE, entry.th32ProcessID);
|
||||
if (!hProc) {
|
||||
hProc = OpenProcess(
|
||||
PROCESS_QUERY_LIMITED_INFORMATION,
|
||||
FALSE, entry.th32ProcessID);
|
||||
}
|
||||
if (hProc) {
|
||||
PROCESS_MEMORY_COUNTERS pmc;
|
||||
ZeroMemory(&pmc, sizeof(pmc));
|
||||
pmc.cb = sizeof(pmc);
|
||||
if (GetProcessMemoryInfo(hProc, &pmc, sizeof(pmc)))
|
||||
totalMB += (double)pmc.WorkingSetSize / (1024.0 * 1024.0);
|
||||
CloseHandle(hProc);
|
||||
}
|
||||
}
|
||||
} while (Process32Next(snap, &entry));
|
||||
}
|
||||
CloseHandle(snap);
|
||||
return totalMB;
|
||||
#elif defined(__APPLE__)
|
||||
// macOS: use pgrep to find hushd PIDs, then read RSS via proc_pidinfo
|
||||
FILE* fp = popen("pgrep -x hushd", "r");
|
||||
if (!fp) return 0.0;
|
||||
double totalMB = 0.0;
|
||||
char line[64];
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
pid_t pid = (pid_t)atoi(line);
|
||||
if (pid <= 0) continue;
|
||||
struct mach_task_basic_info info;
|
||||
mach_msg_type_number_t cnt = MACH_TASK_BASIC_INFO_COUNT;
|
||||
// We can't directly task_for_pid without entitlements, so fall back to ps
|
||||
char cmd[128];
|
||||
snprintf(cmd, sizeof(cmd), "ps -o rss= -p %d", pid);
|
||||
FILE* ps = popen(cmd, "r");
|
||||
if (ps) {
|
||||
char rssLine[64];
|
||||
if (fgets(rssLine, sizeof(rssLine), ps)) {
|
||||
long rssKB = atol(rssLine);
|
||||
if (rssKB > 0) totalMB += (double)rssKB / 1024.0;
|
||||
}
|
||||
pclose(ps);
|
||||
}
|
||||
}
|
||||
pclose(fp);
|
||||
return totalMB;
|
||||
#else
|
||||
// Linux: iterate /proc/<pid>/comm looking for "hushd"
|
||||
DIR* procDir = opendir("/proc");
|
||||
if (!procDir) return 0.0;
|
||||
double totalMB = 0.0;
|
||||
pid_t selfPid = getpid();
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(procDir)) != nullptr) {
|
||||
// Only numeric directories (PIDs)
|
||||
if (entry->d_name[0] < '0' || entry->d_name[0] > '9') continue;
|
||||
pid_t pid = (pid_t)atoi(entry->d_name);
|
||||
if (pid <= 0 || pid == selfPid) continue;
|
||||
|
||||
// Read comm (process name)
|
||||
char commPath[64];
|
||||
snprintf(commPath, sizeof(commPath), "/proc/%d/comm", pid);
|
||||
FILE* cf = fopen(commPath, "r");
|
||||
if (!cf) continue;
|
||||
char comm[256] = {};
|
||||
if (fgets(comm, sizeof(comm), cf)) {
|
||||
// Strip trailing newline
|
||||
size_t len = strlen(comm);
|
||||
if (len > 0 && comm[len - 1] == '\n') comm[len - 1] = '\0';
|
||||
}
|
||||
fclose(cf);
|
||||
|
||||
if (strcmp(comm, "hushd") != 0) continue;
|
||||
|
||||
// Read VmRSS from /proc/<pid>/status
|
||||
char statusPath[64];
|
||||
snprintf(statusPath, sizeof(statusPath), "/proc/%d/status", pid);
|
||||
FILE* sf = fopen(statusPath, "r");
|
||||
if (!sf) continue;
|
||||
char sline[256];
|
||||
while (fgets(sline, sizeof(sline), sf)) {
|
||||
if (strncmp(sline, "VmRSS:", 6) == 0) {
|
||||
long val = 0;
|
||||
if (sscanf(sline + 6, "%ld", &val) == 1)
|
||||
totalMB += (double)val / 1024.0; // kB -> MB
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(sf);
|
||||
}
|
||||
closedir(procDir);
|
||||
return totalMB;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user